@cosmicdrift/kumiko-framework 0.14.0 → 0.16.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 (342) hide show
  1. package/package.json +6 -6
  2. package/src/__tests__/{anonymous-access.integration.ts → anonymous-access.integration.test.ts} +12 -9
  3. package/src/__tests__/{error-contract.integration.ts → error-contract.integration.test.ts} +5 -4
  4. package/src/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +3 -3
  5. package/src/__tests__/{full-stack.integration.ts → full-stack.integration.test.ts} +7 -16
  6. package/src/__tests__/{ownership.integration.ts → ownership.integration.test.ts} +3 -2
  7. package/src/__tests__/{raw-table.integration.ts → raw-table.integration.test.ts} +18 -30
  8. package/src/__tests__/{reference-data.integration.ts → reference-data.integration.test.ts} +24 -11
  9. package/src/__tests__/{transition-guard.integration.ts → transition-guard.integration.test.ts} +12 -10
  10. package/src/api/__tests__/api.test.ts +1 -1
  11. package/src/api/__tests__/auth-middleware-transport.test.ts +1 -1
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +1 -1
  13. package/src/api/__tests__/{batch.integration.ts → batch.integration.test.ts} +30 -30
  14. package/src/api/__tests__/body-limit.test.ts +1 -1
  15. package/src/api/__tests__/csrf-middleware.test.ts +1 -1
  16. package/src/api/__tests__/{dispatcher-live.integration.ts → dispatcher-live.integration.test.ts} +10 -9
  17. package/src/api/__tests__/metrics-endpoint.test.ts +1 -1
  18. package/src/api/__tests__/{nested-write.integration.ts → nested-write.integration.test.ts} +13 -16
  19. package/src/api/__tests__/readiness.test.ts +1 -1
  20. package/src/api/__tests__/request-id-middleware.test.ts +1 -1
  21. package/src/api/__tests__/sse-broker.test.ts +12 -12
  22. package/src/api/__tests__/sse-route.test.ts +1 -1
  23. package/src/api/auth-routes.ts +2 -5
  24. package/src/api/readiness.ts +2 -2
  25. package/src/auth/__tests__/roles.test.ts +2 -2
  26. package/src/bun-db/__tests__/PATTERN.md +73 -0
  27. package/src/bun-db/__tests__/_helpers.ts +103 -0
  28. package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
  29. package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
  30. package/src/bun-db/__tests__/bun-test-db.ts +19 -0
  31. package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
  32. package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
  33. package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
  34. package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
  35. package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
  36. package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
  37. package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
  38. package/src/bun-db/connection.ts +84 -0
  39. package/src/bun-db/index.ts +31 -0
  40. package/src/bun-db/query.ts +842 -0
  41. package/src/compliance/__tests__/duration-spec.test.ts +1 -1
  42. package/src/compliance/__tests__/profiles.test.ts +1 -1
  43. package/src/compliance/__tests__/sub-processors.test.ts +1 -1
  44. package/src/compliance/profiles.ts +1 -4
  45. package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
  46. package/src/db/__tests__/big-int-field.test.ts +15 -14
  47. package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
  48. package/src/db/__tests__/compound-types.test.ts +1 -1
  49. package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
  50. package/src/db/__tests__/connection-options.test.ts +1 -1
  51. package/src/db/__tests__/cursor.test.ts +8 -32
  52. package/src/db/__tests__/dialect-instant.test.ts +1 -1
  53. package/src/db/__tests__/encryption.test.ts +1 -1
  54. package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
  55. package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
  56. package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
  57. package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
  58. package/src/db/__tests__/located-timestamp.test.ts +1 -1
  59. package/src/db/__tests__/migrate-generator.test.ts +71 -0
  60. package/src/db/__tests__/migrate-runner.test.ts +19 -0
  61. package/src/db/__tests__/money.test.ts +1 -1
  62. package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
  63. package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
  64. package/src/db/__tests__/pg-error.test.ts +43 -0
  65. package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
  66. package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
  67. package/src/db/__tests__/sql-inventory.test.ts +56 -0
  68. package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
  69. package/src/db/__tests__/table-builder-required.test.ts +20 -22
  70. package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
  71. package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
  72. package/src/db/api.ts +46 -0
  73. package/src/db/apply-entity-event.ts +45 -36
  74. package/src/db/assert-exists-in.ts +5 -16
  75. package/src/db/bun-provider.ts +37 -0
  76. package/src/db/config-seed.ts +4 -4
  77. package/src/db/connection.ts +14 -57
  78. package/src/db/cursor.ts +5 -56
  79. package/src/db/dialect.ts +472 -99
  80. package/src/db/eagerload.ts +5 -12
  81. package/src/db/entity-table-meta.ts +390 -0
  82. package/src/db/event-store-executor.ts +158 -100
  83. package/src/db/index.ts +33 -5
  84. package/src/db/migrate-generator.ts +350 -0
  85. package/src/db/migrate-runner.ts +206 -0
  86. package/src/db/postgres-provider.ts +25 -0
  87. package/src/db/queries/entity-read.ts +15 -0
  88. package/src/db/queries/es-ops.ts +17 -0
  89. package/src/db/queries/event-consumer.ts +170 -0
  90. package/src/db/queries/event-store-admin.ts +127 -0
  91. package/src/db/queries/event-store.ts +155 -0
  92. package/src/db/queries/projection-rebuild.ts +59 -0
  93. package/src/db/queries/raw-sql.ts +15 -0
  94. package/src/db/queries/schema-drift.ts +35 -0
  95. package/src/db/queries/seed-context.ts +58 -0
  96. package/src/db/queries/table-ops.ts +11 -0
  97. package/src/db/queries/test-stack.ts +56 -0
  98. package/src/db/query-api.ts +22 -0
  99. package/src/db/query.ts +30 -0
  100. package/src/db/reference-data.ts +19 -22
  101. package/src/db/render-ddl.ts +57 -0
  102. package/src/db/row-helpers.ts +3 -52
  103. package/src/db/schema-inspection.ts +17 -4
  104. package/src/db/sql-inventory.ts +208 -0
  105. package/src/db/table-builder.ts +54 -46
  106. package/src/db/tenant-db.ts +105 -326
  107. package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
  108. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
  109. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
  110. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
  111. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
  112. package/src/engine/__tests__/boot-validator.test.ts +4 -3
  113. package/src/engine/__tests__/build-app-schema.test.ts +1 -1
  114. package/src/engine/__tests__/build-target.test.ts +1 -1
  115. package/src/engine/__tests__/claim-keys.test.ts +1 -1
  116. package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
  117. package/src/engine/__tests__/config-helpers.test.ts +1 -1
  118. package/src/engine/__tests__/duration-utils.test.ts +16 -0
  119. package/src/engine/__tests__/effective-features.test.ts +1 -1
  120. package/src/engine/__tests__/engine.test.ts +1 -1
  121. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  122. package/src/engine/__tests__/event-helpers.test.ts +3 -3
  123. package/src/engine/__tests__/extends-registrar.test.ts +4 -4
  124. package/src/engine/__tests__/factories-long-text.test.ts +1 -1
  125. package/src/engine/__tests__/factories-time.test.ts +1 -1
  126. package/src/engine/__tests__/field-access.test.ts +38 -0
  127. package/src/engine/__tests__/field-predicates.test.ts +1 -1
  128. package/src/engine/__tests__/hook-phases.test.ts +1 -1
  129. package/src/engine/__tests__/identifiers.test.ts +1 -1
  130. package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
  131. package/src/engine/__tests__/nav.test.ts +1 -1
  132. package/src/engine/__tests__/no-return-guard.test.ts +17 -0
  133. package/src/engine/__tests__/ownership.test.ts +10 -11
  134. package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
  135. package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
  136. package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
  137. package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
  138. package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
  139. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
  140. package/src/engine/__tests__/post-query-hook.test.ts +1 -1
  141. package/src/engine/__tests__/projection-helpers.test.ts +25 -17
  142. package/src/engine/__tests__/projection.test.ts +4 -4
  143. package/src/engine/__tests__/qualified-name.test.ts +1 -1
  144. package/src/engine/__tests__/raw-table.test.ts +9 -8
  145. package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
  146. package/src/engine/__tests__/run-in.test.ts +1 -1
  147. package/src/engine/__tests__/schema-builder.test.ts +1 -1
  148. package/src/engine/__tests__/screen.test.ts +1 -1
  149. package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
  150. package/src/engine/__tests__/state-machine.test.ts +1 -1
  151. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
  152. package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
  153. package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
  154. package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
  155. package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
  156. package/src/engine/__tests__/steps-read.test.ts +34 -40
  157. package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
  158. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
  159. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
  160. package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
  161. package/src/engine/__tests__/steps-workflow.test.ts +7 -7
  162. package/src/engine/__tests__/system-user.test.ts +1 -1
  163. package/src/engine/__tests__/unmanaged-table.test.ts +98 -0
  164. package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
  165. package/src/engine/__tests__/validation-hooks.test.ts +1 -1
  166. package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
  167. package/src/engine/boot-validator/entity-handler.ts +3 -3
  168. package/src/engine/boot-validator/ownership.ts +1 -1
  169. package/src/engine/define-feature.ts +37 -2
  170. package/src/engine/entity-handlers.ts +5 -5
  171. package/src/engine/factories.ts +1 -1
  172. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
  173. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
  174. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
  175. package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
  176. package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
  177. package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
  178. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
  179. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
  180. package/src/engine/feature-ast/extractors/shared.ts +2 -3
  181. package/src/engine/ownership.ts +113 -41
  182. package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
  183. package/src/engine/projection-helpers.ts +2 -11
  184. package/src/engine/registry.ts +21 -2
  185. package/src/engine/steps/read-find-many.ts +13 -13
  186. package/src/engine/steps/read-find-one.ts +7 -9
  187. package/src/engine/steps/unsafe-projection-delete.ts +4 -5
  188. package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
  189. package/src/engine/types/feature.ts +47 -2
  190. package/src/engine/types/fields.ts +4 -5
  191. package/src/engine/types/index.ts +2 -0
  192. package/src/engine/types/step.ts +10 -10
  193. package/src/engine/validate-projection-allowlist.ts +23 -3
  194. package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
  195. package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
  196. package/src/env/__tests__/compose-env-schema.test.ts +1 -1
  197. package/src/env/__tests__/dry-run.test.ts +1 -1
  198. package/src/errors/__tests__/classes.test.ts +1 -1
  199. package/src/errors/__tests__/error-helpers.test.ts +44 -0
  200. package/src/errors/__tests__/field-issue-compat.test.ts +16 -0
  201. package/src/errors/__tests__/write-failures.test.ts +1 -1
  202. package/src/errors/classes.ts +5 -19
  203. package/src/errors/field-issue.ts +11 -0
  204. package/src/errors/index.ts +1 -0
  205. package/src/errors/zod-bridge.ts +3 -2
  206. package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
  207. package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
  208. package/src/es-ops/__tests__/runner.test.ts +29 -19
  209. package/src/es-ops/context.ts +11 -56
  210. package/src/es-ops/operations-schema.ts +2 -2
  211. package/src/es-ops/runner.ts +12 -26
  212. package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
  213. package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
  214. package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
  215. package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
  216. package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
  217. package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
  218. package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
  219. package/src/event-store/admin-api.ts +55 -83
  220. package/src/event-store/archive.ts +15 -39
  221. package/src/event-store/event-store.ts +92 -86
  222. package/src/event-store/events-schema.ts +2 -1
  223. package/src/event-store/index.ts +1 -0
  224. package/src/event-store/snapshot.ts +26 -24
  225. package/src/event-store/upcaster-dead-letter.ts +19 -18
  226. package/src/files/__tests__/content-disposition.test.ts +1 -1
  227. package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
  228. package/src/files/__tests__/file-handle.test.ts +1 -1
  229. package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
  230. package/src/files/__tests__/read-stream.test.ts +1 -1
  231. package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
  232. package/src/files/__tests__/write-stream.test.ts +1 -1
  233. package/src/files/__tests__/zip-stream.test.ts +1 -1
  234. package/src/files/file-ref-table.ts +2 -2
  235. package/src/files/file-routes.ts +7 -9
  236. package/src/files/storage-tracking.ts +9 -17
  237. package/src/i18n/__tests__/i18n.test.ts +1 -1
  238. package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
  239. package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
  240. package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
  241. package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
  242. package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
  243. package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
  244. package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
  245. package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
  246. package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
  247. package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
  248. package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
  249. package/src/migrations/projection-detection.ts +12 -1
  250. package/src/migrations/schema-drift.ts +7 -23
  251. package/src/observability/__tests__/console-provider.test.ts +1 -1
  252. package/src/observability/__tests__/metric-validator.test.ts +1 -1
  253. package/src/observability/__tests__/noop-provider.test.ts +1 -1
  254. package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
  255. package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
  256. package/src/observability/__tests__/recording-meter.test.ts +1 -1
  257. package/src/observability/__tests__/recording-tracer.test.ts +1 -1
  258. package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
  259. package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
  260. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
  261. package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
  262. package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
  263. package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
  264. package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
  265. package/src/pipeline/__tests__/dispatcher-utils.test.ts +107 -0
  266. package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
  267. package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
  268. package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
  269. package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
  270. package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
  271. package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
  272. package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
  273. package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
  274. package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
  275. package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
  276. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
  277. package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
  278. package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
  279. package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
  280. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
  281. package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
  282. package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
  283. package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
  284. package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
  285. package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
  286. package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
  287. package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
  288. package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
  289. package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
  290. package/src/pipeline/__tests__/redis-keys.test.ts +12 -0
  291. package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
  292. package/src/pipeline/cascade-handler.ts +13 -21
  293. package/src/pipeline/dispatcher-utils.ts +8 -7
  294. package/src/pipeline/dispatcher.ts +43 -48
  295. package/src/pipeline/event-consumer-state.ts +11 -2
  296. package/src/pipeline/event-dispatcher.ts +86 -146
  297. package/src/pipeline/event-retention.ts +14 -24
  298. package/src/pipeline/msp-rebuild.ts +54 -78
  299. package/src/pipeline/projection-rebuild.ts +65 -67
  300. package/src/pipeline/projection-state.ts +2 -2
  301. package/src/random/__tests__/generate.test.ts +13 -13
  302. package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
  303. package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
  304. package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
  305. package/src/redis/__tests__/redis-options.test.ts +1 -1
  306. package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
  307. package/src/search/__tests__/search-adapter.test.ts +1 -1
  308. package/src/secrets/__tests__/dek-cache.test.ts +1 -3
  309. package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
  310. package/src/secrets/__tests__/envelope.test.ts +1 -1
  311. package/src/secrets/__tests__/leak-guard.test.ts +1 -1
  312. package/src/secrets/__tests__/rotation.test.ts +1 -1
  313. package/src/stack/db.ts +25 -48
  314. package/src/stack/push-entity-projection-tables.ts +2 -4
  315. package/src/stack/table-helpers.ts +98 -61
  316. package/src/stack/test-stack.ts +10 -9
  317. package/src/testing/__tests__/db-cleanup.test.ts +40 -0
  318. package/src/testing/__tests__/e2e-generator.test.ts +1 -1
  319. package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
  320. package/src/testing/db-cleanup.ts +44 -0
  321. package/src/testing/expect-error.ts +1 -1
  322. package/src/testing/index.ts +2 -0
  323. package/src/testing/multipart-helper.ts +94 -0
  324. package/src/testing/shared-entities.ts +5 -5
  325. package/src/time/__tests__/polyfill.test.ts +1 -1
  326. package/src/time/__tests__/tz-context.test.ts +1 -1
  327. package/src/utils/__tests__/assert.test.ts +1 -1
  328. package/src/utils/__tests__/case.test.ts +16 -0
  329. package/src/utils/__tests__/env-parse.test.ts +1 -1
  330. package/src/utils/__tests__/is-plain-object.test.ts +16 -0
  331. package/src/utils/__tests__/parse-string-array-json.test.ts +16 -0
  332. package/src/utils/__tests__/safe-json.test.ts +22 -0
  333. package/src/utils/case.ts +6 -0
  334. package/src/utils/index.ts +3 -0
  335. package/src/utils/is-plain-object.ts +4 -0
  336. package/src/utils/parse-string-array-json.ts +14 -0
  337. package/CHANGELOG.md +0 -474
  338. package/src/db/__tests__/db-helpers.test.ts +0 -369
  339. package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
  340. package/src/db/__tests__/row-helpers.test.ts +0 -59
  341. package/src/engine/steps/_drizzle-boundary.ts +0 -19
  342. package/src/files/__tests__/file-field-column.integration.ts +0 -103
@@ -8,12 +8,12 @@
8
8
  // with only a 1→2 migration fails immediately, so a missing upcaster
9
9
  // can never silently hand half-migrated data to consumers.
10
10
 
11
- import { sql } from "drizzle-orm";
12
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
11
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
13
12
  import { z } from "zod";
14
13
  import { integer as pgInteger, table as pgTable, text as pgText } from "../../db/dialect";
15
14
  import { createEventStoreExecutor } from "../../db/event-store-executor";
16
- import { buildDrizzleTable } from "../../db/table-builder";
15
+ import { asRawClient, insertOne, selectMany } from "../../db/query";
16
+ import { buildEntityTable } from "../../db/table-builder";
17
17
  import { createTenantDb, type TenantDb } from "../../db/tenant-db";
18
18
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
19
19
  import type { StoredEvent } from "../../event-store";
@@ -36,7 +36,7 @@ const orderEntity = createEntity({
36
36
  customer: createTextField({ required: true }),
37
37
  },
38
38
  });
39
- const orderTable = buildDrizzleTable("upcast-order", orderEntity);
39
+ const orderTable = buildEntityTable("upcast-order", orderEntity);
40
40
 
41
41
  // Projection stores the UPCAST view: the v3 shape expects `totalCents` (int)
42
42
  // even though the earliest writes might have stored `totalEuros` (string).
@@ -78,18 +78,10 @@ const orderFeature = defineFeature("upcastshop", (r) => {
78
78
  apply: {
79
79
  [orderPriced.name]: async (event, tx) => {
80
80
  const p = event.payload as { totalCents: number; currency: string };
81
- await tx
82
- .insert(orderSummaryTable)
83
- .values({
84
- orderId: event.aggregateId,
85
- tenantId: event.tenantId,
86
- totalCents: p.totalCents,
87
- currency: p.currency,
88
- })
89
- .onConflictDoUpdate({
90
- target: orderSummaryTable.orderId,
91
- set: { totalCents: p.totalCents, currency: p.currency },
92
- });
81
+ await asRawClient(tx).unsafe(
82
+ `INSERT INTO "read_upcast_order_summary" (order_id, tenant_id, total_cents, currency) VALUES ($1, $2, $3, $4) ON CONFLICT (order_id) DO UPDATE SET total_cents = $3, currency = $4`,
83
+ [event.aggregateId, event.tenantId, p.totalCents, p.currency],
84
+ );
93
85
  },
94
86
  },
95
87
  });
@@ -121,8 +113,8 @@ afterAll(async () => {
121
113
  });
122
114
 
123
115
  beforeEach(async () => {
124
- await testDb.db.execute(
125
- sql`TRUNCATE kumiko_events, read_upcast_orders, read_upcast_order_summary, kumiko_projections RESTART IDENTITY CASCADE`,
116
+ await asRawClient(testDb.db).unsafe(
117
+ `TRUNCATE kumiko_events, read_upcast_orders, read_upcast_order_summary, kumiko_projections RESTART IDENTITY CASCADE`,
126
118
  );
127
119
  });
128
120
 
@@ -280,10 +272,7 @@ describe("upcaster: projection rebuild walks the chain on replay", () => {
280
272
  });
281
273
  expect(result.eventsProcessed).toBe(3);
282
274
 
283
- const rows = await testDb.db
284
- .select()
285
- .from(orderSummaryTable)
286
- .orderBy(orderSummaryTable.orderId);
275
+ const rows = await selectMany(testDb.db, orderSummaryTable);
287
276
 
288
277
  // Ordered by orderId → ord1 (10€ = 1000¢), ord2 ($25.50 = 2550¢), ord3 (9900¢)
289
278
  expect(rows).toHaveLength(3);
@@ -305,9 +294,7 @@ describe("upcaster: async (Marten AsyncOnlyEventUpcaster — DB-Lookups)", () =>
305
294
  segment: pgText("segment").notNull(),
306
295
  });
307
296
  await unsafePushTables(testDb.db, { upcastAsyncCustomerSegments: customerSegments });
308
- await testDb.db
309
- .insert(customerSegments)
310
- .values({ customerId: "c-async-1", segment: "PREMIUM" });
297
+ await insertOne(testDb.db, customerSegments, { customerId: "c-async-1", segment: "PREMIUM" });
311
298
 
312
299
  const asyncSummary = pgTable("upcast_async_summary", {
313
300
  orderId: pgText("order_id").primaryKey(),
@@ -327,11 +314,11 @@ describe("upcaster: async (Marten AsyncOnlyEventUpcaster — DB-Lookups)", () =>
327
314
 
328
315
  r.eventMigration("placed", 1, 2, async (payload, ctx) => {
329
316
  const p = payload as { customerId: string };
330
- const [row] = await ctx.db
331
- .select()
332
- .from(customerSegments)
333
- .where(sql`${customerSegments.customerId} = ${p.customerId}`);
334
- return { customerId: p.customerId, segment: row?.segment ?? "UNKNOWN" };
317
+ const [row] = await selectMany(ctx.db, customerSegments, { customerId: p.customerId });
318
+ return {
319
+ customerId: p.customerId,
320
+ segment: (row as { segment?: string } | undefined)?.segment ?? "UNKNOWN",
321
+ };
335
322
  });
336
323
 
337
324
  r.projection({
@@ -341,7 +328,7 @@ describe("upcaster: async (Marten AsyncOnlyEventUpcaster — DB-Lookups)", () =>
341
328
  apply: {
342
329
  [placed.name]: async (event, tx) => {
343
330
  const p = event.payload as { customerId: string; segment: string };
344
- await tx.insert(asyncSummary).values({
331
+ await insertOne(tx, asyncSummary, {
345
332
  orderId: event.aggregateId,
346
333
  customerId: p.customerId,
347
334
  segment: p.segment,
@@ -384,7 +371,7 @@ describe("upcaster: async (Marten AsyncOnlyEventUpcaster — DB-Lookups)", () =>
384
371
  });
385
372
  expect(result.eventsProcessed).toBe(2);
386
373
 
387
- const rows = await testDb.db.select().from(asyncSummary).orderBy(asyncSummary.orderId);
374
+ const rows = await selectMany(testDb.db, asyncSummary);
388
375
  expect(rows).toHaveLength(2);
389
376
  const byId = new Map(rows.map((r) => [r.orderId, r]));
390
377
  // v1 → v2 via async DB lookup → segment from customer_segments.
@@ -7,13 +7,18 @@
7
7
  // Allowlist: samples/*/migration/, scripts/migrations/, die Definition
8
8
  // selbst, das Guard-Script selbst.
9
9
 
10
- import { sql } from "drizzle-orm";
11
10
  import type { DbRunner } from "../db";
12
11
  import { isUniqueViolation } from "../db/pg-error";
12
+ import {
13
+ eventPredecessorExists,
14
+ findExistingEventVersion,
15
+ insertRawEventBatch,
16
+ insertRawFirstEvent,
17
+ insertRawSubsequentEvent,
18
+ } from "../db/queries/event-store-admin";
13
19
  import type { TenantId } from "../engine/types";
14
20
  import { VersionConflictError } from "./errors";
15
21
  import type { EventMetadata } from "./event-store";
16
- import { eventsTable } from "./events-schema";
17
22
 
18
23
  export type RawEventToAppend = {
19
24
  readonly aggregateId: string;
@@ -55,30 +60,28 @@ export async function appendRaw(runner: DbRunner, event: RawEventToAppend): Prom
55
60
  }
56
61
  }
57
62
 
63
+ function rawEventParams(event: RawEventToAppend, newVersion: number, eventVersion: number) {
64
+ return {
65
+ aggregateId: event.aggregateId,
66
+ aggregateType: event.aggregateType,
67
+ tenantId: event.tenantId,
68
+ newVersion,
69
+ type: event.type,
70
+ eventVersion,
71
+ payloadJson: JSON.stringify(event.payload),
72
+ metadataJson: JSON.stringify(event.metadata),
73
+ createdAt: event.createdAt.toString(),
74
+ createdBy: event.createdBy,
75
+ };
76
+ }
77
+
58
78
  async function insertRawFirst(
59
79
  runner: DbRunner,
60
80
  event: RawEventToAppend,
61
81
  newVersion: number,
62
82
  eventVersion: number,
63
83
  ): 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
- `);
84
+ await insertRawFirstEvent(runner, rawEventParams(event, newVersion, eventVersion));
82
85
  }
83
86
 
84
87
  async function insertRawSubsequent(
@@ -87,30 +90,11 @@ async function insertRawSubsequent(
87
90
  newVersion: number,
88
91
  eventVersion: number,
89
92
  ): 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) {
93
+ const inserted = await insertRawSubsequentEvent(runner, {
94
+ ...rawEventParams(event, newVersion, eventVersion),
95
+ expectedVersion: event.expectedVersion,
96
+ });
97
+ if (!inserted) {
114
98
  throw new VersionConflictError(event.aggregateId, event.expectedVersion);
115
99
  }
116
100
  }
@@ -133,31 +117,28 @@ export async function appendRawBatch(
133
117
  await verifyPredecessors(runner, events);
134
118
  await verifyNoDuplicates(runner, events);
135
119
 
136
- const rows = events.map((e) => {
120
+ const params: unknown[] = [];
121
+ const valuesClauses = events.map((e) => {
137
122
  const newVersion = e.expectedVersion + 1;
138
123
  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
- )`;
124
+ const baseIdx = params.length;
125
+ params.push(
126
+ e.aggregateId,
127
+ e.aggregateType,
128
+ e.tenantId,
129
+ newVersion,
130
+ e.type,
131
+ eventVersion,
132
+ JSON.stringify(e.payload),
133
+ JSON.stringify(e.metadata),
134
+ e.createdAt.toString(),
135
+ e.createdBy,
136
+ );
137
+ return `($${baseIdx + 1}::uuid, $${baseIdx + 2}, $${baseIdx + 3}::uuid, $${baseIdx + 4}, $${baseIdx + 5}, $${baseIdx + 6}, $${baseIdx + 7}::jsonb, $${baseIdx + 8}::jsonb, $${baseIdx + 9}::timestamptz, $${baseIdx + 10})`;
151
138
  });
152
139
 
153
140
  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
- `);
141
+ await insertRawEventBatch(runner, valuesClauses.join(", "), params);
161
142
  } catch (e) {
162
143
  if (isUniqueViolation(e)) {
163
144
  // Pre-flight ran but lost a race against a concurrent writer. Rare for
@@ -221,15 +202,8 @@ async function verifyPredecessors(
221
202
 
222
203
  for (const g of groups.values()) {
223
204
  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) {
205
+ const present = await eventPredecessorExists(runner, g.aggregateId, g.tenantId, g.minExpected);
206
+ if (!present) {
233
207
  throw new VersionConflictError(g.aggregateId, g.minExpected);
234
208
  }
235
209
  }
@@ -242,16 +216,14 @@ async function verifyNoDuplicates(
242
216
  runner: DbRunner,
243
217
  events: readonly RawEventToAppend[],
244
218
  ): 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];
219
+ const params: unknown[] = [];
220
+ const tripleClauses = events.map((e) => {
221
+ const baseIdx = params.length;
222
+ params.push(e.tenantId, e.aggregateId, e.expectedVersion + 1);
223
+ return `($${baseIdx + 1}::uuid, $${baseIdx + 2}::uuid, $${baseIdx + 3})`;
224
+ });
225
+ const conflict = await findExistingEventVersion(runner, tripleClauses.join(", "), params);
254
226
  if (conflict) {
255
- throw new VersionConflictError(conflict.aggregate_id, conflict.version - 1);
227
+ throw new VersionConflictError(conflict.aggregateId, conflict.version - 1);
256
228
  }
257
229
  }
@@ -1,6 +1,9 @@
1
- import { and, eq, sql } from "drizzle-orm";
1
+ // sql now comes from native dialect
2
+
2
3
  import type { DbConnection, DbRunner } from "../db/connection";
3
- import { instant, table as pgTable, text, uniqueIndex, uuid } from "../db/dialect";
4
+ import { instant, table as pgTable, sql, text, uniqueIndex, uuid } from "../db/dialect";
5
+ import { upsertArchivedStream } from "../db/queries/event-store";
6
+ import { deleteMany, fetchOne } from "../db/query";
4
7
  import { tableExists } from "../db/schema-inspection";
5
8
  import type { TenantId } from "../engine/types";
6
9
  import { unsafePushTables } from "../stack";
@@ -46,24 +49,13 @@ export type ArchiveStreamArgs = {
46
49
  // aggregate) updates archivedAt/archivedBy to the latest call instead of
47
50
  // failing. That matches Marten's "archive is a state, not an event" model.
48
51
  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
- });
52
+ await upsertArchivedStream(db, {
53
+ tenantId: args.tenantId,
54
+ aggregateId: args.aggregateId,
55
+ aggregateType: args.aggregateType,
56
+ archivedBy: args.archivedBy,
57
+ reason: args.reason ?? null,
58
+ });
67
59
  }
68
60
 
69
61
  // Cheap existence probe — issued in the hot read path, so keep the query to
@@ -73,17 +65,8 @@ export async function isStreamArchived(
73
65
  tenantId: TenantId,
74
66
  aggregateId: string,
75
67
  ): 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;
68
+ const row = await fetchOne(db, archivedStreamsTable, { tenantId, aggregateId });
69
+ return row !== undefined;
87
70
  }
88
71
 
89
72
  // Undo an archive — restores the stream to writable state. Ops tool. The
@@ -95,12 +78,5 @@ export async function restoreStream(
95
78
  tenantId: TenantId,
96
79
  aggregateId: string,
97
80
  ): Promise<void> {
98
- await db
99
- .delete(archivedStreamsTable)
100
- .where(
101
- and(
102
- eq(archivedStreamsTable.tenantId, tenantId),
103
- eq(archivedStreamsTable.aggregateId, aggregateId),
104
- ),
105
- );
81
+ await deleteMany(db, archivedStreamsTable, { tenantId, aggregateId });
106
82
  }
@@ -1,6 +1,13 @@
1
- import { and, asc, eq, gt, lte, max, sql } from "drizzle-orm";
2
1
  import type { DbRunner } from "../db";
3
2
  import { isUniqueViolation } from "../db/pg-error";
3
+ import {
4
+ insertSubsequentEventRow,
5
+ notifyPgChannel,
6
+ selectAggregateMaxVersion,
7
+ selectEventsHighWaterMark,
8
+ selectStreamMaxVersion,
9
+ } from "../db/queries/event-store";
10
+ import { insertOne, selectMany } from "../db/query";
4
11
  import type { TenantId } from "../engine/types";
5
12
  import { isStreamArchived } from "./archive";
6
13
  import { VersionConflictError } from "./errors";
@@ -60,7 +67,19 @@ export type StoredEvent<TPayload = Record<string, unknown>> = {
60
67
  readonly createdBy: string;
61
68
  };
62
69
 
63
- type SelectedEvent = typeof eventsTable.$inferSelect;
70
+ type SelectedEvent = {
71
+ readonly id: bigint;
72
+ readonly aggregateId: string;
73
+ readonly aggregateType: string;
74
+ readonly tenantId: TenantId;
75
+ readonly version: number;
76
+ readonly type: string;
77
+ readonly eventVersion: number;
78
+ readonly payload: Record<string, unknown>;
79
+ readonly metadata: EventMetadata;
80
+ readonly createdAt: Temporal.Instant;
81
+ readonly createdBy: string;
82
+ };
64
83
 
65
84
  // Append one event atomically. Two guarantees combined:
66
85
  //
@@ -99,7 +118,7 @@ export async function append(db: DbRunner, event: EventToAppend): Promise<Stored
99
118
  // NOTIFY fires on commit (PG buffers NOTIFY per TX), so subscribers never
100
119
  // see a wake-up for an event that later rolled back. Harmless no-op when
101
120
  // no LISTENer is attached.
102
- await db.execute(sql`SELECT pg_notify(${EVENTS_PUBSUB_CHANNEL}, '')`);
121
+ await notifyPgChannel(db, EVENTS_PUBSUB_CHANNEL);
103
122
 
104
123
  return buildStoredEvent(event, newVersion, eventVersion, row);
105
124
  } catch (e) {
@@ -122,22 +141,19 @@ async function insertFirstEvent(
122
141
  newVersion: number,
123
142
  eventVersion: number,
124
143
  ): Promise<InsertReturn> {
125
- const [row] = await db
126
- .insert(eventsTable)
127
- .values({
128
- aggregateId: event.aggregateId,
129
- aggregateType: event.aggregateType,
130
- tenantId: event.tenantId,
131
- version: newVersion,
132
- type: event.type,
133
- eventVersion,
134
- payload: event.payload,
135
- metadata: event.metadata,
136
- createdBy: event.metadata.userId,
137
- })
138
- .returning({ id: eventsTable.id, createdAt: eventsTable.createdAt });
144
+ const row = await insertOne<{ id: bigint; createdAt: Temporal.Instant }>(db, eventsTable, {
145
+ aggregateId: event.aggregateId,
146
+ aggregateType: event.aggregateType,
147
+ tenantId: event.tenantId,
148
+ version: newVersion,
149
+ type: event.type,
150
+ eventVersion,
151
+ payload: event.payload,
152
+ metadata: event.metadata,
153
+ createdBy: event.metadata.userId,
154
+ });
139
155
  if (!row) throw new Error("insertFirstEvent: INSERT RETURNING produced no row");
140
- return row;
156
+ return { id: row.id, createdAt: row.createdAt };
141
157
  }
142
158
 
143
159
  // Subsequent event — predecessor must exist AND belong to the same tenant.
@@ -150,31 +166,21 @@ async function insertSubsequentEvent(
150
166
  newVersion: number,
151
167
  eventVersion: number,
152
168
  ): Promise<InsertReturn> {
153
- const payloadJson = JSON.stringify(event.payload);
154
- const metadataJson = JSON.stringify(event.metadata);
155
- const rows = await db.execute<{ id: string; created_at: Date | string }>(sql`
156
- INSERT INTO ${eventsTable} (
157
- aggregate_id, aggregate_type, tenant_id, version,
158
- type, event_version, payload, metadata, created_by
159
- )
160
- SELECT ${event.aggregateId}::uuid, ${event.aggregateType}, ${event.tenantId}::uuid, ${newVersion},
161
- ${event.type}, ${eventVersion}, ${payloadJson}::jsonb,
162
- ${metadataJson}::jsonb, ${event.metadata.userId}
163
- WHERE EXISTS (
164
- SELECT 1 FROM ${eventsTable}
165
- WHERE aggregate_id = ${event.aggregateId}::uuid
166
- AND version = ${event.expectedVersion}
167
- AND tenant_id = ${event.tenantId}::uuid
168
- )
169
- RETURNING id, created_at;
170
- `);
171
- const row = rows[0];
169
+ const row = await insertSubsequentEventRow(db, {
170
+ aggregateId: event.aggregateId,
171
+ aggregateType: event.aggregateType,
172
+ tenantId: event.tenantId,
173
+ newVersion,
174
+ type: event.type,
175
+ eventVersion,
176
+ payloadJson: JSON.stringify(event.payload),
177
+ metadataJson: JSON.stringify(event.metadata),
178
+ createdBy: event.metadata.userId,
179
+ expectedVersion: event.expectedVersion,
180
+ });
172
181
  if (!row) throw new VersionConflictError(event.aggregateId, event.expectedVersion);
173
182
  return {
174
- id: BigInt(row.id),
175
- // Raw SQL bypasses Drizzle's customType — postgres-js returns Date or
176
- // string depending on driver-config. Normalize through Temporal.Instant
177
- // so the InsertReturn shape matches the typed-builder path.
183
+ id: typeof row.id === "bigint" ? row.id : BigInt(row.id),
178
184
  createdAt:
179
185
  row.created_at instanceof Date
180
186
  ? Temporal.Instant.fromEpochMilliseconds(row.created_at.getTime())
@@ -221,11 +227,12 @@ export async function loadAggregate(
221
227
  const archived = await isStreamArchived(db, tenantId, aggregateId);
222
228
  if (archived) return [];
223
229
  }
224
- const rows = await db
225
- .select()
226
- .from(eventsTable)
227
- .where(and(eq(eventsTable.aggregateId, aggregateId), eq(eventsTable.tenantId, tenantId)))
228
- .orderBy(asc(eventsTable.version));
230
+ const rows = await selectMany<SelectedEvent>(
231
+ db,
232
+ eventsTable,
233
+ { aggregateId, tenantId },
234
+ { orderBy: { col: "version", direction: "asc" } },
235
+ );
229
236
  return rows.map(toStoredEvent);
230
237
  }
231
238
 
@@ -243,17 +250,12 @@ export async function loadAggregateAsOf(
243
250
  const archived = await isStreamArchived(db, tenantId, aggregateId);
244
251
  if (archived) return [];
245
252
  }
246
- const rows = await db
247
- .select()
248
- .from(eventsTable)
249
- .where(
250
- and(
251
- eq(eventsTable.aggregateId, aggregateId),
252
- eq(eventsTable.tenantId, tenantId),
253
- lte(eventsTable.createdAt, asOf),
254
- ),
255
- )
256
- .orderBy(asc(eventsTable.version));
253
+ const rows = await selectMany<SelectedEvent>(
254
+ db,
255
+ eventsTable,
256
+ { aggregateId, tenantId, createdAt: { lte: asOf } },
257
+ { orderBy: { col: "version", direction: "asc" } },
258
+ );
257
259
  return rows.map(toStoredEvent);
258
260
  }
259
261
 
@@ -268,11 +270,15 @@ export async function getStreamVersion(
268
270
  aggregateId: string,
269
271
  tenantId: TenantId,
270
272
  ): Promise<number> {
271
- const [row] = await db
272
- .select({ v: max(eventsTable.version) })
273
- .from(eventsTable)
274
- .where(and(eq(eventsTable.aggregateId, aggregateId), eq(eventsTable.tenantId, tenantId)));
275
- return row?.v ?? 0;
273
+ return selectStreamMaxVersion(db, aggregateId, tenantId);
274
+ }
275
+
276
+ /** MAX(version) for one aggregate — no tenant filter. Used by seed idempotency. */
277
+ export async function getAggregateStreamMaxVersion(
278
+ db: DbRunner,
279
+ aggregateId: string,
280
+ ): Promise<number> {
281
+ return selectAggregateMaxVersion(db, aggregateId);
276
282
  }
277
283
 
278
284
  // Global high-water-mark = MAX(events.id). Marten/Wolverine standard for
@@ -280,8 +286,7 @@ export async function getStreamVersion(
280
286
  // the bigserial PK index — sub-millisecond cost. Returns 0n on an empty log
281
287
  // (boot, fresh tenant, post-archive).
282
288
  export async function getEventsHighWaterMark(db: DbRunner): Promise<bigint> {
283
- const [row] = await db.select({ max: max(eventsTable.id) }).from(eventsTable);
284
- return row?.max ?? 0n;
289
+ return selectEventsHighWaterMark(db);
285
290
  }
286
291
 
287
292
  // Load events strictly newer than a given version. Used by snapshot-aware
@@ -293,17 +298,12 @@ export async function loadEventsAfterVersion(
293
298
  tenantId: TenantId,
294
299
  afterVersion: number,
295
300
  ): Promise<readonly StoredEvent[]> {
296
- const rows = await db
297
- .select()
298
- .from(eventsTable)
299
- .where(
300
- and(
301
- eq(eventsTable.aggregateId, aggregateId),
302
- eq(eventsTable.tenantId, tenantId),
303
- gt(eventsTable.version, afterVersion),
304
- ),
305
- )
306
- .orderBy(asc(eventsTable.version));
301
+ const rows = await selectMany<SelectedEvent>(
302
+ db,
303
+ eventsTable,
304
+ { aggregateId, tenantId, version: { gt: afterVersion } },
305
+ { orderBy: { col: "version", direction: "asc" } },
306
+ );
307
307
  return rows.map(toStoredEvent);
308
308
  }
309
309
 
@@ -319,11 +319,17 @@ export async function loadAllEventsByType(
319
319
  db: DbRunner,
320
320
  aggregateType: string,
321
321
  ): Promise<readonly StoredEvent[]> {
322
- const rows = await db
323
- .select()
324
- .from(eventsTable)
325
- .where(eq(eventsTable.aggregateType, aggregateType))
326
- .orderBy(asc(eventsTable.createdAt), asc(eventsTable.id));
322
+ const rows = await selectMany<SelectedEvent>(
323
+ db,
324
+ eventsTable,
325
+ { aggregateType },
326
+ {
327
+ orderBy: [
328
+ { col: "createdAt", direction: "asc" },
329
+ { col: "id", direction: "asc" },
330
+ ],
331
+ },
332
+ );
327
333
  return rows.map(toStoredEvent);
328
334
  }
329
335
 
@@ -359,12 +365,12 @@ export async function* streamAllEventsByType(
359
365
  let cursorId = 0n;
360
366
  while (true) {
361
367
  signal?.throwIfAborted();
362
- const rows = await db
363
- .select()
364
- .from(eventsTable)
365
- .where(and(eq(eventsTable.aggregateType, aggregateType), gt(eventsTable.id, cursorId)))
366
- .orderBy(asc(eventsTable.id))
367
- .limit(batchSize);
368
+ const rows = await selectMany<SelectedEvent>(
369
+ db,
370
+ eventsTable,
371
+ { aggregateType, id: { gt: cursorId } },
372
+ { orderBy: { col: "id", direction: "asc" }, limit: batchSize },
373
+ );
368
374
 
369
375
  if (rows.length === 0) {
370
376
  // skip: end of stream — generator exit is the natural termination.
@@ -1,4 +1,4 @@
1
- import { sql } from "drizzle-orm";
1
+ // sql now comes from native dialect
2
2
  import { type DbConnection, tableExists } from "../db";
3
3
  import {
4
4
  bigserial,
@@ -7,6 +7,7 @@ import {
7
7
  integer,
8
8
  jsonb,
9
9
  table as pgTable,
10
+ sql,
10
11
  text,
11
12
  uniqueIndex,
12
13
  uuid,
@@ -12,6 +12,7 @@ export {
12
12
  EVENTS_PUBSUB_CHANNEL,
13
13
  type EventMetadata,
14
14
  type EventToAppend,
15
+ getAggregateStreamMaxVersion,
15
16
  getEventsHighWaterMark,
16
17
  getStreamVersion,
17
18
  loadAggregate,