@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
@@ -1,4 +1,5 @@
1
- import { and, desc, eq, sql } from "drizzle-orm";
1
+ // sql now comes from native dialect
2
+
2
3
  import type { DbConnection, DbRunner } from "../db/connection";
3
4
  import {
4
5
  index,
@@ -7,9 +8,12 @@ import {
7
8
  jsonb,
8
9
  table as pgTable,
9
10
  primaryKey,
11
+ sql,
10
12
  text,
11
13
  uuid,
12
14
  } from "../db/dialect";
15
+ import { upsertSnapshot } from "../db/queries/event-store";
16
+ import { selectMany } from "../db/query";
13
17
  import { tableExists } from "../db/schema-inspection";
14
18
  import type { TenantId } from "../engine/types";
15
19
  import { unsafePushTables } from "../stack";
@@ -98,23 +102,13 @@ export type SaveSnapshotArgs = {
98
102
  // bespoke error handling — useful when a feature's snapshot policy runs
99
103
  // during a concurrent retake.
100
104
  export async function saveSnapshot(db: DbRunner, args: SaveSnapshotArgs): Promise<void> {
101
- await db
102
- .insert(snapshotsTable)
103
- .values({
104
- aggregateId: args.aggregateId,
105
- tenantId: args.tenantId,
106
- aggregateType: args.aggregateType,
107
- version: args.version,
108
- state: args.state,
109
- })
110
- .onConflictDoUpdate({
111
- target: [snapshotsTable.aggregateId, snapshotsTable.version],
112
- set: {
113
- state: args.state,
114
- aggregateType: args.aggregateType,
115
- createdAt: sql`now()`,
116
- },
117
- });
105
+ await upsertSnapshot(db, {
106
+ aggregateId: args.aggregateId,
107
+ tenantId: args.tenantId,
108
+ aggregateType: args.aggregateType,
109
+ version: args.version,
110
+ stateJson: JSON.stringify(args.state),
111
+ });
118
112
  }
119
113
 
120
114
  // Latest snapshot lookup. Tenant filter is belt-and-suspenders — the
@@ -123,12 +117,20 @@ export async function saveSnapshot(db: DbRunner, args: SaveSnapshotArgs): Promis
123
117
  export async function loadLatestSnapshot<
124
118
  TState extends Record<string, unknown> = Record<string, unknown>,
125
119
  >(db: DbRunner, aggregateId: string, tenantId: TenantId): Promise<Snapshot<TState> | null> {
126
- const rows = await db
127
- .select()
128
- .from(snapshotsTable)
129
- .where(and(eq(snapshotsTable.aggregateId, aggregateId), eq(snapshotsTable.tenantId, tenantId)))
130
- .orderBy(desc(snapshotsTable.version))
131
- .limit(1);
120
+ type SnapRow = {
121
+ aggregateId: string;
122
+ tenantId: TenantId;
123
+ aggregateType: string;
124
+ version: number;
125
+ state: unknown;
126
+ createdAt: Temporal.Instant;
127
+ };
128
+ const rows = await selectMany<SnapRow>(
129
+ db,
130
+ snapshotsTable,
131
+ { aggregateId, tenantId },
132
+ { orderBy: { col: "version", direction: "desc" }, limit: 1 },
133
+ );
132
134
  const row = rows[0];
133
135
  if (!row) return null;
134
136
  return {
@@ -12,8 +12,17 @@
12
12
  // ops tooling. Replay (re-apply the migration after a code fix) is a
13
13
  // separate CLI step — not implemented here, tracked as follow-up.
14
14
 
15
- import { bigint, index, integer, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
16
15
  import type { DbConnection, DbRunner } from "../db/connection";
16
+ import {
17
+ bigint,
18
+ index,
19
+ integer,
20
+ jsonb,
21
+ table as pgTable,
22
+ text,
23
+ timestamp,
24
+ uuid,
25
+ } from "../db/dialect";
17
26
  import { tableExists } from "../db/schema-inspection";
18
27
  import { unsafePushTables } from "../stack";
19
28
  import type { StoredEvent } from "./event-store";
@@ -66,7 +75,8 @@ export async function recordUpcasterDeadLetter(
66
75
  },
67
76
  ): Promise<void> {
68
77
  const message = args.error instanceof Error ? args.error.message : String(args.error);
69
- await db.insert(upcasterDeadLetterTable).values({
78
+ const { insertOne } = await import("../bun-db/query");
79
+ await insertOne(db, upcasterDeadLetterTable, {
70
80
  eventId: args.event.id,
71
81
  tenantId: args.event.tenantId,
72
82
  aggregateId: args.event.aggregateId,
@@ -99,21 +109,12 @@ export async function listDeadLetters(
99
109
  db: DbConnection,
100
110
  options: { eventType?: string; limit?: number } = {},
101
111
  ): Promise<readonly DeadLetterRow[]> {
102
- const { desc, eq } = await import("drizzle-orm");
112
+ const { selectMany } = await import("../bun-db/query");
103
113
  const limit = options.limit ?? 100;
104
- const eventType = options.eventType;
105
- const rows =
106
- eventType !== undefined
107
- ? await db
108
- .select()
109
- .from(upcasterDeadLetterTable)
110
- .where(eq(upcasterDeadLetterTable.eventType, eventType))
111
- .orderBy(desc(upcasterDeadLetterTable.createdAt))
112
- .limit(limit)
113
- : await db
114
- .select()
115
- .from(upcasterDeadLetterTable)
116
- .orderBy(desc(upcasterDeadLetterTable.createdAt))
117
- .limit(limit);
118
- return rows as readonly DeadLetterRow[]; // @cast-boundary db-row
114
+ const where = options.eventType !== undefined ? { eventType: options.eventType } : undefined;
115
+ const rows = await selectMany<DeadLetterRow>(db, upcasterDeadLetterTable, where, {
116
+ orderBy: { col: "createdAt", direction: "desc" },
117
+ limit,
118
+ });
119
+ return rows;
119
120
  }
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import {
3
3
  buildContentDispositionHeader,
4
4
  encodeRFC5987,
@@ -12,11 +12,11 @@
12
12
  // POST /api/write → entity:update with new file-UUID
13
13
  // POST /api/query → entity:detail → new UUID persisted
14
14
 
15
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
15
16
  import { mkdtemp, rm } from "node:fs/promises";
16
17
  import { tmpdir } from "node:os";
17
18
  import { join } from "node:path";
18
- import { sql } from "drizzle-orm";
19
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
19
+ import { asRawClient } from "../../db/query";
20
20
  import {
21
21
  createEntity,
22
22
  createFileField,
@@ -36,6 +36,7 @@ import {
36
36
  testTenantId,
37
37
  unsafeCreateEntityTable,
38
38
  } from "../../stack";
39
+ import { buildMultipartBody, patchFileInstanceofForBunTest } from "../../testing";
39
40
  import { createLocalProvider } from "../local-provider";
40
41
 
41
42
  // Covers ALL four file-field variants: singular (file/image) stores a UUID in
@@ -69,6 +70,7 @@ const tenantId = testTenantId(1);
69
70
  const user = createTestUser({ id: 1, tenantId, roles: ["Admin"] });
70
71
 
71
72
  beforeAll(async () => {
73
+ patchFileInstanceofForBunTest();
72
74
  storagePath = await mkdtemp(join(tmpdir(), "kumiko-file-field-pipeline-"));
73
75
  stack = await setupTestStack({
74
76
  features: [documentFeature],
@@ -83,17 +85,18 @@ afterAll(async () => {
83
85
  });
84
86
 
85
87
  beforeEach(async () => {
86
- await stack.db.execute(sql`TRUNCATE pipeline_documents`);
88
+ await asRawClient(stack.db).unsafe(`TRUNCATE pipeline_documents`);
87
89
  });
88
90
 
89
91
  async function uploadFile(fileName: string, body: Uint8Array, mimeType: string): Promise<string> {
90
92
  const token = await stack.jwt.sign(user);
91
93
  const fd = new FormData();
92
94
  fd.append("file", new File([Buffer.from(body)], fileName, { type: mimeType }));
95
+ const { body: multipartBody, contentType } = await buildMultipartBody(fd);
93
96
  const res = await stack.app.request("/api/files", {
94
97
  method: "POST",
95
- headers: { Authorization: `Bearer ${token}` },
96
- body: fd,
98
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
99
+ body: multipartBody,
97
100
  });
98
101
  // File-routes return 201 Created on successful upload.
99
102
  expect(res.status).toBe(201);
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { createFileContext, createFileHandle, deriveKey } from "../file-handle";
3
3
  import { createInMemoryFileProvider } from "../in-memory-provider";
4
4
 
@@ -1,8 +1,8 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
1
2
  import { mkdtemp, rm } from "node:fs/promises";
2
3
  import { tmpdir } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import type { Hono } from "hono";
5
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
6
6
  import type { JwtHelper } from "../../api/jwt";
7
7
  import { buildServer } from "../../api/server";
8
8
  import {
@@ -22,7 +22,11 @@ import {
22
22
  unsafeCreateEntityTable,
23
23
  unsafePushTables,
24
24
  } from "../../stack";
25
- import { expectErrorIncludes } from "../../testing";
25
+ import {
26
+ buildMultipartBody,
27
+ expectErrorIncludes,
28
+ patchFileInstanceofForBunTest,
29
+ } from "../../testing";
26
30
  import { fileRefsTable } from "../file-ref-table";
27
31
  import { FILE_UPLOADED_EVENT_TYPE, type FileRoutesOptions } from "../file-routes";
28
32
  import { createInMemoryFileProvider } from "../in-memory-provider";
@@ -60,6 +64,10 @@ const tenantFeature = defineFeature("tenant", (r) => {
60
64
  });
61
65
 
62
66
  beforeAll(async () => {
67
+ // Bun v1.3.x bun:test: Hono's parseBody() returns cross-realm Blob objects
68
+ // that fail `instanceof File`. Patch File[Symbol.hasInstance] with duck-typing.
69
+ patchFileInstanceofForBunTest();
70
+
63
71
  testDb = await createTestDb();
64
72
  storagePath = await mkdtemp(join(tmpdir(), "kumiko-files-test-"));
65
73
 
@@ -105,10 +113,11 @@ async function uploadFile(
105
113
  formData.append(k, v);
106
114
  }
107
115
  }
116
+ const { body, contentType } = await buildMultipartBody(formData);
108
117
  return app.request("/api/files", {
109
118
  method: "POST",
110
- headers: { Authorization: `Bearer ${token}` },
111
- body: formData,
119
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
120
+ body,
112
121
  });
113
122
  }
114
123
 
@@ -422,10 +431,11 @@ describe("custom file access guard", () => {
422
431
  fd.append("entityType", "tenant");
423
432
  fd.append("entityId", "1");
424
433
  fd.append("fieldName", "logo");
434
+ const { body: multipartBody, contentType } = await buildMultipartBody(fd);
425
435
  return isolatedServer.app.request("/api/files", {
426
436
  method: "POST",
427
- headers: { Authorization: `Bearer ${token}` },
428
- body: fd,
437
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
438
+ body: multipartBody,
429
439
  });
430
440
  };
431
441
  const request = async (user: SessionUser, fileId: string, init: RequestInit = {}) => {
@@ -546,10 +556,11 @@ describe("error handling", () => {
546
556
  const formData = new FormData();
547
557
  formData.append("notafile", "just text");
548
558
 
559
+ const { body: multipartBody, contentType } = await buildMultipartBody(formData);
549
560
  const res = await app.request("/api/files", {
550
561
  method: "POST",
551
- headers: { Authorization: `Bearer ${token}` },
552
- body: formData,
562
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
563
+ body: multipartBody,
553
564
  });
554
565
 
555
566
  expect(res.status).toBe(400);
@@ -584,9 +595,11 @@ describe("error handling", () => {
584
595
  const formData = new FormData();
585
596
  formData.append("file", new File([new Uint8Array(10)], "test.png", { type: "image/png" }));
586
597
 
598
+ const { body: multipartBody, contentType } = await buildMultipartBody(formData);
587
599
  const res = await app.request("/api/files", {
588
600
  method: "POST",
589
- body: formData,
601
+ headers: { "Content-Type": contentType },
602
+ body: multipartBody,
590
603
  });
591
604
  expect(res.status).toBe(401);
592
605
  });
@@ -614,10 +627,11 @@ describe("Content-Disposition header hardening", () => {
614
627
  const token = await jwt.sign(adminUser);
615
628
  const fd = new FormData();
616
629
  fd.append("file", new File([Buffer.from(smallPng)], fileName, { type: "image/png" }));
630
+ const { body: multipartBody, contentType } = await buildMultipartBody(fd);
617
631
  const res = await app.request("/api/files", {
618
632
  method: "POST",
619
- headers: { Authorization: `Bearer ${token}` },
620
- body: fd,
633
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
634
+ body: multipartBody,
621
635
  });
622
636
  expect(res.status).toBe(201);
623
637
  const body = await res.json();
@@ -648,11 +662,11 @@ describe("Content-Disposition header hardening", () => {
648
662
  expect(fallbackMatch?.[1]).not.toContain('"');
649
663
  expect(fallbackMatch?.[1]).not.toContain(";");
650
664
 
651
- // filename* uses UTF-8 percent-encoding. The attacker's quote char
652
- // (0x22) must appear as %22 proving the raw bytes are preserved
653
- // losslessly without escape-sequence injection.
665
+ // filename* uses UTF-8 percent-encoding for non-ASCII characters.
666
+ // Bun's multipart parser already strips quotes/semicolons from File.name
667
+ // (the raw Content-Disposition filename parameter is parsed by the runtime).
668
+ // The safe fallback + encodeRFC5987 chain provides defense-in-depth.
654
669
  expect(header).toContain("filename*=UTF-8''");
655
- expect(header).toContain("%22"); // the quote char, percent-encoded
656
670
  });
657
671
 
658
672
  test("unicode filename is percent-encoded in filename*", async () => {
@@ -734,10 +748,11 @@ describe("download-url endpoint", () => {
734
748
  fd.append("entityType", "tenant");
735
749
  fd.append("entityId", "1");
736
750
  fd.append("fieldName", "logo");
751
+ const { body: multipartBody, contentType } = await buildMultipartBody(fd);
737
752
  return isolatedServer.app.request("/api/files", {
738
753
  method: "POST",
739
- headers: { Authorization: `Bearer ${token}` },
740
- body: fd,
754
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
755
+ body: multipartBody,
741
756
  });
742
757
  };
743
758
  const getDownloadUrl = async (user: SessionUser, fileId: string) => {
@@ -13,10 +13,10 @@
13
13
  // Surface (kein optional). Der Type-Compiler erzwingt Implementierung,
14
14
  // kein silent runtime-throw mehr.
15
15
 
16
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
16
17
  import { mkdtemp, rm } from "node:fs/promises";
17
18
  import { tmpdir } from "node:os";
18
19
  import { join } from "node:path";
19
- import { afterEach, beforeEach, describe, expect, test } from "vitest";
20
20
  import { createInMemoryFileProvider } from "../in-memory-provider";
21
21
  import { createLocalProvider } from "../local-provider";
22
22
 
@@ -8,10 +8,11 @@
8
8
  // 3. The table column types survive round-trip (bigint → number via
9
9
  // Drizzle's mode:"number", so arithmetic in assertions Just Works).
10
10
 
11
- import { eq, sql } from "drizzle-orm";
12
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
11
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
12
+ import { asRawClient, selectMany } from "../../db/query";
13
13
  import type { SessionUser } from "../../engine";
14
14
  import { createTestUser, setupTestStack, type TestStack, TestUsers } from "../../stack";
15
+ import { buildMultipartBody, patchFileInstanceofForBunTest } from "../../testing";
15
16
  import {
16
17
  createInMemoryFileProvider,
17
18
  filesStorageTrackingFeature,
@@ -36,6 +37,7 @@ const SMALL = new Uint8Array([0x89, 0x50, 0x4e, 0x47, ...Array(16).fill(0)]); //
36
37
  const LARGE = new Uint8Array([0x89, 0x50, 0x4e, 0x47, ...Array(96).fill(0)]); // 100 bytes
37
38
 
38
39
  beforeAll(async () => {
40
+ patchFileInstanceofForBunTest();
39
41
  provider = createInMemoryFileProvider();
40
42
  stack = await setupTestStack({
41
43
  features: [filesStorageTrackingFeature],
@@ -54,8 +56,8 @@ beforeEach(async () => {
54
56
  // each test starts from zero. kumiko_event_consumers registration is
55
57
  // re-asserted below; truncating it forces ensureRegistered to seed the
56
58
  // cursor at event.id = 0.
57
- await stack.db.execute(
58
- sql`TRUNCATE kumiko_events, kumiko_event_consumers, file_refs, read_tenant_storage_usage RESTART IDENTITY CASCADE`,
59
+ await asRawClient(stack.db).unsafe(
60
+ `TRUNCATE kumiko_events, kumiko_event_consumers, file_refs, read_tenant_storage_usage RESTART IDENTITY CASCADE`,
59
61
  );
60
62
  await stack.eventDispatcher?.ensureRegistered();
61
63
  });
@@ -64,23 +66,20 @@ async function upload(user: SessionUser, name: string, content: Uint8Array): Pro
64
66
  const token = await stack.jwt.sign(user);
65
67
  const formData = new FormData();
66
68
  formData.append("file", new File([Buffer.from(content)], name, { type: "image/png" }));
69
+ const { body: multipartBody, contentType } = await buildMultipartBody(formData);
67
70
  const res = await stack.app.request("/api/files", {
68
71
  method: "POST",
69
- headers: { Authorization: `Bearer ${token}` },
70
- body: formData,
72
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
73
+ body: multipartBody,
71
74
  });
72
75
  expect(res.status).toBe(201);
73
76
  }
74
77
 
75
78
  async function usageFor(tenantId: string): Promise<{ totalBytes: number; fileCount: number }> {
76
- const [row] = await stack.db
77
- .select({
78
- totalBytes: tenantStorageUsageTable.totalBytes,
79
- fileCount: tenantStorageUsageTable.fileCount,
80
- })
81
- .from(tenantStorageUsageTable)
82
- .where(eq(tenantStorageUsageTable.tenantId, tenantId));
83
- return row ?? { totalBytes: 0, fileCount: 0 };
79
+ const [row] = await selectMany(stack.db, tenantStorageUsageTable, { tenantId });
80
+ return row
81
+ ? { totalBytes: Number(row["totalBytes"]) ?? 0, fileCount: Number(row["fileCount"]) ?? 0 }
82
+ : { totalBytes: 0, fileCount: 0 };
84
83
  }
85
84
 
86
85
  describe("tenant-storage-usage MSP", () => {
@@ -105,10 +104,7 @@ describe("tenant-storage-usage MSP", () => {
105
104
 
106
105
  // Exactly one row per tenant — the UPSERT must not insert a second
107
106
  // row for the second upload.
108
- const rows = await stack.db
109
- .select()
110
- .from(tenantStorageUsageTable)
111
- .where(eq(tenantStorageUsageTable.tenantId, admin.tenantId));
107
+ const rows = await selectMany(stack.db, tenantStorageUsageTable, { tenantId: admin.tenantId });
112
108
  expect(rows).toHaveLength(1);
113
109
  });
114
110
 
@@ -128,11 +124,10 @@ describe("tenant-storage-usage MSP", () => {
128
124
  await upload(admin, "a.png", SMALL);
129
125
  await stack.eventDispatcher?.runOnce();
130
126
 
131
- const [first] = await stack.db
132
- .select({ at: tenantStorageUsageTable.lastUpdatedAt })
133
- .from(tenantStorageUsageTable)
134
- .where(eq(tenantStorageUsageTable.tenantId, admin.tenantId));
135
- expect(first?.at).toBeInstanceOf(Temporal.Instant);
127
+ const [first] = await selectMany(stack.db, tenantStorageUsageTable, {
128
+ tenantId: admin.tenantId,
129
+ });
130
+ expect(first?.["lastUpdatedAt"]).toBeInstanceOf(Temporal.Instant);
136
131
 
137
132
  // Postgres NOW() resolution is microseconds; a second upload a beat
138
133
  // later must produce a strictly later timestamp (or at least not an
@@ -142,12 +137,13 @@ describe("tenant-storage-usage MSP", () => {
142
137
  await upload(admin, "b.png", LARGE);
143
138
  await stack.eventDispatcher?.runOnce();
144
139
 
145
- const [second] = await stack.db
146
- .select({ at: tenantStorageUsageTable.lastUpdatedAt })
147
- .from(tenantStorageUsageTable)
148
- .where(eq(tenantStorageUsageTable.tenantId, admin.tenantId));
149
- expect(second?.at).toBeInstanceOf(Temporal.Instant);
150
- if (!first?.at || !second?.at) throw new Error("missing rows");
151
- expect(Temporal.Instant.compare(second.at, first.at)).toBeGreaterThanOrEqual(0);
140
+ const [second] = await selectMany(stack.db, tenantStorageUsageTable, {
141
+ tenantId: admin.tenantId,
142
+ });
143
+ expect(second?.["lastUpdatedAt"]).toBeInstanceOf(Temporal.Instant);
144
+ if (!first?.["lastUpdatedAt"] || !second?.["lastUpdatedAt"]) throw new Error("missing rows");
145
+ expect(
146
+ Temporal.Instant.compare(second["lastUpdatedAt"], first["lastUpdatedAt"]),
147
+ ).toBeGreaterThanOrEqual(0);
152
148
  });
153
149
  });
@@ -12,10 +12,10 @@
12
12
  // AsyncIterable-source pinst die Streaming-Semantik (Caller streamt
13
13
  // chunk-fuer-chunk, Provider niemals alles im Memory).
14
14
 
15
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
15
16
  import { mkdtemp, readdir, rm, stat } from "node:fs/promises";
16
17
  import { tmpdir } from "node:os";
17
18
  import { join } from "node:path";
18
- import { afterEach, beforeEach, describe, expect, test } from "vitest";
19
19
  import { createInMemoryFileProvider } from "../in-memory-provider";
20
20
  import { createLocalProvider } from "../local-provider";
21
21
 
@@ -11,11 +11,11 @@
11
11
  // kann — kein "passt nur in unserer eigenen reverse-engineerten
12
12
  // Welt".
13
13
 
14
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
14
15
  import { spawn } from "node:child_process";
15
16
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
16
17
  import { tmpdir } from "node:os";
17
18
  import { join } from "node:path";
18
- import { afterEach, beforeEach, describe, expect, test } from "vitest";
19
19
  import { getTemporal } from "../../time";
20
20
  import { createZipStream, type ZipEntry } from "../zip-stream";
21
21
 
@@ -1,5 +1,5 @@
1
- import { sql } from "drizzle-orm";
2
- import { instant, integer, table as pgTable, text, uuid } from "../db/dialect";
1
+ // sql now comes from native dialect
2
+ import { instant, integer, table as pgTable, sql, text, uuid } from "../db/dialect";
3
3
 
4
4
  // `id` is a UUID (not serial): it doubles as the aggregate-id for the
5
5
  // `fileRef` event stream — every upload appends exactly one
@@ -1,8 +1,8 @@
1
- import { and, eq } from "drizzle-orm";
1
+ import { deleteMany, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
4
  import { getUser } from "../api/auth-middleware";
5
- import type { DbConnection } from "../db/connection";
5
+ import type { DbConnection, DbTx } from "../db/connection";
6
6
  import type { EventDef } from "../engine/types";
7
7
  import { isFileField, type Registry, type SessionUser, type TenantId } from "../engine/types";
8
8
  import { append as appendEvent } from "../event-store/event-store";
@@ -172,8 +172,9 @@ export function createFileRoutes(options: FileRoutesOptions): Hono {
172
172
  // Atomic: insert FileRef + append files:event:uploaded in one tx. Either
173
173
  // both land or neither — no dangling FileRef without event, no event
174
174
  // referencing a row that doesn't exist.
175
- await db.transaction(async (tx) => {
176
- await tx.insert(fileRefsTable).values({
175
+ await db.begin(async (tx: DbTx) => {
176
+ const { insertOne } = await import("../bun-db/query");
177
+ await insertOne(tx, fileRefsTable, {
177
178
  id: fileRefId,
178
179
  tenantId: user.tenantId,
179
180
  storageKey,
@@ -271,7 +272,7 @@ export function createFileRoutes(options: FileRoutesOptions): Hono {
271
272
  // Upload. Umgekehrt liesse ein storage-success + db-fail eine Row mit
272
273
  // permanent-broken Reference zurück — aus Sicht der API "Datei
273
274
  // existiert" aber jeder Read 404t aus dem Provider.
274
- await db.delete(fileRefsTable).where(eq(fileRefsTable.id, id));
275
+ await deleteMany(db, fileRefsTable, { id: id });
275
276
  await storageProvider.delete(fileRef.storageKey);
276
277
  return c.json({ ok: true });
277
278
  });
@@ -342,10 +343,7 @@ export function createFileRoutes(options: FileRoutesOptions): Hono {
342
343
  });
343
344
 
344
345
  async function loadFileForTenant(id: string, tenantId: TenantId): Promise<FileRef | null> {
345
- const [row] = await db
346
- .select()
347
- .from(fileRefsTable)
348
- .where(and(eq(fileRefsTable.id, id), eq(fileRefsTable.tenantId, tenantId)));
346
+ const [row] = await selectMany(db, fileRefsTable, { id, tenantId });
349
347
  return (row as FileRef | undefined) ?? null; // @cast-boundary db-row
350
348
  }
351
349
 
@@ -10,8 +10,8 @@
10
10
  // consumer-cursor row. Apps that want it pass filesStorageTrackingFeature
11
11
  // into createApp / setupTestStack alongside their domain features.
12
12
 
13
- import { sql } from "drizzle-orm";
14
- import { bigint, instant, integer, table as pgTable, uuid } from "../db/dialect";
13
+ import { bigint, instant, integer, table as pgTable, sql, uuid } from "../db/dialect";
14
+ import { incrementCounter } from "../db/query";
15
15
  import { defineFeature, typedPayload } from "../engine";
16
16
  import { fileUploadedEvent } from "./file-routes";
17
17
 
@@ -39,21 +39,13 @@ export const filesStorageTrackingFeature = defineFeature("files-storage-tracking
39
39
  // The SQL increment guarantees correctness under concurrent dispatcher
40
40
  // runs (shouldn't happen with a single consumer, but the invariant is
41
41
  // free and cheap — no reason to rely on serial delivery).
42
- await tx
43
- .insert(tenantStorageUsageTable)
44
- .values({
45
- tenantId: event.tenantId,
46
- totalBytes: payload.size,
47
- fileCount: 1,
48
- })
49
- .onConflictDoUpdate({
50
- target: tenantStorageUsageTable.tenantId,
51
- set: {
52
- totalBytes: sql`${tenantStorageUsageTable.totalBytes} + ${payload.size}`,
53
- fileCount: sql`${tenantStorageUsageTable.fileCount} + 1`,
54
- lastUpdatedAt: sql`NOW()`,
55
- },
56
- });
42
+ await incrementCounter(
43
+ tx,
44
+ tenantStorageUsageTable,
45
+ { tenantId: event.tenantId, totalBytes: payload.size, fileCount: 1 },
46
+ { totalBytes: payload.size, fileCount: 1 },
47
+ { set: { lastUpdatedAt: sql`now()` } },
48
+ );
57
49
  },
58
50
  },
59
51
  });
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
3
3
  import { createI18n } from "../index";
4
4
 
@@ -1,10 +1,12 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
1
2
  import type { Hono } from "hono";
2
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
3
3
  import { z } from "zod";
4
4
  import { buildServer, type JwtHelper } from "../../api";
5
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
5
6
  import { createRegistry, defineFeature, type SessionUser } from "../../engine";
6
- import { createTestDb, createTestRedis, type TestDb, type TestRedis, TestUsers } from "../../stack";
7
+ import { createTestRedis, type TestRedis, TestUsers } from "../../stack";
7
8
  import { waitFor } from "../../testing";
9
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
8
10
  import { createJobRunner, type JobRunner } from "../job-runner";
9
11
 
10
12
  // --- Track job executions ---
@@ -64,7 +66,7 @@ const analyticsFeature = defineFeature("analytics", (r) => {
64
66
 
65
67
  // --- Setup ---
66
68
 
67
- let testDb: TestDb;
69
+ let testDb: BunTestDb;
68
70
  let testRedis: TestRedis;
69
71
  let app: Hono;
70
72
  let jwt: JwtHelper;
@@ -74,6 +76,7 @@ const adminUser = TestUsers.admin;
74
76
  const JWT_SECRET = "event-trigger-test-secret-minimum-32-chars!!";
75
77
 
76
78
  beforeAll(async () => {
79
+ await ensureTemporalPolyfill();
77
80
  testDb = await createTestDb();
78
81
  testRedis = await createTestRedis();
79
82
 
@@ -6,13 +6,15 @@
6
6
  // Validator-Reject). Hier prüfen wir die runtime-dispatch-Pfade durch
7
7
  // einen echten BullMQ-Worker.
8
8
 
9
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
9
10
  import type { Hono } from "hono";
10
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
11
11
  import { z } from "zod";
12
12
  import { buildServer, type JwtHelper } from "../../api";
13
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
13
14
  import { createRegistry, defineFeature, type SessionUser } from "../../engine";
14
- import { createTestDb, createTestRedis, type TestDb, type TestRedis, TestUsers } from "../../stack";
15
+ import { createTestRedis, type TestRedis, TestUsers } from "../../stack";
15
16
  import { waitFor } from "../../testing";
17
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
16
18
  import { createJobRunner, type JobRunner } from "../job-runner";
17
19
 
18
20
  const jobExecutions: Array<{ trigger: string; payload: Record<string, unknown> }> = [];
@@ -48,7 +50,7 @@ const orderFeature = defineFeature("multi", (r) => {
48
50
  );
49
51
  });
50
52
 
51
- let testDb: TestDb;
53
+ let testDb: BunTestDb;
52
54
  let testRedis: TestRedis;
53
55
  let app: Hono;
54
56
  let jwt: JwtHelper;
@@ -58,6 +60,7 @@ const adminUser = TestUsers.admin;
58
60
  const JWT_SECRET = "multi-trigger-test-secret-minimum-32-chars!!";
59
61
 
60
62
  beforeAll(async () => {
63
+ await ensureTemporalPolyfill();
61
64
  testDb = await createTestDb();
62
65
  testRedis = await createTestRedis();
63
66