@cosmicdrift/kumiko-framework 0.13.0 → 0.15.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 (314) hide show
  1. package/package.json +7 -7
  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/readiness.ts +2 -2
  24. package/src/auth/__tests__/roles.test.ts +2 -2
  25. package/src/bun-db/__tests__/PATTERN.md +73 -0
  26. package/src/bun-db/__tests__/_helpers.ts +103 -0
  27. package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
  28. package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
  29. package/src/bun-db/__tests__/bun-test-db.ts +19 -0
  30. package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
  31. package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
  32. package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
  33. package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
  34. package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
  35. package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
  36. package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
  37. package/src/bun-db/connection.ts +84 -0
  38. package/src/bun-db/index.ts +31 -0
  39. package/src/bun-db/query.ts +845 -0
  40. package/src/compliance/__tests__/duration-spec.test.ts +1 -1
  41. package/src/compliance/__tests__/profiles.test.ts +1 -1
  42. package/src/compliance/__tests__/sub-processors.test.ts +1 -1
  43. package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
  44. package/src/db/__tests__/big-int-field.test.ts +15 -14
  45. package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
  46. package/src/db/__tests__/compound-types.test.ts +1 -1
  47. package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
  48. package/src/db/__tests__/connection-options.test.ts +1 -1
  49. package/src/db/__tests__/dialect-instant.test.ts +1 -1
  50. package/src/db/__tests__/encryption.test.ts +1 -1
  51. package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
  52. package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
  53. package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
  54. package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
  55. package/src/db/__tests__/located-timestamp.test.ts +1 -1
  56. package/src/db/__tests__/money.test.ts +1 -1
  57. package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
  58. package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
  59. package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
  60. package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
  61. package/src/db/__tests__/sql-inventory.test.ts +56 -0
  62. package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
  63. package/src/db/__tests__/table-builder-required.test.ts +20 -22
  64. package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
  65. package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
  66. package/src/db/api.ts +46 -0
  67. package/src/db/apply-entity-event.ts +45 -36
  68. package/src/db/assert-exists-in.ts +5 -16
  69. package/src/db/bun-provider.ts +37 -0
  70. package/src/db/config-seed.ts +4 -4
  71. package/src/db/connection.ts +14 -57
  72. package/src/db/cursor.ts +5 -56
  73. package/src/db/dialect.ts +472 -99
  74. package/src/db/eagerload.ts +5 -12
  75. package/src/db/entity-table-meta.ts +390 -0
  76. package/src/db/event-store-executor.ts +158 -100
  77. package/src/db/index.ts +33 -5
  78. package/src/db/migrate-generator.ts +350 -0
  79. package/src/db/migrate-runner.ts +206 -0
  80. package/src/db/postgres-provider.ts +25 -0
  81. package/src/db/queries/entity-read.ts +15 -0
  82. package/src/db/queries/es-ops.ts +17 -0
  83. package/src/db/queries/event-consumer.ts +170 -0
  84. package/src/db/queries/event-store-admin.ts +127 -0
  85. package/src/db/queries/event-store.ts +155 -0
  86. package/src/db/queries/projection-rebuild.ts +59 -0
  87. package/src/db/queries/raw-sql.ts +15 -0
  88. package/src/db/queries/schema-drift.ts +35 -0
  89. package/src/db/queries/seed-context.ts +58 -0
  90. package/src/db/queries/table-ops.ts +11 -0
  91. package/src/db/queries/test-stack.ts +56 -0
  92. package/src/db/query-api.ts +22 -0
  93. package/src/db/query.ts +30 -0
  94. package/src/db/reference-data.ts +19 -22
  95. package/src/db/render-ddl.ts +57 -0
  96. package/src/db/row-helpers.ts +3 -52
  97. package/src/db/schema-inspection.ts +17 -4
  98. package/src/db/sql-inventory.ts +208 -0
  99. package/src/db/table-builder.ts +48 -40
  100. package/src/db/tenant-db.ts +105 -326
  101. package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
  102. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
  103. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
  104. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
  105. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
  106. package/src/engine/__tests__/boot-validator.test.ts +4 -3
  107. package/src/engine/__tests__/build-app-schema.test.ts +1 -1
  108. package/src/engine/__tests__/build-target.test.ts +1 -1
  109. package/src/engine/__tests__/claim-keys.test.ts +1 -1
  110. package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
  111. package/src/engine/__tests__/config-helpers.test.ts +1 -1
  112. package/src/engine/__tests__/effective-features.test.ts +1 -1
  113. package/src/engine/__tests__/engine.test.ts +1 -1
  114. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  115. package/src/engine/__tests__/event-helpers.test.ts +3 -3
  116. package/src/engine/__tests__/extends-registrar.test.ts +4 -4
  117. package/src/engine/__tests__/factories-long-text.test.ts +1 -1
  118. package/src/engine/__tests__/factories-time.test.ts +1 -1
  119. package/src/engine/__tests__/field-predicates.test.ts +1 -1
  120. package/src/engine/__tests__/hook-phases.test.ts +1 -1
  121. package/src/engine/__tests__/identifiers.test.ts +1 -1
  122. package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
  123. package/src/engine/__tests__/nav.test.ts +1 -1
  124. package/src/engine/__tests__/ownership.test.ts +10 -11
  125. package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
  126. package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
  127. package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
  128. package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
  129. package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
  130. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
  131. package/src/engine/__tests__/post-query-hook.test.ts +1 -1
  132. package/src/engine/__tests__/projection-helpers.test.ts +25 -17
  133. package/src/engine/__tests__/projection.test.ts +4 -4
  134. package/src/engine/__tests__/qualified-name.test.ts +1 -1
  135. package/src/engine/__tests__/raw-table.test.ts +9 -8
  136. package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
  137. package/src/engine/__tests__/run-in.test.ts +1 -1
  138. package/src/engine/__tests__/schema-builder.test.ts +1 -1
  139. package/src/engine/__tests__/screen.test.ts +1 -1
  140. package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
  141. package/src/engine/__tests__/state-machine.test.ts +1 -1
  142. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
  143. package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
  144. package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
  145. package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
  146. package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
  147. package/src/engine/__tests__/steps-read.test.ts +34 -40
  148. package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
  149. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
  150. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
  151. package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
  152. package/src/engine/__tests__/steps-workflow.test.ts +7 -7
  153. package/src/engine/__tests__/system-user.test.ts +1 -1
  154. package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
  155. package/src/engine/__tests__/validation-hooks.test.ts +1 -1
  156. package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
  157. package/src/engine/boot-validator/entity-handler.ts +3 -3
  158. package/src/engine/boot-validator/ownership.ts +1 -1
  159. package/src/engine/define-feature.ts +1 -2
  160. package/src/engine/entity-handlers.ts +5 -5
  161. package/src/engine/factories.ts +1 -1
  162. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
  163. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
  164. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
  165. package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
  166. package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
  167. package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
  168. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
  169. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
  170. package/src/engine/ownership.ts +113 -41
  171. package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
  172. package/src/engine/projection-helpers.ts +2 -11
  173. package/src/engine/registry.ts +2 -2
  174. package/src/engine/steps/read-find-many.ts +13 -13
  175. package/src/engine/steps/read-find-one.ts +7 -9
  176. package/src/engine/steps/unsafe-projection-delete.ts +4 -5
  177. package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
  178. package/src/engine/types/feature.ts +7 -2
  179. package/src/engine/types/fields.ts +4 -5
  180. package/src/engine/types/step.ts +10 -10
  181. package/src/engine/validate-projection-allowlist.ts +23 -3
  182. package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
  183. package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
  184. package/src/env/__tests__/compose-env-schema.test.ts +1 -1
  185. package/src/env/__tests__/dry-run.test.ts +1 -1
  186. package/src/errors/__tests__/classes.test.ts +1 -1
  187. package/src/errors/__tests__/write-failures.test.ts +1 -1
  188. package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
  189. package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
  190. package/src/es-ops/__tests__/runner.test.ts +29 -19
  191. package/src/es-ops/context.ts +9 -43
  192. package/src/es-ops/operations-schema.ts +2 -2
  193. package/src/es-ops/runner.ts +12 -26
  194. package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
  195. package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
  196. package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
  197. package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
  198. package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
  199. package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
  200. package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
  201. package/src/event-store/admin-api.ts +55 -83
  202. package/src/event-store/archive.ts +15 -39
  203. package/src/event-store/event-store.ts +92 -86
  204. package/src/event-store/events-schema.ts +2 -1
  205. package/src/event-store/index.ts +1 -0
  206. package/src/event-store/snapshot.ts +26 -24
  207. package/src/event-store/upcaster-dead-letter.ts +19 -18
  208. package/src/files/__tests__/content-disposition.test.ts +1 -1
  209. package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
  210. package/src/files/__tests__/file-handle.test.ts +1 -1
  211. package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
  212. package/src/files/__tests__/read-stream.test.ts +1 -1
  213. package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
  214. package/src/files/__tests__/write-stream.test.ts +1 -1
  215. package/src/files/__tests__/zip-stream.test.ts +1 -1
  216. package/src/files/file-ref-table.ts +2 -2
  217. package/src/files/file-routes.ts +7 -9
  218. package/src/files/storage-tracking.ts +9 -17
  219. package/src/i18n/__tests__/i18n.test.ts +1 -1
  220. package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
  221. package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
  222. package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
  223. package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
  224. package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
  225. package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
  226. package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
  227. package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
  228. package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
  229. package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
  230. package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
  231. package/src/migrations/projection-detection.ts +12 -1
  232. package/src/migrations/schema-drift.ts +7 -23
  233. package/src/observability/__tests__/console-provider.test.ts +1 -1
  234. package/src/observability/__tests__/metric-validator.test.ts +1 -1
  235. package/src/observability/__tests__/noop-provider.test.ts +1 -1
  236. package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
  237. package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
  238. package/src/observability/__tests__/recording-meter.test.ts +1 -1
  239. package/src/observability/__tests__/recording-tracer.test.ts +1 -1
  240. package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
  241. package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
  242. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
  243. package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
  244. package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
  245. package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
  246. package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
  247. package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
  248. package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
  249. package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
  250. package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
  251. package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
  252. package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
  253. package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
  254. package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
  255. package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
  256. package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
  257. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
  258. package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
  259. package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
  260. package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
  261. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
  262. package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
  263. package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
  264. package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
  265. package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
  266. package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
  267. package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
  268. package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
  269. package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
  270. package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
  271. package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
  272. package/src/pipeline/cascade-handler.ts +13 -21
  273. package/src/pipeline/dispatcher.ts +43 -48
  274. package/src/pipeline/event-consumer-state.ts +11 -2
  275. package/src/pipeline/event-dispatcher.ts +86 -146
  276. package/src/pipeline/event-retention.ts +14 -24
  277. package/src/pipeline/msp-rebuild.ts +54 -78
  278. package/src/pipeline/projection-rebuild.ts +65 -67
  279. package/src/pipeline/projection-state.ts +2 -2
  280. package/src/random/__tests__/generate.test.ts +13 -13
  281. package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
  282. package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
  283. package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
  284. package/src/redis/__tests__/redis-options.test.ts +1 -1
  285. package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
  286. package/src/search/__tests__/search-adapter.test.ts +1 -1
  287. package/src/secrets/__tests__/dek-cache.test.ts +1 -3
  288. package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
  289. package/src/secrets/__tests__/envelope.test.ts +1 -1
  290. package/src/secrets/__tests__/leak-guard.test.ts +1 -1
  291. package/src/secrets/__tests__/rotation.test.ts +1 -1
  292. package/src/stack/db.ts +25 -48
  293. package/src/stack/push-entity-projection-tables.ts +2 -4
  294. package/src/stack/table-helpers.ts +98 -61
  295. package/src/stack/test-stack.ts +8 -7
  296. package/src/testing/__tests__/db-cleanup.test.ts +40 -0
  297. package/src/testing/__tests__/e2e-generator.test.ts +1 -1
  298. package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
  299. package/src/testing/db-cleanup.ts +44 -0
  300. package/src/testing/expect-error.ts +1 -1
  301. package/src/testing/index.ts +2 -0
  302. package/src/testing/multipart-helper.ts +94 -0
  303. package/src/testing/shared-entities.ts +5 -5
  304. package/src/time/__tests__/polyfill.test.ts +1 -1
  305. package/src/time/__tests__/tz-context.test.ts +1 -1
  306. package/src/utils/__tests__/assert.test.ts +1 -1
  307. package/src/utils/__tests__/env-parse.test.ts +1 -1
  308. package/CHANGELOG.md +0 -472
  309. package/src/db/__tests__/cursor.test.ts +0 -41
  310. package/src/db/__tests__/db-helpers.test.ts +0 -369
  311. package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
  312. package/src/db/__tests__/row-helpers.test.ts +0 -59
  313. package/src/engine/steps/_drizzle-boundary.ts +0 -19
  314. package/src/files/__tests__/file-field-column.integration.ts +0 -103
@@ -15,12 +15,13 @@
15
15
  // carry a different state row (kumiko_event_consumers, not kumiko_projections)
16
16
  // and a different apply signature (3rd ctx arg).
17
17
 
18
- import { eq, sql } from "drizzle-orm";
19
- import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
18
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
19
+ import { sql } from "@cosmicdrift/kumiko-framework/db";
20
20
  import { z } from "zod";
21
21
  import { integer as pgInteger, table as pgTable, uuid as pgUuid } from "../../db/dialect";
22
22
  import { createEventStoreExecutor } from "../../db/event-store-executor";
23
- import { buildDrizzleTable } from "../../db/table-builder";
23
+ import { asRawClient, selectMany, updateMany } from "../../db/query";
24
+ import { buildEntityTable } from "../../db/table-builder";
24
25
  import { createEntity, createTextField, defineFeature } from "../../engine";
25
26
  import {
26
27
  eventConsumerStateTable,
@@ -41,13 +42,13 @@ const invoiceEntity = createEntity({
41
42
  table: "read_mspreb_invoices",
42
43
  fields: { customer: createTextField({ required: true }) },
43
44
  });
44
- const invoiceTable = buildDrizzleTable("msp-reb-invoice", invoiceEntity);
45
+ const invoiceTable = buildEntityTable("msp-reb-invoice", invoiceEntity);
45
46
 
46
47
  const paymentEntity = createEntity({
47
48
  table: "read_mspreb_payments",
48
49
  fields: { customer: createTextField({ required: true }) },
49
50
  });
50
- const paymentTable = buildDrizzleTable("msp-reb-payment", paymentEntity);
51
+ const paymentTable = buildEntityTable("msp-reb-payment", paymentEntity);
51
52
 
52
53
  // Main read-model: running balance per customer.
53
54
  const balanceTable = pgTable("read_mspreb_balance", {
@@ -88,33 +89,17 @@ const feature = defineFeature("mspreb", (r) => {
88
89
  apply: {
89
90
  [invoiceBilled.name]: async (event, tx) => {
90
91
  const p = event.payload as { customer: string; cents: number };
91
- await tx
92
- .insert(balanceTable)
93
- .values({
94
- customer: p.customer,
95
- tenantId: event.tenantId,
96
- invoicesCents: p.cents,
97
- paymentsCents: 0,
98
- })
99
- .onConflictDoUpdate({
100
- target: balanceTable.customer,
101
- set: { invoicesCents: sql`${balanceTable.invoicesCents} + ${p.cents}` },
102
- });
92
+ await asRawClient(tx).unsafe(
93
+ `INSERT INTO "read_mspreb_balance" (customer, tenant_id, invoices_cents, payments_cents) VALUES ($1::uuid, $2::uuid, $3, 0) ON CONFLICT (customer) DO UPDATE SET invoices_cents = read_mspreb_balance.invoices_cents + $3`,
94
+ [p.customer, event.tenantId, p.cents],
95
+ );
103
96
  },
104
97
  [paymentReceived.name]: async (event, tx) => {
105
98
  const p = event.payload as { customer: string; cents: number };
106
- await tx
107
- .insert(balanceTable)
108
- .values({
109
- customer: p.customer,
110
- tenantId: event.tenantId,
111
- invoicesCents: 0,
112
- paymentsCents: p.cents,
113
- })
114
- .onConflictDoUpdate({
115
- target: balanceTable.customer,
116
- set: { paymentsCents: sql`${balanceTable.paymentsCents} + ${p.cents}` },
117
- });
99
+ await asRawClient(tx).unsafe(
100
+ `INSERT INTO "read_mspreb_balance" (customer, tenant_id, invoices_cents, payments_cents) VALUES ($1::uuid, $2::uuid, 0, $3) ON CONFLICT (customer) DO UPDATE SET payments_cents = read_mspreb_balance.payments_cents + $3`,
101
+ [p.customer, event.tenantId, p.cents],
102
+ );
118
103
  },
119
104
  },
120
105
  });
@@ -248,10 +233,12 @@ describe("rebuildMultiStreamProjection — rebuildable read-model", () => {
248
233
  // Disable the saga MSP for this test — it runs on the same event types
249
234
  // and would trip its own ctx.appendEvent path during live delivery
250
235
  // (which is fine in production, but noise here).
251
- await stack.db
252
- .update(eventConsumerStateTable)
253
- .set({ status: "disabled", updatedAt: sql`now()` })
254
- .where(eq(eventConsumerStateTable.name, SAGA_MSP));
236
+ await updateMany(
237
+ stack.db,
238
+ eventConsumerStateTable,
239
+ { status: "disabled", updatedAt: sql`now()` },
240
+ { name: SAGA_MSP },
241
+ );
255
242
 
256
243
  await stack.http.writeOk("mspreb:write:invoice:bill", { customer: alice, cents: 10_00 }, admin);
257
244
  await stack.http.writeOk("mspreb:write:invoice:bill", { customer: alice, cents: 5_00 }, admin);
@@ -263,7 +250,7 @@ describe("rebuildMultiStreamProjection — rebuildable read-model", () => {
263
250
  await stack.http.writeOk("mspreb:write:invoice:bill", { customer: bob, cents: 7_50 }, admin);
264
251
  await runFullDispatcher();
265
252
 
266
- const liveRows = await stack.db.select().from(balanceTable).orderBy(balanceTable.customer);
253
+ const liveRows = await selectMany(stack.db, balanceTable);
267
254
  const aliceLive = liveRows.find((r) => r.customer === alice);
268
255
  const bobLive = liveRows.find((r) => r.customer === bob);
269
256
  expect(aliceLive).toMatchObject({ invoicesCents: 15_00, paymentsCents: 3_00 });
@@ -279,7 +266,7 @@ describe("rebuildMultiStreamProjection — rebuildable read-model", () => {
279
266
  expect(result.eventsProcessed).toBe(4); // 2 invoices + 1 payment + 1 invoice
280
267
  expect(result.lastProcessedEventId).toBeGreaterThan(0n);
281
268
 
282
- const rebuiltRows = await stack.db.select().from(balanceTable).orderBy(balanceTable.customer);
269
+ const rebuiltRows = await selectMany(stack.db, balanceTable);
283
270
  expect(rebuiltRows).toEqual(liveRows);
284
271
 
285
272
  // Consumer cursor is at head after rebuild — the live dispatcher should
@@ -291,28 +278,29 @@ describe("rebuildMultiStreamProjection — rebuildable read-model", () => {
291
278
 
292
279
  test("rebuild after table corruption restores the correct state", async () => {
293
280
  const carol = "00000000-0000-4000-8000-000000000c03";
294
- await stack.db
295
- .update(eventConsumerStateTable)
296
- .set({ status: "disabled", updatedAt: sql`now()` })
297
- .where(eq(eventConsumerStateTable.name, SAGA_MSP));
281
+ await updateMany(
282
+ stack.db,
283
+ eventConsumerStateTable,
284
+ { status: "disabled", updatedAt: sql`now()` },
285
+ { name: SAGA_MSP },
286
+ );
298
287
  await stack.http.writeOk("mspreb:write:invoice:bill", { customer: carol, cents: 42_00 }, admin);
299
288
  await runFullDispatcher();
300
289
 
301
290
  // Corrupt the read-model — simulate a buggy apply() landing bad numbers.
302
- await stack.db
303
- .update(balanceTable)
304
- .set({ invoicesCents: -999, paymentsCents: 999 })
305
- .where(eq(balanceTable.customer, carol));
291
+ await updateMany(
292
+ stack.db,
293
+ balanceTable,
294
+ { invoicesCents: -999, paymentsCents: 999 },
295
+ { customer: carol },
296
+ );
306
297
 
307
298
  await rebuildMultiStreamProjection(BALANCE_MSP, {
308
299
  db: stack.db,
309
300
  registry: stack.registry,
310
301
  });
311
302
 
312
- const [row] = await stack.db
313
- .select()
314
- .from(balanceTable)
315
- .where(eq(balanceTable.customer, carol));
303
+ const [row] = await selectMany(stack.db, balanceTable, { customer: carol });
316
304
  expect(row).toMatchObject({ invoicesCents: 42_00, paymentsCents: 0 });
317
305
  });
318
306
  });
@@ -339,18 +327,22 @@ describe("rebuildMultiStreamProjection — guard rails", () => {
339
327
  test("saga MSP using ctx.appendEvent fails rebuild at the first appendEvent call", async () => {
340
328
  const dave = "00000000-0000-4000-8000-000000000d04";
341
329
  // Disable the saga in live passes so we control when the apply runs.
342
- await stack.db
343
- .update(eventConsumerStateTable)
344
- .set({ status: "disabled", updatedAt: sql`now()` })
345
- .where(eq(eventConsumerStateTable.name, SAGA_MSP));
330
+ await updateMany(
331
+ stack.db,
332
+ eventConsumerStateTable,
333
+ { status: "disabled", updatedAt: sql`now()` },
334
+ { name: SAGA_MSP },
335
+ );
346
336
  await stack.http.writeOk("mspreb:write:invoice:bill", { customer: dave, cents: 1_00 }, admin);
347
337
  // Put the consumer back to idle so rebuild doesn't treat it as "just
348
338
  // disabled on purpose" — rebuild is opinionated about WHEN it refuses,
349
339
  // not about the consumer's live-status.
350
- await stack.db
351
- .update(eventConsumerStateTable)
352
- .set({ status: "idle", updatedAt: sql`now()` })
353
- .where(eq(eventConsumerStateTable.name, SAGA_MSP));
340
+ await updateMany(
341
+ stack.db,
342
+ eventConsumerStateTable,
343
+ { status: "idle", updatedAt: sql`now()` },
344
+ { name: SAGA_MSP },
345
+ );
354
346
 
355
347
  await expect(
356
348
  rebuildMultiStreamProjection(SAGA_MSP, {
@@ -6,12 +6,12 @@
6
6
  // event-dispatcher — at-least-once delivery, strictly ordered by events.id
7
7
  // per MSP consumer, dead-letters on repeated handler failures.
8
8
 
9
- import { eq, sql } from "drizzle-orm";
10
- import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
9
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
11
10
  import { z } from "zod";
12
11
  import { integer as pgInteger, table as pgTable, uuid as pgUuid } from "../../db/dialect";
13
12
  import { createEventStoreExecutor } from "../../db/event-store-executor";
14
- import { buildDrizzleTable } from "../../db/table-builder";
13
+ import { asRawClient, selectMany } from "../../db/query";
14
+ import { buildEntityTable } from "../../db/table-builder";
15
15
  import { createEntity, createTextField, defineFeature } from "../../engine";
16
16
  import {
17
17
  createTestUser,
@@ -28,13 +28,13 @@ const shipmentEntity = createEntity({
28
28
  table: "read_msp_shipments",
29
29
  fields: { customer: createTextField({ required: true }) },
30
30
  });
31
- const shipmentTable = buildDrizzleTable("msp-shipment", shipmentEntity);
31
+ const shipmentTable = buildEntityTable("msp-shipment", shipmentEntity);
32
32
 
33
33
  const refundEntity = createEntity({
34
34
  table: "read_msp_refunds",
35
35
  fields: { customer: createTextField({ required: true }) },
36
36
  });
37
- const refundTable = buildDrizzleTable("msp-refund", refundEntity);
37
+ const refundTable = buildEntityTable("msp-refund", refundEntity);
38
38
 
39
39
  // Cross-cutting MSP: one row per customer, sums shipments − refunds. Key
40
40
  // differences from a single-stream projection:
@@ -68,41 +68,19 @@ const mspFeature = defineFeature("msptest", (r) => {
68
68
  apply: {
69
69
  [shipmentBilled.name]: async (event, tx) => {
70
70
  const p = event.payload as { customer: string; cents: number };
71
- await tx
72
- .insert(customerBalanceTable)
73
- .values({
74
- customer: p.customer,
75
- tenantId: event.tenantId,
76
- shipments: 1,
77
- refunds: 0,
78
- netCents: p.cents,
79
- })
80
- .onConflictDoUpdate({
81
- target: customerBalanceTable.customer,
82
- set: {
83
- shipments: sql`${customerBalanceTable.shipments} + 1`,
84
- netCents: sql`${customerBalanceTable.netCents} + ${p.cents}`,
85
- },
86
- });
71
+ await asRawClient(tx).unsafe(
72
+ `INSERT INTO "read_msp_customer_balance" (customer, tenant_id, shipments, refunds, net_cents) VALUES ($1::uuid, $2::uuid, 1, 0, $3) ON CONFLICT (customer) DO UPDATE SET shipments = read_msp_customer_balance.shipments + 1, net_cents = read_msp_customer_balance.net_cents + $3`,
73
+ [p.customer, event.tenantId, p.cents],
74
+ );
87
75
  },
88
76
  [refundIssued.name]: async (event, tx) => {
89
77
  const p = event.payload as { customer: string; cents: number };
90
- await tx
91
- .insert(customerBalanceTable)
92
- .values({
93
- customer: p.customer,
94
- tenantId: event.tenantId,
95
- shipments: 0,
96
- refunds: 1,
97
- netCents: -p.cents,
98
- })
99
- .onConflictDoUpdate({
100
- target: customerBalanceTable.customer,
101
- set: {
102
- refunds: sql`${customerBalanceTable.refunds} + 1`,
103
- netCents: sql`${customerBalanceTable.netCents} - ${p.cents}`,
104
- },
105
- });
78
+ // -$3 ohne cast wird vom planner als "unary minus on unknown type"
79
+ // mehrdeutig — wir casten $3 explizit auf integer.
80
+ await asRawClient(tx).unsafe(
81
+ `INSERT INTO "read_msp_customer_balance" (customer, tenant_id, shipments, refunds, net_cents) VALUES ($1::uuid, $2::uuid, 0, 1, -$3::integer) ON CONFLICT (customer) DO UPDATE SET refunds = read_msp_customer_balance.refunds + 1, net_cents = read_msp_customer_balance.net_cents - $3::integer`,
82
+ [p.customer, event.tenantId, p.cents],
83
+ );
106
84
  },
107
85
  },
108
86
  });
@@ -207,10 +185,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
207
185
  // Drain the dispatcher — MSPs run async.
208
186
  await stack.eventDispatcher?.runOnce();
209
187
 
210
- const rows = await stack.db
211
- .select()
212
- .from(customerBalanceTable)
213
- .orderBy(customerBalanceTable.customer);
188
+ const rows = await selectMany(stack.db, customerBalanceTable);
214
189
  const byCustomer = new Map(rows.map((r) => [r.customer, r]));
215
190
 
216
191
  expect(byCustomer.get(customerA)).toMatchObject({
@@ -239,10 +214,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
239
214
  expect(pass2?.byConsumer[mspName]?.processed ?? 0).toBe(0);
240
215
 
241
216
  // Row state is stable across the no-op pass.
242
- const [row] = await stack.db
243
- .select()
244
- .from(customerBalanceTable)
245
- .where(eq(customerBalanceTable.customer, cust));
217
+ const [row] = await selectMany(stack.db, customerBalanceTable, { customer: cust });
246
218
  expect(row?.shipments).toBe(1);
247
219
  expect(row?.netCents).toBe(42);
248
220
  });
@@ -273,10 +245,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
273
245
  );
274
246
  await stack.eventDispatcher?.runOnce();
275
247
 
276
- const rows = await stack.db
277
- .select()
278
- .from(customerBalanceTable)
279
- .orderBy(customerBalanceTable.customer);
248
+ const rows = await selectMany(stack.db, customerBalanceTable);
280
249
  const alpha = rows.find((r) => r.customer === customerAlpha);
281
250
  const beta = rows.find((r) => r.customer === customerBeta);
282
251
 
@@ -298,10 +267,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
298
267
 
299
268
  // Only the shipment-billed event was folded in; the auto "created"
300
269
  // event was silently skipped.
301
- const [row] = await stack.db
302
- .select()
303
- .from(customerBalanceTable)
304
- .where(eq(customerBalanceTable.customer, cust));
270
+ const [row] = await selectMany(stack.db, customerBalanceTable, { customer: cust });
305
271
  expect(row?.shipments).toBe(1);
306
272
  });
307
273
  });
@@ -13,27 +13,25 @@
13
13
  // jitter does not. If this ever flakes in CI, drop to 3000 — the goal is
14
14
  // "catastrophic regression detector", not "perf SLO".
15
15
 
16
- import { sql } from "drizzle-orm";
17
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
18
- import {
19
- integer as drizzleInteger,
20
- table as drizzlePgTable,
21
- uuid as drizzleUuid,
22
- } from "../../db/dialect";
16
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
17
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
18
+ import { integer, table as pgTable, uuid as pgUuid } from "../../db/dialect";
19
+ import { asRawClient } from "../../db/query";
23
20
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
24
21
  import type { ProjectionDefinition } from "../../engine/types";
25
22
  import { createEventsTable } from "../../event-store";
26
23
  import { createProjectionStateTable, rebuildProjection } from "../../pipeline";
27
- import { createTestDb, type TestDb, TestUsers, unsafePushTables } from "../../stack";
24
+ import { TestUsers, unsafePushTables } from "../../stack";
25
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
28
26
  import { generateId as uuid } from "../../utils";
29
27
 
30
28
  // Counter projection: every task.created bumps a counter, every
31
29
  // task.updated is a no-op. Enough to exercise the apply path —
32
30
  // rebuild cost is dominated by event iteration + apply dispatch,
33
31
  // not the projection state shape.
34
- const taskCountTable = drizzlePgTable("read_perf_rebuild_task_count", {
35
- tenantId: drizzleUuid("tenant_id").primaryKey(),
36
- count: drizzleInteger("count").notNull().default(0),
32
+ const taskCountTable = pgTable("read_perf_rebuild_task_count", {
33
+ tenantId: pgUuid("tenant_id").primaryKey(),
34
+ count: integer("count").notNull().default(0),
37
35
  });
38
36
 
39
37
  const taskCountProjection: ProjectionDefinition = {
@@ -42,13 +40,10 @@ const taskCountProjection: ProjectionDefinition = {
42
40
  table: taskCountTable,
43
41
  apply: {
44
42
  "task.created": async (event, tx) => {
45
- await tx
46
- .insert(taskCountTable)
47
- .values({ tenantId: event.tenantId, count: 1 })
48
- .onConflictDoUpdate({
49
- target: taskCountTable.tenantId,
50
- set: { count: sql`${taskCountTable.count} + 1` },
51
- });
43
+ await asRawClient(tx).unsafe(
44
+ `INSERT INTO "read_perf_rebuild_task_count" (tenant_id, count) VALUES ($1, 1) ON CONFLICT (tenant_id) DO UPDATE SET count = read_perf_rebuild_task_count.count + 1`,
45
+ [event.tenantId],
46
+ );
52
47
  },
53
48
  "task.updated": async (_event, _tx) => {
54
49
  // No-op apply — measuring event-iteration overhead, not per-event
@@ -69,11 +64,12 @@ const feature = defineFeature("perfrebuild", (r) => {
69
64
  });
70
65
 
71
66
  const admin = TestUsers.admin;
72
- let testDb: TestDb;
67
+ let testDb: BunTestDb;
73
68
  const registry = createRegistry([feature]);
74
69
  const qualifiedProjectionName = "perfrebuild:projection:task-count";
75
70
 
76
71
  beforeAll(async () => {
72
+ await ensureTemporalPolyfill();
77
73
  testDb = await createTestDb();
78
74
  await createEventsTable(testDb.db);
79
75
  await createProjectionStateTable(testDb.db);
@@ -85,8 +81,8 @@ afterAll(async () => {
85
81
  });
86
82
 
87
83
  beforeEach(async () => {
88
- await testDb.db.execute(
89
- sql`TRUNCATE kumiko_events, read_perf_rebuild_task_count, kumiko_projections RESTART IDENTITY CASCADE`,
84
+ await asRawClient(testDb.db).unsafe(
85
+ `TRUNCATE kumiko_events, read_perf_rebuild_task_count, kumiko_projections RESTART IDENTITY CASCADE`,
90
86
  );
91
87
  });
92
88
 
@@ -96,25 +92,31 @@ beforeEach(async () => {
96
92
  async function seedEvents(count: number, depth: number): Promise<void> {
97
93
  const userId = uuid();
98
94
  // v1 creates
99
- await testDb.db.execute(sql`
95
+ await asRawClient(testDb.db).unsafe(
96
+ `
100
97
  INSERT INTO kumiko_events (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
101
- SELECT gen_random_uuid(), 'task', ${admin.tenantId}::uuid, 1, 'task.created',
98
+ SELECT gen_random_uuid(), 'task', $1::uuid, 1, 'task.created',
102
99
  jsonb_build_object('title', 'Task ' || gs.n),
103
- jsonb_build_object('userId', ${userId}::text),
104
- ${userId}::text
105
- FROM generate_series(1, ${count}) AS gs(n);
106
- `);
100
+ jsonb_build_object('userId', $2::text),
101
+ $3::text
102
+ FROM generate_series(1, $4) AS gs(n);
103
+ `,
104
+ [admin.tenantId, userId, userId, count],
105
+ );
107
106
  // v2..depth updates
108
107
  for (let v = 2; v <= depth; v++) {
109
- await testDb.db.execute(sql`
108
+ await asRawClient(testDb.db).unsafe(
109
+ `
110
110
  INSERT INTO kumiko_events (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
111
- SELECT e.aggregate_id, 'task', ${admin.tenantId}::uuid, ${v}, 'task.updated',
112
- jsonb_build_object('title', 'Task v' || ${v}),
113
- jsonb_build_object('userId', ${userId}::text),
114
- ${userId}::text
111
+ SELECT e.aggregate_id, 'task', $1::uuid, $2, 'task.updated',
112
+ jsonb_build_object('title', 'Task v' || $3),
113
+ jsonb_build_object('userId', $4::text),
114
+ $5::text
115
115
  FROM kumiko_events e
116
- WHERE e.aggregate_type = 'task' AND e.version = ${v - 1};
117
- `);
116
+ WHERE e.aggregate_type = 'task' AND e.version = $6;
117
+ `,
118
+ [admin.tenantId, v, v, userId, userId, v - 1],
119
+ );
118
120
  }
119
121
  }
120
122
 
@@ -9,7 +9,7 @@
9
9
  // Memory `feedback_no_fake_dispatcher`: real HTTP-Calls via setupTestStack,
10
10
  // nicht createTestDispatcher.
11
11
 
12
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
12
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
13
13
  import { z } from "zod";
14
14
  import { createEntity, createTextField, defineFeature } from "../../engine";
15
15
  import type { PostQueryHookFn } from "../../engine/types";
@@ -10,15 +10,11 @@
10
10
  // - status lifecycle (idle → rebuilding → idle on success, → failed on throw)
11
11
  // - never-rebuilt projection has sensible default state
12
12
 
13
- import { eq, sql } from "drizzle-orm";
14
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
15
- import {
16
- integer as drizzleInteger,
17
- table as drizzlePgTable,
18
- uuid as drizzleUuid,
19
- } from "../../db/dialect";
13
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
14
+ import { integer, table as pgTable, uuid } from "../../db/dialect";
20
15
  import { createEventStoreExecutor } from "../../db/event-store-executor";
21
- import { buildDrizzleTable } from "../../db/table-builder";
16
+ import { asRawClient, insertOne, selectMany } from "../../db/query";
17
+ import { buildEntityTable } from "../../db/table-builder";
22
18
  import { createTenantDb, type TenantDb } from "../../db/tenant-db";
23
19
  import {
24
20
  createEntity,
@@ -54,23 +50,19 @@ const itemEntity = createEntity({
54
50
  },
55
51
  softDelete: true,
56
52
  });
57
- const itemTable = buildDrizzleTable("rebuild-item", itemEntity);
53
+ const itemTable = buildEntityTable("rebuild-item", itemEntity);
58
54
 
59
- const itemsPerGroupTable = drizzlePgTable("read_rebuild_items_per_group", {
60
- groupId: drizzleUuid("group_id").primaryKey(),
61
- tenantId: drizzleUuid("tenant_id").notNull(),
62
- itemCount: drizzleInteger("item_count").notNull().default(0),
55
+ const itemsPerGroupTable = pgTable("read_rebuild_items_per_group", {
56
+ groupId: uuid("group_id").primaryKey(),
57
+ tenantId: uuid("tenant_id").notNull(),
58
+ itemCount: integer("item_count").notNull().default(0),
63
59
  });
64
60
 
65
61
  async function bump(tx: unknown, groupId: string, tenantId: string, delta: number): Promise<void> {
66
- // biome-ignore lint/suspicious/noExplicitAny: tx is DbRunner
67
- await (tx as any)
68
- .insert(itemsPerGroupTable)
69
- .values({ groupId, tenantId, itemCount: delta })
70
- .onConflictDoUpdate({
71
- target: itemsPerGroupTable.groupId,
72
- set: { itemCount: sql`${itemsPerGroupTable.itemCount} + ${delta}` },
73
- });
62
+ await asRawClient(tx).unsafe(
63
+ `INSERT INTO "read_rebuild_items_per_group" (group_id, tenant_id, item_count) VALUES ($1::uuid, $2::uuid, $3) ON CONFLICT (group_id) DO UPDATE SET item_count = read_rebuild_items_per_group.item_count + $3`,
64
+ [groupId, tenantId, delta],
65
+ );
74
66
  }
75
67
 
76
68
  type ItemCreated = { groupId: string };
@@ -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_rebuild_items, read_rebuild_items_per_group, kumiko_projections RESTART IDENTITY CASCADE`,
116
+ await asRawClient(testDb.db).unsafe(
117
+ `TRUNCATE kumiko_events, read_rebuild_items, read_rebuild_items_per_group, kumiko_projections RESTART IDENTITY CASCADE`,
126
118
  );
127
119
  });
128
120
 
@@ -138,10 +130,7 @@ async function appendCreatedEvent(groupId: string, name: string): Promise<void>
138
130
  }
139
131
 
140
132
  async function getCount(groupId: string): Promise<number | undefined> {
141
- const [row] = await testDb.db
142
- .select()
143
- .from(itemsPerGroupTable)
144
- .where(eq(itemsPerGroupTable.groupId, groupId));
133
+ const [row] = await selectMany(testDb.db, itemsPerGroupTable, { groupId: groupId });
145
134
  return row?.itemCount;
146
135
  }
147
136
 
@@ -174,9 +163,11 @@ describe("rebuildProjection — happy path", () => {
174
163
  await appendCreatedEvent(group, "b");
175
164
 
176
165
  // Seed the projection table with a stale/wrong value.
177
- await testDb.db
178
- .insert(itemsPerGroupTable)
179
- .values({ groupId: group, tenantId: admin.tenantId, itemCount: 999 });
166
+ await insertOne(testDb.db, itemsPerGroupTable, {
167
+ groupId: group,
168
+ tenantId: admin.tenantId,
169
+ itemCount: 999,
170
+ });
180
171
 
181
172
  const result = await rebuildProjection(qualifiedProjectionName, {
182
173
  db: testDb.db,
@@ -3,7 +3,7 @@
3
3
  // drizzle-tables directly. Auto-filters by tenant_id when the projection
4
4
  // table carries that column.
5
5
 
6
- import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
6
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
7
7
  import { z } from "zod";
8
8
  import {
9
9
  integer as pgInteger,
@@ -12,7 +12,8 @@ import {
12
12
  uuid as pgUuid,
13
13
  } from "../../db/dialect";
14
14
  import { createEventStoreExecutor } from "../../db/event-store-executor";
15
- import { buildDrizzleTable } from "../../db/table-builder";
15
+ import { insertOne } from "../../db/query";
16
+ import { buildEntityTable } from "../../db/table-builder";
16
17
  import { createEntity, createTextField, defineFeature } from "../../engine";
17
18
  import {
18
19
  resetEventStore,
@@ -26,7 +27,7 @@ const widgetEntity = createEntity({
26
27
  table: "read_qp_widgets",
27
28
  fields: { name: createTextField({ required: true }) },
28
29
  });
29
- const widgetTable = buildDrizzleTable("qp-widget", widgetEntity);
30
+ const widgetTable = buildEntityTable("qp-widget", widgetEntity);
30
31
 
31
32
  // Tenant-scoped projection — auto-filter by tenant_id.
32
33
  const tenantScopedTable = pgTable("read_qp_widget_count_tenant", {
@@ -52,7 +53,7 @@ const qpFeature = defineFeature("qp", (r) => {
52
53
  apply: {
53
54
  "qp-widget.created": async (event, tx) => {
54
55
  const p = event.payload as { name?: string };
55
- await tx.insert(tenantScopedTable).values({
56
+ await insertOne(tx, tenantScopedTable, {
56
57
  widgetId: event.aggregateId,
57
58
  tenantId: event.tenantId,
58
59
  label: p.name ?? "?",
@@ -68,7 +69,7 @@ const qpFeature = defineFeature("qp", (r) => {
68
69
  apply: {
69
70
  "qp-widget.created": async (event, tx) => {
70
71
  const p = event.payload as { name?: string };
71
- await tx.insert(systemScopedTable).values({
72
+ await insertOne(tx, systemScopedTable, {
72
73
  widgetId: event.aggregateId,
73
74
  label: p.name ?? "?",
74
75
  });
@@ -1,5 +1,6 @@
1
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { createTestRedis, type TestRedis } from "../../stack";
3
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
3
4
  import { createEntityCache } from "../entity-cache";
4
5
  import { createEventDedup } from "../event-dedup";
5
6
  import { createIdempotencyGuard } from "../idempotency";
@@ -7,6 +8,7 @@ import { createIdempotencyGuard } from "../idempotency";
7
8
  let testRedis: TestRedis;
8
9
 
9
10
  beforeAll(async () => {
11
+ await ensureTemporalPolyfill();
10
12
  testRedis = await createTestRedis();
11
13
  });
12
14