feat: add nestjs replacement, remove nestjs (#285)
* feat: add nestjs replacement, remove nestjs * fix: format issues * fix: dependency issues * fix: dependency issues * fix: format issue * fix: wrong channel used for logging channel
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/tasks": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@trpc/client": "next",
|
||||
"@trpc/server": "next",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
".": "./index.ts",
|
||||
"./types": "./src/types.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
|
||||
1
packages/common/src/types.ts
Normal file
1
packages/common/src/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type MaybePromise<T> = T | Promise<T>;
|
||||
@@ -22,7 +22,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ioredis": "5.3.2",
|
||||
"@homarr/log": "workspace:^"
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/db": "workspace:^",
|
||||
"@homarr/common": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -1,40 +1,13 @@
|
||||
import { Redis } from "ioredis";
|
||||
import superjson from "superjson";
|
||||
import { createQueueChannel, createSubPubChannel } from "./lib/channel";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
const subscriber = new Redis();
|
||||
const publisher = new Redis();
|
||||
const lastDataClient = new Redis();
|
||||
|
||||
const createChannel = <TData>(name: string) => {
|
||||
return {
|
||||
subscribe: (callback: (data: TData) => void) => {
|
||||
void lastDataClient.get(`last-${name}`).then((data) => {
|
||||
if (data) {
|
||||
callback(superjson.parse(data));
|
||||
}
|
||||
});
|
||||
void subscriber.subscribe(name, (err) => {
|
||||
if (!err) {
|
||||
return;
|
||||
}
|
||||
logger.error(
|
||||
`Error with channel '${name}': ${err.name} (${err.message})`,
|
||||
);
|
||||
});
|
||||
subscriber.on("message", (channel, message) => {
|
||||
if (channel !== name) return;
|
||||
|
||||
callback(superjson.parse(message));
|
||||
});
|
||||
},
|
||||
publish: async (data: TData) => {
|
||||
await lastDataClient.set(`last-${name}`, superjson.stringify(data));
|
||||
await publisher.publish(name, superjson.stringify(data));
|
||||
},
|
||||
};
|
||||
};
|
||||
export const exampleChannel = createSubPubChannel<{ message: string }>(
|
||||
"example",
|
||||
);
|
||||
export const queueChannel = createQueueChannel<{
|
||||
name: string;
|
||||
executionDate: Date;
|
||||
data: unknown;
|
||||
}>("common-queue");
|
||||
|
||||
export interface LoggerMessage {
|
||||
message: string;
|
||||
@@ -42,6 +15,4 @@ export interface LoggerMessage {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export const loggingChannel = createChannel<LoggerMessage>("logging");
|
||||
|
||||
export const exampleChannel = createChannel<{ message: string }>("example");
|
||||
export const loggingChannel = createSubPubChannel<LoggerMessage>("logging");
|
||||
|
||||
116
packages/redis/src/lib/channel.ts
Normal file
116
packages/redis/src/lib/channel.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import superjson from "superjson";
|
||||
|
||||
import { createId } from "@homarr/db";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { createRedisConnection } from "./connection";
|
||||
|
||||
const subscriber = createRedisConnection(); // Used for subscribing to channels - after subscribing it can only be used for subscribing
|
||||
const publisher = createRedisConnection();
|
||||
const lastDataClient = createRedisConnection();
|
||||
|
||||
/**
|
||||
* Creates a new pub/sub channel.
|
||||
* @param name name of the channel
|
||||
* @returns pub/sub channel object
|
||||
*/
|
||||
export const createSubPubChannel = <TData>(name: string) => {
|
||||
const lastChannelName = `pubSub:last:${name}`;
|
||||
const channelName = `pubSub:${name}`;
|
||||
return {
|
||||
/**
|
||||
* Subscribes to the channel and calls the callback with the last data saved - when present.
|
||||
* @param callback callback function to be called when new data is published
|
||||
*/
|
||||
subscribe: (callback: (data: TData) => void) => {
|
||||
void lastDataClient.get(lastChannelName).then((data) => {
|
||||
if (data) {
|
||||
callback(superjson.parse(data));
|
||||
}
|
||||
});
|
||||
void subscriber.subscribe(channelName, (err) => {
|
||||
if (!err) {
|
||||
return;
|
||||
}
|
||||
logger.error(
|
||||
`Error with channel '${channelName}': ${err.name} (${err.message})`,
|
||||
);
|
||||
});
|
||||
subscriber.on("message", (channel, message) => {
|
||||
if (channel !== channelName) return;
|
||||
|
||||
callback(superjson.parse(message));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Publish data to the channel with last data saved.
|
||||
* @param data data to be published
|
||||
*/
|
||||
publish: async (data: TData) => {
|
||||
await lastDataClient.set(lastChannelName, superjson.stringify(data));
|
||||
await publisher.publish(channelName, superjson.stringify(data));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const queueClient = createRedisConnection();
|
||||
|
||||
type WithId<TItem> = TItem & { _id: string };
|
||||
|
||||
/**
|
||||
* Creates a queue channel to store and manage queue executions.
|
||||
* @param name name of the queue channel
|
||||
* @returns queue channel object
|
||||
*/
|
||||
export const createQueueChannel = <TItem>(name: string) => {
|
||||
const queueChannelName = `queue:${name}`;
|
||||
const getData = async () => {
|
||||
const data = await queueClient.get(queueChannelName);
|
||||
return data ? superjson.parse<WithId<TItem>[]>(data) : [];
|
||||
};
|
||||
const setData = async (data: WithId<TItem>[]) => {
|
||||
await queueClient.set(queueChannelName, superjson.stringify(data));
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Add a new queue execution.
|
||||
* @param data data to be stored in the queue execution to run it later
|
||||
*/
|
||||
add: async (data: TItem) => {
|
||||
const items = await getData();
|
||||
items.push({ _id: createId(), ...data });
|
||||
await setData(items);
|
||||
},
|
||||
/**
|
||||
* Get all queue executions.
|
||||
*/
|
||||
all: getData,
|
||||
/**
|
||||
* Get a queue execution by its id.
|
||||
* @param id id of the queue execution (stored under _id key)
|
||||
* @returns queue execution or undefined if not found
|
||||
*/
|
||||
byId: async (id: string) => {
|
||||
const items = await getData();
|
||||
return items.find((item) => item._id === id);
|
||||
},
|
||||
/**
|
||||
* Filters the queue executions by a given filter function.
|
||||
* @param filter callback function that returns true if the item should be included in the result
|
||||
* @returns filtered queue executions
|
||||
*/
|
||||
filter: async (filter: (item: WithId<TItem>) => boolean) => {
|
||||
const items = await getData();
|
||||
return items.filter(filter);
|
||||
},
|
||||
/**
|
||||
* Marks an queue execution as done, by deleting it.
|
||||
* @param id id of the queue execution (stored under _id key)
|
||||
*/
|
||||
markAsDone: async (id: string) => {
|
||||
const items = await getData();
|
||||
await setData(items.filter((item) => item._id !== id));
|
||||
},
|
||||
};
|
||||
};
|
||||
7
packages/redis/src/lib/connection.ts
Normal file
7
packages/redis/src/lib/connection.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Redis } from "ioredis";
|
||||
|
||||
/**
|
||||
* Creates a new Redis connection
|
||||
* @returns redis client
|
||||
*/
|
||||
export const createRedisConnection = () => new Redis();
|
||||
@@ -33,12 +33,13 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/form": "workspace:^0.1.0",
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/modals": "workspace:^0.1.0",
|
||||
"@homarr/notifications": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ComponentType } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import type { Loader } from "next/dynamic";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import { Loader as UiLoader } from "@homarr/ui";
|
||||
@@ -14,8 +14,8 @@ import * as weather from "./weather";
|
||||
export { reduceWidgetOptionsWithDefaultValues } from "./options";
|
||||
|
||||
export { WidgetEditModal } from "./modals/widget-edit-modal";
|
||||
export { GlobalItemServerDataRunner } from "./server/runner";
|
||||
export { useServerDataFor } from "./server/provider";
|
||||
export { GlobalItemServerDataRunner } from "./server/runner";
|
||||
|
||||
export const widgetImports = {
|
||||
clock,
|
||||
|
||||
Reference in New Issue
Block a user