@cosmicdrift/kumiko-server-runtime 0.414.0 → 0.416.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.
|
@@ -20,6 +20,7 @@ import { readShutdownDrainTimeoutMs } from "../boot-options/process-signals.js";
|
|
|
20
20
|
import { composeFeatures } from "../compose-features.js";
|
|
21
21
|
import { assertPiiBootInvariants } from "../pii-boot-gate.js";
|
|
22
22
|
import { readEnv, requireEnv } from "../require-env.js";
|
|
23
|
+
import { resolveUnsubscribeWiring } from "../resolve-unsubscribe-wiring.js";
|
|
23
24
|
import { addConfigAccessorFactory, buildBootExtraContext, loggerFromExtraContext, } from "../run-prod-app-boot-context.js";
|
|
24
25
|
import { resolveBootCrypto } from "./boot-crypto.js";
|
|
25
26
|
import { jobRunLoggerCallbacks } from "./job-run-logger.js";
|
|
@@ -184,6 +185,13 @@ export async function bootWorkerProcess(options, envSource, profile) {
|
|
|
184
185
|
configureBlindIndexKey(options.blindIndexKey);
|
|
185
186
|
const bootLogger = loggerFromExtraContext(resolvedExtraContext);
|
|
186
187
|
warnIfRowLevelSecurityOff(rowLevelSecurity, bootLogger ?? createFallbackLogger(processName));
|
|
188
|
+
const { unsubscribeLinks } = resolveUnsubscribeWiring({
|
|
189
|
+
unsubscribe: options.unsubscribe,
|
|
190
|
+
publicOrigin: undefined,
|
|
191
|
+
mailBaseUrl: undefined,
|
|
192
|
+
envSource,
|
|
193
|
+
warn: (message) => (bootLogger ?? createFallbackLogger(processName)).warn(message),
|
|
194
|
+
});
|
|
187
195
|
const autoExtraContext = buildBootExtraContext({
|
|
188
196
|
db,
|
|
189
197
|
features,
|
|
@@ -196,6 +204,7 @@ export async function bootWorkerProcess(options, envSource, profile) {
|
|
|
196
204
|
...(options.kms && { kms: options.kms }),
|
|
197
205
|
...(profile.deliverQueuedInline === true && { deliverQueuedInline: true }),
|
|
198
206
|
...(bootLogger && { log: bootLogger }),
|
|
207
|
+
...(unsubscribeLinks && { unsubscribeLinks }),
|
|
199
208
|
});
|
|
200
209
|
const extraContext = addConfigAccessorFactory({ ...autoExtraContext, ...resolvedExtraContext }, registry);
|
|
201
210
|
const jobLogger = jobRunLoggerCallbacks(registry, db);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { PWA_CACHE_NAME_PREFIX } from "@cosmicdrift/kumiko-renderer/headless";
|
|
2
|
+
export { PWA_CACHE_NAME_PREFIX };
|
|
2
3
|
export type PwaServiceWorkerScriptInput = {
|
|
3
4
|
readonly cacheName: string;
|
|
4
5
|
readonly cacheScopePrefix: string;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// The worker is plain JS in a string because the server hands it out as is: no bundler step,
|
|
2
2
|
// no workbox, nothing loaded from outside the app's own origin.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { PWA_CACHE_NAME_PREFIX } from "@cosmicdrift/kumiko-renderer/headless";
|
|
4
|
+
export { PWA_CACHE_NAME_PREFIX };
|
|
5
|
+
// Cache names before scoping were `kumiko-pwa-<16hex>`; kept only so an update drops those precaches once.
|
|
6
|
+
// The pattern also matches the precache of another app on the same origin that still runs an old
|
|
7
|
+
// framework version; accepted, because that app only has to precache again.
|
|
8
|
+
const UNSCOPED_CACHE_NAME_PATTERN = /^kumiko-pwa-[0-9a-f]{16}$/;
|
|
6
9
|
const SERVICE_WORKER_BODY = `
|
|
7
10
|
const PASS_THROUGH_PATHS = ["/api", "/sse", "/auth", "/health", "/build-info.json", SERVICE_WORKER_PATH, "/_reload"];
|
|
8
11
|
const PASS_THROUGH_PREFIXES = ["/api/", "/sse/", "/auth/"];
|
|
@@ -49,7 +52,11 @@ async function deleteOldCaches() {
|
|
|
49
52
|
const names = await caches.keys();
|
|
50
53
|
await Promise.all(
|
|
51
54
|
names
|
|
52
|
-
.filter(
|
|
55
|
+
.filter(
|
|
56
|
+
(name) =>
|
|
57
|
+
(name.startsWith(CACHE_SCOPE_PREFIX) || UNSCOPED_CACHE_NAME_PATTERN.test(name)) &&
|
|
58
|
+
name !== CACHE_NAME,
|
|
59
|
+
)
|
|
53
60
|
.map((name) => caches.delete(name)),
|
|
54
61
|
);
|
|
55
62
|
}
|
|
@@ -111,6 +118,7 @@ export function buildPwaServiceWorkerScript(input) {
|
|
|
111
118
|
return [
|
|
112
119
|
`const CACHE_SCOPE_PREFIX = ${JSON.stringify(input.cacheScopePrefix)};`,
|
|
113
120
|
`const CACHE_NAME = ${JSON.stringify(input.cacheName)};`,
|
|
121
|
+
`const UNSCOPED_CACHE_NAME_PATTERN = ${UNSCOPED_CACHE_NAME_PATTERN.toString()};`,
|
|
114
122
|
`const SHELL_URL = ${JSON.stringify(input.shellUrl)};`,
|
|
115
123
|
`const SERVICE_WORKER_PATH = ${JSON.stringify(input.serviceWorkerPath)};`,
|
|
116
124
|
`const PRECACHE_URLS = ${JSON.stringify(input.precacheUrls)};`,
|
package/dist/pwa.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { escapeHtmlAttr, PWA_SERVICE_WORKER_META_NAME, } from "@cosmicdrift/kumiko-renderer/headless";
|
|
2
|
+
import { escapeHtmlAttr, PWA_CACHE_SCOPE_HASH_LENGTH, PWA_SERVICE_WORKER_META_NAME, } from "@cosmicdrift/kumiko-renderer/headless";
|
|
3
3
|
import { buildPwaServiceWorkerScript, PWA_CACHE_NAME_PREFIX } from "./pwa-service-worker-script.js";
|
|
4
4
|
const DEFAULT_PWA_BASE_PATH = "/";
|
|
5
5
|
const MANIFEST_FILE = "manifest.webmanifest";
|
|
@@ -93,7 +93,7 @@ function fileResponse(req, body, contentType) {
|
|
|
93
93
|
export function createPwaShell(options, build) {
|
|
94
94
|
assertValidPwaOptions(options);
|
|
95
95
|
const paths = pwaPathsOf(options);
|
|
96
|
-
const cacheScopePrefix = `${PWA_CACHE_NAME_PREFIX}${createHash("sha256").update(paths.shellUrl).digest("hex").slice(0,
|
|
96
|
+
const cacheScopePrefix = `${PWA_CACHE_NAME_PREFIX}${createHash("sha256").update(paths.shellUrl).digest("hex").slice(0, PWA_CACHE_SCOPE_HASH_LENGTH)}-`;
|
|
97
97
|
const cacheName = `${cacheScopePrefix}${computeCacheVersion(options, build)}`;
|
|
98
98
|
const precacheUrls = [
|
|
99
99
|
...new Set([
|
package/dist/run-worker-app.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { FileStorageBootOption } from "./boot-options/file-storage-option.t
|
|
|
16
16
|
import type { KmsMode } from "./boot-options/kms-mode.ts";
|
|
17
17
|
import type { MailBootOption } from "./boot-options/mail-option.ts";
|
|
18
18
|
import { type SystemWireDeps } from "./extra-routes-deps.ts";
|
|
19
|
+
import type { UnsubscribeOption } from "./resolve-unsubscribe-wiring.ts";
|
|
19
20
|
import type { ConfigOverridesInput } from "./run-prod-app-boot-context.ts";
|
|
20
21
|
export type WorkerContextOption = Record<string, unknown> | ((deps: WorkerDeps) => Record<string, unknown>);
|
|
21
22
|
export type WorkerDeps = {
|
|
@@ -112,6 +113,10 @@ export type RunWorkerAppOptions = {
|
|
|
112
113
|
readonly billing?: BillingBootOption;
|
|
113
114
|
/** Same as RunProdAppOptions["featureToggles"]: mounts feature-toggles and layers the operator switches over the tier resolver. */
|
|
114
115
|
readonly featureToggles?: boolean;
|
|
116
|
+
/** Signs the unsubscribe links of `unsubscribe: true` mails that jobs in this process trigger, as
|
|
117
|
+
* RunProdAppOptions["unsubscribe"] does. The unsubscribe routes stay in the API process; `secret`
|
|
118
|
+
* must be the same there. The public URL comes from `APP_BASE_URL`. */
|
|
119
|
+
readonly unsubscribe?: UnsubscribeOption;
|
|
115
120
|
/** Composed env schema (see RunProdAppOptions["envSchema"]). With it the worker validates its env,
|
|
116
121
|
* derives the KMS slots from it (`kmsSlots` is ignored) and understands every `KUMIKO_DRY_RUN_ENV`
|
|
117
122
|
* mode. Without it only `KUMIKO_DRY_RUN_ENV=boot` is available. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-server-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.416.0",
|
|
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>",
|
|
@@ -100,11 +100,11 @@
|
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
"dependencies": {
|
|
103
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
104
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
105
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
106
|
-
"@cosmicdrift/kumiko-renderer-mail-html": "0.
|
|
107
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
103
|
+
"@cosmicdrift/kumiko-bundled-features": "0.416.0",
|
|
104
|
+
"@cosmicdrift/kumiko-framework": "0.416.0",
|
|
105
|
+
"@cosmicdrift/kumiko-renderer": "0.416.0",
|
|
106
|
+
"@cosmicdrift/kumiko-renderer-mail-html": "0.416.0",
|
|
107
|
+
"@cosmicdrift/kumiko-types": "0.416.0",
|
|
108
108
|
"ioredis": "^5.11.1",
|
|
109
109
|
"temporal-polyfill": "^0.3.2",
|
|
110
110
|
"zod": "^4.6.7"
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"bun-types": "^1.4.4"
|
|
114
114
|
},
|
|
115
115
|
"devDependencies": {
|
|
116
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
116
|
+
"@cosmicdrift/kumiko-testing": "0.416.0",
|
|
117
117
|
"bun-types": "^1.4.4"
|
|
118
118
|
},
|
|
119
119
|
"publishConfig": {
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.415.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "PWA update removes the precache of the unscoped cache name format",
|
|
6
|
+
"detail": "Caches named kumiko-pwa-<16hex> from before the per-scope prefix were never deleted after an update. The service worker now drops them once, and keeps caches of other scopes and foreign libraries."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.415.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "runWorkerApp accepts the unsubscribe option",
|
|
12
|
+
"detail": "Pass the same `unsubscribe: { secret }` to runWorkerApp as to runProdApp; a job in the worker (e.g. a cron digest) that sends an `unsubscribe: true` notification then gets a signed link from `APP_BASE_URL`. The unsubscribe routes stay in the API process, which must use the same secret. Before, such mails went out without a link."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.414.0",
|
|
4
16
|
"type": "improvement",
|