@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
@@ -16,10 +16,11 @@ export function isRepresentableAtScale(n, scale) {
16
16
  const tolerance = Math.abs(scaled) * 8 * Number.EPSILON + Number.EPSILON;
17
17
  return Math.abs(scaled - Math.round(scaled)) <= tolerance;
18
18
  }
19
- // Lexikografischer ISO-Vergleich — exakt für `yyyy-mm-dd` (date) und korrekt
20
- // für ISO-Datetime in konsistenter Repräsentation (gleiche Offset-/Präzisions-
21
- // Form). Bewusst ohne Date-API (no-date-api-Guard); die Tag-genaue Grenze
22
- // reicht für min/max-Use-Cases (z.B. Geburtsdatum nicht in der Zukunft).
19
+ // Lexicographic ISO comparison — exact for `yyyy-mm-dd` (date) and correct
20
+ // for ISO datetimes in a consistent representation (same offset/precision
21
+ // form). Deliberately avoids the Date API (no-date-api-Guard); day-level
22
+ // comparison is sufficient for min/max use cases (e.g. a birth date not in
23
+ // the future).
23
24
  function withDateBounds(schema, min, max) {
24
25
  if (min === undefined && max === undefined)
25
26
  return schema;
@@ -76,257 +77,275 @@ function embeddedSubFieldToZod(subField) {
76
77
  assertUnreachable(subField, "embedded sub-field type");
77
78
  }
78
79
  }
79
- // kumiko-lint-ignore complexity-budget field→zod switch is the single schema source of truth
80
+ function textFieldToZod(field, { applyDefaults, isUpdate }) {
81
+ let schema = z.string();
82
+ if (field.maxLength)
83
+ schema = schema.max(field.maxLength);
84
+ if (field.minLength) {
85
+ // "" must stay valid for optional fields and writeOnly updates (""
86
+ // = unchanged); `required` rejects it separately via min(1) below.
87
+ const minLength = field.minLength;
88
+ schema = schema.refine((value) => value.length === 0 || value.length >= minLength, {
89
+ message: `at least ${minLength} characters`,
90
+ });
91
+ }
92
+ if (field.writeOnly === true) {
93
+ // Update: "" = unchanged (dropped before the executor), null = clear,
94
+ // unless required. Create: "" would leave a required field unset.
95
+ if (!isUpdate)
96
+ return field.required ? schema.min(1) : schema;
97
+ return field.required ? schema : schema.nullable();
98
+ }
99
+ if (field.format === "email")
100
+ schema = schema.email();
101
+ if (field.format === "url")
102
+ schema = schema.url();
103
+ if (field.format === "iban") {
104
+ // "" stays valid for an optional field; `required` rejects it via min(1) below.
105
+ schema = schema.superRefine((value, ctx) => {
106
+ if (value !== "" && !isValidIban(value)) {
107
+ ctx.addIssue({ code: "invalid_format", format: "iban", input: value });
108
+ }
109
+ });
110
+ }
111
+ if (field.format === "month") {
112
+ // "" stays valid for an optional field; `required` rejects it via min(1) below.
113
+ schema = schema.refine((value) => value === "" || MONTH_PATTERN.test(value), {
114
+ message: "expected a month as YYYY-MM",
115
+ });
116
+ }
117
+ if (field.required)
118
+ schema = schema.min(1);
119
+ return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
120
+ }
121
+ function longTextFieldToZod(field, applyDefaults) {
122
+ // longText has no `format` variant (by type design). Only optional
123
+ // maxLength and required are supported; otherwise it is an unrestricted
124
+ // z.string().
125
+ let schema = z.string();
126
+ if (field.maxLength)
127
+ schema = schema.max(field.maxLength);
128
+ if (field.required)
129
+ schema = schema.min(1);
130
+ return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
131
+ }
132
+ function selectFieldToZod(field, applyDefaults) {
133
+ const [first, ...rest] = field.options;
134
+ if (!first)
135
+ return z.string();
136
+ const enumSchema = z.enum([first, ...rest]);
137
+ if (field.default !== undefined) {
138
+ // Untouched <select> sends "" too; with a default that maps to the
139
+ // default (same semantics as undefined) instead of the invalid-value
140
+ // rejection. A field with a default is never "unset" —
141
+ // true on both insert AND update, so this branch (and its "" → default
142
+ // mapping) fires regardless of applyDefaults; only the `.default(...)`
143
+ // schema-level fallback for OMITTED input is update-gated below.
144
+ // `null` maps the same way: the no-default branch below normalizes
145
+ // an untouched select to null, and a client that reuses that value
146
+ // against a defaulted field must not get rejected either.
147
+ const mapped = z.preprocess((value) => (value === "" || value === null ? field.default : value), enumSchema);
148
+ return applyDefaults ? mapped.default(field.default) : mapped;
149
+ }
150
+ if (field.required)
151
+ return enumSchema;
152
+ // Optional select without a default: an untouched HTML <select> submits
153
+ // "" for its placeholder option. Treat that as "unset" (null) instead of
154
+ // an invalid enum value — null (not undefined) so the value survives the
155
+ // JSON-serialized event payload and an update can actually clear a
156
+ // previously-set select back to unset, not just skip validation.
157
+ return z.preprocess((value) => (value === "" ? null : value), enumSchema.nullable());
158
+ }
159
+ function multiSelectFieldToZod(field, applyDefaults) {
160
+ const [first, ...rest] = field.options;
161
+ if (!first)
162
+ return z.array(z.string());
163
+ // `required: true` means non-empty, as with a `text` field.
164
+ // An empty array is rejected; the global `.optional()` wrapping
165
+ // in buildInsertSchema determines whether the field may be omitted.
166
+ let schema = z.array(z.enum([first, ...rest]));
167
+ if (field.required)
168
+ schema = schema.min(1);
169
+ return field.default !== undefined && applyDefaults ? schema.default([...field.default]) : schema;
170
+ }
171
+ function numberFieldToZod(field, applyDefaults) {
172
+ let schema = z.number();
173
+ // `integer: true` maps to a Postgres int4 column (entity-table-meta.ts)
174
+ // — bound it here so an out-of-range write fails loud (400) at the
175
+ // schema boundary instead of dying in Postgres (22003 → 500).
176
+ if (field.integer)
177
+ schema = schema.int().min(-2147483648).max(2147483647);
178
+ if (field.min !== undefined)
179
+ schema = schema.min(field.min);
180
+ if (field.max !== undefined)
181
+ schema = schema.max(field.max);
182
+ return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
183
+ }
184
+ function decimalFieldToZod(field, applyDefaults) {
185
+ // Stored as numeric(precision, scale), surfaced as JS number. Bound the
186
+ // value at the write boundary so an over-range or over-scale input fails
187
+ // loud here instead of being silently rounded/rejected by Postgres.
188
+ const limit = 10 ** (field.precision - field.scale);
189
+ const schema = z
190
+ .number()
191
+ .gt(-limit)
192
+ .lt(limit)
193
+ .refine((n) => isRepresentableAtScale(n, field.scale), {
194
+ message: `at most ${field.scale} decimal places`,
195
+ });
196
+ return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
197
+ }
198
+ function moneyFieldToZod(currencies) {
199
+ const [first, ...rest] = currencies;
200
+ if (!first)
201
+ throw new Error("No currencies configured");
202
+ return z.object({
203
+ amount: z.number(),
204
+ currency: z.enum([first, ...rest]),
205
+ });
206
+ }
207
+ function embeddedFieldToZod(field) {
208
+ const shape = {};
209
+ for (const [subName, subField] of Object.entries(field.schema)) {
210
+ const zodSub = embeddedSubFieldToZod(subField);
211
+ shape[subName] = subField.required ? zodSub : zodSub.optional();
212
+ }
213
+ const baseRow = z.object(shape);
214
+ const derived = field.derived;
215
+ // The server is the authority for derived cells: a row is recomputed
216
+ // here, overwriting whatever the client sent, instead of merely
217
+ // being checked against it.
218
+ const row = derived === undefined
219
+ ? baseRow
220
+ : z.preprocess((value) => withDerivedCells(value, derived, field.schema), baseRow);
221
+ if (field.multiple !== true)
222
+ return row;
223
+ // `required: true` means non-empty, same reading as multiSelect —
224
+ // whether the key may be omitted at all is decided by buildInsertSchema
225
+ // off the same flag. `minItems` overrides that default when set.
226
+ let list = z.array(row);
227
+ if (field.maxItems !== undefined)
228
+ list = list.max(field.maxItems);
229
+ const min = field.minItems ?? (field.required === true ? 1 : undefined);
230
+ if (min === undefined)
231
+ return list;
232
+ // A custom issue rather than min(): other arrays (multiSelect) share zod's
233
+ // too_small and must not read "rows"; the bridge honours params.i18nKey.
234
+ // The refinement is invisible to z.toJSONSchema; the meta keeps minItems for agent manifests.
235
+ return list.meta({ minItems: min }).superRefine((rows, ctx) => {
236
+ // skip: the minimum is met, nothing to report
237
+ if (rows.length >= min)
238
+ return;
239
+ ctx.addIssue({
240
+ code: "custom",
241
+ input: rows,
242
+ params: {
243
+ i18nKey: `errors.validation.list_min_rows.${min === 1 ? "one" : "other"}`,
244
+ minimum: min,
245
+ },
246
+ });
247
+ });
248
+ }
249
+ function timestampFieldToZod(field) {
250
+ // When locatedBy is set: wall-clock time without an offset (ISO datetime
251
+ // without `Z`). Otherwise: ISO UTC datetime (with `Z`). Both are validated
252
+ // against the ISO-8601 schema through z.iso.datetime; precision (with or
253
+ // without an offset) depends on locatedBy.
254
+ const schema = field.locatedBy !== undefined ? z.iso.datetime({ local: true }) : z.iso.datetime();
255
+ return withDateBounds(schema, field.min, field.max);
256
+ }
257
+ function locatedTimestampToZod() {
258
+ // Combined wall-clock and time-zone object. On write, either
259
+ // { at, tz } (typical for a UI form, with utc calculated) or { utc, tz }
260
+ // (typical for server-to-server communication, with at calculated) is
261
+ // accepted. On read, the read wrapper provides all three fields.
262
+ //
263
+ // The schema guarantees only tz plus at least one of at or utc.
264
+ const at = z.iso.datetime({ local: true });
265
+ const tz = z.string().refine(isValidIanaTimeZone, { message: "invalid IANA time zone" });
266
+ const utc = z.iso.datetime();
267
+ return z.union([
268
+ z.object({ at, tz, utc: utc.optional() }),
269
+ z.object({ utc, tz, at: at.optional() }),
270
+ ]);
271
+ }
272
+ function fileListFieldToZod(field) {
273
+ // Multi file: array of fileRef UUIDs. Same story as the singular
274
+ // variant — the element type has to match the UUID column on
275
+ // fileRefsTable.id.
276
+ const ids = z.array(z.uuid());
277
+ return field.maxCount === undefined ? ids : ids.max(field.maxCount);
278
+ }
80
279
  export function fieldToZod(field, currencies, opts = {}) {
81
280
  // Insert callers want `.default(...)` applied so an omitted field falls
82
281
  // back to it; buildUpdateSchema passes applyDefaults: false so an omitted
83
282
  // field on update stays omitted (a `{ title }` patch must not clobber
84
283
  // other columns with their defaults) while a field's own default value is
85
- // still known here for "" → default mapping (select case below).
284
+ // still known here for "" → default mapping (selectFieldToZod).
86
285
  const applyDefaults = opts.applyDefaults ?? true;
286
+ const ctx = { applyDefaults, isUpdate: opts.isUpdate === true };
87
287
  switch (field.type) {
88
- case "text": {
89
- let schema = z.string();
90
- if (field.maxLength)
91
- schema = schema.max(field.maxLength);
92
- if (field.minLength) {
93
- // "" must stay valid for optional fields and writeOnly updates (""
94
- // = unchanged); `required` rejects it separately via min(1) below.
95
- const minLength = field.minLength;
96
- schema = schema.refine((value) => value.length === 0 || value.length >= minLength, {
97
- message: `at least ${minLength} characters`,
98
- });
99
- }
100
- if (field.writeOnly === true) {
101
- // Update: "" = unchanged (dropped before the executor), null = clear,
102
- // unless required. Create: "" would leave a required field unset.
103
- if (opts.isUpdate !== true)
104
- return field.required ? schema.min(1) : schema;
105
- return field.required ? schema : schema.nullable();
106
- }
107
- if (field.format === "email")
108
- schema = schema.email();
109
- if (field.format === "url")
110
- schema = schema.url();
111
- if (field.format === "iban") {
112
- // "" stays valid for an optional field; `required` rejects it via min(1) below.
113
- schema = schema.superRefine((value, ctx) => {
114
- if (value !== "" && !isValidIban(value)) {
115
- ctx.addIssue({ code: "invalid_format", format: "iban", input: value });
116
- }
117
- });
118
- }
119
- if (field.format === "month") {
120
- // "" stays valid for an optional field; `required` rejects it via min(1) below.
121
- schema = schema.refine((value) => value === "" || MONTH_PATTERN.test(value), {
122
- message: "expected a month as YYYY-MM",
123
- });
124
- }
125
- if (field.required)
126
- schema = schema.min(1);
127
- return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
128
- }
129
- case "longText": {
130
- // longText hat keine `format`-Variante (per type-design). Nur
131
- // optional maxLength + required, sonst ein offener z.string().
132
- let schema = z.string();
133
- if (field.maxLength)
134
- schema = schema.max(field.maxLength);
135
- if (field.required)
136
- schema = schema.min(1);
137
- return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
138
- }
288
+ case "text":
289
+ return textFieldToZod(field, ctx);
290
+ case "longText":
291
+ return longTextFieldToZod(field, applyDefaults);
139
292
  case "boolean": {
140
293
  const schema = z.boolean();
141
294
  return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
142
295
  }
143
- case "select": {
144
- const [first, ...rest] = field.options;
145
- if (!first)
146
- return z.string();
147
- const enumSchema = z.enum([first, ...rest]);
148
- if (field.default !== undefined) {
149
- // Untouched <select> sends "" too; with a default that maps to the
150
- // default (same semantics as undefined) instead of the invalid-value
151
- // rejection from #1702. A field with a default is never "unset" —
152
- // true on both insert AND update, so this branch (and its "" → default
153
- // mapping) fires regardless of applyDefaults; only the `.default(...)`
154
- // schema-level fallback for OMITTED input is update-gated below.
155
- // `null` maps the same way: the no-default branch below normalizes
156
- // an untouched select to null, and a client that reuses that value
157
- // against a since-defaulted field must not get rejected either.
158
- const mapped = z.preprocess((value) => (value === "" || value === null ? field.default : value), enumSchema);
159
- return applyDefaults ? mapped.default(field.default) : mapped;
160
- }
161
- if (field.required)
162
- return enumSchema;
163
- // Optional select without a default: an untouched HTML <select> submits
164
- // "" for its placeholder option. Treat that as "unset" (null) instead of
165
- // an invalid enum value — null (not undefined) so the value survives the
166
- // JSON-serialized event payload and an update can actually clear a
167
- // previously-set select back to unset, not just skip validation.
168
- return z.preprocess((value) => (value === "" ? null : value), enumSchema.nullable());
169
- }
170
- case "multiSelect": {
171
- const [first, ...rest] = field.options;
172
- if (!first)
173
- return z.array(z.string());
174
- // `required: true` heißt non-empty — Analogie zu `text`-Field.
175
- // Leeres Array wird rejected; das globale `.optional()`-Wrapping
176
- // in buildInsertSchema kümmert sich um „darf fehlen".
177
- let schema = z.array(z.enum([first, ...rest]));
178
- if (field.required)
179
- schema = schema.min(1);
180
- return field.default !== undefined && applyDefaults
181
- ? schema.default([...field.default])
182
- : schema;
183
- }
184
- case "number": {
185
- let schema = z.number();
186
- // `integer: true` maps to a Postgres int4 column (entity-table-meta.ts)
187
- // — bound it here so an out-of-range write fails loud (400) at the
188
- // schema boundary instead of dying in Postgres (22003 → 500).
189
- if (field.integer)
190
- schema = schema.int().min(-2147483648).max(2147483647);
191
- if (field.min !== undefined)
192
- schema = schema.min(field.min);
193
- if (field.max !== undefined)
194
- schema = schema.max(field.max);
195
- return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
196
- }
197
- case "decimal": {
198
- // Stored as numeric(precision, scale), surfaced as JS number. Bound the
199
- // value at the write boundary so an over-range or over-scale input fails
200
- // loud here instead of being silently rounded/rejected by Postgres.
201
- const limit = 10 ** (field.precision - field.scale);
202
- const schema = z
203
- .number()
204
- .gt(-limit)
205
- .lt(limit)
206
- .refine((n) => isRepresentableAtScale(n, field.scale), {
207
- message: `at most ${field.scale} decimal places`,
208
- });
209
- return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
210
- }
296
+ case "select":
297
+ return selectFieldToZod(field, applyDefaults);
298
+ case "multiSelect":
299
+ return multiSelectFieldToZod(field, applyDefaults);
300
+ case "number":
301
+ return numberFieldToZod(field, applyDefaults);
302
+ case "decimal":
303
+ return decimalFieldToZod(field, applyDefaults);
211
304
  case "bigInt": {
212
- // JS-`number`-Round-trip via mode:"number"; sicher bis 2^53.
213
- // safe-integer-Cap ist explizit damit ein Caller, der einen
214
- // Float reinwirft (z.B. parseFloat-Bug), beim Insert sofort
215
- // failed statt silent-Truncation zu kassieren.
305
+ // JS-`number` round-trip via mode:"number"; exact up to 2^53.
306
+ // The safe-integer cap ensures that a caller passing a float (for example,
307
+ // due to a parseFloat bug) fails immediately at insert instead of being
308
+ // silently truncated.
216
309
  const schema = z.number().int().safe();
217
310
  return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
218
311
  }
219
- case "money": {
220
- const [first, ...rest] = currencies;
221
- if (!first)
222
- throw new Error("No currencies configured");
223
- return z.object({
224
- amount: z.number(),
225
- currency: z.enum([first, ...rest]),
226
- });
227
- }
228
- case "embedded": {
229
- const shape = {};
230
- for (const [subName, subField] of Object.entries(field.schema)) {
231
- const zodSub = embeddedSubFieldToZod(subField);
232
- shape[subName] = subField.required ? zodSub : zodSub.optional();
233
- }
234
- const baseRow = z.object(shape);
235
- const derived = field.derived;
236
- // The server is the authority for derived cells: a row is recomputed
237
- // here, overwriting whatever the client sent, instead of merely
238
- // being checked against it.
239
- const row = derived === undefined
240
- ? baseRow
241
- : z.preprocess((value) => withDerivedCells(value, derived, field.schema), baseRow);
242
- if (field.multiple !== true)
243
- return row;
244
- // `required: true` means non-empty, same reading as multiSelect —
245
- // whether the key may be omitted at all is decided by buildInsertSchema
246
- // off the same flag. `minItems` overrides that default when set.
247
- let list = z.array(row);
248
- if (field.maxItems !== undefined)
249
- list = list.max(field.maxItems);
250
- const min = field.minItems ?? (field.required === true ? 1 : undefined);
251
- if (min === undefined)
252
- return list;
253
- // A custom issue rather than min(): other arrays (multiSelect) share zod's
254
- // too_small and must not read "rows"; the bridge honours params.i18nKey.
255
- return list.superRefine((rows, ctx) => {
256
- // skip: the minimum is met, nothing to report
257
- if (rows.length >= min)
258
- return;
259
- ctx.addIssue({
260
- code: "custom",
261
- input: rows,
262
- params: {
263
- i18nKey: `errors.validation.list_min_rows.${min === 1 ? "one" : "other"}`,
264
- minimum: min,
265
- },
266
- });
267
- });
268
- }
269
- case "jsonb": {
270
- // Free-form jsonb — keys sind tenant-/runtime-defined. Validation
271
- // passthrough: any plain object passt durch.
312
+ case "money":
313
+ return moneyFieldToZod(currencies);
314
+ case "embedded":
315
+ return embeddedFieldToZod(field);
316
+ case "jsonb":
317
+ // Free-form jsonb — keys are tenant-/runtime-defined. Validation
318
+ // passes through any plain object.
272
319
  return z.record(z.string(), z.unknown());
273
- }
274
- case "date": {
275
- const schema = z.string().date();
276
- return withDateBounds(schema, field.min, field.max);
277
- }
278
- case "timestamp": {
279
- // Wenn locatedBy gesetzt: Wall-Clock OHNE Offset (ISO-Datetime ohne `Z`).
280
- // Sonst: ISO-UTC-Datetime (mit `Z`). Beide werden über z.iso.datetime
281
- // gegen das ISO-8601-Schema validiert; die Präzision (mit/ohne Offset)
282
- // hängt von locatedBy ab.
283
- const schema = field.locatedBy !== undefined ? z.iso.datetime({ local: true }) : z.iso.datetime();
284
- return withDateBounds(schema, field.min, field.max);
285
- }
286
- case "tz": {
287
- // IANA-Zonenname, validiert gegen die Runtime-Zonenliste
288
- // (isValidIanaTimeZone). Ein ungültiger Name failt hier am
289
- // Write-Boundary statt erst später in ctx.tz.parse / Temporal.
320
+ case "date":
321
+ return withDateBounds(z.string().date(), field.min, field.max);
322
+ case "timestamp":
323
+ return timestampFieldToZod(field);
324
+ case "tz":
325
+ // IANA time-zone name, validated against the runtime's zone list
326
+ // (isValidIanaTimeZone). An invalid name fails at the write boundary
327
+ // instead of later in ctx.tz.parse / Temporal.
290
328
  return z.string().refine(isValidIanaTimeZone, { message: "invalid IANA time zone" });
291
- }
292
- case "locatedTimestamp": {
293
- // Combined Wall-Clock+TZ Object. Beim Write akzeptieren wir entweder
294
- // { at, tz } (typisch UI-Form, utc wird berechnet) oder { utc, tz }
295
- // (typisch Server-zu-Server, at wird berechnet). Beim Read liefert
296
- // der Read-Wrapper alle drei Felder (siehe Phase D in MIGRATION.md).
297
- //
298
- // Hier nur die Schema-Garantie: mindestens tz + (at ODER utc).
299
- const at = z.iso.datetime({ local: true });
300
- const tz = z.string().refine(isValidIanaTimeZone, { message: "invalid IANA time zone" });
301
- const utc = z.iso.datetime();
302
- return z.union([
303
- z.object({ at, tz, utc: utc.optional() }),
304
- z.object({ utc, tz, at: at.optional() }),
305
- ]);
306
- }
329
+ case "locatedTimestamp":
330
+ return locatedTimestampToZod();
307
331
  case "file":
308
- case "image": {
332
+ case "image":
309
333
  // Single file: stores a fileRef UUID — must match fileRefsTable.id
310
334
  // (uuid column). Pre-fix this was z.number() from an era when the
311
335
  // column was (wrongly) integer; the table-builder fix to uuid needs
312
336
  // a matching validation-layer fix here or the CRUD pipeline rejects
313
337
  // every valid UUID with "expected number".
314
338
  return z.uuid();
315
- }
316
339
  case "files":
317
- case "images": {
318
- // Multi file: array of fileRef UUIDs. Same story as the singular
319
- // variant — the element type has to match the UUID column on
320
- // fileRefsTable.id.
321
- const ids = z.array(z.uuid());
322
- return field.maxCount === undefined ? ids : ids.max(field.maxCount);
323
- }
340
+ case "images":
341
+ return fileListFieldToZod(field);
324
342
  case "reference":
325
- // Tier 2.7e-3: Validiert UUID-shape. Existenz-Check der Reference
326
- // (Row im referenced Table existiert + Tenant-Scope) ist Server-
327
- // side-Verantwortung im Handler / Foreign-Key-Constraint, nicht
328
- // im Schema-Validator (würde sonst Round-trip zur DB beim Parse).
329
- // Multi-Mode (Tier 2.7e-Multi): Array von UUIDs.
343
+ // Validates the UUID shape. Checking that the reference exists
344
+ // (the row exists in the referenced table and belongs to the tenant) is
345
+ // the handler / foreign-key constraint's responsibility, not the schema
346
+ // validator's; checking it here would require a database round trip during
347
+ // parsing.
348
+ // Multi-mode accepts an array of UUIDs.
330
349
  return field.multiple === true ? z.array(z.uuid()) : z.uuid();
331
350
  default:
332
351
  assertUnreachable(field, "field type");
@@ -342,8 +361,7 @@ export function fieldToZod(field, currencies, opts = {}) {
342
361
  // fails the check even if the raw minor-unit amounts happen to match.
343
362
  //
344
363
  // Known limitation: compares against rounded `derived` cells, i.e.
345
- // "sum-of-rounded" not "round-of-sum" (kumiko-framework#1866). Follow-up
346
- // for a computed, read-only sibling total: kumiko-framework#1873.
364
+ // "sum-of-rounded" not "round-of-sum".
347
365
  // kumiko-lint-ignore complexity-budget currency-equality check on sibling money payloads
348
366
  function applyTotalsMatchRefinements(entity, schema) {
349
367
  let result = schema;
@@ -360,7 +378,7 @@ function applyTotalsMatchRefinements(entity, schema) {
360
378
  continue;
361
379
  // One side of a totalsMatch pair without the other → reject. Update
362
380
  // payloads only carry `changes`, so omitting the sibling would otherwise
363
- // silently leave sum ≠ total (fw#1841).
381
+ // silently leave sum ≠ total.
364
382
  if (rawRows === undefined || siblingRaw === undefined) {
365
383
  ctx.addIssue({
366
384
  code: "custom",
@@ -478,7 +496,7 @@ export function buildUpdateSchema(entity, currencies = [...DEFAULT_CURRENCIES],
478
496
  // sends only `{ title }` means "only change title"; zod defaults would
479
497
  // silently inject default values for every omitted field and clobber
480
498
  // existing data via the event-store-executor's `changes` payload.
481
- // The field is passed through un-stripped (unlike before fw#1703) so
499
+ // The field is passed through un-stripped so
482
500
  // fieldToZod still knows the default for its "" → default mapping
483
501
  // (e.g. select) — applyDefaults: false only suppresses the schema-level
484
502
  // `.default(...)` fallback for a genuinely omitted key.
@@ -1,18 +1,16 @@
1
- // Screen-Filter (Tier 2.7c) — Op-vs-Field-Type-Compatibility.
1
+ // Screen-Filter — operator-to-field-type compatibility.
2
2
  //
3
- // Wer darf was filtern?
4
- // - text/select/multiSelect: nur equality-Ops (eq, ne, in). lt/gt
5
- // auf Strings ist semantisch fast immer Tippfehler (lexicographic
6
- // compare nutzt der Author selten bewusst); Author kann den
7
- // Filter dann einfach nicht setzen.
8
- // - boolean: eq, ne (in/lt/gt sinnlos für 2-Werte-Type).
9
- // - number/money/decimal/date/timestamp/locatedTimestamp: alle 7
10
- // Ops — die Felder sind natürlich vergleichbar.
3
+ // Allowed operators depend on the field type:
4
+ // - text/select/multiSelect: equality operators only (eq, ne, in). lt/gt
5
+ // on strings are usually accidental; authors can simply omit the filter.
6
+ // - boolean: eq, ne (in/lt/gt are meaningless for a two-value type).
7
+ // - number/money/decimal/date/timestamp/locatedTimestamp: all 7 operators,
8
+ // because these fields are naturally comparable.
11
9
  //
12
- // Boot-Validator nutzt diese Map um Author-Fehler früh zu fangen
13
- // ("filter mit op:lt auf einem text-Feld" → Boot-Fail). Erweitert sich
14
- // transparent: neuer Field-Type → hier eintragen + sortable/filterable-
15
- // Flag im Type-Def, sonst lehnt der Validator das Field generell ab.
10
+ // The boot validator uses this map to catch authoring errors early
11
+ // ("filter with op:lt on a text field" → boot failure). When adding a field
12
+ // type, add it here and set its sortable/filterable flag in the type definition;
13
+ // otherwise the validator rejects the field entirely.
16
14
  const EQUALITY_ONLY = ["eq", "ne", "in"];
17
15
  const COMPARABLE = [
18
16
  "eq",
@@ -39,17 +37,16 @@ export function getAllowedFilterOps(field) {
39
37
  case "timestamp":
40
38
  case "locatedTimestamp":
41
39
  return COMPARABLE;
42
- // tz/embedded/file/image/files/images: nicht filterbar — Author
43
- // kann das Feld nicht als filterable: true markieren (Boot-
44
- // Validator weist `filterable: true` auf den Types ohnehin schon
45
- // ab, weil das Flag dort gar nicht im Type-Def steht).
40
+ // tz/embedded/file/image/files/images: not filterable — authors cannot
41
+ // mark the field as filterable: true because the boot validator already
42
+ // rejects `filterable: true` for these types; the flag is not present in
43
+ // their type definitions.
46
44
  default:
47
45
  return [];
48
46
  }
49
47
  }
50
- // Author hat das Feld als filterable markiert? Tz/Embedded/File-Types
51
- // haben das Flag gar nicht im Type-Def, also fangen wir das per
52
- // `"filterable" in field`-Narrow ab.
48
+ // If an author marked the field as filterable, catch types without that
49
+ // flag in their type definition with the `"filterable" in field` narrowing.
53
50
  export function isFieldFilterable(field) {
54
51
  return "filterable" in field && field.filterable === true;
55
52
  }
@@ -1,3 +1,5 @@
1
+ import { createFallbackLogger } from "../logging/utils.js";
2
+ const log = createFallbackLogger("kumiko");
1
3
  // Entity name the renderer gives the synthesized entity of an actionForm, secretMint or confirm
2
4
  // step. It is not registered, so nothing server-side may treat it as a real entity.
3
5
  export const ACTION_FORM_PSEUDO_ENTITY = "__action-form__";
@@ -71,7 +73,7 @@ export function isWriteFormEditSection(section) {
71
73
  }
72
74
  // Union of both sources, unlike the boot-validator's either-or flattening: that
73
75
  // one runs after the fields-XOR-groups check, collectors run without it and must
74
- // not drop a source (fw#2986).
76
+ // not drop a source.
75
77
  export function sectionFieldSpecs(section) {
76
78
  return [...(section.fields ?? []), ...(section.groups?.flatMap((group) => group.fields) ?? [])];
77
79
  }
@@ -90,8 +92,7 @@ export function normalizeListColumn(c) {
90
92
  process.env.NODE_ENV !== "production" &&
91
93
  col.renderer !== undefined &&
92
94
  typeof col.renderer === "function") {
93
- // biome-ignore lint/suspicious/noConsole: dev-only warning
94
- console.warn(`[kumiko] normalizeListColumn: Feld "${col.field}" hat einen Funktions-Renderer — dieser wird von JSON.stringify verworfen. Bitte auf FormatSpec ({ format: "..." }) migrieren.`);
95
+ log.warn(`normalizeListColumn: field "${col.field}" has a function renderer, which JSON.stringify drops. Migrate it to a FormatSpec ({ format: "..." }).`);
95
96
  }
96
97
  return col;
97
98
  }
@@ -0,0 +1,7 @@
1
+ import type { ScreenDefinition } from "./types/screen.ts";
2
+ export type ScreenKind = ScreenDefinition["type"];
3
+ export type ScreenOfKind = {
4
+ readonly [K in ScreenKind]: Extract<ScreenDefinition, {
5
+ readonly type: K;
6
+ }>;
7
+ };
@@ -0,0 +1 @@
1
+ export {};