@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
@@ -10,25 +10,27 @@
10
10
  // Feature in den Tests zu schwer wäre — wir testen nur den Read-Helper-
11
11
  // Layer, nicht die volle Event-Store-Pipeline.
12
12
 
13
+ import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
13
14
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
14
15
  import { tmpdir } from "node:os";
15
16
  import { join } from "node:path";
16
- import { sql } from "drizzle-orm";
17
- import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
18
- import { createTestDb, type TestDb } from "../../stack";
17
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
18
+ import { asRawClient, selectMany } from "../../db/query";
19
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
19
20
  import { createSeedMigrationContext } from "../context";
20
21
  import { createEsOperationsTable, esOperationsTable } from "../operations-schema";
21
22
  import { runPendingSeedMigrations } from "../runner";
22
23
 
23
- let testDb: TestDb;
24
+ let testDb: BunTestDb;
24
25
 
25
26
  beforeAll(async () => {
27
+ await ensureTemporalPolyfill();
26
28
  testDb = await createTestDb();
27
29
  await createEsOperationsTable(testDb.db);
28
30
 
29
31
  // Minimal-Schema-Stubs für die 3 Read-Tabellen die context.ts liest.
30
32
  // Spalten matchen production (siehe Sysadmin-Stream-Tenant-Bug Memory).
31
- await testDb.db.execute(sql`
33
+ await asRawClient(testDb.db).unsafe(`
32
34
  CREATE TABLE IF NOT EXISTS read_users (
33
35
  id uuid PRIMARY KEY,
34
36
  email text NOT NULL,
@@ -54,7 +56,7 @@ afterAll(async () => {
54
56
  });
55
57
 
56
58
  beforeEach(async () => {
57
- await testDb.db.execute(sql`
59
+ await asRawClient(testDb.db).unsafe(`
58
60
  TRUNCATE kumiko_es_operations, kumiko_events, read_users, read_tenant_memberships, read_tenants
59
61
  RESTART IDENTITY CASCADE
60
62
  `);
@@ -70,26 +72,32 @@ async function insertMembershipWithEvent(args: {
70
72
  readonly streamTenantId: string;
71
73
  readonly roles: string;
72
74
  }): Promise<void> {
73
- await testDb.db.execute(sql`
75
+ await asRawClient(testDb.db).unsafe(
76
+ `
74
77
  INSERT INTO read_tenant_memberships (id, user_id, tenant_id, roles)
75
- VALUES (${args.id}::uuid, ${args.userId}, ${args.payloadTenantId}::uuid, ${args.roles})
76
- `);
77
- await testDb.db.execute(sql`
78
+ VALUES ($1::uuid, $2, $3::uuid, $4)
79
+ `,
80
+ [args.id, args.userId, args.payloadTenantId, args.roles],
81
+ );
82
+ await asRawClient(testDb.db).unsafe(
83
+ `
78
84
  INSERT INTO kumiko_events
79
85
  (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
80
86
  VALUES
81
- (${args.id}::uuid, 'tenant-membership', ${args.streamTenantId}::uuid, 1,
87
+ ($1::uuid, 'tenant-membership', $2::uuid, 1,
82
88
  'tenant-membership.created', '{}'::jsonb, '{"userId":"system"}'::jsonb, 'system')
83
- `);
89
+ `,
90
+ [args.id, args.streamTenantId],
91
+ );
84
92
  }
85
93
 
86
94
  function makeMockDispatcher() {
87
95
  return {
88
- write: vi.fn(async () => ({ isSuccess: true as const, data: {} })),
89
- query: vi.fn(),
90
- command: vi.fn(),
91
- batch: vi.fn(),
92
- resolveAuthClaims: vi.fn(),
96
+ write: mock(async () => ({ isSuccess: true as const, data: {} })),
97
+ query: mock(),
98
+ command: mock(),
99
+ batch: mock(),
100
+ resolveAuthClaims: mock(),
93
101
  };
94
102
  }
95
103
 
@@ -105,10 +113,13 @@ describe("SeedMigrationContext.findUserByEmail (integration)", () => {
105
113
  test("liest existing user-row korrekt + maps tenant_id → tenantId", async () => {
106
114
  const userId = "01900000-0000-7000-8000-000000000001";
107
115
  const tenantId = "00000000-0000-4000-8000-000000000099";
108
- await testDb.db.execute(sql`
116
+ await asRawClient(testDb.db).unsafe(
117
+ `
109
118
  INSERT INTO read_users (id, email, tenant_id)
110
- VALUES (${userId}::uuid, 'admin@example.com', ${tenantId}::uuid)
111
- `);
119
+ VALUES ($1::uuid, 'admin@example.com', $2::uuid)
120
+ `,
121
+ [userId, tenantId],
122
+ );
112
123
 
113
124
  const ctx = createSeedMigrationContext({
114
125
  dispatcher: makeMockDispatcher() as never,
@@ -231,11 +242,14 @@ describe("SeedMigrationContext.findMembershipsOfUser (integration)", () => {
231
242
  // statt einer mit fehlendem stream-tenant zu arbeiten und schwer
232
243
  // diagnostizierbare version_conflict-Errors zu produzieren.
233
244
  const userId = "01900000-0000-7000-8000-000000000003";
234
- await testDb.db.execute(sql`
245
+ await asRawClient(testDb.db).unsafe(
246
+ `
235
247
  INSERT INTO read_tenant_memberships (id, user_id, tenant_id, roles) VALUES
236
- ('00000000-0000-4000-8000-0000000000d1'::uuid, ${userId},
248
+ ('00000000-0000-4000-8000-0000000000d1'::uuid, $1,
237
249
  '00000000-0000-4000-8000-000000000005'::uuid, '["Admin"]')
238
- `);
250
+ `,
251
+ [userId],
252
+ );
239
253
  const ctx = createSeedMigrationContext({
240
254
  dispatcher: makeMockDispatcher() as never,
241
255
  dbRunner: testDb.db,
@@ -247,7 +261,7 @@ describe("SeedMigrationContext.findMembershipsOfUser (integration)", () => {
247
261
 
248
262
  describe("SeedMigrationContext.findTenants (integration)", () => {
249
263
  test("returnt alle Tenants sortiert nach inserted_at", async () => {
250
- await testDb.db.execute(sql`
264
+ await asRawClient(testDb.db).unsafe(`
251
265
  INSERT INTO read_tenants (id, name, tenant_key, inserted_at) VALUES
252
266
  ('00000000-0000-4000-8000-000000000002'::uuid, 'Beta', 'beta', '2026-01-02'),
253
267
  ('00000000-0000-4000-8000-000000000001'::uuid, 'Alpha', 'alpha', '2026-01-01')
@@ -296,7 +310,7 @@ describe("runPendingSeedMigrations: skippable + env-flag (integration)", () => {
296
310
 
297
311
  // Kritisch: KEIN Marker — beim nächsten Boot ohne env-flag würde
298
312
  // der Seed dann tatsächlich laufen.
299
- const markers = await testDb.db.select().from(esOperationsTable);
313
+ const markers = await selectMany(testDb.db, esOperationsTable);
300
314
  expect(markers).toHaveLength(0);
301
315
  } finally {
302
316
  delete process.env[envKey];
@@ -329,7 +343,7 @@ describe("runPendingSeedMigrations: skippable + env-flag (integration)", () => {
329
343
  expect(r.appliedIds).toEqual(["2026-05-20-skippable-but-no-flag"]);
330
344
  expect(r.skippedIds).toEqual([]);
331
345
 
332
- const markers = await testDb.db.select().from(esOperationsTable);
346
+ const markers = await selectMany(testDb.db, esOperationsTable);
333
347
  expect(markers).toHaveLength(1);
334
348
  } finally {
335
349
  rmSync(dir, { recursive: true, force: true });
@@ -341,7 +355,7 @@ describe("runPendingSeedMigrations: skippable + env-flag (integration)", () => {
341
355
 
342
356
  describe("SeedMigrationContext.db (escape-hatch, integration)", () => {
343
357
  test("ctx.db kann für eigene Lookups genutzt werden (read-only)", async () => {
344
- await testDb.db.execute(sql`
358
+ await asRawClient(testDb.db).unsafe(`
345
359
  INSERT INTO read_tenants (id, name, tenant_key) VALUES
346
360
  ('00000000-0000-4000-8000-000000000007'::uuid, 'Lucky', 'lucky')
347
361
  `);
@@ -349,8 +363,8 @@ describe("SeedMigrationContext.db (escape-hatch, integration)", () => {
349
363
  dispatcher: makeMockDispatcher() as never,
350
364
  dbRunner: testDb.db,
351
365
  });
352
- const rows = (await ctx.db.execute(
353
- sql`SELECT name FROM read_tenants WHERE tenant_key = 'lucky'`,
366
+ const rows = (await asRawClient(ctx.db).unsafe(
367
+ `SELECT name FROM read_tenants WHERE tenant_key = 'lucky'`,
354
368
  )) as unknown as readonly { name: string }[];
355
369
  expect(rows[0]?.name).toBe("Lucky");
356
370
  });
@@ -8,19 +8,21 @@
8
8
  // Heavy lifting (mock-dispatcher, in-memory-applied-set) liegt in
9
9
  // runner.test.ts. Hier nur DB-Round-Trip-Wahrheit.
10
10
 
11
+ import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
11
12
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
12
13
  import { tmpdir } from "node:os";
13
14
  import { join } from "node:path";
14
- import { sql } from "drizzle-orm";
15
- import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
16
- import { createTestDb, type TestDb } from "../../stack";
15
+ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
16
+ import { asRawClient, insertOne, selectMany } from "../../db/query";
17
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
17
18
  import { createSeedMigrationContext } from "../context";
18
19
  import { createEsOperationsTable, esOperationsTable } from "../operations-schema";
19
20
  import { runPendingSeedMigrations } from "../runner";
20
21
 
21
- let testDb: TestDb;
22
+ let testDb: BunTestDb;
22
23
 
23
24
  beforeAll(async () => {
25
+ await ensureTemporalPolyfill();
24
26
  testDb = await createTestDb();
25
27
  await createEsOperationsTable(testDb.db);
26
28
  });
@@ -30,7 +32,7 @@ afterAll(async () => {
30
32
  });
31
33
 
32
34
  beforeEach(async () => {
33
- await testDb.db.execute(sql`TRUNCATE kumiko_es_operations RESTART IDENTITY`);
35
+ await asRawClient(testDb.db).unsafe(`TRUNCATE kumiko_es_operations RESTART IDENTITY`);
34
36
  });
35
37
 
36
38
  function makeTempSeedsDir(files: readonly { name: string; content: string }[]): string {
@@ -42,14 +44,14 @@ function makeTempSeedsDir(files: readonly { name: string; content: string }[]):
42
44
  function makeMockDispatcher() {
43
45
  const calls: Array<{ qn: string; payload: unknown }> = [];
44
46
  return {
45
- write: vi.fn(async (qn: string, payload: unknown) => {
47
+ write: mock(async (qn: string, payload: unknown) => {
46
48
  calls.push({ qn, payload });
47
49
  return { isSuccess: true as const, data: {} };
48
50
  }),
49
- query: vi.fn(),
50
- command: vi.fn(),
51
- batch: vi.fn(),
52
- resolveAuthClaims: vi.fn(),
51
+ query: mock(),
52
+ command: mock(),
53
+ batch: mock(),
54
+ resolveAuthClaims: mock(),
53
55
  calls,
54
56
  };
55
57
  }
@@ -82,7 +84,7 @@ describe("runPendingSeedMigrations (integration)", () => {
82
84
  expect(r1.appliedIds).toEqual(["2026-05-20-noop"]);
83
85
 
84
86
  // Marker landed
85
- const markers1 = await testDb.db.select().from(esOperationsTable);
87
+ const markers1 = await selectMany(testDb.db, esOperationsTable);
86
88
  expect(markers1).toHaveLength(1);
87
89
  expect(markers1[0]?.id).toBe("2026-05-20-noop");
88
90
  expect(markers1[0]?.operationType).toBe("seed-migration");
@@ -98,7 +100,7 @@ describe("runPendingSeedMigrations (integration)", () => {
98
100
  logger: () => {},
99
101
  });
100
102
  expect(r2.appliedIds).toEqual([]);
101
- const markers2 = await testDb.db.select().from(esOperationsTable);
103
+ const markers2 = await selectMany(testDb.db, esOperationsTable);
102
104
  expect(markers2).toHaveLength(1);
103
105
  } finally {
104
106
  rmSync(dir, { recursive: true, force: true });
@@ -130,7 +132,7 @@ describe("runPendingSeedMigrations (integration)", () => {
130
132
  }),
131
133
  ).rejects.toThrow(/boom/);
132
134
 
133
- const markers = await testDb.db.select().from(esOperationsTable);
135
+ const markers = await selectMany(testDb.db, esOperationsTable);
134
136
  expect(markers).toHaveLength(0);
135
137
  } finally {
136
138
  rmSync(dir, { recursive: true, force: true });
@@ -192,14 +194,14 @@ describe("runPendingSeedMigrations (integration)", () => {
192
194
  ]);
193
195
  try {
194
196
  const dispatcher = {
195
- write: vi.fn(async () => ({
197
+ write: mock(async () => ({
196
198
  isSuccess: false as const,
197
199
  error: { code: "version_conflict", message: "stream changed" },
198
200
  })),
199
- query: vi.fn(),
200
- command: vi.fn(),
201
- batch: vi.fn(),
202
- resolveAuthClaims: vi.fn(),
201
+ query: mock(),
202
+ command: mock(),
203
+ batch: mock(),
204
+ resolveAuthClaims: mock(),
203
205
  };
204
206
 
205
207
  await expect(
@@ -214,7 +216,7 @@ describe("runPendingSeedMigrations (integration)", () => {
214
216
  ).rejects.toThrow(/version_conflict/);
215
217
 
216
218
  // Kein Marker — bei nächstem Boot würde der Seed retried
217
- const markers = await testDb.db.select().from(esOperationsTable);
219
+ const markers = await selectMany(testDb.db, esOperationsTable);
218
220
  expect(markers).toHaveLength(0);
219
221
  } finally {
220
222
  rmSync(dir, { recursive: true, force: true });
@@ -264,7 +266,7 @@ describe("runPendingSeedMigrations (integration)", () => {
264
266
  expect(dispatcher.write).toHaveBeenCalledTimes(1);
265
267
  // Marker NICHT gesetzt — retry beim nächsten Boot wird die Migration
266
268
  // nochmal ausführen. Wenn der Write nicht idempotent ist → Duplikat.
267
- const markers = await testDb.db.select().from(esOperationsTable);
269
+ const markers = await selectMany(testDb.db, esOperationsTable);
268
270
  expect(markers).toHaveLength(0);
269
271
  } finally {
270
272
  rmSync(dir, { recursive: true, force: true });
@@ -293,7 +295,7 @@ describe("runPendingSeedMigrations (integration)", () => {
293
295
  ]);
294
296
  try {
295
297
  // Pre-seed marker als wäre ein parallel-Pod schon durch
296
- await testDb.db.insert(esOperationsTable).values({
298
+ await insertOne(testDb.db, esOperationsTable, {
297
299
  id: "2026-05-20-race",
298
300
  operationType: "seed-migration",
299
301
  durationMs: 42,
@@ -318,7 +320,7 @@ describe("runPendingSeedMigrations (integration)", () => {
318
320
  });
319
321
 
320
322
  expect(dispatcher.write).not.toHaveBeenCalled();
321
- const markers = await testDb.db.select().from(esOperationsTable);
323
+ const markers = await selectMany(testDb.db, esOperationsTable);
322
324
  expect(markers).toHaveLength(1); // nur der pre-seeded
323
325
  } finally {
324
326
  rmSync(dir, { recursive: true, force: true });
@@ -354,7 +356,7 @@ describe("runPendingSeedMigrations (integration)", () => {
354
356
  ).rejects.toThrow(/stop here/);
355
357
 
356
358
  // Nur first hat marker — fails warf, never wurde nie attempted
357
- const markers = await testDb.db.select().from(esOperationsTable);
359
+ const markers = await selectMany(testDb.db, esOperationsTable);
358
360
  expect(markers.map((m) => m.id)).toEqual(["2026-05-19-first"]);
359
361
  } finally {
360
362
  rmSync(dir, { recursive: true, force: true });
@@ -2,10 +2,10 @@
2
2
  // dispatcher-call) testen wir gegen Postgres in der integration-test.
3
3
  // Hier nur die pure-logic-Pfade die kein echtes DB brauchen.
4
4
 
5
+ import { describe, expect, test } from "bun:test";
5
6
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
6
7
  import { tmpdir } from "node:os";
7
8
  import { join } from "node:path";
8
- import { describe, expect, test } from "vitest";
9
9
  import { runPendingSeedMigrations } from "../runner";
10
10
 
11
11
  function makeTempSeedsDir(files: readonly { name: string; content: string }[]): string {
@@ -14,31 +14,41 @@ function makeTempSeedsDir(files: readonly { name: string; content: string }[]):
14
14
  return dir;
15
15
  }
16
16
 
17
- // Minimal DB-Stub — Runner ruft transaction() + select() + insert() +
18
- // execute() auf. execute() liefert ein leeres array für den
19
- // re-check-inside-lock (= "nicht applied, weiter mit Run").
17
+ // Minimal DB-Stub — Runner uses bun-db helpers (selectMany, insertOne) +
18
+ // asRawClient(tx).unsafe(...) for the advisory lock + re-check + insert.
19
+ // Returns parsed rows based on the SQL text shape:
20
+ // SELECT * FROM "kumiko_es_operations" ... → applied-set rows
21
+ // SELECT pg_advisory_xact_lock(...) → ignored
22
+ // SELECT 1 FROM "kumiko_es_operations" ... → empty (= not applied)
23
+ // INSERT INTO "kumiko_es_operations" ... → record + add to applied
20
24
  function makeStubDb(initialApplied: readonly string[] = []) {
21
25
  const inserts: Array<Record<string, unknown>> = [];
22
26
  const applied = new Set(initialApplied);
27
+ const unsafe = async (
28
+ sqlText: string,
29
+ params?: readonly unknown[],
30
+ ): Promise<readonly unknown[]> => {
31
+ if (/SELECT \* FROM "kumiko_es_operations"/.test(sqlText)) {
32
+ return Array.from(applied).map((id) => ({
33
+ id,
34
+ operationType: "seed-migration",
35
+ }));
36
+ }
37
+ if (/INSERT INTO "kumiko_es_operations"/.test(sqlText)) {
38
+ const id = String(params?.[0]);
39
+ inserts.push({ id });
40
+ applied.add(id);
41
+ return [{ id }];
42
+ }
43
+ // pg_advisory_xact_lock + re-check both yield empty → "not applied, run".
44
+ return [];
45
+ };
23
46
  const db = {
47
+ unsafe,
48
+ begin: async (cb: (tx: unknown) => Promise<unknown>) => cb(db),
24
49
  transaction: async (cb: (tx: unknown) => Promise<void>) => {
25
50
  await cb(db);
26
51
  },
27
- select: () => ({
28
- from: () => ({
29
- where: async () => Array.from(applied).map((id) => ({ id })),
30
- }),
31
- }),
32
- insert: () => ({
33
- values: async (row: Record<string, unknown>) => {
34
- inserts.push(row);
35
- if (typeof row["id"] === "string") applied.add(row["id"]);
36
- },
37
- }),
38
- // execute: für pg_advisory_xact_lock + re-check. Leere Liste = "nicht
39
- // applied im Inner-Lock-Scope, weiter mit Run". applied-set check via
40
- // select() oben wird sowieso schon angewendet.
41
- execute: async (_q: unknown) => [],
42
52
  };
43
53
  return { db, inserts, applied };
44
54
  }
@@ -7,10 +7,15 @@
7
7
  // config-seed.ts:40). Events haben createdBy = SYSTEM_TENANT_ID-User
8
8
  // → audit-fähig.
9
9
 
10
- import { sql } from "drizzle-orm";
11
10
  import type { DbRunner } from "../db";
11
+ import {
12
+ selectAllTenants,
13
+ selectMembershipsOfUser,
14
+ selectUserByEmail,
15
+ } from "../db/queries/seed-context";
12
16
  import { createSystemUser, SYSTEM_TENANT_ID } from "../engine";
13
17
  import type { Dispatcher } from "../pipeline/dispatcher";
18
+ import { parseStringArrayJson } from "../utils/parse-string-array-json";
14
19
  import type { SeedMembershipRow, SeedMigrationContext, SeedTenantRow } from "./types";
15
20
 
16
21
  export type CreateSeedMigrationContextArgs = {
@@ -59,63 +64,25 @@ export function createSeedMigrationContext(
59
64
  },
60
65
 
61
66
  findUserByEmail: async (email) => {
62
- // Direct DB-Read via read_users-Projection (gleicher Pfad wie
63
- // UserQueries.findForAuth aber ohne Dispatcher-Roundtrip; Seeds
64
- // greifen oft 1-N Lookups → direkt schneller).
65
- // @cast-boundary db-row — drizzle execute(sql) returns row-array
66
- // direkt (kein { rows }-Wrapper); column-types vom SQL-Cast oben
67
- const rows = (await args.dbRunner.execute(
68
- sql`SELECT id::text AS id, email, tenant_id::text AS tenant_id
69
- FROM read_users
70
- WHERE email = ${email}
71
- LIMIT 1`,
72
- )) as unknown as readonly { id: string; email: string; tenant_id: string }[];
73
- const row = rows[0];
67
+ const row = await selectUserByEmail(args.dbRunner, email);
74
68
  if (!row) return null;
75
- return { id: row.id, email: row.email, tenantId: row.tenant_id };
69
+ return { id: row.id, email: row.email, tenantId: row.tenantId };
76
70
  },
77
71
 
78
72
  findMembershipsOfUser: async (userId) => {
79
- // INNER JOIN auf kumiko_events um den stream-tenant (events.tenant_id
80
- // der v1-Row) neben dem payload-tenant (memberships.tenant_id) zu
81
- // liefern. Die beiden divergieren wenn das Aggregate von einem
82
- // Executor mit fremder tenantId angelegt wurde (seedTenantMembership
83
- // by=systemAdmin) — typischer publicstatus-Driver-Use-Case.
84
- // INNER (nicht LEFT): kein v1-Event bei vorhandener Read-Row wäre
85
- // Data-Drift, kein legitimer Zustand für Seed-Migrations.
86
- // @cast-boundary db-row — roles ist JSON-string in der text-Spalte
87
- // (Memory: tenant-membership.created payload "[\"User\"]"), wird unten geparst
88
- const rows = (await args.dbRunner.execute(
89
- sql`SELECT m.user_id::text AS user_id,
90
- m.tenant_id::text AS tenant_id,
91
- e.tenant_id::text AS stream_tenant_id,
92
- m.roles
93
- FROM read_tenant_memberships m
94
- JOIN kumiko_events e ON e.aggregate_id = m.id AND e.version = 1
95
- WHERE m.user_id = ${userId}`,
96
- )) as unknown as readonly {
97
- user_id: string;
98
- tenant_id: string;
99
- stream_tenant_id: string;
100
- roles: string;
101
- }[];
73
+ const rows = await selectMembershipsOfUser(args.dbRunner, userId);
102
74
  return rows.map(
103
75
  (r): SeedMembershipRow => ({
104
76
  userId: r.user_id,
105
77
  tenantId: r.tenant_id,
106
78
  streamTenantId: r.stream_tenant_id,
107
- roles: safeParseRolesJson(r.roles),
79
+ roles: parseStringArrayJson(r.roles),
108
80
  }),
109
81
  );
110
82
  },
111
83
 
112
84
  findTenants: async () => {
113
- // @cast-boundary db-row
114
- const rows = (await args.dbRunner.execute(
115
- sql`SELECT id::text AS id, name, tenant_key
116
- FROM read_tenants
117
- ORDER BY inserted_at`,
118
- )) as unknown as readonly { id: string; name: string; tenant_key: string }[];
85
+ const rows = await selectAllTenants(args.dbRunner);
119
86
  return rows.map((r): SeedTenantRow => ({ id: r.id, name: r.name, tenantKey: r.tenant_key }));
120
87
  },
121
88
 
@@ -123,17 +90,5 @@ export function createSeedMigrationContext(
123
90
  };
124
91
  }
125
92
 
126
- function safeParseRolesJson(raw: string): readonly string[] {
127
- try {
128
- const parsed: unknown = JSON.parse(raw);
129
- if (Array.isArray(parsed) && parsed.every((x) => typeof x === "string")) {
130
- return parsed;
131
- }
132
- } catch {
133
- // Fallthrough — return empty rather than throwing in a seed context.
134
- }
135
- return [];
136
- }
137
-
138
93
  // Re-export für Caller-Convenience.
139
94
  export type { SeedMigrationContext } from "./types";
@@ -10,9 +10,9 @@
10
10
  // bunx kumiko ops seed:status → operation_type = "seed-migration"
11
11
  // bunx kumiko ops projection:status → operation_type = "projection-rebuild"
12
12
 
13
- import { sql } from "drizzle-orm";
13
+ // sql now comes from native dialect
14
14
  import { type DbConnection, tableExists } from "../db";
15
- import { index, integer, table as pgTable, text, timestamp } from "../db/dialect";
15
+ import { index, integer, table as pgTable, sql, text, timestamp } from "../db/dialect";
16
16
  import { unsafePushTables } from "../stack";
17
17
 
18
18
  export type EsOperationType = "seed-migration";
@@ -22,20 +22,13 @@
22
22
 
23
23
  import { readdir, readFile } from "node:fs/promises";
24
24
  import path from "node:path";
25
- import { eq, sql } from "drizzle-orm";
26
25
  import type { DbConnection, DbRunner } from "../db";
26
+ import { acquireEsOpsAdvisoryLock, esOperationExists } from "../db/queries/es-ops";
27
+ import { insertOne, selectMany } from "../db/query";
27
28
  import type { Registry } from "../engine";
28
29
  import { esOperationsTable } from "./operations-schema";
29
30
  import type { EsOperationAppliedBy, SeedMigration, SeedMigrationContext } from "./types";
30
31
 
31
- // Stabiler 32-bit-Integer-Lock-Key für pg_advisory_xact_lock. Multi-Replica-
32
- // Boots gegen den selben Stack greifen denselben Lock — sequentialisiert
33
- // die Migration ohne dass jedes Pod alle pending Files parallel anwendet.
34
- // Ohne Lock: Pod A + Pod B sehen beide dieselbe pending-Liste → beide
35
- // laufen migration.run() → events DOUBLED, marker-unique-constraint
36
- // catched zu spät (nur den Marker, nicht die schon-committed Events).
37
- const ES_OPS_LOCK_KEY = 0x65_73_6f_70; // 'esop' als hex
38
-
39
32
  export type RunPendingSeedMigrationsArgs = {
40
33
  readonly db: DbConnection;
41
34
  /** Absoluter Pfad zum seeds-Directory (typically <appRoot>/seeds). */
@@ -130,30 +123,24 @@ export async function runPendingSeedMigrations(
130
123
 
131
124
  const start = Date.now();
132
125
  try {
133
- await args.db.transaction(async (tx) => {
126
+ await args.db.begin(async (tx: DbRunner) => {
134
127
  // Advisory-Lock: sequentialisiert Multi-Replica-Boots. Zweiter
135
128
  // Pod blockt bis erster fertig ist, dann re-checked sein
136
129
  // applied-set (außerhalb dieser Funktion in nächster Iteration)
137
130
  // und findet den Marker → skip. Lock wird beim Tx-Commit
138
131
  // automatisch released (xact-scope).
139
- await tx.execute(sql`SELECT pg_advisory_xact_lock(${ES_OPS_LOCK_KEY})`);
132
+ await acquireEsOpsAdvisoryLock(tx);
140
133
 
141
- // Re-check applied-set INSIDE Tx + Lock — verhindert Race
142
- // wo Pod-A schon committed hat während Pod-B vor dem Lock
143
- // war. Sonst würde Pod-B die Migration nochmal ausführen.
144
- const reCheck = (await tx.execute(
145
- sql`SELECT 1 FROM kumiko_es_operations WHERE id = ${entry.id} LIMIT 1`,
146
- )) as unknown as readonly unknown[];
147
- if (reCheck.length > 0) {
134
+ if (await esOperationExists(tx, entry.id)) {
148
135
  log(`${LOG_PREFIX} race-skip "${entry.id}" — applied by parallel boot`);
149
- // skip: race-detected other replica committed marker between
150
- // loadAppliedIds() and this tx; their run already covered the work.
136
+ // skip: parallel boot won the advisory-lock and already applied
137
+ // this migration. Nothing more to do in this tx.
151
138
  return;
152
139
  }
153
140
 
154
141
  const ctx = args.createContext(tx);
155
142
  await migration.run(ctx);
156
- await tx.insert(esOperationsTable).values({
143
+ await insertOne(tx, esOperationsTable, {
157
144
  id: entry.id,
158
145
  operationType: "seed-migration",
159
146
  durationMs: Date.now() - start,
@@ -204,11 +191,10 @@ async function listSeedFiles(seedsDir: string): Promise<readonly SeedFileEntry[]
204
191
  }
205
192
 
206
193
  async function loadAppliedIds(db: DbConnection): Promise<Set<string>> {
207
- const rows = await db
208
- .select({ id: esOperationsTable.id })
209
- .from(esOperationsTable)
210
- .where(eq(esOperationsTable.operationType, "seed-migration"));
211
- return new Set(rows.map((r: { id: string }) => r.id));
194
+ const rows = await selectMany<{ id: string }>(db, esOperationsTable, {
195
+ operationType: "seed-migration",
196
+ });
197
+ return new Set(rows.map((r) => r.id));
212
198
  }
213
199
 
214
200
  async function loadSeedModule(filePath: string): Promise<SeedMigration> {