@cosmicdrift/kumiko-framework 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/package.json +6 -6
  2. package/src/__tests__/{anonymous-access.integration.ts → anonymous-access.integration.test.ts} +12 -9
  3. package/src/__tests__/{error-contract.integration.ts → error-contract.integration.test.ts} +5 -4
  4. package/src/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +3 -3
  5. package/src/__tests__/{full-stack.integration.ts → full-stack.integration.test.ts} +7 -16
  6. package/src/__tests__/{ownership.integration.ts → ownership.integration.test.ts} +3 -2
  7. package/src/__tests__/{raw-table.integration.ts → raw-table.integration.test.ts} +18 -30
  8. package/src/__tests__/{reference-data.integration.ts → reference-data.integration.test.ts} +24 -11
  9. package/src/__tests__/{transition-guard.integration.ts → transition-guard.integration.test.ts} +12 -10
  10. package/src/api/__tests__/api.test.ts +1 -1
  11. package/src/api/__tests__/auth-middleware-transport.test.ts +1 -1
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +1 -1
  13. package/src/api/__tests__/{batch.integration.ts → batch.integration.test.ts} +30 -30
  14. package/src/api/__tests__/body-limit.test.ts +1 -1
  15. package/src/api/__tests__/csrf-middleware.test.ts +1 -1
  16. package/src/api/__tests__/{dispatcher-live.integration.ts → dispatcher-live.integration.test.ts} +10 -9
  17. package/src/api/__tests__/metrics-endpoint.test.ts +1 -1
  18. package/src/api/__tests__/{nested-write.integration.ts → nested-write.integration.test.ts} +13 -16
  19. package/src/api/__tests__/readiness.test.ts +1 -1
  20. package/src/api/__tests__/request-id-middleware.test.ts +1 -1
  21. package/src/api/__tests__/sse-broker.test.ts +12 -12
  22. package/src/api/__tests__/sse-route.test.ts +1 -1
  23. package/src/api/auth-routes.ts +2 -5
  24. package/src/api/readiness.ts +2 -2
  25. package/src/auth/__tests__/roles.test.ts +2 -2
  26. package/src/bun-db/__tests__/PATTERN.md +73 -0
  27. package/src/bun-db/__tests__/_helpers.ts +103 -0
  28. package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
  29. package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
  30. package/src/bun-db/__tests__/bun-test-db.ts +19 -0
  31. package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
  32. package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
  33. package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
  34. package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
  35. package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
  36. package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
  37. package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
  38. package/src/bun-db/connection.ts +84 -0
  39. package/src/bun-db/index.ts +31 -0
  40. package/src/bun-db/query.ts +842 -0
  41. package/src/compliance/__tests__/duration-spec.test.ts +1 -1
  42. package/src/compliance/__tests__/profiles.test.ts +1 -1
  43. package/src/compliance/__tests__/sub-processors.test.ts +1 -1
  44. package/src/compliance/profiles.ts +1 -4
  45. package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
  46. package/src/db/__tests__/big-int-field.test.ts +15 -14
  47. package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
  48. package/src/db/__tests__/compound-types.test.ts +1 -1
  49. package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
  50. package/src/db/__tests__/connection-options.test.ts +1 -1
  51. package/src/db/__tests__/cursor.test.ts +8 -32
  52. package/src/db/__tests__/dialect-instant.test.ts +1 -1
  53. package/src/db/__tests__/encryption.test.ts +1 -1
  54. package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
  55. package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
  56. package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
  57. package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
  58. package/src/db/__tests__/located-timestamp.test.ts +1 -1
  59. package/src/db/__tests__/migrate-generator.test.ts +71 -0
  60. package/src/db/__tests__/migrate-runner.test.ts +19 -0
  61. package/src/db/__tests__/money.test.ts +1 -1
  62. package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
  63. package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
  64. package/src/db/__tests__/pg-error.test.ts +43 -0
  65. package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
  66. package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
  67. package/src/db/__tests__/sql-inventory.test.ts +56 -0
  68. package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
  69. package/src/db/__tests__/table-builder-required.test.ts +20 -22
  70. package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
  71. package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
  72. package/src/db/api.ts +46 -0
  73. package/src/db/apply-entity-event.ts +45 -36
  74. package/src/db/assert-exists-in.ts +5 -16
  75. package/src/db/bun-provider.ts +37 -0
  76. package/src/db/config-seed.ts +4 -4
  77. package/src/db/connection.ts +14 -57
  78. package/src/db/cursor.ts +5 -56
  79. package/src/db/dialect.ts +472 -99
  80. package/src/db/eagerload.ts +5 -12
  81. package/src/db/entity-table-meta.ts +390 -0
  82. package/src/db/event-store-executor.ts +158 -100
  83. package/src/db/index.ts +33 -5
  84. package/src/db/migrate-generator.ts +350 -0
  85. package/src/db/migrate-runner.ts +206 -0
  86. package/src/db/postgres-provider.ts +25 -0
  87. package/src/db/queries/entity-read.ts +15 -0
  88. package/src/db/queries/es-ops.ts +17 -0
  89. package/src/db/queries/event-consumer.ts +170 -0
  90. package/src/db/queries/event-store-admin.ts +127 -0
  91. package/src/db/queries/event-store.ts +155 -0
  92. package/src/db/queries/projection-rebuild.ts +59 -0
  93. package/src/db/queries/raw-sql.ts +15 -0
  94. package/src/db/queries/schema-drift.ts +35 -0
  95. package/src/db/queries/seed-context.ts +58 -0
  96. package/src/db/queries/table-ops.ts +11 -0
  97. package/src/db/queries/test-stack.ts +56 -0
  98. package/src/db/query-api.ts +22 -0
  99. package/src/db/query.ts +30 -0
  100. package/src/db/reference-data.ts +19 -22
  101. package/src/db/render-ddl.ts +57 -0
  102. package/src/db/row-helpers.ts +3 -52
  103. package/src/db/schema-inspection.ts +17 -4
  104. package/src/db/sql-inventory.ts +208 -0
  105. package/src/db/table-builder.ts +54 -46
  106. package/src/db/tenant-db.ts +105 -326
  107. package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
  108. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
  109. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
  110. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
  111. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
  112. package/src/engine/__tests__/boot-validator.test.ts +4 -3
  113. package/src/engine/__tests__/build-app-schema.test.ts +1 -1
  114. package/src/engine/__tests__/build-target.test.ts +1 -1
  115. package/src/engine/__tests__/claim-keys.test.ts +1 -1
  116. package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
  117. package/src/engine/__tests__/config-helpers.test.ts +1 -1
  118. package/src/engine/__tests__/duration-utils.test.ts +16 -0
  119. package/src/engine/__tests__/effective-features.test.ts +1 -1
  120. package/src/engine/__tests__/engine.test.ts +1 -1
  121. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  122. package/src/engine/__tests__/event-helpers.test.ts +3 -3
  123. package/src/engine/__tests__/extends-registrar.test.ts +4 -4
  124. package/src/engine/__tests__/factories-long-text.test.ts +1 -1
  125. package/src/engine/__tests__/factories-time.test.ts +1 -1
  126. package/src/engine/__tests__/field-access.test.ts +38 -0
  127. package/src/engine/__tests__/field-predicates.test.ts +1 -1
  128. package/src/engine/__tests__/hook-phases.test.ts +1 -1
  129. package/src/engine/__tests__/identifiers.test.ts +1 -1
  130. package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
  131. package/src/engine/__tests__/nav.test.ts +1 -1
  132. package/src/engine/__tests__/no-return-guard.test.ts +17 -0
  133. package/src/engine/__tests__/ownership.test.ts +10 -11
  134. package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
  135. package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
  136. package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
  137. package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
  138. package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
  139. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
  140. package/src/engine/__tests__/post-query-hook.test.ts +1 -1
  141. package/src/engine/__tests__/projection-helpers.test.ts +25 -17
  142. package/src/engine/__tests__/projection.test.ts +4 -4
  143. package/src/engine/__tests__/qualified-name.test.ts +1 -1
  144. package/src/engine/__tests__/raw-table.test.ts +9 -8
  145. package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
  146. package/src/engine/__tests__/run-in.test.ts +1 -1
  147. package/src/engine/__tests__/schema-builder.test.ts +1 -1
  148. package/src/engine/__tests__/screen.test.ts +1 -1
  149. package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
  150. package/src/engine/__tests__/state-machine.test.ts +1 -1
  151. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
  152. package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
  153. package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
  154. package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
  155. package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
  156. package/src/engine/__tests__/steps-read.test.ts +34 -40
  157. package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
  158. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
  159. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
  160. package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
  161. package/src/engine/__tests__/steps-workflow.test.ts +7 -7
  162. package/src/engine/__tests__/system-user.test.ts +1 -1
  163. package/src/engine/__tests__/unmanaged-table.test.ts +98 -0
  164. package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
  165. package/src/engine/__tests__/validation-hooks.test.ts +1 -1
  166. package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
  167. package/src/engine/boot-validator/entity-handler.ts +3 -3
  168. package/src/engine/boot-validator/ownership.ts +1 -1
  169. package/src/engine/define-feature.ts +37 -2
  170. package/src/engine/entity-handlers.ts +5 -5
  171. package/src/engine/factories.ts +1 -1
  172. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
  173. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
  174. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
  175. package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
  176. package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
  177. package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
  178. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
  179. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
  180. package/src/engine/feature-ast/extractors/shared.ts +2 -3
  181. package/src/engine/ownership.ts +113 -41
  182. package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
  183. package/src/engine/projection-helpers.ts +2 -11
  184. package/src/engine/registry.ts +21 -2
  185. package/src/engine/steps/read-find-many.ts +13 -13
  186. package/src/engine/steps/read-find-one.ts +7 -9
  187. package/src/engine/steps/unsafe-projection-delete.ts +4 -5
  188. package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
  189. package/src/engine/types/feature.ts +47 -2
  190. package/src/engine/types/fields.ts +4 -5
  191. package/src/engine/types/index.ts +2 -0
  192. package/src/engine/types/step.ts +10 -10
  193. package/src/engine/validate-projection-allowlist.ts +23 -3
  194. package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
  195. package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
  196. package/src/env/__tests__/compose-env-schema.test.ts +1 -1
  197. package/src/env/__tests__/dry-run.test.ts +1 -1
  198. package/src/errors/__tests__/classes.test.ts +1 -1
  199. package/src/errors/__tests__/error-helpers.test.ts +44 -0
  200. package/src/errors/__tests__/field-issue-compat.test.ts +16 -0
  201. package/src/errors/__tests__/write-failures.test.ts +1 -1
  202. package/src/errors/classes.ts +5 -19
  203. package/src/errors/field-issue.ts +11 -0
  204. package/src/errors/index.ts +1 -0
  205. package/src/errors/zod-bridge.ts +3 -2
  206. package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
  207. package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
  208. package/src/es-ops/__tests__/runner.test.ts +29 -19
  209. package/src/es-ops/context.ts +11 -56
  210. package/src/es-ops/operations-schema.ts +2 -2
  211. package/src/es-ops/runner.ts +12 -26
  212. package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
  213. package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
  214. package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
  215. package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
  216. package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
  217. package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
  218. package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
  219. package/src/event-store/admin-api.ts +55 -83
  220. package/src/event-store/archive.ts +15 -39
  221. package/src/event-store/event-store.ts +92 -86
  222. package/src/event-store/events-schema.ts +2 -1
  223. package/src/event-store/index.ts +1 -0
  224. package/src/event-store/snapshot.ts +26 -24
  225. package/src/event-store/upcaster-dead-letter.ts +19 -18
  226. package/src/files/__tests__/content-disposition.test.ts +1 -1
  227. package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
  228. package/src/files/__tests__/file-handle.test.ts +1 -1
  229. package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
  230. package/src/files/__tests__/read-stream.test.ts +1 -1
  231. package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
  232. package/src/files/__tests__/write-stream.test.ts +1 -1
  233. package/src/files/__tests__/zip-stream.test.ts +1 -1
  234. package/src/files/file-ref-table.ts +2 -2
  235. package/src/files/file-routes.ts +7 -9
  236. package/src/files/storage-tracking.ts +9 -17
  237. package/src/i18n/__tests__/i18n.test.ts +1 -1
  238. package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
  239. package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
  240. package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
  241. package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
  242. package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
  243. package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
  244. package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
  245. package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
  246. package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
  247. package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
  248. package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
  249. package/src/migrations/projection-detection.ts +12 -1
  250. package/src/migrations/schema-drift.ts +7 -23
  251. package/src/observability/__tests__/console-provider.test.ts +1 -1
  252. package/src/observability/__tests__/metric-validator.test.ts +1 -1
  253. package/src/observability/__tests__/noop-provider.test.ts +1 -1
  254. package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
  255. package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
  256. package/src/observability/__tests__/recording-meter.test.ts +1 -1
  257. package/src/observability/__tests__/recording-tracer.test.ts +1 -1
  258. package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
  259. package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
  260. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
  261. package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
  262. package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
  263. package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
  264. package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
  265. package/src/pipeline/__tests__/dispatcher-utils.test.ts +107 -0
  266. package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
  267. package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
  268. package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
  269. package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
  270. package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
  271. package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
  272. package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
  273. package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
  274. package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
  275. package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
  276. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
  277. package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
  278. package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
  279. package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
  280. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
  281. package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
  282. package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
  283. package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
  284. package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
  285. package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
  286. package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
  287. package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
  288. package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
  289. package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
  290. package/src/pipeline/__tests__/redis-keys.test.ts +12 -0
  291. package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
  292. package/src/pipeline/cascade-handler.ts +13 -21
  293. package/src/pipeline/dispatcher-utils.ts +8 -7
  294. package/src/pipeline/dispatcher.ts +43 -48
  295. package/src/pipeline/event-consumer-state.ts +11 -2
  296. package/src/pipeline/event-dispatcher.ts +86 -146
  297. package/src/pipeline/event-retention.ts +14 -24
  298. package/src/pipeline/msp-rebuild.ts +54 -78
  299. package/src/pipeline/projection-rebuild.ts +65 -67
  300. package/src/pipeline/projection-state.ts +2 -2
  301. package/src/random/__tests__/generate.test.ts +13 -13
  302. package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
  303. package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
  304. package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
  305. package/src/redis/__tests__/redis-options.test.ts +1 -1
  306. package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
  307. package/src/search/__tests__/search-adapter.test.ts +1 -1
  308. package/src/secrets/__tests__/dek-cache.test.ts +1 -3
  309. package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
  310. package/src/secrets/__tests__/envelope.test.ts +1 -1
  311. package/src/secrets/__tests__/leak-guard.test.ts +1 -1
  312. package/src/secrets/__tests__/rotation.test.ts +1 -1
  313. package/src/stack/db.ts +25 -48
  314. package/src/stack/push-entity-projection-tables.ts +2 -4
  315. package/src/stack/table-helpers.ts +98 -61
  316. package/src/stack/test-stack.ts +10 -9
  317. package/src/testing/__tests__/db-cleanup.test.ts +40 -0
  318. package/src/testing/__tests__/e2e-generator.test.ts +1 -1
  319. package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
  320. package/src/testing/db-cleanup.ts +44 -0
  321. package/src/testing/expect-error.ts +1 -1
  322. package/src/testing/index.ts +2 -0
  323. package/src/testing/multipart-helper.ts +94 -0
  324. package/src/testing/shared-entities.ts +5 -5
  325. package/src/time/__tests__/polyfill.test.ts +1 -1
  326. package/src/time/__tests__/tz-context.test.ts +1 -1
  327. package/src/utils/__tests__/assert.test.ts +1 -1
  328. package/src/utils/__tests__/case.test.ts +16 -0
  329. package/src/utils/__tests__/env-parse.test.ts +1 -1
  330. package/src/utils/__tests__/is-plain-object.test.ts +16 -0
  331. package/src/utils/__tests__/parse-string-array-json.test.ts +16 -0
  332. package/src/utils/__tests__/safe-json.test.ts +22 -0
  333. package/src/utils/case.ts +6 -0
  334. package/src/utils/index.ts +3 -0
  335. package/src/utils/is-plain-object.ts +4 -0
  336. package/src/utils/parse-string-array-json.ts +14 -0
  337. package/CHANGELOG.md +0 -474
  338. package/src/db/__tests__/db-helpers.test.ts +0 -369
  339. package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
  340. package/src/db/__tests__/row-helpers.test.ts +0 -59
  341. package/src/engine/steps/_drizzle-boundary.ts +0 -19
  342. package/src/files/__tests__/file-field-column.integration.ts +0 -103
@@ -1,369 +0,0 @@
1
- import { getTableName } from "drizzle-orm";
2
- import { getTableConfig } from "drizzle-orm/pg-core";
3
- import { describe, expect, test } from "vitest";
4
- import {
5
- createBooleanField,
6
- createEntity,
7
- createImageField,
8
- createMultiSelectField,
9
- createSelectField,
10
- createTextField,
11
- } from "../../engine";
12
- import type { EntityRelations } from "../../engine/types";
13
- import { decodeCursor, encodeCursor } from "../cursor";
14
- import { buildBaseColumns, buildDrizzleTable, toTableName } from "../table-builder";
15
-
16
- // --- Cursor encoding ---
17
-
18
- describe("cursor encoding", () => {
19
- // String-Roundtrip seit Sprint F: encodeCursor akzeptiert string|number,
20
- // decodeCursor returnt immer einen String — UUID-IDs (Default) brauchen
21
- // keine Number-Kapsel, Integer-IDs werden via PG-Cast in der WHERE-Clause
22
- // korrekt verglichen. Detail-Tests in cursor.test.ts.
23
- test.each([1, 42, 999, 100000])("encodes and decodes integer id %i", (id) => {
24
- const cursor = encodeCursor(id);
25
- expect(decodeCursor(cursor)).toBe(String(id));
26
- });
27
-
28
- test("cursor is url-safe base64", () => {
29
- const cursor = encodeCursor(12345);
30
- expect(cursor).toMatch(/^[A-Za-z0-9_-]+$/);
31
- });
32
-
33
- test("throws on empty/corrupted cursor", () => {
34
- expect(() => decodeCursor("")).toThrow(/invalid cursor/i);
35
- });
36
- });
37
-
38
- // --- Base columns ---
39
-
40
- describe("buildBaseColumns", () => {
41
- test("includes standard columns", () => {
42
- const cols = buildBaseColumns(false);
43
- expect(cols).toHaveProperty("id");
44
- expect(cols).toHaveProperty("tenantId");
45
- expect(cols).toHaveProperty("insertedAt");
46
- expect(cols).toHaveProperty("modifiedAt");
47
- expect(cols).toHaveProperty("insertedById");
48
- expect(cols).toHaveProperty("modifiedById");
49
- });
50
-
51
- test("without softDelete has no isDeleted", () => {
52
- const cols = buildBaseColumns(false);
53
- expect(cols).not.toHaveProperty("isDeleted");
54
- });
55
-
56
- test("with softDelete includes isDeleted", () => {
57
- const cols = buildBaseColumns(true);
58
- expect(cols).toHaveProperty("isDeleted");
59
- });
60
- });
61
-
62
- // --- Table builder ---
63
-
64
- describe("buildDrizzleTable", () => {
65
- test("creates table with base columns + entity fields", () => {
66
- const entity = createEntity({
67
- table: "users",
68
- fields: {
69
- email: createTextField({ required: true }),
70
- firstName: createTextField(),
71
- isEnabled: createBooleanField({ default: true }),
72
- },
73
- });
74
-
75
- const table = buildDrizzleTable("user", entity);
76
-
77
- // Has base columns
78
- expect(table["id"]).toBeDefined();
79
- expect(table["tenantId"]).toBeDefined();
80
- expect(table["insertedAt"]).toBeDefined();
81
-
82
- // Has entity fields
83
- expect(table["email"]).toBeDefined();
84
- expect(table["firstName"]).toBeDefined();
85
- expect(table["isEnabled"]).toBeDefined();
86
- });
87
-
88
- test("soft delete entity includes isDeleted column", () => {
89
- const entity = createEntity({
90
- table: "users",
91
- fields: { email: createTextField() },
92
- softDelete: true,
93
- });
94
-
95
- const table = buildDrizzleTable("user", entity);
96
- expect(table["isDeleted"]).toBeDefined();
97
- });
98
-
99
- test("select field becomes text column", () => {
100
- const entity = createEntity({
101
- table: "users",
102
- fields: {
103
- locale: createSelectField({ options: ["de", "en"] as const }),
104
- },
105
- });
106
-
107
- const table = buildDrizzleTable("user", entity);
108
- expect(table["locale"]).toBeDefined();
109
- });
110
-
111
- test("multiSelect field becomes jsonb column with default []", () => {
112
- const entity = createEntity({
113
- table: "drivers",
114
- fields: {
115
- licenceClasses: createMultiSelectField({ options: ["B", "BE", "C"] as const }),
116
- },
117
- });
118
-
119
- const table = buildDrizzleTable("driver", entity);
120
- const config = getTableConfig(table);
121
- const column = config.columns.find((c) => c.name === "licence_classes");
122
- expect(column).toBeDefined();
123
- // jsonb-customType: column-data-type ist string ("jsonb"); column-type
124
- // hier reicht als Smoke — die Default-`[]`-Garantie testen wir indirekt
125
- // über die Migration-Rebuild-Integration-Tests, die echte Inserts
126
- // gegen Postgres machen.
127
- expect(column?.dataType).toBe("json");
128
- expect(column?.default).toEqual([]);
129
- });
130
-
131
- test("converts camelCase to snake_case", () => {
132
- const entity = createEntity({
133
- table: "users",
134
- fields: {
135
- firstName: createTextField(),
136
- employmentType: createSelectField({ options: ["FullTime", "PartTime"] as const }),
137
- },
138
- });
139
-
140
- const table = buildDrizzleTable("user", entity);
141
- // Column objects exist under camelCase keys
142
- expect(table["firstName"]).toBeDefined();
143
- expect(table["employmentType"]).toBeDefined();
144
- });
145
-
146
- test("featureName option prefixes table name", () => {
147
- const entity = createEntity({
148
- table: "orders",
149
- fields: { name: createTextField() },
150
- });
151
-
152
- const table = buildDrizzleTable("order", entity, { featureName: "shop" });
153
- expect(getTableName(table)).toBe("shop_orders");
154
- });
155
-
156
- test("without featureName, table name is unchanged", () => {
157
- const entity = createEntity({
158
- table: "orders",
159
- fields: { name: createTextField() },
160
- });
161
-
162
- const table = buildDrizzleTable("order", entity);
163
- expect(getTableName(table)).toBe("orders");
164
- });
165
-
166
- test("derives table name from entityName when table is omitted", () => {
167
- const entity = createEntity({ fields: { name: createTextField() } });
168
- const table = buildDrizzleTable("task", entity);
169
- expect(getTableName(table)).toBe("read_tasks");
170
- });
171
-
172
- test("derives table name with featureName prefix when table is omitted", () => {
173
- const entity = createEntity({ fields: { name: createTextField() } });
174
- const table = buildDrizzleTable("order", entity, { featureName: "shop" });
175
- // featureName-Prefix landet zwischen `read_` und dem Plural — alle
176
- // Read-Models starten konsistent mit `read_`, egal ob ein Feature-
177
- // Prefix gesetzt ist oder nicht.
178
- expect(getTableName(table)).toBe("read_shop_orders");
179
- });
180
- });
181
-
182
- // --- Auto-Indices ---
183
-
184
- describe("buildDrizzleTable auto-indices", () => {
185
- test("every table gets a tenant_id index", () => {
186
- const entity = createEntity({
187
- table: "users",
188
- fields: { email: createTextField() },
189
- });
190
- const table = buildDrizzleTable("user", entity);
191
- const { indexes } = getTableConfig(table);
192
-
193
- const tenantIndex = indexes.find((idx) => idx.config.name === "users_tenant_id_idx");
194
- expect(tenantIndex).toBeDefined();
195
- expect(tenantIndex?.config.columns.map((c) => (c as { name: string }).name)).toEqual([
196
- "tenant_id",
197
- ]);
198
- });
199
-
200
- test("file field produces an index on its column", () => {
201
- const entity = createEntity({
202
- table: "documents",
203
- fields: {
204
- title: createTextField(),
205
- avatar: createImageField(),
206
- },
207
- });
208
- const table = buildDrizzleTable("document", entity);
209
- const { indexes } = getTableConfig(table);
210
-
211
- const avatarIndex = indexes.find((idx) => idx.config.name === "documents_avatar_idx");
212
- expect(avatarIndex).toBeDefined();
213
- });
214
-
215
- test("index names include feature prefix when featureName is set", () => {
216
- const entity = createEntity({
217
- table: "items",
218
- fields: { name: createTextField() },
219
- });
220
- const table = buildDrizzleTable("item", entity, { featureName: "shop" });
221
- const { indexes } = getTableConfig(table);
222
-
223
- expect(indexes.some((idx) => idx.config.name === "shop_items_tenant_id_idx")).toBe(true);
224
- });
225
-
226
- test("table without file fields or relations has only the tenant index", () => {
227
- const entity = createEntity({
228
- table: "notes",
229
- fields: { body: createTextField() },
230
- });
231
- const table = buildDrizzleTable("note", entity);
232
- const { indexes } = getTableConfig(table);
233
-
234
- expect(indexes).toHaveLength(1);
235
- expect(indexes[0]?.config.name).toBe("notes_tenant_id_idx");
236
- });
237
-
238
- test("belongsTo relations produce an index on their foreign key column", () => {
239
- const entity = createEntity({
240
- table: "tasks",
241
- fields: {
242
- title: createTextField({ required: true }),
243
- assigneeId: createTextField(),
244
- projectId: createTextField(),
245
- },
246
- });
247
- const relations: EntityRelations = {
248
- assignee: { type: "belongsTo", target: "user", foreignKey: "assigneeId" },
249
- project: { type: "belongsTo", target: "project", foreignKey: "projectId" },
250
- };
251
- const table = buildDrizzleTable("task", entity, { relations });
252
- const { indexes } = getTableConfig(table);
253
-
254
- const names = indexes.map((i) => i.config.name);
255
- expect(names).toContain("tasks_tenant_id_idx");
256
- expect(names).toContain("tasks_assignee_id_idx");
257
- expect(names).toContain("tasks_project_id_idx");
258
- });
259
-
260
- test("hasMany / manyToMany relations do NOT produce indexes on this table (their FK lives on the other side)", () => {
261
- const entity = createEntity({
262
- table: "teams",
263
- fields: { name: createTextField() },
264
- });
265
- const relations: EntityRelations = {
266
- members: { type: "hasMany", target: "user", foreignKey: "teamId" },
267
- tags: {
268
- type: "manyToMany",
269
- target: "tag",
270
- through: { table: "team_tags", sourceKey: "teamId", targetKey: "tagId" },
271
- },
272
- };
273
- const table = buildDrizzleTable("team", entity, { relations });
274
- const { indexes } = getTableConfig(table);
275
-
276
- // Only the tenant index — hasMany FK lives on the "user" table; the join
277
- // table for manyToMany isn't owned by this entity either.
278
- expect(indexes).toHaveLength(1);
279
- expect(indexes[0]?.config.name).toBe("teams_tenant_id_idx");
280
- });
281
-
282
- test("relation and file field on the same column deduplicate to one index", () => {
283
- const entity = createEntity({
284
- table: "photos",
285
- fields: {
286
- title: createTextField(),
287
- ownerId: createImageField(), // contrived: name collides with an FK relation below
288
- },
289
- });
290
- const relations: EntityRelations = {
291
- owner: { type: "belongsTo", target: "user", foreignKey: "ownerId" },
292
- };
293
- const table = buildDrizzleTable("photo", entity, { relations });
294
- const { indexes } = getTableConfig(table);
295
-
296
- const names = indexes.map((i) => i.config.name);
297
- // Exactly one owner_id index, not two
298
- expect(names.filter((n) => n === "photos_owner_id_idx")).toHaveLength(1);
299
- });
300
- });
301
-
302
- // --- toTableName ---
303
-
304
- describe("toTableName", () => {
305
- test.each([
306
- ["task", "read_tasks"],
307
- ["user", "read_users"],
308
- ["tenant", "read_tenants"],
309
- ])("simple plural: %s → %s", (input, expected) => {
310
- expect(toTableName(input)).toBe(expected);
311
- });
312
-
313
- test.each([
314
- ["category", "read_categories"],
315
- ["entity", "read_entities"],
316
- ["policy", "read_policies"],
317
- ])("y → ies: %s → %s", (input, expected) => {
318
- expect(toTableName(input)).toBe(expected);
319
- });
320
-
321
- test.each([
322
- ["key", "read_keys"],
323
- ["survey", "read_surveys"],
324
- ["day", "read_days"],
325
- ])("vowel+y stays: %s → %s", (input, expected) => {
326
- expect(toTableName(input)).toBe(expected);
327
- });
328
-
329
- test.each([
330
- ["status", "read_statuses"],
331
- ["address", "read_addresses"],
332
- ["match", "read_matches"],
333
- ["tax", "read_taxes"],
334
- ["wish", "read_wishes"],
335
- ])("sibilant → es: %s → %s", (input, expected) => {
336
- expect(toTableName(input)).toBe(expected);
337
- });
338
-
339
- test.each([
340
- ["memberTask", "read_member_tasks"],
341
- ["userProfile", "read_user_profiles"],
342
- ["orderItem", "read_order_items"],
343
- ])("camelCase → snake_case + plural: %s → %s", (input, expected) => {
344
- expect(toTableName(input)).toBe(expected);
345
- });
346
-
347
- test.each([
348
- ["tenant-membership", "read_tenant_memberships"],
349
- ["user-profile-address", "read_user_profile_addresses"],
350
- ["invoice-issuer", "read_invoice_issuers"],
351
- ])("kebab-case → snake_case + plural: %s → %s", (input, expected) => {
352
- expect(toTableName(input)).toBe(expected);
353
- });
354
- });
355
-
356
- // --- Sorting in CursorQueryOptions ---
357
-
358
- describe("sorting", () => {
359
- test("CursorQueryOptions accepts sort and sortDirection", () => {
360
- // Type-level test: this should compile
361
- const opts: import("../cursor").CursorQueryOptions = {
362
- tenantId: "00000000-0000-4000-8000-000000000001",
363
- sort: "lastName",
364
- sortDirection: "asc",
365
- };
366
- expect(opts.sort).toBe("lastName");
367
- expect(opts.sortDirection).toBe("asc");
368
- });
369
- });
@@ -1,186 +0,0 @@
1
- import { drizzle } from "drizzle-orm/postgres-js";
2
- import postgres from "postgres";
3
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
4
- import { createBooleanField, createEntity, createTextField } from "../../engine";
5
- import { testTenantId, unsafeCreateEntityTable } from "../../stack";
6
- import { applyCursorQuery, encodeCursor } from "../cursor";
7
- import { buildDrizzleTable } from "../table-builder";
8
-
9
- function requireEnv(name: string): string {
10
- const value = process.env[name];
11
- if (!value) throw new Error(`Missing required env var: ${name}`);
12
- return value;
13
- }
14
-
15
- const TEST_DB_URL = requireEnv("TEST_DATABASE_URL");
16
-
17
- type Row = Record<string, unknown>;
18
-
19
- const entity = createEntity({
20
- table: "test_users",
21
- // cursor-pagination uses gt(id, cursor) which relies on ordered integer ids.
22
- // This test exercises that classic serial-PK path deliberately.
23
- idType: "serial",
24
- fields: {
25
- email: createTextField({ required: true, searchable: true }),
26
- firstName: createTextField({ searchable: true }),
27
- isEnabled: createBooleanField({ default: true }),
28
- },
29
- softDelete: true,
30
- });
31
-
32
- const table = buildDrizzleTable("testUser", entity);
33
-
34
- let client: ReturnType<typeof postgres>;
35
- let db: ReturnType<typeof drizzle>;
36
-
37
- beforeAll(async () => {
38
- const adminClient = postgres(TEST_DB_URL.replace(/\/[^/]+$/, "/postgres"), {
39
- onnotice: () => {},
40
- });
41
- try {
42
- await adminClient`DROP DATABASE IF EXISTS kumiko_test_step7`;
43
- await adminClient`CREATE DATABASE kumiko_test_step7`;
44
- } finally {
45
- await adminClient.end();
46
- }
47
-
48
- const testUrl = TEST_DB_URL.replace(/\/[^/]+$/, "/kumiko_test_step7");
49
- client = postgres(testUrl);
50
- db = drizzle(client);
51
-
52
- await unsafeCreateEntityTable(db, entity);
53
-
54
- const rows = [
55
- {
56
- tenantId: "00000000-0000-4000-8000-000000000001",
57
- email: "admin@test.de",
58
- firstName: "Admin",
59
- },
60
- { tenantId: "00000000-0000-4000-8000-000000000001", email: "marc@test.de", firstName: "Marc" },
61
- { tenantId: "00000000-0000-4000-8000-000000000001", email: "anna@test.de", firstName: "Anna" },
62
- {
63
- tenantId: "00000000-0000-4000-8000-000000000001",
64
- email: "deleted@test.de",
65
- firstName: "Deleted",
66
- isDeleted: true,
67
- },
68
- {
69
- tenantId: "00000000-0000-4000-8000-000000000002",
70
- email: "other@test.de",
71
- firstName: "Other",
72
- },
73
- ];
74
-
75
- for (const row of rows) {
76
- await db.insert(table).values({
77
- tenantId: row.tenantId,
78
- email: row.email,
79
- firstName: row.firstName,
80
- isEnabled: true,
81
- isDeleted: row.isDeleted ?? false,
82
- });
83
- }
84
- });
85
-
86
- afterAll(async () => {
87
- await client.end();
88
- const adminClient = postgres(TEST_DB_URL.replace(/\/[^/]+$/, "/postgres"), {
89
- onnotice: () => {},
90
- });
91
- try {
92
- await adminClient`DROP DATABASE IF EXISTS kumiko_test_step7`;
93
- } finally {
94
- await adminClient.end();
95
- }
96
- });
97
-
98
- async function query(options: Parameters<typeof applyCursorQuery>[2]): Promise<Row[]> {
99
- return applyCursorQuery(db.select().from(table).$dynamic(), table, options);
100
- }
101
-
102
- // --- Tests ---
103
-
104
- describe("tenant isolation", () => {
105
- test("only returns rows for specified tenant", async () => {
106
- const rows = await query({ tenantId: testTenantId(1) });
107
- expect(rows.every((r) => r["tenantId"] === testTenantId(1))).toBe(true);
108
- });
109
-
110
- test("tenant 2 only sees own data", async () => {
111
- const rows = await query({ tenantId: "00000000-0000-4000-8000-000000000002" });
112
- expect(rows).toHaveLength(1);
113
- expect(rows[0]?.["email"]).toBe("other@test.de");
114
- });
115
- });
116
-
117
- describe("soft delete filtering", () => {
118
- test("excludes soft-deleted rows", async () => {
119
- const rows = await query({ tenantId: "00000000-0000-4000-8000-000000000001" });
120
- expect(rows.find((r) => r["email"] === "deleted@test.de")).toBeUndefined();
121
- });
122
- });
123
-
124
- describe("cursor pagination", () => {
125
- test("limits results", async () => {
126
- const rows = await query({ tenantId: "00000000-0000-4000-8000-000000000001", limit: 2 });
127
- expect(rows).toHaveLength(2);
128
- });
129
-
130
- test("cursor skips past previous results", async () => {
131
- const page1 = await query({ tenantId: "00000000-0000-4000-8000-000000000001", limit: 2 });
132
- expect(page1).toHaveLength(2);
133
-
134
- const lastId = page1[page1.length - 1]?.["id"] as number;
135
- const page2 = await query({
136
- tenantId: "00000000-0000-4000-8000-000000000001",
137
- limit: 2,
138
- cursor: encodeCursor(lastId),
139
- });
140
-
141
- const page1Ids = page1.map((r) => r["id"]);
142
- const page2Ids = page2.map((r) => r["id"]);
143
- expect(page1Ids.some((id) => page2Ids.includes(id))).toBe(false);
144
- });
145
- });
146
-
147
- describe("filterIds (search results from SearchAdapter)", () => {
148
- test("filters by ID list from search adapter", async () => {
149
- // SearchAdapter returns IDs, cursor query filters by them
150
- const rows = await query({
151
- tenantId: "00000000-0000-4000-8000-000000000001",
152
- filterIds: [1, 2],
153
- });
154
- expect(rows).toHaveLength(2);
155
- expect(rows.every((r) => [1, 2].includes(r["id"] as number))).toBe(true);
156
- });
157
-
158
- test("empty filterIds returns nothing", async () => {
159
- const rows = await query({ tenantId: "00000000-0000-4000-8000-000000000001", filterIds: [] });
160
- expect(rows).toHaveLength(0);
161
- });
162
- });
163
-
164
- describe("sorting", () => {
165
- test("sorts by column ASC", async () => {
166
- const rows = await query({
167
- tenantId: "00000000-0000-4000-8000-000000000001",
168
- sort: "firstName",
169
- sortDirection: "asc",
170
- });
171
- const names = rows.map((r) => r["firstName"]);
172
- const sorted = [...names].sort();
173
- expect(names).toEqual(sorted);
174
- });
175
-
176
- test("sorts by column DESC", async () => {
177
- const rows = await query({
178
- tenantId: "00000000-0000-4000-8000-000000000001",
179
- sort: "firstName",
180
- sortDirection: "desc",
181
- });
182
- const names = rows.map((r) => r["firstName"]);
183
- const sorted = [...names].sort().reverse();
184
- expect(names).toEqual(sorted);
185
- });
186
- });
@@ -1,59 +0,0 @@
1
- import type { SQL } from "drizzle-orm";
2
- import { describe, expect, test, vi } from "vitest";
3
- import { fetchOne } from "../row-helpers";
4
-
5
- // Drizzle builder chain mocked structurally — fetchOne only calls
6
- // db.select().from(table).where(where).limit(1) and reads the first row.
7
- function makeFakeDb(rows: unknown[]) {
8
- const limit = vi.fn().mockResolvedValue(rows);
9
- const where = vi.fn(() => ({ limit }));
10
- const from = vi.fn(() => ({ where }));
11
- const select = vi.fn(() => ({ from }));
12
- // biome-ignore lint/suspicious/noExplicitAny: test shim — we never feed this to a real DbRunner type check.
13
- return { db: { select } as any, select, from, where, limit };
14
- }
15
-
16
- const fakeTable = {} as never;
17
- const fakeCond1 = { __c: 1 } as unknown as SQL;
18
- const fakeCond2 = { __c: 2 } as unknown as SQL;
19
-
20
- describe("fetchOne", () => {
21
- test("returns the first row when the query yields at least one match", async () => {
22
- const { db } = makeFakeDb([
23
- { id: 1, name: "alice" },
24
- { id: 2, name: "bob" },
25
- ]);
26
- const row = await fetchOne<{ id: number; name: string }>(db, fakeTable, fakeCond1);
27
- expect(row).toEqual({ id: 1, name: "alice" });
28
- });
29
-
30
- test("returns undefined on an empty result", async () => {
31
- const { db } = makeFakeDb([]);
32
- const row = await fetchOne(db, fakeTable, fakeCond1);
33
- expect(row).toBeUndefined();
34
- });
35
-
36
- test("applies limit(1) — no need to pull the whole table", async () => {
37
- const { db, limit } = makeFakeDb([]);
38
- await fetchOne(db, fakeTable, fakeCond1);
39
- expect(limit).toHaveBeenCalledWith(1);
40
- });
41
-
42
- test("passes the single condition directly to .where (no AND wrapping)", async () => {
43
- const { db, where } = makeFakeDb([]);
44
- await fetchOne(db, fakeTable, fakeCond1);
45
- expect(where).toHaveBeenCalledWith(fakeCond1);
46
- });
47
-
48
- test("combines multiple conditions with AND", async () => {
49
- const { db, where } = makeFakeDb([]);
50
- await fetchOne(db, fakeTable, fakeCond1, fakeCond2);
51
- const calls = where.mock.calls as unknown as readonly (readonly unknown[])[];
52
- const arg = calls[0]?.[0];
53
- // drizzle's and() returns an SQL expression — we can't cheaply inspect
54
- // its innards, but it must not be the raw first condition and must be
55
- // defined (i.e. the helper actually composed something).
56
- expect(arg).toBeDefined();
57
- expect(arg).not.toBe(fakeCond1);
58
- });
59
- });
@@ -1,19 +0,0 @@
1
- // Drizzle-boundary cast helper — drizzle's `db.insert()/select()/delete()`
2
- // expect a PgTable<...> shape with `enableRLS` (driver-added). The
3
- // abstract `Table` we accept on step args is missing that method, so
4
- // TS rejects direct assignment. Runtime is identical — drizzle's
5
- // builder methods only read the table-name + column-defs, both of
6
- // which `Table` carries. Cast at the boundary, document it once.
7
- //
8
- // Used by read-find-one, read-find-many, unsafe-projection-upsert,
9
- // unsafe-projection-delete. Followup #13 (closed at the M.1.6
10
- // cleanup-pass).
11
-
12
- import type { Table } from "drizzle-orm";
13
-
14
- // biome-ignore lint/suspicious/noExplicitAny: drizzle type-boundary
15
- type DrizzleQueryTarget = any;
16
-
17
- export function asQueryTarget(t: Table): DrizzleQueryTarget {
18
- return t;
19
- }