@cosmicdrift/kumiko-server-runtime 0.403.0 → 0.404.1
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/dist/boot/worker-boot-core.js +17 -3
- package/dist/boot-options/process-signals.d.ts +6 -0
- package/dist/boot-options/process-signals.js +17 -0
- package/dist/run-prod-app-config.d.ts +5 -0
- package/dist/run-prod-app-config.js +9 -0
- package/dist/run-prod-app-connections.d.ts +2 -1
- package/dist/run-prod-app-connections.js +10 -3
- package/dist/run-prod-app-entrypoint.js +4 -2
- package/dist/run-prod-app-handle.js +8 -5
- package/package.json +8 -8
- package/src/changes.json +6 -0
|
@@ -9,13 +9,15 @@ import { assertRowLevelSecurityBootState, configureEntityFieldEncryption, knownT
|
|
|
9
9
|
import { createRegistry, validateBoot, } from "@cosmicdrift/kumiko-framework/engine";
|
|
10
10
|
import { createWorkerEntrypoint, } from "@cosmicdrift/kumiko-framework/entrypoint";
|
|
11
11
|
import { DEFAULT_ROW_LEVEL_SECURITY_MODE } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
|
|
12
|
+
import { maintenanceWindowFromEnv } from "@cosmicdrift/kumiko-framework/jobs";
|
|
12
13
|
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
13
14
|
import { assertKumikoSchemaCurrent, SchemaDriftError, } from "@cosmicdrift/kumiko-framework/migrations";
|
|
14
15
|
import { createEntityCache, createEventDedup, createIdempotencyGuard, } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
15
|
-
import { createRedisCacheSyncBus, redisClientOptionsFromEnv, } from "@cosmicdrift/kumiko-framework/redis";
|
|
16
|
+
import { createRedisCacheSyncBus, redisClientOptionsFromEnv, redisNamespaceFromEnv, } from "@cosmicdrift/kumiko-framework/redis";
|
|
16
17
|
import { warnIfNonUtcServerTimeZone } from "@cosmicdrift/kumiko-framework/time";
|
|
17
18
|
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
18
19
|
import { Redis } from "ioredis";
|
|
20
|
+
import { readShutdownDrainTimeoutMs } from "../boot-options/process-signals.js";
|
|
19
21
|
import { composeFeatures } from "../compose-features.js";
|
|
20
22
|
import { assertPiiBootInvariants } from "../pii-boot-gate.js";
|
|
21
23
|
import { readEnv, requireEnv } from "../require-env.js";
|
|
@@ -64,6 +66,9 @@ export async function bootWorkerProcess(options, envSource, profile) {
|
|
|
64
66
|
await ensureTemporalPolyfill();
|
|
65
67
|
const databaseUrl = requireEnv("DATABASE_URL", envSource, processName);
|
|
66
68
|
const redisUrl = requireEnv("REDIS_URL", envSource, processName);
|
|
69
|
+
const redisNamespace = redisNamespaceFromEnv(envSource);
|
|
70
|
+
const maintenanceWindow = maintenanceWindowFromEnv(envSource);
|
|
71
|
+
const shutdownDrainTimeoutMs = readShutdownDrainTimeoutMs(envSource);
|
|
67
72
|
const jwtSecretOrKeyring = loadJwtSecretOrKeyring(envSource);
|
|
68
73
|
const rowLevelSecurity = options.rowLevelSecurity ?? DEFAULT_ROW_LEVEL_SECURITY_MODE;
|
|
69
74
|
// MUST be composed with the same `includeBundled` as the API process.
|
|
@@ -102,10 +107,17 @@ export async function bootWorkerProcess(options, envSource, profile) {
|
|
|
102
107
|
await waitForKmsHealthy(options.kms, processName, databaseWaitDeadline, options.databaseWait);
|
|
103
108
|
}
|
|
104
109
|
const { db, closeDb, outsideTransactionDb, closeOutsideTransactionDb } = openDatabasePools(databaseUrl, envSource, rowLevelSecurity);
|
|
105
|
-
const redis = new Redis(redisUrl, {
|
|
110
|
+
const redis = new Redis(redisUrl, {
|
|
111
|
+
maxRetriesPerRequest: null,
|
|
112
|
+
keyPrefix: redisNamespace.keyPrefix,
|
|
113
|
+
});
|
|
106
114
|
const cacheSync = createRedisCacheSyncBus({
|
|
107
115
|
redisUrl,
|
|
108
116
|
clientOptions: redisClientOptionsFromEnv(envSource),
|
|
117
|
+
channelPrefix: redisNamespace.channelPrefix,
|
|
118
|
+
...(redisNamespace.channelSubscribePattern !== undefined && {
|
|
119
|
+
channelSubscribePattern: redisNamespace.channelSubscribePattern,
|
|
120
|
+
}),
|
|
109
121
|
});
|
|
110
122
|
try {
|
|
111
123
|
await waitForDatabase(db, {
|
|
@@ -195,6 +207,8 @@ export async function bootWorkerProcess(options, envSource, profile) {
|
|
|
195
207
|
eventDedup,
|
|
196
208
|
...(options.observability && { observability: options.observability }),
|
|
197
209
|
redisUrl,
|
|
210
|
+
redisNamespace,
|
|
211
|
+
maintenanceWindow,
|
|
198
212
|
...jobLogger,
|
|
199
213
|
...(options.jobs?.queueNamePrefix !== undefined && {
|
|
200
214
|
queueNamePrefix: options.jobs.queueNamePrefix,
|
|
@@ -225,7 +239,7 @@ export async function bootWorkerProcess(options, envSource, profile) {
|
|
|
225
239
|
...(isSecretsContext(extraContext["secrets"]) && { secrets: extraContext["secrets"] }),
|
|
226
240
|
...(bootCrypto.configCipher && { configCipher: bootCrypto.configCipher }),
|
|
227
241
|
close: async () => {
|
|
228
|
-
await entrypoint.stop();
|
|
242
|
+
await entrypoint.stop(shutdownDrainTimeoutMs !== undefined ? { drainTimeoutMs: shutdownDrainTimeoutMs } : {});
|
|
229
243
|
await closeDb();
|
|
230
244
|
await closeOutsideTransactionDb();
|
|
231
245
|
redis.disconnect();
|
|
@@ -5,6 +5,12 @@ export type SignalsOption = false | {
|
|
|
5
5
|
readonly drainTimeoutMs?: number;
|
|
6
6
|
};
|
|
7
7
|
export declare const DEFAULT_DRAIN_TIMEOUT_MS = 25000;
|
|
8
|
+
export declare const SHUTDOWN_DRAIN_TIMEOUT_ENV = "KUMIKO_SHUTDOWN_DRAIN_TIMEOUT_MS";
|
|
9
|
+
/** Shared apps scaled to zero by KEDA need the drain to outlast their longest job. Unset: undefined.
|
|
10
|
+
* An invalid value fails the boot instead of silently shortening the drain. */
|
|
11
|
+
export declare function readShutdownDrainTimeoutMs(env: Readonly<Record<string, string | undefined>>): number | undefined;
|
|
12
|
+
/** Explicit `signals.drainTimeoutMs` wins over the env variable, which wins over the default. */
|
|
13
|
+
export declare function resolveDrainTimeoutMs(explicit: number | undefined, fromEnv: number | undefined): number;
|
|
8
14
|
export type SignalProcess = {
|
|
9
15
|
on(signal: ShutdownSignal, listener: () => void): unknown;
|
|
10
16
|
off(signal: ShutdownSignal, listener: () => void): unknown;
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
// Kubernetes sends SIGKILL after terminationGracePeriodSeconds (default 30 s).
|
|
2
2
|
// Draining 25 s leaves room for the exit and the final log line before that.
|
|
3
3
|
export const DEFAULT_DRAIN_TIMEOUT_MS = 25_000;
|
|
4
|
+
export const SHUTDOWN_DRAIN_TIMEOUT_ENV = "KUMIKO_SHUTDOWN_DRAIN_TIMEOUT_MS";
|
|
5
|
+
/** Shared apps scaled to zero by KEDA need the drain to outlast their longest job. Unset: undefined.
|
|
6
|
+
* An invalid value fails the boot instead of silently shortening the drain. */
|
|
7
|
+
export function readShutdownDrainTimeoutMs(env) {
|
|
8
|
+
const raw = env[SHUTDOWN_DRAIN_TIMEOUT_ENV]?.trim();
|
|
9
|
+
if (raw === undefined || raw === "")
|
|
10
|
+
return undefined;
|
|
11
|
+
const parsed = Number(raw);
|
|
12
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
13
|
+
throw new Error(`[env] ${SHUTDOWN_DRAIN_TIMEOUT_ENV}="${raw}" must be a positive integer`);
|
|
14
|
+
}
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
/** Explicit `signals.drainTimeoutMs` wins over the env variable, which wins over the default. */
|
|
18
|
+
export function resolveDrainTimeoutMs(explicit, fromEnv) {
|
|
19
|
+
return explicit ?? fromEnv ?? DEFAULT_DRAIN_TIMEOUT_MS;
|
|
20
|
+
}
|
|
4
21
|
/** Registers SIGTERM/SIGINT: the first signal drains via `stop()` and exits
|
|
5
22
|
* 0, a drain that fails or outlives the timeout exits 1. Returns the
|
|
6
23
|
* uninstall function so `stop()` can drop the listeners again. */
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { loadJwtSecretOrKeyring } from "@cosmicdrift/kumiko-framework/api";
|
|
2
2
|
import type { RowLevelSecurityMode } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import { type FeatureDefinition, type Registry } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import { type MaintenanceWindow } from "@cosmicdrift/kumiko-framework/jobs";
|
|
4
5
|
import { type Logger } from "@cosmicdrift/kumiko-framework/logging";
|
|
6
|
+
import { type RedisNamespace } from "@cosmicdrift/kumiko-framework/redis";
|
|
5
7
|
import { resolveBootCrypto } from "./boot/boot-crypto.ts";
|
|
6
8
|
import { type BootOptionWiring } from "./boot-options/resolve-boot-options.ts";
|
|
7
9
|
import { resolveRateLimit } from "./rate-limit-defaults.ts";
|
|
@@ -18,6 +20,9 @@ export type ProdBootConfig = {
|
|
|
18
20
|
readonly databaseUrl: string;
|
|
19
21
|
readonly rowLevelSecurity: RowLevelSecurityMode;
|
|
20
22
|
readonly redisUrl: string;
|
|
23
|
+
readonly redisNamespace: RedisNamespace;
|
|
24
|
+
readonly maintenanceWindow: MaintenanceWindow;
|
|
25
|
+
readonly shutdownDrainTimeoutMs: number | undefined;
|
|
21
26
|
readonly jwtSecret: string;
|
|
22
27
|
readonly jwtSecretOrKeyring: ReturnType<typeof loadJwtSecretOrKeyring>;
|
|
23
28
|
readonly jwtIssuer: string | undefined;
|
|
@@ -3,9 +3,12 @@ import { SESSIONS_FEATURE } from "@cosmicdrift/kumiko-bundled-features/sessions"
|
|
|
3
3
|
import { loadJwtSecretOrKeyring, TRUSTED_PROXY_HOPS_ENV } from "@cosmicdrift/kumiko-framework/api";
|
|
4
4
|
import { createRegistry, } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
5
|
import { DEFAULT_ROW_LEVEL_SECURITY_MODE } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
|
|
6
|
+
import { maintenanceWindowFromEnv, } from "@cosmicdrift/kumiko-framework/jobs";
|
|
6
7
|
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
8
|
+
import { redisNamespaceFromEnv } from "@cosmicdrift/kumiko-framework/redis";
|
|
7
9
|
import { resolveBootCrypto } from "./boot/boot-crypto.js";
|
|
8
10
|
import { composeAppFeatures, validateComposedFeatures, withEnvDefaults, } from "./boot/shared-boot-steps.js";
|
|
11
|
+
import { readShutdownDrainTimeoutMs } from "./boot-options/process-signals.js";
|
|
9
12
|
import { resolveBootOptionWiring, } from "./boot-options/resolve-boot-options.js";
|
|
10
13
|
import { assertStackBootRequirements } from "./boot-options/stack-boot-requirements.js";
|
|
11
14
|
import { assertPiiBootInvariants } from "./pii-boot-gate.js";
|
|
@@ -61,6 +64,9 @@ export async function resolveProdBootConfig(options, decryptedEnvSource, log, re
|
|
|
61
64
|
// configured.
|
|
62
65
|
const databaseUrl = requireEnv("DATABASE_URL", envSource);
|
|
63
66
|
const redisUrl = requireEnv("REDIS_URL", envSource);
|
|
67
|
+
const redisNamespace = redisNamespaceFromEnv(envSource);
|
|
68
|
+
const maintenanceWindow = maintenanceWindowFromEnv(envSource);
|
|
69
|
+
const shutdownDrainTimeoutMs = readShutdownDrainTimeoutMs(envSource);
|
|
64
70
|
// JWT_SECRET stays mandatory (also the resolveAuthMail hmacSecret
|
|
65
71
|
// fallback below — a separate, non-rotating HMAC token family, not the
|
|
66
72
|
// session JWT). jwtSecretOrKeyring is the OPTIONAL upgrade: set
|
|
@@ -103,6 +109,9 @@ export async function resolveProdBootConfig(options, decryptedEnvSource, log, re
|
|
|
103
109
|
databaseUrl,
|
|
104
110
|
rowLevelSecurity: options.rowLevelSecurity ?? DEFAULT_ROW_LEVEL_SECURITY_MODE,
|
|
105
111
|
redisUrl,
|
|
112
|
+
redisNamespace,
|
|
113
|
+
maintenanceWindow,
|
|
114
|
+
shutdownDrainTimeoutMs,
|
|
106
115
|
jwtSecret,
|
|
107
116
|
jwtSecretOrKeyring,
|
|
108
117
|
jwtIssuer,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import type { Logger } from "@cosmicdrift/kumiko-framework/logging";
|
|
3
|
-
import { createRedisCacheSyncBus } from "@cosmicdrift/kumiko-framework/redis";
|
|
3
|
+
import { createRedisCacheSyncBus, type RedisNamespace } from "@cosmicdrift/kumiko-framework/redis";
|
|
4
4
|
import { Redis } from "ioredis";
|
|
5
5
|
import { type DatabaseWaitClock } from "./boot/wait-for-database.ts";
|
|
6
6
|
import type { RunProdAppOptions } from "./run-prod-app.ts";
|
|
@@ -11,6 +11,7 @@ export type ProdConnections = {
|
|
|
11
11
|
readonly outsideTransactionDb: DbConnection;
|
|
12
12
|
readonly closeOutsideTransactionDb: () => Promise<void>;
|
|
13
13
|
readonly redis: Redis;
|
|
14
|
+
readonly redisNamespace: RedisNamespace;
|
|
14
15
|
readonly cacheSync: ReturnType<typeof createRedisCacheSyncBus>;
|
|
15
16
|
};
|
|
16
17
|
export declare function connectProdDependencies(config: ProdBootConfig, log: Logger, registerAbortCleanup: RegisterBootAbortCleanup, databaseWait?: DatabaseWaitClock): Promise<ProdConnections>;
|
|
@@ -8,7 +8,7 @@ import { WAIT_FOR_DATABASE_DEADLINE_MS, waitForDatabase, } from "./boot/wait-for
|
|
|
8
8
|
// immediately. `new Redis(...)` connects eagerly, so this must stay AFTER the
|
|
9
9
|
// boot-mode exit.
|
|
10
10
|
export async function connectProdDependencies(config, log, registerAbortCleanup, databaseWait) {
|
|
11
|
-
const { bootKms, databaseUrl, redisUrl, envSource, rowLevelSecurity } = config;
|
|
11
|
+
const { bootKms, databaseUrl, redisUrl, redisNamespace, envSource, rowLevelSecurity } = config;
|
|
12
12
|
// KMS health gate: an app configured for crypto-shredding must not boot
|
|
13
13
|
// against an unreachable key store — a silent start would answer every
|
|
14
14
|
// PII read with 503. Runs BEFORE connections so an abort leaks nothing.
|
|
@@ -17,7 +17,10 @@ export async function connectProdDependencies(config, log, registerAbortCleanup,
|
|
|
17
17
|
await waitForKmsHealthy(bootKms.kms, "runProdApp", databaseWaitDeadline, databaseWait);
|
|
18
18
|
}
|
|
19
19
|
const pools = openDatabasePools(databaseUrl, envSource, rowLevelSecurity);
|
|
20
|
-
const redis = new Redis(redisUrl, {
|
|
20
|
+
const redis = new Redis(redisUrl, {
|
|
21
|
+
maxRetriesPerRequest: null,
|
|
22
|
+
keyPrefix: redisNamespace.keyPrefix,
|
|
23
|
+
});
|
|
21
24
|
registerAbortCleanup(pools.closeDb);
|
|
22
25
|
registerAbortCleanup(pools.closeOutsideTransactionDb);
|
|
23
26
|
registerAbortCleanup(() => redis.disconnect());
|
|
@@ -32,8 +35,12 @@ export async function connectProdDependencies(config, log, registerAbortCleanup,
|
|
|
32
35
|
const cacheSync = createRedisCacheSyncBus({
|
|
33
36
|
redisUrl,
|
|
34
37
|
clientOptions: redisClientOptionsFromEnv(envSource),
|
|
38
|
+
channelPrefix: redisNamespace.channelPrefix,
|
|
39
|
+
...(redisNamespace.channelSubscribePattern !== undefined && {
|
|
40
|
+
channelSubscribePattern: redisNamespace.channelSubscribePattern,
|
|
41
|
+
}),
|
|
35
42
|
});
|
|
36
|
-
return { ...pools, redis, cacheSync };
|
|
43
|
+
return { ...pools, redis, redisNamespace, cacheSync };
|
|
37
44
|
}
|
|
38
45
|
// Schema-drift gate (without drizzle, using kumiko/migrations). `kumiko
|
|
39
46
|
// schema apply` runs as a deploy step before container rollout. Boot only
|
|
@@ -54,10 +54,12 @@ export function buildProdEntrypointOptions(options, config, composed, connection
|
|
|
54
54
|
}),
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
function prodJobOptions(options, composed, connections) {
|
|
57
|
+
function prodJobOptions(options, composed, connections, config) {
|
|
58
58
|
const queueNamePrefix = options.jobs?.queueNamePrefix;
|
|
59
59
|
return {
|
|
60
60
|
...jobRunLoggerCallbacks(composed.registry, connections.db),
|
|
61
|
+
redisNamespace: connections.redisNamespace,
|
|
62
|
+
maintenanceWindow: config.maintenanceWindow,
|
|
61
63
|
...(queueNamePrefix !== undefined && { queueNamePrefix }),
|
|
62
64
|
...(options.jobs?.getActiveTenantIds !== undefined && {
|
|
63
65
|
getActiveTenantIds: options.jobs.getActiveTenantIds,
|
|
@@ -76,7 +78,7 @@ function prodJobOptions(options, composed, connections) {
|
|
|
76
78
|
export function createProdEntrypoint(options, config, composed, connections, entrypointOptions) {
|
|
77
79
|
const runSingleInstance = options.runSingleInstance ?? options.eventDispatcher?.disabled !== true;
|
|
78
80
|
const hasJobs = composed.registry.getAllJobs().size > 0;
|
|
79
|
-
const jobOptions = prodJobOptions(options, composed, connections);
|
|
81
|
+
const jobOptions = prodJobOptions(options, composed, connections, config);
|
|
80
82
|
const dispatcherTunables = options.eventDispatcher?.pollIntervalMs !== undefined
|
|
81
83
|
? { pollIntervalMs: options.eventDispatcher.pollIntervalMs }
|
|
82
84
|
: {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assertNoActiveRowLevelSecurityScope } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { flushEscapeHatchAudits } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
3
|
-
import { installShutdownSignals } from "./boot-options/process-signals.js";
|
|
3
|
+
import { installShutdownSignals, resolveDrainTimeoutMs } from "./boot-options/process-signals.js";
|
|
4
4
|
import { buildBunServeOptions, resolveDerivedMaxRequestBodySize } from "./bun-serve-options.js";
|
|
5
5
|
import { installFatalErrorHandlers } from "./fatal-error-handlers.js";
|
|
6
6
|
import { uninstallProcessLogger } from "./process-logger.js";
|
|
@@ -49,6 +49,10 @@ export function createProdAppHandle(input) {
|
|
|
49
49
|
// Same read-only demo gate as fetchHandler: upgrade requests are GETs, but the socket then writes.
|
|
50
50
|
const securedUpgradeFetch = withSecurityHeaders(config.bootOptionWiring.wrapFetch(async (req, env) => stripNoRouteMatchHeader(await entrypoint.app.fetch(req, env))), options.securityHeaders, cspScriptHashes);
|
|
51
51
|
const webSocketUpgradeFetch = async (req, env) => securedUpgradeFetch(req, env);
|
|
52
|
+
const explicitDrainTimeoutMs = options.signals === false ? undefined : options.signals?.drainTimeoutMs;
|
|
53
|
+
const drainTimeoutMs = resolveDrainTimeoutMs(explicitDrainTimeoutMs, config.shutdownDrainTimeoutMs);
|
|
54
|
+
// Without a configured budget the lifecycle keeps its own default for programmatic stop().
|
|
55
|
+
const jobDrainTimeoutMs = explicitDrainTimeoutMs ?? config.shutdownDrainTimeoutMs;
|
|
52
56
|
let uninstallShutdownSignals;
|
|
53
57
|
let uninstallFatalHandlers;
|
|
54
58
|
const handle = {
|
|
@@ -75,9 +79,7 @@ export function createProdAppHandle(input) {
|
|
|
75
79
|
uninstallShutdownSignals?.();
|
|
76
80
|
uninstallShutdownSignals = installShutdownSignals({
|
|
77
81
|
stop: () => handle.stop(),
|
|
78
|
-
|
|
79
|
-
drainTimeoutMs: options.signals.drainTimeoutMs,
|
|
80
|
-
}),
|
|
82
|
+
drainTimeoutMs,
|
|
81
83
|
});
|
|
82
84
|
uninstallFatalHandlers?.();
|
|
83
85
|
uninstallFatalHandlers = installFatalErrorHandlers({
|
|
@@ -90,7 +92,8 @@ export function createProdAppHandle(input) {
|
|
|
90
92
|
stop: async () => {
|
|
91
93
|
uninstallFatalHandlers?.();
|
|
92
94
|
uninstallFatalHandlers = undefined;
|
|
93
|
-
|
|
95
|
+
// Jobs finish before the HTTP grace period, the DB and Redis close (all below this line).
|
|
96
|
+
await entrypoint.stop(jobDrainTimeoutMs !== undefined ? { drainTimeoutMs: jobDrainTimeoutMs } : {});
|
|
94
97
|
if (handle.server)
|
|
95
98
|
await stopServerGracefully(handle.server);
|
|
96
99
|
await flushEscapeHatchAudits();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-server-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.404.1",
|
|
4
4
|
"description": "Production server-boot runtime for Kumiko apps: connections, schema-drift-gate, seeds, lifecycle, graceful shutdown. Symmetric to kumiko-dev-server's runDevApp, without dev/scaffold tooling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -96,12 +96,12 @@
|
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
100
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
101
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
102
|
-
"@cosmicdrift/kumiko-renderer-mail-html": "0.
|
|
103
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
104
|
-
"ioredis": "^5.
|
|
99
|
+
"@cosmicdrift/kumiko-bundled-features": "0.404.1",
|
|
100
|
+
"@cosmicdrift/kumiko-framework": "0.404.1",
|
|
101
|
+
"@cosmicdrift/kumiko-renderer": "0.404.1",
|
|
102
|
+
"@cosmicdrift/kumiko-renderer-mail-html": "0.404.1",
|
|
103
|
+
"@cosmicdrift/kumiko-types": "0.404.1",
|
|
104
|
+
"ioredis": "^5.11.1",
|
|
105
105
|
"temporal-polyfill": "^0.3.2",
|
|
106
106
|
"zod": "^4.6.7"
|
|
107
107
|
},
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"bun-types": "^1.4.4"
|
|
110
110
|
},
|
|
111
111
|
"devDependencies": {
|
|
112
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
112
|
+
"@cosmicdrift/kumiko-testing": "0.404.1",
|
|
113
113
|
"bun-types": "^1.4.4"
|
|
114
114
|
},
|
|
115
115
|
"publishConfig": {
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.404.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "runProdApp and runWorkerApp use the Redis prefix and drain running jobs on SIGTERM",
|
|
6
|
+
"detail": "Both boots pass the namespace to the Redis client, the cache-sync bus and the job runner, so enqueuer and consumer share one key space. KUMIKO_SHUTDOWN_DRAIN_TIMEOUT_MS (positive integer, invalid aborts the boot) sets the drain budget; signals.drainTimeoutMs wins, the default stays 25 s. Entrypoint stop() now stops every job lane fetching first, then waits for active jobs up to that budget before the database and Redis close; before, the lifecycle's fixed 40 s cut the wait short."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.399.0",
|
|
4
10
|
"type": "fix",
|