@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
@@ -6,12 +6,12 @@
6
6
  // event-dispatcher — at-least-once delivery, strictly ordered by events.id
7
7
  // per MSP consumer, dead-letters on repeated handler failures.
8
8
 
9
- import { eq, sql } from "drizzle-orm";
10
- import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
9
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
11
10
  import { z } from "zod";
12
11
  import { integer as pgInteger, table as pgTable, uuid as pgUuid } from "../../db/dialect";
13
12
  import { createEventStoreExecutor } from "../../db/event-store-executor";
14
- import { buildDrizzleTable } from "../../db/table-builder";
13
+ import { asRawClient, selectMany } from "../../db/query";
14
+ import { buildEntityTable } from "../../db/table-builder";
15
15
  import { createEntity, createTextField, defineFeature } from "../../engine";
16
16
  import {
17
17
  createTestUser,
@@ -28,13 +28,13 @@ const shipmentEntity = createEntity({
28
28
  table: "read_msp_shipments",
29
29
  fields: { customer: createTextField({ required: true }) },
30
30
  });
31
- const shipmentTable = buildDrizzleTable("msp-shipment", shipmentEntity);
31
+ const shipmentTable = buildEntityTable("msp-shipment", shipmentEntity);
32
32
 
33
33
  const refundEntity = createEntity({
34
34
  table: "read_msp_refunds",
35
35
  fields: { customer: createTextField({ required: true }) },
36
36
  });
37
- const refundTable = buildDrizzleTable("msp-refund", refundEntity);
37
+ const refundTable = buildEntityTable("msp-refund", refundEntity);
38
38
 
39
39
  // Cross-cutting MSP: one row per customer, sums shipments − refunds. Key
40
40
  // differences from a single-stream projection:
@@ -68,41 +68,19 @@ const mspFeature = defineFeature("msptest", (r) => {
68
68
  apply: {
69
69
  [shipmentBilled.name]: async (event, tx) => {
70
70
  const p = event.payload as { customer: string; cents: number };
71
- await tx
72
- .insert(customerBalanceTable)
73
- .values({
74
- customer: p.customer,
75
- tenantId: event.tenantId,
76
- shipments: 1,
77
- refunds: 0,
78
- netCents: p.cents,
79
- })
80
- .onConflictDoUpdate({
81
- target: customerBalanceTable.customer,
82
- set: {
83
- shipments: sql`${customerBalanceTable.shipments} + 1`,
84
- netCents: sql`${customerBalanceTable.netCents} + ${p.cents}`,
85
- },
86
- });
71
+ await asRawClient(tx).unsafe(
72
+ `INSERT INTO "read_msp_customer_balance" (customer, tenant_id, shipments, refunds, net_cents) VALUES ($1::uuid, $2::uuid, 1, 0, $3) ON CONFLICT (customer) DO UPDATE SET shipments = read_msp_customer_balance.shipments + 1, net_cents = read_msp_customer_balance.net_cents + $3`,
73
+ [p.customer, event.tenantId, p.cents],
74
+ );
87
75
  },
88
76
  [refundIssued.name]: async (event, tx) => {
89
77
  const p = event.payload as { customer: string; cents: number };
90
- await tx
91
- .insert(customerBalanceTable)
92
- .values({
93
- customer: p.customer,
94
- tenantId: event.tenantId,
95
- shipments: 0,
96
- refunds: 1,
97
- netCents: -p.cents,
98
- })
99
- .onConflictDoUpdate({
100
- target: customerBalanceTable.customer,
101
- set: {
102
- refunds: sql`${customerBalanceTable.refunds} + 1`,
103
- netCents: sql`${customerBalanceTable.netCents} - ${p.cents}`,
104
- },
105
- });
78
+ // -$3 ohne cast wird vom planner als "unary minus on unknown type"
79
+ // mehrdeutig — wir casten $3 explizit auf integer.
80
+ await asRawClient(tx).unsafe(
81
+ `INSERT INTO "read_msp_customer_balance" (customer, tenant_id, shipments, refunds, net_cents) VALUES ($1::uuid, $2::uuid, 0, 1, -$3::integer) ON CONFLICT (customer) DO UPDATE SET refunds = read_msp_customer_balance.refunds + 1, net_cents = read_msp_customer_balance.net_cents - $3::integer`,
82
+ [p.customer, event.tenantId, p.cents],
83
+ );
106
84
  },
107
85
  },
108
86
  });
@@ -207,10 +185,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
207
185
  // Drain the dispatcher — MSPs run async.
208
186
  await stack.eventDispatcher?.runOnce();
209
187
 
210
- const rows = await stack.db
211
- .select()
212
- .from(customerBalanceTable)
213
- .orderBy(customerBalanceTable.customer);
188
+ const rows = await selectMany(stack.db, customerBalanceTable);
214
189
  const byCustomer = new Map(rows.map((r) => [r.customer, r]));
215
190
 
216
191
  expect(byCustomer.get(customerA)).toMatchObject({
@@ -239,10 +214,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
239
214
  expect(pass2?.byConsumer[mspName]?.processed ?? 0).toBe(0);
240
215
 
241
216
  // Row state is stable across the no-op pass.
242
- const [row] = await stack.db
243
- .select()
244
- .from(customerBalanceTable)
245
- .where(eq(customerBalanceTable.customer, cust));
217
+ const [row] = await selectMany(stack.db, customerBalanceTable, { customer: cust });
246
218
  expect(row?.shipments).toBe(1);
247
219
  expect(row?.netCents).toBe(42);
248
220
  });
@@ -273,10 +245,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
273
245
  );
274
246
  await stack.eventDispatcher?.runOnce();
275
247
 
276
- const rows = await stack.db
277
- .select()
278
- .from(customerBalanceTable)
279
- .orderBy(customerBalanceTable.customer);
248
+ const rows = await selectMany(stack.db, customerBalanceTable);
280
249
  const alpha = rows.find((r) => r.customer === customerAlpha);
281
250
  const beta = rows.find((r) => r.customer === customerBeta);
282
251
 
@@ -298,10 +267,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
298
267
 
299
268
  // Only the shipment-billed event was folded in; the auto "created"
300
269
  // event was silently skipped.
301
- const [row] = await stack.db
302
- .select()
303
- .from(customerBalanceTable)
304
- .where(eq(customerBalanceTable.customer, cust));
270
+ const [row] = await selectMany(stack.db, customerBalanceTable, { customer: cust });
305
271
  expect(row?.shipments).toBe(1);
306
272
  });
307
273
  });
@@ -13,27 +13,25 @@
13
13
  // jitter does not. If this ever flakes in CI, drop to 3000 — the goal is
14
14
  // "catastrophic regression detector", not "perf SLO".
15
15
 
16
- import { sql } from "drizzle-orm";
17
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
18
- import {
19
- integer as drizzleInteger,
20
- table as drizzlePgTable,
21
- uuid as drizzleUuid,
22
- } from "../../db/dialect";
16
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
17
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
18
+ import { integer, table as pgTable, uuid as pgUuid } from "../../db/dialect";
19
+ import { asRawClient } from "../../db/query";
23
20
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
24
21
  import type { ProjectionDefinition } from "../../engine/types";
25
22
  import { createEventsTable } from "../../event-store";
26
23
  import { createProjectionStateTable, rebuildProjection } from "../../pipeline";
27
- import { createTestDb, type TestDb, TestUsers, unsafePushTables } from "../../stack";
24
+ import { TestUsers, unsafePushTables } from "../../stack";
25
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
28
26
  import { generateId as uuid } from "../../utils";
29
27
 
30
28
  // Counter projection: every task.created bumps a counter, every
31
29
  // task.updated is a no-op. Enough to exercise the apply path —
32
30
  // rebuild cost is dominated by event iteration + apply dispatch,
33
31
  // not the projection state shape.
34
- const taskCountTable = drizzlePgTable("read_perf_rebuild_task_count", {
35
- tenantId: drizzleUuid("tenant_id").primaryKey(),
36
- count: drizzleInteger("count").notNull().default(0),
32
+ const taskCountTable = pgTable("read_perf_rebuild_task_count", {
33
+ tenantId: pgUuid("tenant_id").primaryKey(),
34
+ count: integer("count").notNull().default(0),
37
35
  });
38
36
 
39
37
  const taskCountProjection: ProjectionDefinition = {
@@ -42,13 +40,10 @@ const taskCountProjection: ProjectionDefinition = {
42
40
  table: taskCountTable,
43
41
  apply: {
44
42
  "task.created": async (event, tx) => {
45
- await tx
46
- .insert(taskCountTable)
47
- .values({ tenantId: event.tenantId, count: 1 })
48
- .onConflictDoUpdate({
49
- target: taskCountTable.tenantId,
50
- set: { count: sql`${taskCountTable.count} + 1` },
51
- });
43
+ await asRawClient(tx).unsafe(
44
+ `INSERT INTO "read_perf_rebuild_task_count" (tenant_id, count) VALUES ($1, 1) ON CONFLICT (tenant_id) DO UPDATE SET count = read_perf_rebuild_task_count.count + 1`,
45
+ [event.tenantId],
46
+ );
52
47
  },
53
48
  "task.updated": async (_event, _tx) => {
54
49
  // No-op apply — measuring event-iteration overhead, not per-event
@@ -69,11 +64,12 @@ const feature = defineFeature("perfrebuild", (r) => {
69
64
  });
70
65
 
71
66
  const admin = TestUsers.admin;
72
- let testDb: TestDb;
67
+ let testDb: BunTestDb;
73
68
  const registry = createRegistry([feature]);
74
69
  const qualifiedProjectionName = "perfrebuild:projection:task-count";
75
70
 
76
71
  beforeAll(async () => {
72
+ await ensureTemporalPolyfill();
77
73
  testDb = await createTestDb();
78
74
  await createEventsTable(testDb.db);
79
75
  await createProjectionStateTable(testDb.db);
@@ -85,8 +81,8 @@ afterAll(async () => {
85
81
  });
86
82
 
87
83
  beforeEach(async () => {
88
- await testDb.db.execute(
89
- sql`TRUNCATE kumiko_events, read_perf_rebuild_task_count, kumiko_projections RESTART IDENTITY CASCADE`,
84
+ await asRawClient(testDb.db).unsafe(
85
+ `TRUNCATE kumiko_events, read_perf_rebuild_task_count, kumiko_projections RESTART IDENTITY CASCADE`,
90
86
  );
91
87
  });
92
88
 
@@ -96,25 +92,31 @@ beforeEach(async () => {
96
92
  async function seedEvents(count: number, depth: number): Promise<void> {
97
93
  const userId = uuid();
98
94
  // v1 creates
99
- await testDb.db.execute(sql`
95
+ await asRawClient(testDb.db).unsafe(
96
+ `
100
97
  INSERT INTO kumiko_events (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
101
- SELECT gen_random_uuid(), 'task', ${admin.tenantId}::uuid, 1, 'task.created',
98
+ SELECT gen_random_uuid(), 'task', $1::uuid, 1, 'task.created',
102
99
  jsonb_build_object('title', 'Task ' || gs.n),
103
- jsonb_build_object('userId', ${userId}::text),
104
- ${userId}::text
105
- FROM generate_series(1, ${count}) AS gs(n);
106
- `);
100
+ jsonb_build_object('userId', $2::text),
101
+ $3::text
102
+ FROM generate_series(1, $4) AS gs(n);
103
+ `,
104
+ [admin.tenantId, userId, userId, count],
105
+ );
107
106
  // v2..depth updates
108
107
  for (let v = 2; v <= depth; v++) {
109
- await testDb.db.execute(sql`
108
+ await asRawClient(testDb.db).unsafe(
109
+ `
110
110
  INSERT INTO kumiko_events (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
111
- SELECT e.aggregate_id, 'task', ${admin.tenantId}::uuid, ${v}, 'task.updated',
112
- jsonb_build_object('title', 'Task v' || ${v}),
113
- jsonb_build_object('userId', ${userId}::text),
114
- ${userId}::text
111
+ SELECT e.aggregate_id, 'task', $1::uuid, $2, 'task.updated',
112
+ jsonb_build_object('title', 'Task v' || $3),
113
+ jsonb_build_object('userId', $4::text),
114
+ $5::text
115
115
  FROM kumiko_events e
116
- WHERE e.aggregate_type = 'task' AND e.version = ${v - 1};
117
- `);
116
+ WHERE e.aggregate_type = 'task' AND e.version = $6;
117
+ `,
118
+ [admin.tenantId, v, v, userId, userId, v - 1],
119
+ );
118
120
  }
119
121
  }
120
122
 
@@ -9,7 +9,7 @@
9
9
  // Memory `feedback_no_fake_dispatcher`: real HTTP-Calls via setupTestStack,
10
10
  // nicht createTestDispatcher.
11
11
 
12
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
12
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
13
13
  import { z } from "zod";
14
14
  import { createEntity, createTextField, defineFeature } from "../../engine";
15
15
  import type { PostQueryHookFn } from "../../engine/types";
@@ -10,15 +10,11 @@
10
10
  // - status lifecycle (idle → rebuilding → idle on success, → failed on throw)
11
11
  // - never-rebuilt projection has sensible default state
12
12
 
13
- import { eq, sql } from "drizzle-orm";
14
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
15
- import {
16
- integer as drizzleInteger,
17
- table as drizzlePgTable,
18
- uuid as drizzleUuid,
19
- } from "../../db/dialect";
13
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
14
+ import { integer, table as pgTable, uuid } from "../../db/dialect";
20
15
  import { createEventStoreExecutor } from "../../db/event-store-executor";
21
- import { buildDrizzleTable } from "../../db/table-builder";
16
+ import { asRawClient, insertOne, selectMany } from "../../db/query";
17
+ import { buildEntityTable } from "../../db/table-builder";
22
18
  import { createTenantDb, type TenantDb } from "../../db/tenant-db";
23
19
  import {
24
20
  createEntity,
@@ -54,23 +50,19 @@ const itemEntity = createEntity({
54
50
  },
55
51
  softDelete: true,
56
52
  });
57
- const itemTable = buildDrizzleTable("rebuild-item", itemEntity);
53
+ const itemTable = buildEntityTable("rebuild-item", itemEntity);
58
54
 
59
- const itemsPerGroupTable = drizzlePgTable("read_rebuild_items_per_group", {
60
- groupId: drizzleUuid("group_id").primaryKey(),
61
- tenantId: drizzleUuid("tenant_id").notNull(),
62
- itemCount: drizzleInteger("item_count").notNull().default(0),
55
+ const itemsPerGroupTable = pgTable("read_rebuild_items_per_group", {
56
+ groupId: uuid("group_id").primaryKey(),
57
+ tenantId: uuid("tenant_id").notNull(),
58
+ itemCount: integer("item_count").notNull().default(0),
63
59
  });
64
60
 
65
61
  async function bump(tx: unknown, groupId: string, tenantId: string, delta: number): Promise<void> {
66
- // biome-ignore lint/suspicious/noExplicitAny: tx is DbRunner
67
- await (tx as any)
68
- .insert(itemsPerGroupTable)
69
- .values({ groupId, tenantId, itemCount: delta })
70
- .onConflictDoUpdate({
71
- target: itemsPerGroupTable.groupId,
72
- set: { itemCount: sql`${itemsPerGroupTable.itemCount} + ${delta}` },
73
- });
62
+ await asRawClient(tx).unsafe(
63
+ `INSERT INTO "read_rebuild_items_per_group" (group_id, tenant_id, item_count) VALUES ($1::uuid, $2::uuid, $3) ON CONFLICT (group_id) DO UPDATE SET item_count = read_rebuild_items_per_group.item_count + $3`,
64
+ [groupId, tenantId, delta],
65
+ );
74
66
  }
75
67
 
76
68
  type ItemCreated = { groupId: string };
@@ -121,8 +113,8 @@ afterAll(async () => {
121
113
  });
122
114
 
123
115
  beforeEach(async () => {
124
- await testDb.db.execute(
125
- sql`TRUNCATE kumiko_events, read_rebuild_items, read_rebuild_items_per_group, kumiko_projections RESTART IDENTITY CASCADE`,
116
+ await asRawClient(testDb.db).unsafe(
117
+ `TRUNCATE kumiko_events, read_rebuild_items, read_rebuild_items_per_group, kumiko_projections RESTART IDENTITY CASCADE`,
126
118
  );
127
119
  });
128
120
 
@@ -138,10 +130,7 @@ async function appendCreatedEvent(groupId: string, name: string): Promise<void>
138
130
  }
139
131
 
140
132
  async function getCount(groupId: string): Promise<number | undefined> {
141
- const [row] = await testDb.db
142
- .select()
143
- .from(itemsPerGroupTable)
144
- .where(eq(itemsPerGroupTable.groupId, groupId));
133
+ const [row] = await selectMany(testDb.db, itemsPerGroupTable, { groupId: groupId });
145
134
  return row?.itemCount;
146
135
  }
147
136
 
@@ -174,9 +163,11 @@ describe("rebuildProjection — happy path", () => {
174
163
  await appendCreatedEvent(group, "b");
175
164
 
176
165
  // Seed the projection table with a stale/wrong value.
177
- await testDb.db
178
- .insert(itemsPerGroupTable)
179
- .values({ groupId: group, tenantId: admin.tenantId, itemCount: 999 });
166
+ await insertOne(testDb.db, itemsPerGroupTable, {
167
+ groupId: group,
168
+ tenantId: admin.tenantId,
169
+ itemCount: 999,
170
+ });
180
171
 
181
172
  const result = await rebuildProjection(qualifiedProjectionName, {
182
173
  db: testDb.db,
@@ -3,7 +3,7 @@
3
3
  // drizzle-tables directly. Auto-filters by tenant_id when the projection
4
4
  // table carries that column.
5
5
 
6
- import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
6
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
7
7
  import { z } from "zod";
8
8
  import {
9
9
  integer as pgInteger,
@@ -12,7 +12,8 @@ import {
12
12
  uuid as pgUuid,
13
13
  } from "../../db/dialect";
14
14
  import { createEventStoreExecutor } from "../../db/event-store-executor";
15
- import { buildDrizzleTable } from "../../db/table-builder";
15
+ import { insertOne } from "../../db/query";
16
+ import { buildEntityTable } from "../../db/table-builder";
16
17
  import { createEntity, createTextField, defineFeature } from "../../engine";
17
18
  import {
18
19
  resetEventStore,
@@ -26,7 +27,7 @@ const widgetEntity = createEntity({
26
27
  table: "read_qp_widgets",
27
28
  fields: { name: createTextField({ required: true }) },
28
29
  });
29
- const widgetTable = buildDrizzleTable("qp-widget", widgetEntity);
30
+ const widgetTable = buildEntityTable("qp-widget", widgetEntity);
30
31
 
31
32
  // Tenant-scoped projection — auto-filter by tenant_id.
32
33
  const tenantScopedTable = pgTable("read_qp_widget_count_tenant", {
@@ -52,7 +53,7 @@ const qpFeature = defineFeature("qp", (r) => {
52
53
  apply: {
53
54
  "qp-widget.created": async (event, tx) => {
54
55
  const p = event.payload as { name?: string };
55
- await tx.insert(tenantScopedTable).values({
56
+ await insertOne(tx, tenantScopedTable, {
56
57
  widgetId: event.aggregateId,
57
58
  tenantId: event.tenantId,
58
59
  label: p.name ?? "?",
@@ -68,7 +69,7 @@ const qpFeature = defineFeature("qp", (r) => {
68
69
  apply: {
69
70
  "qp-widget.created": async (event, tx) => {
70
71
  const p = event.payload as { name?: string };
71
- await tx.insert(systemScopedTable).values({
72
+ await insertOne(tx, systemScopedTable, {
72
73
  widgetId: event.aggregateId,
73
74
  label: p.name ?? "?",
74
75
  });
@@ -0,0 +1,12 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { RedisKeys } from "../redis-keys";
3
+
4
+ describe("RedisKeys", () => {
5
+ test("uses unique kumiko-prefixed namespaces", () => {
6
+ const values = Object.values(RedisKeys);
7
+ expect(new Set(values).size).toBe(values.length);
8
+ for (const key of values) {
9
+ expect(key.startsWith("kumiko:")).toBe(true);
10
+ }
11
+ });
12
+ });
@@ -1,5 +1,6 @@
1
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { createTestRedis, type TestRedis } from "../../stack";
3
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
3
4
  import { createEntityCache } from "../entity-cache";
4
5
  import { createEventDedup } from "../event-dedup";
5
6
  import { createIdempotencyGuard } from "../idempotency";
@@ -7,6 +8,7 @@ import { createIdempotencyGuard } from "../idempotency";
7
8
  let testRedis: TestRedis;
8
9
 
9
10
  beforeAll(async () => {
11
+ await ensureTemporalPolyfill();
10
12
  testRedis = await createTestRedis();
11
13
  });
12
14
 
@@ -1,5 +1,5 @@
1
- import { eq } from "drizzle-orm";
2
1
  import type { TableColumns } from "../db/dialect";
2
+ import { deleteMany, fetchOne, updateMany } from "../db/query";
3
3
  import { OnDeleteStrategies, SystemHookNames, SystemHookPriorities } from "../engine/constants";
4
4
  import type { PreDeleteHookFn, Registry } from "../engine/types";
5
5
  import { ConflictError, FrameworkReasons } from "../errors";
@@ -44,12 +44,8 @@ export function createCascadeDeleteHook(
44
44
  if (!targetTable) continue;
45
45
 
46
46
  if (strategy === OnDeleteStrategies.restrict) {
47
- const rows = await db
48
- .select({ id: targetTable["id"] })
49
- .from(targetTable)
50
- .where(eq(targetTable[relation.foreignKey], payload.id))
51
- .limit(1);
52
- if (rows.length > 0) {
47
+ const row = await fetchOne(db, targetTable, { [relation.foreignKey]: payload.id });
48
+ if (row) {
53
49
  throw new ConflictError({
54
50
  message: `${relation.target} has records referencing ${entityName}#${payload.id}`,
55
51
  i18nKey: "errors.deleteRestricted",
@@ -64,14 +60,16 @@ export function createCascadeDeleteHook(
64
60
  }
65
61
 
66
62
  if (strategy === OnDeleteStrategies.cascade) {
67
- await db.delete(targetTable).where(eq(targetTable[relation.foreignKey], payload.id));
63
+ await deleteMany(db, targetTable, { [relation.foreignKey]: payload.id });
68
64
  }
69
65
 
70
66
  if (strategy === OnDeleteStrategies.setNull) {
71
- await db
72
- .update(targetTable)
73
- .set({ [relation.foreignKey]: null })
74
- .where(eq(targetTable[relation.foreignKey], payload.id));
67
+ await updateMany(
68
+ db,
69
+ targetTable,
70
+ { [relation.foreignKey]: null },
71
+ { [relation.foreignKey]: payload.id },
72
+ );
75
73
  }
76
74
  }
77
75
 
@@ -79,17 +77,11 @@ export function createCascadeDeleteHook(
79
77
  const throughTable = tables.get(relation.through.table);
80
78
  if (!throughTable) continue;
81
79
  // sourceKey points at the owner side (the entity being deleted).
82
- // targetKey would point at the other side — filtering by it here
83
- // would miss every through-row for this entity.
84
80
  const sourceKey = relation.through.sourceKey;
85
81
 
86
82
  if (strategy === OnDeleteStrategies.restrict) {
87
- const rows = await db
88
- .select({ id: throughTable["id"] })
89
- .from(throughTable)
90
- .where(eq(throughTable[sourceKey], payload.id))
91
- .limit(1);
92
- if (rows.length > 0) {
83
+ const row = await fetchOne(db, throughTable, { [sourceKey]: payload.id });
84
+ if (row) {
93
85
  throw new ConflictError({
94
86
  message: `${relation.through.table} has records referencing ${entityName}#${payload.id}`,
95
87
  i18nKey: "errors.deleteRestricted",
@@ -104,7 +96,7 @@ export function createCascadeDeleteHook(
104
96
  }
105
97
 
106
98
  if (strategy === OnDeleteStrategies.cascade) {
107
- await db.delete(throughTable).where(eq(throughTable[sourceKey], payload.id));
99
+ await deleteMany(db, throughTable, { [sourceKey]: payload.id });
108
100
  }
109
101
  }
110
102
  }
@@ -6,7 +6,13 @@ import type {
6
6
  SessionUser,
7
7
  WriteResult,
8
8
  } from "../engine/types";
9
- import { InternalError, isKumikoError, type KumikoError, type WriteErrorInfo } from "../errors";
9
+ import {
10
+ type FieldIssue,
11
+ InternalError,
12
+ isKumikoError,
13
+ type KumikoError,
14
+ type WriteErrorInfo,
15
+ } from "../errors";
10
16
 
11
17
  export type FailedWriteResult = Extract<WriteResult, { isSuccess: false }>;
12
18
 
@@ -146,12 +152,7 @@ export function prefixValidationPath(info: WriteErrorInfo, prefix: string): Writ
146
152
  if (info.code !== "validation_error") return info;
147
153
  const details = info.details as // @cast-boundary error-details
148
154
  | {
149
- fields?: readonly {
150
- path: string;
151
- code: string;
152
- i18nKey: string;
153
- params?: Readonly<Record<string, unknown>>;
154
- }[];
155
+ fields?: readonly FieldIssue[];
155
156
  }
156
157
  | undefined;
157
158
  const fields = details?.fields;