@cosmicdrift/kumiko-framework 0.386.0 → 0.388.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 (468) hide show
  1. package/AGENTS.md +6 -1
  2. package/LICENSE +21 -57
  3. package/README.md +2 -2
  4. package/agents/examples/anonymous-access.md +1 -1
  5. package/agents/examples/apex-landing.md +204 -0
  6. package/agents/examples/api-worker-split.md +64 -0
  7. package/agents/examples/app-config.md +14 -0
  8. package/agents/examples/auth-mfa.md +115 -0
  9. package/agents/examples/env-composition.md +28 -1
  10. package/agents/examples/legal-pages.md +1 -1
  11. package/agents/examples/prod-boot-options.md +16 -0
  12. package/agents/examples/rate-limiting.md +48 -0
  13. package/agents/examples/record-detail-layout.md +692 -0
  14. package/agents/examples/seed-migration.md +212 -0
  15. package/agents/examples/wizard-form.md +941 -0
  16. package/agents/examples/writeform-section.md +548 -0
  17. package/agents/features.md +19 -2
  18. package/agents/widgets.md +40 -39
  19. package/dist/api/api-constants.js +2 -2
  20. package/dist/api/auth-middleware.js +7 -7
  21. package/dist/api/auth-rate-limiters.js +7 -3
  22. package/dist/api/auth-routes-mfa.js +1 -1
  23. package/dist/api/auth-routes-session.js +1 -1
  24. package/dist/api/auth-routes-shared.js +7 -3
  25. package/dist/api/auth-routes-signup.js +1 -1
  26. package/dist/api/client-error-route.js +8 -2
  27. package/dist/api/client-ip.js +3 -1
  28. package/dist/api/extra-route.js +1 -1
  29. package/dist/api/index.d.ts +3 -1
  30. package/dist/api/index.js +3 -1
  31. package/dist/api/pii-leak-guard.js +4 -2
  32. package/dist/api/readiness.js +5 -5
  33. package/dist/api/redis-sse-broker.js +8 -6
  34. package/dist/api/request-context.d.ts +1 -1
  35. package/dist/api/request-context.js +1 -1
  36. package/dist/api/routes.js +2 -2
  37. package/dist/api/server-boot-checks.d.ts +10 -0
  38. package/dist/api/server-boot-checks.js +157 -0
  39. package/dist/api/server-event-dispatcher.d.ts +26 -0
  40. package/dist/api/server-event-dispatcher.js +208 -0
  41. package/dist/api/server-extra-routes.d.ts +11 -0
  42. package/dist/api/server-extra-routes.js +203 -0
  43. package/dist/api/server-middleware.d.ts +20 -0
  44. package/dist/api/server-middleware.js +211 -0
  45. package/dist/api/server-routes.d.ts +19 -0
  46. package/dist/api/server-routes.js +293 -0
  47. package/dist/api/server-websocket-routes.d.ts +4 -0
  48. package/dist/api/server-websocket-routes.js +108 -0
  49. package/dist/api/server-wiring.d.ts +32 -0
  50. package/dist/api/server-wiring.js +236 -0
  51. package/dist/api/server.d.ts +10 -15
  52. package/dist/api/server.js +48 -1250
  53. package/dist/api/session-cookies.js +4 -4
  54. package/dist/api/sse-broker.js +1 -1
  55. package/dist/api/sse-route.d.ts +13 -13
  56. package/dist/api/sse-route.js +31 -27
  57. package/dist/auth/index.js +4 -4
  58. package/dist/auth/roles.d.ts +4 -4
  59. package/dist/auth/roles.js +24 -24
  60. package/dist/compliance/duration-spec.js +18 -20
  61. package/dist/compliance/index.js +2 -3
  62. package/dist/compliance/override-schema.d.ts +4 -4
  63. package/dist/compliance/override-schema.js +19 -22
  64. package/dist/compliance/profiles.d.ts +26 -29
  65. package/dist/compliance/profiles.js +62 -69
  66. package/dist/compliance/sub-processors.d.ts +28 -30
  67. package/dist/compliance/sub-processors.js +17 -21
  68. package/dist/consumer-cli.js +4 -4
  69. package/dist/crypto/blind-index.js +18 -17
  70. package/dist/crypto/ciphertext-pattern.js +1 -1
  71. package/dist/crypto/event-pii.js +3 -3
  72. package/dist/crypto/kek-source.d.ts +1 -1
  73. package/dist/crypto/kek-source.js +3 -2
  74. package/dist/crypto/kms-adapter.d.ts +1 -1
  75. package/dist/crypto/kms-adapter.js +4 -6
  76. package/dist/crypto/kms-wiring.d.ts +1 -2
  77. package/dist/crypto/kms-wiring.js +2 -2
  78. package/dist/crypto/owner-transition-rekey.d.ts +5 -1
  79. package/dist/crypto/owner-transition-rekey.js +1 -1
  80. package/dist/crypto/pg-kms-adapter.js +2 -6
  81. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  82. package/dist/crypto/pii-field-encryption.js +7 -5
  83. package/dist/crypto/subject-resolver.d.ts +1 -1
  84. package/dist/crypto/subject-resolver.js +2 -2
  85. package/dist/db/aggregate-sql.d.ts +21 -0
  86. package/dist/db/aggregate-sql.js +155 -0
  87. package/dist/db/apply-entity-event.d.ts +6 -6
  88. package/dist/db/apply-entity-event.js +55 -55
  89. package/dist/db/blind-index-cleanup.js +7 -7
  90. package/dist/db/closed-connection.d.ts +9 -0
  91. package/dist/db/closed-connection.js +95 -0
  92. package/dist/db/collect-table-metas.js +10 -11
  93. package/dist/db/compound-types.d.ts +1 -1
  94. package/dist/db/compound-types.js +12 -12
  95. package/dist/db/cursor.js +3 -4
  96. package/dist/db/dialect.d.ts +2 -2
  97. package/dist/db/dialect.js +3 -3
  98. package/dist/db/distinct-on.d.ts +1 -1
  99. package/dist/db/driver-row-coercion.d.ts +2 -0
  100. package/dist/db/driver-row-coercion.js +186 -0
  101. package/dist/db/driver-value-preparation.d.ts +16 -0
  102. package/dist/db/driver-value-preparation.js +99 -0
  103. package/dist/db/eagerload.d.ts +3 -3
  104. package/dist/db/eagerload.js +21 -12
  105. package/dist/db/entity-field-encryption.d.ts +1 -0
  106. package/dist/db/entity-field-encryption.js +1 -1
  107. package/dist/db/entity-table-meta.d.ts +2 -2
  108. package/dist/db/entity-table-meta.js +105 -112
  109. package/dist/db/event-store-executor-context.d.ts +3 -2
  110. package/dist/db/event-store-executor-context.js +13 -13
  111. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  112. package/dist/db/event-store-executor-read-aggregate.js +112 -0
  113. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  114. package/dist/db/event-store-executor-read-filter.js +203 -0
  115. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  116. package/dist/db/event-store-executor-read-paging.js +99 -0
  117. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  118. package/dist/db/event-store-executor-read-rows.js +32 -0
  119. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  120. package/dist/db/event-store-executor-read-search.js +221 -0
  121. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  122. package/dist/db/event-store-executor-read-sort.js +130 -0
  123. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  124. package/dist/db/event-store-executor-read-where.js +81 -0
  125. package/dist/db/event-store-executor-read.d.ts +2 -9
  126. package/dist/db/event-store-executor-read.js +50 -704
  127. package/dist/db/event-store-executor-write-steps.d.ts +20 -0
  128. package/dist/db/event-store-executor-write-steps.js +100 -0
  129. package/dist/db/event-store-executor-write.js +401 -510
  130. package/dist/db/index.d.ts +6 -2
  131. package/dist/db/index.js +5 -1
  132. package/dist/db/list-aggregate-labels.d.ts +14 -0
  133. package/dist/db/list-aggregate-labels.js +26 -0
  134. package/dist/db/located-timestamp.d.ts +11 -11
  135. package/dist/db/located-timestamp.js +29 -30
  136. package/dist/db/migrate-generator.d.ts +1 -1
  137. package/dist/db/migrate-generator.js +97 -105
  138. package/dist/db/migrate-runner.js +29 -26
  139. package/dist/db/money.d.ts +6 -6
  140. package/dist/db/money.js +29 -27
  141. package/dist/db/queries/backfill-pii.js +5 -5
  142. package/dist/db/queries/ddl.js +1 -1
  143. package/dist/db/queries/es-ops.d.ts +1 -1
  144. package/dist/db/queries/es-ops.js +1 -1
  145. package/dist/db/queries/event-consumer.js +3 -3
  146. package/dist/db/queries/event-store.js +4 -2
  147. package/dist/db/queries/projection-rebuild.js +3 -3
  148. package/dist/db/queries/raw-sql.d.ts +2 -2
  149. package/dist/db/queries/raw-sql.js +2 -2
  150. package/dist/db/queries/shadow-swap.js +19 -19
  151. package/dist/db/query.d.ts +3 -16
  152. package/dist/db/query.js +8 -761
  153. package/dist/db/rebuild-marker.js +20 -21
  154. package/dist/db/reference-data.js +6 -1
  155. package/dist/db/replay-migration-sql.js +2 -2
  156. package/dist/db/schema-inspection.js +4 -5
  157. package/dist/db/table-builder.d.ts +1 -2
  158. package/dist/db/table-builder.js +137 -190
  159. package/dist/db/table-info.d.ts +10 -0
  160. package/dist/db/table-info.js +45 -0
  161. package/dist/db/temporal-value-guards.d.ts +6 -0
  162. package/dist/db/temporal-value-guards.js +15 -0
  163. package/dist/db/tenant-db-runner.d.ts +1 -0
  164. package/dist/db/tenant-db-runner.js +3 -0
  165. package/dist/db/tenant-db.d.ts +2 -2
  166. package/dist/db/tenant-db.js +48 -11
  167. package/dist/db/where-clause.d.ts +6 -0
  168. package/dist/db/where-clause.js +236 -0
  169. package/dist/derivatives/delete-stored-file.js +6 -2
  170. package/dist/derivatives/derivatives-context.js +1 -1
  171. package/dist/engine/boot-validator/access-roles.js +4 -3
  172. package/dist/engine/boot-validator/api-ext.js +14 -14
  173. package/dist/engine/boot-validator/boot-check.js +1 -1
  174. package/dist/engine/boot-validator/config-deps.js +7 -6
  175. package/dist/engine/boot-validator/detail-screens.js +4 -4
  176. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  177. package/dist/engine/boot-validator/entity-handler.js +77 -61
  178. package/dist/engine/boot-validator/form-derivations.js +35 -1
  179. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  180. package/dist/engine/boot-validator/i18n-keys.js +6 -4
  181. package/dist/engine/boot-validator/index.d.ts +2 -2
  182. package/dist/engine/boot-validator/index.js +22 -20
  183. package/dist/engine/boot-validator/nav.js +5 -5
  184. package/dist/engine/boot-validator/ownership.js +12 -9
  185. package/dist/engine/boot-validator/parent-ref.js +1 -1
  186. package/dist/engine/boot-validator/pii-retention.js +258 -187
  187. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  188. package/dist/engine/boot-validator/query-refs.js +1 -1
  189. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  190. package/dist/engine/boot-validator/screens-actions.js +23 -17
  191. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  192. package/dist/engine/boot-validator/screens-forms.js +12 -17
  193. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  194. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  195. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  196. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  197. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  198. package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
  199. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  200. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  201. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  202. package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
  203. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  204. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  205. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  206. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  207. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  208. package/dist/engine/boot-validator/screens-lists.js +26 -24
  209. package/dist/engine/boot-validator/screens.d.ts +0 -2
  210. package/dist/engine/boot-validator/screens.js +102 -869
  211. package/dist/engine/boot-validator/security-baseline.js +3 -2
  212. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  213. package/dist/engine/boot-validator/workspaces.js +3 -3
  214. package/dist/engine/bridge-stub.js +3 -4
  215. package/dist/engine/build-app-schema.js +130 -170
  216. package/dist/engine/build-config-feature-schema.js +50 -50
  217. package/dist/engine/build-target.js +20 -23
  218. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  219. package/dist/engine/config-helpers.js +4 -4
  220. package/dist/engine/constants.js +2 -2
  221. package/dist/engine/create-app.js +2 -2
  222. package/dist/engine/define-feature.js +21 -13
  223. package/dist/engine/define-handler.js +13 -11
  224. package/dist/engine/define-step.js +1 -1
  225. package/dist/engine/define-workflow.d.ts +5 -5
  226. package/dist/engine/define-workflow.js +6 -6
  227. package/dist/engine/entity-handlers.d.ts +13 -0
  228. package/dist/engine/entity-handlers.js +22 -34
  229. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  230. package/dist/engine/entity-list-runtime-options.js +18 -0
  231. package/dist/engine/event-def.js +2 -2
  232. package/dist/engine/extension-names.d.ts +16 -17
  233. package/dist/engine/extension-names.js +37 -38
  234. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  235. package/dist/engine/extensions/tenant-data.js +2 -2
  236. package/dist/engine/extensions/user-data.d.ts +23 -24
  237. package/dist/engine/factories.d.ts +14 -13
  238. package/dist/engine/factories.js +38 -37
  239. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  240. package/dist/engine/feature-ast/extractors/handlers.js +2 -1
  241. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  242. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  243. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  244. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  245. package/dist/engine/feature-ast/parse.js +13 -13
  246. package/dist/engine/feature-ast/patch.js +4 -182
  247. package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
  248. package/dist/engine/feature-ast/pattern-change-schema.js +2 -1
  249. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  250. package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
  251. package/dist/engine/feature-ast/patterns.d.ts +11 -2
  252. package/dist/engine/feature-ast/patterns.js +43 -51
  253. package/dist/engine/feature-ast/render.js +50 -89
  254. package/dist/engine/feature-config-events-jobs.js +15 -12
  255. package/dist/engine/feature-entity-handlers.js +12 -5
  256. package/dist/engine/feature-manifest.d.ts +9 -9
  257. package/dist/engine/feature-manifest.js +6 -7
  258. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  259. package/dist/engine/feature-ui-extensions.js +30 -22
  260. package/dist/engine/field-access.js +5 -6
  261. package/dist/engine/form-derivations.js +18 -7
  262. package/dist/engine/handler-field-references.js +1 -1
  263. package/dist/engine/index.d.ts +1 -0
  264. package/dist/engine/index.js +4 -4
  265. package/dist/engine/object-form.d.ts +1 -0
  266. package/dist/engine/object-form.js +14 -4
  267. package/dist/engine/ownership.js +3 -3
  268. package/dist/engine/parse-ref-target.js +5 -7
  269. package/dist/engine/pattern-library/index.js +2 -2
  270. package/dist/engine/pattern-library/library.js +2 -2
  271. package/dist/engine/pattern-library/types.js +3 -3
  272. package/dist/engine/pipeline.js +2 -2
  273. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  274. package/dist/engine/project-app-schema-for-roles.js +7 -5
  275. package/dist/engine/projection-helpers.js +9 -9
  276. package/dist/engine/qualified-name.js +2 -2
  277. package/dist/engine/registry-facade.js +1 -1
  278. package/dist/engine/registry-ingest.js +13 -13
  279. package/dist/engine/registry-state.js +17 -19
  280. package/dist/engine/registry-validate.js +12 -12
  281. package/dist/engine/run-in.js +2 -2
  282. package/dist/engine/run-pipeline.js +7 -0
  283. package/dist/engine/schema-builder.js +251 -233
  284. package/dist/engine/screen-filter-ops.js +17 -20
  285. package/dist/engine/screen-helpers.js +4 -3
  286. package/dist/engine/screen-kind.d.ts +7 -0
  287. package/dist/engine/screen-kind.js +1 -0
  288. package/dist/engine/state-machine.d.ts +12 -12
  289. package/dist/engine/state-machine.js +7 -7
  290. package/dist/engine/steps/_resolver-utils.js +4 -3
  291. package/dist/engine/steps/for-each.js +4 -4
  292. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  293. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  294. package/dist/engine/steps/wait-for-event.js +4 -4
  295. package/dist/engine/steps/workflow-system-events.js +3 -3
  296. package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
  297. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  298. package/dist/engine/tier-resolver-extension.js +26 -26
  299. package/dist/engine/transfer-adjacency.js +2 -2
  300. package/dist/engine/validate-projection-allowlist.js +9 -28
  301. package/dist/engine/walk-step-instances.d.ts +2 -0
  302. package/dist/engine/walk-step-instances.js +38 -0
  303. package/dist/engine/where-rule-lint.js +1 -1
  304. package/dist/engine/write-handler-qn-extract.js +1 -1
  305. package/dist/entrypoint/index.d.ts +4 -1
  306. package/dist/entrypoint/index.js +10 -4
  307. package/dist/env/index.d.ts +7 -6
  308. package/dist/env/index.js +10 -9
  309. package/dist/errors/docs-url.d.ts +1 -1
  310. package/dist/errors/docs-url.js +22 -2
  311. package/dist/errors/transition-details.js +7 -7
  312. package/dist/errors/write-error-info.d.ts +6 -6
  313. package/dist/errors/write-error-info.js +6 -6
  314. package/dist/es-ops/context.d.ts +9 -9
  315. package/dist/es-ops/context.js +29 -29
  316. package/dist/es-ops/index.js +7 -9
  317. package/dist/es-ops/operations-schema.js +4 -10
  318. package/dist/es-ops/runner.d.ts +11 -11
  319. package/dist/es-ops/runner.js +49 -53
  320. package/dist/es-ops/types.d.ts +55 -55
  321. package/dist/es-ops/types.js +11 -11
  322. package/dist/event-store/admin-api.js +7 -8
  323. package/dist/event-store/errors.js +1 -1
  324. package/dist/event-store/event-store.js +12 -6
  325. package/dist/event-store/rebuild-dead-letter.js +1 -1
  326. package/dist/files/bind-pending-file-refs.js +3 -3
  327. package/dist/files/create-file-ref-from-bytes.js +4 -1
  328. package/dist/files/feature.js +9 -10
  329. package/dist/files/file-handle.js +7 -9
  330. package/dist/files/file-ref-entity.js +15 -15
  331. package/dist/files/file-ref-table.js +7 -8
  332. package/dist/files/file-routes.d.ts +7 -0
  333. package/dist/files/file-routes.js +22 -13
  334. package/dist/files/in-memory-provider.js +8 -9
  335. package/dist/files/local-provider.js +18 -25
  336. package/dist/files/provider-resolver.d.ts +7 -7
  337. package/dist/files/resolve-content-type.js +43 -0
  338. package/dist/files/storage-tracking.js +2 -2
  339. package/dist/files/upload-ledger.js +7 -0
  340. package/dist/files/zip-stream.d.ts +12 -19
  341. package/dist/files/zip-stream.js +44 -63
  342. package/dist/i18n/required-surface-keys.d.ts +1 -1
  343. package/dist/i18n/required-surface-keys.js +188 -212
  344. package/dist/internal/stack-support.d.ts +2 -1
  345. package/dist/internal/stack-support.js +1 -1
  346. package/dist/jobs/job-runner-context.d.ts +32 -0
  347. package/dist/jobs/job-runner-context.js +305 -0
  348. package/dist/jobs/job-runner-execution.d.ts +7 -0
  349. package/dist/jobs/job-runner-execution.js +160 -0
  350. package/dist/jobs/job-runner-gates.d.ts +19 -0
  351. package/dist/jobs/job-runner-gates.js +198 -0
  352. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  353. package/dist/jobs/job-runner-helpers.js +69 -4
  354. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  355. package/dist/jobs/job-runner-schedulers.js +3 -4
  356. package/dist/jobs/job-runner-scope.d.ts +44 -0
  357. package/dist/jobs/job-runner-scope.js +102 -0
  358. package/dist/jobs/job-runner-types.d.ts +12 -0
  359. package/dist/jobs/job-runner.d.ts +0 -1
  360. package/dist/jobs/job-runner.js +57 -669
  361. package/dist/lifecycle/lifecycle.js +3 -5
  362. package/dist/lifecycle/signal-handlers.js +5 -4
  363. package/dist/logging/utils.d.ts +2 -1
  364. package/dist/logging/utils.js +38 -10
  365. package/dist/migrations/index.js +1 -1
  366. package/dist/migrations/kumiko-drift.js +14 -13
  367. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  368. package/dist/migrations/pending-rebuilds.js +27 -27
  369. package/dist/migrations/projection-table-index.d.ts +3 -3
  370. package/dist/migrations/projection-table-index.js +8 -8
  371. package/dist/observability/escape-hatch-report.d.ts +3 -0
  372. package/dist/observability/escape-hatch-report.js +12 -1
  373. package/dist/observability/metric-validator.js +1 -1
  374. package/dist/observability/metrics-handle.js +3 -3
  375. package/dist/pipeline/append-event-core.js +11 -2
  376. package/dist/pipeline/cascade-handler.d.ts +1 -1
  377. package/dist/pipeline/cascade-handler.js +165 -138
  378. package/dist/pipeline/dispatch-batch.js +2 -2
  379. package/dist/pipeline/dispatch-query.js +20 -13
  380. package/dist/pipeline/dispatch-shared.d.ts +14 -3
  381. package/dist/pipeline/dispatch-shared.js +67 -721
  382. package/dist/pipeline/dispatch-stream.js +1 -1
  383. package/dist/pipeline/dispatch-write.js +297 -190
  384. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  385. package/dist/pipeline/dispatcher-utils.js +1 -1
  386. package/dist/pipeline/event-consumer-state.js +2 -2
  387. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  388. package/dist/pipeline/event-dispatcher-delivery.js +4 -6
  389. package/dist/pipeline/event-dispatcher.js +17 -28
  390. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  391. package/dist/pipeline/handler-context-auth.js +54 -0
  392. package/dist/pipeline/handler-context-config.d.ts +14 -0
  393. package/dist/pipeline/handler-context-config.js +113 -0
  394. package/dist/pipeline/handler-context-db.d.ts +24 -0
  395. package/dist/pipeline/handler-context-db.js +149 -0
  396. package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
  397. package/dist/pipeline/handler-context-dispatch.js +89 -0
  398. package/dist/pipeline/handler-context-events.d.ts +43 -0
  399. package/dist/pipeline/handler-context-events.js +237 -0
  400. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  401. package/dist/pipeline/handler-context-notify.js +55 -0
  402. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  403. package/dist/pipeline/handler-context-observability.js +21 -0
  404. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  405. package/dist/pipeline/handler-context-scope.js +88 -0
  406. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  407. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  408. package/dist/pipeline/member-read-only-transaction.js +1 -3
  409. package/dist/pipeline/msp-rebuild.js +1 -1
  410. package/dist/pipeline/notify-scope.js +12 -1
  411. package/dist/pipeline/pre-handler-gates.js +20 -4
  412. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  413. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  414. package/dist/pipeline/projection-rebuild.js +15 -13
  415. package/dist/pipeline/system-hooks.js +39 -41
  416. package/dist/pipeline/system-identity-switch.js +1 -3
  417. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  418. package/dist/random/generate.d.ts +29 -29
  419. package/dist/random/generate.js +30 -30
  420. package/dist/random/words.js +20 -20
  421. package/dist/rate-limit/middleware.js +9 -5
  422. package/dist/redis/client.js +2 -2
  423. package/dist/redis/pubsub-signal.js +14 -17
  424. package/dist/schema-cli/apply-command.d.ts +2 -0
  425. package/dist/schema-cli/apply-command.js +97 -0
  426. package/dist/schema-cli/baseline-command.d.ts +2 -0
  427. package/dist/schema-cli/baseline-command.js +33 -0
  428. package/dist/schema-cli/generate-command.d.ts +2 -0
  429. package/dist/schema-cli/generate-command.js +90 -0
  430. package/dist/schema-cli/shared.d.ts +39 -0
  431. package/dist/schema-cli/shared.js +95 -0
  432. package/dist/schema-cli/status-command.d.ts +2 -0
  433. package/dist/schema-cli/status-command.js +73 -0
  434. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  435. package/dist/schema-cli/validate-boot-input.js +130 -0
  436. package/dist/schema-cli/validate-command.d.ts +2 -0
  437. package/dist/schema-cli/validate-command.js +130 -0
  438. package/dist/schema-cli.d.ts +2 -18
  439. package/dist/schema-cli.js +47 -510
  440. package/dist/search/index.js +4 -4
  441. package/dist/search/meilisearch-adapter.js +2 -1
  442. package/dist/search/purge-subject.js +4 -5
  443. package/dist/search/reindex-entity.js +6 -9
  444. package/dist/time/boot-tz-warning.d.ts +2 -2
  445. package/dist/time/boot-tz-warning.js +8 -8
  446. package/dist/time/iana.js +10 -10
  447. package/dist/time/index.js +9 -9
  448. package/dist/time/legacy-date.d.ts +2 -2
  449. package/dist/time/legacy-date.js +6 -8
  450. package/dist/time/polyfill.js +1 -1
  451. package/dist/time/tz-context.d.ts +1 -1
  452. package/dist/time/tz-context.js +1 -1
  453. package/dist/ui-types/app-schema.js +7 -7
  454. package/dist/ui-types/list-row-meta.js +4 -4
  455. package/dist/upgrade-cli.js +159 -130
  456. package/dist/utils/compare.js +1 -1
  457. package/package.json +5 -5
  458. package/src/changes.json +358 -0
  459. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  460. package/src/scripts/codemod/feature-layout.ts +10 -2
  461. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  462. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  463. package/src/scripts/codemod/object-form.ts +41 -4
  464. package/src/scripts/codemod/package-merge.ts +58 -8
  465. package/src/scripts/codemod/pii-personal-migration.ts +192 -153
  466. package/src/scripts/codemod/testing-injection.ts +122 -42
  467. package/src/scripts/codemod/testing-move.ts +29 -7
  468. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
@@ -0,0 +1,235 @@
1
+ import { SELECT_OPTION_TONES } from "@cosmicdrift/kumiko-types/fields";
2
+ import { isExtensionEditSection, isFieldsEditSection, isWriteFormEditSection, normalizeEditField, sectionFieldSpecs, } from "../screen-helpers.js";
3
+ import { assertWriteHandlerRegistered, resolveRowActionNavigateTarget, validateActionHasIcon, validatePrimaryActionTarget, } from "./screens-actions.js";
4
+ import { rejectWriteOnlyFormField, validateFieldsXorGroups, validateFormSelectOptions, validateTabSections, } from "./screens-forms.js";
5
+ import { assertExtensionSectionHasComponent, editSectionLabel, listActionContext, navigateActionFromLink, rejectEmptySections, rejectMissingQuery, validateRecordHeaderAction, validateRecordSectionActions, } from "./screens-kind-shared.js";
6
+ import { assertRefTargetRegistered, validateListActionTargets, validateRelatedListSection, } from "./screens-lists.js";
7
+ import { isSelectOptionTone } from "./select-option-tone.js";
8
+ function validateProjectionDetailFlags(featureName, screenId, screen) {
9
+ if (screen.singleton === true && screen.idParam !== undefined) {
10
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) sets both singleton: true ` +
11
+ `and idParam — singleton means the server picks the row from the caller's context, so there ` +
12
+ `is no row id to send under idParam. Remove idParam.`);
13
+ }
14
+ // singleton has no row id in the path, but the auto-generated "Edit"
15
+ // action (defaultEditAction in kumiko-screen.tsx) navigates
16
+ // to the detailFor entity's entityEdit screen using that path id —
17
+ // under singleton it would silently open that screen in CREATE mode
18
+ // instead of editing the singleton row. Reject rather than let that
19
+ // win silently, same as the idParam check above.
20
+ if (screen.singleton === true && screen.detailFor !== undefined) {
21
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) sets both singleton: true ` +
22
+ `and detailFor — the auto-generated "Edit" action navigates using the path's row id, which a ` +
23
+ `singleton screen never has. Remove detailFor, or declare an explicit "edit" action instead.`);
24
+ }
25
+ if (screen.recordTitleField !== undefined && screen.header !== undefined) {
26
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) sets both header and ` +
27
+ `recordTitleField — header.title already names the record, so drop recordTitleField.`);
28
+ }
29
+ }
30
+ function validateProjectionDetailTabs(featureName, screenId, screen) {
31
+ if (screen.layout.mode === "tabs") {
32
+ validateTabSections(featureName, screenId, "projectionDetail", screen.layout.sections);
33
+ for (const section of screen.layout.sections) {
34
+ if (isFieldsEditSection(section) && "groups" in section && section.actions !== undefined) {
35
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) section "${section.id}" ` +
36
+ `declares both groups and actions in tabs mode — actions has no single group's Card to sit ` +
37
+ `in, and an extra title-less Card around the group cards would nest a Card inside a Card. ` +
38
+ `Move the actions onto one group's title, or drop groups for a flat fields grid.`);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ function validateProjectionDetailMetricNavigate(ctx, screenId, screen, field, navigate) {
44
+ const featureName = ctx.feature.name;
45
+ if (navigate.screen === undefined && navigate.entity === undefined) {
46
+ if (navigate.tab !== undefined &&
47
+ !screen.layout.sections.some((section) => section.id === navigate.tab)) {
48
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) metric "${field}" ` +
49
+ `navigates to tab "${navigate.tab}", which is not a section id on this screen. Navigate to an existing section id or remove navigate.tab.`);
50
+ }
51
+ }
52
+ else {
53
+ // Also runs without `tab`: a typo'd screen/entity or an entity
54
+ // target without entityId would otherwise render a clickable
55
+ // metric that silently does nothing.
56
+ resolveRowActionNavigateTarget(featureName, screenId, "projectionDetail", "metric", navigateActionFromLink(field, navigate), ctx.allScreenQns, ctx.navTargetShortIds, ctx.screensByShortId, ctx.detailForScreens);
57
+ }
58
+ }
59
+ function validateProjectionDetailMetric(ctx, screenId, screen, metric) {
60
+ if (typeof metric.label !== "string" || metric.label.length === 0) {
61
+ throw new Error(`[Feature ${ctx.feature.name}] Screen "${screenId}" (projectionDetail) metric "${metric.field}" has ` +
62
+ `no "label" — every metric needs an i18n label key, ` +
63
+ `there is no fallback to the raw column name.`);
64
+ }
65
+ if (metric.navigate !== undefined) {
66
+ validateProjectionDetailMetricNavigate(ctx, screenId, screen, metric.field, metric.navigate);
67
+ }
68
+ }
69
+ function validateProjectionDetailStatusTones(featureName, screenId, screen) {
70
+ for (const [statusValue, tone] of Object.entries(screen.header?.statusTones ?? {})) {
71
+ if (!isSelectOptionTone(tone)) {
72
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) header.statusTones["${statusValue}"] ` +
73
+ `is "${String(tone)}" — expected one of ${SELECT_OPTION_TONES.join(", ")}.`);
74
+ }
75
+ }
76
+ }
77
+ function validateProjectionDetailSubtitleParts(ctx, screenId, subtitleHref, parts) {
78
+ const featureName = ctx.feature.name;
79
+ if (subtitleHref !== undefined) {
80
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) sets header.subtitleHref ` +
81
+ `together with a multi-part header.subtitle — subtitleHref only works with the string form. Use a single-field subtitle or remove subtitleHref.`);
82
+ }
83
+ const seenPartFields = new Set();
84
+ for (const part of parts) {
85
+ const partField = typeof part === "string" ? part : part.field;
86
+ if (seenPartFields.has(partField)) {
87
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) header.subtitle ` +
88
+ `lists field "${partField}" twice. List each field once.`);
89
+ }
90
+ seenPartFields.add(partField);
91
+ const navigate = typeof part === "string" ? undefined : part.navigate;
92
+ if (navigate === undefined)
93
+ continue;
94
+ if (navigate.screen === undefined && navigate.entity === undefined) {
95
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) header.subtitle part ` +
96
+ `"${partField}" navigates with neither "screen" nor "entity" — a tab-only navigate has no link target.`);
97
+ }
98
+ resolveRowActionNavigateTarget(featureName, screenId, "projectionDetail", "subtitle part", navigateActionFromLink(partField, navigate), ctx.allScreenQns, ctx.navTargetShortIds, ctx.screensByShortId, ctx.detailForScreens);
99
+ }
100
+ }
101
+ function validateProjectionDetailSubtitle(ctx, screenId, screen) {
102
+ const header = screen.header;
103
+ if (header !== undefined && Array.isArray(header.subtitle)) {
104
+ validateProjectionDetailSubtitleParts(ctx, screenId, header.subtitleHref, header.subtitle);
105
+ }
106
+ }
107
+ function validateProjectionDetailExtensionSection(featureName, screenId, section) {
108
+ // projectionDetail is read-only (no composed form submit) — an
109
+ // extension that persists through the host's Save button has
110
+ // nothing to save against. Extensions that persist themselves
111
+ // (e.g. notes-history NotesSection, via their own dispatcher
112
+ // writes) are fine.
113
+ if (section.contributesToFormSubmit === true) {
114
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) extension section ` +
115
+ `"${section.title}" sets contributesToFormSubmit: true, but projectionDetail has no ` +
116
+ `form submit to contribute to (read-only screen). Omit contributesToFormSubmit and ` +
117
+ `persist through the extension's own dispatcher writes instead.`);
118
+ }
119
+ assertExtensionSectionHasComponent(featureName, screenId, "projectionDetail", section);
120
+ // The host-derived entity name on projectionDetail is an internal
121
+ // placeholder (no real entity backs this screen) — a self-
122
+ // persisting extension needs the real domain entity name declared
123
+ // explicitly, or it silently reads/writes against the wrong type.
124
+ if (section.entityName === undefined || section.entityName.trim().length === 0) {
125
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) extension section ` +
126
+ `"${section.title}" has no entityName — projectionDetail has no real entity, so the ` +
127
+ `extension must declare entityName explicitly (the domain entity it persists against).`);
128
+ }
129
+ }
130
+ function validateProjectionDetailRelatedList(ctx, screenId, screen, section) {
131
+ if (screen.layout.mode === "wizard") {
132
+ throw new Error(`[Feature ${ctx.feature.name}] Screen "${screenId}" (projectionDetail) section "${section.title}" ` +
133
+ `is kind "relatedList" in a wizard layout — a read-only list is not supported in a ` +
134
+ `stepped form. Remove mode: "wizard" or drop the relatedList section.`);
135
+ }
136
+ validateRelatedListSection({
137
+ featureName: ctx.feature.name,
138
+ featureMap: ctx.featureMap,
139
+ screenId,
140
+ screenType: "projectionDetail",
141
+ where: `Screen "${screenId}" (projectionDetail) section "${section.title}" (relatedList)`,
142
+ section,
143
+ screens: ctx.feature.screens,
144
+ allWriteHandlerQns: ctx.allWriteHandlerQns,
145
+ allScreenQns: ctx.allScreenQns,
146
+ navTargetShortIds: ctx.navTargetShortIds,
147
+ screensByShortId: ctx.screensByShortId,
148
+ detailForScreens: ctx.detailForScreens,
149
+ });
150
+ }
151
+ function validateProjectionDetailWriteForm(ctx, screenId, screen, section) {
152
+ const featureName = ctx.feature.name;
153
+ if (Object.keys(section.fieldDefs).length === 0) {
154
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) section "${section.title}" ` +
155
+ `(writeForm) has an empty fieldDefs map — declare at least one field type.`);
156
+ }
157
+ if (section.fields.length === 0) {
158
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) section "${section.title}" ` +
159
+ `(writeForm) has zero fields — drop the section or add fields to it.`);
160
+ }
161
+ // A ref to a fieldDef the form never renders would never resolve.
162
+ const writeFormFieldNames = new Set(section.fields.map((f) => normalizeEditField(f).field));
163
+ for (const [defName, fdef] of Object.entries(section.fieldDefs)) {
164
+ validateFormSelectOptions(featureName, screenId, `projectionDetail section "${section.title}" writeForm`, defName, fdef, writeFormFieldNames);
165
+ rejectWriteOnlyFormField(featureName, screenId, `projectionDetail section "${section.title}" writeForm`, defName, fdef);
166
+ }
167
+ for (const f of section.fields) {
168
+ const fieldName = normalizeEditField(f).field;
169
+ if (section.fieldDefs[fieldName] === undefined) {
170
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) section "${section.title}" ` +
171
+ `(writeForm) field "${fieldName}" has no entry in fieldDefs — every rendered field needs ` +
172
+ `a type declared there. Add the field to fieldDefs.`);
173
+ }
174
+ }
175
+ if (screen.layout.mode === "wizard") {
176
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail) section "${section.title}" ` +
177
+ `is kind "writeForm" in a wizard layout — a self-persisting form is not supported in a ` +
178
+ `stepped form. Remove mode: "wizard" or drop the writeForm section.`);
179
+ }
180
+ assertWriteHandlerRegistered(featureName, `Screen "${screenId}" (projectionDetail) section "${section.title}" (writeForm)`, section.handler, ctx.allWriteHandlerQns);
181
+ }
182
+ function validateProjectionDetailFieldsSection(ctx, screenId, section) {
183
+ const featureName = ctx.feature.name;
184
+ validateFieldsXorGroups(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail)`, section);
185
+ for (const fieldSpec of sectionFieldSpecs(section)) {
186
+ const normalizedField = normalizeEditField(fieldSpec);
187
+ if (normalizedField.refEntity !== undefined) {
188
+ assertRefTargetRegistered(`[Feature ${featureName}] Screen "${screenId}" (projectionDetail)`, `field "${normalizedField.field}" (refEntity)`, normalizedField.refEntity, featureName, ctx.featureMap);
189
+ }
190
+ }
191
+ }
192
+ function validateProjectionDetailSection(ctx, screenId, screen, section) {
193
+ const featureName = ctx.feature.name;
194
+ validateRecordSectionActions(ctx, screenId, "projectionDetail", section);
195
+ if (section.kind === "relatedList" && section.emptyState?.action !== undefined) {
196
+ const sectionLabel = editSectionLabel(section);
197
+ validateActionHasIcon(featureName, screenId, "projectionDetail", `section "${sectionLabel}" emptyState action`, section.emptyState.action);
198
+ validateListActionTargets(listActionContext(ctx, screenId, "projectionDetail", undefined, `Screen "${screenId}" (projectionDetail) section "${sectionLabel}"`), "emptyState action", section.emptyState.action);
199
+ }
200
+ if (isExtensionEditSection(section)) {
201
+ validateProjectionDetailExtensionSection(featureName, screenId, section);
202
+ }
203
+ else if (section.kind === "relatedList") {
204
+ validateProjectionDetailRelatedList(ctx, screenId, screen, section);
205
+ }
206
+ else if (isWriteFormEditSection(section)) {
207
+ validateProjectionDetailWriteForm(ctx, screenId, screen, section);
208
+ }
209
+ else {
210
+ validateProjectionDetailFieldsSection(ctx, screenId, section);
211
+ }
212
+ }
213
+ // Query-driven like projectionList, but a single row plus layout instead of
214
+ // columns. No entity check is possible: fields resolve against the query
215
+ // response at render time, not against an entity.
216
+ export function validateProjectionDetailScreen(ctx, screenId, screen) {
217
+ const featureName = ctx.feature.name;
218
+ rejectMissingQuery(featureName, screenId, "projectionDetail", screen.query);
219
+ validateProjectionDetailFlags(featureName, screenId, screen);
220
+ rejectEmptySections(featureName, screenId, "projectionDetail", screen.layout.sections.length);
221
+ validateProjectionDetailTabs(featureName, screenId, screen);
222
+ for (const metric of screen.metrics ?? []) {
223
+ validateProjectionDetailMetric(ctx, screenId, screen, metric);
224
+ }
225
+ validateProjectionDetailStatusTones(featureName, screenId, screen);
226
+ validateProjectionDetailSubtitle(ctx, screenId, screen);
227
+ for (const section of screen.layout.sections) {
228
+ validateProjectionDetailSection(ctx, screenId, screen, section);
229
+ }
230
+ validatePrimaryActionTarget(featureName, screenId, screen);
231
+ for (const action of screen.actions ?? []) {
232
+ validateActionHasIcon(featureName, screenId, "projectionDetail", "action", action);
233
+ validateRecordHeaderAction(ctx, screenId, "projectionDetail", screen.detailFor, action);
234
+ }
235
+ }
@@ -0,0 +1,3 @@
1
+ import type { ProjectionListScreenDefinition } from "../types/screen.ts";
2
+ import { type ScreenValidationContext } from "./screens-kind-shared.ts";
3
+ export declare function validateProjectionListScreen(ctx: ScreenValidationContext, screenId: string, screen: ProjectionListScreenDefinition): void;
@@ -0,0 +1,52 @@
1
+ import { normalizeListColumn } from "../screen-helpers.js";
2
+ import { assertWriteHandlerRegistered, resolveRowActionNavigateTarget, validateAtMostOneRowClick, validateDrawerTargetAction, } from "./screens-actions.js";
3
+ import { listActionContext, rejectEmptyColumns, rejectMissingQuery, rejectNonArrayInFilter, validateListColumnRendering, } from "./screens-kind-shared.js";
4
+ import { validateListActionTargets, validateListFacets } from "./screens-lists.js";
5
+ function validateProjectionListRowActions(ctx, screenId, rowActions) {
6
+ const listContext = listActionContext(ctx, screenId, "projectionList", undefined, `Screen "${screenId}" (projectionList)`);
7
+ for (const action of rowActions) {
8
+ validateListActionTargets(listContext, "rowAction", action);
9
+ }
10
+ validateAtMostOneRowClick(ctx.feature.name, screenId, "projectionList", rowActions);
11
+ }
12
+ // Plain navigate toolbarActions (no tab) have no boot check yet.
13
+ function validateProjectionListToolbarActions(ctx, screenId, toolbarActions) {
14
+ const featureName = ctx.feature.name;
15
+ for (const action of toolbarActions) {
16
+ if (action.kind === "drawer") {
17
+ validateDrawerTargetAction(featureName, screenId, "projectionList", "toolbarAction", action, ctx.feature.screens);
18
+ }
19
+ else if (action.kind === "writeHandler") {
20
+ assertWriteHandlerRegistered(featureName, `Screen "${screenId}" (projectionList) toolbarAction "${action.id}"`, action.handler, ctx.allWriteHandlerQns);
21
+ }
22
+ if (action.kind === "navigate" && action.tab !== undefined) {
23
+ resolveRowActionNavigateTarget(featureName, screenId, "projectionList", "toolbarAction", action, ctx.allScreenQns, ctx.navTargetShortIds, ctx.screensByShortId, ctx.detailForScreens);
24
+ }
25
+ }
26
+ }
27
+ // Query-driven, no entity, so only query + columns are checked (the column
28
+ // fields cannot be checked against an entity; they resolve against the
29
+ // projection rows at render time).
30
+ export function validateProjectionListScreen(ctx, screenId, screen) {
31
+ const featureName = ctx.feature.name;
32
+ rejectMissingQuery(featureName, screenId, "projectionList", screen.query);
33
+ rejectEmptyColumns(featureName, screenId, "projectionList", screen.columns.length);
34
+ for (const col of screen.columns) {
35
+ validateListColumnRendering(ctx, screenId, "projectionList", normalizeListColumn(col));
36
+ }
37
+ // Screen filter: field existence can't be checked without an entity (columns
38
+ // aren't a complete field inventory of the underlying query), so only the
39
+ // structure is pinned: "in" requires an array.
40
+ if (screen.filter !== undefined) {
41
+ rejectNonArrayInFilter(featureName, screenId, "projectionList", screen.filter);
42
+ }
43
+ if (screen.facets !== undefined) {
44
+ validateListFacets(`[Feature ${featureName}] Screen "${screenId}" (projectionList)`, screen.facets, screen.columns, featureName, ctx.featureMap);
45
+ }
46
+ if (screen.rowActions !== undefined) {
47
+ validateProjectionListRowActions(ctx, screenId, screen.rowActions);
48
+ }
49
+ if (screen.toolbarActions !== undefined) {
50
+ validateProjectionListToolbarActions(ctx, screenId, screen.toolbarActions);
51
+ }
52
+ }
@@ -0,0 +1,3 @@
1
+ import type { SecretMintScreenDefinition } from "../types/screen.ts";
2
+ import type { ScreenValidationContext } from "./screens-kind-shared.ts";
3
+ export declare function validateSecretMintScreen(ctx: ScreenValidationContext, screenId: string, screen: SecretMintScreenDefinition): void;
@@ -0,0 +1,28 @@
1
+ import { validateInlineFormScreen, validateSecretMintConfirm } from "./screens-forms.js";
2
+ function validateSecretMintRevealFields(featureName, screenId, screen) {
3
+ if (screen.reveal.fields.length === 0) {
4
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (secretMint) has an empty reveal.fields ` +
5
+ `list — declare at least one field to reveal.`);
6
+ }
7
+ const revealFieldNames = new Set();
8
+ for (const revealField of screen.reveal.fields) {
9
+ if (typeof revealField.field !== "string" || revealField.field.trim() === "") {
10
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (secretMint) has a reveal.fields entry ` +
11
+ `with an empty or non-string "field". Set field to a non-empty string.`);
12
+ }
13
+ if (revealFieldNames.has(revealField.field)) {
14
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (secretMint) reveal.fields has a ` +
15
+ `duplicate field "${revealField.field}" — each revealed field must be unique.`);
16
+ }
17
+ revealFieldNames.add(revealField.field);
18
+ if (typeof revealField.label !== "string" || revealField.label.trim() === "") {
19
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (secretMint) reveal.fields entry ` +
20
+ `"${revealField.field}" has an empty or non-string "label". Set label to a non-empty string.`);
21
+ }
22
+ }
23
+ }
24
+ export function validateSecretMintScreen(ctx, screenId, screen) {
25
+ validateInlineFormScreen(ctx.feature, screenId, screen, "secretMint", ctx.allWriteHandlerQns, ctx, ctx.featureMap);
26
+ validateSecretMintRevealFields(ctx.feature.name, screenId, screen);
27
+ validateSecretMintConfirm(ctx.feature, screenId, screen, ctx.allWriteHandlerQns);
28
+ }
@@ -0,0 +1,37 @@
1
+ import { type normalizeListColumn } from "../screen-helpers.ts";
2
+ import type { ConfigKeyDefinition, EntityDefinition, FeatureDefinition } from "../types/index.ts";
3
+ import type { EditExtensionSection, EditFieldsSection, EditSectionSpec, RowAction } from "../types/screen.ts";
4
+ import type { NavigateSourceScreenType, NavigateTargetLookups } from "./screens-actions.ts";
5
+ import { type resolveRowActionNavigateTarget } from "./screens-actions.ts";
6
+ import type { ListActionTargetContext } from "./screens-lists.ts";
7
+ export type ScreenValidationContext = NavigateTargetLookups & {
8
+ readonly feature: FeatureDefinition;
9
+ readonly featureMap: ReadonlyMap<string, FeatureDefinition>;
10
+ readonly allWriteHandlerQns: ReadonlySet<string>;
11
+ readonly configKeyDefsByQn: ReadonlyMap<string, ConfigKeyDefinition>;
12
+ };
13
+ export declare function findEntityFeature(entityName: string, featureMap: ReadonlyMap<string, FeatureDefinition>): string | undefined;
14
+ export declare function buildUnknownFieldMessage(featureName: string, screenId: string, fieldName: string, entityName: string, knownFields: ReadonlySet<string>): string;
15
+ export declare function listActionContext(ctx: ScreenValidationContext, screenId: string, screenType: NavigateSourceScreenType, screenEntity: string | undefined, where: string): ListActionTargetContext;
16
+ export declare function resolveScreenEntity(ctx: ScreenValidationContext, screenId: string, entityName: string): EntityDefinition;
17
+ export declare function rejectMissingQuery(featureName: string, screenId: string, kind: "projectionList" | "projectionDetail", query: unknown): void;
18
+ export declare function rejectEmptyColumns(featureName: string, screenId: string, kind: "projectionList" | "entityList", columnCount: number): void;
19
+ export declare function validateListColumnRendering(ctx: ScreenValidationContext, screenId: string, kind: "projectionList" | "entityList", column: ReturnType<typeof normalizeListColumn>): void;
20
+ export declare function rejectNonArrayInFilter(featureName: string, screenId: string, kind: "projectionList" | "entityList", filter: {
21
+ readonly op: string;
22
+ readonly value?: unknown;
23
+ }): void;
24
+ export declare function rejectEmptySections(featureName: string, screenId: string, kind: "projectionDetail" | "configEdit" | "entityEdit", sectionCount: number): void;
25
+ export declare function assertExtensionSectionHasComponent(featureName: string, screenId: string, kind: "projectionDetail" | "configEdit" | "entityEdit", section: EditExtensionSection): void;
26
+ export declare function rejectProjectionDetailOnlySection(featureName: string, screenId: string, kind: "configEdit" | "entityEdit", section: Exclude<EditSectionSpec, EditExtensionSection>): asserts section is EditFieldsSection;
27
+ export declare function editSectionLabel(section: EditSectionSpec): string;
28
+ export declare function validateRecordSectionActions(ctx: ScreenValidationContext, screenId: string, kind: "projectionDetail" | "entityEdit", section: EditSectionSpec): void;
29
+ export declare function validateRecordHeaderAction(ctx: ScreenValidationContext, screenId: string, kind: "projectionDetail" | "entityEdit", screenEntity: string | undefined, action: RowAction): void;
30
+ type NavigateLink = {
31
+ readonly screen?: string;
32
+ readonly entity?: string;
33
+ readonly entityId?: string;
34
+ readonly tab?: string;
35
+ };
36
+ export declare function navigateActionFromLink(id: string, navigate: NavigateLink): Parameters<typeof resolveRowActionNavigateTarget>[4];
37
+ export {};
@@ -0,0 +1,142 @@
1
+ // Checks shared by several screen kinds; each helper takes the kind it reports under.
2
+ // Dependencies point one way: screens-kind-*.ts -> here ->
3
+ // screens-dashboard/forms/lists -> screens-actions.
4
+ import { qualifyEntityName } from "../qualified-name.js";
5
+ import { isWriteFormEditSection } from "../screen-helpers.js";
6
+ import { isRowClickAction, validateActionHasIcon, validateNavigateActionTab, validateRecordActionRedirect, } from "./screens-actions.js";
7
+ import { assertRefTargetRegistered, rejectInertColumnSortable, validateColumnRendererForm, validateRowActionTarget, } from "./screens-lists.js";
8
+ export function findEntityFeature(entityName, featureMap) {
9
+ for (const [name, feature] of featureMap) {
10
+ if (feature.entities?.[entityName])
11
+ return name;
12
+ }
13
+ return undefined;
14
+ }
15
+ export function buildUnknownFieldMessage(featureName, screenId, fieldName, entityName, knownFields) {
16
+ const known = [...knownFields].sort().join(", ");
17
+ return (`[Feature ${featureName}] Screen "${screenId}" references field "${fieldName}" ` +
18
+ `which does not exist on entity "${entityName}" (known: ${known}).`);
19
+ }
20
+ export function listActionContext(ctx, screenId, screenType, screenEntity, where) {
21
+ return {
22
+ featureName: ctx.feature.name,
23
+ screenId,
24
+ screenType,
25
+ screenEntity,
26
+ where,
27
+ screens: ctx.feature.screens,
28
+ allWriteHandlerQns: ctx.allWriteHandlerQns,
29
+ allScreenQns: ctx.allScreenQns,
30
+ navTargetShortIds: ctx.navTargetShortIds,
31
+ screensByShortId: ctx.screensByShortId,
32
+ detailForScreens: ctx.detailForScreens,
33
+ };
34
+ }
35
+ // entityList / entityEdit: entity-refs are feature-local.
36
+ export function resolveScreenEntity(ctx, screenId, entityName) {
37
+ const entityDef = ctx.feature.entities?.[entityName];
38
+ if (entityDef)
39
+ return entityDef;
40
+ const known = Object.keys(ctx.feature.entities ?? {})
41
+ .sort()
42
+ .join(", ") || "(none)";
43
+ const crossFeature = findEntityFeature(entityName, ctx.featureMap);
44
+ const hint = crossFeature
45
+ ? ` Entity "${entityName}" is owned by feature "${crossFeature}" — cross-feature screen ownership is not supported.`
46
+ : "";
47
+ throw new Error(`[Feature ${ctx.feature.name}] Screen "${screenId}" references entity "${entityName}" ` +
48
+ `which is not declared in this feature (known: ${known}).${hint}`);
49
+ }
50
+ export function rejectMissingQuery(featureName, screenId, kind, query) {
51
+ if (!query || typeof query !== "string") {
52
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${kind}) has empty or non-string query. Set query to a registered query name.`);
53
+ }
54
+ }
55
+ // An empty column list would render a blank table: almost always an
56
+ // in-progress screen the author forgot to fill in.
57
+ export function rejectEmptyColumns(featureName, screenId, kind, columnCount) {
58
+ if (columnCount === 0) {
59
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${kind}) has an empty columns list — ` +
60
+ `declare at least one column.`);
61
+ }
62
+ }
63
+ export function validateListColumnRendering(ctx, screenId, kind, column) {
64
+ const featureName = ctx.feature.name;
65
+ validateColumnRendererForm(featureName, screenId, column);
66
+ rejectInertColumnSortable(featureName, screenId, kind, column);
67
+ if (column.refEntity !== undefined) {
68
+ assertRefTargetRegistered(`[Feature ${featureName}] Screen "${screenId}" (${kind})`, `column "${column.field}" (refEntity)`, column.refEntity, featureName, ctx.featureMap);
69
+ }
70
+ }
71
+ export function rejectNonArrayInFilter(featureName, screenId, kind, filter) {
72
+ if (filter.op === "in" && !Array.isArray(filter.value)) {
73
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${kind}) filter.op "in" requires ` +
74
+ `filter.value to be a readonly array. Pass an array as filter.value.`);
75
+ }
76
+ }
77
+ // A layout with zero sections renders as nothing: reject at boot so the
78
+ // author sees it before the blank form surprises them.
79
+ export function rejectEmptySections(featureName, screenId, kind, sectionCount) {
80
+ if (sectionCount === 0) {
81
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${kind}) has an empty sections list — ` +
82
+ `declare at least one section.`);
83
+ }
84
+ }
85
+ export function assertExtensionSectionHasComponent(featureName, screenId, kind, section) {
86
+ if (section.component?.react === undefined && section.component?.native === undefined) {
87
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${kind}) extension section ` +
88
+ `"${section.title}" has no component — declare a react/native component marker.`);
89
+ }
90
+ }
91
+ // relatedList and writeForm sections exist on projectionDetail only; what
92
+ // remains afterwards is a plain fields/groups section.
93
+ export function rejectProjectionDetailOnlySection(featureName, screenId, kind, section) {
94
+ if (section.kind === "relatedList") {
95
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${kind}) relatedList section ` +
96
+ `"${section.title}" is not supported — relatedList is a projectionDetail-only ` +
97
+ `primitive (fw#2166). Move the section to a projectionDetail screen or remove it.`);
98
+ }
99
+ if (isWriteFormEditSection(section)) {
100
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${kind}) writeForm section ` +
101
+ `"${section.title}" is not supported — writeForm is a projectionDetail-only primitive. Move the section to a projectionDetail screen or remove it.`);
102
+ }
103
+ }
104
+ export function editSectionLabel(section) {
105
+ return section.title ?? section.id ?? "(untitled)";
106
+ }
107
+ export function validateRecordSectionActions(ctx, screenId, kind, section) {
108
+ const actionContext = `section "${editSectionLabel(section)}" action`;
109
+ for (const action of section.actions ?? []) {
110
+ validateActionHasIcon(ctx.feature.name, screenId, kind, actionContext, action);
111
+ validateRecordActionRedirect(ctx.feature, screenId, kind, action, ctx);
112
+ validateNavigateActionTab(ctx.feature.name, screenId, kind, actionContext, action, ctx);
113
+ }
114
+ }
115
+ const NO_ROW_SUBJECT = {
116
+ projectionDetail: "a detail screen",
117
+ entityEdit: "an edit screen",
118
+ };
119
+ // Header actions reuse RowAction (the displayed record stands in for the row),
120
+ // so the same navigate/writeHandler existence checks as list rowActions apply.
121
+ // `rowClick` is rejected outright: there is no row to click.
122
+ export function validateRecordHeaderAction(ctx, screenId, kind, screenEntity, action) {
123
+ const featureName = ctx.feature.name;
124
+ validateRecordActionRedirect(ctx.feature, screenId, kind, action, ctx);
125
+ if (isRowClickAction(action)) {
126
+ throw new Error(`[Feature ${featureName}] Screen "${qualifyEntityName(featureName, "screen", screenId)}" ` +
127
+ `(${kind}) action "${action.id}" sets rowClick: true — there is no row to ` +
128
+ `click on ${NO_ROW_SUBJECT[kind]}. Remove rowClick.`);
129
+ }
130
+ validateRowActionTarget(listActionContext(ctx, screenId, kind, screenEntity, `Screen "${screenId}" (${kind})`), "action", "rowAction", action);
131
+ }
132
+ export function navigateActionFromLink(id, navigate) {
133
+ return {
134
+ kind: "navigate",
135
+ id,
136
+ label: id,
137
+ ...(navigate.screen !== undefined ? { screen: navigate.screen } : {}),
138
+ ...(navigate.entity !== undefined ? { entity: navigate.entity } : {}),
139
+ ...(navigate.entityId !== undefined ? { entityId: navigate.entityId } : {}),
140
+ ...(navigate.tab !== undefined ? { tab: navigate.tab } : {}),
141
+ };
142
+ }
@@ -24,7 +24,7 @@ type RelatedListSectionValidation = {
24
24
  }>;
25
25
  };
26
26
  export declare function validateRelatedListSection(args: RelatedListSectionValidation): void;
27
- type ListActionTargetContext = NavigateTargetLookups & {
27
+ export type ListActionTargetContext = NavigateTargetLookups & {
28
28
  readonly featureName: string;
29
29
  readonly screenId: string;
30
30
  readonly screenType: NavigateSourceScreenType;
@@ -34,6 +34,7 @@ type ListActionTargetContext = NavigateTargetLookups & {
34
34
  readonly allWriteHandlerQns: ReadonlySet<string>;
35
35
  };
36
36
  export declare function validateListActionTargets(ctx: ListActionTargetContext, actionLabel: "rowAction" | "emptyState action", action: RowAction): void;
37
+ export declare function validateRowActionTarget(ctx: ListActionTargetContext, actionLabel: "rowAction" | "emptyState action" | "action", paramsLabel: "rowAction" | "emptyState action", action: RowAction): void;
37
38
  export declare function rejectInertColumnSortable(featureName: string, screenId: string, screenType: "entityList" | "projectionList", column: {
38
39
  readonly field: string;
39
40
  readonly sortable?: boolean;
@@ -2,7 +2,7 @@ import { REFERENCE_LOOKUP_SOURCES } from "../../ui-types/list-row-meta.js";
2
2
  import { parseRefTarget } from "../parse-ref-target.js";
3
3
  import { qualifyEntityName } from "../qualified-name.js";
4
4
  import { explicitListScreenId, normalizeListColumn, resolveNavParentScreen, } from "../screen-helpers.js";
5
- import { isRowClickAction, rejectRedirectOnListRowAction, resolveRowActionNavigateTarget, validateClientActionName, validateDrawerTargetAction, validateRowActionNavigateParams, } from "./screens-actions.js";
5
+ import { assertWriteHandlerRegistered, isRowClickAction, rejectRedirectOnListRowAction, resolveRowActionNavigateTarget, validateClientActionName, validateDrawerTargetAction, validateRowActionNavigateParams, } from "./screens-actions.js";
6
6
  // validateScreenHasNavArea runs once per screen; the index is built once per
7
7
  // featureMap (immutable during boot) instead of re-scanning every feature each call.
8
8
  const navAreaIndexByFeatureMap = new WeakMap();
@@ -115,13 +115,19 @@ function rejectEmptyStateActionParams(ctx, actionLabel, action) {
115
115
  // Target checks shared by list/related-list rowActions and emptyState.action:
116
116
  // navigate target + params, drawer target, write-handler registration.
117
117
  export function validateListActionTargets(ctx, actionLabel, action) {
118
- const { featureName, screenId, screenType } = ctx;
119
- rejectRedirectOnListRowAction(featureName, screenId, screenType, action);
118
+ rejectRedirectOnListRowAction(ctx.featureName, ctx.screenId, ctx.screenType, action);
120
119
  if (actionLabel === "emptyState action")
121
120
  rejectEmptyStateActionParams(ctx, actionLabel, action);
121
+ validateRowActionTarget(ctx, actionLabel, actionLabel, action);
122
+ }
123
+ // Header actions have always reported navigate-params errors under the
124
+ // "rowAction" label, which validateRowActionNavigateParams only knows in list
125
+ // labels — hence paramsLabel apart from actionLabel.
126
+ export function validateRowActionTarget(ctx, actionLabel, paramsLabel, action) {
127
+ const { featureName, screenId, screenType } = ctx;
122
128
  if (action.kind === "navigate") {
123
129
  const target = resolveRowActionNavigateTarget(featureName, screenId, screenType, actionLabel, action, ctx.allScreenQns, ctx.navTargetShortIds, ctx.screensByShortId, ctx.detailForScreens);
124
- validateRowActionNavigateParams(featureName, screenId, screenType, ctx.screenEntity, action, target, actionLabel);
130
+ validateRowActionNavigateParams(featureName, screenId, screenType, ctx.screenEntity, action, target, paramsLabel);
125
131
  }
126
132
  else if (action.kind === "drawer") {
127
133
  validateDrawerTargetAction(featureName, screenId, screenType, actionLabel, action, ctx.screens);
@@ -129,11 +135,8 @@ export function validateListActionTargets(ctx, actionLabel, action) {
129
135
  else if (action.kind === "clientAction") {
130
136
  validateClientActionName(featureName, screenId, screenType, actionLabel, action);
131
137
  }
132
- else if (!ctx.allWriteHandlerQns.has(action.handler)) {
133
- throw new Error(`[Feature ${featureName}] ${ctx.where} ` +
134
- `${actionLabel} "${action.id}" handler "${action.handler}" ` +
135
- `is not a registered write-handler. Check the QN spelling (expected ` +
136
- `"<feature>:write:<short>") and that the handler is declared via r.writeHandler(...).`);
138
+ else {
139
+ assertWriteHandlerRegistered(featureName, `${ctx.where} ${actionLabel} "${action.id}"`, action.handler, ctx.allWriteHandlerQns);
137
140
  }
138
141
  }
139
142
  function validateRelatedListRowActions(args, rowActions) {
@@ -258,24 +261,23 @@ export function assertRefTargetRegistered(prefix, subject, refTarget, currentFea
258
261
  .join(", ") || "(none)"}.`);
259
262
  }
260
263
  }
261
- // Facets (fw#2224) — unlike filter, a field inventory IS available here: the
264
+ // Facets — unlike filter, a field inventory IS available here: the
262
265
  // declared columns. A facet on a field with no column is almost always a
263
266
  // typo (the user never sees the field anywhere), so this is hard-checked
264
- // rather than just documented. Shared by projectionList and relatedList
265
- // (fw#2740) — `prefix` carries the caller's own screen/section message lead-in.
267
+ // rather than just documented. Shared by projectionList and relatedList —
268
+ // `prefix` carries the caller's own screen/section message lead-in.
266
269
  //
267
- // `type: "reference"` is exempt from the same-name-column rule (fw akte-
268
- // bedienkonzept-2): it filters by an id field (e.g. "propertyId") but
269
- // displays a different, human-readable column (e.g. "propertyLabel") — a
270
- // column named after the filter field would almost never exist, and
271
- // requiring one anyway would force apps to add a column no design calls
272
- // for just to satisfy this check. Its own `entity` is mandatory on the type
273
- // (TS, not just this validator) and is resolved below — that explicit,
274
- // checked declaration is this facet type's field inventory, filling the
275
- // same "not just a typo" role the column-name check plays for select/
276
- // boolean facets. select/boolean facets keep the column requirement
277
- // unchanged: both filter and display the same field, so a facet with no
278
- // matching column is still almost always a typo.
270
+ // `type: "reference"` is exempt from the same-name-column rule: it filters
271
+ // by an id field (e.g. "propertyId") but displays a different,
272
+ // human-readable column (e.g. "propertyLabel") — a column named after the
273
+ // filter field would almost never exist, and requiring one anyway would
274
+ // force apps to add a column no design calls for just to satisfy this check.
275
+ // Its own `entity` is mandatory on the type (TS, not just this validator) and
276
+ // is resolved below — that explicit, checked declaration is this facet type's
277
+ // field inventory, filling the same "not just a typo" role the column-name
278
+ // check plays for select/boolean facets. select/boolean facets keep the
279
+ // column requirement unchanged: both filter and display the same field, so a
280
+ // facet with no matching column is still almost always a typo.
279
281
  export function validateListFacets(prefix, facets, columns, currentFeatureName, featureMap) {
280
282
  const columnFieldNames = new Set(columns.map((col) => normalizeListColumn(col).field));
281
283
  const seenFacetFields = new Set();
@@ -10,8 +10,6 @@ export declare function validateScreens(feature: FeatureDefinition, featureMap:
10
10
  readonly featureName: string;
11
11
  readonly screen: ScreenDefinition;
12
12
  }>): void;
13
- export declare function findEntityFeature(entityName: string, featureMap: ReadonlyMap<string, FeatureDefinition>): string | undefined;
14
- export declare function buildUnknownFieldMessage(featureName: string, screenId: string, fieldName: string, entityName: string, knownFields: ReadonlySet<string>): string;
15
13
  export declare function collectScreenQns(features: readonly FeatureDefinition[]): Set<string>;
16
14
  export declare function screenShortIdsFrom(allScreenQns: ReadonlySet<string>): Set<string>;
17
15
  export declare function collectScreensByShortId(features: readonly FeatureDefinition[]): Map<string, ReadonlyArray<{