@cosmicdrift/kumiko-framework 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/package.json +7 -7
  2. package/src/__tests__/{anonymous-access.integration.ts → anonymous-access.integration.test.ts} +12 -9
  3. package/src/__tests__/{error-contract.integration.ts → error-contract.integration.test.ts} +5 -4
  4. package/src/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +3 -3
  5. package/src/__tests__/{full-stack.integration.ts → full-stack.integration.test.ts} +7 -16
  6. package/src/__tests__/{ownership.integration.ts → ownership.integration.test.ts} +3 -2
  7. package/src/__tests__/{raw-table.integration.ts → raw-table.integration.test.ts} +18 -30
  8. package/src/__tests__/{reference-data.integration.ts → reference-data.integration.test.ts} +24 -11
  9. package/src/__tests__/{transition-guard.integration.ts → transition-guard.integration.test.ts} +12 -10
  10. package/src/api/__tests__/api.test.ts +1 -1
  11. package/src/api/__tests__/auth-middleware-transport.test.ts +1 -1
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +1 -1
  13. package/src/api/__tests__/{batch.integration.ts → batch.integration.test.ts} +30 -30
  14. package/src/api/__tests__/body-limit.test.ts +1 -1
  15. package/src/api/__tests__/csrf-middleware.test.ts +1 -1
  16. package/src/api/__tests__/{dispatcher-live.integration.ts → dispatcher-live.integration.test.ts} +10 -9
  17. package/src/api/__tests__/metrics-endpoint.test.ts +1 -1
  18. package/src/api/__tests__/{nested-write.integration.ts → nested-write.integration.test.ts} +13 -16
  19. package/src/api/__tests__/readiness.test.ts +1 -1
  20. package/src/api/__tests__/request-id-middleware.test.ts +1 -1
  21. package/src/api/__tests__/sse-broker.test.ts +12 -12
  22. package/src/api/__tests__/sse-route.test.ts +1 -1
  23. package/src/api/readiness.ts +2 -2
  24. package/src/auth/__tests__/roles.test.ts +2 -2
  25. package/src/bun-db/__tests__/PATTERN.md +73 -0
  26. package/src/bun-db/__tests__/_helpers.ts +103 -0
  27. package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
  28. package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
  29. package/src/bun-db/__tests__/bun-test-db.ts +19 -0
  30. package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
  31. package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
  32. package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
  33. package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
  34. package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
  35. package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
  36. package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
  37. package/src/bun-db/connection.ts +84 -0
  38. package/src/bun-db/index.ts +31 -0
  39. package/src/bun-db/query.ts +845 -0
  40. package/src/compliance/__tests__/duration-spec.test.ts +1 -1
  41. package/src/compliance/__tests__/profiles.test.ts +1 -1
  42. package/src/compliance/__tests__/sub-processors.test.ts +1 -1
  43. package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
  44. package/src/db/__tests__/big-int-field.test.ts +15 -14
  45. package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
  46. package/src/db/__tests__/compound-types.test.ts +1 -1
  47. package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
  48. package/src/db/__tests__/connection-options.test.ts +1 -1
  49. package/src/db/__tests__/dialect-instant.test.ts +1 -1
  50. package/src/db/__tests__/encryption.test.ts +1 -1
  51. package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
  52. package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
  53. package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
  54. package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
  55. package/src/db/__tests__/located-timestamp.test.ts +1 -1
  56. package/src/db/__tests__/money.test.ts +1 -1
  57. package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
  58. package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
  59. package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
  60. package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
  61. package/src/db/__tests__/sql-inventory.test.ts +56 -0
  62. package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
  63. package/src/db/__tests__/table-builder-required.test.ts +20 -22
  64. package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
  65. package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
  66. package/src/db/api.ts +46 -0
  67. package/src/db/apply-entity-event.ts +45 -36
  68. package/src/db/assert-exists-in.ts +5 -16
  69. package/src/db/bun-provider.ts +37 -0
  70. package/src/db/config-seed.ts +4 -4
  71. package/src/db/connection.ts +14 -57
  72. package/src/db/cursor.ts +5 -56
  73. package/src/db/dialect.ts +472 -99
  74. package/src/db/eagerload.ts +5 -12
  75. package/src/db/entity-table-meta.ts +390 -0
  76. package/src/db/event-store-executor.ts +158 -100
  77. package/src/db/index.ts +33 -5
  78. package/src/db/migrate-generator.ts +350 -0
  79. package/src/db/migrate-runner.ts +206 -0
  80. package/src/db/postgres-provider.ts +25 -0
  81. package/src/db/queries/entity-read.ts +15 -0
  82. package/src/db/queries/es-ops.ts +17 -0
  83. package/src/db/queries/event-consumer.ts +170 -0
  84. package/src/db/queries/event-store-admin.ts +127 -0
  85. package/src/db/queries/event-store.ts +155 -0
  86. package/src/db/queries/projection-rebuild.ts +59 -0
  87. package/src/db/queries/raw-sql.ts +15 -0
  88. package/src/db/queries/schema-drift.ts +35 -0
  89. package/src/db/queries/seed-context.ts +58 -0
  90. package/src/db/queries/table-ops.ts +11 -0
  91. package/src/db/queries/test-stack.ts +56 -0
  92. package/src/db/query-api.ts +22 -0
  93. package/src/db/query.ts +30 -0
  94. package/src/db/reference-data.ts +19 -22
  95. package/src/db/render-ddl.ts +57 -0
  96. package/src/db/row-helpers.ts +3 -52
  97. package/src/db/schema-inspection.ts +17 -4
  98. package/src/db/sql-inventory.ts +208 -0
  99. package/src/db/table-builder.ts +48 -40
  100. package/src/db/tenant-db.ts +105 -326
  101. package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
  102. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
  103. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
  104. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
  105. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
  106. package/src/engine/__tests__/boot-validator.test.ts +4 -3
  107. package/src/engine/__tests__/build-app-schema.test.ts +1 -1
  108. package/src/engine/__tests__/build-target.test.ts +1 -1
  109. package/src/engine/__tests__/claim-keys.test.ts +1 -1
  110. package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
  111. package/src/engine/__tests__/config-helpers.test.ts +1 -1
  112. package/src/engine/__tests__/effective-features.test.ts +1 -1
  113. package/src/engine/__tests__/engine.test.ts +1 -1
  114. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  115. package/src/engine/__tests__/event-helpers.test.ts +3 -3
  116. package/src/engine/__tests__/extends-registrar.test.ts +4 -4
  117. package/src/engine/__tests__/factories-long-text.test.ts +1 -1
  118. package/src/engine/__tests__/factories-time.test.ts +1 -1
  119. package/src/engine/__tests__/field-predicates.test.ts +1 -1
  120. package/src/engine/__tests__/hook-phases.test.ts +1 -1
  121. package/src/engine/__tests__/identifiers.test.ts +1 -1
  122. package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
  123. package/src/engine/__tests__/nav.test.ts +1 -1
  124. package/src/engine/__tests__/ownership.test.ts +10 -11
  125. package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
  126. package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
  127. package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
  128. package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
  129. package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
  130. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
  131. package/src/engine/__tests__/post-query-hook.test.ts +1 -1
  132. package/src/engine/__tests__/projection-helpers.test.ts +25 -17
  133. package/src/engine/__tests__/projection.test.ts +4 -4
  134. package/src/engine/__tests__/qualified-name.test.ts +1 -1
  135. package/src/engine/__tests__/raw-table.test.ts +9 -8
  136. package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
  137. package/src/engine/__tests__/run-in.test.ts +1 -1
  138. package/src/engine/__tests__/schema-builder.test.ts +1 -1
  139. package/src/engine/__tests__/screen.test.ts +1 -1
  140. package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
  141. package/src/engine/__tests__/state-machine.test.ts +1 -1
  142. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
  143. package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
  144. package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
  145. package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
  146. package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
  147. package/src/engine/__tests__/steps-read.test.ts +34 -40
  148. package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
  149. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
  150. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
  151. package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
  152. package/src/engine/__tests__/steps-workflow.test.ts +7 -7
  153. package/src/engine/__tests__/system-user.test.ts +1 -1
  154. package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
  155. package/src/engine/__tests__/validation-hooks.test.ts +1 -1
  156. package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
  157. package/src/engine/boot-validator/entity-handler.ts +3 -3
  158. package/src/engine/boot-validator/ownership.ts +1 -1
  159. package/src/engine/define-feature.ts +1 -2
  160. package/src/engine/entity-handlers.ts +5 -5
  161. package/src/engine/factories.ts +1 -1
  162. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
  163. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
  164. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
  165. package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
  166. package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
  167. package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
  168. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
  169. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
  170. package/src/engine/ownership.ts +113 -41
  171. package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
  172. package/src/engine/projection-helpers.ts +2 -11
  173. package/src/engine/registry.ts +2 -2
  174. package/src/engine/steps/read-find-many.ts +13 -13
  175. package/src/engine/steps/read-find-one.ts +7 -9
  176. package/src/engine/steps/unsafe-projection-delete.ts +4 -5
  177. package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
  178. package/src/engine/types/feature.ts +7 -2
  179. package/src/engine/types/fields.ts +4 -5
  180. package/src/engine/types/step.ts +10 -10
  181. package/src/engine/validate-projection-allowlist.ts +23 -3
  182. package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
  183. package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
  184. package/src/env/__tests__/compose-env-schema.test.ts +1 -1
  185. package/src/env/__tests__/dry-run.test.ts +1 -1
  186. package/src/errors/__tests__/classes.test.ts +1 -1
  187. package/src/errors/__tests__/write-failures.test.ts +1 -1
  188. package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
  189. package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
  190. package/src/es-ops/__tests__/runner.test.ts +29 -19
  191. package/src/es-ops/context.ts +9 -43
  192. package/src/es-ops/operations-schema.ts +2 -2
  193. package/src/es-ops/runner.ts +12 -26
  194. package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
  195. package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
  196. package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
  197. package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
  198. package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
  199. package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
  200. package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
  201. package/src/event-store/admin-api.ts +55 -83
  202. package/src/event-store/archive.ts +15 -39
  203. package/src/event-store/event-store.ts +92 -86
  204. package/src/event-store/events-schema.ts +2 -1
  205. package/src/event-store/index.ts +1 -0
  206. package/src/event-store/snapshot.ts +26 -24
  207. package/src/event-store/upcaster-dead-letter.ts +19 -18
  208. package/src/files/__tests__/content-disposition.test.ts +1 -1
  209. package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
  210. package/src/files/__tests__/file-handle.test.ts +1 -1
  211. package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
  212. package/src/files/__tests__/read-stream.test.ts +1 -1
  213. package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
  214. package/src/files/__tests__/write-stream.test.ts +1 -1
  215. package/src/files/__tests__/zip-stream.test.ts +1 -1
  216. package/src/files/file-ref-table.ts +2 -2
  217. package/src/files/file-routes.ts +7 -9
  218. package/src/files/storage-tracking.ts +9 -17
  219. package/src/i18n/__tests__/i18n.test.ts +1 -1
  220. package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
  221. package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
  222. package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
  223. package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
  224. package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
  225. package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
  226. package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
  227. package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
  228. package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
  229. package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
  230. package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
  231. package/src/migrations/projection-detection.ts +12 -1
  232. package/src/migrations/schema-drift.ts +7 -23
  233. package/src/observability/__tests__/console-provider.test.ts +1 -1
  234. package/src/observability/__tests__/metric-validator.test.ts +1 -1
  235. package/src/observability/__tests__/noop-provider.test.ts +1 -1
  236. package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
  237. package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
  238. package/src/observability/__tests__/recording-meter.test.ts +1 -1
  239. package/src/observability/__tests__/recording-tracer.test.ts +1 -1
  240. package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
  241. package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
  242. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
  243. package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
  244. package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
  245. package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
  246. package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
  247. package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
  248. package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
  249. package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
  250. package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
  251. package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
  252. package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
  253. package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
  254. package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
  255. package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
  256. package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
  257. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
  258. package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
  259. package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
  260. package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
  261. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
  262. package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
  263. package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
  264. package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
  265. package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
  266. package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
  267. package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
  268. package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
  269. package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
  270. package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
  271. package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
  272. package/src/pipeline/cascade-handler.ts +13 -21
  273. package/src/pipeline/dispatcher.ts +43 -48
  274. package/src/pipeline/event-consumer-state.ts +11 -2
  275. package/src/pipeline/event-dispatcher.ts +86 -146
  276. package/src/pipeline/event-retention.ts +14 -24
  277. package/src/pipeline/msp-rebuild.ts +54 -78
  278. package/src/pipeline/projection-rebuild.ts +65 -67
  279. package/src/pipeline/projection-state.ts +2 -2
  280. package/src/random/__tests__/generate.test.ts +13 -13
  281. package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
  282. package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
  283. package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
  284. package/src/redis/__tests__/redis-options.test.ts +1 -1
  285. package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
  286. package/src/search/__tests__/search-adapter.test.ts +1 -1
  287. package/src/secrets/__tests__/dek-cache.test.ts +1 -3
  288. package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
  289. package/src/secrets/__tests__/envelope.test.ts +1 -1
  290. package/src/secrets/__tests__/leak-guard.test.ts +1 -1
  291. package/src/secrets/__tests__/rotation.test.ts +1 -1
  292. package/src/stack/db.ts +25 -48
  293. package/src/stack/push-entity-projection-tables.ts +2 -4
  294. package/src/stack/table-helpers.ts +98 -61
  295. package/src/stack/test-stack.ts +8 -7
  296. package/src/testing/__tests__/db-cleanup.test.ts +40 -0
  297. package/src/testing/__tests__/e2e-generator.test.ts +1 -1
  298. package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
  299. package/src/testing/db-cleanup.ts +44 -0
  300. package/src/testing/expect-error.ts +1 -1
  301. package/src/testing/index.ts +2 -0
  302. package/src/testing/multipart-helper.ts +94 -0
  303. package/src/testing/shared-entities.ts +5 -5
  304. package/src/time/__tests__/polyfill.test.ts +1 -1
  305. package/src/time/__tests__/tz-context.test.ts +1 -1
  306. package/src/utils/__tests__/assert.test.ts +1 -1
  307. package/src/utils/__tests__/env-parse.test.ts +1 -1
  308. package/CHANGELOG.md +0 -472
  309. package/src/db/__tests__/cursor.test.ts +0 -41
  310. package/src/db/__tests__/db-helpers.test.ts +0 -369
  311. package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
  312. package/src/db/__tests__/row-helpers.test.ts +0 -59
  313. package/src/engine/steps/_drizzle-boundary.ts +0 -19
  314. package/src/files/__tests__/file-field-column.integration.ts +0 -103
@@ -0,0 +1,22 @@
1
+ // Legacy re-export shim — query-api.ts hat früher drizzle gewrapped.
2
+ // Heute liegen die Helpers in src/bun-db/. Wir re-exportieren von dort
3
+ // damit existing imports `@cosmicdrift/kumiko-framework/db` weiterhin
4
+ // funktionieren während wir die Callers schrittweise auf den direkten
5
+ // bun-db-Import migrieren.
6
+
7
+ export type {
8
+ SelectOptions,
9
+ WhereObject,
10
+ WhereOperator,
11
+ WhereValue,
12
+ } from "../db/query";
13
+ export {
14
+ asRawClient,
15
+ deleteMany,
16
+ fetchOne,
17
+ insertMany,
18
+ insertOne,
19
+ selectMany,
20
+ transaction,
21
+ updateMany,
22
+ } from "../db/query";
@@ -0,0 +1,30 @@
1
+ // Provider-neutrale Query-API. Re-exported aus bun-db/query.
2
+ // Alle Consumer importieren von hier, nicht direkt aus bun-db/.
3
+ export {
4
+ type AnyDb,
5
+ asRawClient,
6
+ coerceRow,
7
+ countWhere,
8
+ type DeleteManyBatchedOptions,
9
+ type DeleteManyBatchedResult,
10
+ deleteMany,
11
+ deleteManyBatched,
12
+ extractTableInfo,
13
+ fetchOne,
14
+ type IncrementCounterOptions,
15
+ incrementCounter,
16
+ insertMany,
17
+ insertOne,
18
+ type OrderByClause,
19
+ type SelectOptions,
20
+ selectMany,
21
+ type TableInfo,
22
+ transaction,
23
+ type UpsertOnConflictOptions,
24
+ updateMany,
25
+ upsertByPk,
26
+ upsertOnConflict,
27
+ type WhereObject,
28
+ type WhereOperator,
29
+ type WhereValue,
30
+ } from "../bun-db/query";
@@ -1,13 +1,20 @@
1
- import { eq } from "drizzle-orm";
1
+ import { fetchOne, insertOne, updateMany } from "../db/query";
2
2
  import type { ReferenceDataDef } from "../engine/types";
3
3
  import { SYSTEM_TENANT_ID } from "../engine/types";
4
4
  import type { DbConnection, DbRow } from "./connection";
5
5
  import type { TableColumns } from "./dialect";
6
- import { toSnakeCase } from "./table-builder";
7
6
 
8
7
  // biome-ignore lint/suspicious/noExplicitAny: Drizzle dynamic tables
9
8
  type Table = TableColumns<any>;
10
9
 
10
+ const KUMIKO_COLUMNS_SYMBOL = Symbol.for("kumiko:schema:Columns");
11
+
12
+ function hasColumn(table: Table, field: string): boolean {
13
+ const cols = (table as Record<symbol, unknown>)[KUMIKO_COLUMNS_SYMBOL];
14
+ if (typeof cols !== "object" || cols === null) return false;
15
+ return field in (cols as Record<string, unknown>);
16
+ }
17
+
11
18
  /**
12
19
  * Seed reference data at boot time.
13
20
  * For each ReferenceDataDef: upsert rows (insert missing, update changed, never delete).
@@ -31,43 +38,33 @@ export async function seedReferenceData(
31
38
  const firstKey = Object.keys(firstRow)[0];
32
39
  if (!firstKey) continue;
33
40
  const upsertKey = def.upsertKey ?? firstKey;
34
- const snakeKey = toSnakeCase(upsertKey);
35
41
 
36
42
  for (const row of def.data) {
37
43
  const keyValue = row[upsertKey];
38
44
  if (keyValue === undefined) continue;
39
45
 
40
- // Check if row exists
41
- const [existing] = await db
42
- .select()
43
- .from(table)
44
- .where(eq(table[upsertKey] ?? table[snakeKey], keyValue))
45
- .limit(1);
46
+ const existing = (await fetchOne(db, table, { [upsertKey]: keyValue })) as DbRow | undefined;
46
47
 
47
48
  if (existing) {
48
- // Update if any field changed
49
- const existingData = existing as DbRow;
50
49
  const changes: Record<string, unknown> = {};
51
50
  for (const [field, value] of Object.entries(row)) {
52
51
  if (field === upsertKey) continue;
53
- if (existingData[field] !== value) {
52
+ if (existing[field] !== value) {
54
53
  changes[field] = value;
55
54
  }
56
55
  }
57
56
  if (Object.keys(changes).length > 0) {
58
- await db
59
- .update(table)
60
- .set(changes)
61
- .where(eq(table[upsertKey] ?? table[snakeKey], keyValue));
57
+ await updateMany(db, table, changes, { [upsertKey]: keyValue });
62
58
  updated++;
63
59
  }
64
60
  } else {
65
- await db.insert(table).values({
66
- ...row,
67
- tenantId: SYSTEM_TENANT_ID,
68
- version: 1,
69
- insertedAt: Temporal.Now.instant(),
70
- });
61
+ // Only add framework columns if the table actually has them.
62
+ // Drizzle used to filter extra fields silently; bunInsertOne doesn't.
63
+ const values: Record<string, unknown> = { ...row };
64
+ if (hasColumn(table, "tenantId")) values["tenantId"] = SYSTEM_TENANT_ID;
65
+ if (hasColumn(table, "version")) values["version"] = 1;
66
+ if (hasColumn(table, "insertedAt")) values["insertedAt"] = Temporal.Now.instant();
67
+ await insertOne(db, table, values);
71
68
  inserted++;
72
69
  }
73
70
  }
@@ -0,0 +1,57 @@
1
+ // Pure renderer: EntityTableMeta → SQL DDL statements.
2
+ // Wird vom Migrate-Generator (Phase 2 — CLI-Tool `kumiko migrate generate`)
3
+ // genutzt um initial-SQL-Files zu schreiben. Output ist Start-Form für
4
+ // User-Review — App-Author darf das SQL danach hand-editieren (extra-Index,
5
+ // partial-Index, BRIN, custom-clauses) bevor committed wird.
6
+ //
7
+ // NO-MAGIC-ON-DATA: dieser Renderer wird NIE zur App-Runtime aufgerufen.
8
+ // Nur Build-Step. Runner liest checked-in SQL, nicht Renderer-Output.
9
+
10
+ import { pgTypeToSqlType } from "./dialect";
11
+ import type { ColumnMeta, EntityTableMeta, IndexMeta } from "./entity-table-meta";
12
+
13
+ function quoteIdent(name: string): string {
14
+ return `"${name.replace(/"/g, '""')}"`;
15
+ }
16
+
17
+ function renderColumn(col: ColumnMeta): string {
18
+ const parts: string[] = [quoteIdent(col.name), pgTypeToSqlType(col.pgType)];
19
+ if (col.identity) parts.push("GENERATED ALWAYS AS IDENTITY");
20
+ if (col.primaryKey) parts.push("PRIMARY KEY");
21
+ if (col.defaultSql !== undefined) parts.push(`DEFAULT ${col.defaultSql}`);
22
+ if (col.notNull && !col.primaryKey) parts.push("NOT NULL");
23
+ return parts.join(" ");
24
+ }
25
+
26
+ function renderIndex(tableName: string, idx: IndexMeta): string {
27
+ const kind = idx.unique === true ? "UNIQUE INDEX" : "INDEX";
28
+ const colList = idx.columns.map(quoteIdent).join(", ");
29
+ if (idx.needsManualWhere === true) {
30
+ return [
31
+ `-- WARN: partial-index "${idx.name}" needs a WHERE clause that the`,
32
+ `-- generator can't render (entity uses drizzle sql\`…\` AST).`,
33
+ `-- Add the WHERE manually before applying:`,
34
+ `-- CREATE ${kind} IF NOT EXISTS ${quoteIdent(idx.name)} ON ${quoteIdent(tableName)} (${colList}) WHERE <your-condition>;`,
35
+ ].join("\n");
36
+ }
37
+ const where = idx.whereSql !== undefined ? ` WHERE ${idx.whereSql}` : "";
38
+ return `CREATE ${kind} IF NOT EXISTS ${quoteIdent(idx.name)} ON ${quoteIdent(tableName)} (${colList})${where};`;
39
+ }
40
+
41
+ export function renderTableDdl(meta: EntityTableMeta): readonly string[] {
42
+ const colLines = meta.columns.map(renderColumn);
43
+ const lines: string[] = [...colLines];
44
+ if (meta.compositePrimaryKey !== undefined) {
45
+ const pkCols = meta.compositePrimaryKey.columns.map(quoteIdent).join(",");
46
+ lines.push(`CONSTRAINT ${quoteIdent(meta.compositePrimaryKey.name)} PRIMARY KEY(${pkCols})`);
47
+ }
48
+ const create = `CREATE TABLE IF NOT EXISTS ${quoteIdent(meta.tableName)} (\n ${lines.join(",\n ")}\n);`;
49
+ const indexes = meta.indexes.map((idx) => renderIndex(meta.tableName, idx));
50
+ return [create, ...indexes];
51
+ }
52
+
53
+ export function renderTablesDdl(metas: readonly EntityTableMeta[]): readonly string[] {
54
+ const stmts: string[] = [];
55
+ for (const m of metas) stmts.push(...renderTableDdl(m));
56
+ return stmts;
57
+ }
@@ -1,53 +1,4 @@
1
- import { and, type SQL } from "drizzle-orm";
2
- import type { DbRow } from "./connection";
3
- import type { TableColumns } from "./dialect";
1
+ // Legacy re-export shim fetchOne lebt jetzt in src/bun-db/query.ts.
4
2
 
5
- // biome-ignore lint/suspicious/noExplicitAny: Mirrors the erased ProjectionTable / event-store-executor pattern — the framework doesn't know user column shapes.
6
- type AnyTable = TableColumns<any>;
7
-
8
- // Minimal DB surface fetchOne uses — structurally satisfied by raw DbRunner
9
- // (connection / tx) AND TenantDb (tenant-scoped wrapper). Both expose the
10
- // same `select().from().where().limit()` chain with compatible rows, so the
11
- // helper types against the shared shape instead of a union that TS can't
12
- // narrow cleanly.
13
- type SelectChainDb = {
14
- select: () => {
15
- from: (table: AnyTable) => {
16
- where: (cond: SQL | undefined) => {
17
- limit: (n: number) => PromiseLike<readonly Record<string, unknown>[]>;
18
- };
19
- };
20
- };
21
- };
22
-
23
- // SELECT * FROM <table> WHERE <...conditions> LIMIT 1 → first row or undefined.
24
- // Collapses the "const [row] = await db.select()...limit(1)" destructure
25
- // that repeats in every detail-query-style handler and existence-check.
26
- //
27
- // Conditions are variadic and non-empty — the tuple `[SQL, ...SQL[]]` rejects
28
- // `fetchOne(db, table)` (would silently pick any row) and `fetchOne(db, table,
29
- // undefined)` (would do the same) at compile time. Multiple conditions are
30
- // combined with AND.
31
- //
32
- // const existing = await fetchOne<{ id: number }>(db, userTable,
33
- // eq(userTable.email, payload.email));
34
- // if (existing) return writeFailure(new ConflictError({ ... }));
35
- //
36
- // const row = await fetchOne(db, membershipTable,
37
- // eq(membershipTable.userId, userId),
38
- // eq(membershipTable.tenantId, tenantId),
39
- // );
40
- //
41
- // For dynamic condition arrays (length known only at runtime), spread
42
- // explicitly: `fetchOne(db, table, first, ...rest)`. Raw `...arr` with
43
- // `arr: SQL[]` won't type-check because TS can't prove the array is non-
44
- // empty — a feature, not a bug.
45
- export async function fetchOne<TRow = DbRow>(
46
- db: SelectChainDb,
47
- table: AnyTable,
48
- ...conditions: readonly [SQL, ...SQL[]]
49
- ): Promise<TRow | undefined> {
50
- const where = conditions.length === 1 ? conditions[0] : and(...conditions);
51
- const rows = await db.select().from(table).where(where).limit(1);
52
- return rows[0] as TRow | undefined; // @cast-boundary db-row
53
- }
3
+ export type { WhereObject } from "../db/query";
4
+ export { fetchOne } from "../db/query";
@@ -1,4 +1,3 @@
1
- import { sql } from "drizzle-orm";
2
1
  import type { DbConnection, DbTx } from "./connection";
3
2
 
4
3
  // True when `<fullyQualifiedName>` refers to an existing relation in the
@@ -18,8 +17,22 @@ export async function tableExists(
18
17
  db: DbConnection | DbTx,
19
18
  fullyQualifiedName: string,
20
19
  ): Promise<boolean> {
21
- const rows = await db.execute<{ exists: boolean }>(
22
- sql`SELECT to_regclass(${fullyQualifiedName}) IS NOT NULL AS exists`,
23
- );
20
+ const dbAny = db as unknown as {
21
+ $client?: {
22
+ unsafe: (s: string, p?: readonly unknown[]) => Promise<readonly { exists: boolean }[]>;
23
+ };
24
+ session?: {
25
+ client?: {
26
+ unsafe: (s: string, p?: readonly unknown[]) => Promise<readonly { exists: boolean }[]>;
27
+ };
28
+ };
29
+ unsafe?: (s: string, p?: readonly unknown[]) => Promise<readonly { exists: boolean }[]>;
30
+ };
31
+ const client = dbAny.$client ?? dbAny.session?.client ?? dbAny;
32
+ const rows = await (
33
+ client as {
34
+ unsafe: (s: string, p?: readonly unknown[]) => Promise<readonly { exists: boolean }[]>;
35
+ }
36
+ ).unsafe(`SELECT to_regclass($1) IS NOT NULL AS exists`, [fullyQualifiedName]);
24
37
  return rows[0]?.exists ?? false;
25
38
  }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Raw-SQL inventory — shared allowlist for `kumiko sql-inventory` and
3
+ * `guard-raw-sql` (Phase 5). Scans TypeScript sources for escape-hatch patterns.
4
+ *
5
+ * Bun-only I/O: Bun.Glob + Bun.file (no node:fs, no node:path).
6
+ */
7
+
8
+ /** POSIX path join without Node path module. */
9
+ export function joinPath(base: string, ...segments: string[]): string {
10
+ return [base, ...segments]
11
+ .join("/")
12
+ .replace(/\/+/g, "/")
13
+ .replace(/\/\.\//g, "/");
14
+ }
15
+
16
+ export type SqlInventoryKind = "unsafe" | "asRawClient" | "delete_from" | "execute";
17
+
18
+ export type SqlInventoryHit = {
19
+ readonly file: string;
20
+ readonly line: number;
21
+ readonly kind: SqlInventoryKind;
22
+ readonly allowed: boolean;
23
+ readonly snippet: string;
24
+ };
25
+
26
+ export type SqlInventoryReport = {
27
+ readonly scannedAt: string;
28
+ readonly root: string;
29
+ readonly hits: readonly SqlInventoryHit[];
30
+ readonly summary: {
31
+ readonly total: number;
32
+ readonly disallowed: number;
33
+ readonly byKind: Readonly<Record<SqlInventoryKind, number>>;
34
+ readonly byBucket: {
35
+ readonly allowed: number;
36
+ readonly tests: number;
37
+ readonly disallowed: number;
38
+ };
39
+ };
40
+ };
41
+
42
+ /** Paths where `.unsafe()` / `asRawClient()` are permitted (Phase 5 guard). */
43
+ export const RAW_SQL_ALLOWLIST: ReadonlyArray<RegExp> = [
44
+ /\/packages\/framework\/src\/db\/queries\//,
45
+ /\/packages\/framework\/src\/db\/migrate-runner\.ts$/,
46
+ /\/packages\/framework\/src\/db\/schema-inspection\.ts$/,
47
+ /\/packages\/framework\/src\/db\/render-ddl\.ts$/,
48
+ /\/packages\/framework\/src\/db\/sql-inventory\.ts$/,
49
+ /\/packages\/framework\/src\/bun-db\/query\.ts$/,
50
+ /\/packages\/framework\/src\/testing\//,
51
+ /\/packages\/bundled-features\/src\/[^/]+\/db\/queries\//,
52
+ /\/packages\/framework\/src\/engine\/steps\/unsafe-projection-/,
53
+ /\/samples\/(apps|recipes)\/[^/]+\/src\/db\/queries\//,
54
+ /\/bin\/commands\//,
55
+ /\/scripts\/codemod-/,
56
+ /\/__tests__\//,
57
+ /\/scripts\/sql-inventory\.ts$/,
58
+ /\/bin\/_lib\//,
59
+ ];
60
+
61
+ const SCAN_DIRS = ["packages", "samples", "scripts", "bin"] as const;
62
+
63
+ const SKIP_PATH_PARTS = ["/node_modules/", "/dist/", "/.kumiko/"] as const;
64
+
65
+ const PATTERNS: ReadonlyArray<{ readonly kind: SqlInventoryKind; readonly re: RegExp }> = [
66
+ { kind: "unsafe", re: /\.unsafe\s*\(/ },
67
+ { kind: "asRawClient", re: /asRawClient\s*\(/ },
68
+ { kind: "delete_from", re: /DELETE\s+FROM/i },
69
+ { kind: "execute", re: /\.execute\s*\(/ },
70
+ ];
71
+
72
+ const TS_GLOB = new Bun.Glob("**/*.{ts,tsx}");
73
+
74
+ function normalizePathForMatch(filePath: string): string {
75
+ return filePath.startsWith("/") ? filePath : `/${filePath}`;
76
+ }
77
+
78
+ export function isRawSqlAllowed(filePath: string): boolean {
79
+ const normalized = normalizePathForMatch(filePath);
80
+ return RAW_SQL_ALLOWLIST.some((re) => re.test(normalized));
81
+ }
82
+
83
+ function isTestPath(filePath: string): boolean {
84
+ return /\/__tests__\//.test(normalizePathForMatch(filePath));
85
+ }
86
+
87
+ function bucketFor(hit: SqlInventoryHit): "allowed" | "tests" | "disallowed" {
88
+ if (isTestPath(hit.file)) return "tests";
89
+ if (hit.allowed) return "allowed";
90
+ return "disallowed";
91
+ }
92
+
93
+ function shouldSkipRelativePath(rel: string): boolean {
94
+ return SKIP_PATH_PARTS.some((part) => rel.includes(part));
95
+ }
96
+
97
+ function directoryExists(path: string): boolean {
98
+ return Bun.spawnSync(["test", "-d", path]).exitCode === 0;
99
+ }
100
+
101
+ async function collectTsFiles(repoRoot: string): Promise<string[]> {
102
+ const out: string[] = [];
103
+ for (const sub of SCAN_DIRS) {
104
+ const cwd = joinPath(repoRoot, sub);
105
+ if (!directoryExists(cwd)) continue;
106
+ for await (const rel of TS_GLOB.scan({ cwd, onlyFiles: true })) {
107
+ const normalized = rel.replace(/\0/g, "");
108
+ if (!normalized || shouldSkipRelativePath(normalized)) continue;
109
+ out.push(joinPath(sub, normalized));
110
+ }
111
+ }
112
+ return out;
113
+ }
114
+
115
+ function scanFileText(relPath: string, text: string, hits: SqlInventoryHit[]): void {
116
+ const lines = text.split("\n");
117
+ for (let i = 0; i < lines.length; i++) {
118
+ const line = lines[i] ?? "";
119
+ const trimmed = line.trim();
120
+ if (
121
+ trimmed.startsWith("//") ||
122
+ trimmed.startsWith("*") ||
123
+ trimmed.startsWith("/**") ||
124
+ trimmed.startsWith("/*")
125
+ ) {
126
+ continue;
127
+ }
128
+ for (const { kind, re } of PATTERNS) {
129
+ if (!re.test(line)) continue;
130
+ hits.push({
131
+ file: relPath,
132
+ line: i + 1,
133
+ kind,
134
+ allowed: isRawSqlAllowed(relPath),
135
+ snippet: trimmed.slice(0, 120),
136
+ });
137
+ }
138
+ }
139
+ }
140
+
141
+ export async function scanRepo(repoRoot: string): Promise<SqlInventoryReport> {
142
+ const relFiles = await collectTsFiles(repoRoot);
143
+ const hits: SqlInventoryHit[] = [];
144
+
145
+ for (const rel of relFiles) {
146
+ const abs = joinPath(repoRoot, rel);
147
+ const text = await Bun.file(abs).text();
148
+ scanFileText(rel, text, hits);
149
+ }
150
+
151
+ const byKind: Record<SqlInventoryKind, number> = {
152
+ unsafe: 0,
153
+ asRawClient: 0,
154
+ delete_from: 0,
155
+ execute: 0,
156
+ };
157
+ let disallowed = 0;
158
+ const byBucket = { allowed: 0, tests: 0, disallowed: 0 };
159
+ for (const h of hits) {
160
+ byKind[h.kind]++;
161
+ const b = bucketFor(h);
162
+ byBucket[b]++;
163
+ if (b === "disallowed") disallowed++;
164
+ }
165
+
166
+ return {
167
+ scannedAt: new Date().toISOString(),
168
+ root: repoRoot,
169
+ hits,
170
+ summary: {
171
+ total: hits.length,
172
+ disallowed,
173
+ byKind,
174
+ byBucket,
175
+ },
176
+ };
177
+ }
178
+
179
+ export function formatReport(report: SqlInventoryReport): string {
180
+ const lines: string[] = [
181
+ "--- sql inventory ---",
182
+ ` scanned: ${report.scannedAt}`,
183
+ ` root: ${report.root}`,
184
+ ` total: ${report.summary.total}`,
185
+ ` allowed: ${report.summary.byBucket.allowed}`,
186
+ ` tests: ${report.summary.byBucket.tests}`,
187
+ ` disallowed:${report.summary.disallowed}`,
188
+ ` unsafe: ${report.summary.byKind.unsafe}`,
189
+ ` asRawClient:${report.summary.byKind.asRawClient}`,
190
+ ` DELETE FROM strings: ${report.summary.byKind.delete_from}`,
191
+ ` .execute: ${report.summary.byKind.execute}`,
192
+ "---",
193
+ ];
194
+
195
+ const bad = report.hits.filter((h) => bucketFor(h) === "disallowed");
196
+ if (bad.length === 0) {
197
+ lines.push(" (no disallowed production hits)");
198
+ } else {
199
+ lines.push(" disallowed (production):");
200
+ for (const h of bad.slice(0, 40)) {
201
+ lines.push(` ${h.kind.padEnd(12)} ${h.file}:${h.line} ${h.snippet}`);
202
+ }
203
+ if (bad.length > 40) {
204
+ lines.push(` … +${bad.length - 40} more`);
205
+ }
206
+ }
207
+ return lines.join("\n");
208
+ }
@@ -1,5 +1,3 @@
1
- import { sql } from "drizzle-orm";
2
- import type { AnyPgColumn } from "drizzle-orm/pg-core";
3
1
  import type {
4
2
  EntityDefinition,
5
3
  EntityRelations,
@@ -10,29 +8,31 @@ import { assertUnreachable } from "../utils";
10
8
  import {
11
9
  bigint,
12
10
  boolean,
11
+ type ColumnBuilder,
12
+ type ColumnHandle,
13
+ type IndexBuilderWithCols,
13
14
  index,
14
15
  instant,
15
16
  integer,
16
17
  jsonb,
17
18
  moneyAmount,
18
19
  table as pgTable,
20
+ type SqlExpression,
19
21
  serial,
22
+ sql,
20
23
  type TableColumns,
21
24
  text,
22
25
  uniqueIndex,
23
26
  uuid,
24
27
  } from "./dialect";
25
28
 
26
- type ColumnBuilder =
27
- | ReturnType<typeof text>
28
- | ReturnType<typeof integer>
29
- | ReturnType<typeof bigint>
30
- | ReturnType<typeof boolean>
31
- | ReturnType<typeof moneyAmount>
32
- | ReturnType<typeof jsonb>
33
- | ReturnType<typeof instant>
34
- | ReturnType<typeof serial>
35
- | ReturnType<typeof uuid>;
29
+ // Local AnyPgColumn alias — kept for legacy field-definition callers that
30
+ // still import this name as a type. ColumnHandle from the native dialect
31
+ // matches the same role (snake_case name + sql type accessor).
32
+ export type AnyPgColumn = ColumnHandle;
33
+
34
+ // biome-ignore lint/suspicious/noExplicitAny: ColumnBuilder is parameterised over value type; we erase here
35
+ type AnyColumnBuilder = ColumnBuilder<any>;
36
36
 
37
37
  // Returns column(s) for a field. Most fields return a single entry,
38
38
  // money returns two (amount + currency), files/images return none.
@@ -48,7 +48,7 @@ function fieldToColumns(
48
48
  name: string,
49
49
  field: FieldDefinition,
50
50
  entity: EntityDefinition,
51
- ): Record<string, ColumnBuilder> {
51
+ ): Record<string, AnyColumnBuilder> {
52
52
  const snakeName = toSnakeCase(name);
53
53
 
54
54
  switch (field.type) {
@@ -246,10 +246,13 @@ export function toTableName(entityName: string): string {
246
246
  // fieldToColumns passen. Type-Tests gegen repräsentative Entities (siehe
247
247
  // db/__tests__/drizzle-table-types.test.ts) catchen Drift.
248
248
 
249
- // Single drizzle column with concrete data + nullability preserves
250
- // Drizzle's `.select`/`eq`/`lt`-Inferenz für T.
251
- type Col<T> = AnyPgColumn<{ data: T; notNull: true }>;
252
- type NullCol<T> = AnyPgColumn<{ data: T; notNull: false }>;
249
+ // Single column handle with concrete data + nullability phantom. After the
250
+ // drizzle removal the runtime carries only the snake_case name + pg type
251
+ // (see ColumnHandle); the phantom-typed wrapper preserves the existing
252
+ // generic-inference call-sites without recreating drizzle's full column
253
+ // brand graph.
254
+ type Col<_T> = ColumnHandle & { readonly __notNull: true };
255
+ type NullCol<_T> = ColumnHandle & { readonly __notNull: false };
253
256
 
254
257
  // Per-field column shape — matches `fieldToColumns`. Money +
255
258
  // locatedTimestamp produce two-column pairs; files/images contribute no
@@ -345,7 +348,7 @@ type SoftDeleteColumnsType = {
345
348
  readonly deletedById: NullCol<string>;
346
349
  };
347
350
 
348
- export type DrizzleTable<E extends EntityDefinition = EntityDefinition> =
351
+ export type EntityTable<E extends EntityDefinition = EntityDefinition> =
349
352
  TableColumns<// biome-ignore lint/suspicious/noExplicitAny: drizzle's internal table-config stays generic; we layer typed columns on top via the intersection below.
350
353
  any> &
351
354
  BaseColumnsType<E> &
@@ -389,7 +392,7 @@ export function buildBaseColumns(softDelete: boolean, idType: "serial" | "uuid"
389
392
  return base;
390
393
  }
391
394
 
392
- export type BuildDrizzleTableOptions = {
395
+ export type BuildEntityTableOptions = {
393
396
  readonly featureName?: string;
394
397
  // Relations declared for this entity. When present, every belongsTo
395
398
  // foreignKey gets an index — otherwise joins and `WHERE fk = ?` filters
@@ -398,13 +401,13 @@ export type BuildDrizzleTableOptions = {
398
401
  readonly relations?: EntityRelations;
399
402
  };
400
403
 
401
- export function buildDrizzleTable<E extends EntityDefinition>(
404
+ export function buildEntityTable<E extends EntityDefinition>(
402
405
  entityName: string,
403
406
  entity: E,
404
- options?: BuildDrizzleTableOptions,
405
- ): DrizzleTable<E> {
407
+ options?: BuildEntityTableOptions,
408
+ ): EntityTable<E> {
406
409
  const baseColumns = buildBaseColumns(entity.softDelete ?? false, entity.idType ?? "uuid");
407
- const fieldColumns: Record<string, ColumnBuilder> = {};
410
+ const fieldColumns: Record<string, AnyColumnBuilder> = {};
408
411
 
409
412
  for (const [name, field] of Object.entries(entity.fields)) {
410
413
  const cols = fieldToColumns(name, field, entity);
@@ -444,7 +447,7 @@ export function buildDrizzleTable<E extends EntityDefinition>(
444
447
  }
445
448
  }
446
449
 
447
- // Cast back to DrizzleTable<E>: drizzle-kit's pgTable returns a fully
450
+ // Cast back to EntityTable<E>: drizzle-kit's pgTable returns a fully
448
451
  // inferred PgTableWithColumns over the *exact* column-builder map we
449
452
  // hand in. Our typed signature narrows that to the static names from
450
453
  // EntityDefinition (kept in sync with fieldToColumns + buildBaseColumns).
@@ -457,14 +460,20 @@ export function buildDrizzleTable<E extends EntityDefinition>(
457
460
  },
458
461
  // Every multi-tenant query filters by tenant_id. Without this index, list
459
462
  // queries scan the whole table across all tenants. Applies to every table
460
- // built via buildDrizzleTable since every entity inherits tenantId.
461
- // biome-ignore lint/suspicious/noExplicitAny: Drizzle's table callback is generic; we access columns by their JS property name.
462
- (table: any) => {
463
- const indexes = [index(`${tableName}_tenant_id_idx`).on(table.tenantId)];
463
+ // built via buildEntityTable since every entity inherits tenantId.
464
+ (table) => {
465
+ const indexes: Record<string, IndexBuilderWithCols> = {};
466
+ const tHandle = table as unknown as Record<string, ColumnHandle>;
467
+ indexes[`${tableName}_tenant_id_idx`] = index(`${tableName}_tenant_id_idx`).on(
468
+ // biome-ignore lint/style/noNonNullAssertion: tenantId column always exists on entity tables
469
+ tHandle["tenantId"]!,
470
+ );
464
471
  for (const fieldName of foreignKeyFields) {
465
- const column = table[fieldName];
472
+ const column = tHandle[fieldName];
466
473
  if (column) {
467
- indexes.push(index(`${tableName}_${toSnakeCase(fieldName)}_idx`).on(column));
474
+ indexes[`${tableName}_${toSnakeCase(fieldName)}_idx`] = index(
475
+ `${tableName}_${toSnakeCase(fieldName)}_idx`,
476
+ ).on(column);
468
477
  }
469
478
  }
470
479
  // entity.indexes = composite/unique-Indices die der Author explizit
@@ -473,23 +482,22 @@ export function buildDrizzleTable<E extends EntityDefinition>(
473
482
  // — Override via index.name möglich.
474
483
  for (const def of entity.indexes ?? []) {
475
484
  const cols = def.columns
476
- .map((fieldName) => table[fieldName])
477
- .filter((col): col is unknown => col !== undefined);
478
- if (cols.length !== def.columns.length) continue; // Boot-Validator catched das
485
+ .map((fieldName) => tHandle[fieldName])
486
+ .filter((col): col is ColumnHandle => col !== undefined);
487
+ if (cols.length !== def.columns.length) continue;
479
488
  const suffix = def.unique === true ? "unique" : "idx";
480
489
  const indexName =
481
490
  def.name ?? `${tableName}_${def.columns.map((c) => toSnakeCase(c)).join("_")}_${suffix}`;
482
491
  const builder = def.unique === true ? uniqueIndex(indexName) : index(indexName);
483
- // biome-ignore lint/suspicious/noExplicitAny: drizzle's .on(...cols) is variadic generic
484
- let chain = (builder.on as any)(...cols); // @cast-boundary drizzle-bridge
492
+ let chain = builder.on(...cols);
493
+ // entity.indexes[].where is now a SqlExpression (was drizzle SQL).
494
+ // Pass through to the IndexBuilderWithCols.where()-API.
485
495
  if (def.where !== undefined) {
486
- // Partial-Index: drizzle's IndexBuilder.where(SQL) emittiert das
487
- // `WHERE <condition>` ans Ende der `CREATE [UNIQUE] INDEX`-DDL.
488
- chain = chain.where(def.where);
496
+ chain = chain.where(def.where as SqlExpression);
489
497
  }
490
- indexes.push(chain);
498
+ indexes[indexName] = chain;
491
499
  }
492
500
  return indexes;
493
501
  },
494
- ) as unknown as DrizzleTable<E>;
502
+ ) as unknown as EntityTable<E>;
495
503
  }