@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,7 +1,8 @@
1
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
2
  import { z } from "zod";
3
3
  import { createEventStoreExecutor } from "../../db/event-store-executor";
4
- import { buildDrizzleTable } from "../../db/table-builder";
4
+ import { asRawClient, insertOne, selectMany } from "../../db/query";
5
+ import { buildEntityTable } from "../../db/table-builder";
5
6
  import {
6
7
  createEntity,
7
8
  createNumberField,
@@ -23,7 +24,7 @@ const itemEntity = createEntity({
23
24
  },
24
25
  });
25
26
 
26
- const itemTable = buildDrizzleTable("item", itemEntity);
27
+ const itemTable = buildEntityTable("item", itemEntity);
27
28
 
28
29
  // Second entity used by an inTransaction hook to prove that hook DB writes
29
30
  // roll back with the main transaction.
@@ -34,7 +35,7 @@ const auditEntity = createEntity({
34
35
  itemId: createTextField({ required: true }),
35
36
  },
36
37
  });
37
- const auditTable = buildDrizzleTable("audit", auditEntity);
38
+ const auditTable = buildEntityTable("audit", auditEntity);
38
39
 
39
40
  // Hook invocation logs — reset per test. Captures which phase each hook saw.
40
41
  const inTxHookLog: Array<{ id: EntityId; name: string }> = [];
@@ -92,10 +93,7 @@ const itemFeature = defineFeature("batch", (r) => {
92
93
  item,
93
94
  async (result, ctx) => {
94
95
  if (!ctx.db) return;
95
- await ctx.db
96
- .insert(auditTable)
97
- .values({ action: "item_saved", itemId: result.id })
98
- .returning();
96
+ await ctx.db.insertOne(auditTable, { action: "item_saved", itemId: result.id });
99
97
  },
100
98
  { phase: HookPhases.inTransaction },
101
99
  );
@@ -166,8 +164,8 @@ beforeEach(async () => {
166
164
  afterCommitShouldThrow = false;
167
165
  parallelismWindows.length = 0;
168
166
  stack.events.reset();
169
- await stack.db.delete(itemTable);
170
- await stack.db.delete(auditTable);
167
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${itemTable.tableName}"`);
168
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${auditTable.tableName}"`);
171
169
  });
172
170
 
173
171
  describe("POST /api/batch", () => {
@@ -213,16 +211,18 @@ describe("POST /api/batch", () => {
213
211
  expect(afterCommitHookLog.map((h) => h.name)).toEqual(["alpha", "beta", "gamma"]);
214
212
 
215
213
  // Rows actually persisted
216
- const rows = await stack.db.select().from(itemTable);
214
+ const rows = await selectMany(stack.db, itemTable);
217
215
  expect(rows).toHaveLength(3);
218
216
  });
219
217
 
220
218
  test("mid-batch failure: all writes roll back, afterCommit hooks do NOT fire", async () => {
221
219
  // Seed with one existing item so we can verify the batch didn't persist anything
222
- await stack.db
223
- .insert(itemTable)
224
- .values({ name: "seed", counter: 0, tenantId: "00000000-0000-4000-8000-000000000001" });
225
- const seedCount = (await stack.db.select().from(itemTable)).length;
220
+ await insertOne(stack.db, itemTable, {
221
+ name: "seed",
222
+ counter: 0,
223
+ tenantId: "00000000-0000-4000-8000-000000000001",
224
+ });
225
+ const seedCount = (await selectMany(stack.db, itemTable)).length;
226
226
 
227
227
  const res = await stack.http.batch(
228
228
  [
@@ -252,7 +252,7 @@ describe("POST /api/batch", () => {
252
252
  expect(afterCommitHookLog).toEqual([]);
253
253
 
254
254
  // DB: only the seed row remains, the batch's first successful write rolled back
255
- const rows = await stack.db.select().from(itemTable);
255
+ const rows = await selectMany(stack.db, itemTable);
256
256
  expect(rows).toHaveLength(seedCount);
257
257
  expect((rows[0] as { name: string }).name).toBe("seed");
258
258
  });
@@ -264,13 +264,13 @@ describe("POST /api/batch", () => {
264
264
  admin,
265
265
  );
266
266
  expect((await okRes.json()).isSuccess).toBe(true);
267
- const auditAfterOk = await stack.db.select().from(auditTable);
267
+ const auditAfterOk = await selectMany(stack.db, auditTable);
268
268
  expect(auditAfterOk).toHaveLength(1);
269
269
  expect((auditAfterOk[0] as { action: string }).action).toBe("item_saved");
270
270
 
271
271
  // Reset — new batch fails mid-way. Both entity rows AND audit rows must roll back.
272
- await stack.db.delete(itemTable);
273
- await stack.db.delete(auditTable);
272
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${itemTable.tableName}"`);
273
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${auditTable.tableName}"`);
274
274
 
275
275
  const failRes = await stack.http.batch(
276
276
  [
@@ -283,8 +283,8 @@ describe("POST /api/batch", () => {
283
283
 
284
284
  // Both tables are empty — the inTransaction audit hook's write rolled back
285
285
  // together with the item row.
286
- const itemsAfterFail = await stack.db.select().from(itemTable);
287
- const auditAfterFail = await stack.db.select().from(auditTable);
286
+ const itemsAfterFail = await selectMany(stack.db, itemTable);
287
+ const auditAfterFail = await selectMany(stack.db, auditTable);
288
288
  expect(itemsAfterFail).toHaveLength(0);
289
289
  expect(auditAfterFail).toHaveLength(0);
290
290
  });
@@ -328,7 +328,7 @@ describe("POST /api/batch", () => {
328
328
  expect(body.isSuccess).toBe(true);
329
329
 
330
330
  // DB row persisted (tx committed)
331
- const rows = await stack.db.select().from(itemTable);
331
+ const rows = await selectMany(stack.db, itemTable);
332
332
  expect(rows).toHaveLength(1);
333
333
 
334
334
  // The hook AFTER the throwing one still ran — errors don't cascade
@@ -344,7 +344,7 @@ describe("POST /api/batch", () => {
344
344
  expect(firstBody.isSuccess).toBe(true);
345
345
  expect(firstBody.results).toHaveLength(1);
346
346
 
347
- const rowsAfterFirst = await stack.db.select().from(itemTable);
347
+ const rowsAfterFirst = await selectMany(stack.db, itemTable);
348
348
  expect(rowsAfterFirst).toHaveLength(1);
349
349
 
350
350
  // Hook logs reflect one execution
@@ -359,7 +359,7 @@ describe("POST /api/batch", () => {
359
359
  expect(secondBody.results).toEqual(firstBody.results);
360
360
 
361
361
  // DB still has only one row (no double-insert)
362
- const rowsAfterSecond = await stack.db.select().from(itemTable);
362
+ const rowsAfterSecond = await selectMany(stack.db, itemTable);
363
363
  expect(rowsAfterSecond).toHaveLength(1);
364
364
 
365
365
  // Hooks didn't fire a second time
@@ -376,8 +376,8 @@ describe("POST /api/write (single write runs in its own transaction)", () => {
376
376
 
377
377
  // Both the item row AND the audit row exist — proves the single write
378
378
  // went through a transaction and the inTx hook shared it.
379
- const items = await stack.db.select().from(itemTable);
380
- const audits = await stack.db.select().from(auditTable);
379
+ const items = await selectMany(stack.db, itemTable);
380
+ const audits = await selectMany(stack.db, auditTable);
381
381
  expect(items).toHaveLength(1);
382
382
  expect(audits).toHaveLength(1);
383
383
  });
@@ -385,8 +385,8 @@ describe("POST /api/write (single write runs in its own transaction)", () => {
385
385
  test("handler throw rolls back inTransaction hook writes too", async () => {
386
386
  // First a successful write so there's something to compare against
387
387
  await stack.http.write("batch:write:item:create", { name: "survivor" }, admin);
388
- const beforeItems = await stack.db.select().from(itemTable);
389
- const beforeAudits = await stack.db.select().from(auditTable);
388
+ const beforeItems = await selectMany(stack.db, itemTable);
389
+ const beforeAudits = await selectMany(stack.db, auditTable);
390
390
  expect(beforeItems).toHaveLength(1);
391
391
  expect(beforeAudits).toHaveLength(1);
392
392
 
@@ -395,8 +395,8 @@ describe("POST /api/write (single write runs in its own transaction)", () => {
395
395
  const body = await res.json();
396
396
  expect(body.isSuccess).toBe(false);
397
397
 
398
- const afterItems = await stack.db.select().from(itemTable);
399
- const afterAudits = await stack.db.select().from(auditTable);
398
+ const afterItems = await selectMany(stack.db, itemTable);
399
+ const afterAudits = await selectMany(stack.db, auditTable);
400
400
  // Counts unchanged — no partial commit
401
401
  expect(afterItems).toHaveLength(beforeItems.length);
402
402
  expect(afterAudits).toHaveLength(beforeAudits.length);
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { z } from "zod";
3
3
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
4
4
  import { buildServer } from "../server";
@@ -2,8 +2,8 @@
2
2
  // layers authMiddleware → csrfMiddleware → handler. Covers the paths that
3
3
  // matter in production: cookie + state-changing, cookie + safe, bearer.
4
4
 
5
+ import { describe, expect, test } from "bun:test";
5
6
  import { Hono } from "hono";
6
- import { describe, expect, test } from "vitest";
7
7
  import { TestUsers } from "../../stack";
8
8
  import {
9
9
  AUTH_COOKIE_NAME,
@@ -1,9 +1,10 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
1
2
  import { createLiveDispatcher } from "@cosmicdrift/kumiko-dispatcher-live";
2
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
3
3
  import { z } from "zod";
4
4
  import { generateToken } from "../../api/tokens";
5
5
  import { createEventStoreExecutor } from "../../db/event-store-executor";
6
- import { buildDrizzleTable } from "../../db/table-builder";
6
+ import { asRawClient, insertOne, selectMany } from "../../db/query";
7
+ import { buildEntityTable } from "../../db/table-builder";
7
8
  import { createEntity, createTextField, defineFeature } from "../../engine";
8
9
  import { setupTestStack, type TestStack, TestUsers, unsafeCreateEntityTable } from "../../stack";
9
10
  import { generateId } from "../../utils";
@@ -23,7 +24,7 @@ const itemEntity = createEntity({
23
24
  table: "dispatcher_live_items",
24
25
  fields: { name: createTextField({ required: true }) },
25
26
  });
26
- const itemTable = buildDrizzleTable("item", itemEntity);
27
+ const itemTable = buildEntityTable("item", itemEntity);
27
28
 
28
29
  const itemFeature = defineFeature("dlive", (r) => {
29
30
  r.entity("item", itemEntity);
@@ -42,7 +43,7 @@ const itemFeature = defineFeature("dlive", (r) => {
42
43
  "item:list",
43
44
  z.object({}).optional(),
44
45
  async (_event, ctx) => {
45
- return ctx.db.select().from(itemTable);
46
+ return selectMany(ctx.db, itemTable);
46
47
  },
47
48
  { access: { roles: ["Admin"] } },
48
49
  );
@@ -105,7 +106,7 @@ afterAll(async () => {
105
106
  });
106
107
 
107
108
  beforeEach(async () => {
108
- await stack.db.delete(itemTable);
109
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${itemTable.tableName}"`);
109
110
  });
110
111
 
111
112
  describe("dispatcher-live (integration) — full path against Kumiko server", () => {
@@ -123,7 +124,7 @@ describe("dispatcher-live (integration) — full path against Kumiko server", ()
123
124
  expect(result.isSuccess).toBe(true);
124
125
 
125
126
  // Prove the server actually persisted.
126
- const rows = await stack.db.select().from(itemTable);
127
+ const rows = await selectMany(stack.db, itemTable);
127
128
  expect(rows).toHaveLength(1);
128
129
  expect(rows[0]?.["name"]).toBe("hello-live");
129
130
  });
@@ -158,7 +159,7 @@ describe("dispatcher-live (integration) — full path against Kumiko server", ()
158
159
 
159
160
  test("query: dispatches GET-style-POST (Kumiko uses POST for query too), returns data", async () => {
160
161
  // Seed a row first.
161
- await stack.db.insert(itemTable).values({
162
+ await insertOne(stack.db, itemTable, {
162
163
  id: generateId(),
163
164
  tenantId: admin.tenantId,
164
165
  name: "seed",
@@ -188,7 +189,7 @@ describe("dispatcher-live (integration) — full path against Kumiko server", ()
188
189
 
189
190
  expect(result.isSuccess).toBe(true);
190
191
 
191
- const rows = await stack.db.select().from(itemTable);
192
+ const rows = await selectMany(stack.db, itemTable);
192
193
  expect(rows).toHaveLength(3);
193
194
  const names = rows.map((r) => r["name"]).sort();
194
195
  expect(names).toEqual(["a", "b", "c"]);
@@ -210,7 +211,7 @@ describe("dispatcher-live (integration) — full path against Kumiko server", ()
210
211
  }
211
212
 
212
213
  // DB must be empty — prior success within a failed batch rolls back.
213
- const rows = await stack.db.select().from(itemTable);
214
+ const rows = await selectMany(stack.db, itemTable);
214
215
  expect(rows).toHaveLength(0);
215
216
  });
216
217
  });
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
3
3
  import {
4
4
  createNoopProvider,
@@ -1,8 +1,8 @@
1
- import { eq } from "drizzle-orm";
2
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
3
2
  import { z } from "zod";
4
3
  import { createEventStoreExecutor } from "../../db/event-store-executor";
5
- import { buildDrizzleTable } from "../../db/table-builder";
4
+ import { asRawClient, selectMany } from "../../db/query";
5
+ import { buildEntityTable } from "../../db/table-builder";
6
6
  import { createEntity, createTextField, defineFeature } from "../../engine";
7
7
  import { setupTestStack, type TestStack, TestUsers, unsafeCreateEntityTable } from "../../stack";
8
8
 
@@ -14,7 +14,7 @@ const projectEntity = createEntity({
14
14
  table: "nested_projects",
15
15
  fields: { name: createTextField({ required: true }) },
16
16
  });
17
- const projectTable = buildDrizzleTable("project", projectEntity);
17
+ const projectTable = buildEntityTable("project", projectEntity);
18
18
 
19
19
  const taskEntity = createEntity({
20
20
  table: "nested_tasks",
@@ -23,7 +23,7 @@ const taskEntity = createEntity({
23
23
  title: createTextField({ required: true }),
24
24
  },
25
25
  });
26
- const taskTable = buildDrizzleTable("task", taskEntity);
26
+ const taskTable = buildEntityTable("task", taskEntity);
27
27
 
28
28
  const nestedFeature = defineFeature("nested", (r) => {
29
29
  r.entity("project", projectEntity);
@@ -78,8 +78,8 @@ afterAll(async () => {
78
78
  });
79
79
 
80
80
  beforeEach(async () => {
81
- await stack.db.delete(taskTable);
82
- await stack.db.delete(projectTable);
81
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${taskTable.tableName}"`);
82
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${projectTable.tableName}"`);
83
83
  });
84
84
 
85
85
  describe("POST /api/write — nested-write (Welle M1)", () => {
@@ -111,11 +111,8 @@ describe("POST /api/write — nested-write (Welle M1)", () => {
111
111
  expect(parent.tasks[1].title).toBe("t2");
112
112
 
113
113
  // DB reflects both writes.
114
- const dbProjects = await stack.db.select().from(projectTable);
115
- const dbTasks = await stack.db
116
- .select()
117
- .from(taskTable)
118
- .where(eq(taskTable["projectId"], parent.id));
114
+ const dbProjects = await selectMany(stack.db, projectTable);
115
+ const dbTasks = await selectMany(stack.db, taskTable, { projectId: parent.id });
119
116
  expect(dbProjects).toHaveLength(1);
120
117
  expect(dbTasks).toHaveLength(2);
121
118
  });
@@ -137,8 +134,8 @@ describe("POST /api/write — nested-write (Welle M1)", () => {
137
134
  expect(body.isSuccess).toBe(false);
138
135
 
139
136
  // DB empty — prior sub-task and parent both rolled back.
140
- const dbProjects = await stack.db.select().from(projectTable);
141
- const dbTasks = await stack.db.select().from(taskTable);
137
+ const dbProjects = await selectMany(stack.db, projectTable);
138
+ const dbTasks = await selectMany(stack.db, taskTable);
142
139
  expect(dbProjects).toHaveLength(0);
143
140
  expect(dbTasks).toHaveLength(0);
144
141
  });
@@ -182,7 +179,7 @@ describe("POST /api/write — nested-write (Welle M1)", () => {
182
179
 
183
180
  // Parent did not persist — the pre-flight check runs before the parent
184
181
  // write, so the TX never opened on a malformed nested key.
185
- const dbProjects = await stack.db.select().from(projectTable);
182
+ const dbProjects = await selectMany(stack.db, projectTable);
186
183
  expect(dbProjects).toHaveLength(0);
187
184
  });
188
185
 
@@ -207,7 +204,7 @@ describe("POST /api/write — nested-write (Welle M1)", () => {
207
204
  expect(body.error.details.fields[0].path).toMatch(/tasks\.0\.projectId/);
208
205
 
209
206
  // DB empty.
210
- const dbProjects = await stack.db.select().from(projectTable);
207
+ const dbProjects = await selectMany(stack.db, projectTable);
211
208
  expect(dbProjects).toHaveLength(0);
212
209
  });
213
210
  });
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { createReadinessProbe, type ReadinessCheck } from "../readiness";
3
3
 
4
4
  function okCheck(name: string, delayMs = 0): ReadinessCheck {
@@ -1,5 +1,5 @@
1
+ import { describe, expect, test } from "bun:test";
1
2
  import { Hono } from "hono";
2
- import { describe, expect, test } from "vitest";
3
3
  import { requestContext } from "../request-context";
4
4
  import { requestIdMiddleware } from "../request-id-middleware";
5
5
 
@@ -1,12 +1,12 @@
1
- import { describe, expect, test, vi } from "vitest";
1
+ import { describe, expect, mock, test } from "bun:test";
2
2
  import { createSseBroker, type SseEvent } from "../sse-broker";
3
3
 
4
4
  describe("SSE broker", () => {
5
5
  test("adds client and tracks count", () => {
6
6
  const broker = createSseBroker();
7
- broker.addClient("ch1", vi.fn(), vi.fn());
8
- broker.addClient("ch1", vi.fn(), vi.fn());
9
- broker.addClient("ch2", vi.fn(), vi.fn());
7
+ broker.addClient("ch1", mock(), mock());
8
+ broker.addClient("ch1", mock(), mock());
9
+ broker.addClient("ch2", mock(), mock());
10
10
 
11
11
  expect(broker.getClientCount("ch1")).toBe(2);
12
12
  expect(broker.getClientCount("ch2")).toBe(1);
@@ -15,13 +15,13 @@ describe("SSE broker", () => {
15
15
 
16
16
  test("pushToChannel sends to all clients on channel", () => {
17
17
  const broker = createSseBroker();
18
- const send1 = vi.fn();
19
- const send2 = vi.fn();
20
- const sendOther = vi.fn();
18
+ const send1 = mock();
19
+ const send2 = mock();
20
+ const sendOther = mock();
21
21
 
22
- broker.addClient("users", send1, vi.fn());
23
- broker.addClient("users", send2, vi.fn());
24
- broker.addClient("other", sendOther, vi.fn());
22
+ broker.addClient("users", send1, mock());
23
+ broker.addClient("users", send2, mock());
24
+ broker.addClient("other", sendOther, mock());
25
25
 
26
26
  const event: SseEvent = { type: "user.created", data: { id: 1 } };
27
27
  broker.pushToChannel("users", event);
@@ -33,9 +33,9 @@ describe("SSE broker", () => {
33
33
 
34
34
  test("removeClient stops delivery", () => {
35
35
  const broker = createSseBroker();
36
- const send = vi.fn();
36
+ const send = mock();
37
37
 
38
- const clientId = broker.addClient("ch", send, vi.fn());
38
+ const clientId = broker.addClient("ch", send, mock());
39
39
  broker.removeClient("ch", clientId);
40
40
 
41
41
  broker.pushToChannel("ch", { type: "test", data: {} });
@@ -1,5 +1,5 @@
1
+ import { describe, expect, test } from "bun:test";
1
2
  import { Hono } from "hono";
2
- import { describe, expect, test } from "vitest";
3
3
  import { TestUsers } from "../../stack";
4
4
  import { authMiddleware } from "../auth-middleware";
5
5
  import { createJwtHelper } from "../jwt";
@@ -11,9 +11,9 @@
11
11
  // - Checks run in parallel — the probe is called on every kubelet/ALB poll,
12
12
  // so total latency must stay ≈ slowest check, not sum.
13
13
 
14
- import { sql } from "drizzle-orm";
15
14
  import type Redis from "ioredis";
16
15
  import type { DbConnection } from "../db/connection";
16
+ import { pingDatabase } from "../db/queries/raw-sql";
17
17
  import { getAllConsumerProgress } from "../pipeline/event-dispatcher";
18
18
 
19
19
  export type ReadinessCheck = {
@@ -90,7 +90,7 @@ export function dbPingCheck(db: DbConnection): ReadinessCheck {
90
90
  return {
91
91
  name: "db",
92
92
  run: async () => {
93
- await db.execute(sql`SELECT 1`);
93
+ await pingDatabase(db);
94
94
  },
95
95
  };
96
96
  }
@@ -1,6 +1,6 @@
1
1
  // Snapshot-Tests fuer ROLES — faengt stille Drift ab.
2
2
 
3
- import { describe, expect, test } from "vitest";
3
+ import { describe, expect, test } from "bun:test";
4
4
  import { ROLES } from "../roles";
5
5
 
6
6
  describe("ROLES constants", () => {
@@ -18,7 +18,7 @@ describe("ROLES constants", () => {
18
18
 
19
19
  test("ROLES-Werte sind identisch zu den Keys (keine Drift im Mapping)", () => {
20
20
  for (const [key, value] of Object.entries(ROLES)) {
21
- expect(value).toBe(key);
21
+ expect(value as string).toBe(key);
22
22
  }
23
23
  });
24
24
  });
@@ -0,0 +1,73 @@
1
+ # Pattern: Provider-agnostische DB-Integration-Tests
2
+
3
+ ## Architektur
4
+
5
+ ```
6
+ db/api.ts createConnection() — liest DB_PROVIDER env
7
+ db/postgres-provider.ts postgres-js Factory (default, stabil)
8
+ db/bun-provider.ts Bun.SQL Factory (DB_PROVIDER=bun, experimentell)
9
+ stack/db.ts createTestDb() — provider-agnostisch via createConnection
10
+ stack/test-stack.ts setupTestStack() — provider-agnostisch via createTestDb
11
+ bun-db/__tests__/bun-test-stack.ts Alias: setupBunTestStack → setupTestStack
12
+ bun-db/__tests__/bun-test-db.ts Alias: createBunTestDb → createTestDb
13
+ ```
14
+
15
+ ## Snippet: Integration-Test
16
+
17
+ ```typescript
18
+ import { setupTestStack, type TestStack } from "../stack";
19
+ import { defineFeature } from "../engine";
20
+
21
+ const feature = defineFeature("example", (r) => { /* ... */ });
22
+
23
+ let stack: TestStack;
24
+
25
+ beforeAll(async () => {
26
+ stack = await setupTestStack({ features: [feature] });
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await stack.cleanup();
31
+ });
32
+
33
+ test("works", async () => {
34
+ // stack.db → provider-agnostic (asRawClient für .unsafe/.begin)
35
+ // stack.http → RequestHelper (writeOk, queryOk, raw)
36
+ // stack.redis → TestRedis
37
+ // stack.events → EventCollector
38
+ });
39
+ ```
40
+
41
+ Nur DB (ohne HTTP/Stack):
42
+
43
+ ```typescript
44
+ import { createTestDb, type TestDb } from "../stack";
45
+ import { asRawClient } from "../bun-db/query";
46
+
47
+ let td: TestDb;
48
+ beforeAll(async () => { td = await createTestDb(); });
49
+ afterAll(async () => { await td.cleanup(); });
50
+ test("works", async () => {
51
+ await asRawClient(td.db).unsafe("SELECT 1");
52
+ });
53
+ ```
54
+
55
+ ## Switching Driver
56
+
57
+ ```bash
58
+ # Default: postgres-js
59
+ bun test ./src/**/*.integration.ts
60
+
61
+ # Bun.SQL (experimentell)
62
+ DB_PROVIDER=bun bun test ./src/**/*.integration.ts
63
+ ```
64
+
65
+ ## Bekannte Issues
66
+
67
+ - **Bun.SQL Extended-Query-Protocol Bug**: PostgresError "bind message has 11 result
68
+ formats but query has 1 columns" bei sequentiellen Queries mit unterschiedlicher
69
+ Spaltenzahl innerhalb derselben Transaction. Nur bei `DB_PROVIDER=bun`.
70
+ - **Temporal-Polyfill**: `createTestDb()` ruft jetzt `ensureTemporalPolyfill()` auf.
71
+ - **TenantDb.insertOne**: Immer `tdb.insertOne(table, ...)` verwenden (Methode),
72
+ NICHT `insertOne(tdb, table, ...)` (standalone aus bun-db/query — bypassed
73
+ tenant-injection via asRawClient).
@@ -0,0 +1,103 @@
1
+ // Helpers für bun-db SQL-Layer-Integration-Tests.
2
+ // Provider-agnostic via createConnection (DB_PROVIDER env).
3
+ //
4
+ // Pattern: pro Test eine eigene Tabelle mit unique-Name (random-Suffix),
5
+ // damit concurrency=8 tests sich nicht in die Quere kommen. CREATE TABLE
6
+ // im before, DROP TABLE im after (auch bei test-fail damit Test-DB clean
7
+ // bleibt). KEINE TEMP TABLES — die wären connection-bound und pools
8
+ // reused connections, was tests in zwei verschiedenen Pool-Slots
9
+ // in verschiedene "TEMP-Welten" stecken würde.
10
+ //
11
+ // Schema-Tabellen bestehen aus einer id (uuid) + den getesteten Spalten.
12
+
13
+ import { randomUUID } from "node:crypto";
14
+ import { createConnection } from "../../db/api";
15
+ import type { ColumnMeta, EntityTableMeta } from "../../db/entity-table-meta";
16
+
17
+ const DATABASE_URL =
18
+ process.env["TEST_DATABASE_URL"] ??
19
+ process.env["DATABASE_URL"] ??
20
+ "postgresql://kumiko:kumiko@localhost:15432/kumiko_test";
21
+
22
+ let dbInstance: { db: unknown; close: () => Promise<void> } | undefined;
23
+
24
+ export async function getDb(): Promise<unknown> {
25
+ return ensureDb();
26
+ }
27
+ export async function ensureDb(): Promise<unknown> {
28
+ if (!dbInstance) {
29
+ dbInstance = await createConnection(DATABASE_URL, { maxConnections: 4 });
30
+ }
31
+ return dbInstance.db;
32
+ }
33
+
34
+ export async function closeDb(): Promise<void> {
35
+ if (dbInstance) {
36
+ await dbInstance.close();
37
+ dbInstance = undefined;
38
+ }
39
+ }
40
+
41
+ // Erzeugt einen pseudo-zufälligen Tabellen-Namen. snake-case, lower,
42
+ // nur a-z0-9_, max 50 chars (Postgres-Identifier-Limit ist 63).
43
+ export function uniqueTableName(prefix = "sqltest"): string {
44
+ const suffix = randomUUID().replace(/-/g, "").slice(0, 12);
45
+ return `${prefix}_${suffix}`;
46
+ }
47
+
48
+ // EntityTableMeta-Helper. id (uuid primary key) wird IMMER gesetzt; der
49
+ // Caller liefert nur die zusätzlichen Test-Spalten.
50
+ export function makeTableMeta(
51
+ tableName: string,
52
+ extraColumns: readonly ColumnMeta[],
53
+ ): EntityTableMeta {
54
+ return {
55
+ tableName,
56
+ source: "unmanaged",
57
+ indexes: [],
58
+ columns: [
59
+ {
60
+ name: "id",
61
+ pgType: "uuid",
62
+ notNull: true,
63
+ primaryKey: true,
64
+ defaultSql: "gen_random_uuid()",
65
+ },
66
+ ...extraColumns,
67
+ ],
68
+ };
69
+ }
70
+
71
+ // CREATE TABLE-Statement aus EntityTableMeta. Minimaler DDL-Renderer —
72
+ // kein Index-Support, kein Komplex-Default. Genug für die Test-Matrix.
73
+ export function renderCreateTable(meta: EntityTableMeta): string {
74
+ const cols = meta.columns
75
+ .map((c) => {
76
+ const parts = [`"${c.name}"`, c.pgType];
77
+ if (c.notNull) parts.push("NOT NULL");
78
+ if (c.defaultSql) parts.push(`DEFAULT ${c.defaultSql}`);
79
+ if (c.primaryKey) parts.push("PRIMARY KEY");
80
+ return parts.join(" ");
81
+ })
82
+ .join(", ");
83
+ return `CREATE TABLE "${meta.tableName}" (${cols})`;
84
+ }
85
+
86
+ // Test-Wrapper: Tabelle anlegen, fn aufrufen, Tabelle droppen.
87
+ // fn bekommt {db, meta} — alle SQL-Operationen laufen direkt darauf.
88
+ export async function withTable<T>(
89
+ columns: readonly ColumnMeta[],
90
+ fn: (ctx: { db: unknown; meta: EntityTableMeta }) => Promise<T>,
91
+ prefix?: string,
92
+ ): Promise<T> {
93
+ const db = await getDb();
94
+ const tableName = uniqueTableName(prefix);
95
+ const meta = makeTableMeta(tableName, columns);
96
+ const { asRawClient } = await import("../query");
97
+ await asRawClient(db).unsafe(renderCreateTable(meta));
98
+ try {
99
+ return await fn({ db, meta });
100
+ } finally {
101
+ await asRawClient(db).unsafe(`DROP TABLE IF EXISTS "${tableName}"`);
102
+ }
103
+ }