@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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
|
+
import { requestContext } from "../api/request-context.js";
|
|
3
|
+
import { createFallbackLogger } from "../logging/index.js";
|
|
4
|
+
import { extractPgError } from "./pg-error.js";
|
|
5
|
+
// A pool can briefly keep handing out connections the server just closed.
|
|
6
|
+
// Each failed attempt discards one, so retries are bounded by pool size.
|
|
7
|
+
const CLOSED_CONNECTION_CODES = new Set([
|
|
8
|
+
"CONNECTION_CLOSED", // postgres-js
|
|
9
|
+
"57P01", // PG SQLSTATE: admin_shutdown / terminated backend
|
|
10
|
+
]);
|
|
11
|
+
export function isClosedConnectionError(err) {
|
|
12
|
+
const code = extractPgError(err)?.code;
|
|
13
|
+
return code !== undefined && CLOSED_CONNECTION_CODES.has(code);
|
|
14
|
+
}
|
|
15
|
+
// Tx/reserved handles are pinned to one physical connection — a retry there
|
|
16
|
+
// would run on the same dead connection, not swap in a fresh one.
|
|
17
|
+
export function isPooledClient(raw) {
|
|
18
|
+
if (raw === null || (typeof raw !== "object" && typeof raw !== "function"))
|
|
19
|
+
return false;
|
|
20
|
+
return (typeof propertyOf(raw, "begin") === "function" &&
|
|
21
|
+
typeof propertyOf(raw, "savepoint") !== "function" &&
|
|
22
|
+
typeof propertyOf(raw, "release") !== "function");
|
|
23
|
+
}
|
|
24
|
+
const closedConnectionLog = createFallbackLogger("postgres-js");
|
|
25
|
+
// The driver rejects in-flight queries with this AbortError (no code) when the
|
|
26
|
+
// pool closes under them, so isClosedConnectionError can't see it.
|
|
27
|
+
const DRIVER_CLOSED_ABORT_MESSAGE = "The connection was closed.";
|
|
28
|
+
function isDriverClosedAbort(err) {
|
|
29
|
+
return (err instanceof Error && err.name === "AbortError" && err.message === DRIVER_CLOSED_ABORT_MESSAGE);
|
|
30
|
+
}
|
|
31
|
+
function isHandleLike(raw) {
|
|
32
|
+
return raw !== null && (typeof raw === "object" || typeof raw === "function");
|
|
33
|
+
}
|
|
34
|
+
// postgres-js pool and tx handles both carry `typed`.
|
|
35
|
+
function driverOf(raw) {
|
|
36
|
+
if (!isHandleLike(raw))
|
|
37
|
+
return "unknown";
|
|
38
|
+
if (typeof raw["typed"] === "function")
|
|
39
|
+
return "postgres-js";
|
|
40
|
+
return "unknown";
|
|
41
|
+
}
|
|
42
|
+
function positiveNumber(v) {
|
|
43
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined;
|
|
44
|
+
}
|
|
45
|
+
// postgres-js configures timeouts in seconds.
|
|
46
|
+
function timeoutOptionMs(options, driver, optionName) {
|
|
47
|
+
if (driver !== "postgres-js")
|
|
48
|
+
return undefined;
|
|
49
|
+
const seconds = positiveNumber(options[optionName]);
|
|
50
|
+
return seconds === undefined ? undefined : seconds * 1000;
|
|
51
|
+
}
|
|
52
|
+
function errorProp(err, name) {
|
|
53
|
+
return isHandleLike(err) ? err[name] : undefined;
|
|
54
|
+
}
|
|
55
|
+
// Deliberately excludes SQL text and parameters: app SQL can carry PII.
|
|
56
|
+
export function describeClosedConnectionFailure(raw, err, attempts) {
|
|
57
|
+
if (!isClosedConnectionError(err) && !isDriverClosedAbort(err))
|
|
58
|
+
return undefined;
|
|
59
|
+
const driver = driverOf(raw);
|
|
60
|
+
const rawOptions = isHandleLike(raw) ? raw["options"] : undefined;
|
|
61
|
+
const options = isHandleLike(rawOptions) ? rawOptions : {};
|
|
62
|
+
return {
|
|
63
|
+
driver,
|
|
64
|
+
pooled: isPooledClient(raw),
|
|
65
|
+
attempts,
|
|
66
|
+
poolMax: positiveNumber(options["max"]),
|
|
67
|
+
idleTimeoutMs: timeoutOptionMs(options, driver, "idle_timeout"),
|
|
68
|
+
maxLifetimeMs: timeoutOptionMs(options, driver, "max_lifetime"),
|
|
69
|
+
connectionTimeoutMs: timeoutOptionMs(options, driver, "connect_timeout"),
|
|
70
|
+
errorName: errorProp(err, "name"),
|
|
71
|
+
errorCode: extractPgError(err)?.code ?? errorProp(err, "code"),
|
|
72
|
+
errorErrno: errorProp(err, "errno"),
|
|
73
|
+
errorMessage: errorProp(err, "message"),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function logClosedConnection(raw, err, attempts, context) {
|
|
77
|
+
const failure = describeClosedConnectionFailure(raw, err, attempts);
|
|
78
|
+
// skip: not a closed-connection error, nothing to diagnose
|
|
79
|
+
if (!failure)
|
|
80
|
+
return;
|
|
81
|
+
// Request identity lets this line be joined to the route's `handler failed` log.
|
|
82
|
+
const request = requestContext.get();
|
|
83
|
+
closedConnectionLog.warn("closed connection surfaced", {
|
|
84
|
+
...failure,
|
|
85
|
+
...context,
|
|
86
|
+
requestId: request?.requestId,
|
|
87
|
+
correlationId: request?.correlationId,
|
|
88
|
+
handler: request?.handler,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const DEFAULT_POOL_MAX = 10;
|
|
92
|
+
export function poolMaxOf(raw) {
|
|
93
|
+
const max = propertyOf(propertyOf(raw, "options"), "max");
|
|
94
|
+
return typeof max === "number" && Number.isInteger(max) && max > 0 ? max : DEFAULT_POOL_MAX;
|
|
95
|
+
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
// Canonical table source for both `kumiko schema generate` and setupTestStack
|
|
2
2
|
// (entities, storeTables, projections, multiStreamProjections). One shared
|
|
3
|
-
// source so the test-DB push and migrations cannot drift apart again
|
|
3
|
+
// source so the test-DB push and migrations cannot drift apart again.
|
|
4
4
|
import { compareByCodepoint } from "../utils/index.js";
|
|
5
5
|
import { assertBackingTableSuperset, deriveEntityTableMeta, } from "./entity-table-meta.js";
|
|
6
6
|
import { enumerateFeatureTableSources } from "./feature-table-sources.js";
|
|
7
7
|
import { asEntityTableMeta } from "./query.js";
|
|
8
8
|
function canonicalColumnsKey(meta) {
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// als das Entity-Meta derselben Tabelle.
|
|
9
|
+
// Column identity independent of declaration order and object-key order.
|
|
10
|
+
// Indexes are excluded: a projection table (buildEntityTable without relations)
|
|
11
|
+
// legitimately has fewer FK indexes than the entity metadata for the same table.
|
|
13
12
|
return [...meta.columns]
|
|
14
13
|
.sort((a, b) => compareByCodepoint(a.name, b.name))
|
|
15
14
|
.map((c) => `${c.name}|${c.pgType}|${c.notNull}|${c.defaultSql ?? ""}|${c.primaryKey ?? false}|${c.identity ?? false}|${c.bigintJsMode ?? ""}`)
|
|
@@ -27,8 +26,8 @@ function registerPass1Meta(metas, byName, meta, origin) {
|
|
|
27
26
|
export function collectTableMetas(features) {
|
|
28
27
|
const metas = [];
|
|
29
28
|
const byName = new Map();
|
|
30
|
-
// Pass 1:
|
|
31
|
-
//
|
|
29
|
+
// Pass 1: canonical schema sources, matching the previous template behavior
|
|
30
|
+
// (same order, same deriveEntityTableMeta options).
|
|
32
31
|
for (const feature of features) {
|
|
33
32
|
for (const [name, ent] of Object.entries(feature.entities ?? {})) {
|
|
34
33
|
const fieldMeta = deriveEntityTableMeta(name, ent, { relations: feature.relations[name] });
|
|
@@ -52,10 +51,10 @@ export function collectTableMetas(features) {
|
|
|
52
51
|
registerPass1Meta(metas, byName, entry.meta, `storeTable "${entry.name}" (${feature.name})`);
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
|
-
// Pass 2: table-
|
|
56
|
-
//
|
|
57
|
-
// Feature A
|
|
58
|
-
//
|
|
54
|
+
// Pass 2: table-bearing registrations. Two passes ensure that an entity table
|
|
55
|
+
// from Feature B takes precedence over a same-named projection table from
|
|
56
|
+
// Feature A—entity metadata is the richer source because of FK indexes from
|
|
57
|
+
// relations.
|
|
59
58
|
for (const feature of features) {
|
|
60
59
|
for (const { table, origin } of enumerateFeatureTableSources(feature)) {
|
|
61
60
|
const meta = asEntityTableMeta(table);
|
|
@@ -4,6 +4,6 @@ import type { EntityDefinition } from "../engine/types/index.ts";
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function flattenCompoundTypes(payload: Record<string, unknown>, entity: EntityDefinition): Record<string, unknown>;
|
|
6
6
|
/**
|
|
7
|
-
* DB
|
|
7
|
+
* DB form (flat) → API form (combined). Called after every read.
|
|
8
8
|
*/
|
|
9
9
|
export declare function rehydrateCompoundTypes(row: Record<string, unknown>, entity: EntityDefinition): Record<string, unknown>;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
// Compound-
|
|
1
|
+
// Compound-type pipeline for the executor.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// DB
|
|
3
|
+
// A compound type is a field represented by ONE API object and MULTIPLE
|
|
4
|
+
// DB columns. Examples:
|
|
5
5
|
// - locatedTimestamp: { at, tz, utc } ↔ <name>Utc + <name>Tz
|
|
6
6
|
// - money: { amount, currency } ↔ <name> + <name>Currency
|
|
7
|
-
// - (
|
|
7
|
+
// - (upcoming) address: { street, zip, city } ↔ <name>Street + <name>Zip + <name>City
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
9
|
+
// Instead of nesting every helper in the executor at four different points,
|
|
10
|
+
// this function pipelines all compound-type conversions in one pass. Adding
|
|
11
|
+
// a new compound type requires extending only ONE place (this array), rather
|
|
12
|
+
// than every executor call.
|
|
13
13
|
import { flattenLocatedTimestamp, rehydrateLocatedTimestamp } from "./located-timestamp.js";
|
|
14
14
|
import { flattenMoney, rehydrateMoney } from "./money.js";
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// Order is irrelevant as long as the converters do not overlap (for example,
|
|
16
|
+
// money must not touch a field already produced by locatedTimestamp). They do
|
|
17
|
+
// not currently overlap—types are disjoint.
|
|
18
18
|
const FLATTENERS = [flattenLocatedTimestamp, flattenMoney];
|
|
19
19
|
const REHYDRATORS = [rehydrateLocatedTimestamp, rehydrateMoney];
|
|
20
20
|
/**
|
|
@@ -24,7 +24,7 @@ export function flattenCompoundTypes(payload, entity) {
|
|
|
24
24
|
return FLATTENERS.reduce((acc, fn) => fn(acc, entity), payload);
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* DB
|
|
27
|
+
* DB form (flat) → API form (combined). Called after every read.
|
|
28
28
|
*/
|
|
29
29
|
export function rehydrateCompoundTypes(row, entity) {
|
|
30
30
|
return REHYDRATORS.reduce((acc, fn) => fn(acc, entity), row);
|
package/dist/db/cursor.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// cursor-Reihenfolge (Default ist v7).
|
|
2
|
+
// Uses strings so UUIDs and integer auto-increment IDs can share the same cursor path.
|
|
3
|
+
// UUIDv7 preserves lexicographic monotonicity through its time-ordered prefix; UUIDv4 does not,
|
|
4
|
+
// so UUIDv4 produces incorrect cursor ordering. The default is v7.
|
|
6
5
|
export function encodeCursor(id) {
|
|
7
6
|
return Buffer.from(String(id)).toString("base64url");
|
|
8
7
|
}
|
package/dist/db/dialect.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { type ColumnHandle, type SchemaTable } from "@cosmicdrift/kumiko-types/s
|
|
|
3
3
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
4
4
|
import type { PgType } from "./entity-table-meta.ts";
|
|
5
5
|
export type { ColumnHandle, SchemaTable } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
6
|
-
export type TableColumns<_T =
|
|
7
|
-
export type SelectQuery =
|
|
6
|
+
export type TableColumns<_T = unknown> = SchemaTable;
|
|
7
|
+
export type SelectQuery = unknown;
|
|
8
8
|
export declare function pgTypeToSqlType(pgType: PgType): string;
|
|
9
9
|
type ColumnFinal = {
|
|
10
10
|
readonly sqlName: string;
|
package/dist/db/dialect.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
//
|
|
16
16
|
// Static import of the shared Temporal module, not the ambient global: the
|
|
17
17
|
// global may be absent (Hermes/Safari) or torn down in tests, and instantToDriver
|
|
18
|
-
// crashed on timestamptz writes without it
|
|
18
|
+
// crashed on timestamptz writes without it.
|
|
19
19
|
import { KUMIKO_COLUMNS_SYMBOL, KUMIKO_META_SYMBOL, KUMIKO_NAME_SYMBOL, } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
20
20
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
21
21
|
import { brandWritable, schemaTableName } from "./table-introspection.js";
|
|
@@ -190,7 +190,7 @@ export function instant(name, opts) {
|
|
|
190
190
|
// PG `date` — no time-of-day, no timezone. Calendar-day fields (invoice
|
|
191
191
|
// date, lease term) round-trip as Temporal.PlainDate, never through an
|
|
192
192
|
// Instant — that detour is exactly what made `type:"date"` TZ-dependent on
|
|
193
|
-
// both read and write
|
|
193
|
+
// both read and write.
|
|
194
194
|
export function plainDate(name) {
|
|
195
195
|
return buildColumn(name, "date");
|
|
196
196
|
}
|
|
@@ -251,7 +251,7 @@ export function primaryKey(opts) {
|
|
|
251
251
|
// Limits: no nested SqlExpression composition (drizzle's recursive
|
|
252
252
|
// `sql\`${other}\``) — schema-files use single-level expressions only.
|
|
253
253
|
// Unforgeable via JSON — a client-supplied jsonb value can fake `kind:
|
|
254
|
-
// "sql-expr"` but can never carry a Symbol, so isSqlExpression() (db/
|
|
254
|
+
// "sql-expr"` but can never carry a Symbol, so isSqlExpression() (db/driver-value-preparation.ts)
|
|
255
255
|
// can't be tricked into treating request data as a raw SQL literal. The same
|
|
256
256
|
// brand gate is enforced here in the schema DSL (sql`...` interpolation +
|
|
257
257
|
// literalDefault) and in entity-table-meta's sqlExpressionText — a duck-typed
|
package/dist/db/distinct-on.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OrderByClause } from "@cosmicdrift/kumiko-types/where-clause-types";
|
|
2
|
-
import type { TableInfo } from "./
|
|
2
|
+
import type { TableInfo } from "./table-info.ts";
|
|
3
3
|
type DistinctOnClause = {
|
|
4
4
|
/** Physical column names, unquoted. */
|
|
5
5
|
readonly columns: readonly string[];
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
|
+
import { createFallbackLogger } from "../logging/index.js";
|
|
3
|
+
import { parseJsonSafe } from "../utils/safe-json.js";
|
|
4
|
+
import { isTemporalInstant, isTemporalPlainDate } from "./temporal-value-guards.js";
|
|
5
|
+
// --- Value coercion at the driver boundary ------------------------------
|
|
6
|
+
//
|
|
7
|
+
// postgres-js returns timestamptz as JS Date (or ISO string depending on
|
|
8
|
+
// driver config). Framework contract says timestamptz surfaces as
|
|
9
|
+
// Temporal.Instant — without coercion every read hands callers a Date and
|
|
10
|
+
// downstream code that does .epochMilliseconds / .add(...) crashes.
|
|
11
|
+
//
|
|
12
|
+
// Symmetric on write: Temporal.Instant doesn't bind directly into postgres-js
|
|
13
|
+
// params — convert to ISO string when the column pgType is timestamptz.
|
|
14
|
+
// Postgres bigint → JS: safe integers become `number` (matches createBigIntField /
|
|
15
|
+
// drizzle mode:"number"); values outside ±2^53 stay `bigint` (money cents, raw
|
|
16
|
+
// unmanaged columns). Uses BigInt equality check so 9007199254740993n never
|
|
17
|
+
// silently rounds to a safe integer.
|
|
18
|
+
function coerceBigIntFromDriver(value) {
|
|
19
|
+
let bi;
|
|
20
|
+
if (typeof value === "string") {
|
|
21
|
+
try {
|
|
22
|
+
bi = BigInt(value);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
bi = value;
|
|
30
|
+
}
|
|
31
|
+
const min = BigInt(Number.MIN_SAFE_INTEGER);
|
|
32
|
+
const max = BigInt(Number.MAX_SAFE_INTEGER);
|
|
33
|
+
if (bi >= min && bi <= max) {
|
|
34
|
+
const n = Number(bi);
|
|
35
|
+
if (BigInt(n) === bi)
|
|
36
|
+
return n;
|
|
37
|
+
}
|
|
38
|
+
return bi;
|
|
39
|
+
}
|
|
40
|
+
function instantFromDriver(value) {
|
|
41
|
+
if (value === null || value === undefined)
|
|
42
|
+
return null;
|
|
43
|
+
if (isTemporalInstant(value))
|
|
44
|
+
return value;
|
|
45
|
+
// Number coercion via +date — equivalent to .getTime() without a Date-API
|
|
46
|
+
// method (guard-no-date-api). Date → epoch milliseconds, then to Temporal.Instant.
|
|
47
|
+
if (value instanceof Date)
|
|
48
|
+
return Temporal.Instant.fromEpochMilliseconds(+value);
|
|
49
|
+
if (typeof value === "string") {
|
|
50
|
+
try {
|
|
51
|
+
return Temporal.Instant.from(value);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === "number")
|
|
58
|
+
return Temporal.Instant.fromEpochMilliseconds(value);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
// Verified empirically: a `date`
|
|
62
|
+
// column comes back as a native JS Date, but ALWAYS anchored at UTC
|
|
63
|
+
// midnight regardless of process TZ — epochMilliseconds is stable across
|
|
64
|
+
// zones, only the local wall-clock read of that Date would drift the day
|
|
65
|
+
// (e.g. America/Los_Angeles reads back the 14th for a stored 15th). Routing
|
|
66
|
+
// through Instant→ZonedDateTimeISO("UTC")→PlainDate reads the calendar day
|
|
67
|
+
// back deterministically without ever touching a local Date getter — the
|
|
68
|
+
// same +value idiom instantFromDriver uses (guard-no-date-api: no
|
|
69
|
+
// `.getTime()`), just anchored to a fixed zone instead of process-local.
|
|
70
|
+
// A real `date` column always arrives as a UTC-midnight Date. Anything else means the column is
|
|
71
|
+
// still timestamptz (declared `date` before its ALTER ran); the UTC anchor then silently shifts
|
|
72
|
+
// the day, so say so once per column instead of returning the wrong day quietly.
|
|
73
|
+
const warnedNonMidnightDateColumns = new Set();
|
|
74
|
+
const MS_PER_DAY = 86_400_000;
|
|
75
|
+
const dateColumnDriftLog = createFallbackLogger("postgres-js");
|
|
76
|
+
function warnIfNotDateColumn(epochMs, column) {
|
|
77
|
+
// skip: midnight value is a correct date column, and an already-warned column is deduped
|
|
78
|
+
if (epochMs % MS_PER_DAY === 0 || warnedNonMidnightDateColumns.has(column))
|
|
79
|
+
return;
|
|
80
|
+
warnedNonMidnightDateColumns.add(column);
|
|
81
|
+
dateColumnDriftLog.warn(`column "${column}" is declared as date but returned a non-midnight timestamp; it is probably still timestamptz. Run the ALTER ... TYPE date migration, otherwise days can shift.`);
|
|
82
|
+
}
|
|
83
|
+
function plainDateFromDriver(value, column) {
|
|
84
|
+
if (value === null || value === undefined)
|
|
85
|
+
return null;
|
|
86
|
+
if (isTemporalPlainDate(value))
|
|
87
|
+
return value;
|
|
88
|
+
if (typeof value === "string") {
|
|
89
|
+
const isoDay = /^\d{4}-\d{2}-\d{2}/.exec(value)?.[0];
|
|
90
|
+
if (isoDay === undefined)
|
|
91
|
+
return null;
|
|
92
|
+
try {
|
|
93
|
+
return Temporal.PlainDate.from(isoDay);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (value instanceof Date) {
|
|
100
|
+
warnIfNotDateColumn(+value, column);
|
|
101
|
+
return Temporal.Instant.fromEpochMilliseconds(+value).toZonedDateTimeISO("UTC").toPlainDate();
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
function coerceTimestamptz(value) {
|
|
106
|
+
const instant = instantFromDriver(value);
|
|
107
|
+
return instant !== null ? instant : value;
|
|
108
|
+
}
|
|
109
|
+
function coerceDate(value, column) {
|
|
110
|
+
const day = plainDateFromDriver(value, column);
|
|
111
|
+
return day !== null ? day : value;
|
|
112
|
+
}
|
|
113
|
+
function coerceJsonb(value) {
|
|
114
|
+
return typeof value === "string" ? parseJsonSafe(value, value) : value;
|
|
115
|
+
}
|
|
116
|
+
function coerceBigIntColumn(value, column, info) {
|
|
117
|
+
if (typeof value !== "string" && typeof value !== "bigint")
|
|
118
|
+
return value;
|
|
119
|
+
if (info.bigintJsModeOf(column) === "number")
|
|
120
|
+
return coerceBigIntFromDriver(value);
|
|
121
|
+
if (typeof value === "string") {
|
|
122
|
+
try {
|
|
123
|
+
return BigInt(value);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// leave as string on parse error
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
function numberUnlessNaN(value) {
|
|
133
|
+
if (typeof value !== "string" && typeof value !== "bigint")
|
|
134
|
+
return value;
|
|
135
|
+
const n = Number(value);
|
|
136
|
+
return Number.isNaN(n) ? value : n;
|
|
137
|
+
}
|
|
138
|
+
// Keyed by the driver's pgType string, not PgType: legacy tables report int4/smallint/int2.
|
|
139
|
+
const DRIVER_VALUE_COERCERS = new Map([
|
|
140
|
+
["timestamptz", coerceTimestamptz],
|
|
141
|
+
["timestamptz(3)", coerceTimestamptz],
|
|
142
|
+
["date", coerceDate],
|
|
143
|
+
["jsonb", coerceJsonb],
|
|
144
|
+
["bigint", coerceBigIntColumn],
|
|
145
|
+
["bigserial", coerceBigIntColumn],
|
|
146
|
+
// Drivers may return int4 as bigint or numeric string.
|
|
147
|
+
// Drizzle coerced to number for numberField columns — match that.
|
|
148
|
+
["integer", numberUnlessNaN],
|
|
149
|
+
["int4", numberUnlessNaN],
|
|
150
|
+
["smallint", numberUnlessNaN],
|
|
151
|
+
["int2", numberUnlessNaN],
|
|
152
|
+
]);
|
|
153
|
+
// pg returns numeric(p,s) as a string to preserve precision; the
|
|
154
|
+
// decimal-field contract surfaces it as JS number (safe ≤ 2^53). A
|
|
155
|
+
// non-numeric string would be DB corruption — leave it untouched so it
|
|
156
|
+
// surfaces loud downstream rather than becoming a silent NaN.
|
|
157
|
+
function driverValueCoercerFor(pgType) {
|
|
158
|
+
if (pgType === undefined)
|
|
159
|
+
return undefined;
|
|
160
|
+
const coercer = DRIVER_VALUE_COERCERS.get(pgType);
|
|
161
|
+
if (coercer)
|
|
162
|
+
return coercer;
|
|
163
|
+
return pgType.startsWith("numeric") ? numberUnlessNaN : undefined;
|
|
164
|
+
}
|
|
165
|
+
export function coerceDriverRow(row, info) {
|
|
166
|
+
const out = {};
|
|
167
|
+
let changed = false;
|
|
168
|
+
for (const key of Object.keys(row)) {
|
|
169
|
+
// Blind-index columns are pure lookup artifacts — the
|
|
170
|
+
// deterministic HMAC must never leak into caller/API responses.
|
|
171
|
+
if ((key.endsWith("_bidx") || key.endsWith("Bidx")) && info.hasColumn(key)) {
|
|
172
|
+
changed = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const value = row[key];
|
|
176
|
+
const coercer = driverValueCoercerFor(info.pgTypeOf(key));
|
|
177
|
+
const coerced = coercer ? coercer(value, key, info) : value;
|
|
178
|
+
const fieldName = info.fieldOf(key);
|
|
179
|
+
if (fieldName !== key)
|
|
180
|
+
changed = true;
|
|
181
|
+
if (coerced !== value)
|
|
182
|
+
changed = true;
|
|
183
|
+
out[fieldName] = coerced;
|
|
184
|
+
}
|
|
185
|
+
return changed ? out : row;
|
|
186
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
|
+
export type PreparedValue = {
|
|
3
|
+
readonly kind: "param";
|
|
4
|
+
readonly sql: string;
|
|
5
|
+
readonly bound: unknown;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: "literal";
|
|
8
|
+
readonly literal: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function isSqlExpression(v: unknown): v is {
|
|
11
|
+
kind: "sql-expr";
|
|
12
|
+
text: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function prepareJsonbValue(value: unknown): PreparedValue | undefined;
|
|
15
|
+
export declare function instantToTimestamptzParam(instant: Temporal.Instant): string;
|
|
16
|
+
export declare function prepareValue(value: unknown, pgType: string | undefined): PreparedValue;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
|
+
import { isPiiCiphertext } from "../crypto/pii-ciphertext-format.js";
|
|
3
|
+
import { SQL_EXPR_BRAND } from "./dialect.js";
|
|
4
|
+
import { isTemporalInstant, isTemporalPlainDate, isTemporalPlainDateTimeLike, } from "./temporal-value-guards.js";
|
|
5
|
+
// Checks the brand Symbol, not the `kind` string — a client-supplied jsonb
|
|
6
|
+
// value can fake `kind: "sql-expr"` over JSON but can never carry a Symbol,
|
|
7
|
+
// so request data can't be smuggled in as a raw SQL literal.
|
|
8
|
+
export function isSqlExpression(v) {
|
|
9
|
+
return typeof v === "object" && v !== null && SQL_EXPR_BRAND in v;
|
|
10
|
+
}
|
|
11
|
+
// A `date` column takes a plain "yyyy-mm-dd" string (or PlainDate.toString())
|
|
12
|
+
// as-is — no Instant detour. Postgres parses a DATE literal without ever
|
|
13
|
+
// consulting the session timezone, so this is TZ-independent by construction
|
|
14
|
+
// (verified empirically — see plainDateFromDriver above for the read side).
|
|
15
|
+
// Compat cushion: code (this field used to be an Instant) may still
|
|
16
|
+
// hand a Temporal.Instant into a where-filter or write on a `date` column —
|
|
17
|
+
// same UTC anchor as plainDateFromDriver's read side, so a filter built with
|
|
18
|
+
// `Temporal.Now.instant()` keeps working instead of binding a raw Instant the
|
|
19
|
+
// driver can't serialize.
|
|
20
|
+
function prepareDateValue(value) {
|
|
21
|
+
if (isTemporalPlainDate(value)) {
|
|
22
|
+
return { kind: "param", sql: "", bound: value.toString() };
|
|
23
|
+
}
|
|
24
|
+
if (isTemporalPlainDateTimeLike(value)) {
|
|
25
|
+
return { kind: "param", sql: "", bound: value.toPlainDate().toString() };
|
|
26
|
+
}
|
|
27
|
+
if (isTemporalInstant(value)) {
|
|
28
|
+
const day = value.toZonedDateTimeISO("UTC").toPlainDate();
|
|
29
|
+
return { kind: "param", sql: "", bound: day.toString() };
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
// structured values bind directly with an ::jsonb cast; JSON.stringify first
|
|
34
|
+
// would produce a JSON-string scalar instead of the structured value.
|
|
35
|
+
export function prepareJsonbValue(value) {
|
|
36
|
+
if (typeof value === "boolean") {
|
|
37
|
+
return { kind: "param", sql: "::text::jsonb", bound: JSON.stringify(value) };
|
|
38
|
+
}
|
|
39
|
+
if (typeof value !== "object" || isTemporalInstant(value)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
if (!Array.isArray(value)) {
|
|
43
|
+
return { kind: "param", sql: "::jsonb", bound: value };
|
|
44
|
+
}
|
|
45
|
+
// All-boolean arrays are inferred as boolean[] by postgres-js; route via text::jsonb.
|
|
46
|
+
if (value.length > 0 && value.every((entry) => typeof entry === "boolean")) {
|
|
47
|
+
return { kind: "param", sql: "::text::jsonb", bound: JSON.stringify(value) };
|
|
48
|
+
}
|
|
49
|
+
return { kind: "param", sql: "::jsonb", bound: value };
|
|
50
|
+
}
|
|
51
|
+
const INTEGER_PG_TYPES = new Set([
|
|
52
|
+
"integer",
|
|
53
|
+
"int4",
|
|
54
|
+
"smallint",
|
|
55
|
+
"int2",
|
|
56
|
+
"bigint",
|
|
57
|
+
"int8",
|
|
58
|
+
"serial",
|
|
59
|
+
"bigserial",
|
|
60
|
+
]);
|
|
61
|
+
// Postgres rounds sub-millisecond params into timestamptz(3); truncating to ms here keeps the
|
|
62
|
+
// stored instant identical across drivers and equal to what postgres.js sends via Date.
|
|
63
|
+
export function instantToTimestamptzParam(instant) {
|
|
64
|
+
return Temporal.Instant.fromEpochMilliseconds(instant.epochMilliseconds).toString();
|
|
65
|
+
}
|
|
66
|
+
function prepareDriverNeutralScalar(value, pgType) {
|
|
67
|
+
if ((pgType === "timestamptz" || pgType === "timestamptz(3)") && isTemporalInstant(value)) {
|
|
68
|
+
return { kind: "param", sql: "", bound: instantToTimestamptzParam(value) };
|
|
69
|
+
}
|
|
70
|
+
// postgres.js sends text, so Postgres rejects 3.5 for an integer column instead of rounding it.
|
|
71
|
+
if (typeof value === "number" && pgType !== undefined && INTEGER_PG_TYPES.has(pgType)) {
|
|
72
|
+
return { kind: "param", sql: "", bound: String(value) };
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
export function prepareValue(value, pgType) {
|
|
77
|
+
if (isSqlExpression(value)) {
|
|
78
|
+
return { kind: "literal", literal: value.text };
|
|
79
|
+
}
|
|
80
|
+
// A PII-encrypted jsonb field is a JSON string scalar; a raw string bound to
|
|
81
|
+
// ::jsonb would be parsed as JSON text (or double-encoded).
|
|
82
|
+
if (pgType === "jsonb" && isPiiCiphertext(value)) {
|
|
83
|
+
return { kind: "param", sql: "::text::jsonb", bound: JSON.stringify(value) };
|
|
84
|
+
}
|
|
85
|
+
if (pgType === "jsonb" && value !== null) {
|
|
86
|
+
const prepared = prepareJsonbValue(value);
|
|
87
|
+
if (prepared)
|
|
88
|
+
return prepared;
|
|
89
|
+
}
|
|
90
|
+
const scalar = prepareDriverNeutralScalar(value, pgType);
|
|
91
|
+
if (scalar)
|
|
92
|
+
return scalar;
|
|
93
|
+
if (pgType === "date") {
|
|
94
|
+
const prepared = prepareDateValue(value);
|
|
95
|
+
if (prepared)
|
|
96
|
+
return prepared;
|
|
97
|
+
}
|
|
98
|
+
return { kind: "param", sql: "", bound: value };
|
|
99
|
+
}
|
package/dist/db/eagerload.d.ts
CHANGED
|
@@ -15,9 +15,9 @@ export type EagerLoadViewer = {
|
|
|
15
15
|
readonly user: SessionUser;
|
|
16
16
|
readonly parentVisibility: ParentVisibilityOption;
|
|
17
17
|
};
|
|
18
|
-
/**
|
|
19
|
-
*
|
|
18
|
+
/** Eagerloads a list of rows without mutating them; returns shallow copies
|
|
19
|
+
* with an added `_refs` property. */
|
|
20
20
|
export declare function enrichWithReferences(rows: ReadonlyArray<Record<string, unknown>>, entity: EntityDefinition, resolveEntity: EagerLoadEntityResolver, db: TenantDb, viewer: EagerLoadViewer): Promise<Array<Record<string, unknown>>>;
|
|
21
|
-
/** Single-
|
|
21
|
+
/** Single-row variant for detail calls. */
|
|
22
22
|
export declare function enrichRowWithReferences(row: Record<string, unknown>, entity: EntityDefinition, resolveEntity: EagerLoadEntityResolver, db: TenantDb, viewer: EagerLoadViewer): Promise<Record<string, unknown>>;
|
|
23
23
|
export {};
|