feat(phase3): worker container + event bus + scheduled jobs

apps/worker:
- BullMQ nightly scheduler (cron 0 3 * * *)
- Redis Streams consumer-group per wired/active project
- Persists events to Event model

schema:
- Event model (streamId unique, project + type indexed)
This commit is contained in:
Semih
2026-05-13 11:03:58 +00:00
parent 5ef1597842
commit b9709ed07e
11 changed files with 457 additions and 2 deletions

View File

@@ -79,6 +79,20 @@ model Project {
updatedAt DateTime @updatedAt
}
model Event {
id String @id @default(cuid())
streamId String @unique
projectKey String
eventType String
version Int @default(1)
payload Json
occurredAt DateTime
receivedAt DateTime @default(now())
@@index([projectKey, occurredAt])
@@index([eventType])
}
model AuditLog {
id String @id @default(cuid())
actorUserId String?

File diff suppressed because one or more lines are too long

26
apps/worker/Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM node:22-alpine AS base
RUN apk add --no-cache libc6-compat openssl
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@9.12.0 --activate
# ---- builder: install everything (need apps/web prisma client) ----
FROM base AS builder
ENV NODE_ENV=development
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml turbo.json ./
COPY apps/web/package.json apps/web/package.json
COPY apps/worker/package.json apps/worker/package.json
RUN pnpm install --frozen-lockfile --prod=false || pnpm install --prod=false
COPY . .
WORKDIR /app/apps/web
RUN npx --no-install prisma generate
# ---- runner ----
FROM base AS runner
ENV NODE_ENV=production
WORKDIR /app
COPY --from=builder /app/package.json /app/pnpm-workspace.yaml /app/turbo.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/apps/web ./apps/web
COPY --from=builder /app/apps/worker ./apps/worker
CMD ["pnpm","--filter","@panel/worker","start"]

21
apps/worker/package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "@panel/worker",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "tsx watch src/index.ts",
"start": "tsx src/index.ts",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@panel/web": "workspace:*",
"@prisma/client": "^5.22.0",
"bullmq": "^5.34.0",
"ioredis": "^5.4.1",
"tsx": "^4.19.2"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.6.0"
}
}

View File

@@ -0,0 +1,94 @@
import { redis } from "../redis";
import { prisma } from "../db";
const GROUP = "panel";
const CONSUMER = `panel-worker-${process.env.HOSTNAME ?? "1"}`;
type RawEvent = {
event_type: string;
version?: string | number;
occurred_at?: string;
payload?: string;
};
async function ensureGroup(stream: string) {
try {
await redis.xgroup("CREATE", stream, GROUP, "0", "MKSTREAM");
console.log(`[event-bus] group created for ${stream}`);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (!msg.includes("BUSYGROUP")) throw e;
}
}
async function consume(stream: string, projectKey: string) {
await ensureGroup(stream);
while (true) {
try {
const res = await redis.xreadgroup(
"GROUP", GROUP, CONSUMER,
"COUNT", 32,
"BLOCK", 15000,
"STREAMS", stream, ">",
) as Array<[string, Array<[string, string[]]>]> | null;
if (!res) continue;
for (const [, entries] of res) {
for (const [streamId, fields] of entries) {
const obj = fieldsToObj(fields);
try {
await persist(streamId, projectKey, obj);
await redis.xack(stream, GROUP, streamId);
} catch (err) {
console.error(`[event-bus] persist failed ${stream} ${streamId}:`, err);
}
}
}
} catch (err) {
console.error(`[event-bus] xreadgroup error for ${stream}:`, err);
await new Promise((r) => setTimeout(r, 2000));
}
}
}
function fieldsToObj(fields: string[]): RawEvent {
const o: Record<string, string> = {};
for (let i = 0; i + 1 < fields.length; i += 2) o[fields[i]] = fields[i + 1];
return o as RawEvent;
}
async function persist(streamId: string, projectKey: string, raw: RawEvent) {
let payload: unknown = null;
try {
payload = raw.payload ? JSON.parse(raw.payload) : null;
} catch {
payload = raw.payload ?? null;
}
await prisma.event.create({
data: {
streamId,
projectKey,
eventType: raw.event_type ?? "unknown",
version: raw.version ? Number(raw.version) : 1,
occurredAt: raw.occurred_at ? new Date(raw.occurred_at) : new Date(),
payload: payload as object,
},
});
}
export async function startEventBus() {
const projects = await prisma.project.findMany({
where: { status: { in: ["wired", "active"] } },
select: { key: true },
});
if (projects.length === 0) {
console.log("[event-bus] no active projects; idle");
return;
}
for (const p of projects) {
const stream = `${p.key}:events`;
console.log(`[event-bus] consuming ${stream}`);
void consume(stream, p.key);
}
}

3
apps/worker/src/db.ts Normal file
View File

@@ -0,0 +1,3 @@
import { PrismaClient } from "@prisma/client";
export const prisma = new PrismaClient({ log: ["error"] });

32
apps/worker/src/index.ts Normal file
View File

@@ -0,0 +1,32 @@
import { startEventBus } from "./consumers/event-bus";
import { startScheduledJobs } from "./schedulers/nightly";
import { redis } from "./redis";
import { prisma } from "./db";
async function main() {
console.log("[worker] starting…");
await redis.ping();
console.log("[worker] redis ok");
await prisma.$queryRaw`SELECT 1`;
console.log("[worker] panel-db ok");
await startScheduledJobs();
await startEventBus();
console.log("[worker] up.");
}
const shutdown = async (sig: string) => {
console.log(`[worker] ${sig} — shutting down`);
await prisma.$disconnect().catch(() => {});
await redis.quit().catch(() => {});
process.exit(0);
};
process.on("SIGTERM", () => void shutdown("SIGTERM"));
process.on("SIGINT", () => void shutdown("SIGINT"));
main().catch((e) => {
console.error("[worker] fatal:", e);
process.exit(1);
});

13
apps/worker/src/redis.ts Normal file
View File

@@ -0,0 +1,13 @@
import IORedis from "ioredis";
const url = process.env.REDIS_URL;
if (!url) throw new Error("REDIS_URL not set");
export const redis = new IORedis(url, {
maxRetriesPerRequest: null,
enableReadyCheck: true,
});
redis.on("error", (err) => {
console.error("[redis]", err.message);
});

View File

@@ -0,0 +1,30 @@
import { Queue, Worker, type Job } from "bullmq";
import { redis } from "../redis";
import { prisma } from "../db";
const QUEUE = "nightly";
const queue = new Queue(QUEUE, { connection: redis });
async function nightlyRefresh(_job: Job) {
// Placeholder: when materialized views are added (Phase 4+), refresh them here.
const projectCount = await prisma.project.count();
console.log(`[nightly] refresh run at ${new Date().toISOString()}${projectCount} projects`);
return { ok: true, projects: projectCount };
}
export async function startScheduledJobs() {
// BullMQ repeat: every day at 03:00 server time
await queue.upsertJobScheduler(
"nightly-refresh",
{ pattern: "0 3 * * *" },
{
name: "nightly-refresh",
data: {},
opts: { removeOnComplete: 50, removeOnFail: 50 },
},
);
new Worker(QUEUE, nightlyRefresh, { connection: redis, concurrency: 1 });
console.log("[scheduler] nightly job armed (0 3 * * *)");
}

16
apps/worker/tsconfig.json Normal file
View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "esnext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"resolveJsonModule": true,
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"isolatedModules": true,
"lib": ["ES2022"],
"types": ["node"]
},
"include": ["src/**/*.ts"]
}

209
pnpm-lock.yaml generated
View File

@@ -8,6 +8,9 @@ importers:
.:
devDependencies:
tsx:
specifier: ^4.19.2
version: 4.21.0
turbo:
specifier: ^2.5.0
version: 2.9.12
@@ -115,6 +118,31 @@ importers:
specifier: ^5.6.0
version: 5.9.3
apps/worker:
dependencies:
'@panel/web':
specifier: workspace:*
version: link:../web
'@prisma/client':
specifier: ^5.22.0
version: 5.22.0(prisma@5.22.0)
bullmq:
specifier: ^5.34.0
version: 5.76.8
ioredis:
specifier: ^5.4.1
version: 5.10.1
tsx:
specifier: ^4.19.2
version: 4.21.0
devDependencies:
'@types/node':
specifier: ^22.0.0
version: 22.19.19
typescript:
specifier: ^5.6.0
version: 5.9.3
packages:
'@alloc/quick-lru@5.2.0':
@@ -750,6 +778,9 @@ packages:
'@types/node':
optional: true
'@ioredis/commands@1.5.1':
resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==}
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -776,6 +807,36 @@ packages:
'@cfworker/json-schema':
optional: true
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
cpu: [arm64]
os: [darwin]
'@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==}
cpu: [x64]
os: [darwin]
'@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==}
cpu: [arm64]
os: [linux]
'@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==}
cpu: [arm]
os: [linux]
'@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==}
cpu: [x64]
os: [linux]
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==}
cpu: [x64]
os: [win32]
'@mswjs/interceptors@0.41.9':
resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==}
engines: {node: '>=18'}
@@ -1421,6 +1482,10 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
bullmq@5.76.8:
resolution: {integrity: sha512-v3WTwA8diFtsADaJ8eK2ozyi2CYK9rDZCeoKF+dIPF/MUL8HxAOa3H72Gmu1lC4yKlho6t1PwNr/QpDVqaNEZQ==}
engines: {node: '>=12.22.0'}
bundle-name@4.1.0:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'}
@@ -1474,6 +1539,10 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
cmdk@1.1.1:
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
peerDependencies:
@@ -1534,6 +1603,10 @@ packages:
typescript:
optional: true
cron-parser@4.9.0:
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
engines: {node: '>=12.0.0'}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -1633,6 +1706,10 @@ packages:
defu@6.1.7:
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'}
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
@@ -1936,6 +2013,10 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
ioredis@5.10.1:
resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==}
engines: {node: '>=12.22.0'}
ip-address@10.2.0:
resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
engines: {node: '>= 12'}
@@ -2147,6 +2228,12 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
lodash.isarguments@3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
log-symbols@6.0.0:
resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
engines: {node: '>=18'}
@@ -2159,6 +2246,10 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
luxon@3.7.2:
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
engines: {node: '>=12'}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@@ -2211,6 +2302,13 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
msgpackr-extract@3.0.3:
resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==}
hasBin: true
msgpackr@2.0.1:
resolution: {integrity: sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==}
msw@2.14.6:
resolution: {integrity: sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg==}
engines: {node: '>=18'}
@@ -2265,6 +2363,9 @@ packages:
sass:
optional: true
node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
@@ -2274,6 +2375,10 @@ packages:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
node-gyp-build-optional-packages@5.2.2:
resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
hasBin: true
node-releases@2.0.44:
resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==}
@@ -2485,6 +2590,14 @@ packages:
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
redis-errors@1.2.0:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
redis-parser@3.0.0:
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
engines: {node: '>=4'}
redux-thunk@3.1.0:
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
peerDependencies:
@@ -2621,6 +2734,9 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
standard-as-callback@2.1.0:
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
@@ -3422,6 +3538,8 @@ snapshots:
optionalDependencies:
'@types/node': 22.19.19
'@ioredis/commands@1.5.1': {}
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -3463,6 +3581,24 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
optional: true
'@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
optional: true
'@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
optional: true
'@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
optional: true
'@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
optional: true
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
optional: true
'@mswjs/interceptors@0.41.9':
dependencies:
'@open-draft/deferred-promise': 2.2.0
@@ -3982,6 +4118,17 @@ snapshots:
node-releases: 2.0.44
update-browserslist-db: 1.2.3(browserslist@4.28.2)
bullmq@5.76.8:
dependencies:
cron-parser: 4.9.0
ioredis: 5.10.1
msgpackr: 2.0.1
node-abort-controller: 3.1.1
semver: 7.8.0
tslib: 2.8.1
transitivePeerDependencies:
- supports-color
bundle-name@4.1.0:
dependencies:
run-applescript: 7.1.0
@@ -4026,6 +4173,8 @@ snapshots:
clsx@2.1.1: {}
cluster-key-slot@1.1.2: {}
cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6)
@@ -4076,6 +4225,10 @@ snapshots:
optionalDependencies:
typescript: 5.9.3
cron-parser@4.9.0:
dependencies:
luxon: 3.7.2
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -4147,6 +4300,8 @@ snapshots:
defu@6.1.7: {}
denque@2.1.0: {}
depd@2.0.0: {}
detect-libc@2.1.2: {}
@@ -4486,6 +4641,20 @@ snapshots:
internmap@2.0.3: {}
ioredis@5.10.1:
dependencies:
'@ioredis/commands': 1.5.1
cluster-key-slot: 1.1.2
debug: 4.4.3
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
ip-address@10.2.0: {}
ipaddr.js@1.9.1: {}
@@ -4621,6 +4790,10 @@ snapshots:
lines-and-columns@1.2.4: {}
lodash.defaults@4.2.0: {}
lodash.isarguments@3.1.0: {}
log-symbols@6.0.0:
dependencies:
chalk: 5.6.2
@@ -4634,6 +4807,8 @@ snapshots:
dependencies:
react: 19.2.6
luxon@3.7.2: {}
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -4671,6 +4846,22 @@ snapshots:
ms@2.1.3: {}
msgpackr-extract@3.0.3:
dependencies:
node-gyp-build-optional-packages: 5.2.2
optionalDependencies:
'@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3
'@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3
'@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3
'@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3
'@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3
'@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3
optional: true
msgpackr@2.0.1:
optionalDependencies:
msgpackr-extract: 3.0.3
msw@2.14.6(@types/node@22.19.19)(typescript@5.9.3):
dependencies:
'@inquirer/confirm': 6.0.13(@types/node@22.19.19)
@@ -4733,6 +4924,8 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
node-abort-controller@3.1.1: {}
node-domexception@1.0.0: {}
node-fetch@3.3.2:
@@ -4741,6 +4934,11 @@ snapshots:
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
node-gyp-build-optional-packages@5.2.2:
dependencies:
detect-libc: 2.1.2
optional: true
node-releases@2.0.44: {}
npm-run-path@4.0.1:
@@ -4957,6 +5155,12 @@ snapshots:
- '@types/react'
- redux
redis-errors@1.2.0: {}
redis-parser@3.0.0:
dependencies:
redis-errors: 1.2.0
redux-thunk@3.1.0(redux@5.0.1):
dependencies:
redux: 5.0.1
@@ -5006,8 +5210,7 @@ snapshots:
semver@6.3.1: {}
semver@7.8.0:
optional: true
semver@7.8.0: {}
send@1.2.1:
dependencies:
@@ -5162,6 +5365,8 @@ snapshots:
source-map@0.6.1: {}
standard-as-callback@2.1.0: {}
statuses@2.0.2: {}
stdin-discarder@0.2.2: {}