@cosmicdrift/kumiko-framework 0.14.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 +6 -6
  2. package/src/__tests__/{anonymous-access.integration.ts → anonymous-access.integration.test.ts} +12 -9
  3. package/src/__tests__/{error-contract.integration.ts → error-contract.integration.test.ts} +5 -4
  4. package/src/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +3 -3
  5. package/src/__tests__/{full-stack.integration.ts → full-stack.integration.test.ts} +7 -16
  6. package/src/__tests__/{ownership.integration.ts → ownership.integration.test.ts} +3 -2
  7. package/src/__tests__/{raw-table.integration.ts → raw-table.integration.test.ts} +18 -30
  8. package/src/__tests__/{reference-data.integration.ts → reference-data.integration.test.ts} +24 -11
  9. package/src/__tests__/{transition-guard.integration.ts → transition-guard.integration.test.ts} +12 -10
  10. package/src/api/__tests__/api.test.ts +1 -1
  11. package/src/api/__tests__/auth-middleware-transport.test.ts +1 -1
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +1 -1
  13. package/src/api/__tests__/{batch.integration.ts → batch.integration.test.ts} +30 -30
  14. package/src/api/__tests__/body-limit.test.ts +1 -1
  15. package/src/api/__tests__/csrf-middleware.test.ts +1 -1
  16. package/src/api/__tests__/{dispatcher-live.integration.ts → dispatcher-live.integration.test.ts} +10 -9
  17. package/src/api/__tests__/metrics-endpoint.test.ts +1 -1
  18. package/src/api/__tests__/{nested-write.integration.ts → nested-write.integration.test.ts} +13 -16
  19. package/src/api/__tests__/readiness.test.ts +1 -1
  20. package/src/api/__tests__/request-id-middleware.test.ts +1 -1
  21. package/src/api/__tests__/sse-broker.test.ts +12 -12
  22. package/src/api/__tests__/sse-route.test.ts +1 -1
  23. package/src/api/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 -474
  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
@@ -1,5 +1,5 @@
1
- import { sql } from "drizzle-orm";
2
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { asRawClient } from "../../db/query";
3
3
  import { createBooleanField, createEntity, createTextField } from "../../engine";
4
4
  import { createEventsTable } from "../../event-store";
5
5
  import {
@@ -10,7 +10,7 @@ import {
10
10
  unsafeCreateEntityTable,
11
11
  } from "../../stack";
12
12
  import { createEventStoreExecutor } from "../event-store-executor";
13
- import { buildDrizzleTable } from "../table-builder";
13
+ import { buildEntityTable } from "../table-builder";
14
14
  import { createTenantDb, type TenantDb } from "../tenant-db";
15
15
 
16
16
  const entity = createEntity({
@@ -22,7 +22,7 @@ const entity = createEntity({
22
22
  },
23
23
  softDelete: true,
24
24
  });
25
- const table = buildDrizzleTable("esExecUser", entity);
25
+ const table = buildEntityTable("esExecUser", entity);
26
26
 
27
27
  let testDb: TestDb;
28
28
  let tdb: TenantDb;
@@ -40,7 +40,9 @@ afterAll(async () => {
40
40
  });
41
41
 
42
42
  beforeEach(async () => {
43
- await testDb.db.execute(sql`TRUNCATE kumiko_events, read_es_exec_users RESTART IDENTITY CASCADE`);
43
+ await asRawClient(testDb.db).unsafe(
44
+ `TRUNCATE kumiko_events, read_es_exec_users RESTART IDENTITY CASCADE`,
45
+ );
44
46
  });
45
47
 
46
48
  describe("event-store-executor", () => {
@@ -117,7 +119,7 @@ const sensitiveEntity = createEntity({
117
119
  },
118
120
  softDelete: true,
119
121
  });
120
- const sensitiveTable = buildDrizzleTable("esExecSensitive", sensitiveEntity);
122
+ const sensitiveTable = buildEntityTable("esExecSensitive", sensitiveEntity);
121
123
 
122
124
  describe("event-store-executor — sensitive fields", () => {
123
125
  const crud = createEventStoreExecutor(sensitiveTable, sensitiveEntity, {
@@ -129,8 +131,8 @@ describe("event-store-executor — sensitive fields", () => {
129
131
  });
130
132
 
131
133
  beforeEach(async () => {
132
- await testDb.db.execute(
133
- sql`TRUNCATE kumiko_events, read_es_exec_sensitive RESTART IDENTITY CASCADE`,
134
+ await asRawClient(testDb.db).unsafe(
135
+ `TRUNCATE kumiko_events, read_es_exec_sensitive RESTART IDENTITY CASCADE`,
134
136
  );
135
137
  });
136
138
 
@@ -138,12 +140,17 @@ describe("event-store-executor — sensitive fields", () => {
138
140
  type: string;
139
141
  payload: TPayload;
140
142
  }> {
141
- const rows = await testDb.db.execute<{ type: string; payload: TPayload }>(
142
- sql`SELECT type, payload FROM kumiko_events ORDER BY id DESC LIMIT 1`,
143
- );
143
+ const rows = (await asRawClient(testDb.db).unsafe(
144
+ `SELECT type, payload FROM kumiko_events ORDER BY id DESC LIMIT 1`,
145
+ )) as Array<{ type: string; payload: unknown }>;
144
146
  const row = rows[0];
145
147
  if (!row) throw new Error("no events in store");
146
- return row;
148
+ return {
149
+ type: row["type"],
150
+ payload: (typeof row["payload"] === "string"
151
+ ? JSON.parse(row["payload"])
152
+ : row["payload"]) as TPayload,
153
+ };
147
154
  }
148
155
 
149
156
  test("create event payload excludes sensitive fields but entity row keeps them", async () => {
@@ -15,16 +15,17 @@
15
15
  // 5. Snapshot erneut nehmen
16
16
  // 6. deep-equal: identische Rows in identischer Reihenfolge
17
17
 
18
- import { asc, sql } from "drizzle-orm";
19
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
18
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
19
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
20
20
  import { createBooleanField, createEntity, createTextField, defineFeature } from "../../engine";
21
21
  import { createRegistry } from "../../engine/registry";
22
22
  import { createEventsTable } from "../../event-store";
23
23
  import { rebuildProjection } from "../../pipeline";
24
24
  import { createProjectionStateTable } from "../../pipeline/projection-state";
25
- import { createTestDb, type TestDb, TestUsers, unsafeCreateEntityTable } from "../../stack";
25
+ import { TestUsers, unsafeCreateEntityTable } from "../../stack";
26
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
26
27
  import { createEventStoreExecutor } from "../event-store-executor";
27
- import { buildDrizzleTable } from "../table-builder";
28
+ import { buildEntityTable } from "../table-builder";
28
29
  import { createTenantDb, type TenantDb } from "../tenant-db";
29
30
 
30
31
  const userEntity = createEntity({
@@ -41,13 +42,14 @@ const userFeature = defineFeature("implicittest", (r) => {
41
42
  r.entity("user", userEntity);
42
43
  });
43
44
 
44
- const userTable = buildDrizzleTable("user", userEntity);
45
+ const userTable = buildEntityTable("user", userEntity);
45
46
 
46
- let testDb: TestDb;
47
+ let testDb: BunTestDb;
47
48
  let tdb: TenantDb;
48
49
  const adminUser = TestUsers.admin;
49
50
 
50
51
  beforeAll(async () => {
52
+ await ensureTemporalPolyfill();
51
53
  testDb = await createTestDb();
52
54
  await unsafeCreateEntityTable(testDb.db, userEntity, "user");
53
55
  await createEventsTable(testDb.db);
@@ -60,13 +62,18 @@ afterAll(async () => {
60
62
  });
61
63
 
62
64
  beforeEach(async () => {
63
- await testDb.db.execute(
64
- sql`TRUNCATE kumiko_events, read_implicit_users, kumiko_projections RESTART IDENTITY CASCADE`,
65
+ await asRawClient(testDb.db).unsafe(
66
+ `TRUNCATE kumiko_events, read_implicit_users, kumiko_projections RESTART IDENTITY CASCADE`,
65
67
  );
66
68
  });
67
69
 
68
70
  async function snapshotTable(): Promise<readonly Record<string, unknown>[]> {
69
- const rows = await testDb.db.select().from(userTable).orderBy(asc(userTable["id"]));
71
+ const rows = await selectMany(
72
+ testDb.db,
73
+ userTable,
74
+ {},
75
+ { orderBy: { col: "id", direction: "asc" } },
76
+ );
70
77
  return rows as readonly Record<string, unknown>[];
71
78
  }
72
79
 
@@ -224,7 +231,7 @@ describe("implicit-projection / Live==Rebuild equivalence", () => {
224
231
  // sensitive-Spalte oder verschlüsseltem Event-Payload), bricht der Test
225
232
  // und zwingt zu Aufmerksamkeit.
226
233
 
227
- import { sql as drizzleSql, eq } from "drizzle-orm";
234
+ import { asRawClient, selectMany } from "../../db/query";
228
235
 
229
236
  const sensitiveTable = "read_implicit_sensitive_users";
230
237
 
@@ -240,7 +247,7 @@ const sensitiveFeature = defineFeature("implicitsensitive", (r) => {
240
247
  r.entity("sensitive-user", sensitiveEntity);
241
248
  });
242
249
 
243
- const sensitiveDrizzleTable = buildDrizzleTable("sensitive-user", sensitiveEntity);
250
+ const sensitiveEntityTable = buildEntityTable("sensitive-user", sensitiveEntity);
244
251
 
245
252
  describe("implicit-projection / dokumentierte Sensitive-Drift", () => {
246
253
  beforeAll(async () => {
@@ -248,15 +255,13 @@ describe("implicit-projection / dokumentierte Sensitive-Drift", () => {
248
255
  });
249
256
 
250
257
  beforeEach(async () => {
251
- await testDb.db.execute(
252
- drizzleSql.raw(
253
- `TRUNCATE ${sensitiveTable}, kumiko_events, kumiko_projections RESTART IDENTITY CASCADE`,
254
- ),
258
+ await asRawClient(testDb.db).unsafe(
259
+ `TRUNCATE ${sensitiveTable}, kumiko_events, kumiko_projections RESTART IDENTITY CASCADE`,
255
260
  );
256
261
  });
257
262
 
258
263
  test("Live schreibt sensitive-Felder, Rebuild lässt sie NULL (Welle-3-Roadmap)", async () => {
259
- const crud = createEventStoreExecutor(sensitiveDrizzleTable, sensitiveEntity, {
264
+ const crud = createEventStoreExecutor(sensitiveEntityTable, sensitiveEntity, {
260
265
  entityName: "sensitive-user",
261
266
  });
262
267
 
@@ -269,20 +274,22 @@ describe("implicit-projection / dokumentierte Sensitive-Drift", () => {
269
274
  );
270
275
  if (!created.isSuccess) throw new Error("setup failed");
271
276
 
272
- const [liveRow] = await testDb.db
273
- .select()
274
- .from(sensitiveDrizzleTable)
275
- .where(eq(sensitiveDrizzleTable["id"], created.data.id as string));
277
+ const [liveRow] = await selectMany(testDb.db, sensitiveEntityTable, {
278
+ id: created.data.id as string,
279
+ });
276
280
  expect(liveRow?.["apiKey"]).toBe("secret-token-abc");
277
281
  expect(liveRow?.["email"]).toBe("x@test.de");
278
282
 
279
283
  // 2. Verifiziere dass das Event-Log das Feld NICHT enthält (stripped).
280
- const events = await testDb.db.execute<{ payload: Record<string, unknown> }>(
281
- drizzleSql`SELECT payload FROM kumiko_events WHERE aggregate_id = ${created.data.id}::uuid`,
284
+ const { eventsTable } = await import("../../event-store");
285
+ const [event] = await selectMany(
286
+ testDb.db,
287
+ eventsTable,
288
+ { aggregateId: created.data.id },
289
+ { orderBy: { col: "version", direction: "asc" } },
282
290
  );
283
- expect(events[0]?.payload).toBeDefined();
284
- expect(events[0]?.payload?.["apiKey"]).toBeUndefined();
285
- expect(events[0]?.payload?.["email"]).toBe("x@test.de");
291
+ expect(event?.payload?.["apiKey"]).toBeUndefined();
292
+ expect(event?.payload?.["email"]).toBe("x@test.de");
286
293
 
287
294
  // 3. Rebuild über die ImplicitProjection. Read-Tabelle wird aus
288
295
  // event.payload neu materialisiert — apiKey ist nicht im Log,
@@ -293,10 +300,9 @@ describe("implicit-projection / dokumentierte Sensitive-Drift", () => {
293
300
  registry,
294
301
  });
295
302
 
296
- const [rebuiltRow] = await testDb.db
297
- .select()
298
- .from(sensitiveDrizzleTable)
299
- .where(eq(sensitiveDrizzleTable["id"], created.data.id as string));
303
+ const [rebuiltRow] = await selectMany(testDb.db, sensitiveEntityTable, {
304
+ id: created.data.id as string,
305
+ });
300
306
  expect(rebuiltRow?.["email"]).toBe("x@test.de");
301
307
  // DAS ist die Drift: sensitive Feld ist nach Rebuild weg.
302
308
  expect(rebuiltRow?.["apiKey"]).toBeNull();
@@ -1,7 +1,7 @@
1
1
  // Pure Unit-Tests für die flatten/rehydrate Helpers — Auto-Convert für
2
2
  // locatedTimestamp-Felder. Keine DB, kein Stack, nur Daten-Transform.
3
3
 
4
- import { describe, expect, test } from "vitest";
4
+ import { describe, expect, test } from "bun:test";
5
5
  import { createEntity, createLocatedTimestampField, createTextField } from "../../engine";
6
6
  import type { EntityDefinition } from "../../engine/types";
7
7
  import { flattenLocatedTimestamp, rehydrateLocatedTimestamp } from "../located-timestamp";
@@ -1,6 +1,6 @@
1
1
  // Pure Unit-Tests für money flatten/rehydrate Helpers.
2
2
 
3
- import { describe, expect, test } from "vitest";
3
+ import { describe, expect, test } from "bun:test";
4
4
  import { createEntity, createMoneyField, createTextField } from "../../engine";
5
5
  import type { EntityDefinition } from "../../engine/types";
6
6
  import { flattenMoney, rehydrateMoney } from "../money";
@@ -7,8 +7,9 @@
7
7
  // have a real footgun, or whether the showcase bug had a different root
8
8
  // cause.
9
9
 
10
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
11
- import { buildDrizzleTable } from "../../db/table-builder";
10
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
11
+ import { insertOne, selectMany } from "../../db/query";
12
+ import { buildEntityTable } from "../../db/table-builder";
12
13
  import { createEntity, createTextField } from "../../engine";
13
14
  import { setupTestStack, type TestStack, unsafeCreateEntityTable } from "../../stack";
14
15
 
@@ -19,7 +20,7 @@ const linkEntity = createEntity({
19
20
  rightId: createTextField({ required: true }),
20
21
  },
21
22
  });
22
- const linkTable = buildDrizzleTable("link", linkEntity);
23
+ const linkTable = buildEntityTable("link", linkEntity);
23
24
 
24
25
  let stack: TestStack;
25
26
 
@@ -41,7 +42,7 @@ describe("instant() customType is forgiving with ISO strings", () => {
41
42
  table: "mri_ts",
42
43
  fields: { name: createTextField({ required: true }) },
43
44
  });
44
- const tsTable = buildDrizzleTable("ts-row", tsEntity);
45
+ const tsTable = buildEntityTable("ts-row", tsEntity);
45
46
 
46
47
  test("INSERT accepts an ISO string for an instant column (forgiving path)", async () => {
47
48
  await unsafeCreateEntityTable(stack.db, tsEntity, "ts-row");
@@ -50,12 +51,12 @@ describe("instant() customType is forgiving with ISO strings", () => {
50
51
  // would call .toString() on a string and produce a malformed driver
51
52
  // value that PG rejects.
52
53
  const isoString = "2026-01-15T12:00:00Z";
53
- await stack.db.insert(tsTable).values({
54
+ await insertOne(stack.db, tsTable, {
54
55
  name: "x",
55
56
  tenantId: "00000000-0000-4000-8000-000000000001",
56
57
  insertedAt: isoString as unknown as Temporal.Instant,
57
58
  });
58
- const rows = await stack.db.select().from(tsTable);
59
+ const rows = await selectMany(stack.db, tsTable);
59
60
  expect(rows).toHaveLength(1);
60
61
  expect(rows[0]?.["insertedAt"]).toBeInstanceOf(Temporal.Instant);
61
62
  });
@@ -63,11 +64,17 @@ describe("instant() customType is forgiving with ISO strings", () => {
63
64
 
64
65
  describe("multi-row INSERT", () => {
65
66
  test("two rows with no id supplied → both rows persist (PG gen_random_uuid per row)", async () => {
66
- await stack.db.insert(linkTable).values([
67
- { leftId: "L1", rightId: "R1", tenantId: "00000000-0000-4000-8000-000000000001" },
68
- { leftId: "L2", rightId: "R2", tenantId: "00000000-0000-4000-8000-000000000001" },
69
- ]);
70
- const rows = await stack.db.select().from(linkTable);
67
+ await insertOne(stack.db, linkTable, {
68
+ leftId: "L1",
69
+ rightId: "R1",
70
+ tenantId: "00000000-0000-4000-8000-000000000001",
71
+ });
72
+ await insertOne(stack.db, linkTable, {
73
+ leftId: "L2",
74
+ rightId: "R2",
75
+ tenantId: "00000000-0000-4000-8000-000000000001",
76
+ });
77
+ const rows = await selectMany(stack.db, linkTable);
71
78
  expect(rows).toHaveLength(2);
72
79
  // Each row got its own id from the PG default.
73
80
  const ids = new Set(rows.map((r) => r["id"] as string));
@@ -7,7 +7,7 @@
7
7
  // Wenn parseAutoVerb für ein Domain-Event versehentlich einen Verb
8
8
  // returnt, würde die ImplicitProjection den falschen Handler firen.
9
9
 
10
- import { describe, expect, test } from "vitest";
10
+ import { describe, expect, test } from "bun:test";
11
11
  import type { StoredEvent } from "../../event-store";
12
12
  import { parseAutoVerb } from "../apply-entity-event";
13
13
 
@@ -16,13 +16,15 @@
16
16
  // solchen Migration eine Sanity-Query auf NULL-Counts in den betroffenen
17
17
  // Spalten laufen, oder DB drop'pen wenn der State Demo-State ist.
18
18
 
19
- import { sql } from "drizzle-orm";
20
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
21
- import { createTestDb, type TestDb } from "../../stack";
19
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
20
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
21
+ import { asRawClient } from "../../db/query";
22
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
22
23
 
23
- let testDb: TestDb;
24
+ let testDb: BunTestDb;
24
25
 
25
26
  beforeAll(async () => {
27
+ await ensureTemporalPolyfill();
26
28
  testDb = await createTestDb();
27
29
  });
28
30
 
@@ -31,53 +33,53 @@ afterAll(async () => {
31
33
  });
32
34
 
33
35
  beforeEach(async () => {
34
- await testDb.db.execute(sql`DROP TABLE IF EXISTS migration_safety_test`);
36
+ await asRawClient(testDb.db).unsafe(`DROP TABLE IF EXISTS migration_safety_test`);
35
37
  });
36
38
 
37
39
  describe("ALTER TABLE SET NOT NULL — Daten-Sicherheits-Verhalten", () => {
38
40
  test("SET NOT NULL kracht wenn die Spalte NULL-Zeilen enthält", async () => {
39
- await testDb.db.execute(sql`
41
+ await asRawClient(testDb.db).unsafe(`
40
42
  CREATE TABLE migration_safety_test (
41
43
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
42
44
  key text
43
45
  )
44
46
  `);
45
47
  // NULL-Zeile einschleusen — simuliert prod state vor dem Drift-Fix.
46
- await testDb.db.execute(sql`INSERT INTO migration_safety_test (key) VALUES (NULL)`);
48
+ await asRawClient(testDb.db).unsafe(`INSERT INTO migration_safety_test (key) VALUES (NULL)`);
47
49
 
48
50
  let caught: unknown;
49
51
  try {
50
- await testDb.db.execute(sql`ALTER TABLE migration_safety_test ALTER COLUMN key SET NOT NULL`);
52
+ await asRawClient(testDb.db).unsafe(
53
+ `ALTER TABLE migration_safety_test ALTER COLUMN key SET NOT NULL`,
54
+ );
51
55
  } catch (err) {
52
56
  caught = err;
53
57
  }
54
58
  expect(caught).toBeDefined();
55
- // Drizzle wrapped den PG-Error in einer DrizzleQueryError. Der echte
56
- // not_null_violation steckt in `.cause` als postgres-js Error mit
57
- // `.code === "23502"` und einem deutschsprachigen oder englischen
58
- // `.message`. Wir prüfen pragmatisch beide Pfade.
59
- const cause = (caught as { cause?: unknown }).cause;
60
- const causeCode = (cause as { code?: string } | undefined)?.code;
61
- expect(causeCode).toBe("23502");
59
+ // Postgres-js throws PostgresError directly (no drizzle wrapper anymore).
60
+ const code = (caught as { code?: string } | undefined)?.code;
61
+ expect(code).toBe("23502");
62
62
  });
63
63
 
64
64
  test("SET NOT NULL läuft sauber durch wenn alle Zeilen Werte haben", async () => {
65
- await testDb.db.execute(sql`
65
+ await asRawClient(testDb.db).unsafe(`
66
66
  CREATE TABLE migration_safety_test (
67
67
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
68
68
  key text
69
69
  )
70
70
  `);
71
- await testDb.db.execute(sql`INSERT INTO migration_safety_test (key) VALUES ('foo')`);
72
- await testDb.db.execute(sql`INSERT INTO migration_safety_test (key) VALUES ('bar')`);
71
+ await asRawClient(testDb.db).unsafe(`INSERT INTO migration_safety_test (key) VALUES ('foo')`);
72
+ await asRawClient(testDb.db).unsafe(`INSERT INTO migration_safety_test (key) VALUES ('bar')`);
73
73
 
74
74
  // Sollte ohne Throw durchlaufen.
75
- await testDb.db.execute(sql`ALTER TABLE migration_safety_test ALTER COLUMN key SET NOT NULL`);
75
+ await asRawClient(testDb.db).unsafe(
76
+ `ALTER TABLE migration_safety_test ALTER COLUMN key SET NOT NULL`,
77
+ );
76
78
 
77
79
  // Verifizieren: zukünftige NULL-Inserts werden jetzt blockiert.
78
80
  let caught: unknown;
79
81
  try {
80
- await testDb.db.execute(sql`INSERT INTO migration_safety_test (key) VALUES (NULL)`);
82
+ await asRawClient(testDb.db).unsafe(`INSERT INTO migration_safety_test (key) VALUES (NULL)`);
81
83
  } catch (err) {
82
84
  caught = err;
83
85
  }
@@ -88,17 +90,19 @@ describe("ALTER TABLE SET NOT NULL — Daten-Sicherheits-Verhalten", () => {
88
90
  // Frisch erstellt, keine Zeilen — der Fall in dem `migrate apply` nach
89
91
  // einem DB-drop läuft. Dieser Pfad muss IMMER grün sein, sonst wäre
90
92
  // jeder Greenfield-Deploy kaputt.
91
- await testDb.db.execute(sql`
93
+ await asRawClient(testDb.db).unsafe(`
92
94
  CREATE TABLE migration_safety_test (
93
95
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
94
96
  key text
95
97
  )
96
98
  `);
97
- await testDb.db.execute(sql`ALTER TABLE migration_safety_test ALTER COLUMN key SET NOT NULL`);
99
+ await asRawClient(testDb.db).unsafe(
100
+ `ALTER TABLE migration_safety_test ALTER COLUMN key SET NOT NULL`,
101
+ );
98
102
 
99
103
  // Beweis: information_schema zeigt die Spalte jetzt als NOT NULL.
100
- const rows = await testDb.db.execute<{ is_nullable: string }>(
101
- sql`SELECT is_nullable FROM information_schema.columns WHERE table_name = 'migration_safety_test' AND column_name = 'key'`,
104
+ const rows = await asRawClient(testDb.db).unsafe<{ is_nullable: string }>(
105
+ `SELECT is_nullable FROM information_schema.columns WHERE table_name = 'migration_safety_test' AND column_name = 'key'`,
102
106
  );
103
107
  expect(rows[0]?.is_nullable).toBe("NO");
104
108
  });
@@ -1,5 +1,6 @@
1
- import { sql } from "drizzle-orm";
2
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
3
+ import { asRawClient, insertOne, selectMany } from "../../db/query";
3
4
  import {
4
5
  createBooleanField,
5
6
  createDateField,
@@ -9,8 +10,9 @@ import {
9
10
  defineFeature,
10
11
  } from "../../engine";
11
12
  import type { FeatureDefinition } from "../../engine/types";
12
- import { createTestDb, type TestDb, unsafePushTables } from "../../stack";
13
- import { buildDrizzleTable } from "../table-builder";
13
+ import { unsafePushTables } from "../../stack";
14
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
15
+ import { buildEntityTable } from "../table-builder";
14
16
 
15
17
  /**
16
18
  * Integration tests for the schema migration workflow.
@@ -18,14 +20,15 @@ import { buildDrizzleTable } from "../table-builder";
18
20
  *
19
21
  * Each test simulates:
20
22
  * 1. Developer defines/changes entities
21
- * 2. buildDrizzleTable creates Drizzle table objects
23
+ * 2. buildEntityTable creates Drizzle table objects
22
24
  * 3. Schema is applied to a real database via unsafePushTables (drizzle-kit push)
23
25
  * 4. We verify the DB state matches expectations
24
26
  */
25
27
 
26
- let testDb: TestDb;
28
+ let testDb: BunTestDb;
27
29
 
28
30
  beforeAll(async () => {
31
+ await ensureTemporalPolyfill();
29
32
  testDb = await createTestDb();
30
33
  });
31
34
 
@@ -38,7 +41,7 @@ async function applySchema(features: readonly FeatureDefinition[]): Promise<void
38
41
  const tables: Record<string, unknown> = {};
39
42
  for (const feature of features) {
40
43
  for (const [entityName, entity] of Object.entries(feature.entities)) {
41
- tables[entityName] = buildDrizzleTable(entityName, entity);
44
+ tables[entityName] = buildEntityTable(entityName, entity);
42
45
  }
43
46
  }
44
47
  await unsafePushTables(testDb.db, tables);
@@ -48,12 +51,13 @@ async function applySchema(features: readonly FeatureDefinition[]): Promise<void
48
51
  async function getTableColumns(
49
52
  tableName: string,
50
53
  ): Promise<Map<string, { dataType: string; isNullable: boolean }>> {
51
- const rows = await testDb.db.execute<{
54
+ const rows = await asRawClient(testDb.db).unsafe<{
52
55
  column_name: string;
53
56
  data_type: string;
54
57
  is_nullable: string;
55
58
  }>(
56
- sql`SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = ${tableName} ORDER BY ordinal_position`,
59
+ `SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = $1 ORDER BY ordinal_position`,
60
+ [tableName],
57
61
  );
58
62
 
59
63
  const result = new Map<string, { dataType: string; isNullable: boolean }>();
@@ -121,10 +125,8 @@ describe("schema migration workflows", () => {
121
125
  });
122
126
  await applySchema([feature]);
123
127
 
124
- const indexRows = await testDb.db.execute<{ indexname: string; indexdef: string }>(
125
- sql.raw(
126
- `SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'wf1b_articles' AND indexname = 'wf1b_articles_tenant_id_idx'`,
127
- ),
128
+ const indexRows = await asRawClient(testDb.db).unsafe<{ indexname: string; indexdef: string }>(
129
+ `SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'wf1b_articles' AND indexname = 'wf1b_articles_tenant_id_idx'`,
128
130
  );
129
131
  expect(indexRows.length).toBe(1);
130
132
  expect(indexRows[0]?.indexdef).toContain("tenant_id");
@@ -136,7 +138,7 @@ describe("schema migration workflows", () => {
136
138
  table: "wf2_users",
137
139
  fields: { email: createTextField() },
138
140
  });
139
- await unsafePushTables(testDb.db, { user: buildDrizzleTable("user", initialEntity) });
141
+ await unsafePushTables(testDb.db, { user: buildEntityTable("user", initialEntity) });
140
142
 
141
143
  // Developer adds a new field
142
144
  const updatedEntity = createEntity({
@@ -150,8 +152,8 @@ describe("schema migration workflows", () => {
150
152
  // Push updated schema — drizzle-kit generates ALTER TABLE ADD COLUMN
151
153
  await unsafePushTables(
152
154
  testDb.db,
153
- { user: buildDrizzleTable("user", updatedEntity) },
154
- { user: buildDrizzleTable("user", initialEntity) },
155
+ { user: buildEntityTable("user", updatedEntity) },
156
+ { user: buildEntityTable("user", initialEntity) },
155
157
  );
156
158
 
157
159
  const columns = await getTableColumns("wf2_users");
@@ -166,24 +168,25 @@ describe("schema migration workflows", () => {
166
168
  table: "wf3_projects",
167
169
  fields: { name: createTextField() },
168
170
  });
169
- const initialTable = buildDrizzleTable("project", initialEntity);
171
+ const initialTable = buildEntityTable("project", initialEntity);
170
172
  await unsafePushTables(testDb.db, { project: initialTable });
171
173
 
172
174
  // Insert a row first (to prove ADD COLUMN with default doesn't break existing rows)
173
- await testDb.db
174
- .insert(initialTable)
175
- .values({ tenantId: "00000000-0000-4000-8000-000000000001", name: "Test Project" });
175
+ await insertOne(testDb.db, initialTable, {
176
+ tenantId: "00000000-0000-4000-8000-000000000001",
177
+ name: "Test Project",
178
+ });
176
179
 
177
180
  // Developer adds boolean field with default
178
181
  const updatedEntity = createEntity({
179
182
  table: "wf3_projects",
180
183
  fields: { name: createTextField(), isArchived: createBooleanField({ default: false }) },
181
184
  });
182
- const updatedTable = buildDrizzleTable("project", updatedEntity);
185
+ const updatedTable = buildEntityTable("project", updatedEntity);
183
186
  await unsafePushTables(testDb.db, { project: updatedTable }, { project: initialTable });
184
187
 
185
188
  // Existing row should have the default value
186
- const rows = await testDb.db.select().from(updatedTable);
189
+ const rows = await selectMany(testDb.db, updatedTable);
187
190
 
188
191
  expect(rows[0]).toMatchObject({ name: "Test Project", isArchived: false });
189
192
  });
@@ -199,12 +202,13 @@ describe("schema migration workflows", () => {
199
202
  table: "wf3b_users",
200
203
  fields: { email: createTextField({ required: true }) },
201
204
  });
202
- const initialTable = buildDrizzleTable("user", initialEntity);
205
+ const initialTable = buildEntityTable("user", initialEntity);
203
206
  await unsafePushTables(testDb.db, { user: initialTable });
204
207
 
205
- await testDb.db
206
- .insert(initialTable)
207
- .values({ tenantId: "00000000-0000-4000-8000-000000000001", email: "x@y.z" });
208
+ await insertOne(testDb.db, initialTable, {
209
+ tenantId: "00000000-0000-4000-8000-000000000001",
210
+ email: "x@y.z",
211
+ });
208
212
 
209
213
  const updatedEntity = createEntity({
210
214
  table: "wf3b_users",
@@ -213,10 +217,10 @@ describe("schema migration workflows", () => {
213
217
  roles: createTextField({ required: true, default: "[]" }),
214
218
  },
215
219
  });
216
- const updatedTable = buildDrizzleTable("user", updatedEntity);
220
+ const updatedTable = buildEntityTable("user", updatedEntity);
217
221
  await unsafePushTables(testDb.db, { user: updatedTable }, { user: initialTable });
218
222
 
219
- const rows = await testDb.db.select().from(updatedTable);
223
+ const rows = await selectMany(testDb.db, updatedTable);
220
224
  expect(rows[0]).toMatchObject({ email: "x@y.z", roles: "[]" });
221
225
  });
222
226
 
@@ -0,0 +1,56 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { formatReport, isRawSqlAllowed, joinPath, scanRepo } from "../sql-inventory";
3
+
4
+ const cleanups: string[] = [];
5
+
6
+ afterEach(async () => {
7
+ for (const dir of cleanups) {
8
+ await Bun.spawn(["rm", "-rf", dir]).exited;
9
+ }
10
+ cleanups.length = 0;
11
+ });
12
+
13
+ async function tempRepo(files: Record<string, string>): Promise<string> {
14
+ const root = joinPath(import.meta.dir, `.tmp-sql-inv-${crypto.randomUUID()}`);
15
+ cleanups.push(root);
16
+ await Promise.all(
17
+ Object.entries(files).map(([rel, content]) => Bun.write(joinPath(root, rel), content)),
18
+ );
19
+ return root;
20
+ }
21
+
22
+ describe("sql-inventory", () => {
23
+ test("isRawSqlAllowed permits db/queries and bun-db/query", () => {
24
+ expect(isRawSqlAllowed("/repo/packages/framework/src/db/queries/event-store.ts")).toBe(true);
25
+ expect(isRawSqlAllowed("/repo/packages/framework/src/bun-db/query.ts")).toBe(true);
26
+ expect(
27
+ isRawSqlAllowed("/repo/packages/bundled-features/src/sessions/db/queries/cleanup.ts"),
28
+ ).toBe(true);
29
+ expect(isRawSqlAllowed("/repo/samples/apps/marketing-demo/src/db/queries/seed-counts.ts")).toBe(
30
+ true,
31
+ );
32
+ expect(isRawSqlAllowed("/repo/bin/commands/schema.ts")).toBe(true);
33
+ expect(isRawSqlAllowed("/repo/scripts/codemod-drizzle-chain-to-bun-db.ts")).toBe(true);
34
+ expect(
35
+ isRawSqlAllowed("/repo/packages/bundled-features/src/sessions/handlers/cleanup.job.ts"),
36
+ ).toBe(false);
37
+ });
38
+
39
+ test("scanRepo classifies production vs test hits", async () => {
40
+ const root = await tempRepo({
41
+ "packages/framework/src/db/queries/demo.ts": `export async function x(db: unknown) {
42
+ return asRawClient(db).unsafe("SELECT 1");
43
+ }`,
44
+ "packages/framework/src/handlers/bad.ts": `export async function y(db: unknown) {
45
+ return asRawClient(db).unsafe("DELETE FROM read_users");
46
+ }`,
47
+ "packages/framework/src/__tests__/ok.integration.ts": `await asRawClient(db).unsafe("DELETE FROM read_users");`,
48
+ });
49
+
50
+ const report = await scanRepo(root);
51
+ expect(report.summary.byBucket.allowed).toBeGreaterThanOrEqual(1);
52
+ expect(report.summary.byBucket.tests).toBeGreaterThanOrEqual(1);
53
+ expect(report.summary.byBucket.disallowed).toBeGreaterThanOrEqual(1);
54
+ expect(formatReport(report)).toContain("sql inventory");
55
+ });
56
+ });