@cosmicdrift/kumiko-dev-server 0.126.0 → 0.127.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.127.0",
|
|
4
4
|
"description": "Development server bootstrap for Kumiko apps. Bundles the client, mints dev-JWTs, injects the resolved AppSchema, and seeds an admin. Not for production.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"kumiko-schema-check": "./bin/kumiko-schema-check.ts"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
54
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
53
|
+
"@cosmicdrift/kumiko-bundled-features": "0.127.0",
|
|
54
|
+
"@cosmicdrift/kumiko-framework": "0.127.0",
|
|
55
55
|
"ts-morph": "^28.0.0"
|
|
56
56
|
},
|
|
57
57
|
"publishConfig": {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createJobsFeature } from "@cosmicdrift/kumiko-bundled-features/jobs";
|
|
3
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
4
|
+
import { createRegistry, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
|
+
import { jobRunLoggerCallbacks } from "../job-run-logger";
|
|
6
|
+
|
|
7
|
+
// ponytail: callbacks shape only — no DB I/O; createTestDb needs TEST_DATABASE_URL (CI unit job has none).
|
|
8
|
+
const mockDb = {} as DbConnection;
|
|
9
|
+
|
|
10
|
+
describe("jobRunLoggerCallbacks", () => {
|
|
11
|
+
test("returns undefined when jobs feature is not registered", () => {
|
|
12
|
+
const registry = createRegistry([
|
|
13
|
+
defineFeature("empty", () => ({ handlers: {}, queries: {} })),
|
|
14
|
+
]);
|
|
15
|
+
expect(jobRunLoggerCallbacks(registry, mockDb)).toBeUndefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("returns logger callbacks when jobs feature is registered", () => {
|
|
19
|
+
const registry = createRegistry([createJobsFeature()]);
|
|
20
|
+
const callbacks = jobRunLoggerCallbacks(registry, mockDb);
|
|
21
|
+
expect(callbacks).toBeDefined();
|
|
22
|
+
expect(typeof callbacks?.onJobStart).toBe("function");
|
|
23
|
+
expect(typeof callbacks?.onJobComplete).toBe("function");
|
|
24
|
+
expect(typeof callbacks?.onJobFailed).toBe("function");
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createJobRunLogger } from "@cosmicdrift/kumiko-bundled-features/jobs";
|
|
2
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
+
import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import type { JobRunIn } from "@cosmicdrift/kumiko-framework/engine/types";
|
|
5
|
+
import { createJobRunner, type JobRunner } from "@cosmicdrift/kumiko-framework/jobs";
|
|
6
|
+
|
|
7
|
+
export function jobRunLoggerCallbacks(
|
|
8
|
+
registry: Registry,
|
|
9
|
+
db: DbConnection,
|
|
10
|
+
): ReturnType<typeof createJobRunLogger> | undefined {
|
|
11
|
+
if (registry.getFeature("jobs") === undefined) return undefined;
|
|
12
|
+
return createJobRunLogger({ db, registry });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Dev-server parity: consume api + worker lanes when jobs are registered. */
|
|
16
|
+
export async function startDevJobRunners(opts: {
|
|
17
|
+
readonly registry: Registry;
|
|
18
|
+
readonly db: DbConnection;
|
|
19
|
+
readonly context: Record<string, unknown>;
|
|
20
|
+
readonly redisUrl: string;
|
|
21
|
+
}): Promise<{ readonly runners: readonly JobRunner[]; readonly stop: () => Promise<void> }> {
|
|
22
|
+
const jobs = [...opts.registry.getAllJobs().values()];
|
|
23
|
+
if (opts.registry.getFeature("jobs") === undefined || jobs.length === 0) {
|
|
24
|
+
return { runners: [], stop: async () => {} };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const logger = createJobRunLogger({ db: opts.db, registry: opts.registry });
|
|
28
|
+
const runners: JobRunner[] = [];
|
|
29
|
+
const lanes = new Set(
|
|
30
|
+
jobs.map((j) => j.runIn).filter((lane): lane is JobRunIn => lane !== undefined),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
for (const lane of lanes) {
|
|
34
|
+
const jr = createJobRunner({
|
|
35
|
+
registry: opts.registry,
|
|
36
|
+
context: { ...opts.context, db: opts.db },
|
|
37
|
+
redisUrl: opts.redisUrl,
|
|
38
|
+
consumerLane: lane,
|
|
39
|
+
...logger,
|
|
40
|
+
});
|
|
41
|
+
await jr.start();
|
|
42
|
+
runners.push(jr);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
runners,
|
|
47
|
+
stop: async () => {
|
|
48
|
+
for (const runner of runners) await runner.stop();
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type TestStackOptions,
|
|
30
30
|
TestUsers,
|
|
31
31
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
32
|
+
import { startDevJobRunners } from "./boot/job-run-logger";
|
|
32
33
|
import { type ExtraRoutesSystemDeps, makeDispatchSystemWrite } from "./extra-routes-deps";
|
|
33
34
|
import { injectSchema } from "./inject-schema";
|
|
34
35
|
import { canResolveTailwindStylesheet, resolveTailwindCli } from "./resolve-tailwind-cli";
|
|
@@ -707,6 +708,14 @@ export async function createKumikoServer(
|
|
|
707
708
|
await stack.eventDispatcher.start();
|
|
708
709
|
}
|
|
709
710
|
|
|
711
|
+
const redisUrl = `redis://${stack.redis.redis.options.host}:${stack.redis.redis.options.port}/${stack.redis.redis.options.db}`;
|
|
712
|
+
const devJobRunners = await startDevJobRunners({
|
|
713
|
+
registry: stack.registry,
|
|
714
|
+
db: stack.db,
|
|
715
|
+
context: { db: stack.db, registry: stack.registry },
|
|
716
|
+
redisUrl,
|
|
717
|
+
});
|
|
718
|
+
|
|
710
719
|
// Dev user = TestUsers.admin. Demo features are openToAll but the
|
|
711
720
|
// auth-middleware still needs a valid JWT to let the request past.
|
|
712
721
|
// Nicht genutzt wenn `options.auth` gesetzt ist — dann macht der Client
|
|
@@ -977,6 +986,7 @@ export async function createKumikoServer(
|
|
|
977
986
|
if (stack.eventDispatcher) {
|
|
978
987
|
await stack.eventDispatcher.stop();
|
|
979
988
|
}
|
|
989
|
+
await devJobRunners.stop();
|
|
980
990
|
await stack.cleanup();
|
|
981
991
|
};
|
|
982
992
|
|
package/src/run-prod-app.ts
CHANGED
|
@@ -138,6 +138,7 @@ import { warnIfNonUtcServerTimeZone } from "@cosmicdrift/kumiko-framework/time";
|
|
|
138
138
|
import Redis from "ioredis";
|
|
139
139
|
import { applyBootSeeds } from "./boot/apply-boot-seeds";
|
|
140
140
|
import { type BootCrypto, resolveBootCrypto } from "./boot/boot-crypto";
|
|
141
|
+
import { jobRunLoggerCallbacks } from "./boot/job-run-logger";
|
|
141
142
|
import { ASSETS_DIR } from "./build-prod-bundle";
|
|
142
143
|
import { buildComposeAuthOptions, composeFeatures } from "./compose-features";
|
|
143
144
|
import { type ExtraRoutesSystemDeps, makeDispatchSystemWrite } from "./extra-routes-deps";
|
|
@@ -1133,6 +1134,7 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
|
|
|
1133
1134
|
const runSingleInstance = options.runSingleInstance ?? options.eventDispatcher?.disabled !== true;
|
|
1134
1135
|
const hasJobs = registry.getAllJobs().size > 0;
|
|
1135
1136
|
const queueNamePrefix = options.jobs?.queueNamePrefix;
|
|
1137
|
+
const jobLogger = jobRunLoggerCallbacks(registry, db);
|
|
1136
1138
|
const dispatcherTunables =
|
|
1137
1139
|
options.eventDispatcher?.pollIntervalMs !== undefined
|
|
1138
1140
|
? { pollIntervalMs: options.eventDispatcher.pollIntervalMs }
|
|
@@ -1141,20 +1143,18 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
|
|
|
1141
1143
|
const entrypoint: ApiEntrypoint | AllInOneEntrypoint = runSingleInstance
|
|
1142
1144
|
? createAllInOneEntrypoint({
|
|
1143
1145
|
...baseEntrypointOptions,
|
|
1144
|
-
// Worker-Seite liest die JobsBlock TOP-LEVEL (nicht nested `jobs` wie
|
|
1145
|
-
// die api-Seite); beide Lane-Runner ziehen redisUrl/prefix von hier.
|
|
1146
1146
|
redisUrl,
|
|
1147
|
+
...jobLogger,
|
|
1147
1148
|
...(queueNamePrefix !== undefined && { queueNamePrefix }),
|
|
1148
1149
|
...(!options.eventDispatcher?.disabled && { eventDispatcher: dispatcherTunables }),
|
|
1149
1150
|
})
|
|
1150
1151
|
: createApiEntrypoint({
|
|
1151
1152
|
...baseEntrypointOptions,
|
|
1152
|
-
// API-only: api-Lane-Jobs laufen lokal, ein dezidierter Worker fährt
|
|
1153
|
-
// worker-Lane + MSPs. createApiEntrypoint liest den nested `jobs`-Block.
|
|
1154
1153
|
...(hasJobs && {
|
|
1155
1154
|
jobs: {
|
|
1156
1155
|
redisUrl,
|
|
1157
1156
|
runLocalJobs: true,
|
|
1157
|
+
...jobLogger,
|
|
1158
1158
|
...(queueNamePrefix !== undefined && { queueNamePrefix }),
|
|
1159
1159
|
},
|
|
1160
1160
|
}),
|