@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
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Migration runner: applies checked-in SQL files to a database.
|
|
2
|
+
// Replaces `drizzle-kit migrate` as a runtime component.
|
|
3
3
|
//
|
|
4
|
-
// **NO-MAGIC-ON-DATA
|
|
5
|
-
// -
|
|
6
|
-
// -
|
|
7
|
-
// -
|
|
8
|
-
// `kumiko migrate apply`
|
|
9
|
-
// - Schema
|
|
4
|
+
// **NO-MAGIC-ON-DATA core principle** (see drizzle-replacement.md):
|
|
5
|
+
// - The runner reads ONLY checked-in `<dir>/*.sql` files.
|
|
6
|
+
// - It does not reach back to EntityDefinition at runtime.
|
|
7
|
+
// - It does not auto-apply on app boot — a deploy script or init container
|
|
8
|
+
// invokes `kumiko migrate apply` as a separate step.
|
|
9
|
+
// - Schema drift is detected fail-loudly through checksum mismatches.
|
|
10
10
|
//
|
|
11
|
-
// Tracking
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
11
|
+
// Tracking table: `_kumiko_migrations(id, applied_at, checksum)`. `id` is the
|
|
12
|
+
// filename without the `.sql` suffix. The checksum is the sha256 hash of the
|
|
13
|
+
// file content at apply time. If an already-applied file is edited, the next
|
|
14
|
+
// apply fails with a clear error (production databases contain the old SQL —
|
|
15
|
+
// editing it makes production state inconsistent with committed state).
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
17
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
20
20
|
import { asRawClient, hasBegin, } from "./query.js";
|
|
21
|
-
// Advisory-lock key (random 32-bit
|
|
22
|
-
//
|
|
23
|
-
//
|
|
21
|
+
// Advisory-lock key (random 32-bit integer, stable). Prevents two Pods
|
|
22
|
+
// booting at the same time from both running migrate apply (the second
|
|
23
|
+
// blocks until the first finishes).
|
|
24
24
|
export const MIGRATION_ADVISORY_LOCK_KEY = 0x6b756d69; // "kumi" in ASCII
|
|
25
25
|
const MIGRATIONS_TABLE_DDL = `
|
|
26
26
|
CREATE TABLE IF NOT EXISTS "_kumiko_migrations" (
|
|
@@ -147,7 +147,7 @@ function sha256Hex(content) {
|
|
|
147
147
|
// returns Migration[] with id + checksum + statements. `preprocess` is an
|
|
148
148
|
// optional hook applied to each file's raw content before splitting/hashing
|
|
149
149
|
// — used by replayMigrationsDir to expand its DESTRUCTIVE-marker comments
|
|
150
|
-
// without a second, drifting copy of this file-discovery logic
|
|
150
|
+
// without a second, drifting copy of this file-discovery logic.
|
|
151
151
|
export function loadMigrationsFromDir(dir, preprocess) {
|
|
152
152
|
const files = readdirSync(dir)
|
|
153
153
|
.filter((f) => f.endsWith(".sql"))
|
|
@@ -230,9 +230,9 @@ export class MigrationChecksumMismatchError extends Error {
|
|
|
230
230
|
this.name = "MigrationChecksumMismatchError";
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
|
-
//
|
|
234
|
-
// _kumiko_migrations
|
|
235
|
-
//
|
|
233
|
+
// Applies pending migrations. Idempotent: already-applied migrations are
|
|
234
|
+
// skipped via the _kumiko_migrations lookup. Checksum mismatches fail loudly.
|
|
235
|
+
// The advisory lock prevents concurrent-apply races.
|
|
236
236
|
export async function runMigrations(db, migrations) {
|
|
237
237
|
// Session-level advisory lock and per-migration BEGIN/COMMIT must share one
|
|
238
238
|
// session. Pool calls may land on different connections (the pool round-robins),
|
|
@@ -256,9 +256,9 @@ export async function runMigrations(db, migrations) {
|
|
|
256
256
|
skippedIds.push(m.id);
|
|
257
257
|
continue;
|
|
258
258
|
}
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
// stuck in
|
|
259
|
+
// Applies the file content and INSERT tracking row in one transaction. If a
|
|
260
|
+
// statement fails, the rollback includes the tracking row, so no partial
|
|
261
|
+
// apply remains stuck in the books.
|
|
262
262
|
await conn.unsafe("BEGIN");
|
|
263
263
|
try {
|
|
264
264
|
for (const stmt of m.statements) {
|
|
@@ -280,7 +280,10 @@ export async function runMigrations(db, migrations) {
|
|
|
280
280
|
return { applied: appliedIds, skipped: skippedIds };
|
|
281
281
|
}
|
|
282
282
|
finally {
|
|
283
|
-
|
|
283
|
+
// A dropped session already released its lock; failing here would mask the migration error.
|
|
284
|
+
await conn
|
|
285
|
+
.unsafe(`SELECT pg_advisory_unlock(${MIGRATION_ADVISORY_LOCK_KEY})`)
|
|
286
|
+
.catch(() => undefined);
|
|
284
287
|
}
|
|
285
288
|
}
|
|
286
289
|
finally {
|
|
@@ -289,8 +292,8 @@ export async function runMigrations(db, migrations) {
|
|
|
289
292
|
await conn.release();
|
|
290
293
|
}
|
|
291
294
|
}
|
|
292
|
-
// Convenience:
|
|
293
|
-
// CLI
|
|
295
|
+
// Convenience: loads and runs migrations in one call. Used by the
|
|
296
|
+
// `kumiko migrate apply` CLI command and by test setup helpers.
|
|
294
297
|
export async function runMigrationsFromDir(db, dir) {
|
|
295
298
|
const migrations = loadMigrationsFromDir(dir);
|
|
296
299
|
return runMigrations(db, migrations);
|
package/dist/db/money.d.ts
CHANGED
|
@@ -15,17 +15,17 @@ export type MoneyRead = {
|
|
|
15
15
|
* @deprecated Removed in 0.390.0. Use `amountScaled` — this name implies ISO-4217 minor units
|
|
16
16
|
* (cents), which is wrong once a currency needing a different scale than
|
|
17
17
|
* the current flat MINOR_UNIT_SCALE=100 lands (e.g. JPY, 0 decimals).
|
|
18
|
-
* Alias of `amountScaled`, kept until
|
|
19
|
-
* consumers off it.
|
|
18
|
+
* Alias of `amountScaled`, kept until the consumers migrate off it.
|
|
20
19
|
*/
|
|
21
20
|
readonly amountMinor: number;
|
|
22
21
|
};
|
|
23
22
|
/**
|
|
24
|
-
*
|
|
23
|
+
* Rehydrate two flat DB columns into a combined `{ amount, currency }` value.
|
|
25
24
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Throws loudly for corrupt DB values (for example, a string that cannot be
|
|
26
|
+
* converted to a number), because silent data loss would hide a bug. A
|
|
27
|
+
* NULL/undefined amount removes the field from the output.
|
|
28
28
|
*
|
|
29
|
-
* Pure —
|
|
29
|
+
* Pure — does not mutate its input.
|
|
30
30
|
*/
|
|
31
31
|
export declare function rehydrateMoney(row: Record<string, unknown>, entity: EntityDefinition): Record<string, unknown>;
|
package/dist/db/money.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Automatically convert money fields in the DB layer.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// API
|
|
5
|
-
// DB
|
|
6
|
-
// Read
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
3
|
+
// Contract (see also db/located-timestamp.ts — the same compound-type pattern):
|
|
4
|
+
// API form: { amount, currency } | number — amount in MAJOR units (56799.16 EUR)
|
|
5
|
+
// DB form: <name> BIGINT (minor units, e.g. cents) + <name>Currency TEXT
|
|
6
|
+
// Read form: { amount, currency, amountScaled } — amount in MAJOR units again,
|
|
7
|
+
// amountScaled sits alongside as the exact integer value in
|
|
8
|
+
// MINOR_UNIT_SCALE units for callers that need scale-exact
|
|
9
|
+
// comparisons (e.g. invoice sums) instead of round-tripping the
|
|
10
|
+
// float amount through /100·*100.
|
|
11
11
|
// `amountMinor` stays as a @deprecated alias of the same
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// value until the remaining consumers
|
|
13
|
+
// (renderer/web/primitives/index.tsx,
|
|
14
|
+
// renderer/components/render-field.tsx) migrate off the old name.
|
|
15
15
|
//
|
|
16
16
|
// table-builder.ts's moneyAmount column has always documented BIGINT as
|
|
17
17
|
// "the integer minor unit" — this file used to just pass the API amount
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
// pass/receive ordinary decimal amounts; DB storage stays exact-integer
|
|
24
24
|
// cents (no float drift in SUM()/aggregate queries).
|
|
25
25
|
//
|
|
26
|
-
//
|
|
27
|
-
// pre-Stufe-3
|
|
28
|
-
//
|
|
26
|
+
// Permissive insert: a primitive number is accepted as amount (legacy from
|
|
27
|
+
// pre-Stufe-3 samples). Currency then falls back to entity.defaultCurrency
|
|
28
|
+
// (or DEFAULT_CURRENCIES[0] = "EUR" as the framework fallback).
|
|
29
29
|
//
|
|
30
|
-
//
|
|
31
|
-
// amount
|
|
32
|
-
//
|
|
30
|
+
// Unlike locatedTimestamp, we retain the field name `<name>` as the
|
|
31
|
+
// amount column (legacy DB convention for money — `SUM(buying_price)` remains
|
|
32
|
+
// idiomatic). `<name>Currency` is the additional column.
|
|
33
33
|
import { MINOR_UNIT_SCALE, toMinorUnits } from "../engine/minor-units.js";
|
|
34
34
|
import { DEFAULT_CURRENCIES } from "../engine/types/index.js";
|
|
35
35
|
const FRAMEWORK_DEFAULT_CURRENCY = DEFAULT_CURRENCIES[0]; // "EUR"
|
|
@@ -46,9 +46,9 @@ function toMajorUnits(amountScaled) {
|
|
|
46
46
|
// scaled the same way. `applyTotalsMatchRefinements` (schema-builder.ts)
|
|
47
47
|
// uses this internally for `EmbeddedFieldDef.totalsMatch`; exported so a
|
|
48
48
|
// custom write-handler doing its own total check doesn't have to re-derive
|
|
49
|
-
// this unwrap-and-scale step by hand
|
|
50
|
-
//
|
|
51
|
-
//
|
|
49
|
+
// this unwrap-and-scale step by hand. A hand-rolled comparison of a raw
|
|
50
|
+
// `{amount}` against a raw minor-unit row sum is exactly what silently fails
|
|
51
|
+
// 100× off.
|
|
52
52
|
export function moneyPayloadToMinorUnits(raw) {
|
|
53
53
|
if (typeof raw === "number")
|
|
54
54
|
return toMinorUnits(raw);
|
|
@@ -81,7 +81,7 @@ export function flattenMoney(payload, entity) {
|
|
|
81
81
|
}
|
|
82
82
|
else if (typeof raw === "number") {
|
|
83
83
|
amount = raw;
|
|
84
|
-
//
|
|
84
|
+
// An explicit currency key in the payload overrides the default fallback.
|
|
85
85
|
const explicitCurrency = result[`${name}Currency`];
|
|
86
86
|
currency = typeof explicitCurrency === "string" ? explicitCurrency : fallbackCurrency;
|
|
87
87
|
}
|
|
@@ -95,12 +95,13 @@ export function flattenMoney(payload, entity) {
|
|
|
95
95
|
return result;
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
98
|
+
* Rehydrate two flat DB columns into a combined `{ amount, currency }` value.
|
|
99
99
|
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
100
|
+
* Throws loudly for corrupt DB values (for example, a string that cannot be
|
|
101
|
+
* converted to a number), because silent data loss would hide a bug. A
|
|
102
|
+
* NULL/undefined amount removes the field from the output.
|
|
102
103
|
*
|
|
103
|
-
* Pure —
|
|
104
|
+
* Pure — does not mutate its input.
|
|
104
105
|
*/
|
|
105
106
|
export function rehydrateMoney(row, entity) {
|
|
106
107
|
const result = { ...row };
|
|
@@ -126,7 +127,8 @@ export function rehydrateMoney(row, entity) {
|
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
else if (typeof amountRaw === "string" && amountRaw !== "") {
|
|
129
|
-
// PG
|
|
130
|
+
// The PG driver sometimes returns BIGINT as a string (when it exceeds the
|
|
131
|
+
// safe integer range of 2^53).
|
|
130
132
|
amountScaled = Number(amountRaw);
|
|
131
133
|
if (!Number.isSafeInteger(amountScaled)) {
|
|
132
134
|
throw new Error(`rehydrateMoney: field "${name}" amount string "${amountRaw}" is not a safe integer — DB corruption?`);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
// One-time backfill for pre-KMS plaintext PII in kumiko_events
|
|
1
|
+
// One-time backfill for pre-KMS plaintext PII in kumiko_events.
|
|
2
2
|
//
|
|
3
|
-
// Crypto-shredding
|
|
3
|
+
// Crypto-shredding only covers NEW writes — events appended
|
|
4
4
|
// before a KMS was configured still carry plaintext (user.created email,
|
|
5
5
|
// delivery attempt recipientAddress, job payloads). This tool re-encrypts
|
|
6
6
|
// them in place, per field, under the owning subject's DEK:
|
|
7
7
|
//
|
|
8
8
|
// - entity lifecycle events (<entity>.created/updated/deleted/forgotten/
|
|
9
9
|
// restored) for every entity with PII subject annotations — generic over
|
|
10
|
-
// subject kind (user/tenant/record
|
|
10
|
+
// subject kind (user/tenant/record), since it goes through the
|
|
11
11
|
// same collectPiiEncryptedFields/resolveSubjectForField the live write
|
|
12
12
|
// path uses
|
|
13
13
|
// - custom events from the event-PII catalog (r.defineEvent piiFields) —
|
|
14
14
|
// user/tenant/self subjects, via the same resolveEventSubject the live
|
|
15
|
-
// append path uses
|
|
15
|
+
// append path uses
|
|
16
16
|
//
|
|
17
17
|
// Already-forgotten subjects must NOT get a fresh key minted for their old
|
|
18
18
|
// plaintext — three erased-detection layers write [[erased]] instead:
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// Owner resolution for entity-lifecycle events. The live write path
|
|
29
29
|
// (event-store-executor-write.ts) resolves the same chain via
|
|
30
30
|
// subject-resolver.ts: row owner, then the owner the same event sets, then
|
|
31
|
-
// the field's whenAbsent
|
|
31
|
+
// the field's whenAbsent. Backfill adds two opt-in stages and
|
|
32
32
|
// delays whenAbsent behind the projection so a known owner is never shadowed:
|
|
33
33
|
// 1. payload — the event section (plus the owner the same event sets) names
|
|
34
34
|
// the owner field; whenAbsent is not applied yet.
|
package/dist/db/queries/ddl.js
CHANGED
|
@@ -12,7 +12,7 @@ export async function alterTableAddColumn(db, tableName, columnName, columnType,
|
|
|
12
12
|
// throwing when a column already exists with a different shape than
|
|
13
13
|
// EntityTableMeta expects — that's how it surfaces drift. Callers that
|
|
14
14
|
// just need an idempotent, race-safe backfill (e.g. event-consumer-state's
|
|
15
|
-
// multi-instance boot path
|
|
15
|
+
// multi-instance boot path) opt in explicitly.
|
|
16
16
|
ifNotExists = false) {
|
|
17
17
|
await asRawClient(db).unsafe(`ALTER TABLE ${quoteTableIdent(tableName)} ADD COLUMN ${ifNotExists ? "IF NOT EXISTS " : ""}${quoteTableIdent(columnName)} ${columnType}${defaultClause}${notNull}`);
|
|
18
18
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnyDb } from "../query.ts";
|
|
2
|
-
/**
|
|
2
|
+
/** Stable lock key for pg_advisory_xact_lock across multi-replica seed boots. */
|
|
3
3
|
export declare const ES_OPS_ADVISORY_LOCK_KEY = 1702063984;
|
|
4
4
|
export declare function acquireEsOpsAdvisoryLock(db: AnyDb): Promise<void>;
|
|
5
5
|
export declare function esOperationExists(db: AnyDb, operationId: string): Promise<boolean>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { asRawClient } from "../query.js";
|
|
2
|
-
/**
|
|
2
|
+
/** Stable lock key for pg_advisory_xact_lock across multi-replica seed boots. */
|
|
3
3
|
export const ES_OPS_ADVISORY_LOCK_KEY = 0x65_73_6f_70; // 'esop'
|
|
4
4
|
export async function acquireEsOpsAdvisoryLock(db) {
|
|
5
5
|
await asRawClient(db).unsafe(`SELECT pg_advisory_xact_lock($1)`, [ES_OPS_ADVISORY_LOCK_KEY]);
|
|
@@ -24,7 +24,7 @@ export async function lockEventConsumersShareMode(db) {
|
|
|
24
24
|
await asRawClient(db).unsafe(`LOCK TABLE "kumiko_event_consumers" IN SHARE MODE`);
|
|
25
25
|
}
|
|
26
26
|
// The MAX(id) horizon plus every currently-open id gap below it, read from a
|
|
27
|
-
// single statement snapshot. Used both to seed startFrom "now"
|
|
27
|
+
// single statement snapshot. Used both to seed startFrom "now" and to
|
|
28
28
|
// seed an MSP rebuild's handoff to the live dispatcher (msp-rebuild.ts).
|
|
29
29
|
//
|
|
30
30
|
// Scan lower bound: the consumer with the highest cursor already tracks every
|
|
@@ -84,7 +84,7 @@ export async function selectEventIdHorizonWithMissingRanges(db) {
|
|
|
84
84
|
// WithMissingRanges): a transaction holding a lower id can still be
|
|
85
85
|
// in-flight at boot, and without seeding those gaps its event would commit
|
|
86
86
|
// after the cursor already sits above it, so `id > cursor` would never
|
|
87
|
-
// deliver it
|
|
87
|
+
// deliver it. The cheap existence check skips the scan entirely for
|
|
88
88
|
// the (overwhelmingly common) case of an already-registered consumer.
|
|
89
89
|
// ON CONFLICT DO NOTHING keeps both branches safe for an existing row: the
|
|
90
90
|
// subquery only ever affects the row this statement inserts.
|
|
@@ -144,7 +144,7 @@ export async function markConsumerProcessing(db, name, instanceId) {
|
|
|
144
144
|
// repeated infra blips (the exact case this backoff exists for) dead-letter
|
|
145
145
|
// the consumer on the next real handler failure instead of after
|
|
146
146
|
// maxAttempts. Visibility is covered by last_error, updated_at, and the
|
|
147
|
-
// caller's
|
|
147
|
+
// caller's error log — status and the cursor are left as-is too, since
|
|
148
148
|
// we don't know at this point whether the consumer should be "dead" (that's
|
|
149
149
|
// the deliverEvents/maxAttempts contract, which never ran this pass).
|
|
150
150
|
//
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { createFallbackLogger } from "../../logging/utils.js";
|
|
1
2
|
import { constraintOf, isLockNotAvailable, isTableAlreadyExists, isUniqueViolation, } from "../pg-error.js";
|
|
2
3
|
import { asRawClient, runInNewTransaction, runnerCanBeginTransaction, unsafeReadRetrying, } from "../query.js";
|
|
3
4
|
import { withoutStatementTimeout } from "../statement-timeout.js";
|
|
4
5
|
import { toPostgresArrayLiteral } from "./postgres-array-literal.js";
|
|
6
|
+
const log = createFallbackLogger("event-store");
|
|
5
7
|
// Gap-finality (event-dispatcher pending_gaps) needs every holder of an
|
|
6
8
|
// event id to already have a *real* xact id by the time it inserts — Postgres
|
|
7
9
|
// assigns those lazily on first write, so a bare INSERT alone doesn't
|
|
@@ -81,9 +83,9 @@ async function buildIdempotencyKeyIndex(db) {
|
|
|
81
83
|
// skip: sibling pod already built a valid index — this race loser is done.
|
|
82
84
|
if ((await indexValidity(client)) === true)
|
|
83
85
|
return;
|
|
84
|
-
|
|
86
|
+
log.warn(`ensureIdempotencyKeyIndex: backed off on a "benign" race but ` +
|
|
85
87
|
`"events_idempotency_uq" is still missing/invalid afterward — likely a ` +
|
|
86
|
-
`lock_timeout during CREATE INDEX CONCURRENTLY, not an actual winner/loser race
|
|
88
|
+
`lock_timeout during CREATE INDEX CONCURRENTLY, not an actual winner/loser race.`, { err: e });
|
|
87
89
|
throw e;
|
|
88
90
|
}
|
|
89
91
|
throw duplicateIdempotencyKeyErrorOr(e);
|
|
@@ -14,7 +14,7 @@ export async function markProjectionRebuilding(db, projectionName) {
|
|
|
14
14
|
export async function selectEventsForProjectionRebuildBatch(db, aggregateTypes, eventTypes, afterId, limit) {
|
|
15
15
|
// Archived streams don't replay (Marten-aligned): their aggregates are
|
|
16
16
|
// frozen ops-tombstones — replaying them would resurrect rows (or, for
|
|
17
|
-
// stranded duplicate-aggregates
|
|
17
|
+
// stranded duplicate-aggregates, collide on unique indexes).
|
|
18
18
|
return unsafeReadRetrying(db, `SELECT * FROM "kumiko_events" e
|
|
19
19
|
WHERE e."aggregate_type" = ANY($1::text[])
|
|
20
20
|
AND e."type" = ANY($2::text[])
|
|
@@ -29,9 +29,9 @@ export async function selectEventsForProjectionRebuildBatch(db, aggregateTypes,
|
|
|
29
29
|
// Total subscribed events in the log — same source/type filter as
|
|
30
30
|
// selectEventsForProjectionRebuildBatch, no id cursor. Under the cutover fence
|
|
31
31
|
// this is the ground-truth count the rebuild must have applied; a shortfall
|
|
32
|
-
// means a lower-id event committed late and the id-cursor leapt past it
|
|
32
|
+
// means a lower-id event committed late and the id-cursor leapt past it.
|
|
33
33
|
export async function countSubscribedEvents(db, aggregateTypes, eventTypes) {
|
|
34
|
-
// Same archived-streams exclusion as the batch query — the
|
|
34
|
+
// Same archived-streams exclusion as the batch query — the recompute
|
|
35
35
|
// compares this count against applied events; a filter mismatch would make
|
|
36
36
|
// every rebuild with an archived stream loop the full-re-replay forever.
|
|
37
37
|
const rows = await unsafeReadRetrying(db, `SELECT count(*)::bigint AS n FROM "kumiko_events" e
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { AnyDb } from "../query.ts";
|
|
2
2
|
/** Escape hatch for caller-built SQL that may write (or lock). No closed-connection retry —
|
|
3
|
-
* retrying an ambiguous write risks double-apply
|
|
3
|
+
* retrying an ambiguous write risks double-apply. Prefer {@link executeRawQueryRead}
|
|
4
4
|
* for SELECT-only paths. */
|
|
5
5
|
export declare function executeRawQuery<T = Record<string, unknown>>(db: AnyDb, sqlText: string, params?: readonly unknown[]): Promise<readonly T[]>;
|
|
6
|
-
/** SELECT-only escape hatch with the
|
|
6
|
+
/** SELECT-only escape hatch with the closed-connection retry. Do not pass
|
|
7
7
|
* INSERT/UPDATE/DELETE — retry re-executes the statement. */
|
|
8
8
|
export declare function executeRawQueryRead<T = Record<string, unknown>>(db: AnyDb, sqlText: string, params?: readonly unknown[]): Promise<readonly T[]>;
|
|
9
9
|
export declare function pingDatabase(db: AnyDb): Promise<void>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { asRawClient, unsafeReadRetrying } from "../query.js";
|
|
2
2
|
/** Escape hatch for caller-built SQL that may write (or lock). No closed-connection retry —
|
|
3
|
-
* retrying an ambiguous write risks double-apply
|
|
3
|
+
* retrying an ambiguous write risks double-apply. Prefer {@link executeRawQueryRead}
|
|
4
4
|
* for SELECT-only paths. */
|
|
5
5
|
export async function executeRawQuery(db, sqlText, params = []) {
|
|
6
6
|
return await asRawClient(db).unsafe(sqlText, params);
|
|
7
7
|
}
|
|
8
|
-
/** SELECT-only escape hatch with the
|
|
8
|
+
/** SELECT-only escape hatch with the closed-connection retry. Do not pass
|
|
9
9
|
* INSERT/UPDATE/DELETE — retry re-executes the statement. */
|
|
10
10
|
// @wrapper-known semantic-alias
|
|
11
11
|
export async function executeRawQueryRead(db, sqlText, params = []) {
|
|
@@ -64,10 +64,10 @@ export function rebuildMetaOrThrow(table, projectionName) {
|
|
|
64
64
|
return meta;
|
|
65
65
|
}
|
|
66
66
|
// Fence against a rebuild running with a registry that does not match the
|
|
67
|
-
// migrated live schema
|
|
67
|
+
// migrated live schema: during a rolling deploy, a pod still running
|
|
68
68
|
// the previous build can pick up an async rebuild job; its shadow — built from
|
|
69
69
|
// the stale EntityTableMeta — would swap away a freshly-migrated column
|
|
70
|
-
// (
|
|
70
|
+
// (a recurring failure). Compares COLUMN NAMES only; a type-/nullability-
|
|
71
71
|
// only drift passes (schema regression, not data loss — the boot gate of the
|
|
72
72
|
// next deploy catches it). A missing live table is fine: nothing to wipe.
|
|
73
73
|
// Must run BEFORE buildShadowTable; columnNamesOf pins table_schema='public',
|
|
@@ -93,7 +93,7 @@ export async function assertLiveColumnsMatchMeta(db, meta, projectionName) {
|
|
|
93
93
|
"Rebuilding would swap away the difference. Likely cause: this pod runs a build whose registry is behind (or ahead of) the applied migrations — rolling deploy in progress? — or DDL was applied by hand. " +
|
|
94
94
|
"Rebuild aborted; retry from a pod whose code matches the migrated schema.");
|
|
95
95
|
}
|
|
96
|
-
// EntityTableMeta carries no RLS/policies, so the swap would silently drop them
|
|
96
|
+
// EntityTableMeta carries no RLS/policies, so the swap would silently drop them.
|
|
97
97
|
export async function assertLiveTableHasNoRowLevelSecurity(tx, tableName, projectionName) {
|
|
98
98
|
const rows = await asRawClient(tx).unsafe(`SELECT c.relrowsecurity AS rls_enabled, c.relforcerowsecurity AS rls_forced,
|
|
99
99
|
(SELECT count(*)::int FROM pg_policy p WHERE p.polrelid = c.oid) AS policy_count
|
|
@@ -158,7 +158,7 @@ const UNREACHABLE_SAMPLE_LIMIT = 20;
|
|
|
158
158
|
// Runs INSIDE the rebuild tx, under the fence, before swapShadowIntoLive. A live
|
|
159
159
|
// row whose aggregate id has NO event in the projection's source streams is
|
|
160
160
|
// UNREACHABLE: no replay can ever reconstruct it, so the swap would silently
|
|
161
|
-
// drop it. That is
|
|
161
|
+
// drop it. That is a ghost row direct-inserted without ever emitting
|
|
162
162
|
// a .created event. The static CI guard cannot see it in data that already
|
|
163
163
|
// exists in production, or on table identifiers it couldn't resolve; this
|
|
164
164
|
// catches it at cutover and aborts (tx rolls back, live untouched).
|
|
@@ -169,14 +169,14 @@ const UNREACHABLE_SAMPLE_LIMIT = 20;
|
|
|
169
169
|
// - a blind-index column recomputed to NULL after the subject's key is
|
|
170
170
|
// shredded (GDPR erase) — the NULL is the intended end state;
|
|
171
171
|
// - a `sensitive` column stripped from the event log by design;
|
|
172
|
-
// - an archived stream that stops replaying
|
|
172
|
+
// - an archived stream that stops replaying — the row's wipe is the
|
|
173
173
|
// intended tombstone behavior, reported via backfill's `failed` list;
|
|
174
174
|
// - a legacy column direct-written before its handler emitted events, healed
|
|
175
|
-
// by the
|
|
175
|
+
// by the backfill-then-rebuild flow.
|
|
176
176
|
// Checking event existence INCLUDING archived streams leaves every one of them
|
|
177
177
|
// alone: those rows all have a real event, so they are not ghosts. Column-level
|
|
178
|
-
// drift is a SEPARATE, non-blocking check — see countColumnDrift below
|
|
179
|
-
// resolves the
|
|
178
|
+
// drift is a SEPARATE, non-blocking check — see countColumnDrift below,
|
|
179
|
+
// which resolves the open question: observe, don't block.
|
|
180
180
|
//
|
|
181
181
|
// Implicit projections only (caller-gated). aggregate_id and the entity id are
|
|
182
182
|
// both uuid, so the anti-join probes the events index without a cast.
|
|
@@ -298,8 +298,8 @@ async function assertBlindIndexKeyMatchesLive(raw, tableName, quotedTable, bidxC
|
|
|
298
298
|
`untouched.`);
|
|
299
299
|
}
|
|
300
300
|
// The bidx column is schema-driven, not key-driven: it exists NULL in a
|
|
301
|
-
// plaintext install and in the
|
|
302
|
-
// both of which are correct as-is. Only a POPULATED column with no key
|
|
301
|
+
// plaintext install and in the case where KMS is configured but no index key
|
|
302
|
+
// is present, both of which are correct as-is. Only a POPULATED column with no key
|
|
303
303
|
// configured in THIS process means the rebuild is about to overwrite proof
|
|
304
304
|
// of a real index with NULL — that's the one provable data-loss case.
|
|
305
305
|
export async function assertNoBlindIndexLoss(tx, tableName, meta, projectionName) {
|
|
@@ -331,29 +331,29 @@ export async function assertNoBlindIndexLoss(tx, tableName, meta, projectionName
|
|
|
331
331
|
// live-vs-shadow divergence. A blind-index column (`<field>_bidx`) is
|
|
332
332
|
// recomputed to NULL on GDPR key-shredding; the NULL is the intended end
|
|
333
333
|
// state, not drift. Everything else that legitimately diverges (archived
|
|
334
|
-
// streams,
|
|
334
|
+
// streams, backfill) either never reaches this comparison (archived rows
|
|
335
335
|
// are absent from the shadow entirely, see swapShadowIntoLive) or IS real
|
|
336
|
-
// column drift that the
|
|
336
|
+
// column drift that the backfill-then-rebuild flow relies on replay to
|
|
337
337
|
// heal — reporting it (without blocking) is correct, not a false positive.
|
|
338
338
|
const COLUMN_DRIFT_SAMPLE_LIMIT = 20;
|
|
339
339
|
// Runs INSIDE the rebuild tx, in the same slot as assertNoUnreachableLiveRows
|
|
340
340
|
// (after replay settles, before swapShadowIntoLive). Non-blocking counterpart
|
|
341
341
|
// to the ghost-row guard: reports live rows whose column values differ from
|
|
342
|
-
// the freshly-replayed shadow, WITHOUT aborting the swap (
|
|
343
|
-
//
|
|
342
|
+
// the freshly-replayed shadow, WITHOUT aborting the swap (it resolves the
|
|
343
|
+
// open question in favor of observe-not-block).
|
|
344
344
|
//
|
|
345
345
|
// Why non-blocking: a legacy column direct-written before its handler emitted
|
|
346
|
-
// events
|
|
346
|
+
// events diverges from replay by design — that divergence is exactly
|
|
347
347
|
// what the backfill-then-rebuild flow relies on replay to heal. Failing hard
|
|
348
348
|
// here would make rebuild mutually exclusive with that shipped healing path.
|
|
349
|
-
// There is no reliable metadata to distinguish "
|
|
349
|
+
// There is no reliable metadata to distinguish "healing in progress" from
|
|
350
350
|
// "someone else corrupted this row" short of an open-ended per-column policy
|
|
351
351
|
// blocklist — wrong-by-default whenever a class is missed. So: surface it,
|
|
352
352
|
// don't police it. The caller logs the result; ops decides.
|
|
353
353
|
//
|
|
354
|
-
// Caveat: sensitive CUSTOM fields still diverge until
|
|
355
|
-
// design
|
|
356
|
-
// post
|
|
354
|
+
// Caveat: sensitive CUSTOM fields still diverge until the Subject-DEK
|
|
355
|
+
// design — regular sensitive fields carry event-payload ciphertext parity
|
|
356
|
+
// post-design and don't drift. Both are reported the same as any other column
|
|
357
357
|
// drift; this is deliberate (see module comment above), not an oversight.
|
|
358
358
|
//
|
|
359
359
|
// Relies on assertLiveColumnsMatchMeta having already run: live/shadow/meta
|
package/dist/db/query.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { AggregateRow, AggregateSpec } from "@cosmicdrift/kumiko-types/aggregate-types";
|
|
2
2
|
import type { DbRunner, DbTx } from "@cosmicdrift/kumiko-types/db-connection";
|
|
3
3
|
import type { ExecutorManagedRunner, ExecutorManagedWriteCapability, WritableTable } from "@cosmicdrift/kumiko-types/executor-brand";
|
|
4
|
-
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
5
4
|
import type { SelectOptions, WhereObject } from "@cosmicdrift/kumiko-types/where-clause-types";
|
|
6
5
|
import type { EntityTableMeta } from "./entity-table-meta.ts";
|
|
6
|
+
import { type TableInfo } from "./table-info.ts";
|
|
7
7
|
import { asEntityTableMeta, requireEntityTableMeta } from "./table-introspection.ts";
|
|
8
8
|
export { asEntityTableMeta, requireEntityTableMeta };
|
|
9
9
|
export type ReservedClient = {
|
|
@@ -24,26 +24,14 @@ export declare function hasBegin(client: RawClient): client is RawClientWithBegi
|
|
|
24
24
|
export declare function asRawClient(db: unknown): RawClient;
|
|
25
25
|
export declare function runnerCanBeginTransaction(db: unknown): boolean;
|
|
26
26
|
export declare function runInSavepoint<T>(tx: ExecutorManagedRunner, fn: (sp: ExecutorManagedRunner) => Promise<T>): Promise<T>;
|
|
27
|
-
export declare function runInSavepoint<T>(tx: unknown, fn: (sp:
|
|
27
|
+
export declare function runInSavepoint<T>(tx: unknown, fn: (sp: DbRunner) => Promise<T>): Promise<T>;
|
|
28
28
|
export declare function runInSavepointIfSupported<T>(db: ExecutorManagedRunner, fn: (sp: ExecutorManagedRunner) => Promise<T>): Promise<T>;
|
|
29
29
|
export declare function runInSavepointIfSupported<T>(db: unknown, fn: (sp: DbRunner) => Promise<T>): Promise<T>;
|
|
30
30
|
export type AnyDb = DbRunner | unknown;
|
|
31
31
|
type AnyRow = any;
|
|
32
32
|
export type { JsonTextMatch, OrderByClause, SelectOptions, WhereObject, WhereOperator, WhereValue, } from "@cosmicdrift/kumiko-types/where-clause-types";
|
|
33
|
-
type TableLike = EntityTableMeta | any;
|
|
34
|
-
export type TableInfo = {
|
|
35
|
-
readonly name: string;
|
|
36
|
-
readonly columnOf: (field: string) => string;
|
|
37
|
-
readonly pgTypeOf: (column: string) => string | undefined;
|
|
38
|
-
readonly bigintJsModeOf: (column: string) => "number" | "bigint" | undefined;
|
|
39
|
-
readonly fieldOf: (column: string) => string;
|
|
40
|
-
readonly hasColumn: (fieldOrColumn: string) => boolean;
|
|
41
|
-
};
|
|
42
|
-
export declare function extractTableInfo(table: TableLike): TableInfo;
|
|
33
|
+
export type TableLike = EntityTableMeta | any;
|
|
43
34
|
export declare function coerceRow<TOut = Record<string, unknown>>(row: Record<string, unknown>, info: TableInfo): TOut;
|
|
44
|
-
export declare function instantToTimestamptzParam(instant: Temporal.Instant): string;
|
|
45
|
-
export declare function isClosedConnectionError(err: unknown): boolean;
|
|
46
|
-
export declare function describeClosedConnectionFailure(raw: unknown, err: unknown, attempts: number): Record<string, unknown> | undefined;
|
|
47
35
|
export declare function unsafeReadRetrying<TRow>(db: AnyDb, sqlText: string, params: readonly unknown[]): Promise<readonly TRow[]>;
|
|
48
36
|
export declare function selectMany<TRow = AnyRow>(db: AnyDb, table: TableLike, where?: WhereObject, options?: SelectOptions): Promise<readonly TRow[]>;
|
|
49
37
|
export declare function fetchOne<TRow = AnyRow>(db: AnyDb, table: TableLike, where: WhereObject): Promise<TRow | undefined>;
|
|
@@ -92,7 +80,6 @@ export type DeleteManyBatchedResult = {
|
|
|
92
80
|
readonly batches: number;
|
|
93
81
|
};
|
|
94
82
|
export declare function countWhere(db: AnyDb, table: TableLike, where?: WhereObject): Promise<number>;
|
|
95
|
-
export declare function isTimestamptzType(pgType: string | undefined): boolean;
|
|
96
83
|
export declare function aggregateWhere(db: AnyDb, table: TableLike, spec: AggregateSpec, where?: WhereObject): Promise<readonly AggregateRow[]>;
|
|
97
84
|
export declare function upsertOnConflict<TRow = AnyRow>(db: AnyDb, table: WritableTable, values: Record<string, unknown>, options: UpsertOnConflictOptions): Promise<TRow | undefined>;
|
|
98
85
|
export declare function upsertOnConflict<TRow = AnyRow>(db: ExecutorManagedWriteCapability, table: EntityTableMeta, values: Record<string, unknown>, options: UpsertOnConflictOptions): Promise<TRow | undefined>;
|