@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.
- package/AGENTS.md +6 -1
- package/LICENSE +21 -57
- package/README.md +2 -2
- package/agents/examples/anonymous-access.md +1 -1
- package/agents/examples/apex-landing.md +204 -0
- package/agents/examples/api-worker-split.md +64 -0
- package/agents/examples/app-config.md +14 -0
- package/agents/examples/auth-mfa.md +115 -0
- package/agents/examples/env-composition.md +28 -1
- package/agents/examples/legal-pages.md +1 -1
- package/agents/examples/prod-boot-options.md +16 -0
- package/agents/examples/rate-limiting.md +48 -0
- package/agents/examples/record-detail-layout.md +692 -0
- package/agents/examples/seed-migration.md +212 -0
- package/agents/examples/wizard-form.md +941 -0
- package/agents/examples/writeform-section.md +548 -0
- package/agents/features.md +19 -2
- package/agents/widgets.md +40 -39
- package/dist/api/api-constants.js +2 -2
- package/dist/api/auth-middleware.js +7 -7
- package/dist/api/auth-rate-limiters.js +7 -3
- package/dist/api/auth-routes-mfa.js +1 -1
- package/dist/api/auth-routes-session.js +1 -1
- package/dist/api/auth-routes-shared.js +7 -3
- package/dist/api/auth-routes-signup.js +1 -1
- package/dist/api/client-error-route.js +8 -2
- package/dist/api/client-ip.js +3 -1
- package/dist/api/extra-route.js +1 -1
- package/dist/api/index.d.ts +3 -1
- package/dist/api/index.js +3 -1
- package/dist/api/pii-leak-guard.js +4 -2
- package/dist/api/readiness.js +5 -5
- package/dist/api/redis-sse-broker.js +8 -6
- package/dist/api/request-context.d.ts +1 -1
- package/dist/api/request-context.js +1 -1
- package/dist/api/routes.js +2 -2
- package/dist/api/server-boot-checks.d.ts +10 -0
- package/dist/api/server-boot-checks.js +157 -0
- package/dist/api/server-event-dispatcher.d.ts +26 -0
- package/dist/api/server-event-dispatcher.js +208 -0
- package/dist/api/server-extra-routes.d.ts +11 -0
- package/dist/api/server-extra-routes.js +203 -0
- package/dist/api/server-middleware.d.ts +20 -0
- package/dist/api/server-middleware.js +211 -0
- package/dist/api/server-routes.d.ts +19 -0
- package/dist/api/server-routes.js +293 -0
- package/dist/api/server-websocket-routes.d.ts +4 -0
- package/dist/api/server-websocket-routes.js +108 -0
- package/dist/api/server-wiring.d.ts +32 -0
- package/dist/api/server-wiring.js +236 -0
- package/dist/api/server.d.ts +10 -15
- package/dist/api/server.js +48 -1250
- package/dist/api/session-cookies.js +4 -4
- package/dist/api/sse-broker.js +1 -1
- package/dist/api/sse-route.d.ts +13 -13
- package/dist/api/sse-route.js +31 -27
- package/dist/auth/index.js +4 -4
- package/dist/auth/roles.d.ts +4 -4
- package/dist/auth/roles.js +24 -24
- package/dist/compliance/duration-spec.js +18 -20
- package/dist/compliance/index.js +2 -3
- package/dist/compliance/override-schema.d.ts +4 -4
- package/dist/compliance/override-schema.js +19 -22
- package/dist/compliance/profiles.d.ts +26 -29
- package/dist/compliance/profiles.js +62 -69
- package/dist/compliance/sub-processors.d.ts +28 -30
- package/dist/compliance/sub-processors.js +17 -21
- package/dist/consumer-cli.js +4 -4
- package/dist/crypto/blind-index.js +18 -17
- package/dist/crypto/ciphertext-pattern.js +1 -1
- package/dist/crypto/event-pii.js +3 -3
- package/dist/crypto/kek-source.d.ts +1 -1
- package/dist/crypto/kek-source.js +3 -2
- package/dist/crypto/kms-adapter.d.ts +1 -1
- package/dist/crypto/kms-adapter.js +4 -6
- package/dist/crypto/kms-wiring.d.ts +1 -2
- package/dist/crypto/kms-wiring.js +2 -2
- package/dist/crypto/owner-transition-rekey.d.ts +5 -1
- package/dist/crypto/owner-transition-rekey.js +1 -1
- package/dist/crypto/pg-kms-adapter.js +2 -6
- package/dist/crypto/pii-field-encryption.d.ts +5 -1
- package/dist/crypto/pii-field-encryption.js +7 -5
- package/dist/crypto/subject-resolver.d.ts +1 -1
- package/dist/crypto/subject-resolver.js +2 -2
- package/dist/db/aggregate-sql.d.ts +21 -0
- package/dist/db/aggregate-sql.js +155 -0
- package/dist/db/apply-entity-event.d.ts +6 -6
- package/dist/db/apply-entity-event.js +55 -55
- package/dist/db/blind-index-cleanup.js +7 -7
- package/dist/db/closed-connection.d.ts +9 -0
- package/dist/db/closed-connection.js +95 -0
- package/dist/db/collect-table-metas.js +10 -11
- package/dist/db/compound-types.d.ts +1 -1
- package/dist/db/compound-types.js +12 -12
- package/dist/db/cursor.js +3 -4
- package/dist/db/dialect.d.ts +2 -2
- package/dist/db/dialect.js +3 -3
- package/dist/db/distinct-on.d.ts +1 -1
- package/dist/db/driver-row-coercion.d.ts +2 -0
- package/dist/db/driver-row-coercion.js +186 -0
- package/dist/db/driver-value-preparation.d.ts +16 -0
- package/dist/db/driver-value-preparation.js +99 -0
- package/dist/db/eagerload.d.ts +3 -3
- package/dist/db/eagerload.js +21 -12
- package/dist/db/entity-field-encryption.d.ts +1 -0
- package/dist/db/entity-field-encryption.js +1 -1
- package/dist/db/entity-table-meta.d.ts +2 -2
- package/dist/db/entity-table-meta.js +105 -112
- package/dist/db/event-store-executor-context.d.ts +3 -2
- package/dist/db/event-store-executor-context.js +13 -13
- package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
- package/dist/db/event-store-executor-read-aggregate.js +112 -0
- package/dist/db/event-store-executor-read-filter.d.ts +31 -0
- package/dist/db/event-store-executor-read-filter.js +203 -0
- package/dist/db/event-store-executor-read-paging.d.ts +14 -0
- package/dist/db/event-store-executor-read-paging.js +99 -0
- package/dist/db/event-store-executor-read-rows.d.ts +6 -0
- package/dist/db/event-store-executor-read-rows.js +32 -0
- package/dist/db/event-store-executor-read-search.d.ts +28 -0
- package/dist/db/event-store-executor-read-search.js +221 -0
- package/dist/db/event-store-executor-read-sort.d.ts +41 -0
- package/dist/db/event-store-executor-read-sort.js +130 -0
- package/dist/db/event-store-executor-read-where.d.ts +24 -0
- package/dist/db/event-store-executor-read-where.js +81 -0
- package/dist/db/event-store-executor-read.d.ts +2 -9
- package/dist/db/event-store-executor-read.js +50 -704
- package/dist/db/event-store-executor-write-steps.d.ts +20 -0
- package/dist/db/event-store-executor-write-steps.js +100 -0
- package/dist/db/event-store-executor-write.js +401 -510
- package/dist/db/index.d.ts +6 -2
- package/dist/db/index.js +5 -1
- package/dist/db/list-aggregate-labels.d.ts +14 -0
- package/dist/db/list-aggregate-labels.js +26 -0
- package/dist/db/located-timestamp.d.ts +11 -11
- package/dist/db/located-timestamp.js +29 -30
- package/dist/db/migrate-generator.d.ts +1 -1
- package/dist/db/migrate-generator.js +97 -105
- package/dist/db/migrate-runner.js +29 -26
- package/dist/db/money.d.ts +6 -6
- package/dist/db/money.js +29 -27
- package/dist/db/queries/backfill-pii.js +5 -5
- package/dist/db/queries/ddl.js +1 -1
- package/dist/db/queries/es-ops.d.ts +1 -1
- package/dist/db/queries/es-ops.js +1 -1
- package/dist/db/queries/event-consumer.js +3 -3
- package/dist/db/queries/event-store.js +4 -2
- package/dist/db/queries/projection-rebuild.js +3 -3
- package/dist/db/queries/raw-sql.d.ts +2 -2
- package/dist/db/queries/raw-sql.js +2 -2
- package/dist/db/queries/shadow-swap.js +19 -19
- package/dist/db/query.d.ts +3 -16
- package/dist/db/query.js +8 -761
- package/dist/db/rebuild-marker.js +20 -21
- package/dist/db/reference-data.js +6 -1
- package/dist/db/replay-migration-sql.js +2 -2
- package/dist/db/schema-inspection.js +4 -5
- package/dist/db/table-builder.d.ts +1 -2
- package/dist/db/table-builder.js +137 -190
- package/dist/db/table-info.d.ts +10 -0
- package/dist/db/table-info.js +45 -0
- package/dist/db/temporal-value-guards.d.ts +6 -0
- package/dist/db/temporal-value-guards.js +15 -0
- package/dist/db/tenant-db-runner.d.ts +1 -0
- package/dist/db/tenant-db-runner.js +3 -0
- package/dist/db/tenant-db.d.ts +2 -2
- package/dist/db/tenant-db.js +48 -11
- package/dist/db/where-clause.d.ts +6 -0
- package/dist/db/where-clause.js +236 -0
- package/dist/derivatives/delete-stored-file.js +6 -2
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/engine/boot-validator/access-roles.js +4 -3
- package/dist/engine/boot-validator/api-ext.js +14 -14
- package/dist/engine/boot-validator/boot-check.js +1 -1
- package/dist/engine/boot-validator/config-deps.js +7 -6
- package/dist/engine/boot-validator/detail-screens.js +4 -4
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +77 -61
- package/dist/engine/boot-validator/form-derivations.js +35 -1
- package/dist/engine/boot-validator/gdpr-storage.js +4 -4
- package/dist/engine/boot-validator/i18n-keys.js +6 -4
- package/dist/engine/boot-validator/index.d.ts +2 -2
- package/dist/engine/boot-validator/index.js +22 -20
- package/dist/engine/boot-validator/nav.js +5 -5
- package/dist/engine/boot-validator/ownership.js +12 -9
- package/dist/engine/boot-validator/parent-ref.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +258 -187
- package/dist/engine/boot-validator/projection-list-screens.js +7 -7
- package/dist/engine/boot-validator/query-refs.js +1 -1
- package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
- package/dist/engine/boot-validator/screens-actions.js +23 -17
- package/dist/engine/boot-validator/screens-dashboard.js +2 -5
- package/dist/engine/boot-validator/screens-forms.js +12 -17
- package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
- package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
- package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
- package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
- package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
- package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
- package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
- package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
- package/dist/engine/boot-validator/screens-lists.js +26 -24
- package/dist/engine/boot-validator/screens.d.ts +0 -2
- package/dist/engine/boot-validator/screens.js +102 -869
- package/dist/engine/boot-validator/security-baseline.js +3 -2
- package/dist/engine/boot-validator/transfer-graph.js +2 -2
- package/dist/engine/boot-validator/workspaces.js +3 -3
- package/dist/engine/bridge-stub.js +3 -4
- package/dist/engine/build-app-schema.js +130 -170
- package/dist/engine/build-config-feature-schema.js +50 -50
- package/dist/engine/build-target.js +20 -23
- package/dist/engine/codemod/pipeline-codemod.js +20 -13
- package/dist/engine/config-helpers.js +4 -4
- package/dist/engine/constants.js +2 -2
- package/dist/engine/create-app.js +2 -2
- package/dist/engine/define-feature.js +21 -13
- package/dist/engine/define-handler.js +13 -11
- package/dist/engine/define-step.js +1 -1
- package/dist/engine/define-workflow.d.ts +5 -5
- package/dist/engine/define-workflow.js +6 -6
- package/dist/engine/entity-handlers.d.ts +13 -0
- package/dist/engine/entity-handlers.js +22 -34
- package/dist/engine/entity-list-runtime-options.d.ts +8 -0
- package/dist/engine/entity-list-runtime-options.js +18 -0
- package/dist/engine/event-def.js +2 -2
- package/dist/engine/extension-names.d.ts +16 -17
- package/dist/engine/extension-names.js +37 -38
- package/dist/engine/extensions/escape-hatch-usage.js +1 -1
- package/dist/engine/extensions/tenant-data.js +2 -2
- package/dist/engine/extensions/user-data.d.ts +23 -24
- package/dist/engine/factories.d.ts +14 -13
- package/dist/engine/factories.js +38 -37
- package/dist/engine/feature-ast/entity-field-types.js +1 -1
- package/dist/engine/feature-ast/extractors/handlers.js +2 -1
- package/dist/engine/feature-ast/extractors/hooks.js +1 -1
- package/dist/engine/feature-ast/extractors/round2.js +25 -6
- package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
- package/dist/engine/feature-ast/extractors/shared.js +2 -3
- package/dist/engine/feature-ast/parse.js +13 -13
- package/dist/engine/feature-ast/patch.js +4 -182
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
- package/dist/engine/feature-ast/pattern-change-schema.js +2 -1
- package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
- package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
- package/dist/engine/feature-ast/patterns.d.ts +11 -2
- package/dist/engine/feature-ast/patterns.js +43 -51
- package/dist/engine/feature-ast/render.js +50 -89
- package/dist/engine/feature-config-events-jobs.js +15 -12
- package/dist/engine/feature-entity-handlers.js +12 -5
- package/dist/engine/feature-manifest.d.ts +9 -9
- package/dist/engine/feature-manifest.js +6 -7
- package/dist/engine/feature-ui-extensions.d.ts +3 -3
- package/dist/engine/feature-ui-extensions.js +30 -22
- package/dist/engine/field-access.js +5 -6
- package/dist/engine/form-derivations.js +18 -7
- package/dist/engine/handler-field-references.js +1 -1
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -4
- package/dist/engine/object-form.d.ts +1 -0
- package/dist/engine/object-form.js +14 -4
- package/dist/engine/ownership.js +3 -3
- package/dist/engine/parse-ref-target.js +5 -7
- package/dist/engine/pattern-library/index.js +2 -2
- package/dist/engine/pattern-library/library.js +2 -2
- package/dist/engine/pattern-library/types.js +3 -3
- package/dist/engine/pipeline.js +2 -2
- package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
- package/dist/engine/project-app-schema-for-roles.js +7 -5
- package/dist/engine/projection-helpers.js +9 -9
- package/dist/engine/qualified-name.js +2 -2
- package/dist/engine/registry-facade.js +1 -1
- package/dist/engine/registry-ingest.js +13 -13
- package/dist/engine/registry-state.js +17 -19
- package/dist/engine/registry-validate.js +12 -12
- package/dist/engine/run-in.js +2 -2
- package/dist/engine/run-pipeline.js +7 -0
- package/dist/engine/schema-builder.js +251 -233
- package/dist/engine/screen-filter-ops.js +17 -20
- package/dist/engine/screen-helpers.js +4 -3
- package/dist/engine/screen-kind.d.ts +7 -0
- package/dist/engine/screen-kind.js +1 -0
- package/dist/engine/state-machine.d.ts +12 -12
- package/dist/engine/state-machine.js +7 -7
- package/dist/engine/steps/_resolver-utils.js +4 -3
- package/dist/engine/steps/for-each.js +4 -4
- package/dist/engine/steps/unsafe-projection-delete.js +1 -1
- package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
- package/dist/engine/steps/wait-for-event.js +4 -4
- package/dist/engine/steps/workflow-system-events.js +3 -3
- package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
- package/dist/engine/tier-resolver-extension.d.ts +29 -27
- package/dist/engine/tier-resolver-extension.js +26 -26
- package/dist/engine/transfer-adjacency.js +2 -2
- package/dist/engine/validate-projection-allowlist.js +9 -28
- package/dist/engine/walk-step-instances.d.ts +2 -0
- package/dist/engine/walk-step-instances.js +38 -0
- package/dist/engine/where-rule-lint.js +1 -1
- package/dist/engine/write-handler-qn-extract.js +1 -1
- package/dist/entrypoint/index.d.ts +4 -1
- package/dist/entrypoint/index.js +10 -4
- package/dist/env/index.d.ts +7 -6
- package/dist/env/index.js +10 -9
- package/dist/errors/docs-url.d.ts +1 -1
- package/dist/errors/docs-url.js +22 -2
- package/dist/errors/transition-details.js +7 -7
- package/dist/errors/write-error-info.d.ts +6 -6
- package/dist/errors/write-error-info.js +6 -6
- package/dist/es-ops/context.d.ts +9 -9
- package/dist/es-ops/context.js +29 -29
- package/dist/es-ops/index.js +7 -9
- package/dist/es-ops/operations-schema.js +4 -10
- package/dist/es-ops/runner.d.ts +11 -11
- package/dist/es-ops/runner.js +49 -53
- package/dist/es-ops/types.d.ts +55 -55
- package/dist/es-ops/types.js +11 -11
- package/dist/event-store/admin-api.js +7 -8
- package/dist/event-store/errors.js +1 -1
- package/dist/event-store/event-store.js +12 -6
- package/dist/event-store/rebuild-dead-letter.js +1 -1
- package/dist/files/bind-pending-file-refs.js +3 -3
- package/dist/files/create-file-ref-from-bytes.js +4 -1
- package/dist/files/feature.js +9 -10
- package/dist/files/file-handle.js +7 -9
- package/dist/files/file-ref-entity.js +15 -15
- package/dist/files/file-ref-table.js +7 -8
- package/dist/files/file-routes.d.ts +7 -0
- package/dist/files/file-routes.js +22 -13
- package/dist/files/in-memory-provider.js +8 -9
- package/dist/files/local-provider.js +18 -25
- package/dist/files/provider-resolver.d.ts +7 -7
- package/dist/files/resolve-content-type.js +43 -0
- package/dist/files/storage-tracking.js +2 -2
- package/dist/files/upload-ledger.js +7 -0
- package/dist/files/zip-stream.d.ts +12 -19
- package/dist/files/zip-stream.js +44 -63
- package/dist/i18n/required-surface-keys.d.ts +1 -1
- package/dist/i18n/required-surface-keys.js +188 -212
- package/dist/internal/stack-support.d.ts +2 -1
- package/dist/internal/stack-support.js +1 -1
- package/dist/jobs/job-runner-context.d.ts +32 -0
- package/dist/jobs/job-runner-context.js +305 -0
- package/dist/jobs/job-runner-execution.d.ts +7 -0
- package/dist/jobs/job-runner-execution.js +160 -0
- package/dist/jobs/job-runner-gates.d.ts +19 -0
- package/dist/jobs/job-runner-gates.js +198 -0
- package/dist/jobs/job-runner-helpers.d.ts +17 -3
- package/dist/jobs/job-runner-helpers.js +69 -4
- package/dist/jobs/job-runner-schedulers.d.ts +1 -1
- package/dist/jobs/job-runner-schedulers.js +3 -4
- package/dist/jobs/job-runner-scope.d.ts +44 -0
- package/dist/jobs/job-runner-scope.js +102 -0
- package/dist/jobs/job-runner-types.d.ts +12 -0
- package/dist/jobs/job-runner.d.ts +0 -1
- package/dist/jobs/job-runner.js +57 -669
- package/dist/lifecycle/lifecycle.js +3 -5
- package/dist/lifecycle/signal-handlers.js +5 -4
- package/dist/logging/utils.d.ts +2 -1
- package/dist/logging/utils.js +38 -10
- package/dist/migrations/index.js +1 -1
- package/dist/migrations/kumiko-drift.js +14 -13
- package/dist/migrations/pending-rebuilds.d.ts +21 -20
- package/dist/migrations/pending-rebuilds.js +27 -27
- package/dist/migrations/projection-table-index.d.ts +3 -3
- package/dist/migrations/projection-table-index.js +8 -8
- package/dist/observability/escape-hatch-report.d.ts +3 -0
- package/dist/observability/escape-hatch-report.js +12 -1
- package/dist/observability/metric-validator.js +1 -1
- package/dist/observability/metrics-handle.js +3 -3
- package/dist/pipeline/append-event-core.js +11 -2
- package/dist/pipeline/cascade-handler.d.ts +1 -1
- package/dist/pipeline/cascade-handler.js +165 -138
- package/dist/pipeline/dispatch-batch.js +2 -2
- package/dist/pipeline/dispatch-query.js +20 -13
- package/dist/pipeline/dispatch-shared.d.ts +14 -3
- package/dist/pipeline/dispatch-shared.js +67 -721
- package/dist/pipeline/dispatch-stream.js +1 -1
- package/dist/pipeline/dispatch-write.js +297 -190
- package/dist/pipeline/dispatcher-utils.d.ts +1 -1
- package/dist/pipeline/dispatcher-utils.js +1 -1
- package/dist/pipeline/event-consumer-state.js +2 -2
- package/dist/pipeline/event-dispatcher-admin.js +2 -1
- package/dist/pipeline/event-dispatcher-delivery.js +4 -6
- package/dist/pipeline/event-dispatcher.js +17 -28
- package/dist/pipeline/handler-context-auth.d.ts +9 -0
- package/dist/pipeline/handler-context-auth.js +54 -0
- package/dist/pipeline/handler-context-config.d.ts +14 -0
- package/dist/pipeline/handler-context-config.js +113 -0
- package/dist/pipeline/handler-context-db.d.ts +24 -0
- package/dist/pipeline/handler-context-db.js +149 -0
- package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
- package/dist/pipeline/handler-context-dispatch.js +89 -0
- package/dist/pipeline/handler-context-events.d.ts +43 -0
- package/dist/pipeline/handler-context-events.js +237 -0
- package/dist/pipeline/handler-context-notify.d.ts +12 -0
- package/dist/pipeline/handler-context-notify.js +55 -0
- package/dist/pipeline/handler-context-observability.d.ts +7 -0
- package/dist/pipeline/handler-context-observability.js +21 -0
- package/dist/pipeline/handler-context-scope.d.ts +48 -0
- package/dist/pipeline/handler-context-scope.js +88 -0
- package/dist/pipeline/irreversible-operation-gate.js +1 -1
- package/dist/pipeline/lifecycle-pipeline.js +9 -9
- package/dist/pipeline/member-read-only-transaction.js +1 -3
- package/dist/pipeline/msp-rebuild.js +1 -1
- package/dist/pipeline/notify-scope.js +12 -1
- package/dist/pipeline/pre-handler-gates.js +20 -4
- package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
- package/dist/pipeline/pre-transaction-reservations.js +9 -3
- package/dist/pipeline/projection-rebuild.js +15 -13
- package/dist/pipeline/system-hooks.js +39 -41
- package/dist/pipeline/system-identity-switch.js +1 -3
- package/dist/pipeline/tenant-timezone-cache.js +1 -1
- package/dist/random/generate.d.ts +29 -29
- package/dist/random/generate.js +30 -30
- package/dist/random/words.js +20 -20
- package/dist/rate-limit/middleware.js +9 -5
- package/dist/redis/client.js +2 -2
- package/dist/redis/pubsub-signal.js +14 -17
- package/dist/schema-cli/apply-command.d.ts +2 -0
- package/dist/schema-cli/apply-command.js +97 -0
- package/dist/schema-cli/baseline-command.d.ts +2 -0
- package/dist/schema-cli/baseline-command.js +33 -0
- package/dist/schema-cli/generate-command.d.ts +2 -0
- package/dist/schema-cli/generate-command.js +90 -0
- package/dist/schema-cli/shared.d.ts +39 -0
- package/dist/schema-cli/shared.js +95 -0
- package/dist/schema-cli/status-command.d.ts +2 -0
- package/dist/schema-cli/status-command.js +73 -0
- package/dist/schema-cli/validate-boot-input.d.ts +5 -0
- package/dist/schema-cli/validate-boot-input.js +130 -0
- package/dist/schema-cli/validate-command.d.ts +2 -0
- package/dist/schema-cli/validate-command.js +130 -0
- package/dist/schema-cli.d.ts +2 -18
- package/dist/schema-cli.js +47 -510
- package/dist/search/index.js +4 -4
- package/dist/search/meilisearch-adapter.js +2 -1
- package/dist/search/purge-subject.js +4 -5
- package/dist/search/reindex-entity.js +6 -9
- package/dist/time/boot-tz-warning.d.ts +2 -2
- package/dist/time/boot-tz-warning.js +8 -8
- package/dist/time/iana.js +10 -10
- package/dist/time/index.js +9 -9
- package/dist/time/legacy-date.d.ts +2 -2
- package/dist/time/legacy-date.js +6 -8
- package/dist/time/polyfill.js +1 -1
- package/dist/time/tz-context.d.ts +1 -1
- package/dist/time/tz-context.js +1 -1
- package/dist/ui-types/app-schema.js +7 -7
- package/dist/ui-types/list-row-meta.js +4 -4
- package/dist/upgrade-cli.js +159 -130
- package/dist/utils/compare.js +1 -1
- package/package.json +5 -5
- package/src/changes.json +358 -0
- package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
- package/src/scripts/codemod/feature-layout.ts +10 -2
- package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
- package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
- package/src/scripts/codemod/object-form.ts +41 -4
- package/src/scripts/codemod/package-merge.ts +58 -8
- package/src/scripts/codemod/pii-personal-migration.ts +192 -153
- package/src/scripts/codemod/testing-injection.ts +122 -42
- package/src/scripts/codemod/testing-move.ts +29 -7
- 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
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
213
|
-
// safe-integer
|
|
214
|
-
//
|
|
215
|
-
//
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
return
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
//
|
|
280
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
326
|
-
// (
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
1
|
+
// Screen-Filter — operator-to-field-type compatibility.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// - text/select/multiSelect:
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
-
//
|
|
13
|
-
// ("filter
|
|
14
|
-
//
|
|
15
|
-
//
|
|
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:
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
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
|
-
//
|
|
51
|
-
//
|
|
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
|
|
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
|
-
|
|
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 @@
|
|
|
1
|
+
export {};
|