@cosmicdrift/kumiko-server-runtime 0.377.0 → 0.379.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.
@@ -0,0 +1,10 @@
1
+ import type { AnonymousExtraRoute } from "@cosmicdrift/kumiko-framework/api";
2
+ export type DiskFileRouteOptions = {
3
+ /** Route path containing the `:file` parameter, e.g. `/fonts/:file`. */
4
+ readonly path: string;
5
+ /** Maps a plain file name to an absolute disk path, or null to answer 404. Only one
6
+ * segment without a leading dot or separator gets here; anything else is 404 already.
7
+ * Must return fixed, allowlisted paths, never `join(dir, name)`: symlinks are not checked. */
8
+ readonly resolveFile: (requestedFile: string) => string | null;
9
+ };
10
+ export declare function diskFileRoute(options: DiskFileRouteOptions): AnonymousExtraRoute;
@@ -0,0 +1,28 @@
1
+ import { readStaticFile, serveDiskFile } from "./run-prod-app-static-files.js";
2
+ // Hono percent-decodes the param, so `..%2F` arrives as `../`; a leading dot also
3
+ // covers `.`, `..` and dotfiles such as `.env` next to the served files.
4
+ function isPlainFileName(name) {
5
+ return name.length > 0 && !name.startsWith(".") && !/[/\\\0]/.test(name);
6
+ }
7
+ export function diskFileRoute(options) {
8
+ if (!options.path.includes(":file")) {
9
+ throw new Error(`diskFileRoute: path "${options.path}" must contain the ":file" parameter`);
10
+ }
11
+ return {
12
+ method: "GET",
13
+ path: options.path,
14
+ entry: "anonymous",
15
+ handler: async (c) => {
16
+ const requestedFile = c.req.param("file") ?? "";
17
+ if (!isPlainFileName(requestedFile))
18
+ return c.notFound();
19
+ const filePath = options.resolveFile(requestedFile);
20
+ if (filePath === null)
21
+ return c.notFound();
22
+ const file = await readStaticFile(filePath);
23
+ if (!file)
24
+ return c.notFound();
25
+ return serveDiskFile(c.req.raw, c.req.path, file);
26
+ },
27
+ };
28
+ }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  export { type BillingBootOption, resolveBillingOption } from "./boot-options/billing-option.ts";
2
2
  export type { DemoAccountEntry, DemoAccountRole, DemoModeNotice, DemoModeOption, } from "./boot-options/demo-mode.ts";
3
3
  export { buildDemoModeRoute, withDemoReadOnlyFetch, } from "./boot-options/demo-mode.ts";
4
+ export { type FeatureTogglesBinding, type FeatureTogglesWiring, resolveFeatureTogglesOption, } from "./boot-options/feature-toggles-option.ts";
4
5
  export type { FileStorageBootOption, FileStorageProviderName, } from "./boot-options/file-storage-option.ts";
5
6
  export type { KmsMode } from "./boot-options/kms-mode.ts";
6
- export type { MailBootOption, MailRecipientScope } from "./boot-options/mail-option.ts";
7
+ export { type MailBootOption, type MailRecipientScope, resolveStoredUserEmail, } from "./boot-options/mail-option.ts";
7
8
  export { withBootOptionEnvBlocks } from "./boot-options/option-env-schema.ts";
8
9
  export type { SignalsOption } from "./boot-options/process-signals.ts";
9
10
  export { type BootOptionFields, type BootOptionWiring, isSeedFnList, rejectRemovedEffectiveFeatures, resolveBootOptionWiring, } from "./boot-options/resolve-boot-options.ts";
@@ -11,6 +12,7 @@ export type { ProdSeedsConfig, ProdSeedTenant } from "./boot-options/seed-shortc
11
12
  export { assertStackBootRequirements } from "./boot-options/stack-boot-requirements.ts";
12
13
  export { type BundledAssetDeclaration, type BundledAssetOptions, readBundledAsset, resolveBundledAsset, } from "./bundled-assets.ts";
13
14
  export { type ComposeFeaturesOptions, composeFeatures } from "./compose-features.ts";
15
+ export { type DiskFileRouteOptions, diskFileRoute } from "./disk-file-route.ts";
14
16
  export type { RateLimitOption } from "./rate-limit-defaults.ts";
15
17
  export type { RunBootstrapOptions } from "./run-bootstrap.ts";
16
18
  export { runBootstrap } from "./run-bootstrap.ts";
package/dist/index.js CHANGED
@@ -4,11 +4,14 @@
4
4
  // Dev-Tooling mehr in ihre node_modules.
5
5
  export { resolveBillingOption } from "./boot-options/billing-option.js";
6
6
  export { buildDemoModeRoute, withDemoReadOnlyFetch, } from "./boot-options/demo-mode.js";
7
+ export { resolveFeatureTogglesOption, } from "./boot-options/feature-toggles-option.js";
8
+ export { resolveStoredUserEmail, } from "./boot-options/mail-option.js";
7
9
  export { withBootOptionEnvBlocks } from "./boot-options/option-env-schema.js";
8
10
  export { isSeedFnList, rejectRemovedEffectiveFeatures, resolveBootOptionWiring, } from "./boot-options/resolve-boot-options.js";
9
11
  export { assertStackBootRequirements } from "./boot-options/stack-boot-requirements.js";
10
12
  export { readBundledAsset, resolveBundledAsset, } from "./bundled-assets.js";
11
13
  export { composeFeatures } from "./compose-features.js";
14
+ export { diskFileRoute } from "./disk-file-route.js";
12
15
  export { runBootstrap } from "./run-bootstrap.js";
13
16
  export { parseRunMode, requireEnv, runProdApp } from "./run-prod-app.js";
14
17
  export { runWorkerApp } from "./run-worker-app.js";
@@ -1,14 +1,11 @@
1
- import type { ServerOptions } from "@cosmicdrift/kumiko-framework/api";
1
+ import { DEFAULT_GLOBAL_RATE_LIMIT, type ServerOptions } from "@cosmicdrift/kumiko-framework/api";
2
2
  type ServerRateLimit = NonNullable<ServerOptions["rateLimit"]>;
3
3
  /** `false` per layer or for the whole option switches it off. */
4
4
  export type RateLimitOption = false | {
5
5
  readonly global?: false | ServerRateLimit["global"];
6
6
  readonly auth?: false | ServerRateLimit["auth"];
7
7
  };
8
- export declare const DEFAULT_GLOBAL_RATE_LIMIT: {
9
- readonly limit: 600;
10
- readonly windowSeconds: 60;
11
- };
8
+ export { DEFAULT_GLOBAL_RATE_LIMIT };
12
9
  export declare const DEFAULT_AUTH_RATE_LIMIT: {
13
10
  readonly limit: 30;
14
11
  readonly windowSeconds: 60;
@@ -16,4 +13,3 @@ export declare const DEFAULT_AUTH_RATE_LIMIT: {
16
13
  /** Layers the app did not mention keep their default; a layer it did mention
17
14
  * overrides only the fields it names. */
18
15
  export declare function resolveRateLimit(option: RateLimitOption | undefined): ServerRateLimit | undefined;
19
- export {};
@@ -1,4 +1,5 @@
1
- export const DEFAULT_GLOBAL_RATE_LIMIT = { limit: 600, windowSeconds: 60 };
1
+ import { DEFAULT_GLOBAL_RATE_LIMIT } from "@cosmicdrift/kumiko-framework/api";
2
+ export { DEFAULT_GLOBAL_RATE_LIMIT };
2
3
  export const DEFAULT_AUTH_RATE_LIMIT = { limit: 30, windowSeconds: 60 };
3
4
  /** Layers the app did not mention keep their default; a layer it did mention
4
5
  * overrides only the fields it names. */
@@ -168,7 +168,7 @@ export function buildBootExtraContext(opts) {
168
168
  const bootConfigResolver = resolveDeliveryConfigResolver(opts, hasDeliveryFeature, crypto);
169
169
  const configResolver = opts.hasAuth ? bootConfigResolver : undefined;
170
170
  return {
171
- templateResolver: createTemplateResolverApi(opts.db),
171
+ templateResolver: createTemplateResolverApi(opts.db, opts.registry),
172
172
  ...(opts.kms && { kms: opts.kms }),
173
173
  ...(escapeHatchAuditSink && { _escapeHatchAuditSink: escapeHatchAuditSink }),
174
174
  ...bootDeliveryContext(opts, {
@@ -8,7 +8,7 @@ import { type AllInOneEntrypoint, type ApiEntrypoint } from "@cosmicdrift/kumiko
8
8
  import type { ComposedEnvSchema, KumikoBootError } from "@cosmicdrift/kumiko-framework/env";
9
9
  import type { ErrorDocsConfig } from "@cosmicdrift/kumiko-framework/errors";
10
10
  import type { JobRunnerOptions } from "@cosmicdrift/kumiko-framework/jobs";
11
- import { type ObservabilityOptions, type ObservabilityProvider } from "@cosmicdrift/kumiko-framework/observability";
11
+ import { type ObservabilityProvider } from "@cosmicdrift/kumiko-framework/observability";
12
12
  import type { MasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
13
13
  import type { PageHeadMeta, PageHeadResolver, PageHeadSystemQuery } from "@cosmicdrift/kumiko-renderer/headless/apex";
14
14
  import { Redis } from "ioredis";
@@ -232,7 +232,7 @@ export type HostDispatchResult = {
232
232
  readonly kind: "html";
233
233
  readonly file: string;
234
234
  /** @deprecated Ignored — HTML never carries the schema; createKumikoApp
235
- * fetches it from the authenticated GET /api/schema. */
235
+ * fetches it from the authenticated GET /api/schema. Removed in 0.390.0. */
236
236
  readonly injectSchema?: boolean;
237
237
  readonly csp?: string;
238
238
  } | {
@@ -326,27 +326,21 @@ export type RunProdAppOptions = {
326
326
  * anonymous role (not system) — the resolver is reachable by any
327
327
  * public visitor, same access gate as a real anonymous request. */
328
328
  readonly resolvePageHead?: PageHeadResolver;
329
- /** Pfad zu kumiko/migrations für den Boot-Gate. Default "./kumiko/
330
- * migrations" relativ zum process-cwd (wo die App gestartet wird —
331
- * bei Container-Deploys typischerweise der App-Workspace-Root, weil
332
- * WORKDIR im Dockerfile dorthin zeigt). Boot wirft SchemaDriftError
333
- * wenn Migrations pending sind oder erwartete Tabellen fehlen.
334
- * Setze auf `false` um den Gate komplett zu deaktivieren — nur für
335
- * Setups die ihren eigenen Schema-Check fahren (z.B. bring-your-own-
336
- * ORM). Standard-Apps lassen das default. */
329
+ /** Path to kumiko/migrations for the boot gate. Default "./kumiko/migrations"
330
+ * relative to the process cwd. Boot throws SchemaDriftError on pending
331
+ * migrations or missing tables. `false` disables the gate for setups that
332
+ * run their own schema check. */
337
333
  readonly migrations?: {
338
334
  readonly dir: string;
339
335
  } | false;
340
- /** Extra AppContext keys. Framework-Defaults werden zur Boot-Zeit
341
- * unter den Factory-Werten ergänzt, App-Werte gewinnen immer:
342
- * - `templateResolver` (createTemplateResolverApi(db)) — immer
343
- * - `secrets` (createSecretsContext) — nur wenn das `secrets`-Feature
344
- * gemountet ist (sonst kein KEK-env-Zwang für Apps ohne secrets)
345
- * - `configResolver` — im Auth-Mode (env-config-overrides)
346
- * Akzeptiert einen statischen Object ODER eine Factory
347
- * `({db, redis, registry}) => Record<string, unknown>` — gleiches
348
- * Pattern wie `anonymousAccess`. Eine App die einen dieser Keys selbst
349
- * setzt (z.B. eigener configResolver) überschreibt den Default. */
336
+ /** Extra AppContext keys. Framework defaults are added at boot beneath the
337
+ * factory values; app values always win:
338
+ * - `templateResolver` (createTemplateResolverApi(db, registry)), always
339
+ * - `secrets` (createSecretsContext), only when the `secrets` feature is
340
+ * mounted (no KEK env requirement otherwise)
341
+ * - `configResolver`, in auth mode (env-config-overrides)
342
+ * Accepts a static object or a factory
343
+ * `({db, redis, registry}) => Record<string, unknown>`, like `anonymousAccess`. */
350
344
  /** Boot default locale (BCP-47) for ctx.locale when the request carries
351
345
  * no locale signal. Merged into AppContext before extraContext (app wins). */
352
346
  readonly defaultLocale?: string;
@@ -519,8 +513,6 @@ export type RunProdAppOptions = {
519
513
  * kein `/metrics`-Endpoint). Setze `createPrometheusMeter()`-basierten
520
514
  * Provider + `metrics` um `/metrics` real zu exposen (publicstatus#91). */
521
515
  readonly observability?: ObservabilityProvider;
522
- /** @deprecated No effect: the HTTP route label carries no query string anymore. */
523
- readonly observabilityOptions?: ObservabilityOptions;
524
516
  /** Aktiviert den `/metrics`-Endpoint (Open-Metrics-Format). Ohne einen
525
517
  * PrometheusMeter-basierten `observability`-Provider antwortet `/metrics`
526
518
  * mit 503 + Misconfiguration-Hinweis statt echter Metriken. */
@@ -5,7 +5,7 @@ import type { FeatureDefinition, Registry, ValidateBootOptions } from "@cosmicdr
5
5
  import type { WorkerEntrypoint } from "@cosmicdrift/kumiko-framework/entrypoint";
6
6
  import type { ComposedEnvSchema, KumikoBootError } from "@cosmicdrift/kumiko-framework/env";
7
7
  import type { JobRunnerOptions } from "@cosmicdrift/kumiko-framework/jobs";
8
- import { type ObservabilityOptions, type ObservabilityProvider } from "@cosmicdrift/kumiko-framework/observability";
8
+ import { type ObservabilityProvider } from "@cosmicdrift/kumiko-framework/observability";
9
9
  import type { MasterKeyProvider, SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
10
10
  import type { Redis } from "ioredis";
11
11
  import { type WorkerMetricsOptions, type WorkerMetricsServer } from "./boot/worker-metrics-server.ts";
@@ -108,8 +108,6 @@ export type RunWorkerAppOptions = {
108
108
  * cannot derive them; without this it reads the env as is. */
109
109
  readonly kmsSlots?: readonly string[];
110
110
  readonly observability?: ObservabilityProvider;
111
- /** @deprecated No effect: the HTTP route label carries no query string anymore. */
112
- readonly observabilityOptions?: ObservabilityOptions;
113
111
  /** Serves `/metrics` on its own port (the worker has no HTTP app). Needs a
114
112
  * PrometheusMeter-based `observability`; both are validated at boot,
115
113
  * including KUMIKO_DRY_RUN_ENV=boot. Same `token`/`path` as runProdApp. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-server-runtime",
3
- "version": "0.377.0",
3
+ "version": "0.379.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": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -88,18 +88,18 @@
88
88
  }
89
89
  },
90
90
  "dependencies": {
91
- "@cosmicdrift/kumiko-bundled-features": "0.377.0",
92
- "@cosmicdrift/kumiko-framework": "0.377.0",
93
- "@cosmicdrift/kumiko-renderer": "0.377.0",
94
- "@cosmicdrift/kumiko-renderer-mail-html": "0.377.0",
95
- "@cosmicdrift/kumiko-types": "0.377.0",
91
+ "@cosmicdrift/kumiko-bundled-features": "0.379.0",
92
+ "@cosmicdrift/kumiko-framework": "0.379.0",
93
+ "@cosmicdrift/kumiko-renderer": "0.379.0",
94
+ "@cosmicdrift/kumiko-renderer-mail-html": "0.379.0",
95
+ "@cosmicdrift/kumiko-types": "0.379.0",
96
96
  "bun-types": "^1.3.13",
97
97
  "ioredis": "^5.10.1",
98
98
  "temporal-polyfill": "^0.3.2",
99
99
  "zod": "^4.4.3"
100
100
  },
101
101
  "devDependencies": {
102
- "@cosmicdrift/kumiko-testing": "0.377.0"
102
+ "@cosmicdrift/kumiko-testing": "0.379.0"
103
103
  },
104
104
  "publishConfig": {
105
105
  "registry": "https://registry.npmjs.org",
package/src/changes.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.379.0",
4
+ "type": "improvement",
5
+ "title": "resolveStoredUserEmail and resolveFeatureTogglesOption exported",
6
+ "detail": "resolveStoredUserEmail (with MailRecipientScope) and resolveFeatureTogglesOption with its binding types are available from the package entry. The production boot builds the template resolver API with the registry."
7
+ },
8
+ {
9
+ "version": "0.378.0",
10
+ "type": "improvement",
11
+ "title": "diskFileRoute for allowlisted disk files",
12
+ "detail": "diskFileRoute({ path, resolveFile }) returns an anonymous GET extra route with a :file parameter. resolveFile maps the raw name to a disk path or null for 404; the file is served with mime type, ETag and 304 handling."
13
+ },
2
14
  {
3
15
  "version": "0.376.0",
4
16
  "type": "breaking",