@cosmicdrift/kumiko-framework 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/package.json +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/readiness.ts +2 -2
  24. package/src/auth/__tests__/roles.test.ts +2 -2
  25. package/src/bun-db/__tests__/PATTERN.md +73 -0
  26. package/src/bun-db/__tests__/_helpers.ts +103 -0
  27. package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
  28. package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
  29. package/src/bun-db/__tests__/bun-test-db.ts +19 -0
  30. package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
  31. package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
  32. package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
  33. package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
  34. package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
  35. package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
  36. package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
  37. package/src/bun-db/connection.ts +84 -0
  38. package/src/bun-db/index.ts +31 -0
  39. package/src/bun-db/query.ts +845 -0
  40. package/src/compliance/__tests__/duration-spec.test.ts +1 -1
  41. package/src/compliance/__tests__/profiles.test.ts +1 -1
  42. package/src/compliance/__tests__/sub-processors.test.ts +1 -1
  43. package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
  44. package/src/db/__tests__/big-int-field.test.ts +15 -14
  45. package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
  46. package/src/db/__tests__/compound-types.test.ts +1 -1
  47. package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
  48. package/src/db/__tests__/connection-options.test.ts +1 -1
  49. package/src/db/__tests__/dialect-instant.test.ts +1 -1
  50. package/src/db/__tests__/encryption.test.ts +1 -1
  51. package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
  52. package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
  53. package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
  54. package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
  55. package/src/db/__tests__/located-timestamp.test.ts +1 -1
  56. package/src/db/__tests__/money.test.ts +1 -1
  57. package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
  58. package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
  59. package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
  60. package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
  61. package/src/db/__tests__/sql-inventory.test.ts +56 -0
  62. package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
  63. package/src/db/__tests__/table-builder-required.test.ts +20 -22
  64. package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
  65. package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
  66. package/src/db/api.ts +46 -0
  67. package/src/db/apply-entity-event.ts +45 -36
  68. package/src/db/assert-exists-in.ts +5 -16
  69. package/src/db/bun-provider.ts +37 -0
  70. package/src/db/config-seed.ts +4 -4
  71. package/src/db/connection.ts +14 -57
  72. package/src/db/cursor.ts +5 -56
  73. package/src/db/dialect.ts +472 -99
  74. package/src/db/eagerload.ts +5 -12
  75. package/src/db/entity-table-meta.ts +390 -0
  76. package/src/db/event-store-executor.ts +158 -100
  77. package/src/db/index.ts +33 -5
  78. package/src/db/migrate-generator.ts +350 -0
  79. package/src/db/migrate-runner.ts +206 -0
  80. package/src/db/postgres-provider.ts +25 -0
  81. package/src/db/queries/entity-read.ts +15 -0
  82. package/src/db/queries/es-ops.ts +17 -0
  83. package/src/db/queries/event-consumer.ts +170 -0
  84. package/src/db/queries/event-store-admin.ts +127 -0
  85. package/src/db/queries/event-store.ts +155 -0
  86. package/src/db/queries/projection-rebuild.ts +59 -0
  87. package/src/db/queries/raw-sql.ts +15 -0
  88. package/src/db/queries/schema-drift.ts +35 -0
  89. package/src/db/queries/seed-context.ts +58 -0
  90. package/src/db/queries/table-ops.ts +11 -0
  91. package/src/db/queries/test-stack.ts +56 -0
  92. package/src/db/query-api.ts +22 -0
  93. package/src/db/query.ts +30 -0
  94. package/src/db/reference-data.ts +19 -22
  95. package/src/db/render-ddl.ts +57 -0
  96. package/src/db/row-helpers.ts +3 -52
  97. package/src/db/schema-inspection.ts +17 -4
  98. package/src/db/sql-inventory.ts +208 -0
  99. package/src/db/table-builder.ts +48 -40
  100. package/src/db/tenant-db.ts +105 -326
  101. package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
  102. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
  103. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
  104. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
  105. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
  106. package/src/engine/__tests__/boot-validator.test.ts +4 -3
  107. package/src/engine/__tests__/build-app-schema.test.ts +1 -1
  108. package/src/engine/__tests__/build-target.test.ts +1 -1
  109. package/src/engine/__tests__/claim-keys.test.ts +1 -1
  110. package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
  111. package/src/engine/__tests__/config-helpers.test.ts +1 -1
  112. package/src/engine/__tests__/effective-features.test.ts +1 -1
  113. package/src/engine/__tests__/engine.test.ts +1 -1
  114. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  115. package/src/engine/__tests__/event-helpers.test.ts +3 -3
  116. package/src/engine/__tests__/extends-registrar.test.ts +4 -4
  117. package/src/engine/__tests__/factories-long-text.test.ts +1 -1
  118. package/src/engine/__tests__/factories-time.test.ts +1 -1
  119. package/src/engine/__tests__/field-predicates.test.ts +1 -1
  120. package/src/engine/__tests__/hook-phases.test.ts +1 -1
  121. package/src/engine/__tests__/identifiers.test.ts +1 -1
  122. package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
  123. package/src/engine/__tests__/nav.test.ts +1 -1
  124. package/src/engine/__tests__/ownership.test.ts +10 -11
  125. package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
  126. package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
  127. package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
  128. package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
  129. package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
  130. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
  131. package/src/engine/__tests__/post-query-hook.test.ts +1 -1
  132. package/src/engine/__tests__/projection-helpers.test.ts +25 -17
  133. package/src/engine/__tests__/projection.test.ts +4 -4
  134. package/src/engine/__tests__/qualified-name.test.ts +1 -1
  135. package/src/engine/__tests__/raw-table.test.ts +9 -8
  136. package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
  137. package/src/engine/__tests__/run-in.test.ts +1 -1
  138. package/src/engine/__tests__/schema-builder.test.ts +1 -1
  139. package/src/engine/__tests__/screen.test.ts +1 -1
  140. package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
  141. package/src/engine/__tests__/state-machine.test.ts +1 -1
  142. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
  143. package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
  144. package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
  145. package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
  146. package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
  147. package/src/engine/__tests__/steps-read.test.ts +34 -40
  148. package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
  149. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
  150. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
  151. package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
  152. package/src/engine/__tests__/steps-workflow.test.ts +7 -7
  153. package/src/engine/__tests__/system-user.test.ts +1 -1
  154. package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
  155. package/src/engine/__tests__/validation-hooks.test.ts +1 -1
  156. package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
  157. package/src/engine/boot-validator/entity-handler.ts +3 -3
  158. package/src/engine/boot-validator/ownership.ts +1 -1
  159. package/src/engine/define-feature.ts +1 -2
  160. package/src/engine/entity-handlers.ts +5 -5
  161. package/src/engine/factories.ts +1 -1
  162. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
  163. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
  164. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
  165. package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
  166. package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
  167. package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
  168. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
  169. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
  170. package/src/engine/ownership.ts +113 -41
  171. package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
  172. package/src/engine/projection-helpers.ts +2 -11
  173. package/src/engine/registry.ts +2 -2
  174. package/src/engine/steps/read-find-many.ts +13 -13
  175. package/src/engine/steps/read-find-one.ts +7 -9
  176. package/src/engine/steps/unsafe-projection-delete.ts +4 -5
  177. package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
  178. package/src/engine/types/feature.ts +7 -2
  179. package/src/engine/types/fields.ts +4 -5
  180. package/src/engine/types/step.ts +10 -10
  181. package/src/engine/validate-projection-allowlist.ts +23 -3
  182. package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
  183. package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
  184. package/src/env/__tests__/compose-env-schema.test.ts +1 -1
  185. package/src/env/__tests__/dry-run.test.ts +1 -1
  186. package/src/errors/__tests__/classes.test.ts +1 -1
  187. package/src/errors/__tests__/write-failures.test.ts +1 -1
  188. package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
  189. package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
  190. package/src/es-ops/__tests__/runner.test.ts +29 -19
  191. package/src/es-ops/context.ts +9 -43
  192. package/src/es-ops/operations-schema.ts +2 -2
  193. package/src/es-ops/runner.ts +12 -26
  194. package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
  195. package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
  196. package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
  197. package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
  198. package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
  199. package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
  200. package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
  201. package/src/event-store/admin-api.ts +55 -83
  202. package/src/event-store/archive.ts +15 -39
  203. package/src/event-store/event-store.ts +92 -86
  204. package/src/event-store/events-schema.ts +2 -1
  205. package/src/event-store/index.ts +1 -0
  206. package/src/event-store/snapshot.ts +26 -24
  207. package/src/event-store/upcaster-dead-letter.ts +19 -18
  208. package/src/files/__tests__/content-disposition.test.ts +1 -1
  209. package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
  210. package/src/files/__tests__/file-handle.test.ts +1 -1
  211. package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
  212. package/src/files/__tests__/read-stream.test.ts +1 -1
  213. package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
  214. package/src/files/__tests__/write-stream.test.ts +1 -1
  215. package/src/files/__tests__/zip-stream.test.ts +1 -1
  216. package/src/files/file-ref-table.ts +2 -2
  217. package/src/files/file-routes.ts +7 -9
  218. package/src/files/storage-tracking.ts +9 -17
  219. package/src/i18n/__tests__/i18n.test.ts +1 -1
  220. package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
  221. package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
  222. package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
  223. package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
  224. package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
  225. package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
  226. package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
  227. package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
  228. package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
  229. package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
  230. package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
  231. package/src/migrations/projection-detection.ts +12 -1
  232. package/src/migrations/schema-drift.ts +7 -23
  233. package/src/observability/__tests__/console-provider.test.ts +1 -1
  234. package/src/observability/__tests__/metric-validator.test.ts +1 -1
  235. package/src/observability/__tests__/noop-provider.test.ts +1 -1
  236. package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
  237. package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
  238. package/src/observability/__tests__/recording-meter.test.ts +1 -1
  239. package/src/observability/__tests__/recording-tracer.test.ts +1 -1
  240. package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
  241. package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
  242. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
  243. package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
  244. package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
  245. package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
  246. package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
  247. package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
  248. package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
  249. package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
  250. package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
  251. package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
  252. package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
  253. package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
  254. package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
  255. package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
  256. package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
  257. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
  258. package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
  259. package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
  260. package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
  261. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
  262. package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
  263. package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
  264. package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
  265. package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
  266. package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
  267. package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
  268. package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
  269. package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
  270. package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
  271. package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
  272. package/src/pipeline/cascade-handler.ts +13 -21
  273. package/src/pipeline/dispatcher.ts +43 -48
  274. package/src/pipeline/event-consumer-state.ts +11 -2
  275. package/src/pipeline/event-dispatcher.ts +86 -146
  276. package/src/pipeline/event-retention.ts +14 -24
  277. package/src/pipeline/msp-rebuild.ts +54 -78
  278. package/src/pipeline/projection-rebuild.ts +65 -67
  279. package/src/pipeline/projection-state.ts +2 -2
  280. package/src/random/__tests__/generate.test.ts +13 -13
  281. package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
  282. package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
  283. package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
  284. package/src/redis/__tests__/redis-options.test.ts +1 -1
  285. package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
  286. package/src/search/__tests__/search-adapter.test.ts +1 -1
  287. package/src/secrets/__tests__/dek-cache.test.ts +1 -3
  288. package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
  289. package/src/secrets/__tests__/envelope.test.ts +1 -1
  290. package/src/secrets/__tests__/leak-guard.test.ts +1 -1
  291. package/src/secrets/__tests__/rotation.test.ts +1 -1
  292. package/src/stack/db.ts +25 -48
  293. package/src/stack/push-entity-projection-tables.ts +2 -4
  294. package/src/stack/table-helpers.ts +98 -61
  295. package/src/stack/test-stack.ts +8 -7
  296. package/src/testing/__tests__/db-cleanup.test.ts +40 -0
  297. package/src/testing/__tests__/e2e-generator.test.ts +1 -1
  298. package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
  299. package/src/testing/db-cleanup.ts +44 -0
  300. package/src/testing/expect-error.ts +1 -1
  301. package/src/testing/index.ts +2 -0
  302. package/src/testing/multipart-helper.ts +94 -0
  303. package/src/testing/shared-entities.ts +5 -5
  304. package/src/time/__tests__/polyfill.test.ts +1 -1
  305. package/src/time/__tests__/tz-context.test.ts +1 -1
  306. package/src/utils/__tests__/assert.test.ts +1 -1
  307. package/src/utils/__tests__/env-parse.test.ts +1 -1
  308. package/CHANGELOG.md +0 -474
  309. package/src/db/__tests__/cursor.test.ts +0 -41
  310. package/src/db/__tests__/db-helpers.test.ts +0 -369
  311. package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
  312. package/src/db/__tests__/row-helpers.test.ts +0 -59
  313. package/src/engine/steps/_drizzle-boundary.ts +0 -19
  314. package/src/files/__tests__/file-field-column.integration.ts +0 -103
@@ -1,9 +1,11 @@
1
- import { and, asc, desc, eq, gt, inArray, lt, ne, type SQL, sql } from "drizzle-orm";
2
- import type { AnyPgColumn } from "drizzle-orm/pg-core";
3
1
  import { requestContext } from "../api/request-context";
2
+ import { executeRawQuery } from "../db/queries/raw-sql";
3
+ import type { WhereObject } from "../db/query";
4
+ import { coerceRow, extractTableInfo, selectMany } from "../db/query";
4
5
  import { checkWriteFieldOwnership } from "../engine/field-access";
5
6
  import {
6
7
  buildOwnershipClause,
8
+ shiftParams,
7
9
  userCanCreateFieldRow,
8
10
  userCanWriteFieldRow,
9
11
  } from "../engine/ownership";
@@ -16,6 +18,7 @@ import type {
16
18
  SessionUser,
17
19
  WriteResult,
18
20
  } from "../engine/types";
21
+ import { SYSTEM_TENANT_ID } from "../engine/types/identifiers";
19
22
  import {
20
23
  VersionConflictError as FrameworkVersionConflict,
21
24
  InternalError,
@@ -41,39 +44,35 @@ import { decodeCursor, encodeCursor } from "./cursor";
41
44
  import type { TableColumns } from "./dialect";
42
45
  import type { CursorResult } from "./index";
43
46
  import { constraintOf, isUniqueViolation } from "./pg-error";
47
+ import { toSnakeCase } from "./table-builder";
44
48
  import type { TenantDb } from "./tenant-db";
45
49
 
46
50
  // biome-ignore lint/suspicious/noExplicitAny: Drizzle dynamic tables
47
51
  type Table = TableColumns<any>;
48
52
 
49
- // Screen-Filter (Tier 2.7c) — Op-Mapping zur Drizzle-WHERE-Clause.
50
- // Lebt isoliert hier (statt inline im list-Body) damit der einzige
51
- // Wire-Boundary `as never`-Cast lokal bleibt: payload.filter.value ist
52
- // `unknown` (Wire-Boundary), Drizzle's eq/ne/lt/gt/inArray verlangen
53
- // den Column-Type. Type-Mismatch wirft erst der PostgreSQL-Driver zur
54
- // Laufzeit; Author hat über `filterable: true` + Boot-Validator op-
55
- // vs-Type-Compat ohnehin Kontrolle was reinkommt.
56
- //
57
- // Empty-array IN ist explizit "no match" (SQL false), nicht "match all".
58
- function buildFilterCondition(
59
- col: AnyPgColumn,
53
+ // Screen-Filter (Tier 2.7c) — Op-Mapping als Where-Operator. Boot-
54
+ // Validator pinst field-Existenz + filterable + op-vs-Type-Compat.
55
+ // `op` ist auf {eq,ne,lt,gt,in} normalisiert; "in" mit empty-array ist
56
+ // explizit no-match.
57
+ function buildFilterWhere(
58
+ field: string,
60
59
  op: "eq" | "ne" | "lt" | "gt" | "in",
61
60
  value: unknown,
62
- ): SQL {
61
+ ): WhereObject | null {
63
62
  switch (op) {
64
63
  case "eq":
65
- return eq(col, value as never); // @cast-boundary db-operator
64
+ return { [field]: value };
66
65
  case "ne":
67
- return ne(col, value as never); // @cast-boundary db-operator
66
+ return { [field]: { ne: value } };
68
67
  case "lt":
69
- return lt(col, value as never); // @cast-boundary db-operator
68
+ return { [field]: { lt: value } };
70
69
  case "gt":
71
- return gt(col, value as never); // @cast-boundary db-operator
70
+ return { [field]: { gt: value } };
72
71
  case "in":
73
72
  if (Array.isArray(value) && value.length > 0) {
74
- return inArray(col, value as never); // @cast-boundary db-operator
73
+ return { [field]: value };
75
74
  }
76
- return sql`false`;
75
+ return null; // no-match short-circuit
77
76
  default:
78
77
  assertUnreachable(op, "filter op");
79
78
  }
@@ -280,22 +279,61 @@ export function createEventStoreExecutor(
280
279
  return result;
281
280
  }
282
281
 
283
- function idFilter(id: EntityId) {
284
- const conditions = [eq(table["id"], id)];
285
- if (softDelete && table["isDeleted"]) {
286
- conditions.push(eq(table["isDeleted"], false));
287
- }
288
- // Drizzle's variadic `and()` is typed `SQL | undefined`; conditions is
289
- // guaranteed non-empty above (we pushed at least one).
290
- return and(...conditions) as SQL; // @cast-boundary db-operator
282
+ function idFilter(id: EntityId): WhereObject {
283
+ const filter: WhereObject = { id };
284
+ if (softDelete && table["isDeleted"]) filter["isDeleted"] = false;
285
+ return filter;
291
286
  }
292
287
 
293
288
  async function loadById(id: EntityId, db: TenantDb): Promise<Record<string, unknown> | null> {
294
- const [row] = await db.select().from(table).where(idFilter(id));
289
+ const row = await db.fetchOne(table, idFilter(id));
295
290
  if (!row) return null;
296
291
  return rehydrateCompoundTypes(row as DbRow, entity);
297
292
  }
298
293
 
294
+ // SELECT a row by id with the ownership clause applied at the DB layer.
295
+ // Detail() uses this both on cold path and as a cache-revalidation probe.
296
+ async function loadWithOwnership(
297
+ db: TenantDb,
298
+ idWhere: WhereObject,
299
+ ownership:
300
+ | { kind: "pass" }
301
+ | { kind: "empty" }
302
+ | { kind: "sql"; sqlText: string; params: readonly unknown[] },
303
+ ): Promise<Record<string, unknown>[]> {
304
+ if (ownership.kind === "empty") return [];
305
+ if (ownership.kind === "pass") {
306
+ const row = await db.fetchOne(table, idWhere);
307
+ return row ? [row as Record<string, unknown>] : [];
308
+ }
309
+ // ownership has raw SQL — splice it into a raw query alongside the
310
+ // idFilter + tenant-filter that TenantDb would have added.
311
+ const tableName = String(
312
+ (table as unknown as Record<symbol, unknown>)[Symbol.for("kumiko:schema:Name")],
313
+ );
314
+ const colSql = (field: string): string =>
315
+ `"${(table[field] as { name?: string } | undefined)?.name ?? toSnakeCase(field)}"`;
316
+ const whereParts: string[] = [];
317
+ const params: unknown[] = [];
318
+ if (table["tenantId"] !== undefined && db.mode === "tenant") {
319
+ params.push(db.tenantId, SYSTEM_TENANT_ID);
320
+ whereParts.push(`${colSql("tenantId")} IN ($${params.length - 1}, $${params.length})`);
321
+ }
322
+ for (const [field, value] of Object.entries(idWhere)) {
323
+ if (typeof value === "boolean") {
324
+ whereParts.push(`${colSql(field)} = ${value ? "TRUE" : "FALSE"}`);
325
+ } else {
326
+ params.push(value);
327
+ whereParts.push(`${colSql(field)} = $${params.length}`);
328
+ }
329
+ }
330
+ const shifted = shiftParams(ownership, params.length);
331
+ whereParts.push(shifted.sqlText);
332
+ for (const p of shifted.params) params.push(p);
333
+ const sqlText = `SELECT * FROM "${tableName}" WHERE ${whereParts.join(" AND ")} LIMIT 1`;
334
+ return [...(await executeRawQuery<Record<string, unknown>>(db.raw, sqlText, params))];
335
+ }
336
+
299
337
  return {
300
338
  async create(payload, user, db) {
301
339
  // Respect an explicit id in the payload (seed pattern, SCIM import). Without
@@ -665,7 +703,7 @@ export function createEventStoreExecutor(
665
703
  );
666
704
  }
667
705
 
668
- const [row] = await db.select().from(table).where(eq(table["id"], payload.id));
706
+ const [row] = await selectMany(db.raw, table, { id: payload.id });
669
707
  if (!row) return writeFailure(new NotFoundError(entityName, payload.id));
670
708
  const data = row as DbRow;
671
709
  if (!data["isDeleted"]) {
@@ -770,59 +808,98 @@ export function createEventStoreExecutor(
770
808
  }
771
809
  }
772
810
 
773
- const conditions: SQL[] = [];
811
+ // Build the WHERE clause as raw SQL ownership produces a
812
+ // parameterised fragment that we splice in alongside simple WhereObject
813
+ // conditions (cursor, search-filter-IDs, screen-filter, tenant-scope).
814
+ const tableName = String(
815
+ (table as unknown as Record<symbol, unknown>)[Symbol.for("kumiko:schema:Name")],
816
+ );
817
+ const whereSql: string[] = [];
818
+ const params: unknown[] = [];
819
+ const colSql = (field: string): string =>
820
+ `"${(table[field] as { name?: string } | undefined)?.name ?? toSnakeCase(field)}"`;
821
+
822
+ // Tenant-Filter (replicates TenantDb's readWhere semantics).
823
+ if (table["tenantId"] !== undefined && db.mode === "tenant") {
824
+ params.push(db.tenantId, SYSTEM_TENANT_ID);
825
+ whereSql.push(`${colSql("tenantId")} IN ($${params.length - 1}, $${params.length})`);
826
+ }
774
827
  if (softDelete && table["isDeleted"]) {
775
- conditions.push(eq(table["isDeleted"], false));
828
+ whereSql.push(`${colSql("isDeleted")} = FALSE`);
776
829
  }
777
- // Cursor und Offset schließen sich aus: Cursor ist DB-stable (gt id),
778
- // Offset ist für klassische Page-Navigation. Wenn beide gesetzt sind,
779
- // gewinnt Cursor — Caller hätte eh nicht gleichzeitig beide nutzen
780
- // sollen, das pinnt die Verteidigung.
781
830
  if (payload.cursor) {
782
- conditions.push(gt(table["id"], decodeCursor(payload.cursor)));
831
+ params.push(decodeCursor(payload.cursor));
832
+ whereSql.push(`${colSql("id")} > $${params.length}`);
783
833
  }
784
834
  if (filterIds) {
785
- conditions.push(inArray(table["id"], filterIds));
835
+ const placeholders = filterIds.map((id) => {
836
+ params.push(id);
837
+ return `$${params.length}`;
838
+ });
839
+ whereSql.push(`${colSql("id")} IN (${placeholders.join(", ")})`);
786
840
  }
787
841
  if (ownership.kind === "sql") {
788
- conditions.push(ownership.sql);
842
+ const shifted = shiftParams(
843
+ { sqlText: ownership.sqlText, params: ownership.params },
844
+ params.length,
845
+ );
846
+ whereSql.push(shifted.sqlText);
847
+ for (const p of shifted.params) params.push(p);
789
848
  }
790
- // Screen-Filter (Tier 2.7c) — Boot-Validator hat field-Existenz
791
- // + filterable + op-vs-Type-Compat schon gepinnt. Runtime-Defense:
792
- // undefined-column → silent skip (kein Crash). Op-Mapping läuft
793
- // durch buildFilterCondition() — da lebt auch der einzige
794
- // `as never`-Cast (Wire-Boundary).
795
849
  if (payload.filter !== undefined) {
796
850
  const col = table[payload.filter.field];
797
851
  if (col !== undefined) {
798
- conditions.push(buildFilterCondition(col, payload.filter.op, payload.filter.value));
852
+ const screen = buildFilterWhere(
853
+ payload.filter.field,
854
+ payload.filter.op,
855
+ payload.filter.value,
856
+ );
857
+ if (screen === null) {
858
+ whereSql.push("FALSE");
859
+ } else {
860
+ for (const [field, value] of Object.entries(screen)) {
861
+ if (Array.isArray(value)) {
862
+ const placeholders = value.map((v) => {
863
+ params.push(v);
864
+ return `$${params.length}`;
865
+ });
866
+ whereSql.push(`${colSql(field)} IN (${placeholders.join(", ")})`);
867
+ } else if (typeof value === "object" && value !== null) {
868
+ const opMap: Record<string, string> = {
869
+ gt: ">",
870
+ gte: ">=",
871
+ lt: "<",
872
+ lte: "<=",
873
+ ne: "<>",
874
+ };
875
+ for (const [opKey, opSym] of Object.entries(opMap)) {
876
+ if (!(opKey in value)) continue;
877
+ params.push((value as Record<string, unknown>)[opKey]);
878
+ whereSql.push(`${colSql(field)} ${opSym} $${params.length}`);
879
+ }
880
+ } else {
881
+ params.push(value);
882
+ whereSql.push(`${colSql(field)} = $${params.length}`);
883
+ }
884
+ }
885
+ }
799
886
  }
800
887
  }
801
888
 
802
- const whereClause = conditions.length > 0 ? (and(...conditions) as SQL) : undefined; // @cast-boundary db-operator
803
- let query = whereClause
804
- ? db.select().from(table).where(whereClause)
805
- : db.select().from(table);
889
+ const orderByClause =
890
+ payload.sort && table[payload.sort]
891
+ ? ` ORDER BY ${colSql(payload.sort)} ${payload.sortDirection === "desc" ? "DESC" : "ASC"}`
892
+ : "";
893
+ const useOffset = !payload.cursor && offset > 0;
894
+ const offsetClause = useOffset ? ` OFFSET ${offset}` : "";
806
895
 
807
- query = query.limit(limit);
808
- // Offset NUR wenn kein Cursor sonst kombinieren wir zwei
809
- // Pagination-Schemes und der Caller bekommt unverhoffte Skips.
810
- if (!payload.cursor && offset > 0) {
811
- query = query.offset(offset);
812
- }
896
+ const whereClauseSqlText = whereSql.length > 0 ? ` WHERE ${whereSql.join(" AND ")}` : "";
897
+ const listSql = `SELECT * FROM "${tableName}"${whereClauseSqlText}${orderByClause} LIMIT ${limit}${offsetClause}`;
813
898
 
814
- if (payload.sort && table[payload.sort]) {
815
- const column = table[payload.sort];
816
- query =
817
- payload.sortDirection === "desc"
818
- ? query.orderBy(desc(column))
819
- : query.orderBy(asc(column));
820
- }
821
-
822
- const rawRows = (await query) as Record<string, unknown>[]; // @cast-boundary engine-payload
823
- // Read-Side rehydrate pro Row. Cache speichert die hydrated Form,
824
- // damit Cache-Hits dieselbe API-Form liefern.
825
- const rows = rawRows.map((r) => rehydrateCompoundTypes(r, entity));
899
+ const rawRows = await executeRawQuery<Record<string, unknown>>(db.raw, listSql, params);
900
+ // Read-Side rehydrate pro Row + snake→camel coercion für driver-agnostic Feldnamen
901
+ const tableInfo = extractTableInfo(table);
902
+ const rows = rawRows.map((r) => coerceRow(rehydrateCompoundTypes(r, entity), tableInfo));
826
903
 
827
904
  if (entityCache && entityName && rows.length > 0) {
828
905
  await entityCache.mset(
@@ -845,11 +922,9 @@ export function createEventStoreExecutor(
845
922
  if (filterIds) {
846
923
  total = filterIds.length;
847
924
  } else {
848
- const countQuery = whereClause
849
- ? db.select({ count: sql<number>`count(*)::int` }).from(table).where(whereClause)
850
- : db.select({ count: sql<number>`count(*)::int` }).from(table);
851
- const countRow = (await countQuery) as Array<{ count: number }>; // @cast-boundary db-row
852
- total = countRow[0]?.count ?? 0;
925
+ const countSql = `SELECT COUNT(*)::int AS count FROM "${tableName}"${whereClauseSqlText}`;
926
+ const countRows = await executeRawQuery<{ count: number }>(db.raw, countSql, params);
927
+ total = countRows[0]?.count ?? 0;
853
928
  }
854
929
  }
855
930
 
@@ -863,50 +938,33 @@ export function createEventStoreExecutor(
863
938
  const ownership = buildOwnershipClause(user, entity.access?.read, table);
864
939
  if (ownership.kind === "empty") return null;
865
940
 
941
+ const idWhere = idFilter(payload.id);
942
+
866
943
  if (entityCache && entityName) {
867
944
  const cached = await entityCache.get(user.tenantId, entityName, payload.id);
868
945
  if (cached) {
869
- // Even with a cache hit the ownership predicate must hold. The
870
- // cache is keyed only by tenant + id, not by role, so a cached
871
- // row may be visible to caller A but not caller B — re-check
872
- // per request.
873
946
  if (ownership.kind === "sql") {
874
- // Reuse the clause by querying the row with it. Cheaper than
875
- // SQL-parsing the predicate: just re-issue detail-by-id with
876
- // the ownership-AND and see if the DB returns it. idFilter()
877
- // handles the soft-delete guard.
878
- const checked = await db
879
- .select()
880
- .from(table)
881
- .where(and(idFilter(payload.id), ownership.sql) as SQL) // @cast-boundary db-operator
882
- .limit(1);
883
- if (checked.length === 0) return null;
947
+ // Re-check ownership predicate against the live row the cache
948
+ // is keyed only by tenant + id, not by role.
949
+ const checkRows = await loadWithOwnership(db, idWhere, ownership);
950
+ if (checkRows.length === 0) return null;
884
951
  }
885
952
  return cached;
886
953
  }
887
954
  }
888
955
 
889
- // Cold path: load the row with the ownership predicate applied so the
890
- // DB does the filtering (cheaper than load-then-filter-in-JS). Reuse
891
- // idFilter() — it handles the soft-delete guard consistently with
892
- // loadById(), which we can't just call directly because it doesn't
893
- // thread the ownership clause.
894
- const baseFilter = idFilter(payload.id);
895
- const whereClause =
896
- ownership.kind === "sql" ? (and(baseFilter, ownership.sql) as SQL) : baseFilter; // @cast-boundary db-operator
897
- const rows = (await db.select().from(table).where(whereClause).limit(1)) as Record<
898
- string,
899
- unknown
900
- >[]; // @cast-boundary db-row
956
+ const rows = await loadWithOwnership(db, idWhere, ownership);
901
957
  const raw = rows[0];
902
958
  if (!raw) return null;
903
959
  const row = rehydrateCompoundTypes(raw, entity);
960
+ const rowInfo = extractTableInfo(table);
961
+ const coerced = coerceRow(row, rowInfo);
904
962
 
905
963
  if (entityCache && entityName) {
906
- await entityCache.set(user.tenantId, entityName, payload.id, row);
964
+ await entityCache.set(user.tenantId, entityName, payload.id, coerced);
907
965
  }
908
966
 
909
- return row;
967
+ return coerced;
910
968
  },
911
969
  };
912
970
  }
package/src/db/index.ts CHANGED
@@ -4,15 +4,22 @@ export { seedConfigValues } from "./config-seed";
4
4
  export type { DbConnection, DbConnectionOptions, DbRow, DbRunner, DbTx } from "./connection";
5
5
  export { createDbConnection, dbConnectionOptionsFromEnv } from "./connection";
6
6
  export type { CursorQueryOptions, CursorResult } from "./cursor";
7
- export { applyCursorQuery, decodeCursor, encodeCursor } from "./cursor";
8
- export type { SelectQuery, TableColumns } from "./dialect";
7
+ export { decodeCursor, encodeCursor } from "./cursor";
8
+ export type { SchemaTable, SelectQuery, TableColumns } from "./dialect";
9
9
  export {
10
+ bigint,
11
+ bigserial,
10
12
  boolean,
13
+ index,
11
14
  instant,
15
+ instantToDriver,
12
16
  integer,
13
17
  jsonb,
18
+ moneyAmount,
19
+ numeric,
14
20
  primaryKey,
15
21
  serial,
22
+ sql,
16
23
  table,
17
24
  text,
18
25
  timestamp,
@@ -27,6 +34,16 @@ export {
27
34
  } from "./eagerload";
28
35
  export type { EncryptionProvider } from "./encryption";
29
36
  export { createEncryptionProvider } from "./encryption";
37
+ export type {
38
+ BuildEntityTableMetaOptions,
39
+ ColumnMeta,
40
+ CompositePrimaryKeyMeta,
41
+ EntityTableMeta,
42
+ IndexMeta,
43
+ PgType,
44
+ UnmanagedTableInput,
45
+ } from "./entity-table-meta";
46
+ export { buildEntityTableMeta, defineUnmanagedTable } from "./entity-table-meta";
30
47
  export type {
31
48
  EntityLifecycleVerb,
32
49
  EventStoreExecutor,
@@ -42,13 +59,24 @@ export {
42
59
  isUniqueViolation,
43
60
  type PgErrorInfo,
44
61
  } from "./pg-error";
62
+ export type { SelectOptions, WhereObject, WhereValue } from "./query-api";
63
+ export {
64
+ asRawClient,
65
+ deleteMany,
66
+ fetchOne,
67
+ insertMany,
68
+ insertOne,
69
+ selectMany,
70
+ transaction,
71
+ updateMany,
72
+ } from "./query-api";
45
73
  export { seedReferenceData } from "./reference-data";
46
- export { fetchOne } from "./row-helpers";
74
+ export { renderTableDdl, renderTablesDdl } from "./render-ddl";
47
75
  export { tableExists } from "./schema-inspection";
48
76
  export {
49
77
  buildBaseColumns,
50
- buildDrizzleTable,
51
- type DrizzleTable,
78
+ buildEntityTable,
79
+ type EntityTable,
52
80
  toSnakeCase,
53
81
  toTableName,
54
82
  } from "./table-builder";