@cosmicdrift/kumiko-framework 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/package.json +6 -6
  2. package/src/__tests__/{anonymous-access.integration.ts → anonymous-access.integration.test.ts} +12 -9
  3. package/src/__tests__/{error-contract.integration.ts → error-contract.integration.test.ts} +5 -4
  4. package/src/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +3 -3
  5. package/src/__tests__/{full-stack.integration.ts → full-stack.integration.test.ts} +7 -16
  6. package/src/__tests__/{ownership.integration.ts → ownership.integration.test.ts} +3 -2
  7. package/src/__tests__/{raw-table.integration.ts → raw-table.integration.test.ts} +18 -30
  8. package/src/__tests__/{reference-data.integration.ts → reference-data.integration.test.ts} +24 -11
  9. package/src/__tests__/{transition-guard.integration.ts → transition-guard.integration.test.ts} +12 -10
  10. package/src/api/__tests__/api.test.ts +1 -1
  11. package/src/api/__tests__/auth-middleware-transport.test.ts +1 -1
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +1 -1
  13. package/src/api/__tests__/{batch.integration.ts → batch.integration.test.ts} +30 -30
  14. package/src/api/__tests__/body-limit.test.ts +1 -1
  15. package/src/api/__tests__/csrf-middleware.test.ts +1 -1
  16. package/src/api/__tests__/{dispatcher-live.integration.ts → dispatcher-live.integration.test.ts} +10 -9
  17. package/src/api/__tests__/metrics-endpoint.test.ts +1 -1
  18. package/src/api/__tests__/{nested-write.integration.ts → nested-write.integration.test.ts} +13 -16
  19. package/src/api/__tests__/readiness.test.ts +1 -1
  20. package/src/api/__tests__/request-id-middleware.test.ts +1 -1
  21. package/src/api/__tests__/sse-broker.test.ts +12 -12
  22. package/src/api/__tests__/sse-route.test.ts +1 -1
  23. package/src/api/auth-routes.ts +2 -5
  24. package/src/api/readiness.ts +2 -2
  25. package/src/auth/__tests__/roles.test.ts +2 -2
  26. package/src/bun-db/__tests__/PATTERN.md +73 -0
  27. package/src/bun-db/__tests__/_helpers.ts +103 -0
  28. package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
  29. package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
  30. package/src/bun-db/__tests__/bun-test-db.ts +19 -0
  31. package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
  32. package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
  33. package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
  34. package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
  35. package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
  36. package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
  37. package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
  38. package/src/bun-db/connection.ts +84 -0
  39. package/src/bun-db/index.ts +31 -0
  40. package/src/bun-db/query.ts +842 -0
  41. package/src/compliance/__tests__/duration-spec.test.ts +1 -1
  42. package/src/compliance/__tests__/profiles.test.ts +1 -1
  43. package/src/compliance/__tests__/sub-processors.test.ts +1 -1
  44. package/src/compliance/profiles.ts +1 -4
  45. package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
  46. package/src/db/__tests__/big-int-field.test.ts +15 -14
  47. package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
  48. package/src/db/__tests__/compound-types.test.ts +1 -1
  49. package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
  50. package/src/db/__tests__/connection-options.test.ts +1 -1
  51. package/src/db/__tests__/cursor.test.ts +8 -32
  52. package/src/db/__tests__/dialect-instant.test.ts +1 -1
  53. package/src/db/__tests__/encryption.test.ts +1 -1
  54. package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
  55. package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
  56. package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
  57. package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
  58. package/src/db/__tests__/located-timestamp.test.ts +1 -1
  59. package/src/db/__tests__/migrate-generator.test.ts +71 -0
  60. package/src/db/__tests__/migrate-runner.test.ts +19 -0
  61. package/src/db/__tests__/money.test.ts +1 -1
  62. package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
  63. package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
  64. package/src/db/__tests__/pg-error.test.ts +43 -0
  65. package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
  66. package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
  67. package/src/db/__tests__/sql-inventory.test.ts +56 -0
  68. package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
  69. package/src/db/__tests__/table-builder-required.test.ts +20 -22
  70. package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
  71. package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
  72. package/src/db/api.ts +46 -0
  73. package/src/db/apply-entity-event.ts +45 -36
  74. package/src/db/assert-exists-in.ts +5 -16
  75. package/src/db/bun-provider.ts +37 -0
  76. package/src/db/config-seed.ts +4 -4
  77. package/src/db/connection.ts +14 -57
  78. package/src/db/cursor.ts +5 -56
  79. package/src/db/dialect.ts +472 -99
  80. package/src/db/eagerload.ts +5 -12
  81. package/src/db/entity-table-meta.ts +390 -0
  82. package/src/db/event-store-executor.ts +158 -100
  83. package/src/db/index.ts +33 -5
  84. package/src/db/migrate-generator.ts +350 -0
  85. package/src/db/migrate-runner.ts +206 -0
  86. package/src/db/postgres-provider.ts +25 -0
  87. package/src/db/queries/entity-read.ts +15 -0
  88. package/src/db/queries/es-ops.ts +17 -0
  89. package/src/db/queries/event-consumer.ts +170 -0
  90. package/src/db/queries/event-store-admin.ts +127 -0
  91. package/src/db/queries/event-store.ts +155 -0
  92. package/src/db/queries/projection-rebuild.ts +59 -0
  93. package/src/db/queries/raw-sql.ts +15 -0
  94. package/src/db/queries/schema-drift.ts +35 -0
  95. package/src/db/queries/seed-context.ts +58 -0
  96. package/src/db/queries/table-ops.ts +11 -0
  97. package/src/db/queries/test-stack.ts +56 -0
  98. package/src/db/query-api.ts +22 -0
  99. package/src/db/query.ts +30 -0
  100. package/src/db/reference-data.ts +19 -22
  101. package/src/db/render-ddl.ts +57 -0
  102. package/src/db/row-helpers.ts +3 -52
  103. package/src/db/schema-inspection.ts +17 -4
  104. package/src/db/sql-inventory.ts +208 -0
  105. package/src/db/table-builder.ts +54 -46
  106. package/src/db/tenant-db.ts +105 -326
  107. package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
  108. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
  109. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
  110. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
  111. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
  112. package/src/engine/__tests__/boot-validator.test.ts +4 -3
  113. package/src/engine/__tests__/build-app-schema.test.ts +1 -1
  114. package/src/engine/__tests__/build-target.test.ts +1 -1
  115. package/src/engine/__tests__/claim-keys.test.ts +1 -1
  116. package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
  117. package/src/engine/__tests__/config-helpers.test.ts +1 -1
  118. package/src/engine/__tests__/duration-utils.test.ts +16 -0
  119. package/src/engine/__tests__/effective-features.test.ts +1 -1
  120. package/src/engine/__tests__/engine.test.ts +1 -1
  121. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  122. package/src/engine/__tests__/event-helpers.test.ts +3 -3
  123. package/src/engine/__tests__/extends-registrar.test.ts +4 -4
  124. package/src/engine/__tests__/factories-long-text.test.ts +1 -1
  125. package/src/engine/__tests__/factories-time.test.ts +1 -1
  126. package/src/engine/__tests__/field-access.test.ts +38 -0
  127. package/src/engine/__tests__/field-predicates.test.ts +1 -1
  128. package/src/engine/__tests__/hook-phases.test.ts +1 -1
  129. package/src/engine/__tests__/identifiers.test.ts +1 -1
  130. package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
  131. package/src/engine/__tests__/nav.test.ts +1 -1
  132. package/src/engine/__tests__/no-return-guard.test.ts +17 -0
  133. package/src/engine/__tests__/ownership.test.ts +10 -11
  134. package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
  135. package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
  136. package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
  137. package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
  138. package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
  139. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
  140. package/src/engine/__tests__/post-query-hook.test.ts +1 -1
  141. package/src/engine/__tests__/projection-helpers.test.ts +25 -17
  142. package/src/engine/__tests__/projection.test.ts +4 -4
  143. package/src/engine/__tests__/qualified-name.test.ts +1 -1
  144. package/src/engine/__tests__/raw-table.test.ts +9 -8
  145. package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
  146. package/src/engine/__tests__/run-in.test.ts +1 -1
  147. package/src/engine/__tests__/schema-builder.test.ts +1 -1
  148. package/src/engine/__tests__/screen.test.ts +1 -1
  149. package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
  150. package/src/engine/__tests__/state-machine.test.ts +1 -1
  151. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
  152. package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
  153. package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
  154. package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
  155. package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
  156. package/src/engine/__tests__/steps-read.test.ts +34 -40
  157. package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
  158. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
  159. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
  160. package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
  161. package/src/engine/__tests__/steps-workflow.test.ts +7 -7
  162. package/src/engine/__tests__/system-user.test.ts +1 -1
  163. package/src/engine/__tests__/unmanaged-table.test.ts +98 -0
  164. package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
  165. package/src/engine/__tests__/validation-hooks.test.ts +1 -1
  166. package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
  167. package/src/engine/boot-validator/entity-handler.ts +3 -3
  168. package/src/engine/boot-validator/ownership.ts +1 -1
  169. package/src/engine/define-feature.ts +37 -2
  170. package/src/engine/entity-handlers.ts +5 -5
  171. package/src/engine/factories.ts +1 -1
  172. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
  173. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
  174. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
  175. package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
  176. package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
  177. package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
  178. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
  179. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
  180. package/src/engine/feature-ast/extractors/shared.ts +2 -3
  181. package/src/engine/ownership.ts +113 -41
  182. package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
  183. package/src/engine/projection-helpers.ts +2 -11
  184. package/src/engine/registry.ts +21 -2
  185. package/src/engine/steps/read-find-many.ts +13 -13
  186. package/src/engine/steps/read-find-one.ts +7 -9
  187. package/src/engine/steps/unsafe-projection-delete.ts +4 -5
  188. package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
  189. package/src/engine/types/feature.ts +47 -2
  190. package/src/engine/types/fields.ts +4 -5
  191. package/src/engine/types/index.ts +2 -0
  192. package/src/engine/types/step.ts +10 -10
  193. package/src/engine/validate-projection-allowlist.ts +23 -3
  194. package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
  195. package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
  196. package/src/env/__tests__/compose-env-schema.test.ts +1 -1
  197. package/src/env/__tests__/dry-run.test.ts +1 -1
  198. package/src/errors/__tests__/classes.test.ts +1 -1
  199. package/src/errors/__tests__/error-helpers.test.ts +44 -0
  200. package/src/errors/__tests__/field-issue-compat.test.ts +16 -0
  201. package/src/errors/__tests__/write-failures.test.ts +1 -1
  202. package/src/errors/classes.ts +5 -19
  203. package/src/errors/field-issue.ts +11 -0
  204. package/src/errors/index.ts +1 -0
  205. package/src/errors/zod-bridge.ts +3 -2
  206. package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
  207. package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
  208. package/src/es-ops/__tests__/runner.test.ts +29 -19
  209. package/src/es-ops/context.ts +11 -56
  210. package/src/es-ops/operations-schema.ts +2 -2
  211. package/src/es-ops/runner.ts +12 -26
  212. package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
  213. package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
  214. package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
  215. package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
  216. package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
  217. package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
  218. package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
  219. package/src/event-store/admin-api.ts +55 -83
  220. package/src/event-store/archive.ts +15 -39
  221. package/src/event-store/event-store.ts +92 -86
  222. package/src/event-store/events-schema.ts +2 -1
  223. package/src/event-store/index.ts +1 -0
  224. package/src/event-store/snapshot.ts +26 -24
  225. package/src/event-store/upcaster-dead-letter.ts +19 -18
  226. package/src/files/__tests__/content-disposition.test.ts +1 -1
  227. package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
  228. package/src/files/__tests__/file-handle.test.ts +1 -1
  229. package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
  230. package/src/files/__tests__/read-stream.test.ts +1 -1
  231. package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
  232. package/src/files/__tests__/write-stream.test.ts +1 -1
  233. package/src/files/__tests__/zip-stream.test.ts +1 -1
  234. package/src/files/file-ref-table.ts +2 -2
  235. package/src/files/file-routes.ts +7 -9
  236. package/src/files/storage-tracking.ts +9 -17
  237. package/src/i18n/__tests__/i18n.test.ts +1 -1
  238. package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
  239. package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
  240. package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
  241. package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
  242. package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
  243. package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
  244. package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
  245. package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
  246. package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
  247. package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
  248. package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
  249. package/src/migrations/projection-detection.ts +12 -1
  250. package/src/migrations/schema-drift.ts +7 -23
  251. package/src/observability/__tests__/console-provider.test.ts +1 -1
  252. package/src/observability/__tests__/metric-validator.test.ts +1 -1
  253. package/src/observability/__tests__/noop-provider.test.ts +1 -1
  254. package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
  255. package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
  256. package/src/observability/__tests__/recording-meter.test.ts +1 -1
  257. package/src/observability/__tests__/recording-tracer.test.ts +1 -1
  258. package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
  259. package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
  260. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
  261. package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
  262. package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
  263. package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
  264. package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
  265. package/src/pipeline/__tests__/dispatcher-utils.test.ts +107 -0
  266. package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
  267. package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
  268. package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
  269. package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
  270. package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
  271. package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
  272. package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
  273. package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
  274. package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
  275. package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
  276. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
  277. package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
  278. package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
  279. package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
  280. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
  281. package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
  282. package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
  283. package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
  284. package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
  285. package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
  286. package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
  287. package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
  288. package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
  289. package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
  290. package/src/pipeline/__tests__/redis-keys.test.ts +12 -0
  291. package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
  292. package/src/pipeline/cascade-handler.ts +13 -21
  293. package/src/pipeline/dispatcher-utils.ts +8 -7
  294. package/src/pipeline/dispatcher.ts +43 -48
  295. package/src/pipeline/event-consumer-state.ts +11 -2
  296. package/src/pipeline/event-dispatcher.ts +86 -146
  297. package/src/pipeline/event-retention.ts +14 -24
  298. package/src/pipeline/msp-rebuild.ts +54 -78
  299. package/src/pipeline/projection-rebuild.ts +65 -67
  300. package/src/pipeline/projection-state.ts +2 -2
  301. package/src/random/__tests__/generate.test.ts +13 -13
  302. package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
  303. package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
  304. package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
  305. package/src/redis/__tests__/redis-options.test.ts +1 -1
  306. package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
  307. package/src/search/__tests__/search-adapter.test.ts +1 -1
  308. package/src/secrets/__tests__/dek-cache.test.ts +1 -3
  309. package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
  310. package/src/secrets/__tests__/envelope.test.ts +1 -1
  311. package/src/secrets/__tests__/leak-guard.test.ts +1 -1
  312. package/src/secrets/__tests__/rotation.test.ts +1 -1
  313. package/src/stack/db.ts +25 -48
  314. package/src/stack/push-entity-projection-tables.ts +2 -4
  315. package/src/stack/table-helpers.ts +98 -61
  316. package/src/stack/test-stack.ts +10 -9
  317. package/src/testing/__tests__/db-cleanup.test.ts +40 -0
  318. package/src/testing/__tests__/e2e-generator.test.ts +1 -1
  319. package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
  320. package/src/testing/db-cleanup.ts +44 -0
  321. package/src/testing/expect-error.ts +1 -1
  322. package/src/testing/index.ts +2 -0
  323. package/src/testing/multipart-helper.ts +94 -0
  324. package/src/testing/shared-entities.ts +5 -5
  325. package/src/time/__tests__/polyfill.test.ts +1 -1
  326. package/src/time/__tests__/tz-context.test.ts +1 -1
  327. package/src/utils/__tests__/assert.test.ts +1 -1
  328. package/src/utils/__tests__/case.test.ts +16 -0
  329. package/src/utils/__tests__/env-parse.test.ts +1 -1
  330. package/src/utils/__tests__/is-plain-object.test.ts +16 -0
  331. package/src/utils/__tests__/parse-string-array-json.test.ts +16 -0
  332. package/src/utils/__tests__/safe-json.test.ts +22 -0
  333. package/src/utils/case.ts +6 -0
  334. package/src/utils/index.ts +3 -0
  335. package/src/utils/is-plain-object.ts +4 -0
  336. package/src/utils/parse-string-array-json.ts +14 -0
  337. package/CHANGELOG.md +0 -474
  338. package/src/db/__tests__/db-helpers.test.ts +0 -369
  339. package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
  340. package/src/db/__tests__/row-helpers.test.ts +0 -59
  341. package/src/engine/steps/_drizzle-boundary.ts +0 -19
  342. package/src/files/__tests__/file-field-column.integration.ts +0 -103
@@ -4,13 +4,15 @@
4
4
  // items-create.integration im Showcase abgedeckt — nicht ausreichend
5
5
  // für Framework-Code der von jeder App genutzt wird.
6
6
 
7
- import { sql } from "drizzle-orm";
8
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
7
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
8
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
9
+ import { asRawClient } from "../../db/query";
9
10
  import { createEntity, createNumberField, createTextField } from "../../engine";
10
11
  import { createEventsTable } from "../../event-store";
11
- import { createTestDb, type TestDb, TestUsers, unsafeCreateEntityTable } from "../../stack";
12
+ import { TestUsers, unsafeCreateEntityTable } from "../../stack";
13
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
12
14
  import { createEventStoreExecutor } from "../event-store-executor";
13
- import { buildDrizzleTable } from "../table-builder";
15
+ import { buildEntityTable } from "../table-builder";
14
16
  import { createTenantDb, type TenantDb } from "../tenant-db";
15
17
 
16
18
  const entity = createEntity({
@@ -20,13 +22,14 @@ const entity = createEntity({
20
22
  rank: createNumberField({ sortable: true }),
21
23
  },
22
24
  });
23
- const table = buildDrizzleTable("pagerItem", entity);
25
+ const table = buildEntityTable("pagerItem", entity);
24
26
 
25
- let testDb: TestDb;
27
+ let testDb: BunTestDb;
26
28
  let tdb: TenantDb;
27
29
  const admin = TestUsers.admin;
28
30
 
29
31
  beforeAll(async () => {
32
+ await ensureTemporalPolyfill();
30
33
  testDb = await createTestDb();
31
34
  await unsafeCreateEntityTable(testDb.db, entity, "pagerItem");
32
35
  await createEventsTable(testDb.db);
@@ -38,7 +41,9 @@ afterAll(async () => {
38
41
  });
39
42
 
40
43
  beforeEach(async () => {
41
- await testDb.db.execute(sql`TRUNCATE kumiko_events, read_pager_items RESTART IDENTITY CASCADE`);
44
+ await asRawClient(testDb.db).unsafe(
45
+ `TRUNCATE kumiko_events, read_pager_items RESTART IDENTITY CASCADE`,
46
+ );
42
47
  });
43
48
 
44
49
  describe("event-store-executor.list — offset + totalCount (Tier 2.6d)", () => {
@@ -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";
@@ -0,0 +1,71 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { EntityTableMeta } from "../entity-table-meta";
3
+ import {
4
+ diffSnapshots,
5
+ generateMigration,
6
+ renderMigrationSql,
7
+ snapshotFromMetas,
8
+ } from "../migrate-generator";
9
+
10
+ function meta(
11
+ tableName: string,
12
+ extraColumn?: EntityTableMeta["columns"][number],
13
+ ): EntityTableMeta {
14
+ return {
15
+ tableName,
16
+ source: "unmanaged",
17
+ indexes: [],
18
+ columns: [
19
+ { name: "id", pgType: "uuid", notNull: true, primaryKey: true },
20
+ ...(extraColumn ? [extraColumn] : []),
21
+ ],
22
+ };
23
+ }
24
+
25
+ describe("snapshotFromMetas", () => {
26
+ test("sorts tables by name for stable snapshots", () => {
27
+ const snap = snapshotFromMetas([meta("zebras"), meta("apples")]);
28
+ expect(snap.tables.map((t) => t.tableName)).toEqual(["apples", "zebras"]);
29
+ expect(snap.version).toBe(1);
30
+ });
31
+ });
32
+
33
+ describe("diffSnapshots", () => {
34
+ test("null prev → all tables are new", () => {
35
+ const next = snapshotFromMetas([meta("tasks")]);
36
+ const diff = diffSnapshots(null, next);
37
+ expect(diff.newTables.map((t) => t.tableName)).toEqual(["tasks"]);
38
+ expect(diff.droppedTables).toEqual([]);
39
+ });
40
+
41
+ test("detects dropped table and new column", () => {
42
+ const prev = snapshotFromMetas([meta("tasks"), meta("legacy")]);
43
+ const next = snapshotFromMetas([
44
+ meta("tasks", { name: "title", pgType: "text", notNull: true }),
45
+ ]);
46
+ const diff = diffSnapshots(prev, next);
47
+ expect(diff.droppedTables).toEqual(["legacy"]);
48
+ expect(diff.changedTables[0]?.newColumns.map((c) => c.name)).toEqual(["title"]);
49
+ });
50
+ });
51
+
52
+ describe("renderMigrationSql / generateMigration", () => {
53
+ test("emits CREATE TABLE for new tables", () => {
54
+ const diff = diffSnapshots(null, snapshotFromMetas([meta("tasks")]));
55
+ const sql = renderMigrationSql(diff, { name: "init", sequenceNumber: 1 });
56
+ expect(sql).toContain('CREATE TABLE IF NOT EXISTS "tasks"');
57
+ expect(sql).toContain("Migration 0001_init");
58
+ });
59
+
60
+ test("generateMigration bundles snapshot + sql", () => {
61
+ const out = generateMigration({
62
+ metas: [meta("tasks")],
63
+ prevSnapshot: null,
64
+ name: "init",
65
+ sequenceNumber: 1,
66
+ });
67
+ expect(out.snapshot.tables).toHaveLength(1);
68
+ expect(out.sqlContent).toContain("0001_init");
69
+ expect(out.filename).toBe("0001_init.sql");
70
+ });
71
+ });
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { splitSqlStatements } from "../migrate-runner";
3
+
4
+ describe("splitSqlStatements", () => {
5
+ test("splits on semicolons and strips line comments", () => {
6
+ const sql = `
7
+ CREATE TABLE "a" (id uuid); -- inline comment
8
+ CREATE TABLE "b" (id uuid);
9
+ `;
10
+ expect(splitSqlStatements(sql)).toEqual([
11
+ 'CREATE TABLE "a" (id uuid);',
12
+ 'CREATE TABLE "b" (id uuid);',
13
+ ]);
14
+ });
15
+
16
+ test("filters empty segments", () => {
17
+ expect(splitSqlStatements("-- only comments\n; ;")).toEqual([]);
18
+ });
19
+ });
@@ -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
 
@@ -0,0 +1,43 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { constraintOf, extractPgError, isTableAlreadyExists, isUniqueViolation } from "../pg-error";
3
+
4
+ describe("extractPgError", () => {
5
+ test("reads code from top-level postgres-js error", () => {
6
+ const info = extractPgError({ code: "23505", constraint_name: "users_email_uq" });
7
+ expect(info).toEqual({ code: "23505", constraint_name: "users_email_uq" });
8
+ });
9
+
10
+ test("unwraps DrizzleQueryError.cause", () => {
11
+ const info = extractPgError({
12
+ message: "wrapper",
13
+ cause: { code: "23505", constraint_name: "uq" },
14
+ });
15
+ expect(info?.code).toBe("23505");
16
+ });
17
+
18
+ test("returns null for non-objects", () => {
19
+ expect(extractPgError("nope")).toBeNull();
20
+ });
21
+ });
22
+
23
+ describe("isUniqueViolation", () => {
24
+ test("true for SQLSTATE 23505", () => {
25
+ expect(isUniqueViolation({ code: "23505" })).toBe(true);
26
+ });
27
+
28
+ test("false otherwise", () => {
29
+ expect(isUniqueViolation({ code: "23503" })).toBe(false);
30
+ });
31
+ });
32
+
33
+ describe("isTableAlreadyExists", () => {
34
+ test("true for SQLSTATE 42P07", () => {
35
+ expect(isTableAlreadyExists({ code: "42P07" })).toBe(true);
36
+ });
37
+ });
38
+
39
+ describe("constraintOf", () => {
40
+ test("returns constraint_name when present", () => {
41
+ expect(constraintOf({ constraint_name: "users_email_uq" })).toBe("users_email_uq");
42
+ });
43
+ });
@@ -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
  });