@cosmicdrift/kumiko-framework 0.1.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 (388) hide show
  1. package/README.md +159 -0
  2. package/package.json +91 -0
  3. package/src/__tests__/anonymous-access.integration.ts +325 -0
  4. package/src/__tests__/error-contract.integration.ts +435 -0
  5. package/src/__tests__/field-access.integration.ts +269 -0
  6. package/src/__tests__/full-stack.integration.ts +914 -0
  7. package/src/__tests__/ownership.integration.ts +449 -0
  8. package/src/__tests__/reference-data.integration.ts +198 -0
  9. package/src/__tests__/transition-guard.integration.ts +340 -0
  10. package/src/api/__tests__/api.test.ts +337 -0
  11. package/src/api/__tests__/auth-middleware-transport.test.ts +80 -0
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +179 -0
  13. package/src/api/__tests__/batch.integration.ts +404 -0
  14. package/src/api/__tests__/body-limit.test.ts +88 -0
  15. package/src/api/__tests__/csrf-middleware.test.ts +97 -0
  16. package/src/api/__tests__/dispatcher-live.integration.ts +216 -0
  17. package/src/api/__tests__/metrics-endpoint.test.ts +126 -0
  18. package/src/api/__tests__/nested-write.integration.ts +213 -0
  19. package/src/api/__tests__/readiness.test.ts +76 -0
  20. package/src/api/__tests__/request-id-middleware.test.ts +72 -0
  21. package/src/api/__tests__/sse-broker.test.ts +58 -0
  22. package/src/api/__tests__/sse-route.test.ts +112 -0
  23. package/src/api/anonymous-cookie.ts +60 -0
  24. package/src/api/api-constants.ts +64 -0
  25. package/src/api/auth-middleware.ts +418 -0
  26. package/src/api/auth-routes.ts +982 -0
  27. package/src/api/csrf-middleware.ts +77 -0
  28. package/src/api/index.ts +31 -0
  29. package/src/api/jwt.ts +66 -0
  30. package/src/api/observability-middleware.ts +89 -0
  31. package/src/api/readiness.ts +132 -0
  32. package/src/api/request-context.ts +49 -0
  33. package/src/api/request-id-middleware.ts +50 -0
  34. package/src/api/route-registrars.ts +195 -0
  35. package/src/api/routes.ts +135 -0
  36. package/src/api/server.ts +640 -0
  37. package/src/api/sse-broker.ts +71 -0
  38. package/src/api/sse-route.ts +62 -0
  39. package/src/api/tokens.ts +16 -0
  40. package/src/db/__tests__/apply-entity-event-tenant.integration.ts +159 -0
  41. package/src/db/__tests__/compound-types.test.ts +114 -0
  42. package/src/db/__tests__/connection-options.test.ts +68 -0
  43. package/src/db/__tests__/cursor.test.ts +41 -0
  44. package/src/db/__tests__/db-helpers.test.ts +369 -0
  45. package/src/db/__tests__/dialect-instant.test.ts +50 -0
  46. package/src/db/__tests__/drizzle-helpers.integration.ts +186 -0
  47. package/src/db/__tests__/drizzle-table-types.test.ts +162 -0
  48. package/src/db/__tests__/encryption.test.ts +39 -0
  49. package/src/db/__tests__/event-store-executor-list.integration.ts +313 -0
  50. package/src/db/__tests__/event-store-executor.integration.ts +235 -0
  51. package/src/db/__tests__/implicit-projection-equivalence.integration.ts +304 -0
  52. package/src/db/__tests__/located-timestamp.test.ts +184 -0
  53. package/src/db/__tests__/money.test.ts +199 -0
  54. package/src/db/__tests__/multi-row-insert.integration.ts +76 -0
  55. package/src/db/__tests__/parse-auto-verb.test.ts +70 -0
  56. package/src/db/__tests__/required-not-null-migration-safety.integration.ts +105 -0
  57. package/src/db/__tests__/row-helpers.test.ts +59 -0
  58. package/src/db/__tests__/schema-migration.integration.ts +273 -0
  59. package/src/db/__tests__/table-builder-indexes.test.ts +153 -0
  60. package/src/db/__tests__/table-builder-required.test.ts +216 -0
  61. package/src/db/__tests__/tenant-db.integration.ts +606 -0
  62. package/src/db/__tests__/unique-violation-mapping.integration.ts +166 -0
  63. package/src/db/apply-entity-event.ts +188 -0
  64. package/src/db/assert-exists-in.ts +59 -0
  65. package/src/db/compound-types.ts +47 -0
  66. package/src/db/connection.ts +104 -0
  67. package/src/db/cursor.ts +83 -0
  68. package/src/db/dialect.ts +109 -0
  69. package/src/db/eagerload.ts +174 -0
  70. package/src/db/encryption.ts +39 -0
  71. package/src/db/event-store-executor.ts +906 -0
  72. package/src/db/index.ts +55 -0
  73. package/src/db/located-timestamp.ts +114 -0
  74. package/src/db/money.ts +120 -0
  75. package/src/db/pg-error.ts +46 -0
  76. package/src/db/reference-data.ts +77 -0
  77. package/src/db/row-helpers.ts +53 -0
  78. package/src/db/schema-inspection.ts +25 -0
  79. package/src/db/table-builder.ts +475 -0
  80. package/src/db/tenant-db.ts +434 -0
  81. package/src/engine/__tests__/auth-claims-registrar.test.ts +74 -0
  82. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +108 -0
  83. package/src/engine/__tests__/boot-validator.test.ts +1865 -0
  84. package/src/engine/__tests__/build-app-schema.test.ts +154 -0
  85. package/src/engine/__tests__/claim-keys.test.ts +274 -0
  86. package/src/engine/__tests__/config-helpers.test.ts +236 -0
  87. package/src/engine/__tests__/effective-features.test.ts +86 -0
  88. package/src/engine/__tests__/engine.test.ts +1461 -0
  89. package/src/engine/__tests__/entity-handlers.test.ts +274 -0
  90. package/src/engine/__tests__/event-helpers.test.ts +68 -0
  91. package/src/engine/__tests__/extends-registrar.test.ts +159 -0
  92. package/src/engine/__tests__/factories-long-text.test.ts +84 -0
  93. package/src/engine/__tests__/factories-time.test.ts +158 -0
  94. package/src/engine/__tests__/field-predicates.test.ts +48 -0
  95. package/src/engine/__tests__/hook-phases.test.ts +132 -0
  96. package/src/engine/__tests__/identifiers.test.ts +35 -0
  97. package/src/engine/__tests__/lifecycle-hooks.test.ts +237 -0
  98. package/src/engine/__tests__/nav.test.ts +267 -0
  99. package/src/engine/__tests__/ownership.test.ts +421 -0
  100. package/src/engine/__tests__/parse-ref-target.test.ts +43 -0
  101. package/src/engine/__tests__/projection-helpers.test.ts +62 -0
  102. package/src/engine/__tests__/projection.test.ts +191 -0
  103. package/src/engine/__tests__/qualified-name.test.ts +264 -0
  104. package/src/engine/__tests__/resolve-config-or-param.test.ts +315 -0
  105. package/src/engine/__tests__/run-in.test.ts +38 -0
  106. package/src/engine/__tests__/schema-builder.test.ts +380 -0
  107. package/src/engine/__tests__/screen.test.ts +408 -0
  108. package/src/engine/__tests__/state-machine.test.ts +148 -0
  109. package/src/engine/__tests__/system-user.test.ts +57 -0
  110. package/src/engine/__tests__/validation-hooks.test.ts +71 -0
  111. package/src/engine/access.ts +23 -0
  112. package/src/engine/boot-validator.ts +1528 -0
  113. package/src/engine/build-app-schema.ts +125 -0
  114. package/src/engine/config-helpers.ts +115 -0
  115. package/src/engine/constants.ts +85 -0
  116. package/src/engine/create-app.ts +98 -0
  117. package/src/engine/define-feature.ts +702 -0
  118. package/src/engine/define-handler.ts +78 -0
  119. package/src/engine/define-roles.ts +19 -0
  120. package/src/engine/effective-features.ts +87 -0
  121. package/src/engine/entity-handlers.ts +364 -0
  122. package/src/engine/event-helpers.ts +73 -0
  123. package/src/engine/factories.ts +328 -0
  124. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +416 -0
  125. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +197 -0
  126. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +128 -0
  127. package/src/engine/feature-ast/__tests__/parse.test.ts +888 -0
  128. package/src/engine/feature-ast/__tests__/patch.test.ts +360 -0
  129. package/src/engine/feature-ast/__tests__/patcher.test.ts +469 -0
  130. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +287 -0
  131. package/src/engine/feature-ast/extractors.ts +2562 -0
  132. package/src/engine/feature-ast/index.ts +105 -0
  133. package/src/engine/feature-ast/parse.ts +369 -0
  134. package/src/engine/feature-ast/patch.ts +525 -0
  135. package/src/engine/feature-ast/patcher.ts +518 -0
  136. package/src/engine/feature-ast/patterns.ts +434 -0
  137. package/src/engine/feature-ast/render.ts +602 -0
  138. package/src/engine/feature-ast/source-location.ts +45 -0
  139. package/src/engine/field-access.ts +120 -0
  140. package/src/engine/index.ts +254 -0
  141. package/src/engine/ownership.ts +337 -0
  142. package/src/engine/parse-ref-target.ts +22 -0
  143. package/src/engine/pattern-library/__tests__/library.test.ts +351 -0
  144. package/src/engine/pattern-library/index.ts +24 -0
  145. package/src/engine/pattern-library/library.ts +1117 -0
  146. package/src/engine/pattern-library/types.ts +255 -0
  147. package/src/engine/projection-helpers.ts +85 -0
  148. package/src/engine/qualified-name.ts +122 -0
  149. package/src/engine/read-claim.ts +31 -0
  150. package/src/engine/registry.ts +1325 -0
  151. package/src/engine/resolve-config-or-param.ts +153 -0
  152. package/src/engine/run-in.ts +29 -0
  153. package/src/engine/schema-builder.ts +175 -0
  154. package/src/engine/screen-filter-ops.ts +51 -0
  155. package/src/engine/state-machine.ts +70 -0
  156. package/src/engine/system-user.ts +32 -0
  157. package/src/engine/types/config.ts +306 -0
  158. package/src/engine/types/event-type-map.ts +37 -0
  159. package/src/engine/types/feature.ts +574 -0
  160. package/src/engine/types/fields.ts +422 -0
  161. package/src/engine/types/handlers.ts +742 -0
  162. package/src/engine/types/hooks.ts +142 -0
  163. package/src/engine/types/http-route.ts +54 -0
  164. package/src/engine/types/identifiers.ts +47 -0
  165. package/src/engine/types/index.ts +208 -0
  166. package/src/engine/types/nav.ts +46 -0
  167. package/src/engine/types/projection.ts +132 -0
  168. package/src/engine/types/relations.ts +51 -0
  169. package/src/engine/types/screen.ts +452 -0
  170. package/src/engine/types/workspace.ts +42 -0
  171. package/src/engine/validation.ts +33 -0
  172. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.ts +173 -0
  173. package/src/entrypoint/__tests__/split-deploy.integration.ts +297 -0
  174. package/src/entrypoint/index.ts +442 -0
  175. package/src/errors/__tests__/classes.test.ts +371 -0
  176. package/src/errors/__tests__/write-failures.test.ts +109 -0
  177. package/src/errors/classes.ts +249 -0
  178. package/src/errors/i18n/de.yaml +83 -0
  179. package/src/errors/i18n/en.yaml +80 -0
  180. package/src/errors/index.ts +41 -0
  181. package/src/errors/kumiko-error.ts +67 -0
  182. package/src/errors/reasons.ts +36 -0
  183. package/src/errors/serialize.ts +136 -0
  184. package/src/errors/transition-details.ts +30 -0
  185. package/src/errors/write-error-info.ts +123 -0
  186. package/src/errors/zod-bridge.ts +49 -0
  187. package/src/event-store/__tests__/admin-api.integration.ts +361 -0
  188. package/src/event-store/__tests__/event-store.integration.ts +584 -0
  189. package/src/event-store/__tests__/get-stream-version-perf.integration.ts +83 -0
  190. package/src/event-store/__tests__/perf.integration.ts +255 -0
  191. package/src/event-store/__tests__/snapshot.integration.ts +267 -0
  192. package/src/event-store/__tests__/upcaster-dead-letter.integration.ts +204 -0
  193. package/src/event-store/__tests__/upcaster.integration.ts +460 -0
  194. package/src/event-store/admin-api.ts +257 -0
  195. package/src/event-store/archive.ts +106 -0
  196. package/src/event-store/errors.ts +35 -0
  197. package/src/event-store/event-store.ts +405 -0
  198. package/src/event-store/events-schema.ts +90 -0
  199. package/src/event-store/index.ts +50 -0
  200. package/src/event-store/snapshot.ts +210 -0
  201. package/src/event-store/upcaster-dead-letter.ts +119 -0
  202. package/src/event-store/upcaster.ts +147 -0
  203. package/src/files/__tests__/content-disposition.test.ts +123 -0
  204. package/src/files/__tests__/file-field-column.integration.ts +103 -0
  205. package/src/files/__tests__/file-field-pipeline.integration.ts +211 -0
  206. package/src/files/__tests__/file-handle.test.ts +122 -0
  207. package/src/files/__tests__/files.integration.ts +830 -0
  208. package/src/files/__tests__/storage-tracking.integration.ts +153 -0
  209. package/src/files/content-disposition.ts +55 -0
  210. package/src/files/file-handle.ts +63 -0
  211. package/src/files/file-ref-table.ts +22 -0
  212. package/src/files/file-routes.ts +353 -0
  213. package/src/files/in-memory-provider.ts +62 -0
  214. package/src/files/index.ts +29 -0
  215. package/src/files/local-provider.ts +35 -0
  216. package/src/files/storage-tracking.ts +60 -0
  217. package/src/files/types.ts +118 -0
  218. package/src/i18n/__tests__/i18n.test.ts +72 -0
  219. package/src/i18n/index.ts +29 -0
  220. package/src/jobs/__tests__/job-event-trigger.integration.ts +172 -0
  221. package/src/jobs/__tests__/job-multi-trigger.integration.ts +144 -0
  222. package/src/jobs/__tests__/jobs.integration.ts +566 -0
  223. package/src/jobs/index.ts +2 -0
  224. package/src/jobs/job-runner.ts +574 -0
  225. package/src/lifecycle/__tests__/create-test-lifecycle.ts +19 -0
  226. package/src/lifecycle/__tests__/lifecycle-server.integration.ts +108 -0
  227. package/src/lifecycle/__tests__/lifecycle.test.ts +212 -0
  228. package/src/lifecycle/__tests__/signal-handlers.test.ts +106 -0
  229. package/src/lifecycle/index.ts +13 -0
  230. package/src/lifecycle/lifecycle.ts +160 -0
  231. package/src/lifecycle/signal-handlers.ts +62 -0
  232. package/src/logging/__tests__/pino-trace-bridge.test.ts +50 -0
  233. package/src/logging/index.ts +3 -0
  234. package/src/logging/pino-logger.ts +64 -0
  235. package/src/logging/types.ts +7 -0
  236. package/src/migrations/__tests__/compare-snapshots.test.ts +150 -0
  237. package/src/migrations/__tests__/detect-drift.integration.ts +320 -0
  238. package/src/migrations/__tests__/detect-projections-to-rebuild.integration.ts +134 -0
  239. package/src/migrations/__tests__/rebuild-marker.test.ts +79 -0
  240. package/src/migrations/index.ts +28 -0
  241. package/src/migrations/projection-detection.ts +149 -0
  242. package/src/migrations/rebuild-marker.ts +64 -0
  243. package/src/migrations/schema-drift.ts +395 -0
  244. package/src/observability/__tests__/console-provider.test.ts +67 -0
  245. package/src/observability/__tests__/metric-validator.test.ts +87 -0
  246. package/src/observability/__tests__/noop-provider.test.ts +82 -0
  247. package/src/observability/__tests__/observability.integration.ts +559 -0
  248. package/src/observability/__tests__/prometheus-meter.test.ts +144 -0
  249. package/src/observability/__tests__/recording-meter.test.ts +101 -0
  250. package/src/observability/__tests__/recording-tracer.test.ts +110 -0
  251. package/src/observability/__tests__/sensitive-filter.test.ts +98 -0
  252. package/src/observability/console-provider.ts +130 -0
  253. package/src/observability/context.ts +26 -0
  254. package/src/observability/fallback.ts +34 -0
  255. package/src/observability/ids.ts +25 -0
  256. package/src/observability/index.ts +79 -0
  257. package/src/observability/metric-validator.ts +86 -0
  258. package/src/observability/metrics-handle.ts +56 -0
  259. package/src/observability/noop-provider.ts +146 -0
  260. package/src/observability/prometheus-meter.ts +284 -0
  261. package/src/observability/recording-meter.ts +156 -0
  262. package/src/observability/recording-tracer.ts +198 -0
  263. package/src/observability/redis-wrapper.ts +132 -0
  264. package/src/observability/sensitive-filter.ts +108 -0
  265. package/src/observability/standard-metrics.ts +213 -0
  266. package/src/observability/types/index.ts +29 -0
  267. package/src/observability/types/metric.ts +56 -0
  268. package/src/observability/types/provider.ts +32 -0
  269. package/src/observability/types/span.ts +64 -0
  270. package/src/pipeline/__tests__/archive-stream.integration.ts +220 -0
  271. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +279 -0
  272. package/src/pipeline/__tests__/cascade-handler.integration.ts +419 -0
  273. package/src/pipeline/__tests__/cascade-handler.test.ts +52 -0
  274. package/src/pipeline/__tests__/causation-chain.integration.ts +206 -0
  275. package/src/pipeline/__tests__/ctx-bridge.integration.ts +234 -0
  276. package/src/pipeline/__tests__/dispatcher.test.ts +379 -0
  277. package/src/pipeline/__tests__/distributed-lock.integration.ts +67 -0
  278. package/src/pipeline/__tests__/domain-events-projections.integration.ts +323 -0
  279. package/src/pipeline/__tests__/event-dedup.integration.ts +153 -0
  280. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +202 -0
  281. package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.ts +220 -0
  282. package/src/pipeline/__tests__/event-dispatcher-multi-instance.integration.ts +423 -0
  283. package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.ts +123 -0
  284. package/src/pipeline/__tests__/event-dispatcher-recovery.integration.ts +202 -0
  285. package/src/pipeline/__tests__/event-dispatcher-second-audit.integration.ts +290 -0
  286. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +65 -0
  287. package/src/pipeline/__tests__/event-dispatcher.integration.ts +287 -0
  288. package/src/pipeline/__tests__/event-retention.integration.ts +239 -0
  289. package/src/pipeline/__tests__/fetch-for-writing.integration.ts +281 -0
  290. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +430 -0
  291. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +266 -0
  292. package/src/pipeline/__tests__/msp-error-mode.integration.ts +149 -0
  293. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +228 -0
  294. package/src/pipeline/__tests__/msp-rebuild.integration.ts +368 -0
  295. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +341 -0
  296. package/src/pipeline/__tests__/perf-rebuild.integration.ts +147 -0
  297. package/src/pipeline/__tests__/projection-rebuild.integration.ts +551 -0
  298. package/src/pipeline/__tests__/query-projection.integration.ts +201 -0
  299. package/src/pipeline/__tests__/redis-pipeline.integration.ts +306 -0
  300. package/src/pipeline/append-event-core.ts +117 -0
  301. package/src/pipeline/auth-claims-resolver.ts +103 -0
  302. package/src/pipeline/cascade-handler.ts +113 -0
  303. package/src/pipeline/dispatcher.ts +1585 -0
  304. package/src/pipeline/distributed-lock.ts +37 -0
  305. package/src/pipeline/entity-cache.ts +113 -0
  306. package/src/pipeline/event-consumer-state.ts +108 -0
  307. package/src/pipeline/event-dedup.ts +23 -0
  308. package/src/pipeline/event-dispatcher.ts +1016 -0
  309. package/src/pipeline/event-retention.ts +154 -0
  310. package/src/pipeline/idempotency.ts +76 -0
  311. package/src/pipeline/index.ts +66 -0
  312. package/src/pipeline/lifecycle-pipeline.ts +409 -0
  313. package/src/pipeline/msp-rebuild.ts +242 -0
  314. package/src/pipeline/multi-stream-apply-context.ts +115 -0
  315. package/src/pipeline/projection-rebuild.ts +334 -0
  316. package/src/pipeline/projection-state.ts +72 -0
  317. package/src/pipeline/projections-runner.ts +56 -0
  318. package/src/pipeline/redis-keys.ts +11 -0
  319. package/src/pipeline/system-hooks.ts +190 -0
  320. package/src/random/__tests__/generate.test.ts +149 -0
  321. package/src/random/generate.ts +141 -0
  322. package/src/random/index.ts +8 -0
  323. package/src/random/words.ts +392 -0
  324. package/src/rate-limit/__tests__/dispatcher-l3.integration.ts +111 -0
  325. package/src/rate-limit/__tests__/middleware.integration.ts +189 -0
  326. package/src/rate-limit/__tests__/resolver.integration.ts +189 -0
  327. package/src/rate-limit/bucket.ts +36 -0
  328. package/src/rate-limit/index.ts +14 -0
  329. package/src/rate-limit/middleware.ts +152 -0
  330. package/src/rate-limit/resolver.ts +267 -0
  331. package/src/redis/__tests__/redis-options.test.ts +54 -0
  332. package/src/redis/index.ts +74 -0
  333. package/src/search/__tests__/meilisearch-adapter.integration.ts +236 -0
  334. package/src/search/__tests__/search-adapter.test.ts +256 -0
  335. package/src/search/in-memory-adapter.ts +123 -0
  336. package/src/search/index.ts +12 -0
  337. package/src/search/meilisearch-adapter.ts +106 -0
  338. package/src/search/types.ts +39 -0
  339. package/src/secrets/__tests__/dek-cache.test.ts +213 -0
  340. package/src/secrets/__tests__/env-master-key-provider.test.ts +119 -0
  341. package/src/secrets/__tests__/envelope.test.ts +74 -0
  342. package/src/secrets/__tests__/leak-guard.test.ts +92 -0
  343. package/src/secrets/__tests__/rotation.test.ts +149 -0
  344. package/src/secrets/dek-cache.ts +116 -0
  345. package/src/secrets/env-master-key-provider.ts +162 -0
  346. package/src/secrets/envelope.ts +55 -0
  347. package/src/secrets/index.ts +19 -0
  348. package/src/secrets/leak-guard.ts +87 -0
  349. package/src/secrets/rotation.ts +34 -0
  350. package/src/secrets/types.ts +107 -0
  351. package/src/stack/db.ts +104 -0
  352. package/src/stack/event-collector.ts +23 -0
  353. package/src/stack/index.ts +32 -0
  354. package/src/stack/redis.ts +44 -0
  355. package/src/stack/request-helper.ts +168 -0
  356. package/src/stack/table-helpers.ts +104 -0
  357. package/src/stack/test-stack.ts +357 -0
  358. package/src/stack/test-users.ts +37 -0
  359. package/src/testing/__tests__/e2e-generator.test.ts +230 -0
  360. package/src/testing/__tests__/ensure-entity-table.integration.ts +54 -0
  361. package/src/testing/access-assertions.ts +15 -0
  362. package/src/testing/assertions.ts +35 -0
  363. package/src/testing/e2e-generator.ts +465 -0
  364. package/src/testing/expect-error.ts +25 -0
  365. package/src/testing/handler-context.ts +125 -0
  366. package/src/testing/http-cookies.ts +52 -0
  367. package/src/testing/index.ts +41 -0
  368. package/src/testing/late-bound.ts +39 -0
  369. package/src/testing/mutable-master-key-provider.ts +31 -0
  370. package/src/testing/observability-recorder.ts +54 -0
  371. package/src/testing/shared-entities.ts +49 -0
  372. package/src/testing/utils.ts +1 -0
  373. package/src/testing/wait-for.ts +31 -0
  374. package/src/time/__tests__/polyfill.test.ts +73 -0
  375. package/src/time/__tests__/tz-context.test.ts +121 -0
  376. package/src/time/index.ts +21 -0
  377. package/src/time/polyfill.ts +70 -0
  378. package/src/time/tz-context.ts +107 -0
  379. package/src/ui-types/app-schema.ts +57 -0
  380. package/src/ui-types/index.ts +65 -0
  381. package/src/utils/__tests__/assert.test.ts +17 -0
  382. package/src/utils/__tests__/env-parse.test.ts +54 -0
  383. package/src/utils/assert.ts +18 -0
  384. package/src/utils/env-parse.ts +16 -0
  385. package/src/utils/ids.ts +16 -0
  386. package/src/utils/index.ts +5 -0
  387. package/src/utils/safe-json.ts +30 -0
  388. package/src/utils/serialization.ts +7 -0
@@ -0,0 +1,442 @@
1
+ // Canonical entrypoint factories for the three deploy shapes:
2
+ //
3
+ // - `createApiEntrypoint` — HTTP + SSE receiver. Does NOT start the
4
+ // event-dispatcher or job-runner. One or more instances behind a load
5
+ // balancer handle user requests.
6
+ // - `createWorkerEntrypoint` — Event-dispatcher + job-runner, no HTTP.
7
+ // Single instance (or few, SKIP LOCKED serialises them) drains events
8
+ // post-commit and runs scheduled/triggered jobs.
9
+ // - `createAllInOneEntrypoint` — both in one process. Convenient for
10
+ // dev, samples, single-tenant self-hosts; NOT recommended for scaled
11
+ // prod deploys because CPU-intensive jobs would block request
12
+ // handling.
13
+ //
14
+ // Each factory returns a unified `{ lifecycle, start, stop }` so `main.ts`
15
+ // wiring stays identical regardless of mode:
16
+ //
17
+ // const entry = createApiEntrypoint(opts);
18
+ // attachSignalHandlers(entry.lifecycle);
19
+ // await entry.start();
20
+ // serve({ fetch: entry.app.fetch, port: 3000 });
21
+ //
22
+ // The `lifecycle` handle drives graceful-shutdown LIFO; the framework
23
+ // registers its own shutdown hooks (eventDispatcher.stop, jobRunner.stop)
24
+ // in the order they were built.
25
+ //
26
+ // Known limitation (tracked in uebersicht.md Offene Follow-Ups): the
27
+ // built-in SSE broker is in-memory per process. In a split api/worker
28
+ // deploy, system-consumers that push to SSE (new-row broadcasts) run on
29
+ // the worker and therefore can't reach clients connected to the API
30
+ // instances. Either run all-in-one, put the SSE consumer on the API
31
+ // side explicitly, or wait for the Redis-Pub/Sub bridge.
32
+
33
+ import type { Hono } from "hono";
34
+ import type { AuthRoutesConfig } from "../api/auth-routes";
35
+ import type { JwtHelper } from "../api/jwt";
36
+ import type { KumikoServer, ServerOptions } from "../api/server";
37
+ import { buildServer } from "../api/server";
38
+ import type { SseBroker } from "../api/sse-broker";
39
+ import type { PgClient } from "../db/connection";
40
+ import type { AppContext, JobRunIn, Registry, RunIn } from "../engine/types";
41
+ import type { FileRoutesOptions } from "../files/file-routes";
42
+ import type { JobRunner, JobRunnerOptions } from "../jobs/job-runner";
43
+ import { createJobRunner } from "../jobs/job-runner";
44
+ import type { Lifecycle } from "../lifecycle";
45
+ import { createLifecycle } from "../lifecycle";
46
+ import type { ObservabilityOptions, ObservabilityProvider } from "../observability";
47
+ import type { EventDedup, EventDispatcher } from "../pipeline";
48
+ import type { DispatcherOptions } from "../pipeline/dispatcher";
49
+ import type { SystemHooks } from "../pipeline/lifecycle-pipeline";
50
+
51
+ // Shared fields across all three modes. A caller that swaps between
52
+ // modes can reuse the same options object.
53
+ export type BaseEntrypointOptions = {
54
+ readonly registry: Registry;
55
+ readonly context: AppContext;
56
+ readonly jwtSecret: string;
57
+ readonly jwtIssuer?: string;
58
+ readonly observability?: ObservabilityProvider;
59
+ readonly observabilityOptions?: ObservabilityOptions;
60
+ readonly dispatcherOptions?: Omit<DispatcherOptions, "lifecycle">;
61
+ readonly systemHooks?: SystemHooks;
62
+ readonly eventDedup?: EventDedup;
63
+ // Optional pre-built lifecycle. If omitted, each factory builds its own.
64
+ // Pass one in when you want to register caller-specific shutdown hooks
65
+ // alongside the framework's.
66
+ readonly lifecycle?: Lifecycle;
67
+ };
68
+
69
+ // Shape the JobRunner block takes in every entrypoint mode. Extracted so
70
+ // adding a new JobRunnerOption doesn't need three parallel changes.
71
+ type JobsBlock = {
72
+ readonly redisUrl: string;
73
+ readonly queueNamePrefix?: string;
74
+ readonly getActiveTenantIds?: JobRunnerOptions["getActiveTenantIds"];
75
+ readonly onJobStart?: JobRunnerOptions["onJobStart"];
76
+ readonly onJobComplete?: JobRunnerOptions["onJobComplete"];
77
+ readonly onJobFailed?: JobRunnerOptions["onJobFailed"];
78
+ };
79
+
80
+ export type ApiEntrypointOptions = BaseEntrypointOptions & {
81
+ readonly auth?: AuthRoutesConfig;
82
+ readonly anonymousAccess?: ServerOptions["anonymousAccess"];
83
+ readonly sseBroker?: SseBroker;
84
+ readonly files?: Omit<FileRoutesOptions, "db"> & { db?: FileRoutesOptions["db"] };
85
+ readonly rateLimit?: ServerOptions["rateLimit"];
86
+ readonly maxRequestBytes?: ServerOptions["maxRequestBytes"];
87
+ readonly readiness?: ServerOptions["readiness"];
88
+ readonly metrics?: ServerOptions["metrics"];
89
+ // Job-enqueue surface for the API process. Required whenever the registry
90
+ // defines event-triggered jobs: command-dispatcher fires handleEvent as
91
+ // an afterCommit-hook — without a jobRunner the enqueue silently drops.
92
+ //
93
+ // `runLocalJobs: true` additionally starts a BullMQ worker for the "api"
94
+ // lane inside this API process. Only useful for short, CPU-light jobs —
95
+ // a long-running handler on the API lane will starve request handlers.
96
+ readonly jobs?: JobsBlock & {
97
+ readonly runLocalJobs?: boolean;
98
+ };
99
+ };
100
+
101
+ export type WorkerEntrypointOptions = BaseEntrypointOptions &
102
+ JobsBlock & {
103
+ // Tuning knobs for the event-dispatcher loop. Workers typically set a
104
+ // pgClient so LISTEN/NOTIFY drops the poll latency from seconds to TCP
105
+ // round-trip.
106
+ readonly eventDispatcher?: ServerOptions["eventDispatcher"];
107
+ };
108
+
109
+ export type AllInOneEntrypointOptions = ApiEntrypointOptions & WorkerEntrypointOptions;
110
+
111
+ export type ApiEntrypoint = {
112
+ readonly app: Hono;
113
+ readonly jwt: JwtHelper;
114
+ readonly sseBroker: SseBroker;
115
+ readonly lifecycle: Lifecycle;
116
+ readonly observability: ObservabilityProvider;
117
+ readonly mode: "api";
118
+ // No-op on API mode — dispatcher isn't built, job-runner doesn't exist.
119
+ // Kept for a uniform call-site so `main.ts` doesn't branch on mode.
120
+ start(): Promise<void>;
121
+ stop(): Promise<void>;
122
+ };
123
+
124
+ export type WorkerEntrypoint = {
125
+ readonly lifecycle: Lifecycle;
126
+ readonly eventDispatcher: EventDispatcher;
127
+ readonly jobRunner: JobRunner;
128
+ readonly observability: ObservabilityProvider;
129
+ readonly mode: "worker";
130
+ // Starts event-dispatcher poll + BullMQ worker. SIGTERM triggers
131
+ // `lifecycle.drain()`, which stops both via registered hooks.
132
+ start(): Promise<void>;
133
+ stop(): Promise<void>;
134
+ };
135
+
136
+ export type AllInOneEntrypoint = Omit<ApiEntrypoint, "mode"> &
137
+ Omit<WorkerEntrypoint, "mode"> & {
138
+ readonly mode: "all-in-one";
139
+ };
140
+
141
+ // --- Shared builders ------------------------------------------------------
142
+ //
143
+ // Three factories, three near-identical buildServer() + createJobRunner()
144
+ // blocks. Extract once so adding a new ServerOptions field doesn't need
145
+ // three parallel edits — one helper, one place to maintain.
146
+
147
+ // Merge an internally-built jobRunner into the caller's dispatcherOptions
148
+ // so the command-dispatcher fires handleEvent as an afterCommit-hook for
149
+ // event-triggered jobs (dispatcher.ts:997). Without this plumbing,
150
+ // `r.job({ trigger: { on: … } })` silently drops on every write — that
151
+ // was the hidden Welle-2.5 gap.
152
+ //
153
+ // Caller-supplied `dispatcherOptions.jobRunner` wins (tests sometimes
154
+ // inject a mock runner directly).
155
+ function mergeDispatcherOptions(
156
+ caller: Omit<DispatcherOptions, "lifecycle"> | undefined,
157
+ jobRunner: JobRunner | undefined,
158
+ ): Omit<DispatcherOptions, "lifecycle"> | undefined {
159
+ if (!jobRunner) return caller;
160
+ if (caller?.jobRunner) return caller;
161
+ return { ...(caller ?? {}), jobRunner };
162
+ }
163
+
164
+ // buildApiServer shapes ServerOptions from API-mode caller-options.
165
+ // AllInOneEntrypointOptions extends ApiEntrypointOptions, so structural
166
+ // subtyping makes the all-in-one path a valid caller without an explicit
167
+ // union. `dispatcherOverride` lets API-mode slam `{disabled:true}` while
168
+ // All-in-one passes the caller's real config through. `jobRunner`, when
169
+ // present, is merged into dispatcherOptions so the command-dispatcher can
170
+ // fire event-triggered jobs.
171
+ function buildApiServer(
172
+ opts: ApiEntrypointOptions,
173
+ lifecycle: Lifecycle,
174
+ dispatcherOverride: ServerOptions["eventDispatcher"] | undefined,
175
+ jobRunner: JobRunner | undefined,
176
+ processLane: RunIn,
177
+ ): KumikoServer {
178
+ const dispatcherOptions = mergeDispatcherOptions(opts.dispatcherOptions, jobRunner);
179
+ return buildServer({
180
+ registry: opts.registry,
181
+ context: opts.context,
182
+ jwtSecret: opts.jwtSecret,
183
+ lifecycle,
184
+ processLane,
185
+ jwtIssuer: opts.jwtIssuer,
186
+ auth: opts.auth,
187
+ anonymousAccess: opts.anonymousAccess,
188
+ files: opts.files,
189
+ sseBroker: opts.sseBroker,
190
+ rateLimit: opts.rateLimit,
191
+ maxRequestBytes: opts.maxRequestBytes,
192
+ readiness: opts.readiness,
193
+ metrics: opts.metrics,
194
+ observability: opts.observability,
195
+ observabilityOptions: opts.observabilityOptions,
196
+ dispatcherOptions,
197
+ systemHooks: opts.systemHooks,
198
+ eventDedup: opts.eventDedup,
199
+ eventDispatcher: dispatcherOverride,
200
+ });
201
+ }
202
+
203
+ // Worker path is narrower — no HTTP-specific options. `eventDispatcher`
204
+ // comes straight from the caller (LISTEN/NOTIFY wiring, pollIntervalMs).
205
+ // `processLane` is "worker" — any MSP with runIn="api" gets filtered out
206
+ // of this process's dispatcher.
207
+ function buildWorkerServer(
208
+ opts: WorkerEntrypointOptions,
209
+ lifecycle: Lifecycle,
210
+ jobRunner: JobRunner,
211
+ ): KumikoServer {
212
+ const dispatcherOptions = mergeDispatcherOptions(opts.dispatcherOptions, jobRunner);
213
+ return buildServer({
214
+ registry: opts.registry,
215
+ context: opts.context,
216
+ jwtSecret: opts.jwtSecret,
217
+ lifecycle,
218
+ processLane: "worker",
219
+ observability: opts.observability,
220
+ observabilityOptions: opts.observabilityOptions,
221
+ dispatcherOptions,
222
+ systemHooks: opts.systemHooks,
223
+ eventDedup: opts.eventDedup,
224
+ eventDispatcher: opts.eventDispatcher,
225
+ });
226
+ }
227
+
228
+ // Build a lane-scoped JobRunner AND register its stop-hook on the lifecycle.
229
+ // Hook order (LIFO): jobRunner runs BEFORE eventDispatcher so no in-flight
230
+ // job tries to enqueue an event to an already-torn-down dispatcher.
231
+ // buildServer registers the dispatcher hook earlier in the factory, so
232
+ // this one lands later in registration order → runs first on drain.
233
+ //
234
+ // `consumerLane` = "api" | "worker" starts a BullMQ worker for that lane's
235
+ // queue plus cron/boot scheduling for lane-matching jobs. `undefined`
236
+ // builds an enqueuer-only runner: holds queue-clients for both lanes so
237
+ // dispatch()/handleEvent() route per jobDef.runIn, but starts no local
238
+ // consumer. Used by the API process when `runLocalJobs` is false.
239
+ function buildJobRunnerWithHook(
240
+ registry: Registry,
241
+ context: AppContext,
242
+ jobs: JobsBlock,
243
+ consumerLane: JobRunIn | undefined,
244
+ lifecycle: Lifecycle,
245
+ hookName: string,
246
+ ): JobRunner {
247
+ const jobRunner = createJobRunner({
248
+ registry,
249
+ context,
250
+ redisUrl: jobs.redisUrl,
251
+ consumerLane,
252
+ queueNamePrefix: jobs.queueNamePrefix,
253
+ getActiveTenantIds: jobs.getActiveTenantIds,
254
+ onJobStart: jobs.onJobStart,
255
+ onJobComplete: jobs.onJobComplete,
256
+ onJobFailed: jobs.onJobFailed,
257
+ });
258
+ lifecycle.registerShutdownHook(hookName, async () => {
259
+ await jobRunner.stop();
260
+ });
261
+ return jobRunner;
262
+ }
263
+
264
+ // A worker-shaped process with no consumers (no SSE, no search adapter,
265
+ // no MSPs) has nothing to drain — that's a caller-config bug, not a
266
+ // usable process shape. Fail loud so ops sees it before prod takes
267
+ // traffic from an API that enqueues events nobody consumes.
268
+ function requireDispatcher(server: KumikoServer, mode: string): EventDispatcher {
269
+ if (!server.eventDispatcher) {
270
+ throw new Error(
271
+ `[entrypoint] ${mode} mode requires at least one event consumer (SSE broker, search adapter, or r.multiStreamProjection)`,
272
+ );
273
+ }
274
+ return server.eventDispatcher;
275
+ }
276
+
277
+ // --- API entrypoint -------------------------------------------------------
278
+
279
+ export function createApiEntrypoint(options: ApiEntrypointOptions): ApiEntrypoint {
280
+ const lifecycle = options.lifecycle ?? createLifecycle({ startReady: true });
281
+
282
+ // Boot-validation (Welle 2.6.c) — fail loud before traffic arrives:
283
+ // (a) Any jobs declared + no jobs-block → command-dispatcher would
284
+ // silently drop every event-triggered enqueue. Fix: add jobs:
285
+ // { redisUrl } so the API holds lane-queue-clients.
286
+ // (b) A job with runIn="api" + runLocalJobs !== true → the API
287
+ // process is the ONLY container that can consume "api"-lane
288
+ // queues (workers only consume "worker"). Without runLocalJobs,
289
+ // the job would enqueue and stay pending forever.
290
+ const allJobs = [...options.registry.getAllJobs().values()];
291
+ if (allJobs.length > 0 && !options.jobs) {
292
+ throw new Error(
293
+ `[entrypoint] createApiEntrypoint: registry declares ${allJobs.length} job(s) but no \`jobs\` block was passed. ` +
294
+ `Event-triggered writes would silently drop their enqueue. Add \`jobs: { redisUrl: ... }\` to createApiEntrypoint options.`,
295
+ );
296
+ }
297
+ if (options.jobs && !options.jobs.runLocalJobs) {
298
+ const apiOnlyJobs = allJobs.filter((j) => j.runIn === "api").map((j) => j.name);
299
+ if (apiOnlyJobs.length > 0) {
300
+ throw new Error(
301
+ `[entrypoint] createApiEntrypoint: ${apiOnlyJobs.length} job(s) declared runIn="api" but runLocalJobs is not set — these jobs would have no consumer. ` +
302
+ `Set \`jobs: { runLocalJobs: true, ... }\` or change the jobs' runIn to "worker". Affected: ${apiOnlyJobs.join(", ")}`,
303
+ );
304
+ }
305
+ }
306
+
307
+ // When the app declares any jobs, the API process needs a job-enqueuer
308
+ // so event-triggered jobs fired as afterCommit-hooks of a write reach
309
+ // the queue at all. `runLocalJobs: true` upgrades the enqueuer to a full
310
+ // runner that also consumes the "api" lane's queue in-process.
311
+ const apiJobRunner = options.jobs
312
+ ? buildJobRunnerWithHook(
313
+ options.registry,
314
+ options.context,
315
+ options.jobs,
316
+ options.jobs.runLocalJobs ? "api" : undefined,
317
+ lifecycle,
318
+ "jobRunner",
319
+ )
320
+ : undefined;
321
+
322
+ // `{disabled:true}` skips dispatcher creation entirely — an API process
323
+ // doesn't hold an idle poller.
324
+ const server = buildApiServer(options, lifecycle, { disabled: true }, apiJobRunner, "api");
325
+
326
+ return {
327
+ app: server.app,
328
+ jwt: server.jwt,
329
+ sseBroker: server.sseBroker,
330
+ lifecycle,
331
+ observability: server.observability,
332
+ mode: "api",
333
+ async start() {
334
+ // Start the local BullMQ worker when runLocalJobs=true; enqueuer-only
335
+ // runners have a no-op .start() by design (JobRunner skips worker
336
+ // creation when consumerLane is undefined).
337
+ if (apiJobRunner) await apiJobRunner.start();
338
+ },
339
+ async stop() {
340
+ await lifecycle.drain();
341
+ },
342
+ };
343
+ }
344
+
345
+ // --- Worker entrypoint ----------------------------------------------------
346
+
347
+ export function createWorkerEntrypoint(options: WorkerEntrypointOptions): WorkerEntrypoint {
348
+ const lifecycle = options.lifecycle ?? createLifecycle({ startReady: true });
349
+ const jobRunner = buildJobRunnerWithHook(
350
+ options.registry,
351
+ options.context,
352
+ options,
353
+ "worker",
354
+ lifecycle,
355
+ "jobRunner",
356
+ );
357
+ const server = buildWorkerServer(options, lifecycle, jobRunner);
358
+ const eventDispatcher = requireDispatcher(server, "worker");
359
+
360
+ return {
361
+ lifecycle,
362
+ eventDispatcher,
363
+ jobRunner,
364
+ observability: server.observability,
365
+ mode: "worker",
366
+ async start() {
367
+ await eventDispatcher.start();
368
+ await jobRunner.start();
369
+ },
370
+ async stop() {
371
+ await lifecycle.drain();
372
+ },
373
+ };
374
+ }
375
+
376
+ // --- All-in-one entrypoint ------------------------------------------------
377
+
378
+ export function createAllInOneEntrypoint(options: AllInOneEntrypointOptions): AllInOneEntrypoint {
379
+ const lifecycle = options.lifecycle ?? createLifecycle({ startReady: true });
380
+
381
+ // All-in-one consumes BOTH lanes: two runners, each with a BullMQ worker
382
+ // for its own lane's queue. Both runners hold queue-clients for both
383
+ // lanes, so dispatch()/handleEvent() always route per jobDef.runIn —
384
+ // picking either runner as the dispatcher's enqueuer surface would work.
385
+ // The worker runner wins the dispatcherOptions slot by convention (that's
386
+ // where the majority of jobs live). Each runner handles cron/boot for
387
+ // its own lane in its own .start().
388
+ const workerJobRunner = buildJobRunnerWithHook(
389
+ options.registry,
390
+ options.context,
391
+ options,
392
+ "worker",
393
+ lifecycle,
394
+ "jobRunner",
395
+ );
396
+ const apiJobRunner = buildJobRunnerWithHook(
397
+ options.registry,
398
+ options.context,
399
+ options,
400
+ "api",
401
+ lifecycle,
402
+ "jobRunnerApi",
403
+ );
404
+
405
+ // Same builder as the API path — but WITH the caller's eventDispatcher
406
+ // config instead of `{disabled:true}`, so buildServer wires the poller
407
+ // alongside the HTTP app. processLane "both" disables MSP lane-filter
408
+ // entirely: all-in-one is a single process that fills every role, so
409
+ // every MSP (api-only, worker-only, both) must run here.
410
+ const server = buildApiServer(
411
+ options,
412
+ lifecycle,
413
+ options.eventDispatcher,
414
+ workerJobRunner,
415
+ "both",
416
+ );
417
+ const eventDispatcher = requireDispatcher(server, "all-in-one");
418
+
419
+ return {
420
+ app: server.app,
421
+ jwt: server.jwt,
422
+ sseBroker: server.sseBroker,
423
+ lifecycle,
424
+ eventDispatcher,
425
+ jobRunner: workerJobRunner,
426
+ observability: server.observability,
427
+ mode: "all-in-one",
428
+ async start() {
429
+ await eventDispatcher.start();
430
+ await workerJobRunner.start();
431
+ await apiJobRunner.start();
432
+ },
433
+ async stop() {
434
+ await lifecycle.drain();
435
+ },
436
+ };
437
+ }
438
+
439
+ // Keep PgClient imported so TS sees the import as used when callers take
440
+ // our re-exported ServerOptions.eventDispatcher type (which references it).
441
+ // Pure re-export of the concrete type is enough to anchor it.
442
+ export type { PgClient };