@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
@@ -5,8 +5,8 @@
5
5
  // pinned by parse.test.ts + render-roundtrip.test.ts) so a failure
6
6
  // here narrows the cause to patch.ts itself.
7
7
 
8
+ import { describe, expect, test } from "bun:test";
8
9
  import { Project, type SourceFile } from "ts-morph";
9
- import { describe, expect, test } from "vitest";
10
10
  import { parseSourceFile } from "../parse";
11
11
  import { addPattern, applyChanges, type PatternId, removePattern, replacePattern } from "../patch";
12
12
  import { createFeaturePatcher } from "../patcher";
@@ -8,8 +8,8 @@
8
8
  // arg layout the AI/Designer provides and creates a syntactically valid
9
9
  // pattern.
10
10
 
11
+ import { describe, expect, test } from "bun:test";
11
12
  import { Project, type SourceFile } from "ts-morph";
12
- import { describe, expect, test } from "vitest";
13
13
  import { parseSourceFile } from "../parse";
14
14
  import { createFeaturePatcher } from "../patcher";
15
15
 
@@ -8,8 +8,8 @@
8
8
  // (where the pattern came from) and the rendered file (where it ends
9
9
  // up at canonical positions). We compare every other field.
10
10
 
11
+ import { describe, expect, test } from "bun:test";
11
12
  import { Project } from "ts-morph";
12
- import { describe, expect, test } from "vitest";
13
13
  import { parseSourceFile } from "../parse";
14
14
  import type { FeaturePattern } from "../patterns";
15
15
  import { renderFeatureFile, renderPattern } from "../render";
@@ -7,8 +7,8 @@
7
7
  // AST extractor + the Designer/AI consumers (renderPattern,
8
8
  // getEditability, PATTERN_LIBRARY). Both halves must agree on shape.
9
9
 
10
+ import { describe, expect, test } from "bun:test";
10
11
  import { Project } from "ts-morph";
11
- import { describe, expect, test } from "vitest";
12
12
  import { parseSourceFile } from "../parse";
13
13
  import { getEditability } from "../patterns";
14
14
  import { renderPattern } from "../render";
@@ -1,5 +1,6 @@
1
1
  import type { CallExpression, Node } from "ts-morph";
2
2
  import { SyntaxKind } from "ts-morph";
3
+ import { isPlainObject } from "../../../utils/is-plain-object";
3
4
  import type { ParseError } from "../parse";
4
5
 
5
6
  export type ExtractOutput<TPattern> =
@@ -107,9 +108,7 @@ export function readDataLiteralNode(node: Node): unknown {
107
108
  }
108
109
  }
109
110
 
110
- export function isPlainObject(value: unknown): value is Record<string, unknown> {
111
- return typeof value === "object" && value !== null && !Array.isArray(value);
112
- }
111
+ export { isPlainObject } from "../../../utils/is-plain-object";
113
112
 
114
113
  export function readPropertyKey(propAssign: import("ts-morph").PropertyAssignment): string {
115
114
  const nameNode = propAssign.getNameNode();
@@ -12,14 +12,22 @@
12
12
  // { from: "claim:<featureQn>",
13
13
  // column?: "..." } → row[column ?? claim.shortName] === user.claims[claim.qn]
14
14
  // (string[] claim → inArray)
15
- // { where: (user, table) => SQL } → escape hatch, arbitrary Drizzle predicate
15
+ // { where: (user, ctx) => SqlFragment } → escape hatch, raw parameterised SQL
16
16
  //
17
17
  // Construction: use the `from(ref, column?)` helper. It returns a FromRule
18
18
  // ready to drop into an access map.
19
19
 
20
- import { eq, inArray, or, type SQL, sql } from "drizzle-orm";
20
+ import { toSnakeCase } from "../db/table-builder";
21
21
  import type { SessionUser } from "./types";
22
22
 
23
+ // Parameterised SQL fragment — produced by buildOwnershipClause + by the
24
+ // WhereRule escape-hatch. Caller weaves `sqlText` into a larger statement,
25
+ // renumbering placeholders if needed (FragmentBuilder below).
26
+ export type SqlFragment = {
27
+ readonly sqlText: string;
28
+ readonly params: readonly unknown[];
29
+ };
30
+
23
31
  // Reference spec supported by `from()`:
24
32
  // "user:id" → user.id
25
33
  // "user:tenantId" → user.tenantId (rarely needed — TenantDb scopes anyway)
@@ -49,9 +57,18 @@ export type FromRule = {
49
57
  readonly column: string;
50
58
  };
51
59
 
60
+ // Context passed to a WhereRule escape-hatch. The author returns a SqlFragment
61
+ // whose placeholders start at `paramStart` ($N, $N+1, ...); the framework
62
+ // concatenates the fragment into the larger query.
63
+ export type WhereRuleContext<TTable = unknown> = {
64
+ readonly table: TTable;
65
+ readonly tableName: string;
66
+ readonly paramStart: number;
67
+ };
68
+
52
69
  export type WhereRule<TTable = unknown> = {
53
70
  readonly kind: "where";
54
- readonly where: (user: SessionUser, table: TTable) => SQL;
71
+ readonly where: (user: SessionUser, ctx: WhereRuleContext<TTable>) => SqlFragment;
55
72
  };
56
73
 
57
74
  // "all" collapses to a primitive so map authors can write `Admin: "all"`
@@ -244,51 +261,94 @@ export function userCanCreateFieldRow(
244
261
  // "empty" → user has a role mapped but no rule accepts any row (missing
245
262
  // claim, empty array, role not in map). Skip the DB call entirely
246
263
  // — returning [] is equivalent and avoids a pointless roundtrip.
247
- // "sql" → apply `.sql` as an AND to the query's where clause.
264
+ // "sql" → apply the parameterised fragment as an AND on the query.
265
+ // Caller is responsible for renumbering placeholders when
266
+ // concatenating with other fragments (see `shiftParams` below).
248
267
  //
249
268
  // "empty" vs. "pass" is the critical distinction for a safe default:
250
- // undefined/pass = allow, empty = deny-by-construction. Mixing them up was
251
- // the exact leak direction advisor flagged; the disjoint type prevents it.
269
+ // undefined/pass = allow, empty = deny-by-construction.
252
270
  export type OwnershipClause =
253
271
  | { readonly kind: "pass" }
254
272
  | { readonly kind: "empty" }
255
- | { readonly kind: "sql"; readonly sql: SQL };
273
+ | { readonly kind: "sql"; readonly sqlText: string; readonly params: readonly unknown[] };
256
274
 
257
275
  const PASS_CLAUSE: OwnershipClause = { kind: "pass" };
258
276
  const EMPTY_CLAUSE: OwnershipClause = { kind: "empty" };
259
277
 
260
- // Build an ownership clause for entity-level READ access. The caller
261
- // translates the result to its query layer (see above).
278
+ const KUMIKO_NAME_SYMBOL = Symbol.for("kumiko:schema:Name");
279
+ const KUMIKO_COLUMNS_SYMBOL = Symbol.for("kumiko:schema:Columns");
280
+
281
+ function tableNameOf(table: unknown): string {
282
+ if (table !== null && typeof table === "object") {
283
+ const sym = (table as Record<symbol, unknown>)[KUMIKO_NAME_SYMBOL];
284
+ if (typeof sym === "string") return sym;
285
+ }
286
+ return "<unknown>";
287
+ }
288
+
289
+ // Resolve a JS-field name on the table to its underlying SQL column name.
290
+ // Drizzle tables carry the mapping under Symbol.for("kumiko:schema:Columns");
291
+ // we read it without importing drizzle-orm at runtime.
292
+ function columnSqlName(table: unknown, field: string): string | null {
293
+ if (table === null || typeof table !== "object") return null;
294
+ const cols = (table as Record<symbol, unknown>)[KUMIKO_COLUMNS_SYMBOL];
295
+ if (cols && typeof cols === "object") {
296
+ const col = (cols as Record<string, unknown>)[field];
297
+ if (col && typeof col === "object") {
298
+ const nameVal = (col as Record<string, unknown>)["name"];
299
+ if (typeof nameVal === "string") return nameVal;
300
+ }
301
+ }
302
+ // Field may already be the SQL column name on plain objects (tests, etc.).
303
+ if ((table as Record<string, unknown>)[field] !== undefined) {
304
+ return toSnakeCase(field);
305
+ }
306
+ return null;
307
+ }
308
+
309
+ function quoteIdent(name: string): string {
310
+ return `"${name.replace(/"/g, '""')}"`;
311
+ }
312
+
313
+ // Shift `$N` placeholder numbers in an embedded fragment so they line up
314
+ // with the outer query's param array.
315
+ export function shiftParams(fragment: SqlFragment, shift: number): SqlFragment {
316
+ if (shift === 0) return fragment;
317
+ const sqlText = fragment.sqlText.replace(/\$(\d+)/g, (_, num) => `$${Number(num) + shift}`);
318
+ return { sqlText, params: fragment.params };
319
+ }
320
+
321
+ // Build an ownership clause for entity-level READ access. Caller weaves
322
+ // the result into a raw-SQL WHERE (see event-store-executor list/getById).
262
323
  //
263
- // `table` is the Drizzle table with column objects. Unknown column on a
264
- // from-rule is a boot-time misconfiguration; at request time we treat it
265
- // as empty (safe default) rather than passing silently.
324
+ // `table` is the (drizzle or compatible) table object; we extract column
325
+ // SQL names via the kumiko:schema:Columns symbol. Unknown column on a from-rule
326
+ // is a boot-time misconfiguration; at request time we treat it as empty
327
+ // (safe default) rather than passing silently.
266
328
  export function buildOwnershipClause(
267
329
  user: SessionUser,
268
330
  accessMap: OwnershipMap | undefined,
269
- // biome-ignore lint/suspicious/noExplicitAny: Drizzle tables carry schema-dependent column shapes
270
- table: any,
331
+ table: unknown,
332
+ paramStart = 1,
271
333
  ): OwnershipClause {
272
334
  if (!accessMap || Object.keys(accessMap).length === 0) return PASS_CLAUSE;
273
335
 
274
- const clauses: SQL[] = [];
336
+ const clauses: SqlFragment[] = [];
275
337
  let anyRoleMatched = false;
276
338
  let everyRuleCollapsedToEmpty = true;
339
+ let nextParamIdx = paramStart;
277
340
 
278
341
  for (const role of user.roles) {
279
342
  const rule = accessMap[role];
280
343
  if (!rule) continue;
281
344
  anyRoleMatched = true;
282
- // "all" = no filter at all for this role; short-circuit.
283
345
  if (rule === "all") return PASS_CLAUSE;
284
- const resolved = ruleToClause(rule, user, table);
346
+ const resolved = ruleToFragment(rule, user, table, nextParamIdx);
285
347
  if (resolved.kind === "sql") {
286
- clauses.push(resolved.sql);
348
+ clauses.push({ sqlText: resolved.sqlText, params: resolved.params });
349
+ nextParamIdx += resolved.params.length;
287
350
  everyRuleCollapsedToEmpty = false;
288
351
  }
289
- // "empty" contribution from one role doesn't short-circuit: another
290
- // role might still contribute an OR-branch. But if ALL branches are
291
- // empty, the result is empty.
292
352
  }
293
353
 
294
354
  if (!anyRoleMatched) return EMPTY_CLAUSE;
@@ -296,42 +356,54 @@ export function buildOwnershipClause(
296
356
  if (clauses.length === 1) {
297
357
  const only = clauses[0];
298
358
  if (!only) return EMPTY_CLAUSE;
299
- return { kind: "sql", sql: only };
359
+ return { kind: "sql", sqlText: `(${only.sqlText})`, params: only.params };
300
360
  }
301
- // @cast-boundary db-operator drizzle or() widened signature
302
- // biome-ignore lint/suspicious/noExplicitAny: same reason as above
303
- const combined = or(...(clauses as any)) as SQL;
304
- return { kind: "sql", sql: combined };
361
+ const sqlText = clauses.map((c) => `(${c.sqlText})`).join(" OR ");
362
+ const params: unknown[] = [];
363
+ for (const c of clauses) for (const p of c.params) params.push(p);
364
+ return { kind: "sql", sqlText: `(${sqlText})`, params };
305
365
  }
306
366
 
307
- type RuleClauseResult = { readonly kind: "empty" } | { readonly kind: "sql"; readonly sql: SQL };
367
+ type RuleFragmentResult =
368
+ | { readonly kind: "empty" }
369
+ | { readonly kind: "sql"; readonly sqlText: string; readonly params: readonly unknown[] };
308
370
 
309
- function ruleToClause(
371
+ function ruleToFragment(
310
372
  rule: OwnershipRule,
311
373
  user: SessionUser,
312
- // biome-ignore lint/suspicious/noExplicitAny: Drizzle tables carry schema-dependent column shapes
313
- table: any,
314
- ): RuleClauseResult {
374
+ table: unknown,
375
+ paramStart: number,
376
+ ): RuleFragmentResult {
315
377
  if (rule === "all") {
316
- // Caller handles "all" by short-circuit before reaching here; defensive
317
- // fallback.
318
- return { kind: "sql", sql: sql`true` };
378
+ return { kind: "sql", sqlText: "TRUE", params: [] };
319
379
  }
320
380
  if (rule.kind === "where") {
321
- return { kind: "sql", sql: rule.where(user, table) };
381
+ const frag = rule.where(user, {
382
+ table,
383
+ tableName: tableNameOf(table),
384
+ paramStart,
385
+ });
386
+ return { kind: "sql", sqlText: frag.sqlText, params: frag.params };
322
387
  }
323
388
  // FromRule
324
- const column = table[rule.column];
325
- // Unknown column boot validator should have caught this, but at request
326
- // time we treat as empty (fail-closed).
327
- if (!column) return { kind: "empty" };
389
+ const colName = columnSqlName(table, rule.column);
390
+ if (!colName) return { kind: "empty" };
328
391
 
329
392
  const value = resolveUserValue(rule, user);
330
393
  if (value === undefined || value === null) return { kind: "empty" };
331
394
 
332
395
  if (Array.isArray(value)) {
333
396
  if (value.length === 0) return { kind: "empty" };
334
- return { kind: "sql", sql: inArray(column, value) };
397
+ const placeholders = value.map((_, i) => `$${paramStart + i}`).join(", ");
398
+ return {
399
+ kind: "sql",
400
+ sqlText: `${quoteIdent(colName)} IN (${placeholders})`,
401
+ params: value,
402
+ };
335
403
  }
336
- return { kind: "sql", sql: eq(column, value) };
404
+ return {
405
+ kind: "sql",
406
+ sqlText: `${quoteIdent(colName)} = $${paramStart}`,
407
+ params: [value],
408
+ };
337
409
  }
@@ -7,7 +7,7 @@
7
7
  // sample pattern (sanity check that entity-fields-editor points at
8
8
  // `definition.fields`, not `fields`).
9
9
 
10
- import { describe, expect, expectTypeOf, test } from "vitest";
10
+ import { describe, expect, expectTypeOf, test } from "bun:test";
11
11
  import {
12
12
  type FeaturePattern,
13
13
  type FeaturePatternKind,
@@ -24,7 +24,7 @@ import {
24
24
  // All FeaturePatternKind discriminator values, hand-listed so the test
25
25
  // fails CI when a new pattern is added without a library entry. Match
26
26
  // against the FeaturePattern union via type-test below.
27
- const ALL_KINDS: readonly FeaturePatternKind[] = [
27
+ const ALL_KINDS: FeaturePatternKind[] = [
28
28
  "requires",
29
29
  "optionalRequires",
30
30
  "readsConfig",
@@ -1,5 +1,5 @@
1
- import { eq } from "drizzle-orm";
2
1
  import type { DbRunner } from "../db/connection";
2
+ import { updateMany } from "../db/query";
3
3
  import type { StoredEvent } from "../event-store/event-store";
4
4
  import type { MultiStreamApplyContext } from "../pipeline/multi-stream-apply-context";
5
5
  import type { MultiStreamApplyFn, ProjectionTable, SingleStreamApplyFn } from "./types/projection";
@@ -71,15 +71,6 @@ export function setFields(
71
71
  }
72
72
  return async (event, tx) => {
73
73
  const values = typeof fields === "function" ? fields(event) : fields;
74
- // ProjectionTable erases its column shape on purpose (the framework
75
- // does not know user table shapes). Drizzle's tx.update().set() is
76
- // strict about the concrete row, so we feed it the erased value; the
77
- // type-safety guarantee for `values` lives at the setFields call-site.
78
- // biome-ignore lint/suspicious/noExplicitAny: see note above.
79
- const set = values as any; // @cast-boundary engine-bridge
80
- await tx
81
- .update(table)
82
- .set(set)
83
- .where(eq(idCol as never, event.aggregateId)); // @cast-boundary db-operator
74
+ await updateMany(tx, table, values, { id: event.aggregateId });
84
75
  };
85
76
  }
@@ -1,5 +1,5 @@
1
1
  import { applyEntityEvent } from "../db/apply-entity-event";
2
- import { buildDrizzleTable } from "../db/table-builder";
2
+ import { buildEntityTable } from "../db/table-builder";
3
3
  import { buildMetricName, validateMetricName } from "../observability";
4
4
  import { type QnType, qualifyEntityName } from "./qualified-name";
5
5
  import type {
@@ -40,6 +40,7 @@ import type {
40
40
  TranslationKeys,
41
41
  TreeActionDef,
42
42
  TreeChildrenSubscribe,
43
+ UnmanagedTableDef,
43
44
  WorkspaceDefinition,
44
45
  WriteHandlerDef,
45
46
  } from "./types";
@@ -67,7 +68,7 @@ function buildImplicitProjection(
67
68
  qualify: typeof qualifyEntityName,
68
69
  ): ProjectionDefinition {
69
70
  const name = qualify(featureName, "projection", `${entityName}${IMPLICIT_PROJECTION_SUFFIX}`);
70
- const drizzleTable = buildDrizzleTable(entityName, entity);
71
+ const drizzleTable = buildEntityTable(entityName, entity);
71
72
  // applyEntityEvent gibt ApplyResult zurück; SingleStreamApplyFn erwartet
72
73
  // Promise<void>. Im rebuild-Pfad ist die Row irrelevant — wir discarden.
73
74
  const handler = async (
@@ -169,6 +170,10 @@ export function createRegistry(features: readonly FeatureDefinition[]): Registry
169
170
  // enforced at ingest below (collisions would race two CREATE TABLE
170
171
  // statements at the same physical name and break boot).
171
172
  const rawTableMap = new Map<string, RawTableDef>();
173
+ // Unmanaged tables — declared via r.unmanagedTable() (EntityTableMeta).
174
+ // Cousin of rawTables: same uniqueness-by-tableName invariant, different
175
+ // storage shape (post-drizzle migrate-runner consumes EntityTableMeta).
176
+ const unmanagedTableMap = new Map<string, UnmanagedTableDef>();
172
177
  // Auth-claims hooks — tagged with featureName so the login resolver can
173
178
  // auto-prefix each hook's returned keys with "<feature>:".
174
179
  const authClaimsHooks: AuthClaimsHookDef[] = [];
@@ -543,6 +548,20 @@ export function createRegistry(features: readonly FeatureDefinition[]): Registry
543
548
  rawTableMap.set(rawName, { ...rawDef, featureName: feature.name });
544
549
  }
545
550
 
551
+ // Unmanaged tables — same cross-feature uniqueness invariant as rawTables.
552
+ // Two features registering the same physical tableName would race two
553
+ // CREATE TABLE statements via migrate-runner.
554
+ for (const [umName, umDef] of Object.entries(feature.unmanagedTables ?? {})) {
555
+ const existing = unmanagedTableMap.get(umName);
556
+ if (existing) {
557
+ throw new Error(
558
+ `Unmanaged-table "${umName}" registered by both feature "${existing.featureName}" and ` +
559
+ `"${feature.name}". Pick a feature-prefixed tableName to disambiguate.`,
560
+ );
561
+ }
562
+ unmanagedTableMap.set(umName, { ...umDef, featureName: feature.name });
563
+ }
564
+
546
565
  // Claim keys: aggregated by qualified name. Two features cannot collide
547
566
  // here (qualified by feature name), but we still guard for explicit
548
567
  // correctness — the only way to hit this is a hand-built FeatureDefinition
@@ -1,24 +1,22 @@
1
1
  // r.step.read.findMany — load multiple rows from a projection table.
2
2
  //
3
- // Sibling to read.findOne — same tenant-filter caveat (caller-owned),
4
- // same drizzle-boundary cast. Resolves to a row-array (possibly empty),
5
- // landed under steps.<name>.
3
+ // Sibling to read.findOne — same tenant-filter caveat (caller-owned).
4
+ // Resolves to a row-array (possibly empty), landed under steps.<name>.
6
5
  //
7
6
  // Optional `limit` — defaults to no-limit (caller-chosen, NOT a
8
7
  // guard-rail). Most legitimate uses iterate via r.step.forEach (M.1.6)
9
8
  // over the result, where unbounded arrays would be the bug. Set
10
9
  // `limit` explicitly when the row-count could grow without bound.
11
10
 
12
- import type { SQL, Table } from "drizzle-orm";
11
+ import { selectMany, type WhereObject } from "../../db/query";
13
12
  import { defineStep } from "../define-step";
14
13
  import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
15
- import { asQueryTarget } from "./_drizzle-boundary";
16
14
  import { resolveOptional } from "./_resolver-utils";
17
15
 
18
16
  type ReadFindManyArgs = {
19
17
  readonly name: string;
20
- readonly table: Table;
21
- readonly where?: StepResolver<SQL | undefined>;
18
+ readonly table: unknown;
19
+ readonly where?: StepResolver<WhereObject | undefined>;
22
20
  readonly limit?: number;
23
21
  };
24
22
 
@@ -28,10 +26,12 @@ defineStep<ReadFindManyArgs, readonly Record<string, unknown>[]>({
28
26
  resultKey: (args) => args.name,
29
27
  run: async (args, ctx: PipelineCtx) => {
30
28
  const where = resolveOptional(args.where, ctx);
31
- const baseQuery = ctx.db.select().from(asQueryTarget(args.table));
32
- const filteredQuery = where === undefined ? baseQuery : baseQuery.where(where);
33
- const finalQuery = args.limit === undefined ? filteredQuery : filteredQuery.limit(args.limit);
34
- const rows = await finalQuery;
29
+ const rows = await selectMany(
30
+ ctx.db.raw,
31
+ args.table,
32
+ where,
33
+ args.limit !== undefined ? { limit: args.limit } : undefined,
34
+ );
35
35
  return rows as readonly Record<string, unknown>[];
36
36
  },
37
37
  });
@@ -39,8 +39,8 @@ defineStep<ReadFindManyArgs, readonly Record<string, unknown>[]>({
39
39
  export function buildReadFindManyStep(
40
40
  name: string,
41
41
  opts: {
42
- readonly table: Table;
43
- readonly where?: StepResolver<SQL | undefined>;
42
+ readonly table: unknown;
43
+ readonly where?: StepResolver<WhereObject | undefined>;
44
44
  readonly limit?: number;
45
45
  },
46
46
  ): StepInstance {
@@ -1,6 +1,6 @@
1
1
  // r.step.read.findOne — load a single row from a projection table.
2
2
  //
3
- // Thin wrapper on ctx.db.select().from(table).where(where).limit(1).
3
+ // Thin wrapper on selectMany(db, table, where, { limit: 1 }) (bun-db).
4
4
  // Resolves to the first row or null. Tenant-isolation: the caller's
5
5
  // `where` clause is responsible for any tenantId filter — read.findOne
6
6
  // does NOT auto-inject one (different from ctx.queryProjection which
@@ -20,16 +20,15 @@
20
20
  // fine for "find by uuid", a footgun for "find by tenantId". No
21
21
  // runtime check; reviewer responsibility.
22
22
 
23
- import type { SQL, Table } from "drizzle-orm";
23
+ import { selectMany, type WhereObject } from "../../db/query";
24
24
  import { defineStep } from "../define-step";
25
25
  import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
26
- import { asQueryTarget } from "./_drizzle-boundary";
27
26
  import { resolveRequired } from "./_resolver-utils";
28
27
 
29
28
  type ReadFindOneArgs = {
30
29
  readonly name: string;
31
- readonly table: Table;
32
- readonly where: StepResolver<SQL | undefined>;
30
+ readonly table: unknown;
31
+ readonly where: StepResolver<WhereObject | undefined>;
33
32
  };
34
33
 
35
34
  defineStep<ReadFindOneArgs, Record<string, unknown> | null>({
@@ -38,8 +37,7 @@ defineStep<ReadFindOneArgs, Record<string, unknown> | null>({
38
37
  resultKey: (args) => args.name,
39
38
  run: async (args, ctx: PipelineCtx) => {
40
39
  const where = resolveRequired(args.where, ctx);
41
- const query = ctx.db.select().from(asQueryTarget(args.table));
42
- const rows = where === undefined ? await query.limit(1) : await query.where(where).limit(1);
40
+ const rows = await selectMany(ctx.db.raw, args.table, where, { limit: 1 });
43
41
  return (rows[0] as Record<string, unknown> | undefined) ?? null;
44
42
  },
45
43
  });
@@ -47,8 +45,8 @@ defineStep<ReadFindOneArgs, Record<string, unknown> | null>({
47
45
  export function buildReadFindOneStep(
48
46
  name: string,
49
47
  opts: {
50
- readonly table: Table;
51
- readonly where: StepResolver<SQL | undefined>;
48
+ readonly table: unknown;
49
+ readonly where: StepResolver<WhereObject | undefined>;
52
50
  },
53
51
  ): StepInstance {
54
52
  return {
@@ -16,10 +16,9 @@
16
16
  // must commit in the same TX as the aggregate-mutation that triggered
17
17
  // it (stronger consistency than an async projection). Reviewer judges.
18
18
 
19
- import type { SQL, Table } from "drizzle-orm";
19
+ import { deleteMany, type WhereObject } from "../../db/query";
20
20
  import { defineStep } from "../define-step";
21
21
  import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
22
- import { asQueryTarget } from "./_drizzle-boundary";
23
22
  import { resolveRequired } from "./_resolver-utils";
24
23
 
25
24
  // `where` is REQUIRED — table-wide DELETE without a clause is a TRUNCATE
@@ -28,8 +27,8 @@ import { resolveRequired } from "./_resolver-utils";
28
27
  // `r.step.unsafeProjectionTruncate` step rather than loosening this
29
28
  // type to `SQL | undefined`.
30
29
  type UnsafeProjectionDeleteArgs = {
31
- readonly table: Table;
32
- readonly where: StepResolver<SQL>;
30
+ readonly table: unknown;
31
+ readonly where: StepResolver<WhereObject>;
33
32
  };
34
33
 
35
34
  defineStep<UnsafeProjectionDeleteArgs, void>({
@@ -37,7 +36,7 @@ defineStep<UnsafeProjectionDeleteArgs, void>({
37
36
  defaultFailureStrategy: "throw",
38
37
  run: async (args, ctx: PipelineCtx) => {
39
38
  const where = resolveRequired(args.where, ctx);
40
- await ctx.db.delete(asQueryTarget(args.table)).where(where);
39
+ await deleteMany(ctx.db.raw, args.table, where);
41
40
  },
42
41
  });
43
42