@cosmicdrift/kumiko-framework 1.0.0 → 2.0.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 (424) hide show
  1. package/README.md +9 -38
  2. package/package.json +21 -2
  3. package/src/__tests__/anonymous-access.integration.test.ts +185 -0
  4. package/src/__tests__/consumer-cli.integration.test.ts +172 -0
  5. package/src/__tests__/entity-permalink-open.integration.test.ts +94 -0
  6. package/src/__tests__/full-stack.integration.test.ts +1 -1
  7. package/src/__tests__/schema-cli-temporal-polyfill.test.ts +48 -0
  8. package/src/__tests__/schema-cli.integration.test.ts +50 -0
  9. package/src/__tests__/store-table.integration.test.ts +93 -0
  10. package/src/api/__tests__/api.test.ts +373 -0
  11. package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
  13. package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +253 -0
  14. package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +222 -0
  15. package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +249 -0
  16. package/src/api/__tests__/auth-routes-mfa-verify.test.ts +202 -0
  17. package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
  18. package/src/api/__tests__/batch.integration.test.ts +101 -11
  19. package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
  20. package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
  21. package/src/api/__tests__/jwt.test.ts +200 -1
  22. package/src/api/__tests__/login-rate-limiter-sweep.test.ts +50 -0
  23. package/src/api/__tests__/pat-scope.test.ts +36 -0
  24. package/src/api/__tests__/pii-leak-guard.integration.test.ts +103 -0
  25. package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +138 -0
  26. package/src/api/__tests__/request-id-middleware.test.ts +51 -0
  27. package/src/api/__tests__/server-boot-guards.test.ts +71 -0
  28. package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
  29. package/src/api/__tests__/sse-broker.test.ts +57 -0
  30. package/src/api/__tests__/sse-route.test.ts +4 -0
  31. package/src/api/api-constants.ts +11 -0
  32. package/src/api/auth-middleware.ts +248 -40
  33. package/src/api/auth-routes.ts +576 -95
  34. package/src/api/index.ts +13 -4
  35. package/src/api/jwt.ts +170 -11
  36. package/src/api/pat-scope.ts +14 -0
  37. package/src/api/pii-leak-guard.ts +47 -0
  38. package/src/api/request-context.ts +3 -0
  39. package/src/api/request-id-middleware.ts +2 -0
  40. package/src/api/routes.ts +169 -2
  41. package/src/api/server.ts +112 -16
  42. package/src/api/sse-broker.ts +39 -0
  43. package/src/bun-db/__tests__/PATTERN.md +0 -1
  44. package/src/bun-db/__tests__/coerce-row-temporal.test.ts +41 -0
  45. package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
  46. package/src/bun-db/__tests__/write-brand.test.ts +21 -0
  47. package/src/bun-db/connection.ts +3 -3
  48. package/src/bun-db/index.ts +2 -0
  49. package/src/bun-db/query.ts +105 -32
  50. package/src/consumer-cli.ts +134 -0
  51. package/src/crypto/__tests__/blind-index.test.ts +130 -0
  52. package/src/crypto/__tests__/event-pii.test.ts +161 -0
  53. package/src/crypto/__tests__/kek-rotation.integration.test.ts +180 -0
  54. package/src/crypto/__tests__/kms-adapter-contract.ts +134 -0
  55. package/src/crypto/__tests__/kms-adapter.contract.test.ts +4 -0
  56. package/src/crypto/__tests__/pg-kms-adapter.integration.test.ts +117 -0
  57. package/src/crypto/__tests__/pii-field-encryption.test.ts +376 -0
  58. package/src/crypto/__tests__/request-kms-cache.test.ts +74 -0
  59. package/src/crypto/__tests__/subject-resolver.test.ts +108 -0
  60. package/src/crypto/blind-index.ts +118 -0
  61. package/src/crypto/event-pii.ts +70 -0
  62. package/src/crypto/in-memory-kms-adapter.ts +50 -0
  63. package/src/crypto/index.ts +67 -0
  64. package/src/crypto/kms-adapter.ts +2 -0
  65. package/src/crypto/pg-kms-adapter.ts +295 -0
  66. package/src/crypto/pii-field-encryption.ts +248 -0
  67. package/src/crypto/request-kms-cache.ts +38 -0
  68. package/src/crypto/subject-resolver.ts +91 -0
  69. package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +191 -0
  70. package/src/db/__tests__/blind-index.integration.test.ts +248 -0
  71. package/src/db/__tests__/build-filter-where.test.ts +34 -0
  72. package/src/db/__tests__/collect-table-metas.test.ts +10 -10
  73. package/src/db/__tests__/config-seed.integration.test.ts +13 -5
  74. package/src/db/__tests__/dialect-instant.test.ts +1 -4
  75. package/src/db/__tests__/entity-field-encryption.test.ts +7 -7
  76. package/src/db/__tests__/entity-table-meta-source.test.ts +50 -0
  77. package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
  78. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +402 -0
  79. package/src/db/__tests__/event-store-executor.integration.test.ts +299 -29
  80. package/src/db/__tests__/feature-table-sources.test.ts +34 -0
  81. package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +118 -39
  82. package/src/db/__tests__/instant-to-driver-temporal.test.ts +21 -0
  83. package/src/db/__tests__/located-timestamp.test.ts +19 -0
  84. package/src/db/__tests__/migrate-generator.test.ts +21 -0
  85. package/src/db/__tests__/migrate-runner.test.ts +61 -0
  86. package/src/db/__tests__/number-field-fractional.integration.test.ts +58 -0
  87. package/src/db/__tests__/rebuild-marker.test.ts +13 -3
  88. package/src/db/__tests__/replay-migration-sql.test.ts +384 -0
  89. package/src/db/__tests__/schema-inspection.test.ts +7 -0
  90. package/src/db/__tests__/schema-migration.integration.test.ts +1 -1
  91. package/src/db/__tests__/table-builder-meta-lockstep.test.ts +39 -0
  92. package/src/db/__tests__/tenant-db-where-merge.test.ts +49 -3
  93. package/src/db/__tests__/tenant-db.integration.test.ts +6 -2
  94. package/src/db/api.ts +2 -2
  95. package/src/db/apply-entity-event.ts +18 -14
  96. package/src/db/blind-index-cleanup.ts +55 -0
  97. package/src/db/bun-provider.ts +2 -2
  98. package/src/db/collect-table-metas.ts +6 -7
  99. package/src/db/config-seed.ts +9 -9
  100. package/src/db/connection.ts +7 -12
  101. package/src/db/cursor.ts +1 -18
  102. package/src/db/dialect.ts +20 -26
  103. package/src/db/entity-field-encryption.ts +81 -24
  104. package/src/db/entity-table-meta-types.ts +2 -0
  105. package/src/db/entity-table-meta.ts +63 -90
  106. package/src/db/event-store-executor-context.ts +404 -0
  107. package/src/db/event-store-executor-read.ts +275 -0
  108. package/src/db/event-store-executor-write.ts +644 -0
  109. package/src/db/event-store-executor.ts +28 -1155
  110. package/src/db/feature-table-sources.ts +6 -5
  111. package/src/db/index.ts +20 -3
  112. package/src/db/located-timestamp.ts +4 -0
  113. package/src/db/migrate-generator.ts +39 -6
  114. package/src/db/migrate-runner.ts +107 -11
  115. package/src/db/pg-error.ts +9 -1
  116. package/src/db/postgres-provider.ts +2 -2
  117. package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
  118. package/src/db/queries/backfill-pii.ts +277 -0
  119. package/src/db/queries/ddl.ts +45 -0
  120. package/src/db/queries/event-consumer.ts +35 -2
  121. package/src/db/queries/event-store.ts +126 -19
  122. package/src/db/queries/projection-rebuild.ts +22 -8
  123. package/src/db/queries/shadow-swap.ts +183 -0
  124. package/src/db/queries/test-stack.ts +4 -30
  125. package/src/db/query-api.ts +1 -0
  126. package/src/db/query.ts +1 -0
  127. package/src/db/rebuild-marker.ts +18 -2
  128. package/src/db/reference-data.ts +2 -3
  129. package/src/db/replay-migration-sql.ts +257 -0
  130. package/src/db/schema-inspection.ts +1 -1
  131. package/src/db/table-builder.ts +102 -71
  132. package/src/db/tenant-db.ts +15 -53
  133. package/src/engine/__tests__/boot-validator-action-wiring.test.ts +242 -0
  134. package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
  135. package/src/engine/__tests__/boot-validator-dashboard.test.ts +237 -0
  136. package/src/engine/__tests__/boot-validator-entity-list.test.ts +104 -0
  137. package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -69
  138. package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +26 -5
  139. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +3 -19
  140. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +246 -4
  141. package/src/engine/__tests__/boot-validator.test.ts +219 -15
  142. package/src/engine/__tests__/build-app-schema.test.ts +82 -0
  143. package/src/engine/__tests__/build-config-feature-schema.test.ts +125 -0
  144. package/src/engine/__tests__/build-target.test.ts +3 -11
  145. package/src/engine/__tests__/codemod-pipeline.test.ts +152 -21
  146. package/src/engine/__tests__/config-helpers.test.ts +16 -0
  147. package/src/engine/__tests__/define-feature-entity-mapping.test.ts +6 -0
  148. package/src/engine/__tests__/define-roles.test.ts +21 -0
  149. package/src/engine/__tests__/engine.test.ts +163 -1
  150. package/src/engine/__tests__/entity-handlers.test.ts +80 -0
  151. package/src/engine/__tests__/event-migration-declarative.test.ts +65 -0
  152. package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
  153. package/src/engine/__tests__/extend-entity-projection.test.ts +123 -0
  154. package/src/engine/__tests__/factories-time.test.ts +2 -66
  155. package/src/engine/__tests__/feature-crud-shorthand.test.ts +28 -0
  156. package/src/engine/__tests__/feature-manifest.test.ts +31 -1
  157. package/src/engine/__tests__/field-access.test.ts +23 -1
  158. package/src/engine/__tests__/hook-phases.test.ts +5 -5
  159. package/src/engine/__tests__/membership-roles.test.ts +4 -10
  160. package/src/engine/__tests__/nav.test.ts +86 -1
  161. package/src/engine/__tests__/pipeline-engine.test.ts +9 -9
  162. package/src/engine/__tests__/pipeline-handler.integration.test.ts +18 -18
  163. package/src/engine/__tests__/pipeline-observability.integration.test.ts +2 -2
  164. package/src/engine/__tests__/pipeline-performance.integration.test.ts +3 -3
  165. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +9 -9
  166. package/src/engine/__tests__/post-query-hook.test.ts +7 -7
  167. package/src/engine/__tests__/registrar-object-form.test.ts +141 -0
  168. package/src/engine/__tests__/registry.test.ts +144 -0
  169. package/src/engine/__tests__/schema-builder.test.ts +18 -0
  170. package/src/engine/__tests__/screen.test.ts +147 -1
  171. package/src/engine/__tests__/soft-delete-cleanup.test.ts +3 -0
  172. package/src/engine/__tests__/store-table.test.ts +229 -0
  173. package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
  174. package/src/engine/__tests__/{visual-tree-patterns.test.ts → tree-actions-patterns.test.ts} +7 -95
  175. package/src/engine/__tests__/validate-projection-allowlist.test.ts +15 -15
  176. package/src/engine/boot-validator/__tests__/config-deps.test.ts +92 -0
  177. package/src/engine/boot-validator/action-wiring.ts +149 -0
  178. package/src/engine/boot-validator/boot-check.ts +21 -0
  179. package/src/engine/boot-validator/config-deps.ts +40 -4
  180. package/src/engine/boot-validator/entity-handler.ts +20 -21
  181. package/src/engine/boot-validator/entity-list-screens.ts +88 -0
  182. package/src/engine/boot-validator/gdpr-storage.ts +0 -20
  183. package/src/engine/boot-validator/i18n-keys.ts +58 -4
  184. package/src/engine/boot-validator/index.ts +33 -12
  185. package/src/engine/boot-validator/nav.ts +125 -0
  186. package/src/engine/boot-validator/pii-retention.ts +95 -10
  187. package/src/engine/boot-validator/{screens-nav.ts → screens.ts} +214 -191
  188. package/src/engine/boot-validator/workspaces.ts +68 -0
  189. package/src/engine/build-app-schema.ts +16 -0
  190. package/src/engine/build-config-feature-schema.ts +44 -7
  191. package/src/engine/codemod/pipeline-codemod.ts +5 -5
  192. package/src/engine/config-helpers.ts +15 -0
  193. package/src/engine/constants.ts +32 -6
  194. package/src/engine/create-app.ts +11 -0
  195. package/src/engine/define-feature.ts +95 -947
  196. package/src/engine/define-handler.ts +28 -94
  197. package/src/engine/define-workflow.ts +1 -1
  198. package/src/engine/effective-features.ts +12 -2
  199. package/src/engine/entity-handlers.ts +76 -11
  200. package/src/engine/extensions/tenant-data.ts +19 -0
  201. package/src/engine/extensions/user-data.ts +29 -2
  202. package/src/engine/factories.ts +8 -49
  203. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +26 -29
  204. package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/feature.ts +9 -0
  205. package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/screens.ts +4 -0
  206. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -2
  207. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +18 -8
  208. package/src/engine/feature-ast/__tests__/parse.test.ts +1640 -160
  209. package/src/engine/feature-ast/__tests__/patch.test.ts +206 -12
  210. package/src/engine/feature-ast/__tests__/patcher.test.ts +20 -23
  211. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +380 -5
  212. package/src/engine/feature-ast/extractors/events.ts +322 -0
  213. package/src/engine/feature-ast/extractors/handlers.ts +234 -0
  214. package/src/engine/feature-ast/extractors/hooks.ts +243 -0
  215. package/src/engine/feature-ast/extractors/index.ts +34 -31
  216. package/src/engine/feature-ast/extractors/jobs-routes.ts +221 -0
  217. package/src/engine/feature-ast/extractors/projections-screens.ts +269 -0
  218. package/src/engine/feature-ast/extractors/round1.ts +3 -3
  219. package/src/engine/feature-ast/extractors/round5.ts +3 -3
  220. package/src/engine/feature-ast/extractors/round6.ts +2 -36
  221. package/src/engine/feature-ast/extractors/shared.ts +64 -6
  222. package/src/engine/feature-ast/index.ts +2 -4
  223. package/src/engine/feature-ast/parse.ts +130 -23
  224. package/src/engine/feature-ast/patch.ts +39 -50
  225. package/src/engine/feature-ast/patcher.ts +37 -38
  226. package/src/engine/feature-ast/patterns.ts +50 -62
  227. package/src/engine/feature-ast/render.ts +67 -44
  228. package/src/engine/feature-builder-state.ts +168 -0
  229. package/src/engine/feature-config-events-jobs.ts +403 -0
  230. package/src/engine/feature-entity-handlers.ts +208 -0
  231. package/src/engine/feature-manifest.ts +2 -1
  232. package/src/engine/feature-ui-extensions.ts +500 -0
  233. package/src/engine/field-access.ts +13 -2
  234. package/src/engine/field-helpers.ts +31 -0
  235. package/src/engine/handler-helpers.ts +26 -0
  236. package/src/engine/hook-helpers.ts +16 -0
  237. package/src/engine/index.ts +23 -7
  238. package/src/engine/membership-roles.ts +13 -0
  239. package/src/engine/object-form.ts +27 -0
  240. package/src/engine/ownership.ts +26 -79
  241. package/src/engine/pattern-library/__tests__/library.test.ts +7 -20
  242. package/src/engine/pattern-library/library.ts +44 -1152
  243. package/src/engine/pattern-library/mixed-schemas.ts +450 -0
  244. package/src/engine/pattern-library/opaque-schemas.ts +124 -0
  245. package/src/engine/pattern-library/shared-fields.ts +75 -0
  246. package/src/engine/pattern-library/static-schemas.ts +456 -0
  247. package/src/engine/pipeline.ts +6 -11
  248. package/src/engine/registry-facade.ts +362 -0
  249. package/src/engine/registry-ingest.ts +478 -0
  250. package/src/engine/registry-state.ts +388 -0
  251. package/src/engine/registry-validate.ts +642 -0
  252. package/src/engine/registry.ts +78 -1658
  253. package/src/engine/run-pipeline.ts +1 -1
  254. package/src/engine/schema-builder.ts +1 -0
  255. package/src/engine/screen-helpers.ts +54 -0
  256. package/src/engine/soft-delete-cleanup.ts +6 -2
  257. package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
  258. package/src/engine/steps/_duration-utils.ts +2 -0
  259. package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
  260. package/src/engine/tier-resolver-extension.ts +3 -2
  261. package/src/engine/types/config.ts +2 -482
  262. package/src/engine/types/define-handler.ts +2 -0
  263. package/src/engine/types/entity-handlers.ts +2 -0
  264. package/src/engine/types/event-type-map.ts +1 -37
  265. package/src/engine/types/feature.ts +2 -972
  266. package/src/engine/types/fields.ts +2 -675
  267. package/src/engine/types/handlers.ts +2 -774
  268. package/src/engine/types/hooks.ts +2 -184
  269. package/src/engine/types/http-route.ts +1 -54
  270. package/src/engine/types/identifiers.ts +1 -47
  271. package/src/engine/types/index.ts +86 -40
  272. package/src/engine/types/nav.ts +2 -63
  273. package/src/engine/types/ownership.ts +2 -0
  274. package/src/engine/types/projection.ts +2 -138
  275. package/src/engine/types/relations.ts +1 -51
  276. package/src/engine/types/screen.ts +2 -574
  277. package/src/engine/types/step.ts +2 -334
  278. package/src/engine/types/target-ref.ts +1 -21
  279. package/src/engine/types/tree-node.ts +1 -132
  280. package/src/engine/types/workspace.ts +2 -49
  281. package/src/engine/validate-projection-allowlist.ts +6 -6
  282. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +120 -1
  283. package/src/entrypoint/index.ts +49 -6
  284. package/src/errors/classes.ts +22 -1
  285. package/src/errors/field-issue.ts +0 -3
  286. package/src/errors/index.ts +1 -1
  287. package/src/errors/write-error-info.ts +10 -22
  288. package/src/es-ops/README.md +1 -1
  289. package/src/es-ops/__tests__/runner.integration.test.ts +74 -0
  290. package/src/es-ops/context.ts +4 -2
  291. package/src/es-ops/types.ts +8 -1
  292. package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
  293. package/src/event-store/__tests__/backfill-pii.integration.test.ts +279 -0
  294. package/src/event-store/__tests__/event-store.integration.test.ts +201 -0
  295. package/src/event-store/__tests__/row-to-stored-event.test.ts +2 -2
  296. package/src/event-store/__tests__/snapshot.integration.test.ts +86 -0
  297. package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +58 -0
  298. package/src/event-store/__tests__/upcaster.integration.test.ts +77 -45
  299. package/src/event-store/admin-api.ts +11 -4
  300. package/src/event-store/errors.ts +2 -35
  301. package/src/event-store/event-store.ts +64 -78
  302. package/src/event-store/events-schema.ts +11 -13
  303. package/src/event-store/index.ts +18 -3
  304. package/src/event-store/rebuild-dead-letter.ts +111 -0
  305. package/src/event-store/snapshot.ts +63 -40
  306. package/src/event-store/types.ts +2 -0
  307. package/src/files/__tests__/build-storage-key.test.ts +28 -0
  308. package/src/files/__tests__/file-ref-entity.test.ts +8 -0
  309. package/src/files/__tests__/files.integration.test.ts +24 -0
  310. package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
  311. package/src/files/__tests__/local-provider.test.ts +31 -0
  312. package/src/files/__tests__/provider-resolver.test.ts +70 -0
  313. package/src/files/__tests__/write-stream.test.ts +13 -0
  314. package/src/files/file-handle.ts +2 -19
  315. package/src/files/file-ref-entity.ts +2 -2
  316. package/src/files/file-routes.ts +13 -0
  317. package/src/files/index.ts +1 -1
  318. package/src/files/local-provider.ts +27 -8
  319. package/src/files/provider-resolver.ts +31 -20
  320. package/src/files/types.ts +13 -55
  321. package/src/i18n/__tests__/required-surface-keys.test.ts +17 -0
  322. package/src/i18n/required-surface-keys.ts +120 -1
  323. package/src/jobs/__tests__/job-queue-depth.integration.test.ts +82 -0
  324. package/src/jobs/__tests__/jobs.integration.test.ts +329 -3
  325. package/src/jobs/job-runner.ts +82 -12
  326. package/src/logging/types.ts +1 -7
  327. package/src/migrations/pending-rebuilds.ts +1 -1
  328. package/src/observability/__tests__/observability.integration.test.ts +4 -1
  329. package/src/observability/__tests__/recording-tracer.test.ts +6 -4
  330. package/src/observability/index.ts +2 -0
  331. package/src/observability/noop-provider.ts +0 -9
  332. package/src/observability/recording-tracer.ts +0 -12
  333. package/src/observability/standard-metrics.ts +64 -2
  334. package/src/observability/types/index.ts +1 -29
  335. package/src/observability/types/metric.ts +1 -56
  336. package/src/observability/types/provider.ts +1 -32
  337. package/src/observability/types/span.ts +1 -64
  338. package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +2 -2
  339. package/src/pipeline/__tests__/dispatcher.test.ts +366 -1
  340. package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
  341. package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
  342. package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +263 -0
  343. package/src/pipeline/__tests__/event-dispatcher.integration.test.ts +10 -0
  344. package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +106 -0
  345. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +308 -77
  346. package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +16 -8
  347. package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
  348. package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +80 -2
  349. package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +274 -0
  350. package/src/pipeline/__tests__/try-append-event.integration.test.ts +166 -0
  351. package/src/pipeline/dispatch-batch.ts +187 -0
  352. package/src/pipeline/dispatch-query.ts +165 -0
  353. package/src/pipeline/dispatch-shared.ts +826 -0
  354. package/src/pipeline/dispatch-stream.ts +90 -0
  355. package/src/pipeline/dispatch-write.ts +451 -0
  356. package/src/pipeline/dispatcher-utils.ts +1 -1
  357. package/src/pipeline/dispatcher.ts +44 -1372
  358. package/src/pipeline/entity-cache.ts +2 -33
  359. package/src/pipeline/event-consumer-state.ts +30 -2
  360. package/src/pipeline/event-dispatcher-admin.ts +293 -0
  361. package/src/pipeline/event-dispatcher-delivery.ts +305 -0
  362. package/src/pipeline/event-dispatcher.ts +85 -542
  363. package/src/pipeline/index.ts +2 -0
  364. package/src/pipeline/msp-rebuild.ts +42 -3
  365. package/src/pipeline/multi-stream-apply-context.ts +4 -42
  366. package/src/pipeline/projection-rebuild.ts +105 -3
  367. package/src/pipeline/system-hooks.ts +144 -1
  368. package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
  369. package/src/rate-limit/resolver.ts +16 -32
  370. package/src/schema-cli.ts +76 -16
  371. package/src/search/__tests__/meilisearch-adapter.integration.test.ts +207 -185
  372. package/src/search/__tests__/meilisearch-ids.test.ts +30 -0
  373. package/src/search/__tests__/reindex-entity.integration.test.ts +144 -0
  374. package/src/search/index.ts +6 -0
  375. package/src/search/meilisearch-adapter.ts +13 -12
  376. package/src/search/reindex-entity.ts +177 -0
  377. package/src/search/types.ts +1 -39
  378. package/src/secrets/__tests__/contains-secret.test.ts +34 -0
  379. package/src/secrets/__tests__/envelope-cipher.test.ts +59 -0
  380. package/src/secrets/__tests__/envelope.test.ts +1 -1
  381. package/src/secrets/dek-cache.ts +26 -5
  382. package/src/secrets/envelope-cipher.ts +53 -0
  383. package/src/secrets/envelope.ts +5 -3
  384. package/src/secrets/index.ts +13 -1
  385. package/src/secrets/stored-envelope.ts +46 -0
  386. package/src/secrets/types.ts +2 -162
  387. package/src/stack/__tests__/event-collector.test.ts +42 -0
  388. package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +125 -0
  389. package/src/stack/db.ts +2 -1
  390. package/src/stack/push-entity-projection-tables.ts +4 -3
  391. package/src/stack/redis.ts +8 -0
  392. package/src/stack/request-helper.ts +38 -2
  393. package/src/stack/table-helpers.ts +6 -4
  394. package/src/stack/test-stack.ts +249 -131
  395. package/src/testing/__tests__/late-bound.test.ts +32 -0
  396. package/src/testing/__tests__/wait-for.test.ts +59 -0
  397. package/src/testing/boot-validator-fixture.ts +121 -0
  398. package/src/testing/e2e-generator.ts +8 -0
  399. package/src/testing/file-provider-contract.ts +104 -0
  400. package/src/testing/handler-context.ts +3 -1
  401. package/src/testing/index.ts +5 -0
  402. package/src/testing/late-bound.ts +5 -3
  403. package/src/testing/mutable-master-key-provider.ts +29 -3
  404. package/src/testing/wait-for.ts +3 -0
  405. package/src/testing/without-ambient-temporal.ts +14 -0
  406. package/src/time/__tests__/tz-dateline.test.ts +8 -10
  407. package/src/time/geo-tz.ts +1 -32
  408. package/src/time/index.ts +1 -0
  409. package/src/time/legacy-date.ts +18 -0
  410. package/src/time/polyfill.ts +21 -38
  411. package/src/time/tz-context.ts +44 -85
  412. package/src/ui-types/app-schema.ts +12 -0
  413. package/src/ui-types/index.ts +20 -7
  414. package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
  415. package/src/utils/safe-json.ts +13 -1
  416. package/src/__tests__/raw-table.integration.test.ts +0 -116
  417. package/src/bun-db/__tests__/bun-test-stack.ts +0 -6
  418. package/src/db/__tests__/encryption.test.ts +0 -39
  419. package/src/db/encryption.ts +0 -39
  420. package/src/db/row-helpers.ts +0 -4
  421. package/src/engine/__tests__/raw-table.test.ts +0 -150
  422. package/src/engine/__tests__/unmanaged-table.test.ts +0 -127
  423. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +0 -184
  424. package/src/engine/feature-ast/extractors/round4.ts +0 -1366
@@ -1,7 +1,8 @@
1
1
  // Single enumeration of every table-bearing registration on a feature
2
- // (r.projection, r.multiStreamProjection with table, r.rawTable). Consumed
3
- // by BOTH the setupTestStack auto-push and collectTableMetas — one list, so
4
- // test-DB-push and `kumiko schema generate` cannot drift apart again (#255).
2
+ // (r.projection, r.multiStreamProjection with table, r.storeTable).
3
+ // Consumed by BOTH the setupTestStack auto-push and collectTableMetas —
4
+ // one list, so test-DB-push and `kumiko schema generate` cannot drift
5
+ // apart again (#255).
5
6
  // A new table-bearing registrar must be added HERE, not in the consumers.
6
7
 
7
8
  import type { FeatureDefinition } from "../engine/types";
@@ -28,8 +29,8 @@ export function enumerateFeatureTableSources(
28
29
  origin: `multiStreamProjection "${name}" (${feature.name})`,
29
30
  });
30
31
  }
31
- for (const [name, raw] of Object.entries(feature.rawTables)) {
32
- sources.push({ table: raw.table, origin: `rawTable "${name}" (${feature.name})` });
32
+ for (const [name, raw] of Object.entries(feature.storeTables)) {
33
+ sources.push({ table: raw.meta, origin: `storeTable "${name}" (${feature.name})` });
33
34
  }
34
35
  return sources;
35
36
  }
package/src/db/index.ts CHANGED
@@ -1,8 +1,16 @@
1
1
  export { assertExistsIn } from "./assert-exists-in";
2
+ export { nullBlindIndexesForSubject } from "./blind-index-cleanup";
2
3
  export { collectTableMetas } from "./collect-table-metas";
3
4
  export { flattenCompoundTypes, rehydrateCompoundTypes } from "./compound-types";
4
5
  export { seedConfigValues } from "./config-seed";
5
- export type { DbConnection, DbConnectionOptions, DbRow, DbRunner, DbTx } from "./connection";
6
+ export type {
7
+ DbConnection,
8
+ DbConnectionOptions,
9
+ DbPoolHandle,
10
+ DbRow,
11
+ DbRunner,
12
+ DbTx,
13
+ } from "./connection";
6
14
  export { createDbConnection, dbConnectionOptionsFromEnv } from "./connection";
7
15
  export type { CursorQueryOptions, CursorResult } from "./cursor";
8
16
  export { decodeCursor, encodeCursor } from "./cursor";
@@ -34,12 +42,13 @@ export {
34
42
  enrichRowWithReferences,
35
43
  enrichWithReferences,
36
44
  } from "./eagerload";
37
- export type { EncryptionProvider } from "./encryption";
38
- export { createEncryptionProvider } from "./encryption";
39
45
  export {
40
46
  collectEncryptedFieldNames,
47
+ configuredEntityFieldEncryption,
48
+ configureEntityFieldEncryption,
41
49
  decryptEntityFieldValues,
42
50
  encryptEntityFieldValues,
51
+ resetEntityFieldEncryptionCacheForTests,
43
52
  } from "./entity-field-encryption";
44
53
  export type {
45
54
  BuildEntityTableMetaOptions,
@@ -103,6 +112,7 @@ export {
103
112
  fetchOne,
104
113
  insertMany,
105
114
  insertOne,
115
+ runInSavepoint,
106
116
  selectMany,
107
117
  transaction,
108
118
  updateMany,
@@ -114,6 +124,13 @@ export {
114
124
  } from "./rebuild-marker";
115
125
  export { seedReferenceData } from "./reference-data";
116
126
  export { renderTableDdl, renderTablesDdl } from "./render-ddl";
127
+ export {
128
+ diffReplayAgainstSnapshot,
129
+ type ReplayedSchema,
130
+ type ReplayedTable,
131
+ type ReplayMismatch,
132
+ replayMigrationsDir,
133
+ } from "./replay-migration-sql";
117
134
  export { tableExists } from "./schema-inspection";
118
135
  export {
119
136
  buildBaseColumns,
@@ -9,6 +9,10 @@
9
9
  // gespeicherter tz). Server kennt User-TZ nicht — User-spezifische
10
10
  // Anzeige passiert client-seitig aus utc.
11
11
 
12
+ // Static import, not the ambient global: Bun doesn't expose Temporal on
13
+ // globalThis, so this crashed with "Temporal is not defined" outside boot
14
+ // paths that install it (#1480).
15
+ import { Temporal } from "temporal-polyfill";
12
16
  import type { EntityDefinition } from "../engine/types";
13
17
 
14
18
  // Sprint F: <name>Utc-Spalte ist jetzt instant() (siehe dialect.ts) —
@@ -202,13 +202,40 @@ export function diffSnapshots(prev: Snapshot | null, next: Snapshot): SchemaDiff
202
202
  }
203
203
 
204
204
  // Managed projections are event-stream derivatives: in-place-unsafe changes (NOT NULL w/o default, UNIQUE, SET NOT NULL, type change, dropped col) → DROP+CREATE + replay; additive-safe changes stay cheap ALTERs.
205
+ function describeRecreateReasons(td: TableDiff): readonly string[] {
206
+ const reasons: string[] = [];
207
+ if (td.droppedColumns.length > 0) {
208
+ reasons.push(`dropped column(s): ${td.droppedColumns.join(", ")}`);
209
+ }
210
+ const notNullNoDefault = td.newColumns
211
+ .filter((c) => c.notNull && c.defaultSql === undefined)
212
+ .map((c) => c.name);
213
+ if (notNullNoDefault.length > 0) {
214
+ reasons.push(`new NOT NULL column(s) without default: ${notNullNoDefault.join(", ")}`);
215
+ }
216
+ const newUniqueIndexes = td.newIndexes
217
+ .filter((idx) => idx.unique === true)
218
+ .map((idx) => idx.name);
219
+ if (newUniqueIndexes.length > 0) {
220
+ reasons.push(`new UNIQUE index(es): ${newUniqueIndexes.join(", ")}`);
221
+ }
222
+ const madeNotNull = td.changedColumns
223
+ .filter((c) => c.nullabilityChanged?.to === true)
224
+ .map((c) => c.name);
225
+ if (madeNotNull.length > 0) {
226
+ reasons.push(`column(s) set NOT NULL: ${madeNotNull.join(", ")}`);
227
+ }
228
+ const typeChanged = td.changedColumns
229
+ .filter((c) => c.typeChanged !== undefined)
230
+ .map((c) => c.name);
231
+ if (typeChanged.length > 0) {
232
+ reasons.push(`column type change: ${typeChanged.join(", ")}`);
233
+ }
234
+ return reasons;
235
+ }
236
+
205
237
  export function managedChangeRequiresRecreate(td: TableDiff): boolean {
206
- if (td.droppedColumns.length > 0) return true;
207
- if (td.newColumns.some((c) => c.notNull && c.defaultSql === undefined)) return true;
208
- if (td.newIndexes.some((idx) => idx.unique === true)) return true;
209
- return td.changedColumns.some(
210
- (c) => c.nullabilityChanged?.to === true || c.typeChanged !== undefined,
211
- );
238
+ return describeRecreateReasons(td).length > 0;
212
239
  }
213
240
 
214
241
  // --- SQL-Render ---------------------------------------------------------
@@ -296,6 +323,12 @@ export function renderMigrationSql(
296
323
  lines.push(`-- ${td.tableName}`);
297
324
  if (td.nextMeta.source === "managed" && managedChangeRequiresRecreate(td)) {
298
325
  lines.push("-- managed projection — recreated + rebuilt from events (see .rebuild.json)");
326
+ lines.push(
327
+ `-- WARN: destructive change (${describeRecreateReasons(td).join("; ")}) forces DROP+CREATE + full event replay.`,
328
+ );
329
+ lines.push(
330
+ "-- Consider an Expand/Contract split across two releases to avoid the rebuild — see docs/guides/expand-contract-managed-projections.md.",
331
+ );
299
332
  lines.push(`DROP TABLE IF EXISTS ${quoteIdent(td.tableName)};`);
300
333
  lines.push(...renderTableDdl(td.nextMeta));
301
334
  lines.push("");
@@ -74,14 +74,103 @@ CREATE TABLE IF NOT EXISTS "_kumiko_migrations" (
74
74
  )
75
75
  `.trim();
76
76
 
77
- // Splittet SQL-File-Text in einzelne Statements. Pragma: simpler `;`-Split,
78
- // reicht weil unsere generierten SQL-Files keine eingebetteten `;` in String-
79
- // Literalen haben. App-Author der hand-editiert + tricky SQL einfügt sollte
80
- // das wissen sonst pg-Parser einziehen.
77
+ // Splits SQL-file text into individual statements on top-level `;`. A plain
78
+ // `text.split(";")` breaks the moment a `--` line comment or `/* */` block
79
+ // comment contains a semicolon (#1542) it splits mid-comment before the
80
+ // comment is ever stripped. This scans char-by-char tracking whether we're
81
+ // inside a line comment, block comment, single-quoted string, or
82
+ // double-quoted identifier, so `;` only ends a statement in plain SQL text;
83
+ // comments are dropped, quoted/identifier content (incl. `''`/`""` escapes)
84
+ // is kept verbatim. Does not handle dollar-quoted (`$$...$$`) bodies — none
85
+ // of this repo's checked-in migrations use them; add that state if one ever
86
+ // does.
87
+ type SqlScanState = "normal" | "lineComment" | "blockComment" | "singleQuote" | "doubleQuote";
88
+
81
89
  export function splitSqlStatements(sqlText: string): readonly string[] {
82
- return sqlText
83
- .split(";")
84
- .map((s) => s.replace(/--[^\n]*/g, "").trim())
90
+ const statements: string[] = [];
91
+ let current = "";
92
+ let state: SqlScanState = "normal";
93
+
94
+ for (let i = 0; i < sqlText.length; i++) {
95
+ const ch = sqlText.charAt(i);
96
+ const next = sqlText.charAt(i + 1);
97
+
98
+ if (state === "lineComment") {
99
+ if (ch === "\n") {
100
+ state = "normal";
101
+ current += ch;
102
+ }
103
+ continue;
104
+ }
105
+ if (state === "blockComment") {
106
+ if (ch === "*" && next === "/") {
107
+ state = "normal";
108
+ i++;
109
+ }
110
+ continue;
111
+ }
112
+ if (state === "singleQuote") {
113
+ current += ch;
114
+ if (ch === "'") {
115
+ if (next === "'") {
116
+ current += next;
117
+ i++;
118
+ } else {
119
+ state = "normal";
120
+ }
121
+ }
122
+ continue;
123
+ }
124
+ if (state === "doubleQuote") {
125
+ current += ch;
126
+ if (ch === '"') {
127
+ if (next === '"') {
128
+ current += next;
129
+ i++;
130
+ } else {
131
+ state = "normal";
132
+ }
133
+ }
134
+ continue;
135
+ }
136
+
137
+ // state === "normal"
138
+ if (ch === "-" && next === "-") {
139
+ state = "lineComment";
140
+ i++;
141
+ continue;
142
+ }
143
+ if (ch === "/" && next === "*") {
144
+ state = "blockComment";
145
+ i++;
146
+ continue;
147
+ }
148
+ if (ch === "'") {
149
+ state = "singleQuote";
150
+ current += ch;
151
+ continue;
152
+ }
153
+ if (ch === '"') {
154
+ state = "doubleQuote";
155
+ current += ch;
156
+ continue;
157
+ }
158
+ if (ch === ";") {
159
+ statements.push(current);
160
+ current = "";
161
+ continue;
162
+ }
163
+ current += ch;
164
+ }
165
+ if (state === "blockComment" || state === "singleQuote" || state === "doubleQuote") {
166
+ throw new Error(
167
+ `splitSqlStatements: unterminated ${state} — migration SQL is malformed, refusing to split`,
168
+ );
169
+ }
170
+ statements.push(current);
171
+
172
+ return statements
173
+ .map((s) => s.trim())
85
174
  .filter((s) => s.length > 0)
86
175
  .map((s) => `${s};`);
87
176
  }
@@ -90,14 +179,21 @@ function sha256Hex(content: string): string {
90
179
  return createHash("sha256").update(content).digest("hex");
91
180
  }
92
181
 
93
- // Liest <dir>/*.sql, sortiert lex (z.B. 0001_init.sql, 0002_add_locale.sql),
94
- // returnt Migration[] mit id + checksum + statements.
95
- export function loadMigrationsFromDir(dir: string): readonly Migration[] {
182
+ // Reads <dir>/*.sql, sorted lexically (e.g. 0001_init.sql, 0002_add_locale.sql),
183
+ // returns Migration[] with id + checksum + statements. `preprocess` is an
184
+ // optional hook applied to each file's raw content before splitting/hashing
185
+ // — used by replayMigrationsDir to expand its DESTRUCTIVE-marker comments
186
+ // without a second, drifting copy of this file-discovery logic (#1522/9).
187
+ export function loadMigrationsFromDir(
188
+ dir: string,
189
+ preprocess?: (sql: string) => string,
190
+ ): readonly Migration[] {
96
191
  const files = readdirSync(dir)
97
192
  .filter((f) => f.endsWith(".sql"))
98
193
  .sort();
99
194
  return files.map((file) => {
100
- const content = readFileSync(join(dir, file), "utf8");
195
+ const raw = readFileSync(join(dir, file), "utf8");
196
+ const content = preprocess ? preprocess(raw) : raw;
101
197
  return {
102
198
  id: file.replace(/\.sql$/, ""),
103
199
  checksum: sha256Hex(content),
@@ -3,7 +3,7 @@
3
3
  // unwrap both layers so callers don't have to know which layer produced the
4
4
  // error. Used by the event-store to distinguish a unique-violation on the
5
5
  // aggregate-version index (optimistic-concurrency conflict) from the one on
6
- // the request-id idempotency index (replay signal).
6
+ // the idempotency-key index (caller-side replay signal).
7
7
 
8
8
  export type PgErrorInfo = {
9
9
  readonly code: string | undefined;
@@ -41,6 +41,14 @@ export function isTableAlreadyExists(e: unknown): boolean {
41
41
  return extractPgError(e)?.code === "42P07";
42
42
  }
43
43
 
44
+ // PG SQLSTATE 55P03 — "lock not available". Raised by a NOWAIT-style lock
45
+ // request that couldn't be granted; a CREATE/DROP INDEX CONCURRENTLY racing
46
+ // another session's DDL on the same relation can hit this instead of a
47
+ // duplicate-relation error, depending on exact timing.
48
+ export function isLockNotAvailable(e: unknown): boolean {
49
+ return extractPgError(e)?.code === "55P03";
50
+ }
51
+
44
52
  export function constraintOf(e: unknown): string | undefined {
45
53
  return extractPgError(e)?.constraint_name;
46
54
  }
@@ -3,9 +3,9 @@
3
3
  // `asRawClient(db)` wrappt .unsafe und .begin transparent.
4
4
 
5
5
  import postgres from "postgres";
6
- import type { DbConnection, DbConnectionOptions } from "./api";
6
+ import type { DbConnectionOptions, DbPoolHandle } from "./api";
7
7
 
8
- export function createPgConnection(url: string, options: DbConnectionOptions = {}): DbConnection {
8
+ export function createPgConnection(url: string, options: DbConnectionOptions = {}): DbPoolHandle {
9
9
  const pgOptions: Parameters<typeof postgres>[1] = {};
10
10
  if (options.maxConnections !== undefined) pgOptions.max = options.maxConnections;
11
11
  if (options.idleTimeoutSeconds !== undefined) pgOptions.idle_timeout = options.idleTimeoutSeconds;
@@ -0,0 +1,80 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { type BunTestDb, createTestDb } from "../../../bun-db/__tests__/bun-test-db";
3
+ import { createEventsTable } from "../../../event-store/events-schema";
4
+ import { asRawClient } from "../../query";
5
+ import { ensureIdempotencyKeyIndex } from "../event-store";
6
+
7
+ let testDb: BunTestDb;
8
+
9
+ beforeAll(async () => {
10
+ testDb = await createTestDb();
11
+ await createEventsTable(testDb.db);
12
+ });
13
+
14
+ afterAll(async () => {
15
+ await testDb.cleanup();
16
+ });
17
+
18
+ async function indexValidity(): Promise<boolean | undefined> {
19
+ const rows = (await asRawClient(testDb.db).unsafe(
20
+ `SELECT i.indisvalid FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid ` +
21
+ `WHERE c.relname = 'events_idempotency_uq'`,
22
+ )) as ReadonlyArray<{ indisvalid: boolean }>;
23
+ return rows[0]?.indisvalid;
24
+ }
25
+
26
+ describe("ensureIdempotencyKeyIndex (#1499)", () => {
27
+ test("repeated calls stay idempotent — index ends up valid, no throw", async () => {
28
+ await ensureIdempotencyKeyIndex(testDb.db);
29
+ await ensureIdempotencyKeyIndex(testDb.db);
30
+ expect(await indexValidity()).toBe(true);
31
+ });
32
+
33
+ // Regression for the rolling-deploy race: two pods booting concurrently
34
+ // against the same DB both try to build the same CONCURRENTLY index.
35
+ // A plain CREATE INDEX IF NOT EXISTS no-ops the loser; a CONCURRENTLY
36
+ // build can instead raise a duplicate-relation error mid-build — that
37
+ // must be swallowed the same way createEventsTable already tolerates a
38
+ // racing CREATE TABLE, not crash-loop the booting pod.
39
+ test("concurrent calls from two racing boots don't throw", async () => {
40
+ // The prior test already built the index — without dropping it here,
41
+ // both calls below would just hit the IF NOT EXISTS no-op path (indeed
42
+ // what the original single-connection version of this test silently
43
+ // did) and never actually contend on a real CONCURRENTLY build.
44
+ await asRawClient(testDb.db).unsafe(
45
+ `DROP INDEX CONCURRENTLY IF EXISTS "events_idempotency_uq"`,
46
+ );
47
+
48
+ // Two independent connections against the SAME database, mirroring two
49
+ // pods — a single shared `testDb.db` pool would serialize both calls
50
+ // and never pin the race (kumiko-framework#1522).
51
+ const secondPodDb = await createTestDb({ dbName: testDb.dbName, persistent: true });
52
+ try {
53
+ await Promise.all([
54
+ ensureIdempotencyKeyIndex(testDb.db),
55
+ ensureIdempotencyKeyIndex(secondPodDb.db),
56
+ ]);
57
+ expect(await indexValidity()).toBe(true);
58
+ } finally {
59
+ // persistent: true → cleanup() only closes this pool, doesn't drop the
60
+ // shared DB (testDb's afterAll still owns that).
61
+ await secondPodDb.cleanup();
62
+ }
63
+ });
64
+
65
+ test("an INVALID leftover from a killed CONCURRENTLY build gets dropped and rebuilt", async () => {
66
+ // Simulates a build interrupted mid-flight (crash, deploy restart): the
67
+ // catalog entry exists but never finished — pg_index.indisvalid=false.
68
+ // Postgres has no direct DDL to force this state, so flip it straight
69
+ // in the catalog, matching what a real killed CONCURRENTLY build leaves
70
+ // behind.
71
+ await asRawClient(testDb.db).unsafe(
72
+ `UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'events_idempotency_uq'::regclass`,
73
+ );
74
+ expect(await indexValidity()).toBe(false);
75
+
76
+ await ensureIdempotencyKeyIndex(testDb.db);
77
+
78
+ expect(await indexValidity()).toBe(true);
79
+ });
80
+ });