@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,257 @@
1
+ // Event-Store Admin-API — Marten-Bypass für Legacy-Daten-Importe.
2
+ // Spec + Verhalten: docs/plans/features/event-store-admin-api.md
3
+ //
4
+ // Guard-Rail: dieses Modul ist NICHT aus event-store/index.ts re-exportiert.
5
+ // Import nur via deep-path `@cosmicdrift/kumiko-framework/event-store/admin-api`. Das
6
+ // Guard-Script `scripts/guard-admin-api.ts` blockt Aufrufe aus App-Code —
7
+ // Allowlist: samples/*/migration/, scripts/migrations/, die Definition
8
+ // selbst, das Guard-Script selbst.
9
+
10
+ import { sql } from "drizzle-orm";
11
+ import type { DbRunner } from "../db";
12
+ import { isUniqueViolation } from "../db/pg-error";
13
+ import type { TenantId } from "../engine/types";
14
+ import { VersionConflictError } from "./errors";
15
+ import type { EventMetadata } from "./event-store";
16
+ import { eventsTable } from "./events-schema";
17
+
18
+ export type RawEventToAppend = {
19
+ readonly aggregateId: string;
20
+ readonly aggregateType: string;
21
+ readonly tenantId: TenantId;
22
+ // Predecessor version. 0 writes a new stream at version 1; > 0 requires
23
+ // the predecessor to exist (same UUID, same tenant). Mirrors EventToAppend.
24
+ readonly expectedVersion: number;
25
+ readonly type: string;
26
+ readonly eventVersion?: number;
27
+ readonly payload: Record<string, unknown>;
28
+ readonly metadata: EventMetadata;
29
+ // Historisch preserved — MUSS gesetzt sein, kein Default auf now().
30
+ readonly createdAt: Temporal.Instant;
31
+ // Historisch preserved. Legacy-UserId oder 'system' für pre-auth Daten.
32
+ // Bewusst getrennt von metadata.userId: der Migration-Runner läuft unter
33
+ // einer eigenen Service-Identität, der Legacy-Actor ist der Ursprung.
34
+ readonly createdBy: string;
35
+ };
36
+
37
+ // Mirrors append()'s two-path structure: typed builder equivalent for v=0,
38
+ // INSERT … SELECT … WHERE EXISTS gate for v>0. Caller-supplied createdAt +
39
+ // createdBy skip the usual userResolver/now() paths.
40
+ export async function appendRaw(runner: DbRunner, event: RawEventToAppend): Promise<void> {
41
+ const newVersion = event.expectedVersion + 1;
42
+ const eventVersion = event.eventVersion ?? 1;
43
+
44
+ try {
45
+ if (event.expectedVersion === 0) {
46
+ await insertRawFirst(runner, event, newVersion, eventVersion);
47
+ } else {
48
+ await insertRawSubsequent(runner, event, newVersion, eventVersion);
49
+ }
50
+ } catch (e) {
51
+ if (isUniqueViolation(e)) {
52
+ throw new VersionConflictError(event.aggregateId, event.expectedVersion);
53
+ }
54
+ throw e;
55
+ }
56
+ }
57
+
58
+ async function insertRawFirst(
59
+ runner: DbRunner,
60
+ event: RawEventToAppend,
61
+ newVersion: number,
62
+ eventVersion: number,
63
+ ): Promise<void> {
64
+ await runner.execute(sql`
65
+ INSERT INTO ${eventsTable} (
66
+ aggregate_id, aggregate_type, tenant_id, version,
67
+ type, event_version, payload, metadata, created_at, created_by
68
+ )
69
+ VALUES (
70
+ ${event.aggregateId}::uuid,
71
+ ${event.aggregateType},
72
+ ${event.tenantId}::uuid,
73
+ ${newVersion},
74
+ ${event.type},
75
+ ${eventVersion},
76
+ ${JSON.stringify(event.payload)}::jsonb,
77
+ ${JSON.stringify(event.metadata)}::jsonb,
78
+ ${event.createdAt.toString()}::timestamptz,
79
+ ${event.createdBy}
80
+ )
81
+ `);
82
+ }
83
+
84
+ async function insertRawSubsequent(
85
+ runner: DbRunner,
86
+ event: RawEventToAppend,
87
+ newVersion: number,
88
+ eventVersion: number,
89
+ ): Promise<void> {
90
+ const rows = await runner.execute<{ id: string }>(sql`
91
+ INSERT INTO ${eventsTable} (
92
+ aggregate_id, aggregate_type, tenant_id, version,
93
+ type, event_version, payload, metadata, created_at, created_by
94
+ )
95
+ SELECT ${event.aggregateId}::uuid,
96
+ ${event.aggregateType},
97
+ ${event.tenantId}::uuid,
98
+ ${newVersion},
99
+ ${event.type},
100
+ ${eventVersion},
101
+ ${JSON.stringify(event.payload)}::jsonb,
102
+ ${JSON.stringify(event.metadata)}::jsonb,
103
+ ${event.createdAt.toString()}::timestamptz,
104
+ ${event.createdBy}
105
+ WHERE EXISTS (
106
+ SELECT 1 FROM ${eventsTable}
107
+ WHERE aggregate_id = ${event.aggregateId}::uuid
108
+ AND version = ${event.expectedVersion}
109
+ AND tenant_id = ${event.tenantId}::uuid
110
+ )
111
+ RETURNING id
112
+ `);
113
+ if (rows.length === 0) {
114
+ throw new VersionConflictError(event.aggregateId, event.expectedVersion);
115
+ }
116
+ }
117
+
118
+ // Batch append. One multi-VALUES INSERT — atomic by PG statement semantics.
119
+ // Three pre-flight checks identify the specific conflicting aggregate, so the
120
+ // thrown VersionConflictError points at a real event (not at a batch-first
121
+ // placeholder). The INSERT's UNIQUE constraint is still the authoritative
122
+ // gate — pre-flight is for diagnostic precision, not correctness.
123
+ export async function appendRawBatch(
124
+ runner: DbRunner,
125
+ events: readonly RawEventToAppend[],
126
+ ): Promise<void> {
127
+ const firstEvent = events[0];
128
+ // skip: empty batch is a no-op by contract — callers that chunk a stream
129
+ // into size-N batches shouldn't need to guard the tail-case themselves.
130
+ if (!firstEvent) return;
131
+
132
+ verifyContiguousWithinBatch(events);
133
+ await verifyPredecessors(runner, events);
134
+ await verifyNoDuplicates(runner, events);
135
+
136
+ const rows = events.map((e) => {
137
+ const newVersion = e.expectedVersion + 1;
138
+ const eventVersion = e.eventVersion ?? 1;
139
+ return sql`(
140
+ ${e.aggregateId}::uuid,
141
+ ${e.aggregateType},
142
+ ${e.tenantId}::uuid,
143
+ ${newVersion},
144
+ ${e.type},
145
+ ${eventVersion},
146
+ ${JSON.stringify(e.payload)}::jsonb,
147
+ ${JSON.stringify(e.metadata)}::jsonb,
148
+ ${e.createdAt.toString()}::timestamptz,
149
+ ${e.createdBy}
150
+ )`;
151
+ });
152
+
153
+ try {
154
+ await runner.execute(sql`
155
+ INSERT INTO ${eventsTable} (
156
+ aggregate_id, aggregate_type, tenant_id, version,
157
+ type, event_version, payload, metadata, created_at, created_by
158
+ )
159
+ VALUES ${sql.join(rows, sql`, `)}
160
+ `);
161
+ } catch (e) {
162
+ if (isUniqueViolation(e)) {
163
+ // Pre-flight ran but lost a race against a concurrent writer. Rare for
164
+ // migration (single-runner) but possible; we can't name the exact row.
165
+ throw new VersionConflictError(firstEvent.aggregateId, firstEvent.expectedVersion);
166
+ }
167
+ throw e;
168
+ }
169
+ }
170
+
171
+ // Defense-in-depth against a buggy event-mapper: within one batch, for each
172
+ // aggregate the expectedVersion sequence must be contiguous (no gaps). Without
173
+ // this, [expectedVersion=0, expectedVersion=2] for the same aggregate would
174
+ // write v=1 and v=3 with v=2 missing — UNIQUE won't catch it (no collision),
175
+ // predecessor-EXISTS won't catch it (min is 0, check skipped), and the
176
+ // orphan only surfaces at projection-rebuild time. Fail-loud here instead.
177
+ function verifyContiguousWithinBatch(events: readonly RawEventToAppend[]): void {
178
+ const byAggregate = new Map<string, RawEventToAppend[]>();
179
+ for (const e of events) {
180
+ const key = `${e.tenantId}:${e.aggregateId}`;
181
+ const list = byAggregate.get(key) ?? [];
182
+ list.push(e);
183
+ byAggregate.set(key, list);
184
+ }
185
+
186
+ for (const list of byAggregate.values()) {
187
+ if (list.length < 2) continue;
188
+ const sorted = [...list].sort((a, b) => a.expectedVersion - b.expectedVersion);
189
+ const [first, ...rest] = sorted;
190
+ if (!first) continue;
191
+ let prev = first;
192
+ for (const curr of rest) {
193
+ if (curr.expectedVersion !== prev.expectedVersion + 1) {
194
+ throw new VersionConflictError(curr.aggregateId, curr.expectedVersion);
195
+ }
196
+ prev = curr;
197
+ }
198
+ }
199
+ }
200
+
201
+ // Per aggregate-group, check the predecessor (min(expectedVersion) > 0)
202
+ // exists in the DB. For migration batches that are usually single-aggregate
203
+ // or fresh-stream, this loops zero or one times.
204
+ async function verifyPredecessors(
205
+ runner: DbRunner,
206
+ events: readonly RawEventToAppend[],
207
+ ): Promise<void> {
208
+ type GroupKey = { tenantId: TenantId; aggregateId: string; minExpected: number };
209
+ const groups = new Map<string, GroupKey>();
210
+ for (const e of events) {
211
+ const key = `${e.tenantId}:${e.aggregateId}`;
212
+ const existing = groups.get(key);
213
+ if (!existing || e.expectedVersion < existing.minExpected) {
214
+ groups.set(key, {
215
+ tenantId: e.tenantId,
216
+ aggregateId: e.aggregateId,
217
+ minExpected: e.expectedVersion,
218
+ });
219
+ }
220
+ }
221
+
222
+ for (const g of groups.values()) {
223
+ if (g.minExpected === 0) continue;
224
+ const rows = await runner.execute<{ present: boolean }>(sql`
225
+ SELECT EXISTS(
226
+ SELECT 1 FROM ${eventsTable}
227
+ WHERE aggregate_id = ${g.aggregateId}::uuid
228
+ AND tenant_id = ${g.tenantId}::uuid
229
+ AND version = ${g.minExpected}
230
+ ) AS present
231
+ `);
232
+ if (!rows[0]?.present) {
233
+ throw new VersionConflictError(g.aggregateId, g.minExpected);
234
+ }
235
+ }
236
+ }
237
+
238
+ // Single IN-query checks whether any (tenant, aggregate, newVersion) tuple
239
+ // already exists. Returns the first collision, so the thrown error names
240
+ // the real conflicting aggregate instead of the batch's first event.
241
+ async function verifyNoDuplicates(
242
+ runner: DbRunner,
243
+ events: readonly RawEventToAppend[],
244
+ ): Promise<void> {
245
+ const triples = events.map(
246
+ (e) => sql`(${e.tenantId}::uuid, ${e.aggregateId}::uuid, ${e.expectedVersion + 1})`,
247
+ );
248
+ const rows = await runner.execute<{ aggregate_id: string; version: number }>(sql`
249
+ SELECT aggregate_id, version FROM ${eventsTable}
250
+ WHERE (tenant_id, aggregate_id, version) IN (${sql.join(triples, sql`, `)})
251
+ LIMIT 1
252
+ `);
253
+ const conflict = rows[0];
254
+ if (conflict) {
255
+ throw new VersionConflictError(conflict.aggregate_id, conflict.version - 1);
256
+ }
257
+ }
@@ -0,0 +1,106 @@
1
+ import { and, eq, sql } from "drizzle-orm";
2
+ import type { DbConnection, DbRunner } from "../db/connection";
3
+ import { instant, table as pgTable, text, uniqueIndex, uuid } from "../db/dialect";
4
+ import { tableExists } from "../db/schema-inspection";
5
+ import type { TenantId } from "../engine/types";
6
+ import { pushTables } from "../stack";
7
+
8
+ // Marten-aligned stream archival. Archived streams become read-only: fresh
9
+ // appendEvent on an archived aggregate throws, and loadAggregate returns
10
+ // an empty slice unless the caller passes { includeArchived: true }.
11
+ //
12
+ // Storage: sparse table — only ARCHIVED streams have a row. Active streams
13
+ // stay out of this table to keep the hot path free of extra writes. A
14
+ // tenant-scoped PK guards against cross-tenant reuse of aggregate IDs.
15
+
16
+ export const archivedStreamsTable = pgTable(
17
+ "kumiko_archived_streams",
18
+ {
19
+ tenantId: uuid("tenant_id").notNull(),
20
+ aggregateId: uuid("aggregate_id").notNull(),
21
+ aggregateType: text("aggregate_type").notNull(),
22
+ archivedAt: instant("archived_at", { precision: 3 }).notNull().default(sql`now()`),
23
+ archivedBy: text("archived_by").notNull(),
24
+ reason: text("reason"),
25
+ },
26
+ (t) => ({
27
+ pk: uniqueIndex("kumiko_archived_streams_pk").on(t.tenantId, t.aggregateId),
28
+ }),
29
+ );
30
+
31
+ export async function createArchivedStreamsTable(db: DbConnection): Promise<void> {
32
+ // skip: table already exists — idempotent boot + test-setup call
33
+ if (await tableExists(db, "public.kumiko_archived_streams")) return;
34
+ await pushTables(db, { kumikoArchivedStreams: archivedStreamsTable });
35
+ }
36
+
37
+ export type ArchiveStreamArgs = {
38
+ readonly tenantId: TenantId;
39
+ readonly aggregateId: string;
40
+ readonly aggregateType: string;
41
+ readonly archivedBy: string;
42
+ readonly reason?: string;
43
+ };
44
+
45
+ // Mark a stream as archived. Idempotent — re-archiving (same tenant +
46
+ // aggregate) updates archivedAt/archivedBy to the latest call instead of
47
+ // failing. That matches Marten's "archive is a state, not an event" model.
48
+ export async function archiveStream(db: DbRunner, args: ArchiveStreamArgs): Promise<void> {
49
+ await db
50
+ .insert(archivedStreamsTable)
51
+ .values({
52
+ tenantId: args.tenantId,
53
+ aggregateId: args.aggregateId,
54
+ aggregateType: args.aggregateType,
55
+ archivedBy: args.archivedBy,
56
+ reason: args.reason ?? null,
57
+ })
58
+ .onConflictDoUpdate({
59
+ target: [archivedStreamsTable.tenantId, archivedStreamsTable.aggregateId],
60
+ set: {
61
+ archivedAt: sql`now()`,
62
+ archivedBy: args.archivedBy,
63
+ aggregateType: args.aggregateType,
64
+ reason: args.reason ?? null,
65
+ },
66
+ });
67
+ }
68
+
69
+ // Cheap existence probe — issued in the hot read path, so keep the query to
70
+ // a single indexed lookup on the composite PK.
71
+ export async function isStreamArchived(
72
+ db: DbRunner,
73
+ tenantId: TenantId,
74
+ aggregateId: string,
75
+ ): Promise<boolean> {
76
+ const rows = await db
77
+ .select({ one: sql`1` })
78
+ .from(archivedStreamsTable)
79
+ .where(
80
+ and(
81
+ eq(archivedStreamsTable.tenantId, tenantId),
82
+ eq(archivedStreamsTable.aggregateId, aggregateId),
83
+ ),
84
+ )
85
+ .limit(1);
86
+ return rows.length > 0;
87
+ }
88
+
89
+ // Undo an archive — restores the stream to writable state. Ops tool. The
90
+ // historical archivedAt is lost; if auditing needs the archive-history,
91
+ // use domain events on the aggregate (e.g. "stream.archived" / "stream.
92
+ // restored") instead of relying on this row.
93
+ export async function restoreStream(
94
+ db: DbRunner,
95
+ tenantId: TenantId,
96
+ aggregateId: string,
97
+ ): Promise<void> {
98
+ await db
99
+ .delete(archivedStreamsTable)
100
+ .where(
101
+ and(
102
+ eq(archivedStreamsTable.tenantId, tenantId),
103
+ eq(archivedStreamsTable.aggregateId, aggregateId),
104
+ ),
105
+ );
106
+ }
@@ -0,0 +1,35 @@
1
+ // Failure modes of the event-store's append() path. Surfaced as typed
2
+ // errors so the executor layer can map them to the framework's
3
+ // WriteResult error contract (version_conflict).
4
+
5
+ export class VersionConflictError extends Error {
6
+ public readonly aggregateId: string;
7
+ public readonly expectedVersion: number;
8
+ constructor(aggregateId: string, expectedVersion: number) {
9
+ super(
10
+ `Version conflict on aggregate ${aggregateId}: expected predecessor version ${expectedVersion}`,
11
+ );
12
+ this.name = "VersionConflictError";
13
+ this.aggregateId = aggregateId;
14
+ this.expectedVersion = expectedVersion;
15
+ }
16
+ }
17
+
18
+ // Thrown when ctx.appendEvent targets an archived stream. Archived aggregates
19
+ // are read-only — restoreStream() makes them writable again. The archive
20
+ // state is not carried on the events themselves; it lives on the sparse
21
+ // kumiko_archived_streams table. Handlers that need to branch on archive
22
+ // state should call ctx.isStreamArchived(id) first.
23
+ export class ArchivedStreamError extends Error {
24
+ public readonly tenantId: string;
25
+ public readonly aggregateId: string;
26
+ constructor(tenantId: string, aggregateId: string) {
27
+ super(
28
+ `Aggregate ${aggregateId} on tenant ${tenantId} is archived — appendEvent is blocked. ` +
29
+ `Call restoreStream() to re-open the stream before writing.`,
30
+ );
31
+ this.name = "ArchivedStreamError";
32
+ this.tenantId = tenantId;
33
+ this.aggregateId = aggregateId;
34
+ }
35
+ }