@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
package/src/db/api.ts ADDED
@@ -0,0 +1,46 @@
1
+ // DB-API: Unified Types und Connection-Factory.
2
+ // Provider-agnostic code verwendet ausschliesslich diese Types.
3
+ // Provider-Implementierungen: postgres-provider.ts, bun-provider.ts.
4
+ //
5
+ // `asRawClient(db)` aus bun-db/query.ts normalisiert beide Provider
6
+ // zu { unsafe, begin } — business code ist provider-neutral.
7
+ //
8
+ // Default: postgres-js. Set DB_PROVIDER=bun für Bun.SQL (experimentell).
9
+
10
+ export type DbConnectionOptions = {
11
+ readonly maxConnections?: number;
12
+ readonly idleTimeoutSeconds?: number;
13
+ readonly connectTimeoutSeconds?: number;
14
+ };
15
+
16
+ // Connection-Handle: db für Queries, client für Legacy-Zugriff (LISTEN/NOTIFY-Peer
17
+ // bei Bun.SQL), close für Pool-Shutdown.
18
+ export type DbConnection = {
19
+ /** Provider Connection — Calls gehen über asRawClient() oder direkt. */
20
+ // biome-ignore lint/suspicious/noExplicitAny: cross-provider connection — postgres-js | Bun.SQL
21
+ readonly db: any;
22
+ /** Legacy postgres-js Client (für LISTEN peer) */
23
+ // biome-ignore lint/suspicious/noExplicitAny: postgres-js client
24
+ readonly client: any;
25
+ /** Optionaler Bun.SQL LISTEN peer */
26
+ // biome-ignore lint/suspicious/noExplicitAny: postgres-js LISTEN peer
27
+ readonly listenClient?: any;
28
+ /** Pool schliessen */
29
+ close: () => Promise<void>;
30
+ };
31
+
32
+ let _provider: undefined | (() => Promise<typeof import("./postgres-provider")>);
33
+
34
+ export async function createConnection(
35
+ url: string,
36
+ options: DbConnectionOptions = {},
37
+ ): Promise<DbConnection> {
38
+ const p = process.env["DB_PROVIDER"];
39
+ if (p === "bun" || p === "bun-sql") {
40
+ const { createBunConnection } = await import("./bun-provider");
41
+ return createBunConnection(url, options);
42
+ }
43
+ // postgres-js: sync, kein async import nötig
44
+ const { createPgConnection } = await import("./postgres-provider");
45
+ return createPgConnection(url, options);
46
+ }
@@ -47,7 +47,7 @@
47
47
  // - "skipped" → Event ist kein Auto-Verb (Domain-Event auf demselben
48
48
  // Aggregate). Caller no-op.
49
49
 
50
- import { eq } from "drizzle-orm";
50
+ import { deleteMany, insertOne, updateMany } from "../db/query";
51
51
  import type { EntityDefinition } from "../engine/types";
52
52
  import { InternalError } from "../errors";
53
53
  import type { StoredEvent } from "../event-store";
@@ -112,57 +112,65 @@ export async function applyEntityEvent(
112
112
  message: `applyEntityEvent: payload.tenantId set but invalid (${JSON.stringify(payloadTenantId)}). Tenant-isolation-kritisch: silent fallback auf event.tenantId würde Cross-Tenant-Drift erzeugen.`,
113
113
  });
114
114
  }
115
- const [row] = await tx
116
- .insert(table)
117
- .values({
118
- ...event.payload,
119
- tenantId,
120
- id: event.aggregateId,
121
- version: event.version,
122
- insertedAt: event.createdAt,
123
- insertedById: event.createdBy,
124
- })
125
- .returning();
126
- return { kind: "applied", verb, row: (row as DbRow | undefined) ?? null };
115
+ const row = await insertOne<DbRow>(tx, table, {
116
+ ...event.payload,
117
+ tenantId,
118
+ id: event.aggregateId,
119
+ version: event.version,
120
+ insertedAt: event.createdAt,
121
+ insertedById: event.createdBy,
122
+ });
123
+ return { kind: "applied", verb, row: row ?? null };
127
124
  }
128
125
 
129
126
  case "updated": {
130
127
  // payload-Shape: { changes, previous } — siehe event-store-executor.ts.
131
- const changes = (event.payload["changes"] ?? {}) as Record<string, unknown>; // @cast-boundary engine-payload
132
- const [row] = await tx
133
- .update(table)
134
- .set({
128
+ const rawChanges = (event.payload["changes"] ?? {}) as Record<string, unknown>; // @cast-boundary engine-payload
129
+ // Plural file fields (files/images) have no entity-table column — the
130
+ // array of UUIDs lives in the event payload only. Strip them from the
131
+ // UPDATE so Postgres doesn't error with "column X does not exist".
132
+ const changes: Record<string, unknown> = {};
133
+ for (const [key, value] of Object.entries(rawChanges)) {
134
+ const fieldType = entity.fields[key]?.type;
135
+ if (fieldType === "files" || fieldType === "images") continue;
136
+ changes[key] = value;
137
+ }
138
+ const rows = await updateMany<DbRow>(
139
+ tx,
140
+ table,
141
+ {
135
142
  ...changes,
136
143
  version: event.version,
137
144
  modifiedAt: event.createdAt,
138
145
  modifiedById: event.createdBy,
139
- })
140
- .where(eq(table["id"], event.aggregateId))
141
- .returning();
142
- return { kind: "applied", verb, row: (row as DbRow | undefined) ?? null };
146
+ },
147
+ { id: event.aggregateId },
148
+ );
149
+ return { kind: "applied", verb, row: rows[0] ?? null };
143
150
  }
144
151
 
145
152
  case "deleted": {
146
153
  if (softDelete) {
147
- const [row] = await tx
148
- .update(table)
149
- .set({
154
+ const rows = await updateMany<DbRow>(
155
+ tx,
156
+ table,
157
+ {
150
158
  isDeleted: true,
151
159
  deletedAt: event.createdAt,
152
160
  deletedById: event.createdBy,
153
161
  version: event.version,
154
162
  modifiedAt: event.createdAt,
155
163
  modifiedById: event.createdBy,
156
- })
157
- .where(eq(table["id"], event.aggregateId))
158
- .returning();
159
- return { kind: "applied", verb, row: (row as DbRow | undefined) ?? null };
164
+ },
165
+ { id: event.aggregateId },
166
+ );
167
+ return { kind: "applied", verb, row: rows[0] ?? null };
160
168
  }
161
169
  // Hard-Delete: DELETE-Statement gibt keine returning-Row her und
162
170
  // der Live-Pfad nutzt eh `existing` (pre-delete-Snapshot) für die
163
171
  // Response. Beim Replay ist das fine, der Caller braucht die Row
164
172
  // nicht weiter.
165
- await tx.delete(table).where(eq(table["id"], event.aggregateId));
173
+ await deleteMany(tx, table, { id: event.aggregateId });
166
174
  return { kind: "applied", verb, row: null };
167
175
  }
168
176
 
@@ -170,19 +178,20 @@ export async function applyEntityEvent(
170
178
  // Restore ist nur bei softDelete sinnvoll. Hard-Delete-Entities sollten
171
179
  // keine restored-Events erhalten — falls doch, defensive skip.
172
180
  if (!softDelete) return { kind: "skipped" };
173
- const [row] = await tx
174
- .update(table)
175
- .set({
181
+ const rows = await updateMany<DbRow>(
182
+ tx,
183
+ table,
184
+ {
176
185
  isDeleted: false,
177
186
  deletedAt: null,
178
187
  deletedById: null,
179
188
  version: event.version,
180
189
  modifiedAt: event.createdAt,
181
190
  modifiedById: event.createdBy,
182
- })
183
- .where(eq(table["id"], event.aggregateId))
184
- .returning();
185
- return { kind: "applied", verb, row: (row as DbRow | undefined) ?? null };
191
+ },
192
+ { id: event.aggregateId },
193
+ );
194
+ return { kind: "applied", verb, row: rows[0] ?? null };
186
195
  }
187
196
  }
188
197
  }
@@ -1,4 +1,4 @@
1
- import { and, eq, type SQL } from "drizzle-orm";
1
+ import { fetchOne } from "../db/query";
2
2
  import type { TenantId } from "../engine/types/identifiers";
3
3
  import { NotFoundError } from "../errors";
4
4
  import type { DbConnection } from "./connection";
@@ -28,22 +28,11 @@ export async function assertExistsIn(
28
28
  entityName?: string;
29
29
  },
30
30
  ): Promise<NotFoundError | null> {
31
- const conditions = [eq(entity[options.field], options.value)];
31
+ const where: Record<string, unknown> = { [options.field]: options.value };
32
+ if (options.tenantId !== undefined) where["tenantId"] = options.tenantId;
33
+ if (options.where) Object.assign(where, options.where);
32
34
 
33
- if (options.tenantId !== undefined) {
34
- conditions.push(eq(entity["tenantId"], options.tenantId));
35
- }
36
-
37
- if (options.where) {
38
- for (const [key, val] of Object.entries(options.where)) {
39
- conditions.push(eq(entity[key], val));
40
- }
41
- }
42
-
43
- const [row] = await db
44
- .select()
45
- .from(entity)
46
- .where(and(...conditions) as SQL); // @cast-boundary db-operator
35
+ const row = await fetchOne(db, entity, where);
47
36
 
48
37
  if (!row) {
49
38
  const entityName = options.entityName ?? String(options.field).replace(/Id$/, "");
@@ -0,0 +1,37 @@
1
+ // Bun.SQL Provider — experimentell.
2
+ // Default: postgres-js. Dieser Provider nur aktiv wenn DB_PROVIDER=bun.
3
+ // Bekannter Bug: Extended-Query-Protocol Caching verursacht
4
+ // PostgresError "bind message has 11 result formats but query has 1 columns"
5
+ // bei sequentiellen Queries mit unterschiedlicher Spaltenzahl innerhalb
6
+ // derselben Connection.
7
+ //
8
+ // Bun.SQL hat kein LISTEN — postgres-js-Peer für event-dispatcher.
9
+
10
+ import postgres from "postgres";
11
+ import type { DbConnection, DbConnectionOptions } from "./api";
12
+
13
+ export function createBunConnection(url: string, options: DbConnectionOptions = {}): DbConnection {
14
+ const bunOpts: { max?: number; idleTimeout?: number; connectionTimeout?: number } = {};
15
+ if (options.maxConnections !== undefined) bunOpts.max = options.maxConnections;
16
+ if (options.idleTimeoutSeconds !== undefined) bunOpts.idleTimeout = options.idleTimeoutSeconds;
17
+ if (options.connectTimeoutSeconds !== undefined)
18
+ bunOpts.connectionTimeout = options.connectTimeoutSeconds;
19
+ const db = new Bun.SQL(url, bunOpts);
20
+
21
+ // LISTEN peer — 1 connection reicht für NOTIFY-Wakeups
22
+ const pgOpts: Parameters<typeof postgres>[1] = { max: 1 };
23
+ if (options.idleTimeoutSeconds !== undefined) pgOpts.idle_timeout = options.idleTimeoutSeconds;
24
+ if (options.connectTimeoutSeconds !== undefined)
25
+ pgOpts.connect_timeout = options.connectTimeoutSeconds;
26
+ const listenClient = postgres(url, pgOpts);
27
+
28
+ return {
29
+ db,
30
+ client: listenClient,
31
+ listenClient,
32
+ close: async () => {
33
+ await db.end();
34
+ await listenClient.end();
35
+ },
36
+ };
37
+ }
@@ -5,7 +5,7 @@ import type { ConfigSeedDef, Registry } from "../engine/types";
5
5
  import type { DbConnection } from "./connection";
6
6
  import type { EncryptionProvider } from "./encryption";
7
7
  import { createEventStoreExecutor } from "./event-store-executor";
8
- import type { DrizzleTable } from "./table-builder";
8
+ import type { EntityTable } from "./table-builder";
9
9
  import { createTenantDb } from "./tenant-db";
10
10
 
11
11
  // Namespace UUID for deterministic seed aggregate IDs. Same namespace +
@@ -24,10 +24,10 @@ const CONFIG_SEED_NS = "6f1e9d8c-2a5b-4c7d-9e3f-1a2b3c4d5e6f";
24
24
  * Idempotent, race-safe via DB-level unique constraints, and visible to
25
25
  * multi-stream-projection subscribers as normal configValue.created events.
26
26
  */
27
- export async function seedConfigValues(
27
+ export async function seedConfigValues<E extends EntityDefinition>(
28
28
  seeds: readonly ConfigSeedDef[],
29
- table: DrizzleTable,
30
- entity: EntityDefinition,
29
+ table: EntityTable<E>,
30
+ entity: E,
31
31
  registry: Registry,
32
32
  db: DbConnection,
33
33
  encryption?: EncryptionProvider,
@@ -1,67 +1,30 @@
1
- import { drizzle } from "drizzle-orm/postgres-js";
1
+ // DB-Connection-Types: provider-agnostic via db/api.ts.
2
+ // createConnection delegiert an postgres-provider (default) oder bun-provider (DB_PROVIDER=bun).
2
3
  import postgres from "postgres";
3
4
  import { readPositiveIntEnv } from "../utils/env-parse";
4
5
 
5
- export type DbConnection = ReturnType<typeof drizzle>;
6
+ export { createConnection, type DbConnectionOptions } from "./api";
6
7
 
7
- // Drizzle's transaction callback receives a tx handle with the same query API
8
- // as the top-level DbConnection. Extracted via Parameters so we stay in sync
9
- // with whatever Drizzle defines without hard-coding the internal type name.
10
- export type DbTx = Parameters<Parameters<DbConnection["transaction"]>[0]>[0];
11
-
12
- // Code paths that operate on either a connection or an active transaction
13
- // (e.g. TenantDb, dispatcher pipeline) accept both.
8
+ // Legacy Types für Aufrufer die direkt diese Module importieren
9
+ // biome-ignore lint/suspicious/noExplicitAny: Bun.SQL global type
10
+ export type DbConnection = ReturnType<typeof postgres> | any;
11
+ // biome-ignore lint/suspicious/noExplicitAny: postgres-js namespace lookup
12
+ export type DbTx = postgres.TransactionSql<any> | any;
14
13
  export type DbRunner = DbConnection | DbTx;
15
-
16
- // Dynamic Drizzle tables (buildDrizzleTable with `any` column schema) lose
17
- // their per-column types at the Drizzle boundary. Query results come back as
18
- // arbitrary records. `DbRow` marks those typing-loss sites so readers see the
19
- // limitation without re-spelling `Record<string, unknown>` at every callsite.
20
- // Use `DbRow` for rows read via dynamic tables; a concrete entity-row type
21
- // is preferred whenever the table is statically typed.
22
14
  export type DbRow = Record<string, unknown>;
23
-
24
- // The raw postgres.js client. Exposed alongside the Drizzle wrapper so the
25
- // event-dispatcher (or other components that need LISTEN / pg-specific
26
- // features Drizzle doesn't surface) can subscribe without re-opening a
27
- // connection from the URL.
28
15
  export type PgClient = ReturnType<typeof postgres>;
29
16
 
30
- // Connection-pool options thin wrapper around the postgres.js fields the
31
- // framework explicitly supports. Omitted keys fall back to postgres.js
32
- // defaults (max=10, idle_timeout=PGIDLE_TIMEOUT env, connect_timeout=
33
- // PGCONNECT_TIMEOUT env). See `docs/plans/architecture/scaling.md` for
34
- // sizing guidance per deployment shape.
35
- export type DbConnectionOptions = {
36
- // Max concurrent connections in the pool. postgres.js defaults to 10 —
37
- // fine for a single app process against a small DB. Multi-worker or
38
- // high-concurrency API deploys should scale this with `num_workers *
39
- // per-request-concurrency` and stay below the DB's own max_connections
40
- // (typical managed postgres: 100–400).
41
- readonly maxConnections?: number;
42
- // Seconds before an idle connection is closed. Null/undefined → keep
43
- // connections warm forever (postgres.js default when the env var is
44
- // unset). Managed pgBouncer tiers usually want this explicitly set to
45
- // something like 30–60 so a single burst doesn't hold connections
46
- // indefinitely.
47
- readonly idleTimeoutSeconds?: number;
48
- // Seconds to wait while establishing a new connection. Fails the query
49
- // with a timeout error rather than hanging indefinitely when the DB is
50
- // unreachable — critical for `/health/ready` to actually flip to 503
51
- // within its 2s probe budget.
52
- readonly connectTimeoutSeconds?: number;
53
- };
17
+ export type PgListenClient = ReturnType<typeof postgres>;
54
18
 
19
+ // Legacy: postgres-js only. Neue Aufrufer: createConnection() aus api.ts.
55
20
  export function createDbConnection(
56
21
  url: string,
57
- options: DbConnectionOptions = {},
22
+ options: import("./api").DbConnectionOptions = {},
58
23
  ): {
59
24
  db: DbConnection;
60
25
  client: PgClient;
61
26
  close: () => Promise<void>;
62
27
  } {
63
- // Only forward fields the caller set — empty object otherwise preserves
64
- // postgres.js's env-var-driven defaults (PGIDLE_TIMEOUT / PGCONNECT_TIMEOUT).
65
28
  const pgOptions: Parameters<typeof postgres>[1] = {};
66
29
  if (options.maxConnections !== undefined) pgOptions.max = options.maxConnections;
67
30
  if (options.idleTimeoutSeconds !== undefined) pgOptions.idle_timeout = options.idleTimeoutSeconds;
@@ -70,10 +33,9 @@ export function createDbConnection(
70
33
  }
71
34
 
72
35
  const client = postgres(url, pgOptions);
73
- const db = drizzle(client);
74
36
 
75
37
  return {
76
- db,
38
+ db: client,
77
39
  client,
78
40
  close: async () => {
79
41
  await client.end();
@@ -81,15 +43,10 @@ export function createDbConnection(
81
43
  };
82
44
  }
83
45
 
84
- // Parse the supported env vars into a DbConnectionOptions object. Useful
85
- // for a main.ts that wants to read DATABASE_POOL_MAX / DATABASE_POOL_
86
- // IDLE_TIMEOUT / DATABASE_POOL_CONNECT_TIMEOUT without re-implementing
87
- // the number-coercion + validation. Unrecognised / non-numeric values
88
- // throw — misconfig surfaces at boot, not mid-request.
89
46
  export function dbConnectionOptionsFromEnv(
90
47
  env: Readonly<Record<string, string | undefined>> = process.env,
91
- ): DbConnectionOptions {
92
- const opts: DbConnectionOptions & {
48
+ ): import("./api").DbConnectionOptions {
49
+ const opts: import("./api").DbConnectionOptions & {
93
50
  maxConnections?: number;
94
51
  idleTimeoutSeconds?: number;
95
52
  connectTimeoutSeconds?: number;
package/src/db/cursor.ts CHANGED
@@ -1,6 +1,4 @@
1
- import { and, asc, desc, eq, gt, inArray, type SQL, sql } from "drizzle-orm";
2
1
  import type { EntityId, TenantId } from "../engine/types/identifiers";
3
- import type { SelectQuery as PgSelect } from "./dialect";
4
2
 
5
3
  export type CursorQueryOptions = {
6
4
  tenantId: TenantId;
@@ -9,24 +7,20 @@ export type CursorQueryOptions = {
9
7
  filterIds?: readonly EntityId[];
10
8
  sort?: string;
11
9
  sortDirection?: "asc" | "desc";
12
- extraWhere?: SQL;
13
10
  };
14
11
 
15
12
  export type CursorResult<T> = {
16
13
  rows: T[];
17
14
  nextCursor: string | null;
18
15
  /** Optional total row count — nur present wenn der Caller `totalCount: true`
19
- * in der Query setzt. Pager-UI braucht's für "Page X of Y"; Infinite-
20
- * Scroll und Default-Lists lassen den extra COUNT(*) weg. */
16
+ * in der Query setzt. */
21
17
  total?: number;
22
18
  };
23
19
 
24
- // String-basiert damit sowohl UUIDs (Default seit Sprint F) als auch
25
- // Integer-Auto-Increment-IDs (Legacy/Spezialfälle) durch denselben
26
- // Cursor-Pfad laufen. Stable-Sort-Voraussetzung: die id-Spalte muss
27
- // lexikografisch monoton zur Insertion-Order sein. UUIDv7 erfüllt das
28
- // (time-ordered Prefix); UUIDv4 nicht — wer den nutzt, kriegt
29
- // inkorrekte cursor-Reihenfolge, das ist erwartet (Default ist v7).
20
+ // String-basiert damit UUIDs (Default seit Sprint F) + Integer-Auto-Increment
21
+ // durch denselben Cursor-Pfad laufen. UUIDv7 erfüllt die lex-Monotonie
22
+ // (time-ordered Prefix); UUIDv4 nicht wer den nutzt, kriegt inkorrekte
23
+ // cursor-Reihenfolge (Default ist v7).
30
24
  export function encodeCursor(id: string | number): string {
31
25
  return Buffer.from(String(id)).toString("base64url");
32
26
  }
@@ -36,48 +30,3 @@ export function decodeCursor(cursor: string): string {
36
30
  if (decoded === "") throw new Error(`Invalid cursor: ${cursor}`);
37
31
  return decoded;
38
32
  }
39
-
40
- export function applyCursorQuery<T extends PgSelect>(
41
- query: T,
42
- // biome-ignore lint/suspicious/noExplicitAny: Drizzle dynamic tables lose column types
43
- table: any,
44
- options: CursorQueryOptions,
45
- ): T {
46
- const conditions: SQL[] = [eq(table.tenantId, options.tenantId)];
47
-
48
- if (table.isDeleted) {
49
- conditions.push(eq(table.isDeleted, false));
50
- }
51
-
52
- if (options.cursor) {
53
- conditions.push(gt(table.id, decodeCursor(options.cursor)));
54
- }
55
-
56
- if (options.filterIds !== undefined) {
57
- if (options.filterIds.length === 0) {
58
- // No matching IDs — return empty result via raw `false`. Statisch
59
- // false ist type-agnostisch (int-PK / uuid-PK egal); ein eq(id, "")
60
- // oder eq(id, -1) würde je nach Spalten-Type einen Cast-Error
61
- // werfen.
62
- conditions.push(sql`false`);
63
- } else {
64
- conditions.push(inArray(table.id, options.filterIds as readonly string[])); // @cast-boundary db-operator
65
- }
66
- }
67
-
68
- if (options.extraWhere) {
69
- conditions.push(options.extraWhere);
70
- }
71
-
72
- const limit = options.limit ?? 50;
73
-
74
- let result = query.where(and(...conditions)).limit(limit);
75
-
76
- if (options.sort && table[options.sort]) {
77
- const column = table[options.sort];
78
- result =
79
- options.sortDirection === "desc" ? result.orderBy(desc(column)) : result.orderBy(asc(column));
80
- }
81
-
82
- return result as T; // @cast-boundary engine-bridge
83
- }