@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
@@ -8,10 +8,11 @@
8
8
  // 3. The table column types survive round-trip (bigint → number via
9
9
  // Drizzle's mode:"number", so arithmetic in assertions Just Works).
10
10
 
11
- import { eq, 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";
12
+ import { asRawClient, selectMany } from "../../db/query";
13
13
  import type { SessionUser } from "../../engine";
14
14
  import { createTestUser, setupTestStack, type TestStack, TestUsers } from "../../stack";
15
+ import { buildMultipartBody, patchFileInstanceofForBunTest } from "../../testing";
15
16
  import {
16
17
  createInMemoryFileProvider,
17
18
  filesStorageTrackingFeature,
@@ -36,6 +37,7 @@ const SMALL = new Uint8Array([0x89, 0x50, 0x4e, 0x47, ...Array(16).fill(0)]); //
36
37
  const LARGE = new Uint8Array([0x89, 0x50, 0x4e, 0x47, ...Array(96).fill(0)]); // 100 bytes
37
38
 
38
39
  beforeAll(async () => {
40
+ patchFileInstanceofForBunTest();
39
41
  provider = createInMemoryFileProvider();
40
42
  stack = await setupTestStack({
41
43
  features: [filesStorageTrackingFeature],
@@ -54,8 +56,8 @@ beforeEach(async () => {
54
56
  // each test starts from zero. kumiko_event_consumers registration is
55
57
  // re-asserted below; truncating it forces ensureRegistered to seed the
56
58
  // cursor at event.id = 0.
57
- await stack.db.execute(
58
- sql`TRUNCATE kumiko_events, kumiko_event_consumers, file_refs, read_tenant_storage_usage RESTART IDENTITY CASCADE`,
59
+ await asRawClient(stack.db).unsafe(
60
+ `TRUNCATE kumiko_events, kumiko_event_consumers, file_refs, read_tenant_storage_usage RESTART IDENTITY CASCADE`,
59
61
  );
60
62
  await stack.eventDispatcher?.ensureRegistered();
61
63
  });
@@ -64,23 +66,20 @@ async function upload(user: SessionUser, name: string, content: Uint8Array): Pro
64
66
  const token = await stack.jwt.sign(user);
65
67
  const formData = new FormData();
66
68
  formData.append("file", new File([Buffer.from(content)], name, { type: "image/png" }));
69
+ const { body: multipartBody, contentType } = await buildMultipartBody(formData);
67
70
  const res = await stack.app.request("/api/files", {
68
71
  method: "POST",
69
- headers: { Authorization: `Bearer ${token}` },
70
- body: formData,
72
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
73
+ body: multipartBody,
71
74
  });
72
75
  expect(res.status).toBe(201);
73
76
  }
74
77
 
75
78
  async function usageFor(tenantId: string): Promise<{ totalBytes: number; fileCount: number }> {
76
- const [row] = await stack.db
77
- .select({
78
- totalBytes: tenantStorageUsageTable.totalBytes,
79
- fileCount: tenantStorageUsageTable.fileCount,
80
- })
81
- .from(tenantStorageUsageTable)
82
- .where(eq(tenantStorageUsageTable.tenantId, tenantId));
83
- return row ?? { totalBytes: 0, fileCount: 0 };
79
+ const [row] = await selectMany(stack.db, tenantStorageUsageTable, { tenantId });
80
+ return row
81
+ ? { totalBytes: Number(row["totalBytes"]) ?? 0, fileCount: Number(row["fileCount"]) ?? 0 }
82
+ : { totalBytes: 0, fileCount: 0 };
84
83
  }
85
84
 
86
85
  describe("tenant-storage-usage MSP", () => {
@@ -105,10 +104,7 @@ describe("tenant-storage-usage MSP", () => {
105
104
 
106
105
  // Exactly one row per tenant — the UPSERT must not insert a second
107
106
  // row for the second upload.
108
- const rows = await stack.db
109
- .select()
110
- .from(tenantStorageUsageTable)
111
- .where(eq(tenantStorageUsageTable.tenantId, admin.tenantId));
107
+ const rows = await selectMany(stack.db, tenantStorageUsageTable, { tenantId: admin.tenantId });
112
108
  expect(rows).toHaveLength(1);
113
109
  });
114
110
 
@@ -128,11 +124,10 @@ describe("tenant-storage-usage MSP", () => {
128
124
  await upload(admin, "a.png", SMALL);
129
125
  await stack.eventDispatcher?.runOnce();
130
126
 
131
- const [first] = await stack.db
132
- .select({ at: tenantStorageUsageTable.lastUpdatedAt })
133
- .from(tenantStorageUsageTable)
134
- .where(eq(tenantStorageUsageTable.tenantId, admin.tenantId));
135
- expect(first?.at).toBeInstanceOf(Temporal.Instant);
127
+ const [first] = await selectMany(stack.db, tenantStorageUsageTable, {
128
+ tenantId: admin.tenantId,
129
+ });
130
+ expect(first?.["lastUpdatedAt"]).toBeInstanceOf(Temporal.Instant);
136
131
 
137
132
  // Postgres NOW() resolution is microseconds; a second upload a beat
138
133
  // later must produce a strictly later timestamp (or at least not an
@@ -142,12 +137,13 @@ describe("tenant-storage-usage MSP", () => {
142
137
  await upload(admin, "b.png", LARGE);
143
138
  await stack.eventDispatcher?.runOnce();
144
139
 
145
- const [second] = await stack.db
146
- .select({ at: tenantStorageUsageTable.lastUpdatedAt })
147
- .from(tenantStorageUsageTable)
148
- .where(eq(tenantStorageUsageTable.tenantId, admin.tenantId));
149
- expect(second?.at).toBeInstanceOf(Temporal.Instant);
150
- if (!first?.at || !second?.at) throw new Error("missing rows");
151
- expect(Temporal.Instant.compare(second.at, first.at)).toBeGreaterThanOrEqual(0);
140
+ const [second] = await selectMany(stack.db, tenantStorageUsageTable, {
141
+ tenantId: admin.tenantId,
142
+ });
143
+ expect(second?.["lastUpdatedAt"]).toBeInstanceOf(Temporal.Instant);
144
+ if (!first?.["lastUpdatedAt"] || !second?.["lastUpdatedAt"]) throw new Error("missing rows");
145
+ expect(
146
+ Temporal.Instant.compare(second["lastUpdatedAt"], first["lastUpdatedAt"]),
147
+ ).toBeGreaterThanOrEqual(0);
152
148
  });
153
149
  });
@@ -12,10 +12,10 @@
12
12
  // AsyncIterable-source pinst die Streaming-Semantik (Caller streamt
13
13
  // chunk-fuer-chunk, Provider niemals alles im Memory).
14
14
 
15
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
15
16
  import { mkdtemp, readdir, rm, stat } from "node:fs/promises";
16
17
  import { tmpdir } from "node:os";
17
18
  import { join } from "node:path";
18
- import { afterEach, beforeEach, describe, expect, test } from "vitest";
19
19
  import { createInMemoryFileProvider } from "../in-memory-provider";
20
20
  import { createLocalProvider } from "../local-provider";
21
21
 
@@ -11,11 +11,11 @@
11
11
  // kann — kein "passt nur in unserer eigenen reverse-engineerten
12
12
  // Welt".
13
13
 
14
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
14
15
  import { spawn } from "node:child_process";
15
16
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
16
17
  import { tmpdir } from "node:os";
17
18
  import { join } from "node:path";
18
- import { afterEach, beforeEach, describe, expect, test } from "vitest";
19
19
  import { getTemporal } from "../../time";
20
20
  import { createZipStream, type ZipEntry } from "../zip-stream";
21
21
 
@@ -1,5 +1,5 @@
1
- import { sql } from "drizzle-orm";
2
- import { instant, integer, table as pgTable, text, uuid } from "../db/dialect";
1
+ // sql now comes from native dialect
2
+ import { instant, integer, table as pgTable, sql, text, uuid } from "../db/dialect";
3
3
 
4
4
  // `id` is a UUID (not serial): it doubles as the aggregate-id for the
5
5
  // `fileRef` event stream — every upload appends exactly one
@@ -1,8 +1,8 @@
1
- import { and, eq } from "drizzle-orm";
1
+ import { deleteMany, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
4
  import { getUser } from "../api/auth-middleware";
5
- import type { DbConnection } from "../db/connection";
5
+ import type { DbConnection, DbTx } from "../db/connection";
6
6
  import type { EventDef } from "../engine/types";
7
7
  import { isFileField, type Registry, type SessionUser, type TenantId } from "../engine/types";
8
8
  import { append as appendEvent } from "../event-store/event-store";
@@ -172,8 +172,9 @@ export function createFileRoutes(options: FileRoutesOptions): Hono {
172
172
  // Atomic: insert FileRef + append files:event:uploaded in one tx. Either
173
173
  // both land or neither — no dangling FileRef without event, no event
174
174
  // referencing a row that doesn't exist.
175
- await db.transaction(async (tx) => {
176
- await tx.insert(fileRefsTable).values({
175
+ await db.begin(async (tx: DbTx) => {
176
+ const { insertOne } = await import("../bun-db/query");
177
+ await insertOne(tx, fileRefsTable, {
177
178
  id: fileRefId,
178
179
  tenantId: user.tenantId,
179
180
  storageKey,
@@ -271,7 +272,7 @@ export function createFileRoutes(options: FileRoutesOptions): Hono {
271
272
  // Upload. Umgekehrt liesse ein storage-success + db-fail eine Row mit
272
273
  // permanent-broken Reference zurück — aus Sicht der API "Datei
273
274
  // existiert" aber jeder Read 404t aus dem Provider.
274
- await db.delete(fileRefsTable).where(eq(fileRefsTable.id, id));
275
+ await deleteMany(db, fileRefsTable, { id: id });
275
276
  await storageProvider.delete(fileRef.storageKey);
276
277
  return c.json({ ok: true });
277
278
  });
@@ -342,10 +343,7 @@ export function createFileRoutes(options: FileRoutesOptions): Hono {
342
343
  });
343
344
 
344
345
  async function loadFileForTenant(id: string, tenantId: TenantId): Promise<FileRef | null> {
345
- const [row] = await db
346
- .select()
347
- .from(fileRefsTable)
348
- .where(and(eq(fileRefsTable.id, id), eq(fileRefsTable.tenantId, tenantId)));
346
+ const [row] = await selectMany(db, fileRefsTable, { id, tenantId });
349
347
  return (row as FileRef | undefined) ?? null; // @cast-boundary db-row
350
348
  }
351
349
 
@@ -10,8 +10,8 @@
10
10
  // consumer-cursor row. Apps that want it pass filesStorageTrackingFeature
11
11
  // into createApp / setupTestStack alongside their domain features.
12
12
 
13
- import { sql } from "drizzle-orm";
14
- import { bigint, instant, integer, table as pgTable, uuid } from "../db/dialect";
13
+ import { bigint, instant, integer, table as pgTable, sql, uuid } from "../db/dialect";
14
+ import { incrementCounter } from "../db/query";
15
15
  import { defineFeature, typedPayload } from "../engine";
16
16
  import { fileUploadedEvent } from "./file-routes";
17
17
 
@@ -39,21 +39,13 @@ export const filesStorageTrackingFeature = defineFeature("files-storage-tracking
39
39
  // The SQL increment guarantees correctness under concurrent dispatcher
40
40
  // runs (shouldn't happen with a single consumer, but the invariant is
41
41
  // free and cheap — no reason to rely on serial delivery).
42
- await tx
43
- .insert(tenantStorageUsageTable)
44
- .values({
45
- tenantId: event.tenantId,
46
- totalBytes: payload.size,
47
- fileCount: 1,
48
- })
49
- .onConflictDoUpdate({
50
- target: tenantStorageUsageTable.tenantId,
51
- set: {
52
- totalBytes: sql`${tenantStorageUsageTable.totalBytes} + ${payload.size}`,
53
- fileCount: sql`${tenantStorageUsageTable.fileCount} + 1`,
54
- lastUpdatedAt: sql`NOW()`,
55
- },
56
- });
42
+ await incrementCounter(
43
+ tx,
44
+ tenantStorageUsageTable,
45
+ { tenantId: event.tenantId, totalBytes: payload.size, fileCount: 1 },
46
+ { totalBytes: payload.size, fileCount: 1 },
47
+ { set: { lastUpdatedAt: sql`now()` } },
48
+ );
57
49
  },
58
50
  },
59
51
  });
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
3
3
  import { createI18n } from "../index";
4
4
 
@@ -1,10 +1,12 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
1
2
  import type { Hono } from "hono";
2
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
3
3
  import { z } from "zod";
4
4
  import { buildServer, type JwtHelper } from "../../api";
5
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
5
6
  import { createRegistry, defineFeature, type SessionUser } from "../../engine";
6
- import { createTestDb, createTestRedis, type TestDb, type TestRedis, TestUsers } from "../../stack";
7
+ import { createTestRedis, type TestRedis, TestUsers } from "../../stack";
7
8
  import { waitFor } from "../../testing";
9
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
8
10
  import { createJobRunner, type JobRunner } from "../job-runner";
9
11
 
10
12
  // --- Track job executions ---
@@ -64,7 +66,7 @@ const analyticsFeature = defineFeature("analytics", (r) => {
64
66
 
65
67
  // --- Setup ---
66
68
 
67
- let testDb: TestDb;
69
+ let testDb: BunTestDb;
68
70
  let testRedis: TestRedis;
69
71
  let app: Hono;
70
72
  let jwt: JwtHelper;
@@ -74,6 +76,7 @@ const adminUser = TestUsers.admin;
74
76
  const JWT_SECRET = "event-trigger-test-secret-minimum-32-chars!!";
75
77
 
76
78
  beforeAll(async () => {
79
+ await ensureTemporalPolyfill();
77
80
  testDb = await createTestDb();
78
81
  testRedis = await createTestRedis();
79
82
 
@@ -6,13 +6,15 @@
6
6
  // Validator-Reject). Hier prüfen wir die runtime-dispatch-Pfade durch
7
7
  // einen echten BullMQ-Worker.
8
8
 
9
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
9
10
  import type { Hono } from "hono";
10
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
11
11
  import { z } from "zod";
12
12
  import { buildServer, type JwtHelper } from "../../api";
13
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
13
14
  import { createRegistry, defineFeature, type SessionUser } from "../../engine";
14
- import { createTestDb, createTestRedis, type TestDb, type TestRedis, TestUsers } from "../../stack";
15
+ import { createTestRedis, type TestRedis, TestUsers } from "../../stack";
15
16
  import { waitFor } from "../../testing";
17
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
16
18
  import { createJobRunner, type JobRunner } from "../job-runner";
17
19
 
18
20
  const jobExecutions: Array<{ trigger: string; payload: Record<string, unknown> }> = [];
@@ -48,7 +50,7 @@ const orderFeature = defineFeature("multi", (r) => {
48
50
  );
49
51
  });
50
52
 
51
- let testDb: TestDb;
53
+ let testDb: BunTestDb;
52
54
  let testRedis: TestRedis;
53
55
  let app: Hono;
54
56
  let jwt: JwtHelper;
@@ -58,6 +60,7 @@ const adminUser = TestUsers.admin;
58
60
  const JWT_SECRET = "multi-trigger-test-secret-minimum-32-chars!!";
59
61
 
60
62
  beforeAll(async () => {
63
+ await ensureTemporalPolyfill();
61
64
  testDb = await createTestDb();
62
65
  testRedis = await createTestRedis();
63
66
 
@@ -1,4 +1,4 @@
1
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { requestContext } from "../../api/request-context";
3
3
  import { createRegistry, defineFeature } from "../../engine";
4
4
  import type { AppContext, Registry } from "../../engine/types";
@@ -192,7 +192,7 @@ describe("scenario 2: scheduled job", () => {
192
192
 
193
193
  // BullMQ's repeatable scheduler needs a second or two to register its
194
194
  // first tick — a generous delay schedule covers the startup window.
195
- test("cron job fires via BullMQ scheduler", { timeout: 15_000 }, async () => {
195
+ test("cron job fires via BullMQ scheduler", async () => {
196
196
  clearLog();
197
197
  await withRunner(async () => {
198
198
  await waitFor(
@@ -278,7 +278,7 @@ describe("concurrency: skip", () => {
278
278
  });
279
279
 
280
280
  describe("concurrency: sequential", () => {
281
- test("same-name dispatches run strictly one after the other", { timeout: 15_000 }, async () => {
281
+ test("same-name dispatches run strictly one after the other", async () => {
282
282
  clearLog();
283
283
  await withRunner(async (runner) => {
284
284
  // Three rapid dispatches. Parallel mode would land all entries
@@ -318,7 +318,7 @@ describe("concurrency: sequential", () => {
318
318
  });
319
319
  });
320
320
 
321
- test("lock is released even when the handler throws", { timeout: 10_000 }, async () => {
321
+ test("lock is released even when the handler throws", async () => {
322
322
  clearLog();
323
323
  await withRunner(async (runner) => {
324
324
  // First dispatch fails. If the finally-path didn't release the lock,
@@ -355,9 +355,7 @@ describe("concurrency: sequential", () => {
355
355
  });
356
356
  });
357
357
 
358
- test("lock release is value-matched: foreign tokens survive expiration races", {
359
- timeout: 5_000,
360
- }, async () => {
358
+ test("lock release is value-matched: foreign tokens survive expiration races", async () => {
361
359
  // Pin the contract that distributed-lock's release script enforces:
362
360
  // a release call from a worker whose token has already expired and
363
361
  // been claimed by someone else must NOT delete the new owner's lock.
@@ -1,7 +1,7 @@
1
1
  // Full-stack proof for lifecycle ↔ buildServer wiring.
2
2
  // Drives drain() directly — SIGTERM plumbing has its own unit test.
3
3
 
4
- import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
4
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
5
5
  import { defineFeature } from "../../engine";
6
6
  import { setupTestStack, type TestStack } from "../../stack";
7
7
  import { sharedWidgetEntity } from "../../testing";
@@ -1,4 +1,4 @@
1
- import { describe, expect, test, vi } from "vitest";
1
+ import { describe, expect, mock, test } from "bun:test";
2
2
  import { createLifecycle } from "../lifecycle";
3
3
 
4
4
  describe("lifecycle — state machine", () => {
@@ -20,7 +20,7 @@ describe("lifecycle — state machine", () => {
20
20
 
21
21
  test("markReady from 'ready' is a no-op", () => {
22
22
  const lc = createLifecycle({ startReady: true });
23
- const listener = vi.fn();
23
+ const listener = mock();
24
24
  lc.onStateChange(listener);
25
25
  lc.markReady();
26
26
  expect(listener).not.toHaveBeenCalled();
@@ -70,7 +70,7 @@ describe("lifecycle — shutdown hooks", () => {
70
70
  });
71
71
 
72
72
  test("one failing hook does not block the others, and the error is logged", async () => {
73
- const logger = { error: vi.fn() };
73
+ const logger = { error: mock() };
74
74
  const lc = createLifecycle({ startReady: true, logger });
75
75
  const calls: string[] = [];
76
76
  lc.registerShutdownHook("healthy-a", async () => {
@@ -171,7 +171,7 @@ describe("lifecycle — onStateChange", () => {
171
171
 
172
172
  test("unsubscribe stops further callbacks", () => {
173
173
  const lc = createLifecycle();
174
- const cb = vi.fn();
174
+ const cb = mock();
175
175
  const unsubscribe = lc.onStateChange(cb);
176
176
  lc.markReady();
177
177
  expect(cb).toHaveBeenCalledTimes(1);
@@ -184,9 +184,9 @@ describe("lifecycle — onStateChange", () => {
184
184
  });
185
185
 
186
186
  test("broken listener does not break others, and the error is logged", () => {
187
- const logger = { error: vi.fn() };
187
+ const logger = { error: mock() };
188
188
  const lc = createLifecycle({ logger });
189
- const healthy = vi.fn();
189
+ const healthy = mock();
190
190
  lc.onStateChange(() => {
191
191
  throw new Error("subscriber exploded");
192
192
  });
@@ -1,4 +1,4 @@
1
- import { describe, expect, test, vi } from "vitest";
1
+ import { describe, expect, mock, test } from "bun:test";
2
2
  import { createLifecycle } from "../lifecycle";
3
3
  import { attachSignalHandlers } from "../signal-handlers";
4
4
  import { createTestLifecycle } from "./create-test-lifecycle";
@@ -6,7 +6,7 @@ import { createTestLifecycle } from "./create-test-lifecycle";
6
6
  describe("attachSignalHandlers", () => {
7
7
  test("SIGTERM triggers drain and calls exit(0)", async () => {
8
8
  const lc = createLifecycle({ startReady: true });
9
- const exit = vi.fn();
9
+ const exit = mock();
10
10
  const hookCalls: string[] = [];
11
11
  lc.registerShutdownHook("spy", async (signal) => {
12
12
  hookCalls.push(signal);
@@ -25,7 +25,7 @@ describe("attachSignalHandlers", () => {
25
25
 
26
26
  test("SIGINT path drains with the right signal label", async () => {
27
27
  const lc = createLifecycle({ startReady: true });
28
- const exit = vi.fn();
28
+ const exit = mock();
29
29
  const seen: string[] = [];
30
30
  lc.registerShutdownHook("spy", async (signal) => {
31
31
  seen.push(signal);
@@ -44,7 +44,7 @@ describe("attachSignalHandlers", () => {
44
44
 
45
45
  test("multiple SIGTERMs still call exit exactly once", async () => {
46
46
  const lc = createLifecycle({ startReady: true });
47
- const exit = vi.fn();
47
+ const exit = mock();
48
48
  // Slow hook so we can fire additional signals while drain is in-flight.
49
49
  lc.registerShutdownHook("slow", async () => {
50
50
  await new Promise((r) => setTimeout(r, 30));
@@ -74,7 +74,7 @@ describe("attachSignalHandlers", () => {
74
74
  throw new Error("drain itself exploded");
75
75
  },
76
76
  });
77
- const exit = vi.fn();
77
+ const exit = mock();
78
78
  const handle = attachSignalHandlers(brokenLifecycle, { exit, signals: ["SIGTERM"] });
79
79
  try {
80
80
  process.emit("SIGTERM");
@@ -87,7 +87,7 @@ describe("attachSignalHandlers", () => {
87
87
 
88
88
  test("detach() removes the process listeners", () => {
89
89
  const lc = createLifecycle({ startReady: true });
90
- const exit = vi.fn();
90
+ const exit = mock();
91
91
  const before = process.listenerCount("SIGTERM");
92
92
  const handle = attachSignalHandlers(lc, { exit, signals: ["SIGTERM"] });
93
93
  expect(process.listenerCount("SIGTERM")).toBe(before + 1);
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it } from "bun:test";
2
2
  import { createConsoleProvider } from "../../observability";
3
3
  import { mergeTraceFields } from "../pino-logger";
4
4
 
@@ -3,7 +3,7 @@
3
3
  // Projection-Tabelle muss der Detector die richtigen Tabellen-Namen
4
4
  // melden, damit migrate apply den richtigen Rebuild triggert.
5
5
 
6
- import { describe, expect, test } from "vitest";
6
+ import { describe, expect, test } from "bun:test";
7
7
  import { compareSnapshots } from "../projection-detection";
8
8
  import type { Snapshot, SnapshotTable } from "../schema-drift";
9
9
 
@@ -4,18 +4,20 @@
4
4
  // hier blockiert Container-Starts; jeder False-Negative lässt
5
5
  // Schema-Drift unentdeckt durch.
6
6
 
7
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
7
8
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
8
9
  import { tmpdir } from "node:os";
9
10
  import { join } from "node:path";
10
- import { sql } from "drizzle-orm";
11
- import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
12
- import { createTestDb, type TestDb } from "../../stack";
11
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
12
+ import { asRawClient } from "../../db/query";
13
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
13
14
  import { detectDrift } from "../schema-drift";
14
15
 
15
- let testDb: TestDb;
16
+ let testDb: BunTestDb;
16
17
  let migrationsDir: string;
17
18
 
18
19
  beforeAll(async () => {
20
+ await ensureTemporalPolyfill();
19
21
  testDb = await createTestDb();
20
22
  });
21
23
 
@@ -82,8 +84,8 @@ function writeSnapshotSimple(idx: number, tableNames: string[]): void {
82
84
  }
83
85
 
84
86
  async function ensureDrizzleMigrationsTable(): Promise<void> {
85
- await testDb.db.execute(sql`CREATE SCHEMA IF NOT EXISTS drizzle`);
86
- await testDb.db.execute(sql`
87
+ await asRawClient(testDb.db).unsafe(`CREATE SCHEMA IF NOT EXISTS drizzle`);
88
+ await asRawClient(testDb.db).unsafe(`
87
89
  CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (
88
90
  id serial PRIMARY KEY,
89
91
  hash text NOT NULL,
@@ -93,12 +95,13 @@ async function ensureDrizzleMigrationsTable(): Promise<void> {
93
95
  }
94
96
 
95
97
  async function dropDrizzleMigrationsTable(): Promise<void> {
96
- await testDb.db.execute(sql`DROP TABLE IF EXISTS drizzle.__drizzle_migrations`);
98
+ await asRawClient(testDb.db).unsafe(`DROP TABLE IF EXISTS drizzle.__drizzle_migrations`);
97
99
  }
98
100
 
99
101
  async function insertAppliedMigration(hash: string): Promise<void> {
100
- await testDb.db.execute(
101
- sql`INSERT INTO drizzle.__drizzle_migrations (hash, created_at) VALUES (${hash}, ${Date.now()})`,
102
+ await asRawClient(testDb.db).unsafe(
103
+ `INSERT INTO drizzle.__drizzle_migrations (hash, created_at) VALUES ($1, $2)`,
104
+ [hash, Date.now()],
102
105
  );
103
106
  }
104
107
 
@@ -106,8 +109,8 @@ describe("detectDrift", () => {
106
109
  beforeEach(async () => {
107
110
  await dropDrizzleMigrationsTable();
108
111
  // Cleanup test tables that might still exist from earlier runs
109
- await testDb.db.execute(sql`DROP TABLE IF EXISTS drift_test_users`);
110
- await testDb.db.execute(sql`DROP TABLE IF EXISTS drift_test_orders`);
112
+ await asRawClient(testDb.db).unsafe(`DROP TABLE IF EXISTS drift_test_users`);
113
+ await asRawClient(testDb.db).unsafe(`DROP TABLE IF EXISTS drift_test_orders`);
111
114
  });
112
115
 
113
116
  test("frische DB ohne __drizzle_migrations + 1 Migration im Journal → 1 pending + table missing", async () => {
@@ -124,7 +127,7 @@ describe("detectDrift", () => {
124
127
  test("alle Migrations applied + alle Tabellen existieren → ok", async () => {
125
128
  writeJournal([{ idx: 0, tag: "0000_init" }]);
126
129
  writeSnapshotSimple(0, ["drift_test_users"]);
127
- await testDb.db.execute(sql`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
130
+ await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
128
131
  await ensureDrizzleMigrationsTable();
129
132
  await insertAppliedMigration("hash-0000");
130
133
 
@@ -140,8 +143,8 @@ describe("detectDrift", () => {
140
143
  { idx: 1, tag: "0001_add_orders" },
141
144
  ]);
142
145
  writeSnapshotSimple(1, ["drift_test_users", "drift_test_orders"]);
143
- await testDb.db.execute(sql`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
144
- await testDb.db.execute(sql`CREATE TABLE drift_test_orders (id uuid PRIMARY KEY)`);
146
+ await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
147
+ await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_orders (id uuid PRIMARY KEY)`);
145
148
  await ensureDrizzleMigrationsTable();
146
149
  await insertAppliedMigration("hash-0000"); // nur eine applied
147
150
 
@@ -155,7 +158,7 @@ describe("detectDrift", () => {
155
158
  test("alle Migrations applied aber Tabelle fehlt manuell → drift", async () => {
156
159
  writeJournal([{ idx: 0, tag: "0000_init" }]);
157
160
  writeSnapshotSimple(0, ["drift_test_users", "drift_test_orders"]);
158
- await testDb.db.execute(sql`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
161
+ await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
159
162
  // drift_test_orders bewusst NICHT angelegt (simuliert manuellen DROP)
160
163
  await ensureDrizzleMigrationsTable();
161
164
  await insertAppliedMigration("hash-0000");
@@ -179,7 +182,9 @@ describe("detectDrift", () => {
179
182
  },
180
183
  ]);
181
184
  // DB hat email NULLABLE — drift.
182
- await testDb.db.execute(sql`CREATE TABLE drift_test_users (id uuid PRIMARY KEY, email text)`);
185
+ await asRawClient(testDb.db).unsafe(
186
+ `CREATE TABLE drift_test_users (id uuid PRIMARY KEY, email text)`,
187
+ );
183
188
  await ensureDrizzleMigrationsTable();
184
189
  await insertAppliedMigration("hash-0000");
185
190
 
@@ -204,7 +209,7 @@ describe("detectDrift", () => {
204
209
  },
205
210
  ]);
206
211
  // DB hat KEINE email-Spalte.
207
- await testDb.db.execute(sql`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
212
+ await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
208
213
  await ensureDrizzleMigrationsTable();
209
214
  await insertAppliedMigration("hash-0000");
210
215
 
@@ -226,8 +231,8 @@ describe("detectDrift", () => {
226
231
  },
227
232
  ]);
228
233
  // DB hat zusätzliche Spalte (z.B. manueller ALTER TABLE in Prod).
229
- await testDb.db.execute(
230
- sql`CREATE TABLE drift_test_users (id uuid PRIMARY KEY, secret_legacy text)`,
234
+ await asRawClient(testDb.db).unsafe(
235
+ `CREATE TABLE drift_test_users (id uuid PRIMARY KEY, secret_legacy text)`,
231
236
  );
232
237
  await ensureDrizzleMigrationsTable();
233
238
  await insertAppliedMigration("hash-0000");
@@ -251,7 +256,9 @@ describe("detectDrift", () => {
251
256
  },
252
257
  ]);
253
258
  // DB hat age als TEXT statt INTEGER.
254
- await testDb.db.execute(sql`CREATE TABLE drift_test_users (id uuid PRIMARY KEY, age text)`);
259
+ await asRawClient(testDb.db).unsafe(
260
+ `CREATE TABLE drift_test_users (id uuid PRIMARY KEY, age text)`,
261
+ );
255
262
  await ensureDrizzleMigrationsTable();
256
263
  await insertAppliedMigration("hash-0000");
257
264
 
@@ -278,7 +285,7 @@ describe("detectDrift", () => {
278
285
  },
279
286
  },
280
287
  ]);
281
- await testDb.db.execute(sql`
288
+ await asRawClient(testDb.db).unsafe(`
282
289
  CREATE TABLE drift_test_users (
283
290
  id uuid PRIMARY KEY,
284
291
  email text NOT NULL,
@@ -297,10 +304,10 @@ describe("detectDrift", () => {
297
304
  test("public.__drizzle_migrations Fallback (Pre-0.20-Drizzle)", async () => {
298
305
  writeJournal([{ idx: 0, tag: "0000_init" }]);
299
306
  writeSnapshotSimple(0, ["drift_test_users"]);
300
- await testDb.db.execute(sql`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
307
+ await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
301
308
  // Legacy: Tabelle in public-Schema statt drizzle-Schema
302
309
  await dropDrizzleMigrationsTable();
303
- await testDb.db.execute(sql`
310
+ await asRawClient(testDb.db).unsafe(`
304
311
  CREATE TABLE public.__drizzle_migrations (
305
312
  id serial PRIMARY KEY,
306
313
  hash text NOT NULL,
@@ -308,13 +315,14 @@ describe("detectDrift", () => {
308
315
  )
309
316
  `);
310
317
  try {
311
- await testDb.db.execute(
312
- sql`INSERT INTO public.__drizzle_migrations (hash, created_at) VALUES ('hash-0000', ${Date.now()})`,
318
+ await asRawClient(testDb.db).unsafe(
319
+ `INSERT INTO public.__drizzle_migrations (hash, created_at) VALUES ('hash-0000', $1)`,
320
+ [Date.now()],
313
321
  );
314
322
  const report = await detectDrift(testDb.db, migrationsDir);
315
323
  expect(report.ok).toBe(true);
316
324
  } finally {
317
- await testDb.db.execute(sql`DROP TABLE public.__drizzle_migrations`);
325
+ await asRawClient(testDb.db).unsafe(`DROP TABLE public.__drizzle_migrations`);
318
326
  }
319
327
  });
320
328
  });