@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
@@ -341,14 +341,20 @@ export type ScreenPattern = {
341
341
  export type WriteHandlerPattern = {
342
342
  readonly kind: "writeHandler";
343
343
  readonly source: SourceLocation;
344
- readonly handlerName: string;
344
+ // handlerName/schemaSource/handlerBody are set together, or all left
345
+ // undefined for a single reference standing in for the whole handler
346
+ // (`r.writeHandler(handlerRef)`) that couldn't be resolved to an object
347
+ // literal — see #1007. An opaque pattern re-emits `source.raw` verbatim
348
+ // and, like UnknownPattern, has no PatternId variant (not individually
349
+ // patchable).
350
+ readonly handlerName?: string;
345
351
  // schemaSource: the Zod call as source text (e.g. "z.object({...})").
346
352
  // We keep it as an opaque block instead of decoding it back to JSON
347
353
  // schema — Zod's full surface is too rich for a faithful round-trip.
348
- readonly schemaSource: SourceLocation;
354
+ readonly schemaSource?: SourceLocation;
349
355
  // handlerBody: the closure body as source text. Always opaque — AI
350
356
  // generates raw TypeScript, no DSL interpretation.
351
- readonly handlerBody: SourceLocation;
357
+ readonly handlerBody?: SourceLocation;
352
358
  readonly access?: AccessRule;
353
359
  readonly rateLimit?: RateLimitOption;
354
360
  readonly unsafeSkipTransitionGuard?: boolean;
@@ -356,43 +362,48 @@ export type WriteHandlerPattern = {
356
362
 
357
363
  // `r.queryHandler(...)` — registers a read handler: name, Zod input schema,
358
364
  // handler closure, plus optional `access` and `rateLimit` rules. Read-side
359
- // counterpart of `r.writeHandler` with the same header/body split.
365
+ // counterpart of `r.writeHandler` with the same header/body split. Opaque
366
+ // (no handlerName) for the same single-reference case as WriteHandlerPattern.
360
367
  export type QueryHandlerPattern = {
361
368
  readonly kind: "queryHandler";
362
369
  readonly source: SourceLocation;
363
- readonly handlerName: string;
364
- readonly schemaSource: SourceLocation;
365
- readonly handlerBody: SourceLocation;
370
+ readonly handlerName?: string;
371
+ readonly schemaSource?: SourceLocation;
372
+ readonly handlerBody?: SourceLocation;
373
+ readonly access?: AccessRule;
374
+ readonly rateLimit?: RateLimitOption;
375
+ };
376
+
377
+ // `r.streamHandler(...)` — registers a streaming read handler: name, Zod
378
+ // input schema, async-generator handler closure, plus optional `access` and
379
+ // `rateLimit` rules. Same header/body split and opaque single-reference
380
+ // case as QueryHandlerPattern.
381
+ export type StreamHandlerPattern = {
382
+ readonly kind: "streamHandler";
383
+ readonly source: SourceLocation;
384
+ readonly handlerName?: string;
385
+ readonly schemaSource?: SourceLocation;
386
+ readonly handlerBody?: SourceLocation;
366
387
  readonly access?: AccessRule;
367
388
  readonly rateLimit?: RateLimitOption;
368
389
  };
369
390
 
370
391
  // `r.hook(type, target, fn, options?)` — attaches a lifecycle hook
371
392
  // (`validation`, `preSave`, `postSave`, `preDelete`, `postDelete`,
372
- // `preQuery`, `postQuery`) to one or more target handlers. Post-hooks
373
- // accept a `phase` option; `preDelete` always runs in-transaction — it
374
- // guards the delete. The hook body is an opaque code span.
393
+ // `preQuery`, `postQuery`) to one or more target handlers, or — for
394
+ // postSave/preDelete/postDelete/postQuery to `{ allOf: entityRef }`
395
+ // ("every write/query handler of this entity", replacing the old
396
+ // `r.entityHook(type, entity, fn)`). Post-hooks accept a `phase` option;
397
+ // `preDelete` always runs in-transaction — it guards the delete. The hook
398
+ // body is an opaque code span.
375
399
  export type HookPattern = {
376
400
  readonly kind: "hook";
377
401
  readonly source: SourceLocation;
378
402
  readonly hookType: LifecycleHookType | "validation";
379
- // r.hook accepts a single target or a list; we keep both shapes so
380
- // the Designer can render the original author intent.
381
- readonly target: string | readonly string[];
382
- readonly fnBody: SourceLocation;
383
- readonly phase?: HookPhase;
384
- };
385
-
386
- // `r.entityHook(type, entity, fn, options?)` — like `r.hook`, but bound to
387
- // an entity instead of individual handlers: `postSave`, `preDelete`, and
388
- // `postDelete` fire on every matching write. The runtime API additionally
389
- // accepts `postQuery` (fires for all query-handlers of the entity), but
390
- // this pattern type only represents the three write-side hooks.
391
- export type EntityHookPattern = {
392
- readonly kind: "entityHook";
393
- readonly source: SourceLocation;
394
- readonly hookType: "postSave" | "preDelete" | "postDelete";
395
- readonly entityName: string;
403
+ // r.hook accepts a single target, a list, or an entity-wide { allOf }
404
+ // target; we keep all three shapes so the Designer can render the
405
+ // original author intent.
406
+ readonly target: string | readonly string[] | { readonly allOf: string };
396
407
  readonly fnBody: SourceLocation;
397
408
  readonly phase?: HookPhase;
398
409
  };
@@ -445,18 +456,6 @@ export type AuthClaimsPattern = {
445
456
  readonly fnBody: SourceLocation;
446
457
  };
447
458
 
448
- // `r.tree(provider)` — the feature's top-level tree provider: a subscribe
449
- // function (emit-fn in, unsubscribe-fn out) that feeds workspaces with
450
- // `navigation: "tree"`. Features without `r.tree()` are invisible there —
451
- // provider presence IS the filter, there is no workspace mapping.
452
- // Closure-only, no header form: the Designer renders a read-only code
453
- // block, the AI patcher overwrites the span verbatim.
454
- export type TreePattern = {
455
- readonly kind: "tree";
456
- readonly source: SourceLocation;
457
- readonly providerBody: SourceLocation;
458
- };
459
-
460
459
  // `r.httpRoute(definition)` — mounts an HTTP route owned by the feature,
461
460
  // outside the dispatcher pipeline (not under `/api/write|query|batch`) —
462
461
  // for RSS/Atom feeds, OG images, OpenAPI specs and similar. Duplicate
@@ -506,29 +505,22 @@ export type MultiStreamProjectionPattern = {
506
505
  // `r.defineEvent(name, schema, options?)` — registers an event payload
507
506
  // shape and returns the qualified `EventDef`, so callers pass `.name` to
508
507
  // `ctx.appendEvent` instead of hand-building `<feature>:event:<short>`.
509
- // `options.version` declares the CURRENT schema generation (default 1);
510
- // bump it together with an `r.eventMigration` step the framework refuses
511
- // to boot if the chain from 1 to the current version has gaps.
508
+ // `options.version` declares the CURRENT schema generation (default 1).
509
+ // `options.migrations` is a step-wise upcast chainkeyed by fromVersion
510
+ // (toVersion is always fromVersion + 1, enforced at registration, so it is
511
+ // not stored separately); the framework refuses to boot if the chain from
512
+ // 1 to `version` has gaps. Formerly a separate `r.eventMigration()` call
513
+ // per step, folded in here (#1082 step 8) — an event and its schema
514
+ // evolution are one lifecycle, not two registrar concepts.
512
515
  export type DefineEventPattern = {
513
516
  readonly kind: "defineEvent";
514
517
  readonly source: SourceLocation;
515
518
  readonly eventName: string;
516
519
  readonly schemaSource: SourceLocation;
517
520
  readonly version?: number;
518
- };
519
-
520
- // `r.eventMigration(eventName, fromVersion, toVersion, transform)` —
521
- // registers a step-wise payload upcast for event-schema evolution.
522
- // `toVersion` must be `fromVersion + 1`; chain larger jumps step by step.
523
- // Transforms are pure old-payload-in/new-payload-out functions and run once
524
- // per READ, not once per event persisted — keep them cheap.
525
- export type EventMigrationPattern = {
526
- readonly kind: "eventMigration";
527
- readonly source: SourceLocation;
528
- readonly eventName: string;
529
- readonly fromVersion: number;
530
- readonly toVersion: number;
531
- readonly transformBody: SourceLocation;
521
+ // Map fromVersion (as string, e.g. "1") → SourceLocation of the transform
522
+ // closure for the fromVersion → fromVersion+1 step.
523
+ readonly migrations?: Readonly<Record<string, SourceLocation>>;
532
524
  };
533
525
 
534
526
  // `r.extendsRegistrar(extensionName, def)` — declares a named, globally
@@ -614,8 +606,8 @@ export type FeaturePattern =
614
606
  | ScreenPattern
615
607
  | WriteHandlerPattern
616
608
  | QueryHandlerPattern
609
+ | StreamHandlerPattern
617
610
  | HookPattern
618
- | EntityHookPattern
619
611
  | JobPattern
620
612
  | NotificationPattern
621
613
  | AuthClaimsPattern
@@ -623,9 +615,7 @@ export type FeaturePattern =
623
615
  | ProjectionPattern
624
616
  | MultiStreamProjectionPattern
625
617
  | DefineEventPattern
626
- | EventMigrationPattern
627
618
  | ExtendsRegistrarPattern
628
- | TreePattern
629
619
  | EnvSchemaPattern
630
620
  // Catch-all
631
621
  | UnknownPattern;
@@ -672,19 +662,17 @@ export function getEditability(pattern: FeaturePattern): Editability {
672
662
  case "screen":
673
663
  case "writeHandler":
674
664
  case "queryHandler":
665
+ case "streamHandler":
675
666
  case "hook":
676
- case "entityHook":
677
667
  case "job":
678
668
  case "notification":
679
669
  case "httpRoute":
680
670
  case "projection":
681
671
  case "multiStreamProjection":
682
672
  case "defineEvent":
683
- case "eventMigration":
684
673
  return "mixed";
685
674
  case "authClaims":
686
675
  case "extendsRegistrar":
687
- case "tree":
688
676
  case "envSchema":
689
677
  case "uiHints":
690
678
  case "unknown":
@@ -16,16 +16,15 @@
16
16
  // `// kumiko-feature-version: 1`. Future format bumps run a dedicated
17
17
  // migrator over the version comment.
18
18
 
19
+ import { isRawRefSentinel } from "./extractors/shared";
19
20
  import type {
20
21
  AuthClaimsPattern,
21
22
  ClaimKeyPattern,
22
23
  ConfigPattern,
23
24
  DefineEventPattern,
24
25
  DescribePattern,
25
- EntityHookPattern,
26
26
  EntityPattern,
27
27
  EnvSchemaPattern,
28
- EventMigrationPattern,
29
28
  ExposesApiPattern,
30
29
  ExtendsRegistrarPattern,
31
30
  FeaturePattern,
@@ -45,11 +44,11 @@ import type {
45
44
  RequiresPattern,
46
45
  ScreenPattern,
47
46
  SecretPattern,
47
+ StreamHandlerPattern,
48
48
  SystemScopePattern,
49
49
  ToggleablePattern,
50
50
  TranslationsPattern,
51
51
  TreeActionsPattern,
52
- TreePattern,
53
52
  UiHintsPattern,
54
53
  UnknownPattern,
55
54
  UseExtensionPattern,
@@ -111,10 +110,10 @@ export function renderPattern(pattern: FeaturePattern): string {
111
110
  return renderWriteHandler(pattern);
112
111
  case "queryHandler":
113
112
  return renderQueryHandler(pattern);
113
+ case "streamHandler":
114
+ return renderStreamHandler(pattern);
114
115
  case "hook":
115
116
  return renderHook(pattern);
116
- case "entityHook":
117
- return renderEntityHook(pattern);
118
117
  case "job":
119
118
  return renderJob(pattern);
120
119
  case "notification":
@@ -129,8 +128,6 @@ export function renderPattern(pattern: FeaturePattern): string {
129
128
  return renderMultiStreamProjection(pattern);
130
129
  case "defineEvent":
131
130
  return renderDefineEvent(pattern);
132
- case "eventMigration":
133
- return renderEventMigration(pattern);
134
131
  case "extendsRegistrar":
135
132
  return renderExtendsRegistrar(pattern);
136
133
  case "usesApi":
@@ -139,8 +136,6 @@ export function renderPattern(pattern: FeaturePattern): string {
139
136
  return renderExposesApi(pattern);
140
137
  case "treeActions":
141
138
  return renderTreeActions(pattern);
142
- case "tree":
143
- return renderTree(pattern);
144
139
  case "envSchema":
145
140
  return renderEnvSchema(pattern);
146
141
  case "unknown":
@@ -177,6 +172,7 @@ const SINGLE_LINE_WIDTH = 80;
177
172
  * multi-line otherwise — biome-stable in both branches.
178
173
  */
179
174
  export function renderValue(value: unknown, indent = 0): string {
175
+ if (isRawRefSentinel(value)) return value.__raw;
180
176
  if (value === null) return "null";
181
177
  if (typeof value === "string") return JSON.stringify(value);
182
178
  if (typeof value === "number" || typeof value === "boolean") return String(value);
@@ -243,6 +239,13 @@ function renderDescribe(p: DescribePattern): string {
243
239
  }
244
240
 
245
241
  function renderEntity(p: EntityPattern): string {
242
+ // A whole-value raw-ref (`r.entity("x", eventEntity)`) can't be spread
243
+ // into the merged Object-Form without losing `name` to the sentinel
244
+ // shortcut in renderValue — fall back to the classic positional form,
245
+ // which keeps the reference verbatim alongside the name.
246
+ if (isRawRefSentinel(p.definition)) {
247
+ return `r.entity(${JSON.stringify(p.entityName)}, ${p.definition.__raw});`;
248
+ }
246
249
  // Inline `name` into the definition object — canonical Object-Form
247
250
  // is a single arg with name-as-property.
248
251
  const merged = { name: p.entityName, ...p.definition };
@@ -250,6 +253,9 @@ function renderEntity(p: EntityPattern): string {
250
253
  }
251
254
 
252
255
  function renderRelation(p: RelationPattern): string {
256
+ if (isRawRefSentinel(p.definition)) {
257
+ return `r.relation(${JSON.stringify(p.entityName)}, ${JSON.stringify(p.relationName)}, ${p.definition.__raw});`;
258
+ }
253
259
  const merged = { entity: p.entityName, name: p.relationName, ...p.definition };
254
260
  return `r.relation(${renderValue(merged)});`;
255
261
  }
@@ -271,11 +277,17 @@ function renderTranslations(p: TranslationsPattern): string {
271
277
  }
272
278
 
273
279
  function renderMetric(p: MetricPattern): string {
280
+ if (isRawRefSentinel(p.options)) {
281
+ return `r.metric(${JSON.stringify(p.shortName)}, ${p.options.__raw});`;
282
+ }
274
283
  const merged = { name: p.shortName, ...p.options };
275
284
  return `r.metric(${renderValue(merged)});`;
276
285
  }
277
286
 
278
287
  function renderSecret(p: SecretPattern): string {
288
+ if (isRawRefSentinel(p.options)) {
289
+ return `r.secret(${JSON.stringify(p.shortName)}, ${p.options.__raw});`;
290
+ }
279
291
  const merged = { name: p.shortName, ...p.options };
280
292
  return `r.secret(${renderValue(merged)});`;
281
293
  }
@@ -294,6 +306,9 @@ function renderReferenceData(p: ReferenceDataPattern): string {
294
306
  }
295
307
 
296
308
  function renderUseExtension(p: UseExtensionPattern): string {
309
+ if (isRawRefSentinel(p.options)) {
310
+ return `r.useExtension(${JSON.stringify(p.extensionName)}, ${JSON.stringify(p.entityName)}, ${p.options.__raw});`;
311
+ }
297
312
  const merged: Record<string, unknown> = {
298
313
  name: p.extensionName,
299
314
  entity: p.entityName,
@@ -359,10 +374,11 @@ function reindentBody(raw: string, newIndent: string): string {
359
374
  }
360
375
 
361
376
  function renderWriteHandler(p: WriteHandlerPattern): string {
377
+ if (p.handlerName === undefined) return p.source.raw;
362
378
  const lines: string[] = ["r.writeHandler({"];
363
379
  lines.push(` name: ${JSON.stringify(p.handlerName)},`);
364
- lines.push(` schema: ${reindentBody(p.schemaSource.raw, PATTERN_INDENT)},`);
365
- lines.push(` handler: ${reindentBody(p.handlerBody.raw, PATTERN_INDENT)},`);
380
+ lines.push(` schema: ${reindentBody(p.schemaSource?.raw ?? "", PATTERN_INDENT)},`);
381
+ lines.push(` handler: ${reindentBody(p.handlerBody?.raw ?? "", PATTERN_INDENT)},`);
366
382
  if (p.access !== undefined) lines.push(` access: ${renderValue(p.access)},`);
367
383
  if (p.rateLimit !== undefined) lines.push(` rateLimit: ${renderValue(p.rateLimit)},`);
368
384
  if (p.unsafeSkipTransitionGuard === true) lines.push(" unsafeSkipTransitionGuard: true,");
@@ -371,30 +387,39 @@ function renderWriteHandler(p: WriteHandlerPattern): string {
371
387
  }
372
388
 
373
389
  function renderQueryHandler(p: QueryHandlerPattern): string {
390
+ if (p.handlerName === undefined) return p.source.raw;
374
391
  const lines: string[] = ["r.queryHandler({"];
375
392
  lines.push(` name: ${JSON.stringify(p.handlerName)},`);
376
- lines.push(` schema: ${reindentBody(p.schemaSource.raw, PATTERN_INDENT)},`);
377
- lines.push(` handler: ${reindentBody(p.handlerBody.raw, PATTERN_INDENT)},`);
393
+ lines.push(` schema: ${reindentBody(p.schemaSource?.raw ?? "", PATTERN_INDENT)},`);
394
+ lines.push(` handler: ${reindentBody(p.handlerBody?.raw ?? "", PATTERN_INDENT)},`);
378
395
  if (p.access !== undefined) lines.push(` access: ${renderValue(p.access)},`);
379
396
  if (p.rateLimit !== undefined) lines.push(` rateLimit: ${renderValue(p.rateLimit)},`);
380
397
  lines.push("});");
381
398
  return lines.join("\n");
382
399
  }
383
400
 
384
- function renderHook(p: HookPattern): string {
385
- const lines: string[] = ["r.hook({"];
386
- lines.push(` type: ${JSON.stringify(p.hookType)},`);
387
- lines.push(` target: ${renderValue(typeof p.target === "string" ? p.target : [...p.target])},`);
388
- lines.push(` handler: ${reindentBody(p.fnBody.raw, PATTERN_INDENT)},`);
389
- if (p.phase !== undefined) lines.push(` phase: ${JSON.stringify(p.phase)},`);
401
+ function renderStreamHandler(p: StreamHandlerPattern): string {
402
+ if (p.handlerName === undefined) return p.source.raw;
403
+ const lines: string[] = ["r.streamHandler({"];
404
+ lines.push(` name: ${JSON.stringify(p.handlerName)},`);
405
+ lines.push(` schema: ${reindentBody(p.schemaSource?.raw ?? "", PATTERN_INDENT)},`);
406
+ lines.push(` handler: ${reindentBody(p.handlerBody?.raw ?? "", PATTERN_INDENT)},`);
407
+ if (p.access !== undefined) lines.push(` access: ${renderValue(p.access)},`);
408
+ if (p.rateLimit !== undefined) lines.push(` rateLimit: ${renderValue(p.rateLimit)},`);
390
409
  lines.push("});");
391
410
  return lines.join("\n");
392
411
  }
393
412
 
394
- function renderEntityHook(p: EntityHookPattern): string {
395
- const lines: string[] = ["r.entityHook({"];
413
+ function renderHookTarget(target: HookPattern["target"]): string {
414
+ if (typeof target === "string") return renderValue(target);
415
+ if ("allOf" in target) return `{ allOf: ${JSON.stringify(target.allOf)} }`;
416
+ return renderValue([...target]);
417
+ }
418
+
419
+ function renderHook(p: HookPattern): string {
420
+ const lines: string[] = ["r.hook({"];
396
421
  lines.push(` type: ${JSON.stringify(p.hookType)},`);
397
- lines.push(` entity: ${JSON.stringify(p.entityName)},`);
422
+ lines.push(` target: ${renderHookTarget(p.target)},`);
398
423
  lines.push(` handler: ${reindentBody(p.fnBody.raw, PATTERN_INDENT)},`);
399
424
  if (p.phase !== undefined) lines.push(` phase: ${JSON.stringify(p.phase)},`);
400
425
  lines.push("});");
@@ -433,16 +458,11 @@ function renderAuthClaims(p: AuthClaimsPattern): string {
433
458
  return `r.authClaims(${p.fnBody.raw});`;
434
459
  }
435
460
 
436
- // Visual-Tree patterns. treeActions is a static object-literal (mirrors
437
- // renderWorkspace), tree is opaque-only (mirrors renderAuthClaims).
461
+ // treeActions is a static object-literal (mirrors renderWorkspace).
438
462
  function renderTreeActions(p: TreeActionsPattern): string {
439
463
  return `r.treeActions(${renderValue(p.definitions)});`;
440
464
  }
441
465
 
442
- function renderTree(p: TreePattern): string {
443
- return `r.tree(${p.providerBody.raw});`;
444
- }
445
-
446
466
  function renderHttpRoute(p: HttpRoutePattern): string {
447
467
  const lines: string[] = ["r.httpRoute({"];
448
468
  lines.push(` method: ${JSON.stringify(p.method)},`);
@@ -486,20 +506,25 @@ function renderMultiStreamProjection(p: MultiStreamProjectionPattern): string {
486
506
  }
487
507
 
488
508
  function renderDefineEvent(p: DefineEventPattern): string {
489
- const lines: string[] = ["r.defineEvent({"];
490
- lines.push(` name: ${JSON.stringify(p.eventName)},`);
491
- lines.push(` schema: ${p.schemaSource.raw},`);
509
+ const migrationEntries = p.migrations !== undefined ? Object.entries(p.migrations) : [];
510
+ const hasOptions = p.version !== undefined || migrationEntries.length > 0;
511
+ if (!hasOptions) {
512
+ return `r.defineEvent(${JSON.stringify(p.eventName)}, ${p.schemaSource.raw});`;
513
+ }
514
+ const lines: string[] = [
515
+ `r.defineEvent(${JSON.stringify(p.eventName)}, ${p.schemaSource.raw}, {`,
516
+ ];
492
517
  if (p.version !== undefined) lines.push(` version: ${p.version},`);
493
- lines.push("});");
494
- return lines.join("\n");
495
- }
496
-
497
- function renderEventMigration(p: EventMigrationPattern): string {
498
- const lines: string[] = ["r.eventMigration({"];
499
- lines.push(` event: ${JSON.stringify(p.eventName)},`);
500
- lines.push(` fromVersion: ${p.fromVersion},`);
501
- lines.push(` toVersion: ${p.toVersion},`);
502
- lines.push(` transform: ${p.transformBody.raw},`);
518
+ if (migrationEntries.length > 0) {
519
+ lines.push(" migrations: [");
520
+ for (const [fromVersion, transformBody] of migrationEntries) {
521
+ const from = Number(fromVersion);
522
+ lines.push(
523
+ ` { fromVersion: ${from}, toVersion: ${from + 1}, transform: ${transformBody.raw} },`,
524
+ );
525
+ }
526
+ lines.push(" ],");
527
+ }
503
528
  lines.push("});");
504
529
  return lines.join("\n");
505
530
  }
@@ -571,9 +596,7 @@ function weaveOpaque(
571
596
  }
572
597
 
573
598
  function renderValueWithRawSlots(value: WovenValue, indent: number): string {
574
- if (value !== null && typeof value === "object" && !Array.isArray(value) && "__raw" in value) {
575
- return (value as { __raw: string }).__raw;
576
- }
599
+ if (isRawRefSentinel(value)) return value.__raw;
577
600
  if (Array.isArray(value)) {
578
601
  if (value.length === 0) return "[]";
579
602
  const inner = value
@@ -0,0 +1,168 @@
1
+ import type { ZodType, z } from "zod";
2
+ import { LifecycleHookTypes } from "./constants";
3
+ import type {
4
+ AuthClaimsFn,
5
+ BootCheckFn,
6
+ ClaimKeyDefinition,
7
+ ConfigKeyDefinition,
8
+ ConfigSeedDef,
9
+ EntityDefinition,
10
+ EntityProjectionExtension,
11
+ EventMigrationDef,
12
+ ExtensionSelectorDef,
13
+ FeatureMetricDef,
14
+ JobDefinition,
15
+ LifecycleHookFn,
16
+ MultiStreamProjectionDefinition,
17
+ NotificationDefinition,
18
+ OwnedFn,
19
+ PhasedHook,
20
+ PostDeleteHookFn,
21
+ PostQueryHookFn,
22
+ PostSaveHookFn,
23
+ PreDeleteHookFn,
24
+ ProjectionDefinition,
25
+ QueryHandlerDef,
26
+ ReferenceDataDef,
27
+ RegistrarExtensionDef,
28
+ RegistrarExtensionRegistration,
29
+ RelationDefinition,
30
+ SearchPayloadContributorFn,
31
+ SecretKeyDefinition,
32
+ StoreTableEntry,
33
+ StreamHandlerDef,
34
+ TranslationKeys,
35
+ TreeActionDef,
36
+ UiHints,
37
+ ValidationHookFn,
38
+ WriteHandlerDef,
39
+ } from "./types";
40
+ import type { HttpRouteDefinition } from "./types/http-route";
41
+ import type { NavDefinition } from "./types/nav";
42
+ import type { ScreenDefinition } from "./types/screen";
43
+ import type { WorkspaceDefinition } from "./types/workspace";
44
+
45
+ const LIFECYCLE_TYPES = Object.values(LifecycleHookTypes);
46
+
47
+ // Bundles every Record/Set/array/scalar defineFeature populates while the
48
+ // registrar's ~40 methods run — hoisted out of defineFeature's closure so a
49
+ // future move-diff (extracting each method into its own module) can thread
50
+ // it explicitly. Every field is held BY REFERENCE — no destructured copies.
51
+ export type FeatureBuilderState = {
52
+ requires: string[];
53
+ optionalRequires: string[];
54
+ requiredProjections: Set<string>;
55
+ requiredSteps: Set<string>;
56
+ entities: Record<string, EntityDefinition>;
57
+ entityTables: Record<string, unknown>;
58
+ relations: Record<string, Record<string, RelationDefinition>>;
59
+ writeHandlers: Record<string, WriteHandlerDef>;
60
+ queryHandlers: Record<string, QueryHandlerDef>;
61
+ streamHandlers: Record<string, StreamHandlerDef>;
62
+ validationHooks: Record<string, ValidationHookFn>;
63
+ lifecycleHooks: Record<string, Record<string, OwnedFn<LifecycleHookFn>[]>>;
64
+ phasedLifecycleHooks: Record<
65
+ "postSave" | "preDelete" | "postDelete",
66
+ Record<string, PhasedHook<LifecycleHookFn>[]>
67
+ >;
68
+ configKeys: Record<string, ConfigKeyDefinition>;
69
+ configSeeds: ConfigSeedDef[];
70
+ jobs: Record<string, JobDefinition>;
71
+ events: Record<string, { name: string; schema: ZodType; version: number }>;
72
+ eventMigrations: Record<string, EventMigrationDef[]>;
73
+ configReads: string[];
74
+ entityPostSave: Record<string, PhasedHook<PostSaveHookFn>[]>;
75
+ entityPreDelete: Record<string, PhasedHook<PreDeleteHookFn>[]>;
76
+ entityPostDelete: Record<string, PhasedHook<PostDeleteHookFn>[]>;
77
+ entityPostQuery: Record<string, OwnedFn<PostQueryHookFn>[]>;
78
+ searchPayloadExtensions: Record<string, OwnedFn<SearchPayloadContributorFn>[]>;
79
+ notifications: Record<string, NotificationDefinition>;
80
+ registrarExtensions: Record<string, RegistrarExtensionDef>;
81
+ extensionUsages: RegistrarExtensionRegistration[];
82
+ extensionSelectors: ExtensionSelectorDef[];
83
+ exposedApis: Set<string>;
84
+ usedApis: Set<string>;
85
+ bootChecks: BootCheckFn[];
86
+ referenceData: ReferenceDataDef[];
87
+ handlerEntityMappings: Record<string, string>;
88
+ metrics: Record<string, FeatureMetricDef>;
89
+ secretKeys: Record<string, SecretKeyDefinition>;
90
+ projections: Record<string, ProjectionDefinition>;
91
+ multiStreamProjections: Record<string, MultiStreamProjectionDefinition>;
92
+ entityProjectionExtensions: Record<string, EntityProjectionExtension[]>;
93
+ storeTables: Record<string, StoreTableEntry>;
94
+ authClaimsHooks: AuthClaimsFn[];
95
+ claimKeys: Record<string, ClaimKeyDefinition>;
96
+ screens: Record<string, ScreenDefinition>;
97
+ navs: Record<string, NavDefinition>;
98
+ workspaces: Record<string, WorkspaceDefinition>;
99
+ httpRoutes: Record<string, HttpRouteDefinition>;
100
+ translations: TranslationKeys;
101
+ isSystemScoped: boolean;
102
+ toggleableDefault: boolean | undefined;
103
+ description: string | undefined;
104
+ uiHints: UiHints | undefined;
105
+ treeActions: Readonly<Record<string, TreeActionDef>> | undefined;
106
+ envSchema: z.ZodObject<z.ZodRawShape> | undefined;
107
+ };
108
+
109
+ export function createInitialFeatureBuilderState(): FeatureBuilderState {
110
+ const lifecycleHooks: Record<string, Record<string, OwnedFn<LifecycleHookFn>[]>> = {};
111
+ for (const t of LIFECYCLE_TYPES) {
112
+ lifecycleHooks[t] = {};
113
+ }
114
+ return {
115
+ requires: [],
116
+ optionalRequires: [],
117
+ requiredProjections: new Set<string>(),
118
+ requiredSteps: new Set<string>(),
119
+ entities: {},
120
+ entityTables: {},
121
+ relations: {},
122
+ writeHandlers: {},
123
+ queryHandlers: {},
124
+ streamHandlers: {},
125
+ validationHooks: {},
126
+ lifecycleHooks,
127
+ phasedLifecycleHooks: { postSave: {}, preDelete: {}, postDelete: {} },
128
+ configKeys: {},
129
+ configSeeds: [],
130
+ jobs: {},
131
+ events: {},
132
+ eventMigrations: {},
133
+ configReads: [],
134
+ entityPostSave: {},
135
+ entityPreDelete: {},
136
+ entityPostDelete: {},
137
+ entityPostQuery: {},
138
+ searchPayloadExtensions: {},
139
+ notifications: {},
140
+ registrarExtensions: {},
141
+ extensionUsages: [],
142
+ extensionSelectors: [],
143
+ exposedApis: new Set(),
144
+ usedApis: new Set(),
145
+ bootChecks: [],
146
+ referenceData: [],
147
+ handlerEntityMappings: {},
148
+ metrics: {},
149
+ secretKeys: {},
150
+ projections: {},
151
+ multiStreamProjections: {},
152
+ entityProjectionExtensions: {},
153
+ storeTables: {},
154
+ authClaimsHooks: [],
155
+ claimKeys: {},
156
+ screens: {},
157
+ navs: {},
158
+ workspaces: {},
159
+ httpRoutes: {},
160
+ translations: {},
161
+ isSystemScoped: false,
162
+ toggleableDefault: undefined,
163
+ description: undefined,
164
+ uiHints: undefined,
165
+ treeActions: undefined,
166
+ envSchema: undefined,
167
+ };
168
+ }