@cosmicdrift/kumiko-framework 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/package.json +7 -7
  2. package/src/__tests__/{anonymous-access.integration.ts → anonymous-access.integration.test.ts} +12 -9
  3. package/src/__tests__/{error-contract.integration.ts → error-contract.integration.test.ts} +5 -4
  4. package/src/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +3 -3
  5. package/src/__tests__/{full-stack.integration.ts → full-stack.integration.test.ts} +7 -16
  6. package/src/__tests__/{ownership.integration.ts → ownership.integration.test.ts} +3 -2
  7. package/src/__tests__/{raw-table.integration.ts → raw-table.integration.test.ts} +18 -30
  8. package/src/__tests__/{reference-data.integration.ts → reference-data.integration.test.ts} +24 -11
  9. package/src/__tests__/{transition-guard.integration.ts → transition-guard.integration.test.ts} +12 -10
  10. package/src/api/__tests__/api.test.ts +1 -1
  11. package/src/api/__tests__/auth-middleware-transport.test.ts +1 -1
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +1 -1
  13. package/src/api/__tests__/{batch.integration.ts → batch.integration.test.ts} +30 -30
  14. package/src/api/__tests__/body-limit.test.ts +1 -1
  15. package/src/api/__tests__/csrf-middleware.test.ts +1 -1
  16. package/src/api/__tests__/{dispatcher-live.integration.ts → dispatcher-live.integration.test.ts} +10 -9
  17. package/src/api/__tests__/metrics-endpoint.test.ts +1 -1
  18. package/src/api/__tests__/{nested-write.integration.ts → nested-write.integration.test.ts} +13 -16
  19. package/src/api/__tests__/readiness.test.ts +1 -1
  20. package/src/api/__tests__/request-id-middleware.test.ts +1 -1
  21. package/src/api/__tests__/sse-broker.test.ts +12 -12
  22. package/src/api/__tests__/sse-route.test.ts +1 -1
  23. package/src/api/readiness.ts +2 -2
  24. package/src/auth/__tests__/roles.test.ts +2 -2
  25. package/src/bun-db/__tests__/PATTERN.md +73 -0
  26. package/src/bun-db/__tests__/_helpers.ts +103 -0
  27. package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
  28. package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
  29. package/src/bun-db/__tests__/bun-test-db.ts +19 -0
  30. package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
  31. package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
  32. package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
  33. package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
  34. package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
  35. package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
  36. package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
  37. package/src/bun-db/connection.ts +84 -0
  38. package/src/bun-db/index.ts +31 -0
  39. package/src/bun-db/query.ts +845 -0
  40. package/src/compliance/__tests__/duration-spec.test.ts +1 -1
  41. package/src/compliance/__tests__/profiles.test.ts +1 -1
  42. package/src/compliance/__tests__/sub-processors.test.ts +1 -1
  43. package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
  44. package/src/db/__tests__/big-int-field.test.ts +15 -14
  45. package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
  46. package/src/db/__tests__/compound-types.test.ts +1 -1
  47. package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
  48. package/src/db/__tests__/connection-options.test.ts +1 -1
  49. package/src/db/__tests__/dialect-instant.test.ts +1 -1
  50. package/src/db/__tests__/encryption.test.ts +1 -1
  51. package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
  52. package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
  53. package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
  54. package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
  55. package/src/db/__tests__/located-timestamp.test.ts +1 -1
  56. package/src/db/__tests__/money.test.ts +1 -1
  57. package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
  58. package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
  59. package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
  60. package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
  61. package/src/db/__tests__/sql-inventory.test.ts +56 -0
  62. package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
  63. package/src/db/__tests__/table-builder-required.test.ts +20 -22
  64. package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
  65. package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
  66. package/src/db/api.ts +46 -0
  67. package/src/db/apply-entity-event.ts +45 -36
  68. package/src/db/assert-exists-in.ts +5 -16
  69. package/src/db/bun-provider.ts +37 -0
  70. package/src/db/config-seed.ts +4 -4
  71. package/src/db/connection.ts +14 -57
  72. package/src/db/cursor.ts +5 -56
  73. package/src/db/dialect.ts +472 -99
  74. package/src/db/eagerload.ts +5 -12
  75. package/src/db/entity-table-meta.ts +390 -0
  76. package/src/db/event-store-executor.ts +158 -100
  77. package/src/db/index.ts +33 -5
  78. package/src/db/migrate-generator.ts +350 -0
  79. package/src/db/migrate-runner.ts +206 -0
  80. package/src/db/postgres-provider.ts +25 -0
  81. package/src/db/queries/entity-read.ts +15 -0
  82. package/src/db/queries/es-ops.ts +17 -0
  83. package/src/db/queries/event-consumer.ts +170 -0
  84. package/src/db/queries/event-store-admin.ts +127 -0
  85. package/src/db/queries/event-store.ts +155 -0
  86. package/src/db/queries/projection-rebuild.ts +59 -0
  87. package/src/db/queries/raw-sql.ts +15 -0
  88. package/src/db/queries/schema-drift.ts +35 -0
  89. package/src/db/queries/seed-context.ts +58 -0
  90. package/src/db/queries/table-ops.ts +11 -0
  91. package/src/db/queries/test-stack.ts +56 -0
  92. package/src/db/query-api.ts +22 -0
  93. package/src/db/query.ts +30 -0
  94. package/src/db/reference-data.ts +19 -22
  95. package/src/db/render-ddl.ts +57 -0
  96. package/src/db/row-helpers.ts +3 -52
  97. package/src/db/schema-inspection.ts +17 -4
  98. package/src/db/sql-inventory.ts +208 -0
  99. package/src/db/table-builder.ts +48 -40
  100. package/src/db/tenant-db.ts +105 -326
  101. package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
  102. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
  103. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
  104. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
  105. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
  106. package/src/engine/__tests__/boot-validator.test.ts +4 -3
  107. package/src/engine/__tests__/build-app-schema.test.ts +1 -1
  108. package/src/engine/__tests__/build-target.test.ts +1 -1
  109. package/src/engine/__tests__/claim-keys.test.ts +1 -1
  110. package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
  111. package/src/engine/__tests__/config-helpers.test.ts +1 -1
  112. package/src/engine/__tests__/effective-features.test.ts +1 -1
  113. package/src/engine/__tests__/engine.test.ts +1 -1
  114. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  115. package/src/engine/__tests__/event-helpers.test.ts +3 -3
  116. package/src/engine/__tests__/extends-registrar.test.ts +4 -4
  117. package/src/engine/__tests__/factories-long-text.test.ts +1 -1
  118. package/src/engine/__tests__/factories-time.test.ts +1 -1
  119. package/src/engine/__tests__/field-predicates.test.ts +1 -1
  120. package/src/engine/__tests__/hook-phases.test.ts +1 -1
  121. package/src/engine/__tests__/identifiers.test.ts +1 -1
  122. package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
  123. package/src/engine/__tests__/nav.test.ts +1 -1
  124. package/src/engine/__tests__/ownership.test.ts +10 -11
  125. package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
  126. package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
  127. package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
  128. package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
  129. package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
  130. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
  131. package/src/engine/__tests__/post-query-hook.test.ts +1 -1
  132. package/src/engine/__tests__/projection-helpers.test.ts +25 -17
  133. package/src/engine/__tests__/projection.test.ts +4 -4
  134. package/src/engine/__tests__/qualified-name.test.ts +1 -1
  135. package/src/engine/__tests__/raw-table.test.ts +9 -8
  136. package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
  137. package/src/engine/__tests__/run-in.test.ts +1 -1
  138. package/src/engine/__tests__/schema-builder.test.ts +1 -1
  139. package/src/engine/__tests__/screen.test.ts +1 -1
  140. package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
  141. package/src/engine/__tests__/state-machine.test.ts +1 -1
  142. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
  143. package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
  144. package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
  145. package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
  146. package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
  147. package/src/engine/__tests__/steps-read.test.ts +34 -40
  148. package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
  149. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
  150. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
  151. package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
  152. package/src/engine/__tests__/steps-workflow.test.ts +7 -7
  153. package/src/engine/__tests__/system-user.test.ts +1 -1
  154. package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
  155. package/src/engine/__tests__/validation-hooks.test.ts +1 -1
  156. package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
  157. package/src/engine/boot-validator/entity-handler.ts +3 -3
  158. package/src/engine/boot-validator/ownership.ts +1 -1
  159. package/src/engine/define-feature.ts +1 -2
  160. package/src/engine/entity-handlers.ts +5 -5
  161. package/src/engine/factories.ts +1 -1
  162. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
  163. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
  164. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
  165. package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
  166. package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
  167. package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
  168. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
  169. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
  170. package/src/engine/ownership.ts +113 -41
  171. package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
  172. package/src/engine/projection-helpers.ts +2 -11
  173. package/src/engine/registry.ts +2 -2
  174. package/src/engine/steps/read-find-many.ts +13 -13
  175. package/src/engine/steps/read-find-one.ts +7 -9
  176. package/src/engine/steps/unsafe-projection-delete.ts +4 -5
  177. package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
  178. package/src/engine/types/feature.ts +7 -2
  179. package/src/engine/types/fields.ts +4 -5
  180. package/src/engine/types/step.ts +10 -10
  181. package/src/engine/validate-projection-allowlist.ts +23 -3
  182. package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
  183. package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
  184. package/src/env/__tests__/compose-env-schema.test.ts +1 -1
  185. package/src/env/__tests__/dry-run.test.ts +1 -1
  186. package/src/errors/__tests__/classes.test.ts +1 -1
  187. package/src/errors/__tests__/write-failures.test.ts +1 -1
  188. package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
  189. package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
  190. package/src/es-ops/__tests__/runner.test.ts +29 -19
  191. package/src/es-ops/context.ts +9 -43
  192. package/src/es-ops/operations-schema.ts +2 -2
  193. package/src/es-ops/runner.ts +12 -26
  194. package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
  195. package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
  196. package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
  197. package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
  198. package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
  199. package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
  200. package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
  201. package/src/event-store/admin-api.ts +55 -83
  202. package/src/event-store/archive.ts +15 -39
  203. package/src/event-store/event-store.ts +92 -86
  204. package/src/event-store/events-schema.ts +2 -1
  205. package/src/event-store/index.ts +1 -0
  206. package/src/event-store/snapshot.ts +26 -24
  207. package/src/event-store/upcaster-dead-letter.ts +19 -18
  208. package/src/files/__tests__/content-disposition.test.ts +1 -1
  209. package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
  210. package/src/files/__tests__/file-handle.test.ts +1 -1
  211. package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
  212. package/src/files/__tests__/read-stream.test.ts +1 -1
  213. package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
  214. package/src/files/__tests__/write-stream.test.ts +1 -1
  215. package/src/files/__tests__/zip-stream.test.ts +1 -1
  216. package/src/files/file-ref-table.ts +2 -2
  217. package/src/files/file-routes.ts +7 -9
  218. package/src/files/storage-tracking.ts +9 -17
  219. package/src/i18n/__tests__/i18n.test.ts +1 -1
  220. package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
  221. package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
  222. package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
  223. package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
  224. package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
  225. package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
  226. package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
  227. package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
  228. package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
  229. package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
  230. package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
  231. package/src/migrations/projection-detection.ts +12 -1
  232. package/src/migrations/schema-drift.ts +7 -23
  233. package/src/observability/__tests__/console-provider.test.ts +1 -1
  234. package/src/observability/__tests__/metric-validator.test.ts +1 -1
  235. package/src/observability/__tests__/noop-provider.test.ts +1 -1
  236. package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
  237. package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
  238. package/src/observability/__tests__/recording-meter.test.ts +1 -1
  239. package/src/observability/__tests__/recording-tracer.test.ts +1 -1
  240. package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
  241. package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
  242. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
  243. package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
  244. package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
  245. package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
  246. package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
  247. package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
  248. package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
  249. package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
  250. package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
  251. package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
  252. package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
  253. package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
  254. package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
  255. package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
  256. package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
  257. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
  258. package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
  259. package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
  260. package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
  261. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
  262. package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
  263. package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
  264. package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
  265. package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
  266. package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
  267. package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
  268. package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
  269. package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
  270. package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
  271. package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
  272. package/src/pipeline/cascade-handler.ts +13 -21
  273. package/src/pipeline/dispatcher.ts +43 -48
  274. package/src/pipeline/event-consumer-state.ts +11 -2
  275. package/src/pipeline/event-dispatcher.ts +86 -146
  276. package/src/pipeline/event-retention.ts +14 -24
  277. package/src/pipeline/msp-rebuild.ts +54 -78
  278. package/src/pipeline/projection-rebuild.ts +65 -67
  279. package/src/pipeline/projection-state.ts +2 -2
  280. package/src/random/__tests__/generate.test.ts +13 -13
  281. package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
  282. package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
  283. package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
  284. package/src/redis/__tests__/redis-options.test.ts +1 -1
  285. package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
  286. package/src/search/__tests__/search-adapter.test.ts +1 -1
  287. package/src/secrets/__tests__/dek-cache.test.ts +1 -3
  288. package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
  289. package/src/secrets/__tests__/envelope.test.ts +1 -1
  290. package/src/secrets/__tests__/leak-guard.test.ts +1 -1
  291. package/src/secrets/__tests__/rotation.test.ts +1 -1
  292. package/src/stack/db.ts +25 -48
  293. package/src/stack/push-entity-projection-tables.ts +2 -4
  294. package/src/stack/table-helpers.ts +98 -61
  295. package/src/stack/test-stack.ts +8 -7
  296. package/src/testing/__tests__/db-cleanup.test.ts +40 -0
  297. package/src/testing/__tests__/e2e-generator.test.ts +1 -1
  298. package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
  299. package/src/testing/db-cleanup.ts +44 -0
  300. package/src/testing/expect-error.ts +1 -1
  301. package/src/testing/index.ts +2 -0
  302. package/src/testing/multipart-helper.ts +94 -0
  303. package/src/testing/shared-entities.ts +5 -5
  304. package/src/time/__tests__/polyfill.test.ts +1 -1
  305. package/src/time/__tests__/tz-context.test.ts +1 -1
  306. package/src/utils/__tests__/assert.test.ts +1 -1
  307. package/src/utils/__tests__/env-parse.test.ts +1 -1
  308. package/CHANGELOG.md +0 -472
  309. package/src/db/__tests__/cursor.test.ts +0 -41
  310. package/src/db/__tests__/db-helpers.test.ts +0 -369
  311. package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
  312. package/src/db/__tests__/row-helpers.test.ts +0 -59
  313. package/src/engine/steps/_drizzle-boundary.ts +0 -19
  314. package/src/files/__tests__/file-field-column.integration.ts +0 -103
@@ -1,42 +1,46 @@
1
- import { generateDrizzleJson, generateMigration } from "drizzle-kit/api";
2
- import { getTableName, sql } from "drizzle-orm";
3
- import type { PgTable } from "drizzle-orm/pg-core";
4
- import type { drizzle } from "drizzle-orm/postgres-js";
1
+ import type { DbConnection } from "../db/connection";
2
+ import { pgTypeToSqlType } from "../db/dialect";
3
+ import type { ColumnMeta, EntityTableMeta } from "../db/entity-table-meta";
4
+ import {
5
+ alterTableAddColumn,
6
+ createIndexIfNotExists,
7
+ executeDdlStatement,
8
+ truncateTablesRestartIdentity,
9
+ } from "../db/queries/test-stack";
10
+ import { renderTableDdl } from "../db/render-ddl";
5
11
  import { tableExists } from "../db/schema-inspection";
6
- import { buildDrizzleTable, toTableName } from "../db/table-builder";
7
- import type { TestStack } from "./test-stack";
12
+ import { buildEntityTable, toTableName } from "../db/table-builder";
13
+ import type { EventDispatcher } from "../pipeline";
14
+
15
+ const KUMIKO_NAME_SYMBOL = Symbol.for("kumiko:schema:Name");
16
+ function tableNameOf(table: unknown): string {
17
+ if (typeof table !== "object" || table === null) {
18
+ throw new Error("table-helpers: table is not a SchemaTable object");
19
+ }
20
+ const rec = table as Record<string | symbol, unknown>;
21
+ if (typeof rec[KUMIKO_NAME_SYMBOL] === "string") return rec[KUMIKO_NAME_SYMBOL] as string;
22
+ if (typeof (rec as { tableName?: unknown }).tableName === "string") {
23
+ return (rec as { tableName: string }).tableName;
24
+ }
25
+ throw new Error("table-helpers: table has no name");
26
+ }
8
27
 
9
28
  /**
10
- * Bypass: creates a Drizzle table directly, without registering it as
11
- * a projection of the event-sourcing engine. Apps should declare data
12
- * via `r.entity(...)` and get tables, migrations, snapshots and audit
13
- * for free — this helper is reserved for framework-internal meta-tables
14
- * (event-store, snapshots, projection-state) and test setup.
15
- *
16
- * Strict: raises a postgres `relation already exists` (42P07) error if
17
- * the table is already there. Use `unsafeEnsureEntityTable` for the
18
- * idempotent boot-path variant.
29
+ * Bypass: creates an entity-table directly without going through the
30
+ * full registry. Reserved for framework-internal meta-tables and
31
+ * test setup — apps declare data via `r.entity(...)`.
19
32
  */
20
33
  export async function unsafeCreateEntityTable(
21
- db: ReturnType<typeof drizzle>,
34
+ db: DbConnection,
22
35
  entity: import("../engine/types").EntityDefinition,
23
36
  entityName?: string,
24
37
  ): Promise<void> {
25
- const table = buildDrizzleTable(entityName ?? "entity", entity);
38
+ const table = buildEntityTable(entityName ?? "entity", entity);
26
39
  await unsafePushTables(db, { [entityName ?? "entity"]: table });
27
40
  }
28
41
 
29
- /**
30
- * Bypass (idempotent): same caveat as `unsafeCreateEntityTable` —
31
- * apps declare data via `r.entity(...)`. Checks whether the entity's
32
- * table already exists and skips creation if so. Schema-drift is *not*
33
- * detected: if the table is there but has the wrong columns, that's
34
- * the caller's problem (the dev-server contract is "drop the DB by
35
- * hand when you change the schema"). Tests should use
36
- * `unsafeCreateEntityTable` instead, since they rely on fresh DBs.
37
- */
38
42
  export async function unsafeEnsureEntityTable(
39
- db: ReturnType<typeof drizzle>,
43
+ db: DbConnection,
40
44
  entity: import("../engine/types").EntityDefinition,
41
45
  entityName?: string,
42
46
  ): Promise<boolean> {
@@ -46,54 +50,88 @@ export async function unsafeEnsureEntityTable(
46
50
  return true;
47
51
  }
48
52
 
53
+ // Tables produced by the native dialect already carry EntityTableMeta-shape
54
+ // (source/columns/indexes). renderTableDdl converts that to CREATE TABLE +
55
+ // CREATE INDEX statements executed via db/queries/test-stack.
56
+ function tableToMeta(table: unknown): EntityTableMeta {
57
+ if (
58
+ typeof table === "object" &&
59
+ table !== null &&
60
+ "tableName" in table &&
61
+ "columns" in table &&
62
+ "indexes" in table &&
63
+ "source" in table
64
+ ) {
65
+ return table as EntityTableMeta;
66
+ }
67
+ throw new Error("unsafePushTables: argument is not a SchemaTable / EntityTableMeta");
68
+ }
69
+
49
70
  /**
50
- * Bypass: pushes Drizzle table definitions to the database directly.
51
- * Uses drizzle-kit's generateDrizzleJson + generateMigration to produce SQL,
52
- * then executes it. Same SQL that `drizzle-kit push` would generate.
53
- * Reserved for framework-internal meta-tables (event-store, projections,
54
- * consumer-state) and test setup — apps declare data via `r.entity(...)`.
71
+ * Bypass: pushes table definitions to the database directly. Produces
72
+ * CREATE TABLE IF NOT EXISTS + CREATE INDEX statements via renderTableDdl
73
+ * and executes them via db/queries/test-stack. Idempotent re-runs are safe.
55
74
  *
56
- * @param prevTables - Previous table definitions (for ALTER TABLE scenarios).
57
- * If omitted, assumes empty DB (CREATE TABLE).
75
+ * Reserved for framework-internal meta-tables + test setup. App-defined
76
+ * entities go through `kumiko schema apply` (committed SQL files).
58
77
  */
59
78
  export async function unsafePushTables(
60
- db: ReturnType<typeof drizzle>,
79
+ db: DbConnection,
61
80
  tables: Record<string, unknown>,
62
81
  prevTables?: Record<string, unknown>,
63
82
  ): Promise<void> {
64
- const prevJson = generateDrizzleJson(prevTables ?? {});
65
- const targetJson = generateDrizzleJson(tables);
66
- const statements = await generateMigration(prevJson, targetJson);
67
- for (const stmt of statements) {
68
- await db.execute(sql.raw(stmt));
83
+ const prevMetas = new Map<string, EntityTableMeta>();
84
+ if (prevTables) {
85
+ for (const [key, table] of Object.entries(prevTables)) {
86
+ const meta = tableToMeta(table);
87
+ prevMetas.set(key, meta);
88
+ }
89
+ }
90
+
91
+ for (const [key, table] of Object.entries(tables)) {
92
+ const meta = tableToMeta(table);
93
+ const prev = prevMetas.get(key);
94
+
95
+ if (prev) {
96
+ const prevCols = new Set(prev.columns.map((c) => c.name));
97
+ for (const col of meta.columns) {
98
+ if (!prevCols.has(col.name)) {
99
+ const type = renderColumnType(col);
100
+ const notNull = col.notNull && !col.primaryKey ? " NOT NULL" : "";
101
+ const defaultClause = col.defaultSql !== undefined ? ` DEFAULT ${col.defaultSql}` : "";
102
+ await alterTableAddColumn(db, meta.tableName, col.name, type, defaultClause, notNull);
103
+ }
104
+ }
105
+ const prevIdxNames = new Set(prev.indexes.map((i) => i.name));
106
+ for (const idx of meta.indexes) {
107
+ if (!prevIdxNames.has(idx.name)) {
108
+ const kind = idx.unique ? "UNIQUE INDEX" : "INDEX";
109
+ const colList = idx.columns.map((c) => `"${c}"`).join(", ");
110
+ await createIndexIfNotExists(db, kind, idx.name, meta.tableName, colList);
111
+ }
112
+ }
113
+ } else {
114
+ const statements = renderTableDdl(meta);
115
+ for (const stmt of statements) {
116
+ await executeDdlStatement(db, stmt);
117
+ }
118
+ }
69
119
  }
70
120
  }
71
121
 
122
+ function renderColumnType(col: ColumnMeta): string {
123
+ return pgTypeToSqlType(col.pgType);
124
+ }
125
+
72
126
  /**
73
127
  * Wipes event store + framework-state + the given feature read-models in
74
128
  * one TRUNCATE, then re-registers the event-consumer state rows. Used in
75
129
  * test beforeEach-hooks to return the stack to a clean slate without
76
130
  * rebuilding it.
77
- *
78
- * Fixed list of framework tables (kumiko_events, kumiko_event_consumers,
79
- * kumiko_archived_streams, kumiko_snapshots, kumiko_projections) is always
80
- * included — any event-sourced test setup needs those cleared. The
81
- * `extraTables` arg covers the feature's own read-model tables that would
82
- * otherwise accumulate rows across tests.
83
- *
84
- * Accepts either a Drizzle PgTable (for locally-defined tables: getTableName
85
- * extracts the SQL name) or a plain string (for SQL names whose Drizzle
86
- * reference lives in another module and importing it for the TRUNCATE
87
- * alone would be overkill). Both round-trip to the same TRUNCATE list.
88
- *
89
- * Pre-existing code duplicates this block 30+ times, each with its own
90
- * list of extras. The helper collapses that to a one-liner per test and
91
- * lets a future change to the framework-table set (e.g. adding a new
92
- * consumer-state table) ripple through without touching every suite.
93
131
  */
94
132
  export async function resetEventStore(
95
- stack: TestStack,
96
- extraTables: readonly (PgTable | string)[] = [],
133
+ stack: { db: unknown; eventDispatcher?: EventDispatcher },
134
+ extraTables: readonly (unknown | string)[] = [],
97
135
  ): Promise<void> {
98
136
  const frameworkTables = [
99
137
  "kumiko_events",
@@ -102,9 +140,8 @@ export async function resetEventStore(
102
140
  "kumiko_snapshots",
103
141
  "kumiko_projections",
104
142
  ];
105
- const extraNames = extraTables.map((t) => (typeof t === "string" ? t : getTableName(t)));
106
- const allTables = [...frameworkTables, ...extraNames];
107
- await stack.db.execute(sql.raw(`TRUNCATE ${allTables.join(", ")} RESTART IDENTITY CASCADE`));
143
+ const extraNames = extraTables.map((t) => (typeof t === "string" ? t : tableNameOf(t)));
144
+ await truncateTablesRestartIdentity(stack.db, [...frameworkTables, ...extraNames]);
108
145
  if (stack.eventDispatcher) {
109
146
  await stack.eventDispatcher.ensureRegistered();
110
147
  }
@@ -3,7 +3,7 @@ import type { AuthRoutesConfig } from "../api/auth-routes";
3
3
  import type { JwtHelper } from "../api/jwt";
4
4
  import { buildServer } from "../api/server";
5
5
  import { createSseBroker } from "../api/sse-broker";
6
- import type { DbConnection } from "../db/connection";
6
+ import type { PgClient } from "../db/connection";
7
7
  import { createRegistry } from "../engine/registry";
8
8
  import type { FeatureDefinition, Registry, TenantId } from "../engine/types";
9
9
  import { createArchivedStreamsTable, createEventsTable } from "../event-store";
@@ -23,9 +23,8 @@ export type TestStack = {
23
23
  app: Hono;
24
24
  jwt: JwtHelper;
25
25
  registry: Registry;
26
- /** Drizzle connection the test DB's lifecycle (name, raw pg client,
27
- * drop) lives inside setupTestStack and is released via stack.cleanup(). */
28
- db: DbConnection;
26
+ // biome-ignore lint/suspicious/noExplicitAny: cross-provider connection
27
+ db: any;
29
28
  redis: TestRedis;
30
29
  search: SearchAdapter;
31
30
  events: EventCollector;
@@ -58,7 +57,8 @@ export type TestStackOptions = {
58
57
  | Record<string, unknown>
59
58
  | ((deps: {
60
59
  registry: Registry;
61
- db: import("../db/connection").DbConnection;
60
+ // biome-ignore lint/suspicious/noExplicitAny: cross-provider connection
61
+ db: any;
62
62
  sseBroker: import("../api/sse-broker").SseBroker;
63
63
  redis: import("ioredis").default;
64
64
  }) => Record<string, unknown>);
@@ -110,7 +110,8 @@ export type TestStackOptions = {
110
110
  | import("../api/server").ServerOptions["anonymousAccess"]
111
111
  | ((deps: {
112
112
  registry: Registry;
113
- db: import("../db/connection").DbConnection;
113
+ // biome-ignore lint/suspicious/noExplicitAny: cross-provider connection
114
+ db: any;
114
115
  sseBroker: import("../api/sse-broker").SseBroker;
115
116
  redis: import("ioredis").default;
116
117
  }) => import("../api/server").ServerOptions["anonymousAccess"]);
@@ -294,7 +295,7 @@ export async function setupTestStack(options: TestStackOptions): Promise<TestSta
294
295
  // post-commit latency (Sprint E.4).
295
296
  eventDispatcher: {
296
297
  pollIntervalMs: 50,
297
- pgClient: testDb.client,
298
+ pgClient: testDb.client as PgClient | undefined,
298
299
  systemConsumers: {
299
300
  sse: enabledHooks.includes("sse"),
300
301
  search: enabledHooks.includes("search"),
@@ -0,0 +1,40 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { clearTables } from "../db-cleanup";
3
+
4
+ describe("db-cleanup", () => {
5
+ test("clearTables issues DELETE without WHERE per table via deleteMany", async () => {
6
+ const sqlLog: string[] = [];
7
+ const mockDb = {
8
+ unsafe: async (sql: string) => {
9
+ sqlLog.push(sql);
10
+ return [];
11
+ },
12
+ };
13
+
14
+ await clearTables(mockDb, ["read_users", "kumiko_events"]);
15
+
16
+ expect(sqlLog).toHaveLength(2);
17
+ expect(sqlLog[0]).toBe('DELETE FROM "read_users"');
18
+ expect(sqlLog[1]).toBe('DELETE FROM "kumiko_events"');
19
+ });
20
+
21
+ test("clearTables accepts EntityTableMeta-shaped tables", async () => {
22
+ const sqlLog: string[] = [];
23
+ const mockDb = {
24
+ unsafe: async (sql: string) => {
25
+ sqlLog.push(sql);
26
+ return [];
27
+ },
28
+ };
29
+
30
+ const userTable = {
31
+ source: "managed" as const,
32
+ tableName: "read_users",
33
+ columns: [{ name: "id", pgType: "uuid", notNull: true, primaryKey: true }],
34
+ indexes: [],
35
+ };
36
+
37
+ await clearTables(mockDb, [userTable]);
38
+ expect(sqlLog[0]).toBe('DELETE FROM "read_users"');
39
+ });
40
+ });
@@ -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 {
4
4
  createBooleanField,
@@ -1,5 +1,5 @@
1
- import { sql } from "drizzle-orm";
2
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { asRawClient } from "../../db/query";
3
3
  import type { EntityDefinition } from "../../engine/types";
4
4
  import {
5
5
  createTestDb,
@@ -34,8 +34,8 @@ describe("unsafeEnsureEntityTable", () => {
34
34
  test("legt die Tabelle beim ersten Aufruf an (returnt true)", async () => {
35
35
  const created = await unsafeEnsureEntityTable(db.db, tenantEntity, "probe");
36
36
  expect(created).toBe(true);
37
- const rows = await db.db.execute<{ exists: boolean }>(
38
- sql`SELECT to_regclass('public.ensure_entity_table_probe') IS NOT NULL AS exists`,
37
+ const rows = await asRawClient(db.db).unsafe<{ exists: boolean }>(
38
+ `SELECT to_regclass('public.ensure_entity_table_probe') IS NOT NULL AS exists`,
39
39
  );
40
40
  expect(rows[0]?.exists).toBe(true);
41
41
  });
@@ -45,15 +45,8 @@ describe("unsafeEnsureEntityTable", () => {
45
45
  expect(created).toBe(false);
46
46
  });
47
47
 
48
- test("unsafeCreateEntityTable bleibt strict — wirft bei existierender Tabelle", async () => {
49
- // Gleiche Entity zweimal via unsafeCreateEntityTable postgres 42P07
50
- // (relation already exists). Drizzle wrappt den PG-Error in
51
- // DrizzleQueryError; der echte Code steckt in .cause. Sicherstellt,
52
- // dass unsafeEnsureEntityTable nicht versehentlich das strict-Verhalten
53
- // verändert.
54
- await expect(unsafeCreateEntityTable(db.db, tenantEntity, "probe")).rejects.toSatisfy((err) => {
55
- const cause = (err as { cause?: { code?: string } }).cause;
56
- return cause?.code === "42P07";
57
- });
48
+ test("unsafeCreateEntityTable ist idempotentzweiter Push wirft nicht (CREATE IF NOT EXISTS)", async () => {
49
+ // CREATE TABLE IF NOT EXISTS idempotent by design.
50
+ await expect(unsafeCreateEntityTable(db.db, tenantEntity, "probe")).resolves.toBeUndefined();
58
51
  });
59
52
  });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Integration-test DB cleanup — replaces copy-pasted `DELETE FROM …` in
3
+ * beforeEach hooks. All table clears go through typed `deleteMany` (empty
4
+ * where = full table wipe). Raw SQL stays out of test files.
5
+ */
6
+ import { type AnyDb, deleteMany } from "../db/query";
7
+
8
+ const KUMIKO_NAME_SYMBOL = Symbol.for("kumiko:schema:Name");
9
+ const KUMIKO_COLUMNS_SYMBOL = Symbol.for("kumiko:schema:Columns");
10
+
11
+ /** EntityTableMeta, drizzle pgTable, or plain table name string. */
12
+ export type ClearableTable = string | { readonly tableName?: string } | unknown;
13
+
14
+ function tableFromName(name: string): unknown {
15
+ return {
16
+ [KUMIKO_NAME_SYMBOL]: name,
17
+ [KUMIKO_COLUMNS_SYMBOL]: {},
18
+ };
19
+ }
20
+
21
+ function resolveClearableTable(table: ClearableTable): unknown {
22
+ if (typeof table === "string") return tableFromName(table);
23
+ if (
24
+ typeof table === "object" &&
25
+ table !== null &&
26
+ "tableName" in table &&
27
+ typeof (table as { tableName?: unknown }).tableName === "string"
28
+ ) {
29
+ return table;
30
+ }
31
+ return table;
32
+ }
33
+
34
+ /** Delete all rows from each table (order preserved — FK-sensitive callers order explicitly). */
35
+ export async function clearTables(db: AnyDb, tables: readonly ClearableTable[]): Promise<void> {
36
+ for (const table of tables) {
37
+ await deleteMany(db, resolveClearableTable(table), {});
38
+ }
39
+ }
40
+
41
+ /** Alias — same as clearTables, reads better in beforeEach. */
42
+ export async function resetTestTables(db: AnyDb, tables: readonly ClearableTable[]): Promise<void> {
43
+ await clearTables(db, tables);
44
+ }
@@ -1,4 +1,4 @@
1
- import { expect } from "vitest";
1
+ import { expect } from "bun:test";
2
2
  import type { WriteErrorInfo } from "../errors";
3
3
 
4
4
  // Vitest's toContain doesn't operate on plain objects, so after the move from
@@ -5,6 +5,7 @@
5
5
 
6
6
  export { rolesOf } from "./access-assertions";
7
7
  export { expectError, expectSuccess } from "./assertions";
8
+ export { type ClearableTable, clearTables, resetTestTables } from "./db-cleanup";
8
9
  export {
9
10
  type E2EGeneratorOptions,
10
11
  type E2ETestSpec,
@@ -21,6 +22,7 @@ export {
21
22
  type ParsedSetCookie,
22
23
  } from "./http-cookies";
23
24
  export { createLateBoundHolder, type LateBoundHolder } from "./late-bound";
25
+ export { buildMultipartBody, patchFileInstanceofForBunTest } from "./multipart-helper";
24
26
  export {
25
27
  createMutableMasterKeyProvider,
26
28
  type MutableMasterKeyProvider,
@@ -0,0 +1,94 @@
1
+ // Workarounds for Bun v1.3.x bun:test limitations with multipart/form-data.
2
+ //
3
+ // Two bugs combine to break file upload tests in bun:test:
4
+ //
5
+ // 1. Content-Type omission: both app.request({body: formData}) and
6
+ // fetch(url, {body: formData}) stringify FormData via .toString() instead
7
+ // of serializing it as multipart, so no Content-Type header is set.
8
+ // Fix: serialize FormData manually via buildMultipartBody().
9
+ //
10
+ // 2. Cross-realm instanceof: Hono's multipart parser creates Blob objects from
11
+ // a different JS realm than the test globals. In bun:test this means
12
+ // `parsedValue instanceof File` is always false even when the value has all
13
+ // File properties. Fix: patchFilInstanceofForBunTest().
14
+ //
15
+ // Both fixes are test-only — production code and real HTTP clients are unaffected.
16
+
17
+ /**
18
+ * Serializes a FormData instance to multipart/form-data bytes.
19
+ *
20
+ * Returns the encoded body and the Content-Type header value (including the
21
+ * generated boundary). Pass both directly to app.request or fetch.
22
+ */
23
+ export async function buildMultipartBody(
24
+ fd: FormData,
25
+ ): Promise<{ body: BodyInit; contentType: string }> {
26
+ const boundary = `KumikoBnd${Date.now()}${Math.random().toString(36).slice(2, 8)}`;
27
+ const enc = new TextEncoder();
28
+ const parts: Uint8Array[] = [];
29
+
30
+ for (const [name, value] of fd.entries()) {
31
+ const v = value as unknown as {
32
+ name?: string;
33
+ type?: string;
34
+ arrayBuffer?: () => Promise<ArrayBuffer>;
35
+ };
36
+ if (typeof v === "object" && v !== null && typeof v.arrayBuffer === "function") {
37
+ parts.push(
38
+ enc.encode(
39
+ `--${boundary}\r\nContent-Disposition: form-data; name="${name}"; filename="${v.name ?? "blob"}"\r\nContent-Type: ${v.type || "application/octet-stream"}\r\n\r\n`,
40
+ ),
41
+ );
42
+ parts.push(new Uint8Array(await v.arrayBuffer()));
43
+ parts.push(enc.encode("\r\n"));
44
+ } else {
45
+ parts.push(
46
+ enc.encode(
47
+ `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${String(value)}\r\n`,
48
+ ),
49
+ );
50
+ }
51
+ }
52
+ parts.push(enc.encode(`--${boundary}--\r\n`));
53
+
54
+ const total = parts.reduce((s, p) => s + p.length, 0);
55
+ const buf = new Uint8Array(total);
56
+ let off = 0;
57
+ for (const p of parts) {
58
+ buf.set(p, off);
59
+ off += p.length;
60
+ }
61
+ return {
62
+ body: buf as unknown as BodyInit,
63
+ contentType: `multipart/form-data; boundary=${boundary}`,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Patches File[Symbol.hasInstance] so that cross-realm Blob objects returned
69
+ * by Hono's parseBody() pass `instanceof File` checks in bun:test.
70
+ *
71
+ * In bun:test the multipart parser runs in a different JS realm than the test
72
+ * globals, so the Blob/File constructors differ. The patch replaces the
73
+ * prototype-chain check with a duck-type check: an object with string `.name`,
74
+ * number `.size`, and function `.arrayBuffer` is treated as a File.
75
+ *
76
+ * Safe to call multiple times (idempotent via the `_patched` marker).
77
+ */
78
+ export function patchFileInstanceofForBunTest(): void {
79
+ // skip: idempotent re-patch — already installed, nothing to do
80
+ if ((File as unknown as { _kumikoPatched?: boolean })._kumikoPatched) return;
81
+ Object.defineProperty(File, Symbol.hasInstance, {
82
+ value(instance: unknown): boolean {
83
+ if (typeof instance !== "object" || instance === null) return false;
84
+ const f = instance as Record<string, unknown>;
85
+ return (
86
+ typeof f["name"] === "string" &&
87
+ typeof f["size"] === "number" &&
88
+ typeof f["arrayBuffer"] === "function"
89
+ );
90
+ },
91
+ configurable: true,
92
+ });
93
+ (File as unknown as { _kumikoPatched?: boolean })._kumikoPatched = true;
94
+ }
@@ -1,4 +1,4 @@
1
- import { buildDrizzleTable } from "../db/table-builder";
1
+ import { buildEntityTable } from "../db/table-builder";
2
2
  import {
3
3
  createBooleanField,
4
4
  createEntity,
@@ -8,7 +8,7 @@ import {
8
8
 
9
9
  // --- Shared Entity Fixtures -------------------------------------------------
10
10
  //
11
- // Replaces inline `createEntity(...) + buildDrizzleTable(...)` boilerplate
11
+ // Replaces inline `createEntity(...) + buildEntityTable(...)` boilerplate
12
12
  // that appeared in 20+ integration tests. Pick the shape closest to what
13
13
  // the test needs; if a feature needs extras (hooks, state-machine, fields),
14
14
  // keep a local inline entity rather than bloating these shared ones.
@@ -20,7 +20,7 @@ export const sharedWidgetEntity = createEntity({
20
20
  fields: { name: createTextField({ required: true }) },
21
21
  softDelete: true,
22
22
  });
23
- export const sharedWidgetTable = buildDrizzleTable("widget", sharedWidgetEntity);
23
+ export const sharedWidgetTable = buildEntityTable("widget", sharedWidgetEntity);
24
24
 
25
25
  // User with searchable name/email fields. Used by full-stack, cascade,
26
26
  // and any test that exercises search-indexing or field-access on a
@@ -35,7 +35,7 @@ export const sharedUserEntity = createEntity({
35
35
  softDelete: true,
36
36
  searchWeight: 10,
37
37
  });
38
- export const sharedUserTable = buildDrizzleTable("user", sharedUserEntity);
38
+ export const sharedUserTable = buildEntityTable("user", sharedUserEntity);
39
39
 
40
40
  // Item with name + optional price. Used by error-contract, batch,
41
41
  // projection-rebuild — tests that need "a thing you can CRUD".
@@ -46,4 +46,4 @@ export const sharedItemEntity = createEntity({
46
46
  },
47
47
  softDelete: true,
48
48
  });
49
- export const sharedItemTable = buildDrizzleTable("item", sharedItemEntity);
49
+ export const sharedItemTable = buildEntityTable("item", sharedItemEntity);
@@ -2,7 +2,7 @@
2
2
  // Sicher dass nach ensureTemporalPolyfill() die wichtigsten Temporal-Typen
3
3
  // (Instant, PlainDate, ZonedDateTime) konstruktor-fähig sind.
4
4
 
5
- import { describe, expect, test } from "vitest";
5
+ import { describe, expect, test } from "bun:test";
6
6
  import { ensureTemporalPolyfill, getTemporal } from "../polyfill";
7
7
 
8
8
  describe("Temporal Polyfill", () => {
@@ -1,7 +1,7 @@
1
1
  // Unit-Tests für ctx.tz API.
2
2
  // Test-Fokus: korrekte Konvertierung Wall-Clock+TZ ↔ Instant ↔ JSON-Pair.
3
3
 
4
- import { beforeAll, describe, expect, test } from "vitest";
4
+ import { beforeAll, describe, expect, test } from "bun:test";
5
5
  import { ensureTemporalPolyfill } from "../polyfill";
6
6
  import { createTzContext } from "../tz-context";
7
7
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { assertUnreachable } from "../assert";
3
3
 
4
4
  describe("assertUnreachable", () => {
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { readPositiveIntEnv } from "../env-parse";
3
3
 
4
4
  describe("readPositiveIntEnv", () => {