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:
Meier Lukas
2024-04-04 18:31:40 +02:00
committed by GitHub
parent c82915c6dc
commit 1936596c04
38 changed files with 599 additions and 2197 deletions

View File

@@ -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");

View 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));
},
};
};

View File

@@ -0,0 +1,7 @@
import { Redis } from "ioredis";
/**
* Creates a new Redis connection
* @returns redis client
*/
export const createRedisConnection = () => new Redis();