feat: add redis (#242)
* feat: add refis * feat: add redis package Co-authored-by: Meier Lukas <meierschlumpf@gmail.com> * feat: add example docker compose, add redis connection in package * fix: usage of client after subscribe * feat: add logger for redis * refactor: format files Co-authored-by: Meier Lukas <meierschlumpf@gmail.com> --------- Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
@@ -4,4 +4,5 @@ node_modules
|
|||||||
npm-debug.log
|
npm-debug.log
|
||||||
README.md
|
README.md
|
||||||
.next
|
.next
|
||||||
.git
|
.git
|
||||||
|
development.docker-compose.yml
|
||||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -5,5 +5,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||||
"js/ts.implicitProjectConfig.experimentalDecorators": true
|
"js/ts.implicitProjectConfig.experimentalDecorators": true,
|
||||||
|
"cSpell.words": [
|
||||||
|
"superjson",
|
||||||
|
"homarr",
|
||||||
|
"trpc"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
FROM node:20-alpine AS base
|
FROM node:20-alpine AS base
|
||||||
|
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
RUN apk add --no-cache libc6-compat
|
RUN apk add --no-cache libc6-compat redis
|
||||||
RUN apk update
|
RUN apk update
|
||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -1,23 +1,38 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
import type { ChangeEvent } from "react";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { Stack, Text } from "@homarr/ui";
|
import { Button, Stack, Text, TextInput } from "@homarr/ui";
|
||||||
|
|
||||||
export const Test = () => {
|
export const Test = () => {
|
||||||
const [value, setValue] = useState<number>(0);
|
const [value, setValue] = useState("");
|
||||||
|
const [message, setMessage] = useState<string>("Hello, world!");
|
||||||
|
const { mutate } = clientApi.user.setMessage.useMutation();
|
||||||
clientApi.user.test.useSubscription(undefined, {
|
clientApi.user.test.useSubscription(undefined, {
|
||||||
onData(data) {
|
onData({ message }) {
|
||||||
setValue(data);
|
setMessage(message);
|
||||||
},
|
},
|
||||||
onError(err) {
|
onError(err) {
|
||||||
alert(err);
|
alert(err);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onChange = useCallback(
|
||||||
|
(event: ChangeEvent<HTMLInputElement>) => setValue(event.target.value),
|
||||||
|
[setValue],
|
||||||
|
);
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
mutate(value);
|
||||||
|
setValue("");
|
||||||
|
}, [mutate, value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text>This will change after one second: {value}</Text>
|
<TextInput label="Update message" value={value} onChange={onChange} />
|
||||||
|
<Button onClick={onClick}>Update message</Button>
|
||||||
|
<Text>This message gets through subscription: {message}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
14
development.docker-compose.yml
Normal file
14
development.docker-compose.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
############################
|
||||||
|
#
|
||||||
|
# This compose file is only for development.
|
||||||
|
# Do not use this in production.
|
||||||
|
#
|
||||||
|
############################
|
||||||
|
|
||||||
|
name: development-docker-compose
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
container_name: redis
|
||||||
|
image: redis
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/log": "workspace:^",
|
"@homarr/log": "workspace:^",
|
||||||
|
"@homarr/redis": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@trpc/client": "next",
|
"@trpc/client": "next",
|
||||||
"@trpc/server": "next",
|
"@trpc/server": "next",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { createSalt, hashPassword } from "@homarr/auth";
|
|||||||
import type { Database } from "@homarr/db";
|
import type { Database } from "@homarr/db";
|
||||||
import { createId, eq, schema } from "@homarr/db";
|
import { createId, eq, schema } from "@homarr/db";
|
||||||
import { users } from "@homarr/db/schema/sqlite";
|
import { users } from "@homarr/db/schema/sqlite";
|
||||||
|
import { exampleChannel } from "@homarr/redis";
|
||||||
import { validation, z } from "@homarr/validation";
|
import { validation, z } from "@homarr/validation";
|
||||||
|
|
||||||
import { createTRPCRouter, publicProcedure } from "../trpc";
|
import { createTRPCRouter, publicProcedure } from "../trpc";
|
||||||
@@ -106,13 +107,14 @@ export const userRouter = createTRPCRouter({
|
|||||||
})
|
})
|
||||||
.where(eq(users.id, input.userId));
|
.where(eq(users.id, input.userId));
|
||||||
}),
|
}),
|
||||||
|
setMessage: publicProcedure.input(z.string()).mutation(async ({ input }) => {
|
||||||
|
await exampleChannel.publish({ message: input });
|
||||||
|
}),
|
||||||
test: publicProcedure.subscription(() => {
|
test: publicProcedure.subscription(() => {
|
||||||
return observable<number>((emit) => {
|
return observable<{ message: string }>((emit) => {
|
||||||
let counter = 0;
|
exampleChannel.subscribe((message) => {
|
||||||
setInterval(() => {
|
emit.next(message);
|
||||||
counter = counter + 1;
|
});
|
||||||
emit.next(counter);
|
|
||||||
}, 1000);
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
1
packages/redis/index.ts
Normal file
1
packages/redis/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./src";
|
||||||
40
packages/redis/package.json
Normal file
40
packages/redis/package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "@homarr/redis",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"exports": {
|
||||||
|
".": "./index.ts"
|
||||||
|
},
|
||||||
|
"typesVersions": {
|
||||||
|
"*": {
|
||||||
|
"*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rm -rf .turbo node_modules",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ioredis": "5.3.2",
|
||||||
|
"@homarr/log": "workspace:^"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"typescript": "^5.4.3"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"@homarr/eslint-config/base"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"prettier": "@homarr/prettier-config"
|
||||||
|
}
|
||||||
39
packages/redis/src/index.ts
Normal file
39
packages/redis/src/index.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { Redis } from "ioredis";
|
||||||
|
import superjson from "superjson";
|
||||||
|
|
||||||
|
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 = createChannel<{ message: string }>("example");
|
||||||
8
packages/redis/tsconfig.json
Normal file
8
packages/redis/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@homarr/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||||
|
},
|
||||||
|
"include": ["*.ts", "src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
78
pnpm-lock.yaml
generated
78
pnpm-lock.yaml
generated
@@ -319,6 +319,9 @@ importers:
|
|||||||
'@homarr/log':
|
'@homarr/log':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../log
|
version: link:../log
|
||||||
|
'@homarr/redis':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../redis
|
||||||
'@homarr/validation':
|
'@homarr/validation':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../validation
|
version: link:../validation
|
||||||
@@ -601,6 +604,31 @@ importers:
|
|||||||
specifier: ^5.4.3
|
specifier: ^5.4.3
|
||||||
version: 5.4.3
|
version: 5.4.3
|
||||||
|
|
||||||
|
packages/redis:
|
||||||
|
dependencies:
|
||||||
|
'@homarr/log':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../log
|
||||||
|
ioredis:
|
||||||
|
specifier: 5.3.2
|
||||||
|
version: 5.3.2
|
||||||
|
devDependencies:
|
||||||
|
'@homarr/eslint-config':
|
||||||
|
specifier: workspace:^0.2.0
|
||||||
|
version: link:../../tooling/eslint
|
||||||
|
'@homarr/prettier-config':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../../tooling/prettier
|
||||||
|
'@homarr/tsconfig':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../../tooling/typescript
|
||||||
|
eslint:
|
||||||
|
specifier: ^8.57.0
|
||||||
|
version: 8.57.0
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.4.3
|
||||||
|
version: 5.4.3
|
||||||
|
|
||||||
packages/spotlight:
|
packages/spotlight:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@homarr/translation':
|
'@homarr/translation':
|
||||||
@@ -2145,6 +2173,10 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/@ioredis/commands@1.2.0:
|
||||||
|
resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@isaacs/cliui@8.0.2:
|
/@isaacs/cliui@8.0.2:
|
||||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -4782,6 +4814,11 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/cluster-key-slot@1.1.2:
|
||||||
|
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/color-convert@1.9.3:
|
/color-convert@1.9.3:
|
||||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6835,6 +6872,23 @@ packages:
|
|||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/ioredis@5.3.2:
|
||||||
|
resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==}
|
||||||
|
engines: {node: '>=12.22.0'}
|
||||||
|
dependencies:
|
||||||
|
'@ioredis/commands': 1.2.0
|
||||||
|
cluster-key-slot: 1.1.2
|
||||||
|
debug: 4.3.4
|
||||||
|
denque: 2.1.0
|
||||||
|
lodash.defaults: 4.2.0
|
||||||
|
lodash.isarguments: 3.1.0
|
||||||
|
redis-errors: 1.2.0
|
||||||
|
redis-parser: 3.0.0
|
||||||
|
standard-as-callback: 2.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ip-address@9.0.5:
|
/ip-address@9.0.5:
|
||||||
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
|
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
@@ -7384,10 +7438,18 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
|
|
||||||
|
/lodash.defaults@4.2.0:
|
||||||
|
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lodash.get@4.4.2:
|
/lodash.get@4.4.2:
|
||||||
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/lodash.isarguments@3.1.0:
|
||||||
|
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lodash.merge@4.6.2:
|
/lodash.merge@4.6.2:
|
||||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||||
|
|
||||||
@@ -8868,6 +8930,18 @@ packages:
|
|||||||
resolve: 1.22.8
|
resolve: 1.22.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/redis-errors@1.2.0:
|
||||||
|
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/redis-parser@3.0.0:
|
||||||
|
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
dependencies:
|
||||||
|
redis-errors: 1.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/reflect-metadata@0.2.1:
|
/reflect-metadata@0.2.1:
|
||||||
resolution: {integrity: sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==}
|
resolution: {integrity: sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==}
|
||||||
|
|
||||||
@@ -9389,6 +9463,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/standard-as-callback@2.1.0:
|
||||||
|
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/statuses@2.0.1:
|
/statuses@2.0.1:
|
||||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# Run migrations
|
# Run migrations
|
||||||
node ./db/migrate.mjs ./db/migrations
|
node ./db/migrate.mjs ./db/migrations
|
||||||
|
|
||||||
|
# Start Redis
|
||||||
|
redis-server &
|
||||||
|
|
||||||
# Run the nestjs backend
|
# Run the nestjs backend
|
||||||
node apps/nestjs/dist/main.mjs &
|
node apps/nestjs/dist/main.mjs &
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user