@cosmicdrift/kumiko-framework 0.388.0 → 0.390.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 +12 -4
- package/README.md +1 -1
- package/agents/examples/apex-surface-auth.md +2 -3
- package/agents/examples/api-worker-split.md +9 -2
- package/agents/examples/auth-claims.md +1 -1
- package/agents/examples/cross-feature-events.md +12 -12
- package/agents/examples/crypto-shredding-hr.md +2 -2
- package/agents/examples/encrypted-tenant-config.md +1 -1
- package/agents/examples/error-contract.md +12 -7
- package/agents/examples/event-sourcing.md +29 -29
- package/agents/examples/files-post-processing.md +10 -8
- package/agents/examples/golden-path.md +13 -1
- package/agents/examples/inbound-mail-triage.md +1 -0
- package/agents/examples/lane-routing.md +2 -1
- package/agents/examples/legal-pages.md +2 -2
- package/agents/examples/lifecycle-hooks.md +55 -2
- package/agents/examples/list-table-options.md +466 -0
- package/agents/examples/managed-config.md +4 -4
- package/agents/examples/managed-pages.md +1 -1
- package/agents/examples/pdf-audit-export.md +4 -2
- package/agents/examples/relations.md +19 -4
- package/agents/examples/search.md +4 -4
- package/agents/examples/secrets-demo.md +9 -6
- package/agents/examples/state-machine.md +5 -4
- package/agents/examples/tenant-isolation.md +73 -1
- package/agents/examples/tenant-settings.md +1 -1
- package/agents/examples/user-data-rights.md +3 -6
- package/agents/examples/workflow-engine.md +53 -47
- package/agents/examples.md +5 -4
- package/agents/features.md +1 -1
- package/agents/widgets.md +1 -0
- package/dist/api/api-constants.d.ts +1 -0
- package/dist/api/api-constants.js +2 -0
- package/dist/api/auth-middleware.js +1 -1
- package/dist/api/auth-routes-invite.js +2 -1
- package/dist/api/auth-routes-shared.d.ts +2 -0
- package/dist/api/auth-routes-shared.js +5 -0
- package/dist/api/client-error-route.js +2 -3
- package/dist/api/entity-read-access.d.ts +4 -1
- package/dist/api/entity-read-access.js +14 -0
- package/dist/api/export/export-cells-reference.d.ts +9 -0
- package/dist/api/export/export-cells-reference.js +21 -0
- package/dist/api/export/export-cells.d.ts +11 -0
- package/dist/api/export/export-cells.js +136 -0
- package/dist/api/export/export-ciphertext-guard.d.ts +2 -0
- package/dist/api/export/export-ciphertext-guard.js +27 -0
- package/dist/api/export/export-columns.d.ts +31 -0
- package/dist/api/export/export-columns.js +85 -0
- package/dist/api/export/export-errors.d.ts +4 -0
- package/dist/api/export/export-errors.js +14 -0
- package/dist/api/export/export-route.d.ts +4 -0
- package/dist/api/export/export-route.js +75 -0
- package/dist/api/export/export-rows.d.ts +13 -0
- package/dist/api/export/export-rows.js +62 -0
- package/dist/api/export/export-screen.d.ts +13 -0
- package/dist/api/export/export-screen.js +47 -0
- package/dist/api/export/export-xlsx-writer.d.ts +4 -0
- package/dist/api/export/export-xlsx-writer.js +21 -0
- package/dist/api/extra-route.d.ts +7 -1
- package/dist/api/index.d.ts +23 -27
- package/dist/api/index.js +13 -15
- package/dist/api/jwt.js +1 -1
- package/dist/api/observability-middleware.d.ts +1 -1
- package/dist/api/observability-middleware.js +2 -1
- package/dist/api/pii-leak-guard.d.ts +3 -0
- package/dist/api/pii-leak-guard.js +3 -2
- package/dist/api/request-context-internals.d.ts +31 -0
- package/dist/api/request-context-internals.js +11 -0
- package/dist/api/request-context-scopes.d.ts +14 -0
- package/dist/api/request-context-scopes.js +69 -0
- package/dist/api/request-context.d.ts +1 -40
- package/dist/api/request-context.js +22 -51
- package/dist/api/request-id-middleware.d.ts +1 -1
- package/dist/api/route-registrars.d.ts +2 -1
- package/dist/api/route-registrars.js +1 -1
- package/dist/api/routes.d.ts +16 -0
- package/dist/api/routes.js +9 -5
- package/dist/api/server-boot-checks.d.ts +1 -1
- package/dist/api/server-boot-checks.js +2 -2
- package/dist/api/server-event-dispatcher.d.ts +6 -1
- package/dist/api/server-event-dispatcher.js +3 -4
- package/dist/api/server-extra-routes.d.ts +1 -1
- package/dist/api/server-extra-routes.js +5 -1
- package/dist/api/server-middleware.js +3 -1
- package/dist/api/server-routes.d.ts +3 -2
- package/dist/api/server-routes.js +8 -2
- package/dist/api/server-wiring.js +8 -4
- package/dist/api/server.d.ts +2 -1
- package/dist/api/sse-route.d.ts +3 -1
- package/dist/api/sse-route.js +11 -1
- package/dist/api/tenant-scoped-secrets.d.ts +2 -0
- package/dist/api/tenant-scoped-secrets.js +13 -0
- package/dist/api/websocket-route.d.ts +1 -1
- package/dist/auth/index.d.ts +0 -1
- package/dist/auth/roles.d.ts +0 -5
- package/dist/compliance/duration-spec.d.ts +0 -1
- package/dist/compliance/duration-spec.js +0 -5
- package/dist/compliance/index.d.ts +4 -4
- package/dist/compliance/index.js +2 -2
- package/dist/compliance/profiles.d.ts +0 -15
- package/dist/compliance/profiles.js +0 -23
- package/dist/config/index.d.ts +11 -9
- package/dist/config/index.js +13 -1
- package/dist/config/internals.d.ts +9 -0
- package/dist/config/internals.js +1 -0
- package/dist/consumer-cli-internals.d.ts +4 -0
- package/dist/consumer-cli-internals.js +1 -0
- package/dist/consumer-cli.d.ts +1 -4
- package/dist/consumer-cli.js +5 -1
- package/dist/crypto/event-pii.js +1 -1
- package/dist/crypto/index.d.ts +8 -9
- package/dist/crypto/index.js +7 -8
- package/dist/crypto/pg-kms-adapter.d.ts +1 -0
- package/dist/crypto/pg-kms-adapter.js +17 -18
- package/dist/db/aggregate-sql.d.ts +2 -0
- package/dist/db/aggregate-sql.js +10 -0
- package/dist/db/apply-entity-event.d.ts +1 -1
- package/dist/db/apply-entity-event.js +15 -47
- package/dist/db/blind-index-cleanup.js +91 -77
- package/dist/db/closed-connection.js +12 -19
- package/dist/db/collect-table-metas.js +1 -1
- package/dist/db/compound-types.d.ts +1 -1
- package/dist/db/compound-types.js +0 -7
- package/dist/db/config-seed.d.ts +2 -1
- package/dist/db/config-seed.js +6 -1
- package/dist/db/connection.d.ts +7 -6
- package/dist/db/connection.js +43 -24
- package/dist/db/cross-tenant-reads.d.ts +1 -1
- package/dist/db/cross-tenant-reads.js +1 -1
- package/dist/db/dialect.d.ts +0 -5
- package/dist/db/dialect.js +0 -3
- package/dist/db/driver-row-coercion.js +4 -4
- package/dist/db/driver-value-preparation.js +6 -4
- package/dist/db/eagerload.d.ts +2 -1
- package/dist/db/entity-field-encryption.d.ts +1 -1
- package/dist/db/entity-table-meta.d.ts +1 -1
- package/dist/db/event-store-executor-context.d.ts +3 -1
- package/dist/db/event-store-executor-context.js +4 -3
- package/dist/db/event-store-executor-read-aggregate-currencies.d.ts +17 -0
- package/dist/db/event-store-executor-read-aggregate-currencies.js +40 -0
- package/dist/db/event-store-executor-read-aggregate.js +36 -5
- package/dist/db/event-store-executor-read-filter.d.ts +1 -1
- package/dist/db/event-store-executor-read-filter.js +6 -3
- package/dist/db/event-store-executor-read-rows.d.ts +2 -1
- package/dist/db/event-store-executor-read-search.d.ts +2 -1
- package/dist/db/event-store-executor-read-sort.d.ts +2 -1
- package/dist/db/event-store-executor-read.js +2 -2
- package/dist/db/event-store-executor-write-atomic.d.ts +4 -0
- package/dist/db/event-store-executor-write-atomic.js +42 -0
- package/dist/db/event-store-executor-write-steps.d.ts +3 -5
- package/dist/db/event-store-executor-write-steps.js +0 -5
- package/dist/db/event-store-executor-write.js +22 -19
- package/dist/db/event-store-executor.d.ts +1 -1
- package/dist/db/event-store-executor.js +2 -1
- package/dist/db/executor-managed-tables.d.ts +7 -1
- package/dist/db/executor-managed-tables.js +12 -3
- package/dist/db/index.d.ts +29 -28
- package/dist/db/index.js +22 -20
- package/dist/db/known-table-names.d.ts +6 -0
- package/dist/db/known-table-names.js +21 -0
- package/dist/db/list-aggregate-labels.d.ts +1 -1
- package/dist/db/located-timestamp.d.ts +1 -1
- package/dist/db/located-timestamp.js +0 -5
- package/dist/db/migrate-generator.d.ts +17 -1
- package/dist/db/migrate-generator.js +50 -20
- package/dist/db/migrate-primary-key.d.ts +12 -0
- package/dist/db/migrate-primary-key.js +45 -0
- package/dist/db/migrate-rls-diff.d.ts +20 -0
- package/dist/db/migrate-rls-diff.js +61 -0
- package/dist/db/migrate-runner.js +7 -3
- package/dist/db/money.d.ts +1 -1
- package/dist/db/money.js +1 -1
- package/dist/db/parent-ref-clause.d.ts +2 -1
- package/dist/db/pg-error.d.ts +2 -0
- package/dist/db/pg-error.js +37 -21
- package/dist/db/queries/backfill-pii.js +3 -6
- package/dist/db/queries/database-now.js +2 -1
- package/dist/db/queries/ddl.d.ts +3 -0
- package/dist/db/queries/ddl.js +34 -1
- package/dist/db/queries/event-store.js +4 -2
- package/dist/db/queries/event-tenants.d.ts +2 -0
- package/dist/db/queries/event-tenants.js +17 -0
- package/dist/db/queries/rebuild-rls-guard.d.ts +2 -0
- package/dist/db/queries/rebuild-rls-guard.js +29 -0
- package/dist/db/queries/rls-boot-catalog.d.ts +16 -0
- package/dist/db/queries/rls-boot-catalog.js +39 -0
- package/dist/db/queries/rls-catalog.d.ts +6 -0
- package/dist/db/queries/rls-catalog.js +82 -0
- package/dist/db/queries/rls-scope.d.ts +3 -0
- package/dist/db/queries/rls-scope.js +17 -0
- package/dist/db/queries/rls-shape.d.ts +10 -0
- package/dist/db/queries/rls-shape.js +42 -0
- package/dist/db/queries/session-advisory-lock.d.ts +2 -0
- package/dist/db/queries/session-advisory-lock.js +23 -0
- package/dist/db/queries/shadow-swap.d.ts +1 -2
- package/dist/db/queries/shadow-swap.js +11 -28
- package/dist/db/queries/test-stack.d.ts +4 -1
- package/dist/db/queries/test-stack.js +83 -2
- package/dist/db/query.d.ts +14 -0
- package/dist/db/query.js +77 -48
- package/dist/db/reference-data.d.ts +1 -1
- package/dist/db/reference-data.js +20 -3
- package/dist/db/render-ddl.d.ts +6 -2
- package/dist/db/render-ddl.js +7 -7
- package/dist/db/render-rls-ddl.d.ts +35 -0
- package/dist/db/render-rls-ddl.js +127 -0
- package/dist/db/replay-migration-sql.d.ts +3 -1
- package/dist/db/replay-migration-sql.js +22 -5
- package/dist/db/replay-rls.d.ts +25 -0
- package/dist/db/replay-rls.js +156 -0
- package/dist/db/row-level-security-boot-check.d.ts +5 -0
- package/dist/db/row-level-security-boot-check.js +85 -0
- package/dist/db/row-level-security-connection.d.ts +3 -0
- package/dist/db/row-level-security-connection.js +213 -0
- package/dist/db/row-level-security-scope.d.ts +33 -0
- package/dist/db/row-level-security-scope.js +127 -0
- package/dist/db/scope-tenant-db-to-tenant.d.ts +3 -0
- package/dist/db/scope-tenant-db-to-tenant.js +14 -0
- package/dist/db/sql-identifier.d.ts +1 -0
- package/dist/db/sql-identifier.js +3 -0
- package/dist/db/statement-timeout.js +2 -1
- package/dist/db/table-builder.d.ts +2 -1
- package/dist/db/table-builder.js +1 -1
- package/dist/db/table-info.js +1 -1
- package/dist/db/table-push.d.ts +11 -2
- package/dist/db/table-push.js +15 -2
- package/dist/db/tenant-db-runner.d.ts +3 -1
- package/dist/db/tenant-db-runner.js +4 -3
- package/dist/db/tenant-db-scoped-rebind.d.ts +10 -0
- package/dist/db/tenant-db-scoped-rebind.js +33 -0
- package/dist/db/tenant-db-statement-runner.d.ts +13 -0
- package/dist/db/tenant-db-statement-runner.js +48 -0
- package/dist/db/tenant-db-upsert.d.ts +5 -0
- package/dist/db/tenant-db-upsert.js +91 -0
- package/dist/db/tenant-db.d.ts +17 -12
- package/dist/db/tenant-db.js +60 -121
- package/dist/db/tenant-id-filter.d.ts +3 -0
- package/dist/db/tenant-id-filter.js +30 -0
- package/dist/db/transaction-serialization.d.ts +6 -0
- package/dist/db/transaction-serialization.js +60 -0
- package/dist/derivatives/delete-stored-file.js +1 -1
- package/dist/derivatives/index.d.ts +2 -5
- package/dist/derivatives/index.js +2 -3
- package/dist/engine/access.d.ts +2 -1
- package/dist/engine/access.js +1 -1
- package/dist/engine/boot-validator/bundled-lists-table.d.ts +2 -0
- package/dist/engine/boot-validator/bundled-lists-table.js +36 -0
- package/dist/engine/boot-validator/entity-handler.d.ts +2 -1
- package/dist/engine/boot-validator/entity-handler.js +1 -1
- package/dist/engine/boot-validator/entity-list-aggregate-options.d.ts +3 -0
- package/dist/engine/boot-validator/entity-list-aggregate-options.js +23 -0
- package/dist/engine/boot-validator/list-bulk-actions.d.ts +3 -0
- package/dist/engine/boot-validator/list-bulk-actions.js +49 -0
- package/dist/engine/boot-validator/list-column-layout.d.ts +8 -0
- package/dist/engine/boot-validator/list-column-layout.js +41 -0
- package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +24 -0
- package/dist/engine/boot-validator/list-table-aggregate-options.js +145 -0
- package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +11 -0
- package/dist/engine/boot-validator/list-table-aggregate-query.js +31 -0
- package/dist/engine/boot-validator/list-table-export-options.d.ts +1 -0
- package/dist/engine/boot-validator/list-table-export-options.js +27 -0
- package/dist/engine/boot-validator/list-table-options.d.ts +3 -0
- package/dist/engine/boot-validator/list-table-options.js +77 -0
- package/dist/engine/boot-validator/list-table-reject-unknown-keys.d.ts +1 -0
- package/dist/engine/boot-validator/list-table-reject-unknown-keys.js +7 -0
- package/dist/engine/boot-validator/nav.d.ts +3 -1
- package/dist/engine/boot-validator/nav.js +1 -1
- package/dist/engine/boot-validator/ownership.d.ts +2 -1
- package/dist/engine/boot-validator/parent-ref.d.ts +2 -1
- package/dist/engine/boot-validator/projection-list-screens.d.ts +2 -1
- package/dist/engine/boot-validator/projection-list-screens.js +3 -13
- package/dist/engine/boot-validator/query-refs.d.ts +2 -1
- package/dist/engine/boot-validator/related-list-table-rules.d.ts +4 -0
- package/dist/engine/boot-validator/related-list-table-rules.js +42 -0
- package/dist/engine/boot-validator/screens-forms.d.ts +2 -1
- package/dist/engine/boot-validator/screens-kind-entity-list.js +7 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.js +4 -0
- package/dist/engine/boot-validator/screens-kind-shared.d.ts +3 -1
- package/dist/engine/boot-validator/screens-lists.js +12 -2
- package/dist/engine/boot-validator/screens.d.ts +2 -1
- package/dist/engine/boot-validator/workspaces.d.ts +3 -1
- package/dist/engine/bridge-stub.d.ts +5 -2
- package/dist/engine/bridge-stub.js +39 -24
- package/dist/engine/build-app-schema.d.ts +2 -1
- package/dist/engine/build-app-schema.js +9 -1
- package/dist/engine/build-config-feature-schema.js +6 -1
- package/dist/engine/config-helpers.d.ts +2 -2
- package/dist/engine/config-helpers.js +0 -9
- package/dist/engine/constants.d.ts +0 -10
- package/dist/engine/constants.js +0 -8
- package/dist/engine/create-app.js +1 -1
- package/dist/engine/define-feature.d.ts +2 -1
- package/dist/engine/define-feature.js +5 -2
- package/dist/engine/define-handler.d.ts +1 -1
- package/dist/engine/define-step.d.ts +0 -1
- package/dist/engine/define-step.js +0 -3
- package/dist/engine/embedded-derived.d.ts +1 -1
- package/dist/engine/entity-handlers.d.ts +2 -1
- package/dist/engine/entity-handlers.js +7 -7
- package/dist/engine/entity-list-runtime-options.js +3 -1
- package/dist/engine/entity-list-searchability.d.ts +2 -1
- package/dist/engine/event-def.d.ts +2 -1
- package/dist/engine/event-helpers.d.ts +3 -1
- package/dist/engine/event-helpers.js +3 -3
- package/dist/engine/extension-names.d.ts +2 -8
- package/dist/engine/extension-names.js +2 -2
- package/dist/engine/extension-selector-plugins.d.ts +1 -1
- package/dist/engine/extensions/storage-provider.d.ts +1 -2
- package/dist/engine/extensions/user-data.d.ts +0 -8
- package/dist/engine/factories.d.ts +2 -1
- package/dist/engine/feature-builder-state.d.ts +9 -1
- package/dist/engine/feature-builder-state.js +1 -0
- package/dist/engine/feature-changelog.d.ts +1 -1
- package/dist/engine/feature-config-events-jobs.d.ts +4 -1
- package/dist/engine/feature-entity-handlers.d.ts +5 -1
- package/dist/engine/feature-manifest.d.ts +1 -0
- package/dist/engine/feature-manifest.js +2 -1
- package/dist/engine/feature-ui-extensions.d.ts +7 -1
- package/dist/engine/feature-ui-extensions.js +6 -4
- package/dist/engine/field-access.d.ts +3 -1
- package/dist/engine/field-access.js +12 -0
- package/dist/engine/handler-field-references.d.ts +0 -4
- package/dist/engine/index.d.ts +58 -55
- package/dist/engine/index.js +23 -28
- package/dist/engine/instruction-fields.d.ts +1 -1
- package/dist/engine/personal-data-fields.d.ts +1 -1
- package/dist/engine/qualified-name.d.ts +0 -1
- package/dist/engine/read-claim.d.ts +2 -1
- package/dist/engine/registry-facade.js +0 -3
- package/dist/engine/registry-ingest.d.ts +2 -1
- package/dist/engine/registry-ingest.js +1 -2
- package/dist/engine/registry-state.d.ts +12 -2
- package/dist/engine/registry-state.js +2 -3
- package/dist/engine/registry-validate.d.ts +3 -1
- package/dist/engine/registry-validate.js +7 -7
- package/dist/engine/run-in.d.ts +1 -1
- package/dist/engine/run-in.js +0 -1
- package/dist/engine/schema-builder.d.ts +1 -1
- package/dist/engine/schema-builder.js +1 -1
- package/dist/engine/screen-filter-ops.d.ts +2 -1
- package/dist/engine/screen-filter-ops.js +2 -5
- package/dist/engine/soft-delete-cleanup.d.ts +1 -1
- package/dist/engine/soft-delete-cleanup.js +4 -4
- package/dist/engine/state-machine.js +2 -1
- package/dist/engine/steps/_read-source.js +1 -1
- package/dist/engine/steps/aggregate-append-event.js +2 -2
- package/dist/engine/steps/call-feature.js +2 -2
- package/dist/engine/steps/mail-send.js +1 -1
- package/dist/engine/steps/retry.js +1 -1
- package/dist/engine/steps/wait-for-event.js +1 -1
- package/dist/engine/steps/wait.js +1 -1
- package/dist/engine/steps/webhook-send.js +1 -1
- package/dist/engine/strip-disabled-features-from-app-schema.js +7 -3
- package/dist/engine/template-variables.d.ts +2 -1
- package/dist/engine/transfer-adjacency.d.ts +1 -1
- package/dist/engine/types/feature.d.ts +1 -0
- package/dist/engine/types/index.d.ts +8 -30
- package/dist/engine/types/index.js +2 -10
- package/dist/engine/types/screen.d.ts +1 -1
- package/dist/engine/types/screen.js +1 -1
- package/dist/engine/validation.d.ts +2 -1
- package/dist/entrypoint/index.d.ts +9 -35
- package/dist/entrypoint/index.js +10 -1
- package/dist/entrypoint/internals.d.ts +40 -0
- package/dist/entrypoint/internals.js +1 -0
- package/dist/entrypoint/row-level-security-mode-check.d.ts +3 -0
- package/dist/entrypoint/row-level-security-mode-check.js +19 -0
- package/dist/env/dry-run-internals.d.ts +10 -0
- package/dist/env/dry-run-internals.js +1 -0
- package/dist/env/dry-run.d.ts +1 -10
- package/dist/env/dry-run.js +2 -1
- package/dist/env/index.d.ts +1 -85
- package/dist/env/index.js +6 -50
- package/dist/env/internals.d.ts +87 -0
- package/dist/env/internals.js +53 -0
- package/dist/errors/classes.js +2 -2
- package/dist/errors/index.d.ts +5 -14
- package/dist/errors/index.js +4 -9
- package/dist/errors/member-resolution.js +1 -1
- package/dist/errors/reasons.d.ts +0 -1
- package/dist/errors/reasons.js +7 -7
- package/dist/errors/serialize.d.ts +0 -18
- package/dist/errors/serialize.js +0 -27
- package/dist/es-ops/index.d.ts +4 -4
- package/dist/es-ops/index.js +2 -2
- package/dist/es-ops/operations-schema.d.ts +1 -1
- package/dist/es-ops/types.d.ts +4 -5
- package/dist/event-store/admin-api-internals.d.ts +14 -0
- package/dist/event-store/admin-api-internals.js +1 -0
- package/dist/event-store/admin-api.d.ts +1 -14
- package/dist/event-store/admin-api.js +11 -6
- package/dist/event-store/archive.js +4 -2
- package/dist/event-store/errors.js +2 -2
- package/dist/event-store/event-store.d.ts +1 -0
- package/dist/event-store/event-store.js +42 -32
- package/dist/event-store/events-schema.js +4 -1
- package/dist/event-store/index.d.ts +11 -13
- package/dist/event-store/index.js +7 -9
- package/dist/event-store/provenance-append.js +1 -1
- package/dist/event-store/rebuild-dead-letter.js +4 -2
- package/dist/event-store/snapshot.js +4 -3
- package/dist/event-store/stream-scope.d.ts +2 -0
- package/dist/event-store/stream-scope.js +6 -0
- package/dist/event-store/upcaster-dead-letter.js +4 -2
- package/dist/event-store/upcaster.d.ts +2 -1
- package/dist/files/action-form-upload.d.ts +2 -1
- package/dist/files/action-form-upload.js +1 -1
- package/dist/files/assert-file-refs-belong-to-tenant.d.ts +2 -1
- package/dist/files/assert-file-refs-belong-to-tenant.js +1 -1
- package/dist/files/bind-pending-file-refs.d.ts +2 -1
- package/dist/files/bind-pending-file-refs.js +2 -1
- package/dist/files/bind-referenced-files.write.js +1 -1
- package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
- package/dist/files/create-file-ref-from-bytes.js +6 -3
- package/dist/files/create-file-ref.d.ts +6 -2
- package/dist/files/create-file-ref.js +13 -4
- package/dist/files/dispose-removed-file-refs.d.ts +2 -1
- package/dist/files/dispose-removed-file-refs.js +1 -1
- package/dist/files/feature.js +2 -2
- package/dist/files/file-handle.js +1 -1
- package/dist/files/file-routes.d.ts +3 -1
- package/dist/files/file-routes.js +41 -4
- package/dist/files/index.d.ts +11 -13
- package/dist/files/index.js +5 -7
- package/dist/files/load-save-file-refs.d.ts +2 -1
- package/dist/files/load-save-file-refs.js +1 -1
- package/dist/files/provider-resolver.d.ts +6 -4
- package/dist/files/provider-resolver.js +5 -5
- package/dist/files/record-write-check.d.ts +6 -0
- package/dist/files/record-write-check.js +13 -0
- package/dist/files/storage-tracking.js +4 -2
- package/dist/files/sweep-orphaned-uploads.d.ts +1 -1
- package/dist/files/sweep-orphaned-uploads.js +1 -1
- package/dist/files/upload-ledger.d.ts +2 -2
- package/dist/files/upload-ledger.js +4 -4
- package/dist/i18n/index.d.ts +2 -8
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/internals.d.ts +7 -0
- package/dist/i18n/internals.js +1 -0
- package/dist/i18n/required-surface-keys.d.ts +4 -1
- package/dist/i18n/required-surface-keys.js +7 -2
- package/dist/internal/row-level-security.d.ts +3 -0
- package/dist/internal/row-level-security.js +3 -0
- package/dist/internal/stack-support.d.ts +1 -1
- package/dist/internal/stack-support.js +1 -1
- package/dist/internal/testing.d.ts +1 -0
- package/dist/internal/testing.js +10 -6
- package/dist/jobs/index.d.ts +1 -1
- package/dist/jobs/job-append-event.d.ts +12 -0
- package/dist/jobs/job-append-event.js +30 -0
- package/dist/jobs/job-runner-context.d.ts +2 -2
- package/dist/jobs/job-runner-context.js +46 -16
- package/dist/jobs/job-runner-execution.d.ts +1 -1
- package/dist/jobs/job-runner-execution.js +6 -4
- package/dist/jobs/job-runner-gates.d.ts +1 -1
- package/dist/jobs/job-runner-gates.js +0 -1
- package/dist/jobs/job-runner-helpers.d.ts +11 -4
- package/dist/jobs/job-runner-helpers.js +10 -1
- package/dist/jobs/job-runner-scope.d.ts +5 -2
- package/dist/jobs/job-runner-scope.js +6 -6
- package/dist/jobs/job-runner-types.d.ts +2 -1
- package/dist/jobs/job-runner.js +22 -6
- package/dist/jobs/max-per-tenant.d.ts +1 -1
- package/dist/logging/index.d.ts +1 -2
- package/dist/logging/index.js +1 -1
- package/dist/logging/pino-logger.js +1 -1
- package/dist/logging/utils.d.ts +1 -0
- package/dist/logging/utils.js +6 -0
- package/dist/migrations/index.d.ts +3 -3
- package/dist/migrations/index.js +3 -3
- package/dist/migrations/pending-rebuilds.d.ts +1 -1
- package/dist/migrations/pending-rebuilds.js +1 -1
- package/dist/migrations/pii-event-backfill.js +4 -1
- package/dist/observability/escape-hatch-report.d.ts +2 -1
- package/dist/observability/escape-hatch-report.js +9 -2
- package/dist/observability/index.d.ts +7 -15
- package/dist/observability/index.js +4 -12
- package/dist/observability/metric-validator.js +1 -1
- package/dist/observability/metrics-handle.d.ts +0 -1
- package/dist/observability/metrics-handle.js +2 -15
- package/dist/observability/recording-tracer.d.ts +1 -2
- package/dist/observability/recording-tracer.js +0 -6
- package/dist/observability/sensitive-filter.d.ts +0 -2
- package/dist/observability/sensitive-filter.js +0 -29
- package/dist/pipeline/active-membership.d.ts +2 -1
- package/dist/pipeline/active-membership.js +6 -2
- package/dist/pipeline/after-rollback-sink.d.ts +6 -0
- package/dist/pipeline/after-rollback-sink.js +32 -0
- package/dist/pipeline/append-event-core.d.ts +2 -1
- package/dist/pipeline/append-event-core.js +2 -2
- package/dist/pipeline/attach-outside-transaction.d.ts +3 -0
- package/dist/pipeline/attach-outside-transaction.js +13 -0
- package/dist/pipeline/auth-claims-resolver.d.ts +2 -1
- package/dist/pipeline/cascade-handler.d.ts +2 -1
- package/dist/pipeline/cascade-handler.js +3 -2
- package/dist/pipeline/config-accessor-factory.d.ts +1 -1
- package/dist/pipeline/config-accessor-factory.js +1 -1
- package/dist/pipeline/context-services.d.ts +56 -0
- package/dist/pipeline/context-services.js +73 -0
- package/dist/pipeline/dispatch-batch.d.ts +2 -1
- package/dist/pipeline/dispatch-batch.js +39 -19
- package/dist/pipeline/dispatch-query.js +5 -4
- package/dist/pipeline/dispatch-scope.d.ts +6 -0
- package/dist/pipeline/dispatch-scope.js +27 -0
- package/dist/pipeline/dispatch-shared.d.ts +13 -9
- package/dist/pipeline/dispatch-shared.js +75 -194
- package/dist/pipeline/dispatch-stream.js +8 -4
- package/dist/pipeline/dispatch-write.d.ts +2 -1
- package/dist/pipeline/dispatch-write.js +10 -8
- package/dist/pipeline/dispatcher-utils.d.ts +3 -1
- package/dist/pipeline/dispatcher-utils.js +8 -2
- package/dist/pipeline/dispatcher.d.ts +3 -2
- package/dist/pipeline/dispatcher.js +4 -3
- package/dist/pipeline/event-consumer-state.d.ts +0 -1
- package/dist/pipeline/event-dispatcher-delivery.d.ts +1 -1
- package/dist/pipeline/event-dispatcher-delivery.js +7 -3
- package/dist/pipeline/event-dispatcher.d.ts +3 -2
- package/dist/pipeline/event-dispatcher.js +13 -12
- package/dist/pipeline/event-retention.js +59 -56
- package/dist/pipeline/handler-access-error.js +2 -1
- package/dist/pipeline/handler-context-auth.d.ts +5 -6
- package/dist/pipeline/handler-context-auth.js +7 -4
- package/dist/pipeline/handler-context-config.d.ts +2 -5
- package/dist/pipeline/handler-context-config.js +2 -2
- package/dist/pipeline/handler-context-db.d.ts +32 -7
- package/dist/pipeline/handler-context-db.js +26 -17
- package/dist/pipeline/handler-context-dispatch.d.ts +14 -6
- package/dist/pipeline/handler-context-dispatch.js +18 -5
- package/dist/pipeline/handler-context-events.d.ts +22 -38
- package/dist/pipeline/handler-context-events.js +42 -35
- package/dist/pipeline/handler-context-member-resolution.d.ts +2 -0
- package/dist/pipeline/handler-context-member-resolution.js +110 -0
- package/dist/pipeline/handler-context-notify.d.ts +3 -2
- package/dist/pipeline/handler-context-notify.js +1 -1
- package/dist/pipeline/handler-context-observability.d.ts +2 -4
- package/dist/pipeline/handler-context-observability.js +7 -5
- package/dist/pipeline/handler-context-phase-denials.d.ts +11 -0
- package/dist/pipeline/handler-context-phase-denials.js +98 -0
- package/dist/pipeline/handler-context-scope.d.ts +7 -10
- package/dist/pipeline/handler-context-scope.js +18 -20
- package/dist/pipeline/index.d.ts +9 -21
- package/dist/pipeline/index.js +5 -7
- package/dist/pipeline/irreversible-operation-gate.d.ts +1 -1
- package/dist/pipeline/irreversible-operation-gate.js +2 -2
- package/dist/pipeline/lifecycle-pipeline.d.ts +8 -7
- package/dist/pipeline/lifecycle-pipeline.js +9 -15
- package/dist/pipeline/member-read-only-transaction.js +11 -3
- package/dist/pipeline/member-reader.d.ts +1 -1
- package/dist/pipeline/member-reader.js +2 -2
- package/dist/pipeline/msp-rebuild.js +5 -3
- package/dist/pipeline/multi-stream-apply-context.js +3 -2
- package/dist/pipeline/notify-scope.d.ts +2 -1
- package/dist/pipeline/pre-handler-gates.d.ts +2 -1
- package/dist/pipeline/pre-transaction-reservations.d.ts +3 -3
- package/dist/pipeline/pre-transaction-reservations.js +9 -7
- package/dist/pipeline/projection-rebuild.js +12 -5
- package/dist/pipeline/projection-state.d.ts +0 -6
- package/dist/pipeline/projection-state.js +0 -5
- package/dist/pipeline/projections-runner.d.ts +2 -1
- package/dist/pipeline/projections-runner.js +10 -6
- package/dist/pipeline/system-hooks.d.ts +6 -2
- package/dist/pipeline/system-hooks.js +61 -10
- package/dist/pipeline/system-identity-switch.d.ts +4 -2
- package/dist/pipeline/system-identity-switch.js +51 -36
- package/dist/pipeline/typed-append-event.d.ts +2 -0
- package/dist/pipeline/typed-append-event.js +4 -0
- package/dist/pipeline/write-origin.js +2 -2
- package/dist/pipeline/write-result-masking.d.ts +1 -1
- package/dist/pipeline/write-result-masking.js +13 -10
- package/dist/random/index.d.ts +1 -2
- package/dist/random/index.js +1 -2
- package/dist/rate-limit/bucket.d.ts +2 -1
- package/dist/rate-limit/middleware.js +2 -1
- package/dist/redis/index.d.ts +3 -4
- package/dist/redis/index.js +2 -2
- package/dist/schema-cli/apply-command.js +5 -2
- package/dist/schema-cli/baseline-command.js +7 -2
- package/dist/schema-cli/generate-command.js +7 -9
- package/dist/schema-cli/shared.d.ts +4 -0
- package/dist/schema-cli/shared.js +21 -2
- package/dist/schema-cli/status-command.js +8 -2
- package/dist/schema-cli/validate-command.js +52 -4
- package/dist/schema-cli.d.ts +0 -1
- package/dist/search/configure-tenant-index.d.ts +1 -3
- package/dist/search/configure-tenant-index.js +0 -4
- package/dist/search/index.d.ts +1 -4
- package/dist/search/index.js +0 -2
- package/dist/search/meilisearch-adapter-internals.d.ts +8 -0
- package/dist/search/meilisearch-adapter-internals.js +9 -0
- package/dist/search/meilisearch-adapter.d.ts +1 -8
- package/dist/search/meilisearch-adapter.js +6 -7
- package/dist/search/meilisearch-peer.d.ts +2 -0
- package/dist/search/meilisearch-peer.js +5 -0
- package/dist/search/reindex-entity.js +107 -91
- package/dist/secrets/index.d.ts +3 -4
- package/dist/secrets/index.js +2 -3
- package/dist/seeding/index.d.ts +2 -2
- package/dist/seeding/index.js +1 -2
- package/dist/time/index.d.ts +2 -2
- package/dist/time/index.js +1 -1
- package/dist/time/tz-context.d.ts +0 -5
- package/dist/time/tz-context.js +0 -9
- package/dist/ui-types/index.d.ts +8 -11
- package/dist/ui-types/index.js +3 -3
- package/dist/upgrade-changelog.d.ts +3 -0
- package/dist/upgrade-changelog.js +18 -0
- package/dist/upgrade-cli-internals.d.ts +8 -0
- package/dist/upgrade-cli-internals.js +63 -0
- package/dist/upgrade-cli.d.ts +1 -18
- package/dist/upgrade-cli.js +29 -137
- package/dist/upgrade-marker-parse.d.ts +32 -0
- package/dist/upgrade-marker-parse.js +73 -0
- package/dist/utils/import-optional-peer.d.ts +6 -0
- package/dist/utils/import-optional-peer.js +19 -0
- package/dist/utils/index.d.ts +1 -4
- package/dist/utils/index.js +1 -4
- package/package.json +27 -20
- package/src/changes.json +451 -0
- package/dist/crypto/request-kms-cache.d.ts +0 -8
- package/dist/crypto/request-kms-cache.js +0 -21
- package/dist/engine/codemod/index.d.ts +0 -2
- package/dist/engine/codemod/index.js +0 -1
- package/dist/engine/codemod/pipeline-codemod.d.ts +0 -50
- package/dist/engine/codemod/pipeline-codemod.js +0 -536
- package/dist/engine/feature-ast/entity-field-types.d.ts +0 -13
- package/dist/engine/feature-ast/entity-field-types.js +0 -35
- package/dist/engine/feature-ast/extractors/ai-steps.d.ts +0 -6
- package/dist/engine/feature-ast/extractors/ai-steps.js +0 -254
- package/dist/engine/feature-ast/extractors/events.d.ts +0 -5
- package/dist/engine/feature-ast/extractors/events.js +0 -275
- package/dist/engine/feature-ast/extractors/handlers.d.ts +0 -24
- package/dist/engine/feature-ast/extractors/handlers.js +0 -358
- package/dist/engine/feature-ast/extractors/hooks.d.ts +0 -24
- package/dist/engine/feature-ast/extractors/hooks.js +0 -248
- package/dist/engine/feature-ast/extractors/index.d.ts +0 -14
- package/dist/engine/feature-ast/extractors/index.js +0 -13
- package/dist/engine/feature-ast/extractors/jobs-routes.d.ts +0 -7
- package/dist/engine/feature-ast/extractors/jobs-routes.js +0 -145
- package/dist/engine/feature-ast/extractors/projections-screens.d.ts +0 -10
- package/dist/engine/feature-ast/extractors/projections-screens.js +0 -195
- package/dist/engine/feature-ast/extractors/resolve-import.d.ts +0 -21
- package/dist/engine/feature-ast/extractors/resolve-import.js +0 -180
- package/dist/engine/feature-ast/extractors/round1.d.ts +0 -10
- package/dist/engine/feature-ast/extractors/round1.js +0 -81
- package/dist/engine/feature-ast/extractors/round2.d.ts +0 -7
- package/dist/engine/feature-ast/extractors/round2.js +0 -191
- package/dist/engine/feature-ast/extractors/round3.d.ts +0 -23
- package/dist/engine/feature-ast/extractors/round3.js +0 -292
- package/dist/engine/feature-ast/extractors/round5.d.ts +0 -9
- package/dist/engine/feature-ast/extractors/round5.js +0 -68
- package/dist/engine/feature-ast/extractors/round6.d.ts +0 -4
- package/dist/engine/feature-ast/extractors/round6.js +0 -17
- package/dist/engine/feature-ast/extractors/shared.d.ts +0 -94
- package/dist/engine/feature-ast/extractors/shared.js +0 -343
- package/dist/engine/feature-ast/index.d.ts +0 -16
- package/dist/engine/feature-ast/index.js +0 -10
- package/dist/engine/feature-ast/parse.d.ts +0 -31
- package/dist/engine/feature-ast/parse.js +0 -483
- package/dist/engine/feature-ast/patch.d.ts +0 -210
- package/dist/engine/feature-ast/patch.js +0 -610
- package/dist/engine/feature-ast/patcher.d.ts +0 -217
- package/dist/engine/feature-ast/patcher.js +0 -281
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +0 -1476
- package/dist/engine/feature-ast/pattern-change-schema.js +0 -1226
- package/dist/engine/feature-ast/pattern-id-matchers.d.ts +0 -7
- package/dist/engine/feature-ast/pattern-id-matchers.js +0 -210
- package/dist/engine/feature-ast/patterns.d.ts +0 -318
- package/dist/engine/feature-ast/patterns.js +0 -106
- package/dist/engine/feature-ast/render.d.ts +0 -47
- package/dist/engine/feature-ast/render.js +0 -662
- package/dist/engine/feature-ast/source-location.d.ts +0 -17
- package/dist/engine/feature-ast/source-location.js +0 -27
- package/dist/engine/pattern-library/index.d.ts +0 -2
- package/dist/engine/pattern-library/index.js +0 -3
- package/dist/engine/pattern-library/library.d.ts +0 -14
- package/dist/engine/pattern-library/library.js +0 -93
- package/dist/engine/pattern-library/mixed-schemas.d.ts +0 -16
- package/dist/engine/pattern-library/mixed-schemas.js +0 -559
- package/dist/engine/pattern-library/opaque-schemas.d.ts +0 -7
- package/dist/engine/pattern-library/opaque-schemas.js +0 -115
- package/dist/engine/pattern-library/shared-fields.d.ts +0 -122
- package/dist/engine/pattern-library/shared-fields.js +0 -88
- package/dist/engine/pattern-library/static-schemas.d.ts +0 -20
- package/dist/engine/pattern-library/static-schemas.js +0 -478
- package/dist/engine/pattern-library/types.d.ts +0 -158
- package/dist/engine/pattern-library/types.js +0 -36
- package/dist/engine/provider-plugin-context.d.ts +0 -3
- package/dist/engine/provider-plugin-context.js +0 -28
- package/dist/engine/resolve-config-or-param.d.ts +0 -18
- package/dist/engine/resolve-config-or-param.js +0 -76
- package/src/scripts/codemod/README.md +0 -187
- package/src/scripts/codemod/codemod-support.ts +0 -334
- package/src/scripts/codemod/collect-source-files.ts +0 -48
- package/src/scripts/codemod/crypto-shredding-testing-move.ts +0 -119
- package/src/scripts/codemod/db-factory.ts +0 -810
- package/src/scripts/codemod/feature-factory.ts +0 -429
- package/src/scripts/codemod/feature-layout.ts +0 -424
- package/src/scripts/codemod/import-extensions.ts +0 -118
- package/src/scripts/codemod/migrate-cross-tenant.ts +0 -328
- package/src/scripts/codemod/migrate-db-raw.ts +0 -401
- package/src/scripts/codemod/migrate-open-to-all.ts +0 -235
- package/src/scripts/codemod/object-form.ts +0 -746
- package/src/scripts/codemod/package-merge.ts +0 -299
- package/src/scripts/codemod/pii-personal-migration.ts +0 -816
- package/src/scripts/codemod/remove-engine-payload-casts.ts +0 -318
- package/src/scripts/codemod/s3-imports.ts +0 -236
- package/src/scripts/codemod/tenant-db-options.ts +0 -454
- package/src/scripts/codemod/testing-injection.ts +0 -215
- package/src/scripts/codemod/testing-move-targets.ts +0 -161
- package/src/scripts/codemod/testing-move.ts +0 -542
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +0 -411
|
@@ -17,19 +17,20 @@
|
|
|
17
17
|
// Boundary: the shadow table is rebuilt from EntityTableMeta, so any index NOT
|
|
18
18
|
// expressed in meta (hand-added in a migration) is not reconstructed, and a
|
|
19
19
|
// partial index whose WHERE the renderer can't express is rejected up-front.
|
|
20
|
+
// RLS flags and policies are copied from the live catalog at swap time.
|
|
20
21
|
import { isUuid, parseTenantId } from "@cosmicdrift/kumiko-types/identifiers";
|
|
21
22
|
import { configuredBlindIndexKey } from "../../crypto/index.js";
|
|
23
|
+
import { extractPgError } from "../pg-error.js";
|
|
22
24
|
import { asEntityTableMeta, asRawClient } from "../query.js";
|
|
23
25
|
import { renderTableDdl } from "../render-ddl.js";
|
|
24
26
|
import { columnNamesOf, tableExists } from "../schema-inspection.js";
|
|
25
27
|
import { toPostgresArrayLiteral } from "./postgres-array-literal.js";
|
|
28
|
+
import { readRowLevelSecurityState, restoreRowLevelSecurityState } from "./rls-catalog.js";
|
|
26
29
|
import { quoteTableIdent } from "./table-ops.js";
|
|
27
30
|
export const PROJECTION_REBUILD_SCHEMA = "kumiko_rebuild";
|
|
28
31
|
const SCHEMA_IDENT = quoteTableIdent(PROJECTION_REBUILD_SCHEMA);
|
|
29
32
|
function isDuplicateSchemaError(e) {
|
|
30
|
-
|
|
31
|
-
return false;
|
|
32
|
-
const { code } = e;
|
|
33
|
+
const code = extractPgError(e)?.code;
|
|
33
34
|
// 42P06 duplicate_schema, 23505 unique_violation on pg_namespace — both
|
|
34
35
|
// surface from the well-known CREATE SCHEMA IF NOT EXISTS race.
|
|
35
36
|
return code === "42P06" || code === "23505";
|
|
@@ -93,27 +94,6 @@ export async function assertLiveColumnsMatchMeta(db, meta, projectionName) {
|
|
|
93
94
|
"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
95
|
"Rebuild aborted; retry from a pod whose code matches the migrated schema.");
|
|
95
96
|
}
|
|
96
|
-
// EntityTableMeta carries no RLS/policies, so the swap would silently drop them.
|
|
97
|
-
export async function assertLiveTableHasNoRowLevelSecurity(tx, tableName, projectionName) {
|
|
98
|
-
const rows = await asRawClient(tx).unsafe(`SELECT c.relrowsecurity AS rls_enabled, c.relforcerowsecurity AS rls_forced,
|
|
99
|
-
(SELECT count(*)::int FROM pg_policy p WHERE p.polrelid = c.oid) AS policy_count
|
|
100
|
-
FROM pg_class c
|
|
101
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
102
|
-
WHERE n.nspname = 'public' AND c.relname = $1`, [tableName]);
|
|
103
|
-
const row = rows[0];
|
|
104
|
-
// skip: no live table yet — nothing to have RLS on
|
|
105
|
-
if (!row)
|
|
106
|
-
return;
|
|
107
|
-
// skip: no RLS flags and no policies — nothing the swap could drop
|
|
108
|
-
if (!row.rls_enabled && !row.rls_forced && row.policy_count === 0)
|
|
109
|
-
return;
|
|
110
|
-
const context = projectionName !== undefined ? `projection-rebuild "${projectionName}"` : "shadow swap";
|
|
111
|
-
throw new Error(`${context}: live table "${tableName}" has row level security (enabled: ${row.rls_enabled}, ` +
|
|
112
|
-
`forced: ${row.rls_forced}, policies: ${row.policy_count}). The shadow swap rebuilds the table ` +
|
|
113
|
-
"from EntityTableMeta and would silently drop RLS and every policy. Kumiko does not support RLS " +
|
|
114
|
-
"on rebuildable tables: drop the RLS flags/policies on this table or exclude it from online " +
|
|
115
|
-
"rebuild. Rebuild aborted; live table untouched.");
|
|
116
|
-
}
|
|
117
97
|
// Runs INSIDE the rebuild tx, AFTER the state/consumer row lock is taken.
|
|
118
98
|
// Points search_path at the shadow schema (SET LOCAL → auto-reset on commit or
|
|
119
99
|
// rollback), drops any leftover shadow from a crashed run, then builds the
|
|
@@ -397,11 +377,14 @@ export async function countColumnDrift(tx, tableName, meta) {
|
|
|
397
377
|
export async function swapShadowIntoLive(tx, tableName) {
|
|
398
378
|
const raw = asRawClient(tx);
|
|
399
379
|
const ident = quoteTableIdent(tableName);
|
|
400
|
-
// Held before the
|
|
401
|
-
// (and is
|
|
402
|
-
// the unfenced paths (MSP rebuild, projection with no subscribed events) would drop RLS.
|
|
380
|
+
// Held before the catalog read so a concurrent ENABLE ROW LEVEL SECURITY / CREATE POLICY either
|
|
381
|
+
// commits first (and is carried over) or waits behind the DROP.
|
|
403
382
|
await raw.unsafe(`LOCK TABLE public.${ident} IN ACCESS EXCLUSIVE MODE`);
|
|
404
|
-
await
|
|
383
|
+
const live = await readRowLevelSecurityState(tx, "public", tableName);
|
|
384
|
+
if (live === undefined) {
|
|
385
|
+
throw new Error(`shadow swap: live table public.${tableName} does not exist`);
|
|
386
|
+
}
|
|
405
387
|
await raw.unsafe(`DROP TABLE public.${ident}`);
|
|
406
388
|
await raw.unsafe(`ALTER TABLE ${SCHEMA_IDENT}.${ident} SET SCHEMA public`);
|
|
389
|
+
await restoreRowLevelSecurityState(tx, tableName, live);
|
|
407
390
|
}
|
|
@@ -2,7 +2,10 @@ import type { AnyDb } from "../query.ts";
|
|
|
2
2
|
export { alterTableAddColumn, createIndexIfNotExists, executeDdlStatement } from "./ddl.ts";
|
|
3
3
|
export declare function truncateTablesRestartIdentity(db: AnyDb, tableNames: readonly string[]): Promise<void>;
|
|
4
4
|
export declare function databaseExists(db: AnyDb, dbName: string): Promise<boolean>;
|
|
5
|
-
export declare function createDatabase(db: AnyDb, dbName: string, templateDbName?: string): Promise<void>;
|
|
5
|
+
export declare function createDatabase(db: AnyDb, dbName: string, templateDbName?: string, owner?: string): Promise<void>;
|
|
6
|
+
export declare function createApplicationRole(admin: AnyDb, role: string, password: string): Promise<void>;
|
|
7
|
+
export declare function transferDatabaseOwnership(admin: AnyDb, dbName: string, owner: string): Promise<void>;
|
|
8
|
+
export declare function transferPublicSchemaOwnership(db: AnyDb, owner: string): Promise<void>;
|
|
6
9
|
export declare function renameDatabase(db: AnyDb, fromName: string, toName: string): Promise<void>;
|
|
7
10
|
export declare function listDatabaseNamesWithPrefix(db: AnyDb, prefix: string): Promise<readonly string[]>;
|
|
8
11
|
export declare function acquireSessionAdvisoryLock(db: AnyDb, key: string): Promise<void>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { extractPgError } from "../pg-error.js";
|
|
1
2
|
import { asRawClient } from "../query.js";
|
|
3
|
+
import { PROJECTION_REBUILD_SCHEMA } from "./shadow-swap.js";
|
|
2
4
|
import { quoteTableIdent } from "./table-ops.js";
|
|
3
5
|
// Re-exported for back-compat — the generic DDL helpers moved to ./ddl so
|
|
4
6
|
// the prod-boot path (db/table-push.ts, pipeline/event-consumer-state.ts)
|
|
@@ -12,9 +14,88 @@ export async function databaseExists(db, dbName) {
|
|
|
12
14
|
const rows = await asRawClient(db).unsafe(`SELECT EXISTS (SELECT 1 FROM pg_database WHERE datname = $1) AS exists`, [dbName]);
|
|
13
15
|
return rows[0]?.exists === true;
|
|
14
16
|
}
|
|
15
|
-
export async function createDatabase(db, dbName, templateDbName) {
|
|
17
|
+
export async function createDatabase(db, dbName, templateDbName, owner) {
|
|
16
18
|
const template = templateDbName === undefined ? "" : ` TEMPLATE ${quoteTableIdent(templateDbName)}`;
|
|
17
|
-
|
|
19
|
+
const ownerClause = owner === undefined ? "" : ` OWNER ${quoteTableIdent(owner)}`;
|
|
20
|
+
await asRawClient(db).unsafe(`CREATE DATABASE ${quoteTableIdent(dbName)}${template}${ownerClause}`);
|
|
21
|
+
}
|
|
22
|
+
const DUPLICATE_OBJECT = "42710";
|
|
23
|
+
const UNIQUE_VIOLATION = "23505";
|
|
24
|
+
const INTERNAL_ERROR = "XX000";
|
|
25
|
+
const MAX_CAUSE_DEPTH = 8;
|
|
26
|
+
// XX000 is Postgres' generic internal error; only the concurrent catalog update counts as a lost race.
|
|
27
|
+
// The driver error carrying the message may sit below a wrapper, as for the SQLSTATE.
|
|
28
|
+
function messageInCauseChain(error, needle) {
|
|
29
|
+
let current = error;
|
|
30
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && current instanceof Error; depth++) {
|
|
31
|
+
if (current.message.includes(needle))
|
|
32
|
+
return true;
|
|
33
|
+
current = current.cause;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
function isLostRoleRace(error) {
|
|
38
|
+
const pgError = extractPgError(error);
|
|
39
|
+
if (pgError?.code === DUPLICATE_OBJECT || pgError?.code === UNIQUE_VIOLATION)
|
|
40
|
+
return true;
|
|
41
|
+
return (pgError?.code === INTERNAL_ERROR && messageInCauseChain(error, "tuple concurrently updated"));
|
|
42
|
+
}
|
|
43
|
+
// Parallel workers race on CREATE ROLE and ALTER ROLE; losing either race means the role has the
|
|
44
|
+
// attributes and password this call wanted, which is the goal.
|
|
45
|
+
export async function createApplicationRole(admin, role, password) {
|
|
46
|
+
const raw = asRawClient(admin);
|
|
47
|
+
const existing = await raw.unsafe(`SELECT 1 FROM pg_roles WHERE rolname = $1`, [role]);
|
|
48
|
+
const attributes = `LOGIN NOSUPERUSER NOBYPASSRLS PASSWORD ${quoteLiteral(password)}`;
|
|
49
|
+
// An earlier run may have created the role with another password, so an existing role is realigned.
|
|
50
|
+
const statement = existing.length > 0
|
|
51
|
+
? `ALTER ROLE ${quoteTableIdent(role)} ${attributes}`
|
|
52
|
+
: `CREATE ROLE ${quoteTableIdent(role)} ${attributes}`;
|
|
53
|
+
try {
|
|
54
|
+
await raw.unsafe(statement);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (!isLostRoleRace(error))
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function quoteLiteral(value) {
|
|
62
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
63
|
+
}
|
|
64
|
+
export async function transferDatabaseOwnership(admin, dbName, owner) {
|
|
65
|
+
await asRawClient(admin).unsafe(`ALTER DATABASE ${quoteTableIdent(dbName)} OWNER TO ${quoteTableIdent(owner)}`);
|
|
66
|
+
}
|
|
67
|
+
const OWNED_SCHEMA_PUBLIC = "public";
|
|
68
|
+
const OWNED_RELATION_KINDS = {
|
|
69
|
+
r: "TABLE",
|
|
70
|
+
p: "TABLE",
|
|
71
|
+
S: "SEQUENCE",
|
|
72
|
+
v: "VIEW",
|
|
73
|
+
m: "MATERIALIZED VIEW",
|
|
74
|
+
};
|
|
75
|
+
// Run on a connection to the database itself. REASSIGN OWNED fails for a bootstrap superuser, so owners move per relation.
|
|
76
|
+
// The rebuild schema is included: a persistent database that already rebuilt as superuser would otherwise refuse the
|
|
77
|
+
// application role's next rebuild for lack of CREATE on it.
|
|
78
|
+
export async function transferPublicSchemaOwnership(db, owner) {
|
|
79
|
+
const raw = asRawClient(db);
|
|
80
|
+
const ownedSchemas = [OWNED_SCHEMA_PUBLIC, PROJECTION_REBUILD_SCHEMA];
|
|
81
|
+
const rebuildSchema = await raw.unsafe(`SELECT 1 FROM pg_namespace WHERE nspname = $1`, [
|
|
82
|
+
PROJECTION_REBUILD_SCHEMA,
|
|
83
|
+
]);
|
|
84
|
+
if (rebuildSchema.length > 0) {
|
|
85
|
+
await raw.unsafe(`ALTER SCHEMA ${quoteTableIdent(PROJECTION_REBUILD_SCHEMA)} OWNER TO ${quoteTableIdent(owner)}`);
|
|
86
|
+
}
|
|
87
|
+
const relations = await raw.unsafe(`SELECT n.nspname, c.relname, c.relkind::text AS relkind
|
|
88
|
+
FROM pg_class c
|
|
89
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
90
|
+
JOIN pg_roles r ON r.oid = c.relowner
|
|
91
|
+
WHERE n.nspname = ANY($2::text[]) AND c.relkind IN ('r', 'p', 'S', 'v', 'm') AND r.rolname <> $1
|
|
92
|
+
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = c.oid AND d.deptype IN ('a', 'i'))`, [owner, ownedSchemas]);
|
|
93
|
+
for (const { nspname, relname, relkind } of relations) {
|
|
94
|
+
const kind = OWNED_RELATION_KINDS[relkind];
|
|
95
|
+
if (kind === undefined)
|
|
96
|
+
continue;
|
|
97
|
+
await raw.unsafe(`ALTER ${kind} ${quoteTableIdent(nspname)}.${quoteTableIdent(relname)} OWNER TO ${quoteTableIdent(owner)}`);
|
|
98
|
+
}
|
|
18
99
|
}
|
|
19
100
|
export async function renameDatabase(db, fromName, toName) {
|
|
20
101
|
await asRawClient(db).unsafe(`ALTER DATABASE ${quoteTableIdent(fromName)} RENAME TO ${quoteTableIdent(toName)}`);
|
package/dist/db/query.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { AggregateRow, AggregateSpec } from "@cosmicdrift/kumiko-types/aggr
|
|
|
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
4
|
import type { SelectOptions, WhereObject } from "@cosmicdrift/kumiko-types/where-clause-types";
|
|
5
|
+
import { type ClosedConnectionContext } from "./closed-connection.ts";
|
|
6
|
+
import { type PreparedValue } from "./driver-value-preparation.ts";
|
|
5
7
|
import type { EntityTableMeta } from "./entity-table-meta.ts";
|
|
6
8
|
import { type TableInfo } from "./table-info.ts";
|
|
7
9
|
import { asEntityTableMeta, requireEntityTableMeta } from "./table-introspection.ts";
|
|
@@ -32,6 +34,7 @@ type AnyRow = any;
|
|
|
32
34
|
export type { JsonTextMatch, OrderByClause, SelectOptions, WhereObject, WhereOperator, WhereValue, } from "@cosmicdrift/kumiko-types/where-clause-types";
|
|
33
35
|
export type TableLike = EntityTableMeta | any;
|
|
34
36
|
export declare function coerceRow<TOut = Record<string, unknown>>(row: Record<string, unknown>, info: TableInfo): TOut;
|
|
37
|
+
export declare function unsafeLoggingClosedConnection<TRow = unknown>(db: AnyDb, sqlText: string, params: readonly unknown[], context: ClosedConnectionContext): Promise<readonly TRow[]>;
|
|
35
38
|
export declare function unsafeReadRetrying<TRow>(db: AnyDb, sqlText: string, params: readonly unknown[]): Promise<readonly TRow[]>;
|
|
36
39
|
export declare function selectMany<TRow = AnyRow>(db: AnyDb, table: TableLike, where?: WhereObject, options?: SelectOptions): Promise<readonly TRow[]>;
|
|
37
40
|
export declare function fetchOne<TRow = AnyRow>(db: AnyDb, table: TableLike, where: WhereObject): Promise<TRow | undefined>;
|
|
@@ -63,7 +66,18 @@ export declare function updateMany<TRow = AnyRow>(db: AnyDb, table: WritableTabl
|
|
|
63
66
|
export declare function updateMany<TRow = AnyRow>(db: ExecutorManagedWriteCapability, table: EntityTableMeta, set: Record<string, unknown>, where: WhereObject): Promise<readonly TRow[]>;
|
|
64
67
|
export declare function deleteMany(db: AnyDb, table: WritableTable, where: WhereObject): Promise<number>;
|
|
65
68
|
export declare function deleteMany(db: ExecutorManagedWriteCapability, table: EntityTableMeta, where: WhereObject): Promise<number>;
|
|
69
|
+
export type InsertEntry = {
|
|
70
|
+
readonly field: string;
|
|
71
|
+
readonly col: string;
|
|
72
|
+
readonly prepared: PreparedValue;
|
|
73
|
+
};
|
|
74
|
+
export declare function insertEntries(info: TableInfo, values: Record<string, unknown>): InsertEntry[];
|
|
66
75
|
export declare function resolveConflictColumns(table: TableLike, info: TableInfo, conflictKeys: readonly string[] | undefined): readonly string[];
|
|
76
|
+
export declare function conflictUpdateParts(info: TableInfo, entries: readonly InsertEntry[], update: Record<string, unknown> | undefined, skipCols: ReadonlySet<string>, params: unknown[]): string[];
|
|
77
|
+
export declare function buildInsertSql(info: TableInfo, entries: readonly InsertEntry[]): {
|
|
78
|
+
sqlPrefix: string;
|
|
79
|
+
params: unknown[];
|
|
80
|
+
};
|
|
67
81
|
export type UpsertOnConflictOptions = {
|
|
68
82
|
readonly conflictKeys: readonly string[];
|
|
69
83
|
/** Columns to set on conflict. Default: EXCLUDED for every inserted non-key column. */
|
package/dist/db/query.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Typed query helpers over the
|
|
1
|
+
// Typed query helpers over the Bun.SQL driver: EntityTableMeta in, rows out.
|
|
2
2
|
//
|
|
3
3
|
// API:
|
|
4
4
|
// selectMany<T>(db, table, where?, opts?) → readonly T[]
|
|
@@ -24,6 +24,7 @@ import { executorManagedWriteDenied } from "./executor-managed-tables.js";
|
|
|
24
24
|
import { extractPgError } from "./pg-error.js";
|
|
25
25
|
import { extractTableInfo } from "./table-info.js";
|
|
26
26
|
import { asEntityTableMeta, requireEntityTableMeta } from "./table-introspection.js";
|
|
27
|
+
import { openSerializedSavepoint } from "./transaction-serialization.js";
|
|
27
28
|
import { buildWhereClause } from "./where-clause.js";
|
|
28
29
|
// table() (dialect) stores the canonical, shadow-proof EntityTableMeta under
|
|
29
30
|
// `Symbol.for("kumiko:schema:Meta")`. The column handles on the table object are
|
|
@@ -33,15 +34,18 @@ import { buildWhereClause } from "./where-clause.js";
|
|
|
33
34
|
export { asEntityTableMeta, requireEntityTableMeta };
|
|
34
35
|
export function hasBegin(client) {
|
|
35
36
|
// Tx/savepoint handles can keep a begin() that throws; only savepoint() tells them apart from a pool.
|
|
36
|
-
return typeof client.begin === "function" &&
|
|
37
|
+
return typeof client.begin === "function" && !hasSavepoint(client);
|
|
38
|
+
}
|
|
39
|
+
function hasSavepoint(client) {
|
|
40
|
+
return typeof client.savepoint === "function";
|
|
37
41
|
}
|
|
38
42
|
function isRawClient(value) {
|
|
39
43
|
return typeof propertyOf(value, "unsafe") === "function";
|
|
40
44
|
}
|
|
41
45
|
export function asRawClient(db) {
|
|
42
|
-
// Direct:
|
|
43
|
-
// expose `.unsafe`; only
|
|
44
|
-
// `.savepoint` for
|
|
46
|
+
// Direct: Bun.SQL pool / transaction handle. Both
|
|
47
|
+
// expose `.unsafe`; only the pool starts a transaction — a transaction handle uses
|
|
48
|
+
// `.savepoint` for nesting. We only require `.unsafe` here; callers that
|
|
45
49
|
// need `.begin` (e.g. `transaction()`) verify it themselves.
|
|
46
50
|
if (isRawClient(db))
|
|
47
51
|
return db;
|
|
@@ -52,15 +56,15 @@ export function asRawClient(db) {
|
|
|
52
56
|
"tenant filter — use a ctx.db.<method>, or ctx.db.unsafeRaw() with escapeHatch: " +
|
|
53
57
|
"{ reason } on the handler/hook (r.systemScope() features: ctx.systemDb.unsafeRaw(reason)).");
|
|
54
58
|
}
|
|
55
|
-
// Wrapper handle: $client =
|
|
59
|
+
// Wrapper handle: $client = Bun.SQL pool.
|
|
56
60
|
const $client = propertyOf(db, "$client");
|
|
57
61
|
if (isRawClient($client))
|
|
58
62
|
return $client;
|
|
59
|
-
// Wrapper transaction handle: session.client =
|
|
63
|
+
// Wrapper transaction handle: session.client = Bun.SQL pool.
|
|
60
64
|
const sessionClient = propertyOf(propertyOf(db, "session"), "client");
|
|
61
65
|
if (isRawClient(sessionClient))
|
|
62
66
|
return sessionClient;
|
|
63
|
-
throw new Error("asRawClient: db argument has no .unsafe() — pass a
|
|
67
|
+
throw new Error("asRawClient: db argument has no .unsafe() — pass a Bun.SQL pool or a transaction handle.");
|
|
64
68
|
}
|
|
65
69
|
export function runnerCanBeginTransaction(db) {
|
|
66
70
|
return hasBegin(asRawClient(db));
|
|
@@ -71,36 +75,57 @@ function savepointRunnerOf(sp) {
|
|
|
71
75
|
}
|
|
72
76
|
export async function runInSavepoint(tx, fn) {
|
|
73
77
|
const raw = asRawClient(tx);
|
|
74
|
-
if (
|
|
75
|
-
throw new Error("runInSavepoint: transaction handle has no savepoint() — pass the driver tx (
|
|
78
|
+
if (!hasSavepoint(raw)) {
|
|
79
|
+
throw new Error("runInSavepoint: transaction handle has no savepoint() — pass the driver tx (Bun.SQL TransactionSQL).");
|
|
76
80
|
}
|
|
77
|
-
return raw
|
|
81
|
+
return openSerializedSavepoint(raw, (sp) => fn(savepointRunnerOf(sp)));
|
|
82
|
+
}
|
|
83
|
+
// Same error-confinement as runInSavepoint, but for call sites that don't
|
|
84
|
+
// know whether `db` is a bare pool connection or an active transaction
|
|
85
|
+
// (e.g. the CRUD executor, invoked both from a dispatcher tx and directly
|
|
86
|
+
// against the pool by seeds/tests). A pool connection has no savepoint() —
|
|
87
|
+
// and doesn't need one, since each statement there is its own auto-committed
|
|
88
|
+
// unit and a failed statement can't poison anything downstream.
|
|
89
|
+
//
|
|
90
|
+
// `.savepoint` being present isn't proof the transaction is still open: a
|
|
91
|
+
// committed transaction handle still exposes `.savepoint`, and calling it
|
|
92
|
+
// fails (Bun.SQL: ERR_POSTGRES_INVALID_TRANSACTION_STATE, Postgres: 25P01)
|
|
93
|
+
// since there's no BEGIN left to nest into. That is now a programming error, not something
|
|
94
|
+
// to paper over by re-running `fn` without the savepoint's isolation.
|
|
95
|
+
const CONNECTION_CLOSED_CODE = "ERR_POSTGRES_CONNECTION_CLOSED";
|
|
96
|
+
const SAVEPOINT_ON_COMMITTED_CODES = new Set([
|
|
97
|
+
"25P01",
|
|
98
|
+
"ERR_POSTGRES_INVALID_TRANSACTION_STATE",
|
|
99
|
+
]);
|
|
100
|
+
function isSavepointOnDeadTransactionCode(code) {
|
|
101
|
+
return (code !== undefined &&
|
|
102
|
+
(code === CONNECTION_CLOSED_CODE || SAVEPOINT_ON_COMMITTED_CODES.has(code)));
|
|
78
103
|
}
|
|
79
104
|
export async function runInSavepointIfSupported(db, fn) {
|
|
80
105
|
const raw = asRawClient(db);
|
|
81
|
-
if (
|
|
106
|
+
if (!hasSavepoint(raw))
|
|
82
107
|
return fn(savepointRunnerOf(db));
|
|
83
108
|
// An error from inside fn must never be remapped, even if it carries a closed-connection code.
|
|
84
109
|
let fnStarted = false;
|
|
85
110
|
try {
|
|
86
|
-
return await raw
|
|
111
|
+
return await openSerializedSavepoint(raw, (sp) => {
|
|
87
112
|
fnStarted = true;
|
|
88
113
|
return fn(savepointRunnerOf(sp));
|
|
89
114
|
});
|
|
90
115
|
}
|
|
91
116
|
catch (e) {
|
|
92
117
|
const code = extractPgError(e)?.code;
|
|
93
|
-
if (!fnStarted && (code
|
|
118
|
+
if (!fnStarted && isSavepointOnDeadTransactionCode(code)) {
|
|
94
119
|
throw new InternalError({
|
|
95
|
-
message: code ===
|
|
96
|
-
? "runInSavepointIfSupported:
|
|
97
|
-
"this is almost always database work in an afterCommit hook. afterCommit is the default " +
|
|
98
|
-
"phase of r.hook/postSave/postDelete. Use HookPhases.inTransaction for writes that must " +
|
|
99
|
-
"roll back with the trigger, or ctx.dbOutsideTransaction for work that must survive a rollback."
|
|
100
|
-
: "runInSavepointIfSupported: the transaction handle's connection is gone — the transaction " +
|
|
120
|
+
message: code === CONNECTION_CLOSED_CODE
|
|
121
|
+
? "runInSavepointIfSupported: the transaction handle's connection is gone — the transaction " +
|
|
101
122
|
"already committed (almost always database work in an afterCommit hook) or the connection dropped. " +
|
|
102
123
|
"afterCommit is the default phase of r.hook/postSave/postDelete. Use HookPhases.inTransaction " +
|
|
103
|
-
"for writes that must roll back with the trigger, or ctx.
|
|
124
|
+
"for writes that must roll back with the trigger, or ctx.db.outsideTransaction for work that must survive a rollback."
|
|
125
|
+
: "runInSavepointIfSupported: savepoint on a transaction that has already committed — " +
|
|
126
|
+
"this is almost always database work in an afterCommit hook. afterCommit is the default " +
|
|
127
|
+
"phase of r.hook/postSave/postDelete. Use HookPhases.inTransaction for writes that must " +
|
|
128
|
+
"roll back with the trigger, or ctx.db.outsideTransaction for work that must survive a rollback.",
|
|
104
129
|
cause: e instanceof Error ? e : undefined,
|
|
105
130
|
});
|
|
106
131
|
}
|
|
@@ -131,7 +156,8 @@ function assertNotTenantScoped(db, fnName) {
|
|
|
131
156
|
if (tenantDbDelegate(db) !== undefined) {
|
|
132
157
|
throw new Error(`${fnName}: received a tenant-scoped db but this helper does not apply the tenant filter. ` +
|
|
133
158
|
"Pass ctx.db.unsafeRaw() (with escapeHatch: { reason } on the handler/hook) for an " +
|
|
134
|
-
"explicit cross-tenant op, or use a scoped TenantDb method
|
|
159
|
+
"explicit cross-tenant op, or use a scoped TenantDb method " +
|
|
160
|
+
"(for an upsert: ctx.db.upsert(table, values, { conflictTarget })).");
|
|
135
161
|
}
|
|
136
162
|
}
|
|
137
163
|
export function coerceRow(row, info) {
|
|
@@ -143,7 +169,7 @@ function coerceRows(rows, info) {
|
|
|
143
169
|
}
|
|
144
170
|
// Write and other non-retried paths: log when a closed-connection error leaves
|
|
145
171
|
// the db layer, rethrow unchanged.
|
|
146
|
-
async function unsafeLoggingClosedConnection(db, sqlText, params, context) {
|
|
172
|
+
export async function unsafeLoggingClosedConnection(db, sqlText, params, context) {
|
|
147
173
|
const raw = asRawClient(db);
|
|
148
174
|
try {
|
|
149
175
|
return await raw.unsafe(sqlText, params);
|
|
@@ -453,14 +479,14 @@ export async function deleteMany(db, table, where) {
|
|
|
453
479
|
operation: "deleteMany",
|
|
454
480
|
table: info.name,
|
|
455
481
|
});
|
|
456
|
-
//
|
|
482
|
+
// Bun.SQL reports the affected-row count of a DELETE on the result, no RETURNING needed.
|
|
457
483
|
const count = propertyOf(result, "count");
|
|
458
484
|
if (typeof count !== "number") {
|
|
459
485
|
throw new Error(`deleteMany(${info.name}): the driver result carries no affected-row count`);
|
|
460
486
|
}
|
|
461
487
|
return count;
|
|
462
488
|
}
|
|
463
|
-
function insertEntries(info, values) {
|
|
489
|
+
export function insertEntries(info, values) {
|
|
464
490
|
return Object.entries(values)
|
|
465
491
|
.filter(([k]) => info.hasColumn(k))
|
|
466
492
|
.map(([k, v]) => {
|
|
@@ -486,7 +512,31 @@ export function resolveConflictColumns(table, info, conflictKeys) {
|
|
|
486
512
|
return [info.columnOf("id")];
|
|
487
513
|
throw new Error("upsert: cannot infer conflict keys — pass conflictKeys explicitly");
|
|
488
514
|
}
|
|
489
|
-
|
|
515
|
+
// Appends bound values to `params`; `skipCols` only applies to the default EXCLUDED set.
|
|
516
|
+
export function conflictUpdateParts(info, entries, update, skipCols, params) {
|
|
517
|
+
const parts = [];
|
|
518
|
+
if (update === undefined) {
|
|
519
|
+
for (const e of entries) {
|
|
520
|
+
if (skipCols.has(e.col))
|
|
521
|
+
continue;
|
|
522
|
+
parts.push(`${quoteIdent(e.col)} = EXCLUDED.${quoteIdent(e.col)}`);
|
|
523
|
+
}
|
|
524
|
+
return parts;
|
|
525
|
+
}
|
|
526
|
+
for (const [field, value] of Object.entries(update)) {
|
|
527
|
+
const col = info.columnOf(field);
|
|
528
|
+
const p = prepareValue(value, info.pgTypeOf(col));
|
|
529
|
+
if (p.kind === "literal") {
|
|
530
|
+
parts.push(`${quoteIdent(col)} = ${p.literal}`);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
params.push(p.bound);
|
|
534
|
+
parts.push(`${quoteIdent(col)} = $${params.length}${p.sql}`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return parts;
|
|
538
|
+
}
|
|
539
|
+
export function buildInsertSql(info, entries) {
|
|
490
540
|
if (entries.length === 0)
|
|
491
541
|
throw new Error("insert: empty values object");
|
|
492
542
|
const cols = entries.map((e) => quoteIdent(e.col)).join(", ");
|
|
@@ -556,28 +606,7 @@ async function upsertOnConflictChecked(db, table, values, options) {
|
|
|
556
606
|
const conflictCols = resolveConflictColumns(table, info, options.conflictKeys);
|
|
557
607
|
const conflictSet = new Set(conflictCols);
|
|
558
608
|
const { sqlPrefix, params } = buildInsertSql(info, entries);
|
|
559
|
-
const updateParts =
|
|
560
|
-
let idx = params.length + 1;
|
|
561
|
-
if (options.update !== undefined) {
|
|
562
|
-
for (const [field, value] of Object.entries(options.update)) {
|
|
563
|
-
const col = info.columnOf(field);
|
|
564
|
-
const p = prepareValue(value, info.pgTypeOf(col));
|
|
565
|
-
if (p.kind === "literal") {
|
|
566
|
-
updateParts.push(`${quoteIdent(col)} = ${p.literal}`);
|
|
567
|
-
}
|
|
568
|
-
else {
|
|
569
|
-
updateParts.push(`${quoteIdent(col)} = $${idx++}${p.sql}`);
|
|
570
|
-
params.push(p.bound);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
for (const e of entries) {
|
|
576
|
-
if (conflictSet.has(e.col))
|
|
577
|
-
continue;
|
|
578
|
-
updateParts.push(`${quoteIdent(e.col)} = EXCLUDED.${quoteIdent(e.col)}`);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
609
|
+
const updateParts = conflictUpdateParts(info, entries, options.update, conflictSet, params);
|
|
581
610
|
if (updateParts.length === 0) {
|
|
582
611
|
throw new Error("upsertOnConflict: nothing to update on conflict");
|
|
583
612
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { ReferenceDataDef } from "@cosmicdrift/kumiko-types/config";
|
|
1
2
|
import type { Registry } from "../engine/index.ts";
|
|
2
|
-
import type { ReferenceDataDef } from "../engine/types/index.ts";
|
|
3
3
|
import type { DbConnection } from "./connection.ts";
|
|
4
4
|
/**
|
|
5
5
|
* Seed reference data at boot time.
|
|
@@ -2,8 +2,10 @@ import { fetchOne } from "../db/query.js";
|
|
|
2
2
|
import { createSystemUser } from "../engine/system-user.js";
|
|
3
3
|
import { SYSTEM_TENANT_ID } from "../engine/types/index.js";
|
|
4
4
|
import { reraiseAsKumikoError } from "../errors/index.js";
|
|
5
|
+
import { getStreamVersion } from "../event-store/event-store.js";
|
|
5
6
|
import { entityTableFromRegistry } from "./entity-table-from-registry.js";
|
|
6
7
|
import { createEventStoreExecutor } from "./event-store-executor.js";
|
|
8
|
+
import { deleteRowsWithoutReplayableStream } from "./queries/event-store-orphan-rows.js";
|
|
7
9
|
import { createTenantDb } from "./tenant-db.js";
|
|
8
10
|
/**
|
|
9
11
|
* Seed reference data at boot time.
|
|
@@ -17,7 +19,12 @@ export async function seedReferenceData(defs, registry, db) {
|
|
|
17
19
|
let inserted = 0;
|
|
18
20
|
let updated = 0;
|
|
19
21
|
const by = createSystemUser(SYSTEM_TENANT_ID);
|
|
20
|
-
const tdb = createTenantDb({
|
|
22
|
+
const tdb = createTenantDb({
|
|
23
|
+
db,
|
|
24
|
+
tenantId: SYSTEM_TENANT_ID,
|
|
25
|
+
mode: "system",
|
|
26
|
+
reason: "reference data lives in the system tenant",
|
|
27
|
+
});
|
|
21
28
|
for (const def of defs) {
|
|
22
29
|
const entity = registry.getEntity(def.entityName);
|
|
23
30
|
if (!entity)
|
|
@@ -38,11 +45,21 @@ export async function seedReferenceData(defs, registry, db) {
|
|
|
38
45
|
if (keyValue === undefined)
|
|
39
46
|
continue;
|
|
40
47
|
// Soft-deleted rows are invisible to executor.update, so treat them as missing and re-create.
|
|
41
|
-
const
|
|
48
|
+
const existingRow = await fetchOne(db, table, {
|
|
42
49
|
[upsertKey]: keyValue,
|
|
43
50
|
tenantId: SYSTEM_TENANT_ID,
|
|
44
51
|
...(entity.softDelete && { isDeleted: false }),
|
|
45
52
|
});
|
|
53
|
+
// Rows seeded before reference data went through the executor have no events: update would
|
|
54
|
+
// hit a stream-version conflict and a rebuild would drop them. Drop the row and re-create
|
|
55
|
+
// it under the same id so its stream starts now.
|
|
56
|
+
let existing = existingRow;
|
|
57
|
+
let recreateId;
|
|
58
|
+
if (existingRow && (await getStreamVersion(db, existingRow.id, SYSTEM_TENANT_ID)) === 0) {
|
|
59
|
+
await deleteRowsWithoutReplayableStream(tdb, { table, ids: [existingRow.id] });
|
|
60
|
+
existing = undefined;
|
|
61
|
+
recreateId = existingRow.id;
|
|
62
|
+
}
|
|
46
63
|
if (existing) {
|
|
47
64
|
const changes = {};
|
|
48
65
|
for (const [field, value] of Object.entries(row)) {
|
|
@@ -60,7 +77,7 @@ export async function seedReferenceData(defs, registry, db) {
|
|
|
60
77
|
}
|
|
61
78
|
}
|
|
62
79
|
else {
|
|
63
|
-
const result = await executor.create({ ...row, tenantId: SYSTEM_TENANT_ID }, by, tdb);
|
|
80
|
+
const result = await executor.create({ ...row, ...(recreateId && { id: recreateId }), tenantId: SYSTEM_TENANT_ID }, by, tdb);
|
|
64
81
|
if (!result.isSuccess)
|
|
65
82
|
throw reraiseAsKumikoError(result.error);
|
|
66
83
|
inserted++;
|
package/dist/db/render-ddl.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { EntityTableMeta, IndexMeta } from "./entity-table-meta.ts";
|
|
2
|
+
import { type RowLevelSecurityMode } from "./render-rls-ddl.ts";
|
|
2
3
|
export declare function renderIndex(tableName: string, idx: IndexMeta): string;
|
|
3
|
-
export
|
|
4
|
-
|
|
4
|
+
export type RenderDdlOptions = {
|
|
5
|
+
readonly rowLevelSecurity?: RowLevelSecurityMode;
|
|
6
|
+
};
|
|
7
|
+
export declare function renderTableDdl(meta: EntityTableMeta, options?: RenderDdlOptions): readonly string[];
|
|
8
|
+
export declare function renderTablesDdl(metas: readonly EntityTableMeta[], options?: RenderDdlOptions): readonly string[];
|
package/dist/db/render-ddl.js
CHANGED
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
// NO-MAGIC-ON-DATA: this renderer never runs at app runtime, only as a build
|
|
8
8
|
// step. The runner reads the checked-in SQL, not renderer output.
|
|
9
9
|
import { pgTypeToSqlType } from "./dialect.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
10
|
+
import { renderTableRowLevelSecurityDdl } from "./render-rls-ddl.js";
|
|
11
|
+
import { quoteIdent } from "./sql-identifier.js";
|
|
13
12
|
function renderColumn(col) {
|
|
14
13
|
const parts = [quoteIdent(col.name), pgTypeToSqlType(col.pgType)];
|
|
15
14
|
if (col.identity)
|
|
@@ -36,7 +35,7 @@ export function renderIndex(tableName, idx) {
|
|
|
36
35
|
const where = idx.whereSql !== undefined ? ` WHERE ${idx.whereSql}` : "";
|
|
37
36
|
return `CREATE ${kind} IF NOT EXISTS ${quoteIdent(idx.name)} ON ${quoteIdent(tableName)} (${colList})${where};`;
|
|
38
37
|
}
|
|
39
|
-
export function renderTableDdl(meta) {
|
|
38
|
+
export function renderTableDdl(meta, options = {}) {
|
|
40
39
|
const colLines = meta.columns.map(renderColumn);
|
|
41
40
|
const lines = [...colLines];
|
|
42
41
|
if (meta.compositePrimaryKey !== undefined) {
|
|
@@ -45,11 +44,12 @@ export function renderTableDdl(meta) {
|
|
|
45
44
|
}
|
|
46
45
|
const create = `CREATE TABLE IF NOT EXISTS ${quoteIdent(meta.tableName)} (\n ${lines.join(",\n ")}\n);`;
|
|
47
46
|
const indexes = meta.indexes.map((idx) => renderIndex(meta.tableName, idx));
|
|
48
|
-
|
|
47
|
+
const policies = options.rowLevelSecurity === "on" ? renderTableRowLevelSecurityDdl(meta) : [];
|
|
48
|
+
return [create, ...indexes, ...policies];
|
|
49
49
|
}
|
|
50
|
-
export function renderTablesDdl(metas) {
|
|
50
|
+
export function renderTablesDdl(metas, options = {}) {
|
|
51
51
|
const stmts = [];
|
|
52
52
|
for (const m of metas)
|
|
53
|
-
stmts.push(...renderTableDdl(m));
|
|
53
|
+
stmts.push(...renderTableDdl(m, options));
|
|
54
54
|
return stmts;
|
|
55
55
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { EntityTableMeta } from "./entity-table-meta.ts";
|
|
2
|
+
export declare const ROW_LEVEL_SECURITY_MODES: readonly ["off", "on"];
|
|
3
|
+
export type RowLevelSecurityMode = (typeof ROW_LEVEL_SECURITY_MODES)[number];
|
|
4
|
+
export declare const DEFAULT_ROW_LEVEL_SECURITY_MODE: RowLevelSecurityMode;
|
|
5
|
+
export declare const TENANT_ISOLATION_POLICY = "kumiko_tenant_isolation";
|
|
6
|
+
export declare const SYSTEM_TENANT_READ_POLICY = "kumiko_system_tenant_read";
|
|
7
|
+
export declare const TENANT_ID_SETTING = "kumiko.tenant_id";
|
|
8
|
+
export declare const SCOPE_SETTING = "kumiko.scope";
|
|
9
|
+
export declare const SYSTEM_SCOPE = "system";
|
|
10
|
+
export declare const POLICY_COMMANDS: readonly ["ALL", "SELECT", "INSERT", "UPDATE", "DELETE"];
|
|
11
|
+
export declare const POLICY_KINDS: readonly ["PERMISSIVE", "RESTRICTIVE"];
|
|
12
|
+
export type RowLevelSecurityPolicy = {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly command: (typeof POLICY_COMMANDS)[number];
|
|
15
|
+
readonly kind: (typeof POLICY_KINDS)[number];
|
|
16
|
+
readonly rolesSql: string;
|
|
17
|
+
readonly using?: string;
|
|
18
|
+
readonly withCheck?: string;
|
|
19
|
+
};
|
|
20
|
+
export type TableRowLevelSecurity = {
|
|
21
|
+
readonly policies: readonly RowLevelSecurityPolicy[];
|
|
22
|
+
};
|
|
23
|
+
export type RowLevelSecurityFlags = {
|
|
24
|
+
readonly enabled: boolean;
|
|
25
|
+
readonly forced: boolean;
|
|
26
|
+
};
|
|
27
|
+
export declare function tenantRowLevelSecurityFor(meta: EntityTableMeta): TableRowLevelSecurity | undefined;
|
|
28
|
+
export declare function rowLevelSecurityByTable(metas: readonly EntityTableMeta[]): Record<string, TableRowLevelSecurity>;
|
|
29
|
+
export declare function renderCreatePolicy(tableSql: string, policy: RowLevelSecurityPolicy): string;
|
|
30
|
+
export declare function renderRowLevelSecurityFlags(tableSql: string, flags: RowLevelSecurityFlags): string[];
|
|
31
|
+
export declare function renderEnableRowLevelSecurity(tableSql: string, next: TableRowLevelSecurity, previous?: TableRowLevelSecurity): string[];
|
|
32
|
+
export declare function renderDisableRowLevelSecurity(tableSql: string, previous: TableRowLevelSecurity): string[];
|
|
33
|
+
export declare function renderTableRowLevelSecurityDdl(meta: EntityTableMeta): readonly string[];
|
|
34
|
+
export declare function isSameTableRowLevelSecurity(a: TableRowLevelSecurity, b: TableRowLevelSecurity): boolean;
|
|
35
|
+
export declare function isTableRowLevelSecurityRecord(value: unknown): value is Record<string, TableRowLevelSecurity>;
|