@cosmicdrift/kumiko-framework 0.387.0 → 0.388.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.
Files changed (240) hide show
  1. package/AGENTS.md +6 -1
  2. package/agents/examples/anonymous-access.md +1 -1
  3. package/agents/examples/apex-landing.md +204 -0
  4. package/agents/examples/api-worker-split.md +64 -0
  5. package/agents/examples/app-config.md +14 -0
  6. package/agents/examples/auth-mfa.md +115 -0
  7. package/agents/examples/env-composition.md +28 -1
  8. package/agents/examples/legal-pages.md +1 -1
  9. package/agents/examples/prod-boot-options.md +16 -0
  10. package/agents/examples/rate-limiting.md +48 -0
  11. package/agents/examples/record-detail-layout.md +692 -0
  12. package/agents/examples/seed-migration.md +212 -0
  13. package/agents/examples/wizard-form.md +941 -0
  14. package/agents/examples/writeform-section.md +548 -0
  15. package/agents/features.md +19 -2
  16. package/agents/widgets.md +1 -0
  17. package/dist/api/client-error-route.js +8 -2
  18. package/dist/api/index.d.ts +3 -1
  19. package/dist/api/index.js +3 -1
  20. package/dist/api/request-context.d.ts +1 -1
  21. package/dist/api/request-context.js +1 -1
  22. package/dist/api/server-boot-checks.d.ts +10 -0
  23. package/dist/api/server-boot-checks.js +157 -0
  24. package/dist/api/server-event-dispatcher.d.ts +26 -0
  25. package/dist/api/server-event-dispatcher.js +208 -0
  26. package/dist/api/server-extra-routes.d.ts +11 -0
  27. package/dist/api/server-extra-routes.js +203 -0
  28. package/dist/api/server-middleware.d.ts +20 -0
  29. package/dist/api/server-middleware.js +211 -0
  30. package/dist/api/server-routes.d.ts +19 -0
  31. package/dist/api/server-routes.js +293 -0
  32. package/dist/api/server-websocket-routes.d.ts +4 -0
  33. package/dist/api/server-websocket-routes.js +108 -0
  34. package/dist/api/server-wiring.d.ts +32 -0
  35. package/dist/api/server-wiring.js +236 -0
  36. package/dist/api/server.d.ts +10 -15
  37. package/dist/api/server.js +48 -1251
  38. package/dist/api/session-cookies.js +4 -4
  39. package/dist/api/sse-route.js +16 -12
  40. package/dist/crypto/owner-transition-rekey.d.ts +5 -1
  41. package/dist/crypto/owner-transition-rekey.js +1 -1
  42. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  43. package/dist/crypto/pii-field-encryption.js +3 -1
  44. package/dist/db/aggregate-sql.d.ts +21 -0
  45. package/dist/db/aggregate-sql.js +155 -0
  46. package/dist/db/closed-connection.d.ts +9 -0
  47. package/dist/db/closed-connection.js +95 -0
  48. package/dist/db/dialect.js +1 -1
  49. package/dist/db/distinct-on.d.ts +1 -1
  50. package/dist/db/driver-row-coercion.d.ts +2 -0
  51. package/dist/db/driver-row-coercion.js +186 -0
  52. package/dist/db/driver-value-preparation.d.ts +16 -0
  53. package/dist/db/driver-value-preparation.js +99 -0
  54. package/dist/db/entity-field-encryption.d.ts +1 -0
  55. package/dist/db/entity-field-encryption.js +1 -1
  56. package/dist/db/entity-table-meta.js +103 -110
  57. package/dist/db/event-store-executor-context.d.ts +2 -1
  58. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  59. package/dist/db/event-store-executor-read-aggregate.js +112 -0
  60. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  61. package/dist/db/event-store-executor-read-filter.js +203 -0
  62. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  63. package/dist/db/event-store-executor-read-paging.js +99 -0
  64. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  65. package/dist/db/event-store-executor-read-rows.js +32 -0
  66. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  67. package/dist/db/event-store-executor-read-search.js +221 -0
  68. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  69. package/dist/db/event-store-executor-read-sort.js +130 -0
  70. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  71. package/dist/db/event-store-executor-read-where.js +81 -0
  72. package/dist/db/event-store-executor-read.d.ts +2 -9
  73. package/dist/db/event-store-executor-read.js +48 -701
  74. package/dist/db/event-store-executor-write-steps.d.ts +20 -0
  75. package/dist/db/event-store-executor-write-steps.js +100 -0
  76. package/dist/db/event-store-executor-write.js +395 -499
  77. package/dist/db/index.d.ts +6 -2
  78. package/dist/db/index.js +5 -1
  79. package/dist/db/list-aggregate-labels.d.ts +14 -0
  80. package/dist/db/list-aggregate-labels.js +26 -0
  81. package/dist/db/migrate-generator.js +71 -86
  82. package/dist/db/migrate-runner.js +4 -1
  83. package/dist/db/query.d.ts +2 -15
  84. package/dist/db/query.js +8 -761
  85. package/dist/db/reference-data.js +6 -1
  86. package/dist/db/table-builder.js +113 -166
  87. package/dist/db/table-info.d.ts +10 -0
  88. package/dist/db/table-info.js +45 -0
  89. package/dist/db/temporal-value-guards.d.ts +6 -0
  90. package/dist/db/temporal-value-guards.js +15 -0
  91. package/dist/db/tenant-db-runner.d.ts +1 -0
  92. package/dist/db/tenant-db-runner.js +3 -0
  93. package/dist/db/tenant-db.js +39 -9
  94. package/dist/db/where-clause.d.ts +6 -0
  95. package/dist/db/where-clause.js +236 -0
  96. package/dist/derivatives/delete-stored-file.js +2 -1
  97. package/dist/engine/boot-validator/boot-check.js +1 -1
  98. package/dist/engine/boot-validator/detail-screens.js +2 -2
  99. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  100. package/dist/engine/boot-validator/entity-handler.js +17 -0
  101. package/dist/engine/boot-validator/form-derivations.js +35 -1
  102. package/dist/engine/boot-validator/i18n-keys.js +5 -3
  103. package/dist/engine/boot-validator/index.js +2 -0
  104. package/dist/engine/boot-validator/pii-retention.js +233 -155
  105. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  106. package/dist/engine/boot-validator/screens-actions.js +8 -1
  107. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  108. package/dist/engine/boot-validator/screens-forms.js +2 -6
  109. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  110. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  111. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  112. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  113. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  114. package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
  115. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  116. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  117. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  118. package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
  119. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  120. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  121. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  122. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  123. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  124. package/dist/engine/boot-validator/screens-lists.js +12 -9
  125. package/dist/engine/boot-validator/screens.d.ts +0 -2
  126. package/dist/engine/boot-validator/screens.js +99 -860
  127. package/dist/engine/build-app-schema.js +56 -96
  128. package/dist/engine/define-feature.js +9 -2
  129. package/dist/engine/define-handler.js +1 -0
  130. package/dist/engine/entity-handlers.d.ts +13 -0
  131. package/dist/engine/entity-handlers.js +10 -21
  132. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  133. package/dist/engine/entity-list-runtime-options.js +18 -0
  134. package/dist/engine/feature-ast/extractors/handlers.js +1 -0
  135. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  136. package/dist/engine/feature-ast/patch.js +4 -182
  137. package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
  138. package/dist/engine/feature-ast/pattern-change-schema.js +1 -0
  139. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  140. package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
  141. package/dist/engine/feature-ast/patterns.d.ts +9 -0
  142. package/dist/engine/feature-ast/patterns.js +43 -51
  143. package/dist/engine/feature-ast/render.js +48 -87
  144. package/dist/engine/feature-entity-handlers.js +3 -0
  145. package/dist/engine/form-derivations.js +18 -7
  146. package/dist/engine/handler-field-references.js +1 -1
  147. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  148. package/dist/engine/project-app-schema-for-roles.js +6 -4
  149. package/dist/engine/registry-ingest.js +1 -1
  150. package/dist/engine/schema-builder.js +228 -212
  151. package/dist/engine/screen-helpers.js +1 -1
  152. package/dist/engine/screen-kind.d.ts +7 -0
  153. package/dist/engine/screen-kind.js +1 -0
  154. package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
  155. package/dist/engine/validate-projection-allowlist.js +2 -1
  156. package/dist/entrypoint/index.d.ts +4 -1
  157. package/dist/entrypoint/index.js +7 -1
  158. package/dist/errors/docs-url.d.ts +1 -1
  159. package/dist/errors/docs-url.js +22 -2
  160. package/dist/es-ops/context.js +1 -1
  161. package/dist/event-store/admin-api.js +1 -1
  162. package/dist/event-store/event-store.js +10 -4
  163. package/dist/files/bind-pending-file-refs.js +3 -3
  164. package/dist/files/create-file-ref-from-bytes.js +4 -1
  165. package/dist/files/file-handle.js +2 -3
  166. package/dist/files/file-routes.js +11 -8
  167. package/dist/files/resolve-content-type.js +43 -0
  168. package/dist/files/upload-ledger.js +7 -0
  169. package/dist/i18n/required-surface-keys.js +182 -206
  170. package/dist/internal/stack-support.d.ts +2 -1
  171. package/dist/internal/stack-support.js +1 -1
  172. package/dist/jobs/job-runner-context.d.ts +32 -0
  173. package/dist/jobs/job-runner-context.js +305 -0
  174. package/dist/jobs/job-runner-execution.d.ts +7 -0
  175. package/dist/jobs/job-runner-execution.js +160 -0
  176. package/dist/jobs/job-runner-gates.d.ts +19 -0
  177. package/dist/jobs/job-runner-gates.js +198 -0
  178. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  179. package/dist/jobs/job-runner-helpers.js +61 -2
  180. package/dist/jobs/job-runner-scope.d.ts +44 -0
  181. package/dist/jobs/job-runner-scope.js +102 -0
  182. package/dist/jobs/job-runner-types.d.ts +12 -0
  183. package/dist/jobs/job-runner.d.ts +0 -1
  184. package/dist/jobs/job-runner.js +47 -657
  185. package/dist/observability/escape-hatch-report.d.ts +3 -0
  186. package/dist/observability/escape-hatch-report.js +12 -1
  187. package/dist/pipeline/cascade-handler.js +165 -138
  188. package/dist/pipeline/dispatch-query.js +10 -3
  189. package/dist/pipeline/dispatch-shared.d.ts +13 -3
  190. package/dist/pipeline/dispatch-shared.js +58 -710
  191. package/dist/pipeline/dispatch-write.js +296 -189
  192. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  193. package/dist/pipeline/event-dispatcher-delivery.js +3 -5
  194. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  195. package/dist/pipeline/handler-context-auth.js +54 -0
  196. package/dist/pipeline/handler-context-config.d.ts +14 -0
  197. package/dist/pipeline/handler-context-config.js +113 -0
  198. package/dist/pipeline/handler-context-db.d.ts +24 -0
  199. package/dist/pipeline/handler-context-db.js +149 -0
  200. package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
  201. package/dist/pipeline/handler-context-dispatch.js +89 -0
  202. package/dist/pipeline/handler-context-events.d.ts +43 -0
  203. package/dist/pipeline/handler-context-events.js +237 -0
  204. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  205. package/dist/pipeline/handler-context-notify.js +55 -0
  206. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  207. package/dist/pipeline/handler-context-observability.js +21 -0
  208. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  209. package/dist/pipeline/handler-context-scope.js +88 -0
  210. package/dist/pipeline/notify-scope.js +12 -1
  211. package/dist/pipeline/pre-handler-gates.js +20 -4
  212. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  213. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  214. package/dist/pipeline/projection-rebuild.js +2 -1
  215. package/dist/schema-cli/apply-command.d.ts +2 -0
  216. package/dist/schema-cli/apply-command.js +97 -0
  217. package/dist/schema-cli/baseline-command.d.ts +2 -0
  218. package/dist/schema-cli/baseline-command.js +33 -0
  219. package/dist/schema-cli/generate-command.d.ts +2 -0
  220. package/dist/schema-cli/generate-command.js +90 -0
  221. package/dist/schema-cli/shared.d.ts +39 -0
  222. package/dist/schema-cli/shared.js +95 -0
  223. package/dist/schema-cli/status-command.d.ts +2 -0
  224. package/dist/schema-cli/status-command.js +73 -0
  225. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  226. package/dist/schema-cli/validate-boot-input.js +130 -0
  227. package/dist/schema-cli/validate-command.d.ts +2 -0
  228. package/dist/schema-cli/validate-command.js +130 -0
  229. package/dist/schema-cli.d.ts +2 -18
  230. package/dist/schema-cli.js +47 -604
  231. package/dist/search/meilisearch-adapter.js +2 -1
  232. package/dist/upgrade-cli.js +149 -121
  233. package/package.json +4 -4
  234. package/src/changes.json +310 -0
  235. package/src/scripts/codemod/feature-layout.ts +10 -2
  236. package/src/scripts/codemod/object-form.ts +41 -4
  237. package/src/scripts/codemod/package-merge.ts +58 -8
  238. package/src/scripts/codemod/pii-personal-migration.ts +175 -145
  239. package/src/scripts/codemod/testing-injection.ts +122 -42
  240. package/src/scripts/codemod/testing-move.ts +29 -7
@@ -0,0 +1,32 @@
1
+ import type { CacheSyncBus } from "@cosmicdrift/kumiko-types/cache-sync-types";
2
+ import type { DbConnection } from "../db/connection.ts";
3
+ import type { AppContext, Registry } from "../engine/types/index.ts";
4
+ import { type ObservabilityProvider } from "../observability/index.ts";
5
+ import { type Dispatcher } from "../pipeline/dispatcher.ts";
6
+ import type { EventConsumer, EventDispatcher } from "../pipeline/event-dispatcher.ts";
7
+ import { type LifecycleHooks } from "../pipeline/lifecycle-pipeline.ts";
8
+ import { type ClientIpResolver } from "./client-ip.ts";
9
+ import { type JwtHelper } from "./jwt.ts";
10
+ import type { ServerOptions } from "./server.ts";
11
+ import { type BootLogger } from "./server-boot-checks.ts";
12
+ import type { SseBroker } from "./sse-broker.ts";
13
+ export declare function withFileProviderResolver(registry: Registry, context: AppContext, cacheSync?: CacheSyncBus): AppContext;
14
+ export type ServerWiring = {
15
+ readonly options: ServerOptions;
16
+ readonly bootLog: BootLogger;
17
+ readonly clientIpResolver: ClientIpResolver;
18
+ readonly jwt: JwtHelper;
19
+ readonly sseBroker: SseBroker;
20
+ readonly observability: ObservabilityProvider;
21
+ readonly tracedRedis: AppContext["redis"];
22
+ readonly rateLimitResolver: AppContext["rateLimit"];
23
+ readonly rateLimitMiddlewareRequested: boolean;
24
+ readonly context: AppContext;
25
+ readonly fileProviderResolver: AppContext["_fileProviderResolver"];
26
+ readonly dispatcher: Dispatcher;
27
+ readonly lifecycleHooks: LifecycleHooks;
28
+ readonly baseDb: DbConnection | undefined;
29
+ readonly eventConsumers: readonly EventConsumer[];
30
+ readonly eventDispatcher: EventDispatcher | undefined;
31
+ };
32
+ export declare function resolveServerWiring(options: ServerOptions): ServerWiring;
@@ -0,0 +1,236 @@
1
+ import { EXT_FILE_PROVIDER } from "../engine/extension-names.js";
2
+ import { assertSeedModeNotInProduction } from "../event-store/seed-clock.js";
3
+ import { makeFileProviderResolver } from "../files/provider-resolver.js";
4
+ import { createFallbackLogger } from "../logging/index.js";
5
+ import { createNoopProvider, registerStandardMetrics, wrapRedisClient, } from "../observability/index.js";
6
+ import { configAccessorFactoryOf } from "../pipeline/config-accessor-factory.js";
7
+ import { createDispatcher } from "../pipeline/dispatcher.js";
8
+ import { createLifecycleHooks } from "../pipeline/lifecycle-pipeline.js";
9
+ import { createPayloadDigest, createRateLimitResolver } from "../rate-limit/index.js";
10
+ import { resolveCacheSyncBus } from "../redis/cache-sync-bus.js";
11
+ import { assertValidTrustedProxyHops, createClientIpResolver, } from "./client-ip.js";
12
+ import { createJwtHelper } from "./jwt.js";
13
+ import { createDefaultSseBroker } from "./redis-sse-broker.js";
14
+ import { assertFileStorageProviderForFileFields, assertNoPayloadRateLimitsWithoutResolver, resolveServerInstanceId, warnSearchableScreensWithoutSearchAdapter, warnUnlimitedSystemScopeFeatures, } from "./server-boot-checks.js";
15
+ import { createServerEventDispatcher, registerOwnedShutdownHooks, } from "./server-event-dispatcher.js";
16
+ // The per-tenant file-provider resolver, built once for a registry+context so
17
+ // a job-runner and the server it runs beside share one instance (and one
18
+ // per-tenant provider cache). Mirrors buildServer's own resolution exactly —
19
+ // including NOT inventing a resolver when no `file-provider-*` plugin is
20
+ // mounted, which is what keeps assertFileStorageProviderForFileFields able to fire.
21
+ export function withFileProviderResolver(registry, context, cacheSync) {
22
+ if (context._fileProviderResolver !== undefined)
23
+ return context;
24
+ if (registry.getExtensionUsages(EXT_FILE_PROVIDER).length === 0)
25
+ return context;
26
+ return {
27
+ ...context,
28
+ _fileProviderResolver: makeFileProviderResolver({
29
+ registry,
30
+ _configAccessorFactory: configAccessorFactoryOf(context),
31
+ secrets: context.secrets,
32
+ db: context.db,
33
+ ...(cacheSync ? { cacheSync } : {}),
34
+ }),
35
+ };
36
+ }
37
+ export function resolveServerWiring(options) {
38
+ assertSeedModeNotInProduction();
39
+ const bootLog = createFallbackLogger("kumiko:boot", options.context.log);
40
+ const clientIpResolver = resolveServerClientIpResolver(options);
41
+ assertFileStorageProviderForFileFields(options.registry, options.context);
42
+ warnSearchableScreensWithoutSearchAdapter(options.registry, options.context, bootLog);
43
+ const jwt = createServerJwtHelper(options);
44
+ const { sseBroker, ownedRedisSseBroker } = resolveServerSseBroker(options.sseBroker, bootLog);
45
+ // Same decision as the SSE broker: an explicit dispatcherOptions value
46
+ // (incl. null = opt out) wins, otherwise REDIS_URL turns on cross-replica
47
+ // cache invalidation (no REDIS_URL = process-local bus).
48
+ const { bus: cacheSync, owned: ownedCacheSync } = resolveCacheSyncBus(options.dispatcherOptions?.cacheSync);
49
+ const instanceId = resolveServerInstanceId(options.instanceId, bootLog);
50
+ const observability = resolveServerObservability(options.observability, options.registry);
51
+ const tracedRedis = resolveTracedRedis(options, observability);
52
+ // Inject tracer + meter into the AppContext so the dispatcher can propagate
53
+ // them into every HandlerContext it builds. Build the per-tenant file-provider
54
+ // resolver once (when a provider plugin is mounted) — the dispatcher uses it
55
+ // to materialise `ctx.files`, the upload routes + MSP-applies share it. The
56
+ // resolver reads config + the s3.secretAccessKey secret under SYSTEM identity.
57
+ const contextWithFiles = withFileProviderResolver(options.registry, options.context, cacheSync);
58
+ const fileProviderResolver = contextWithFiles._fileProviderResolver;
59
+ const rateLimit = resolveServerRateLimit(options, tracedRedis, bootLog);
60
+ const context = buildServerAppContext(options, {
61
+ contextWithFiles,
62
+ tracedRedis,
63
+ rateLimit,
64
+ cacheSync,
65
+ observability,
66
+ });
67
+ const lifecycleHooks = createLifecycleHooks(options.registry, options.systemHooks, options.eventDedup ? { eventDedup: options.eventDedup } : undefined);
68
+ const dispatcher = createServerDispatcher(options, context, lifecycleHooks, sseBroker, cacheSync);
69
+ const events = createServerEventDispatcher({
70
+ options,
71
+ context,
72
+ sseBroker,
73
+ observability,
74
+ fileProviderResolver,
75
+ instanceId,
76
+ });
77
+ registerOwnedShutdownHooks(options.lifecycle, {
78
+ eventDispatcher: events.eventDispatcher,
79
+ ownedRedisSseBroker,
80
+ ownedCacheSync,
81
+ });
82
+ return {
83
+ options,
84
+ bootLog,
85
+ clientIpResolver,
86
+ jwt,
87
+ sseBroker,
88
+ observability,
89
+ tracedRedis,
90
+ rateLimitResolver: rateLimit.resolver,
91
+ rateLimitMiddlewareRequested: rateLimit.middlewareRequested,
92
+ context,
93
+ fileProviderResolver,
94
+ dispatcher,
95
+ lifecycleHooks,
96
+ baseDb: events.baseDb,
97
+ eventConsumers: events.consumers,
98
+ eventDispatcher: events.eventDispatcher,
99
+ };
100
+ }
101
+ function resolveServerClientIpResolver(options) {
102
+ // Single effective hop-count for every IP-derived rate-limit + requestMeta
103
+ // consumer — the top-level option wins over the deprecated
104
+ // `auth.trustedProxyHops` fallback.
105
+ const trustedProxyHops = options.trustedProxyHops ?? options.auth?.trustedProxyHops ?? 0;
106
+ assertValidTrustedProxyHops(trustedProxyHops, "buildServer");
107
+ // Exactly ONE resolver for the whole server — the warn-once flag must fire
108
+ // once per boot, not once per consumer (requestIdMiddleware, L1, L2,
109
+ // auth-routes each sharing this same instance rather than building a
110
+ // private one).
111
+ return createClientIpResolver(trustedProxyHops, "buildServer");
112
+ }
113
+ function createServerJwtHelper(options) {
114
+ // Stateless JWTs (no sessionChecker → no revocation) default to a shorter
115
+ // TTL than session-backed ones, since a leaked stateless token can't be
116
+ // revoked and stays valid until it expires. Explicit jwtTtl always wins.
117
+ const defaultJwtTtl = options.auth?.sessionChecker ? 8 * 60 * 60 : 60 * 60;
118
+ return createJwtHelper(options.jwtSecret, options.jwtIssuer, options.jwtTtl ?? defaultJwtTtl);
119
+ }
120
+ function resolveServerSseBroker(explicitSseBroker, bootLog) {
121
+ // An explicit ServerOptions.sseBroker always wins (caller owns its
122
+ // lifecycle) — createDefaultSseBroker is the single decision point every
123
+ // other caller must funnel through instead of picking a broker on its
124
+ // own, so the REDIS_URL-gated cross-replica default can't be silently
125
+ // bypassed the way runProdApp bypassed it before this existed.
126
+ let sseBroker;
127
+ let ownedRedisSseBroker;
128
+ if (explicitSseBroker) {
129
+ sseBroker = explicitSseBroker;
130
+ }
131
+ else {
132
+ const defaults = createDefaultSseBroker();
133
+ sseBroker = defaults.sseBroker;
134
+ ownedRedisSseBroker = defaults.ownedRedisSseBroker;
135
+ if (!ownedRedisSseBroker) {
136
+ bootLog.warn("REDIS_URL is not set: cross-replica fanout disabled. SSE, access-invalidation and toggle-sync reach only the cursor-owning process, so with more than one replica clients on the other replicas miss events and revoked sessions stay open there.");
137
+ }
138
+ }
139
+ return { sseBroker, ownedRedisSseBroker };
140
+ }
141
+ function resolveServerObservability(explicitObservability, registry) {
142
+ // Observability — Noop by default so no call-site needs to null-check.
143
+ // Every handler/middleware that reaches for ctx.tracer / ctx.metrics gets
144
+ // a working, zero-cost fallback when no provider is configured.
145
+ const observability = explicitObservability ?? createNoopProvider();
146
+ // Register framework + feature metrics once on this meter. Standard
147
+ // metrics (HTTP, dispatcher, DB) are used by Auto-Instrumentation; feature
148
+ // metrics come from r.metric(...) declarations collected in the registry.
149
+ registerStandardMetrics(observability.meter);
150
+ for (const [name, def] of registry.getAllMetrics()) {
151
+ if (observability.meter.definitions().has(name))
152
+ continue;
153
+ observability.meter.registerMetric({
154
+ name,
155
+ type: def.type,
156
+ description: def.description,
157
+ labels: def.labels,
158
+ buckets: def.buckets,
159
+ unit: def.unit,
160
+ tenantLabel: def.tenantLabel,
161
+ });
162
+ }
163
+ return observability;
164
+ }
165
+ function resolveTracedRedis(options, observability) {
166
+ // When a non-default provider is configured, wrap the injected Redis clients
167
+ // so `redis.cmd` spans attach to every command. For the default NoopProvider
168
+ // we skip the proxy to keep zero runtime overhead when observability is off.
169
+ const shouldWrapRedis = options.observability !== undefined;
170
+ const redisCtx = options.context.redis;
171
+ return shouldWrapRedis && redisCtx ? wrapRedisClient(redisCtx, observability.tracer) : redisCtx;
172
+ }
173
+ function resolveServerRateLimit(options, tracedRedis, bootLog) {
174
+ // Auto-wire the rate-limit resolver, but ONLY when at least one
175
+ // handler actually declared a rateLimit option. Apps that don't use
176
+ // L3 pay zero cost: no resolver instance, no Lua-script registration
177
+ // on Redis, no AppContext field. Apps that wire L1/L2 middleware can
178
+ // pass `context.rateLimit` explicitly — that takes precedence over
179
+ // the auto-wire (e.g. middleware-only setup without any L3 handler).
180
+ // Auto-build the resolver when L3 handlers declared rateLimit OR when
181
+ // the caller asked for L1/L2 middleware. Either path needs a resolver;
182
+ // both share the same instance to avoid duplicate Lua-script registration.
183
+ const wantsL3 = options.registry.hasRateLimitedHandler();
184
+ const wantsL1L2 = options.rateLimit?.global !== undefined || options.rateLimit?.auth !== undefined;
185
+ const wantsHttpRouteLimit = [...options.registry.features.values()].some((feature) => Object.values(feature.httpRoutes).some((route) => route.rateLimit !== undefined));
186
+ const wantsResolver = wantsL3 || wantsL1L2 || wantsHttpRouteLimit;
187
+ // Master for the payload-bucket HMAC: the current signing secret (the
188
+ // keyring's signKid for rotation setups). buildServer always has one, so
189
+ // the digest is available whenever a handler declares additionalRateLimits.
190
+ const payloadDigest = createPayloadDigest(payloadDigestMasterSecret(options.jwtSecret));
191
+ const rateLimitResolver = options.context.rateLimit ??
192
+ (tracedRedis && wantsResolver ? createRateLimitResolver({ redis: tracedRedis }) : undefined);
193
+ if (!rateLimitResolver) {
194
+ assertNoPayloadRateLimitsWithoutResolver(options.registry);
195
+ warnUnlimitedSystemScopeFeatures(options.registry, bootLog);
196
+ }
197
+ return { resolver: rateLimitResolver, middlewareRequested: wantsL1L2, payloadDigest };
198
+ }
199
+ function buildServerAppContext(options, parts) {
200
+ const { contextWithFiles, tracedRedis, rateLimit, cacheSync, observability } = parts;
201
+ return {
202
+ ...contextWithFiles,
203
+ ...(tracedRedis ? { redis: tracedRedis } : {}),
204
+ ...(rateLimit.resolver ? { rateLimit: rateLimit.resolver } : {}),
205
+ _rateLimitPayloadDigest: rateLimit.payloadDigest,
206
+ ...(cacheSync ? { cacheSync } : {}),
207
+ // Propagate the feature-toggle resolver to the context so the event-
208
+ // dispatcher (and any future context-reading consumer) sees the same
209
+ // source as the command dispatcher's handler-gate. Options take
210
+ // precedence over whatever was already on context — the
211
+ // dispatcher-options arg is the authoritative wire-up point.
212
+ ...(options.dispatcherOptions?.effectiveFeatures
213
+ ? { effectiveFeatures: options.dispatcherOptions.effectiveFeatures }
214
+ : {}),
215
+ tracer: observability.tracer,
216
+ meter: observability.meter,
217
+ };
218
+ }
219
+ function createServerDispatcher(options, context, lifecycle, sseBroker, cacheSync) {
220
+ return createDispatcher(options.registry, context, {
221
+ ...options.dispatcherOptions,
222
+ lifecycle,
223
+ sseBroker,
224
+ cacheSync: cacheSync ?? null,
225
+ ...(options.auth ? { membershipQuery: options.auth.membershipQuery } : {}),
226
+ });
227
+ }
228
+ function payloadDigestMasterSecret(jwtSecret) {
229
+ if (typeof jwtSecret === "string")
230
+ return jwtSecret;
231
+ const signingSecret = jwtSecret.keys[jwtSecret.signKid];
232
+ if (signingSecret === undefined) {
233
+ throw new Error(`[kumiko] jwtSecret.signKid "${jwtSecret.signKid}" not found in jwtSecret.keys`);
234
+ }
235
+ return signingSecret;
236
+ }
@@ -1,20 +1,18 @@
1
- import type { CacheSyncBus } from "@cosmicdrift/kumiko-types/cache-sync-types";
2
1
  import { Hono } from "hono";
3
2
  import type { PgListenClient } from "../db/connection.ts";
4
- import { type AppContext, type Registry, type RunIn, type WriteResult } from "../engine/types/index.ts";
3
+ import type { AppContext, Registry, RunIn } from "../engine/types/index.ts";
5
4
  import type { ErrorDocsConfig } from "../errors/docs-url.ts";
6
5
  import type { Lifecycle } from "../lifecycle/index.ts";
7
- import { type ObservabilityProvider } from "../observability/index.ts";
8
- import type { DispatcherOptions } from "../pipeline/dispatcher.ts";
9
- import { type Dispatcher } from "../pipeline/dispatcher.ts";
6
+ import type { ObservabilityProvider } from "../observability/index.ts";
7
+ import type { Dispatcher, DispatcherOptions } from "../pipeline/dispatcher.ts";
10
8
  import type { EventDedup } from "../pipeline/event-dedup.ts";
11
9
  import type { EventDispatcher } from "../pipeline/event-dispatcher.ts";
12
- import { type SystemHooks } from "../pipeline/lifecycle-pipeline.ts";
13
- import { type AuthEndpointRateLimitOptions, type GlobalIpRateLimitOptions } from "../rate-limit/index.ts";
14
- import { type AnonymousAccessResolved } from "./auth-middleware.ts";
15
- import { type AuthRoutesConfig } from "./auth-routes.ts";
16
- import { type ExtraRouteDefinition, type RoleDispatchArgs, type SystemDispatchArgs } from "./extra-route.ts";
17
- import { type JwtHelper, type JwtKeyring } from "./jwt.ts";
10
+ import type { LifecycleHooks, SystemHooks } from "../pipeline/lifecycle-pipeline.ts";
11
+ import type { AuthEndpointRateLimitOptions, GlobalIpRateLimitOptions } from "../rate-limit/index.ts";
12
+ import type { AnonymousAccessResolved } from "./auth-middleware.ts";
13
+ import type { AuthRoutesConfig } from "./auth-routes.ts";
14
+ import type { ExtraRouteDefinition } from "./extra-route.ts";
15
+ import type { JwtHelper, JwtKeyring } from "./jwt.ts";
18
16
  import type { SseBroker } from "./sse-broker.ts";
19
17
  export type ServerOptions = {
20
18
  registry: Registry;
@@ -74,12 +72,9 @@ export type KumikoServer = {
74
72
  sseBroker: SseBroker;
75
73
  observability: ObservabilityProvider;
76
74
  dispatcher: Dispatcher;
75
+ lifecycleHooks: LifecycleHooks;
77
76
  eventDispatcher?: EventDispatcher;
78
77
  lifecycle?: Lifecycle;
79
78
  context: AppContext;
80
79
  };
81
- export declare function withFileProviderResolver(registry: Registry, context: AppContext, cacheSync?: CacheSyncBus): AppContext;
82
80
  export declare function buildServer(options: ServerOptions): KumikoServer;
83
- export declare function makeDispatchSystemWrite(dispatcher: Dispatcher): (args: SystemDispatchArgs) => Promise<WriteResult>;
84
- export declare function makeDispatchWriteAs(dispatcher: Dispatcher): (args: RoleDispatchArgs) => Promise<WriteResult>;
85
- export declare function makeDispatchSystemQuery(dispatcher: Dispatcher): (args: SystemDispatchArgs) => Promise<unknown>;