@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,173 @@
1
+ // Regression test for Welle 2.6.b — the `mergeDispatcherOptions` plumbing.
2
+ //
3
+ // The command-dispatcher (packages/framework/src/pipeline/dispatcher.ts:997)
4
+ // fires `jobRunner.handleEvent` as an afterCommit-hook. Without a jobRunner
5
+ // reference IN the dispatcher's options, event-triggered jobs silently drop
6
+ // on every write. Welle 2.5 built the jobRunner in the entrypoint factory
7
+ // but never wired it in — welle 2.6.b closes that via mergeDispatcherOptions.
8
+ //
9
+ // `job-event-trigger.integration.ts` only covers the path when the CALLER
10
+ // hand-wires `dispatcherOptions: { jobRunner }`. This test pins the
11
+ // auto-wiring path that a real app boot uses (`createAllInOneEntrypoint`) —
12
+ // so a future refactor that drops the merge fails here instead of silently
13
+ // regressing to the Welle-2.5 state.
14
+
15
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
16
+ import { z } from "zod";
17
+ import { createRegistry, defineFeature } from "../../engine";
18
+ import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
19
+ import { createEventConsumerStateTable } from "../../pipeline";
20
+ import { createTestDb, createTestRedis, type TestDb, type TestRedis, TestUsers } from "../../stack";
21
+ import { waitFor } from "../../testing";
22
+ import { createAllInOneEntrypoint } from "../index";
23
+
24
+ const jobRuns: Array<{ name: string; payload: Record<string, unknown> }> = [];
25
+
26
+ const wiringFeature = defineFeature("wiring", (r) => {
27
+ r.writeHandler(
28
+ "order:create",
29
+ z.object({ sku: z.string() }),
30
+ async (event) => ({
31
+ isSuccess: true as const,
32
+ data: { id: 1, sku: event.payload.sku },
33
+ }),
34
+ { access: { openToAll: true } },
35
+ );
36
+ r.job(
37
+ "record-order",
38
+ { trigger: { on: "wiring:write:order:create" }, runIn: "worker" },
39
+ async (payload) => {
40
+ jobRuns.push({ name: "wiring:job:record-order", payload });
41
+ },
42
+ );
43
+ });
44
+
45
+ // Mixed-lane feature: one worker-lane + one api-lane job triggered on the
46
+ // same event. The all-in-one process spins up both BullMQ workers (one per
47
+ // queue) and both must execute. Proves the two-runner construction in
48
+ // createAllInOneEntrypoint actually consumes both lanes — not just that
49
+ // enqueue routing works.
50
+ const mixedLaneFeature = defineFeature("mixed", (r) => {
51
+ r.writeHandler(
52
+ "ping",
53
+ z.object({ msg: z.string() }),
54
+ async (event) => ({
55
+ isSuccess: true as const,
56
+ data: { id: 1, msg: event.payload.msg },
57
+ }),
58
+ { access: { openToAll: true } },
59
+ );
60
+ r.job(
61
+ "handle-on-worker",
62
+ { trigger: { on: "mixed:write:ping" }, runIn: "worker" },
63
+ async (payload) => {
64
+ jobRuns.push({ name: "mixed:job:handle-on-worker", payload });
65
+ },
66
+ );
67
+ r.job("handle-on-api", { trigger: { on: "mixed:write:ping" }, runIn: "api" }, async (payload) => {
68
+ jobRuns.push({ name: "mixed:job:handle-on-api", payload });
69
+ });
70
+ });
71
+
72
+ const JWT = "entrypoint-wiring-test-secret-must-be-32-chars!";
73
+ const adminUser = TestUsers.admin;
74
+
75
+ let testDb: TestDb;
76
+ let testRedis: TestRedis;
77
+
78
+ beforeAll(async () => {
79
+ [testDb, testRedis] = await Promise.all([createTestDb(), createTestRedis()]);
80
+ await createEventsTable(testDb.db);
81
+ await createArchivedStreamsTable(testDb.db);
82
+ await createEventConsumerStateTable(testDb.db);
83
+ });
84
+
85
+ afterAll(async () => {
86
+ await Promise.all([testDb.cleanup(), testRedis.cleanup()]);
87
+ });
88
+
89
+ describe("createAllInOneEntrypoint auto-wires jobRunner into command-dispatcher", () => {
90
+ test("event-triggered job runs end-to-end (HTTP write → afterCommit → BullMQ → handler)", async () => {
91
+ jobRuns.length = 0;
92
+ const registry = createRegistry([wiringFeature]);
93
+ const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
94
+ const entry = createAllInOneEntrypoint({
95
+ registry,
96
+ context: { db: testDb.db, redis: testRedis.redis },
97
+ jwtSecret: JWT,
98
+ redisUrl,
99
+ queueNamePrefix: `wiring-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
100
+ });
101
+ await entry.start();
102
+ try {
103
+ const token = await entry.jwt.sign(adminUser);
104
+ const res = await entry.app.request("/api/write", {
105
+ method: "POST",
106
+ headers: {
107
+ "Content-Type": "application/json",
108
+ Authorization: `Bearer ${token}`,
109
+ },
110
+ body: JSON.stringify({
111
+ type: "wiring:write:order:create",
112
+ payload: { sku: "W-1" },
113
+ }),
114
+ });
115
+ const result = (await res.json()) as { isSuccess: boolean };
116
+ expect(result.isSuccess).toBe(true);
117
+
118
+ // If mergeDispatcherOptions ever stops wiring the jobRunner, the
119
+ // afterCommit-hook at dispatcher.ts:997 becomes a no-op, the job
120
+ // is never enqueued, and waitFor times out. That's the regression
121
+ // this test is here to catch.
122
+ await waitFor(() => {
123
+ const run = jobRuns.find((e) => e.name === "wiring:job:record-order");
124
+ expect(run).toBeDefined();
125
+ expect(run?.payload["sku"]).toBe("W-1");
126
+ });
127
+ } finally {
128
+ await entry.stop();
129
+ }
130
+ });
131
+
132
+ test("all-in-one runs BOTH lane workers — api-lane + worker-lane jobs fire on the same event", async () => {
133
+ jobRuns.length = 0;
134
+ const registry = createRegistry([mixedLaneFeature]);
135
+ const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
136
+ const entry = createAllInOneEntrypoint({
137
+ registry,
138
+ context: { db: testDb.db, redis: testRedis.redis },
139
+ jwtSecret: JWT,
140
+ redisUrl,
141
+ queueNamePrefix: `wiring-mixed-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
142
+ });
143
+ await entry.start();
144
+ try {
145
+ const token = await entry.jwt.sign(adminUser);
146
+ const res = await entry.app.request("/api/write", {
147
+ method: "POST",
148
+ headers: {
149
+ "Content-Type": "application/json",
150
+ Authorization: `Bearer ${token}`,
151
+ },
152
+ body: JSON.stringify({ type: "mixed:write:ping", payload: { msg: "hi" } }),
153
+ });
154
+ const result = (await res.json()) as { isSuccess: boolean };
155
+ expect(result.isSuccess).toBe(true);
156
+
157
+ // Both jobs must fire: worker-lane runner picks kumiko-jobs-<prefix>-worker,
158
+ // api-lane runner picks kumiko-jobs-<prefix>-api. If either BullMQ
159
+ // worker failed to start (bug in the two-runner build in createAllInOne
160
+ // Entrypoint), waitFor times out on the missing entry.
161
+ await waitFor(() => {
162
+ const workerRun = jobRuns.find((e) => e.name === "mixed:job:handle-on-worker");
163
+ const apiRun = jobRuns.find((e) => e.name === "mixed:job:handle-on-api");
164
+ expect(workerRun).toBeDefined();
165
+ expect(apiRun).toBeDefined();
166
+ expect(workerRun?.payload["msg"]).toBe("hi");
167
+ expect(apiRun?.payload["msg"]).toBe("hi");
168
+ });
169
+ } finally {
170
+ await entry.stop();
171
+ }
172
+ });
173
+ });
@@ -0,0 +1,297 @@
1
+ // Shape-level proof of the three entrypoint factories. End-to-end
2
+ // split-deploy (API writes → worker MSP applies) requires the full
3
+ // test-stack infrastructure (consumer-state table, MSP wiring, dispatcher
4
+ // options) and is better exercised via a dedicated sample app. Here we
5
+ // pin the public guarantees:
6
+ //
7
+ // 1. API entrypoint has no eventDispatcher/jobRunner handles.
8
+ // 2. Worker entrypoint has no HTTP app.
9
+ // 3. All-in-one has both.
10
+ // 4. Worker throws when there's literally nothing to consume (defensive
11
+ // guard — buildServer always wires an SSE consumer so this only
12
+ // fires with systemConsumers explicitly disabled).
13
+
14
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
15
+ import { z } from "zod";
16
+ import { createRegistry, defineFeature } from "../../engine";
17
+ import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
18
+ import { createEventConsumerStateTable } from "../../pipeline";
19
+ import { createTestDb, createTestRedis, type TestDb, type TestRedis } from "../../stack";
20
+ import { createAllInOneEntrypoint, createApiEntrypoint, createWorkerEntrypoint } from "../index";
21
+
22
+ const splitFeature = defineFeature("split", (r) => {
23
+ const tick = r.defineEvent("tick", z.object({ note: z.string() }), { version: 1 });
24
+ r.multiStreamProjection({
25
+ name: "spy",
26
+ apply: {
27
+ [tick.name]: async () => {},
28
+ },
29
+ });
30
+ });
31
+
32
+ const JWT = "split-deploy-test-secret-must-be-32-chars!!";
33
+
34
+ // Per-test queue-name with a random suffix. Date.now() alone collided
35
+ // in jobs.integration.ts when two tests landed in the same millisecond —
36
+ // the random suffix pins each test's BullMQ queues even then.
37
+ function uniquePrefix(label: string): string {
38
+ return `${label}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
39
+ }
40
+
41
+ let testDb: TestDb;
42
+ let testRedis: TestRedis;
43
+
44
+ beforeAll(async () => {
45
+ [testDb, testRedis] = await Promise.all([createTestDb(), createTestRedis()]);
46
+ await createEventsTable(testDb.db);
47
+ await createArchivedStreamsTable(testDb.db);
48
+ await createEventConsumerStateTable(testDb.db);
49
+ });
50
+
51
+ afterAll(async () => {
52
+ await Promise.all([testDb.cleanup(), testRedis.cleanup()]);
53
+ });
54
+
55
+ describe("entrypoint factories", () => {
56
+ test("API entrypoint exposes HTTP app but NOT dispatcher / jobRunner handles", async () => {
57
+ const registry = createRegistry([splitFeature]);
58
+ const api = createApiEntrypoint({
59
+ registry,
60
+ context: { db: testDb.db, redis: testRedis.redis },
61
+ jwtSecret: JWT,
62
+ });
63
+
64
+ expect(api.mode).toBe("api");
65
+ expect(api.app).toBeDefined();
66
+ expect(api.jwt).toBeDefined();
67
+ expect(api.sseBroker).toBeDefined();
68
+ expect("eventDispatcher" in api).toBe(false);
69
+ expect("jobRunner" in api).toBe(false);
70
+
71
+ await api.start(); // no-op
72
+ await api.stop();
73
+ });
74
+
75
+ test("Worker entrypoint exposes dispatcher + jobRunner but NOT an HTTP app", async () => {
76
+ const registry = createRegistry([splitFeature]);
77
+ const redisUrl = process.env["REDIS_URL"] ?? "redis://localhost:16379";
78
+ const worker = createWorkerEntrypoint({
79
+ registry,
80
+ context: { db: testDb.db, redis: testRedis.redis },
81
+ jwtSecret: JWT,
82
+ redisUrl,
83
+ });
84
+
85
+ expect(worker.mode).toBe("worker");
86
+ expect(worker.eventDispatcher).toBeDefined();
87
+ expect(worker.jobRunner).toBeDefined();
88
+ expect("app" in worker).toBe(false);
89
+ expect("jwt" in worker).toBe(false);
90
+
91
+ // Stop without starting — lifecycle.drain runs the registered
92
+ // jobRunner hook which must be idempotent against an unstarted
93
+ // BullMQ worker.
94
+ await worker.stop();
95
+ });
96
+
97
+ test("All-in-one entrypoint has both HTTP surface and background workers", async () => {
98
+ const registry = createRegistry([splitFeature]);
99
+ const redisUrl = process.env["REDIS_URL"] ?? "redis://localhost:16379";
100
+ const entry = createAllInOneEntrypoint({
101
+ registry,
102
+ context: { db: testDb.db, redis: testRedis.redis },
103
+ jwtSecret: JWT,
104
+ redisUrl,
105
+ });
106
+
107
+ expect(entry.mode).toBe("all-in-one");
108
+ expect(entry.app).toBeDefined();
109
+ expect(entry.eventDispatcher).toBeDefined();
110
+ expect(entry.jobRunner).toBeDefined();
111
+
112
+ await entry.stop();
113
+ });
114
+
115
+ test("lifecycle.drain() flips /health/ready to 503 across modes", async () => {
116
+ const registry = createRegistry([splitFeature]);
117
+ const api = createApiEntrypoint({
118
+ registry,
119
+ context: { db: testDb.db, redis: testRedis.redis },
120
+ jwtSecret: JWT,
121
+ });
122
+
123
+ const before = await api.app.request("/health/ready");
124
+ expect(before.status).toBe(200);
125
+
126
+ await api.stop();
127
+
128
+ const after = await api.app.request("/health/ready");
129
+ expect(after.status).toBe(503);
130
+ });
131
+ });
132
+
133
+ // --- Welle 2.6.b: runIn lane-filtering ---
134
+ //
135
+ // Covers the per-lane consumer filtering added to buildServer. The feature
136
+ // declares three MSPs, one per runIn value, plus jobs with different runIns.
137
+ // We observe the number of consumers the dispatcher actually wires in each
138
+ // mode — the mechanism is "MSPs whose runIn isn't eligible for this
139
+ // process's lane are skipped during buildServer()".
140
+
141
+ const laneFeature = defineFeature("lane", (r) => {
142
+ const ping = r.defineEvent("ping", z.object({}), { version: 1 });
143
+ // Three MSPs: one pinned to api, one to worker (explicit), one to both.
144
+ // A fourth would be a default-undefined-runIn MSP which resolves to
145
+ // "worker" — covered implicitly by the worker test below.
146
+ r.multiStreamProjection({
147
+ name: "lane-api",
148
+ runIn: "api",
149
+ apply: { [ping.name]: async () => {} },
150
+ });
151
+ r.multiStreamProjection({
152
+ name: "lane-worker",
153
+ runIn: "worker",
154
+ apply: { [ping.name]: async () => {} },
155
+ });
156
+ r.multiStreamProjection({
157
+ name: "lane-both",
158
+ runIn: "both",
159
+ apply: { [ping.name]: async () => {} },
160
+ });
161
+ // Default runIn (= "worker") — makes the legacy-no-runIn path observable.
162
+ r.multiStreamProjection({
163
+ name: "lane-default",
164
+ apply: { [ping.name]: async () => {} },
165
+ });
166
+ });
167
+
168
+ describe("runIn lane-filtering (Welle 2.6.b)", () => {
169
+ test("API entrypoint with runLocalJobs filters MSPs to runIn ∈ {api, both}", async () => {
170
+ const registry = createRegistry([laneFeature]);
171
+ const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
172
+ const api = createApiEntrypoint({
173
+ registry,
174
+ context: { db: testDb.db, redis: testRedis.redis },
175
+ jwtSecret: JWT,
176
+ // Force the dispatcher to actually build by clearing {disabled:true} —
177
+ // needs a jobs block because the API otherwise has no consumer at all
178
+ // once SSE is the default-on system-consumer. We still observe MSP
179
+ // count via the registered consumers; JobRunner is incidental here.
180
+ jobs: { redisUrl, queueNamePrefix: uniquePrefix("split-api"), runLocalJobs: true },
181
+ });
182
+
183
+ try {
184
+ // API defaults the dispatcher to disabled, so eventDispatcher is not
185
+ // in the return shape. Assert the shape contract plus the fact that
186
+ // start() is a real operation now (runLocalJobs started a worker).
187
+ expect(api.mode).toBe("api");
188
+ expect("eventDispatcher" in api).toBe(false);
189
+ expect("jobRunner" in api).toBe(false);
190
+ await api.start();
191
+ } finally {
192
+ await api.stop();
193
+ }
194
+ });
195
+
196
+ test("Worker entrypoint runs lane-worker + lane-both + lane-default, skips lane-api", async () => {
197
+ const registry = createRegistry([laneFeature]);
198
+ const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
199
+ const worker = createWorkerEntrypoint({
200
+ registry,
201
+ context: { db: testDb.db, redis: testRedis.redis },
202
+ jwtSecret: JWT,
203
+ redisUrl,
204
+ queueNamePrefix: uniquePrefix("split-worker"),
205
+ });
206
+
207
+ // eventDispatcher.consumers exposes the filtered list — lane-api must
208
+ // be absent, the other three present. SSE + Search system-consumers
209
+ // add noise (both default-on), so we only check MSP names.
210
+ const consumerNames = worker.eventDispatcher.consumers.map((c) => c.name);
211
+ expect(consumerNames).toContain("lane:projection:lane-worker");
212
+ expect(consumerNames).toContain("lane:projection:lane-both");
213
+ expect(consumerNames).toContain("lane:projection:lane-default");
214
+ expect(consumerNames).not.toContain("lane:projection:lane-api");
215
+
216
+ await worker.stop();
217
+ });
218
+
219
+ test("All-in-one runs every MSP — processLane 'both' disables the filter", async () => {
220
+ const registry = createRegistry([laneFeature]);
221
+ const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
222
+ const entry = createAllInOneEntrypoint({
223
+ registry,
224
+ context: { db: testDb.db, redis: testRedis.redis },
225
+ jwtSecret: JWT,
226
+ redisUrl,
227
+ queueNamePrefix: uniquePrefix("split-all"),
228
+ });
229
+
230
+ const consumerNames = entry.eventDispatcher.consumers.map((c) => c.name);
231
+ expect(consumerNames).toContain("lane:projection:lane-api");
232
+ expect(consumerNames).toContain("lane:projection:lane-worker");
233
+ expect(consumerNames).toContain("lane:projection:lane-both");
234
+ expect(consumerNames).toContain("lane:projection:lane-default");
235
+
236
+ await entry.stop();
237
+ });
238
+ });
239
+
240
+ // --- Welle 2.6.c: boot-validation ---
241
+ //
242
+ // Jobs with runIn="api" can only be consumed by an API process that has
243
+ // runLocalJobs=true — workers never look at the "api" queue. The entrypoint
244
+ // factory must refuse to start with a config that would orphan those jobs,
245
+ // because otherwise an operator finds out at traffic time via "jobs
246
+ // enqueue fine, nothing runs".
247
+
248
+ const apiJobFeature = defineFeature("api-jobs", (r) => {
249
+ r.job("local-cleanup", { trigger: { manual: true }, runIn: "api" }, async () => {});
250
+ });
251
+
252
+ const workerJobFeature = defineFeature("worker-jobs", (r) => {
253
+ r.job("heavy", { trigger: { manual: true }, runIn: "worker" }, async () => {});
254
+ });
255
+
256
+ describe("createApiEntrypoint boot-validation (Welle 2.6.c)", () => {
257
+ test("declared jobs + no jobs-block → fails fast (enqueue would drop)", () => {
258
+ const registry = createRegistry([workerJobFeature]);
259
+ expect(() =>
260
+ createApiEntrypoint({
261
+ registry,
262
+ context: { db: testDb.db, redis: testRedis.redis },
263
+ jwtSecret: JWT,
264
+ }),
265
+ ).toThrow(/no `jobs` block was passed.*event-triggered writes would silently drop/i);
266
+ });
267
+
268
+ test("runIn='api' jobs require runLocalJobs=true on the api entrypoint", () => {
269
+ const registry = createRegistry([apiJobFeature]);
270
+ const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
271
+ expect(() =>
272
+ createApiEntrypoint({
273
+ registry,
274
+ context: { db: testDb.db, redis: testRedis.redis },
275
+ jwtSecret: JWT,
276
+ jobs: { redisUrl, queueNamePrefix: uniquePrefix("val") },
277
+ }),
278
+ ).toThrow(/runIn="api".*runLocalJobs.*no consumer.*local-cleanup/is);
279
+ });
280
+
281
+ test("runIn='worker' jobs do NOT require runLocalJobs — api is enqueuer-only", async () => {
282
+ const registry = createRegistry([workerJobFeature]);
283
+ const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
284
+ const api = createApiEntrypoint({
285
+ registry,
286
+ context: { db: testDb.db, redis: testRedis.redis },
287
+ jwtSecret: JWT,
288
+ jobs: { redisUrl, queueNamePrefix: uniquePrefix("val-ok") },
289
+ });
290
+ try {
291
+ expect(api.mode).toBe("api");
292
+ await api.start();
293
+ } finally {
294
+ await api.stop();
295
+ }
296
+ });
297
+ });