@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,7 +17,9 @@ 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
|
+
import { applyRowLevelSecurityScope } from "./queries/rls-scope.js";
|
|
20
21
|
import { asRawClient, hasBegin, } from "./query.js";
|
|
22
|
+
import { SYSTEM_ROW_LEVEL_SECURITY_SCOPE } from "./row-level-security-scope.js";
|
|
21
23
|
// Advisory-lock key (random 32-bit integer, stable). Prevents two Pods
|
|
22
24
|
// booting at the same time from both running migrate apply (the second
|
|
23
25
|
// blocks until the first finishes).
|
|
@@ -188,16 +190,16 @@ function migrationClient(db) {
|
|
|
188
190
|
if (hasBegin(client))
|
|
189
191
|
return client;
|
|
190
192
|
}
|
|
191
|
-
throw new Error("migrate-runner: db argument has no .unsafe() and .begin() (need a
|
|
193
|
+
throw new Error("migrate-runner: db argument has no .unsafe() and .begin() (need a Bun.SQL pool or a DbConnection)");
|
|
192
194
|
}
|
|
193
|
-
// Raw-SQL via
|
|
195
|
+
// Raw-SQL via Bun.SQL .unsafe().
|
|
194
196
|
async function executeRaw(db, sqlText) {
|
|
195
197
|
await migrationClient(db).unsafe(sqlText);
|
|
196
198
|
}
|
|
197
199
|
async function reserveConnection(db) {
|
|
198
200
|
const client = migrationClient(db);
|
|
199
201
|
if (typeof client.reserve !== "function") {
|
|
200
|
-
throw new Error("migrate-runner: db argument has no .reserve() (need a
|
|
202
|
+
throw new Error("migrate-runner: db argument has no .reserve() (need a Bun.SQL pool)");
|
|
201
203
|
}
|
|
202
204
|
return client.reserve();
|
|
203
205
|
}
|
|
@@ -261,6 +263,8 @@ export async function runMigrations(db, migrations) {
|
|
|
261
263
|
// apply remains stuck in the books.
|
|
262
264
|
await conn.unsafe("BEGIN");
|
|
263
265
|
try {
|
|
266
|
+
// Transaction-local, so the next migration's BEGIN starts clean; migrations rewrite every tenant's rows.
|
|
267
|
+
await applyRowLevelSecurityScope(conn, SYSTEM_ROW_LEVEL_SECURITY_SCOPE);
|
|
264
268
|
for (const stmt of m.statements) {
|
|
265
269
|
await conn.unsafe(stmt);
|
|
266
270
|
}
|
package/dist/db/money.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { EntityDefinition } from "@cosmicdrift/kumiko-types/fields";
|
|
1
2
|
import { toMinorUnits } from "../engine/minor-units.ts";
|
|
2
|
-
import type { EntityDefinition } from "../engine/types/index.ts";
|
|
3
3
|
export { toMinorUnits };
|
|
4
4
|
export declare function moneyPayloadToMinorUnits(raw: unknown): number | undefined;
|
|
5
5
|
export declare function flattenMoney(payload: Record<string, unknown>, entity: EntityDefinition): Record<string, unknown>;
|
package/dist/db/money.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// MINOR_UNIT_SCALE units for callers that need scale-exact
|
|
9
9
|
// comparisons (e.g. invoice sums) instead of round-tripping the
|
|
10
10
|
// float amount through /100·*100.
|
|
11
|
+
import { DEFAULT_CURRENCIES } from "../engine/field-helpers.js";
|
|
11
12
|
// `amountMinor` stays as a @deprecated alias of the same
|
|
12
13
|
// value until the remaining consumers
|
|
13
14
|
// (renderer/web/primitives/index.tsx,
|
|
@@ -31,7 +32,6 @@
|
|
|
31
32
|
// amount column (legacy DB convention for money — `SUM(buying_price)` remains
|
|
32
33
|
// idiomatic). `<name>Currency` is the additional column.
|
|
33
34
|
import { MINOR_UNIT_SCALE, toMinorUnits } from "../engine/minor-units.js";
|
|
34
|
-
import { DEFAULT_CURRENCIES } from "../engine/types/index.js";
|
|
35
35
|
const FRAMEWORK_DEFAULT_CURRENCY = DEFAULT_CURRENCIES[0]; // "EUR"
|
|
36
36
|
export { toMinorUnits };
|
|
37
37
|
function toMajorUnits(amountScaled) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ParentVisibilityOption } from "@cosmicdrift/kumiko-types/event-store-executor-types";
|
|
2
|
+
import type { EntityDefinition } from "@cosmicdrift/kumiko-types/fields";
|
|
2
3
|
import { type OwnershipClause } from "../engine/ownership.ts";
|
|
3
|
-
import type {
|
|
4
|
+
import type { SessionUser } from "../engine/types/index.ts";
|
|
4
5
|
import type { Table } from "./event-store-executor-context.ts";
|
|
5
6
|
import type { TenantDb } from "./tenant-db.ts";
|
|
6
7
|
export type { ParentVisibilityOption } from "@cosmicdrift/kumiko-types/event-store-executor-types";
|
package/dist/db/pg-error.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ export type PgErrorInfo = {
|
|
|
3
3
|
readonly constraint_name: string | undefined;
|
|
4
4
|
};
|
|
5
5
|
export declare function extractPgError(e: unknown): PgErrorInfo | null;
|
|
6
|
+
export declare function isInvalidDateTimeValue(e: unknown): boolean;
|
|
6
7
|
export declare function isUniqueViolation(e: unknown): boolean;
|
|
7
8
|
export declare function isTableAlreadyExists(e: unknown): boolean;
|
|
8
9
|
export declare function isLockNotAvailable(e: unknown): boolean;
|
|
9
10
|
export declare function isUndefinedTable(e: unknown): boolean;
|
|
11
|
+
export declare function isRowLevelSecurityViolation(e: unknown): boolean;
|
|
10
12
|
export declare function constraintOf(e: unknown): string | undefined;
|
package/dist/db/pg-error.js
CHANGED
|
@@ -1,31 +1,43 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
// the idempotency-key index (caller-side replay signal).
|
|
1
|
+
// Bun.SQL server errors carry the SQLSTATE in `errno` and the constraint in
|
|
2
|
+
// `constraint` (`code` is the generic ERR_POSTGRES_SERVER_ERROR). Client-side
|
|
3
|
+
// failures have no `errno`, so their `code` (e.g. ERR_POSTGRES_CONNECTION_CLOSED)
|
|
4
|
+
// is returned instead. The `.cause` chain is walked so wrapped errors resolve too.
|
|
5
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
7
6
|
const MAX_CAUSE_DEPTH = 8;
|
|
7
|
+
function stringOrUndefined(value) {
|
|
8
|
+
return typeof value === "string" ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
function infoOfLayer(layer) {
|
|
11
|
+
const sqlState = stringOrUndefined(propertyOf(layer, "errno"));
|
|
12
|
+
if (sqlState !== undefined) {
|
|
13
|
+
return {
|
|
14
|
+
code: sqlState,
|
|
15
|
+
constraint_name: stringOrUndefined(propertyOf(layer, "constraint")),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const code = stringOrUndefined(propertyOf(layer, "code"));
|
|
19
|
+
return code === undefined ? null : { code, constraint_name: undefined };
|
|
20
|
+
}
|
|
8
21
|
export function extractPgError(e) {
|
|
9
|
-
if (typeof e !== "object" || e === null)
|
|
10
|
-
return null;
|
|
11
|
-
const layers = [];
|
|
12
22
|
let current = e;
|
|
13
23
|
// Depth cap doubles as cycle protection for self-referential causes.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const code = layer.code;
|
|
22
|
-
const constraintName = layer.constraint_name; // @cast-boundary error-details
|
|
23
|
-
if (code !== undefined || constraintName !== undefined) {
|
|
24
|
-
return { code, constraint_name: constraintName };
|
|
25
|
-
}
|
|
24
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth++) {
|
|
25
|
+
if (typeof current !== "object" || current === null)
|
|
26
|
+
return null;
|
|
27
|
+
const info = infoOfLayer(current);
|
|
28
|
+
if (info !== null)
|
|
29
|
+
return info;
|
|
30
|
+
current = propertyOf(current, "cause");
|
|
26
31
|
}
|
|
27
32
|
return null;
|
|
28
33
|
}
|
|
34
|
+
// PG SQLSTATE 22007 invalid_datetime_format and 22008 datetime_field_overflow: the
|
|
35
|
+
// column type rejects the value ("not-a-date", or "2026-02-30" for a date column).
|
|
36
|
+
const INVALID_DATETIME_VALUE_CODES = new Set(["22007", "22008"]);
|
|
37
|
+
export function isInvalidDateTimeValue(e) {
|
|
38
|
+
const code = extractPgError(e)?.code;
|
|
39
|
+
return code !== undefined && INVALID_DATETIME_VALUE_CODES.has(code);
|
|
40
|
+
}
|
|
29
41
|
export function isUniqueViolation(e) {
|
|
30
42
|
return extractPgError(e)?.code === "23505";
|
|
31
43
|
}
|
|
@@ -50,6 +62,10 @@ export function isLockNotAvailable(e) {
|
|
|
50
62
|
export function isUndefinedTable(e) {
|
|
51
63
|
return extractPgError(e)?.code === "42P01";
|
|
52
64
|
}
|
|
65
|
+
// PG SQLSTATE 42501 insufficient_privilege: a row failing a row-level-security WITH CHECK policy.
|
|
66
|
+
export function isRowLevelSecurityViolation(e) {
|
|
67
|
+
return extractPgError(e)?.code === "42501";
|
|
68
|
+
}
|
|
53
69
|
export function constraintOf(e) {
|
|
54
70
|
return extractPgError(e)?.constraint_name;
|
|
55
71
|
}
|
|
@@ -499,7 +499,7 @@ async function rekeyTenantKeyedValue(deps, target) {
|
|
|
499
499
|
const userSubject = keyedUnder?.kind === "tenant" && keyedUnder.tenantId === target.tenantId
|
|
500
500
|
? rekeyTargetOfTenantKeyedValue(target)
|
|
501
501
|
: keyedUnder?.kind === "user"
|
|
502
|
-
? currentProjectionOwnerIfOther(target, keyedUnder.userId)
|
|
502
|
+
? currentProjectionOwnerIfOther(target, fieldDef.userOwned.ownerField, keyedUnder.userId)
|
|
503
503
|
: null;
|
|
504
504
|
if (userSubject === null)
|
|
505
505
|
return { kind: "unchanged" };
|
|
@@ -536,11 +536,8 @@ function rekeyTargetOfTenantKeyedValue(target) {
|
|
|
536
536
|
// A value keyed under a previous owner moves to the owner the row has now. The
|
|
537
537
|
// projection of the event's own tenant is authoritative here: the payload of
|
|
538
538
|
// an old created event still names the owner the row had back then.
|
|
539
|
-
function currentProjectionOwnerIfOther(target, keyedUserId) {
|
|
540
|
-
const
|
|
541
|
-
if (fieldDef === undefined || !("userOwned" in fieldDef) || !fieldDef.userOwned)
|
|
542
|
-
return null;
|
|
543
|
-
const owner = nonEmptyString(target.projectionOwnerRow?.[fieldDef.userOwned.ownerField]);
|
|
539
|
+
function currentProjectionOwnerIfOther(target, ownerField, keyedUserId) {
|
|
540
|
+
const owner = nonEmptyString(target.projectionOwnerRow?.[ownerField]);
|
|
544
541
|
return owner === null || owner === keyedUserId ? null : { kind: "user", userId: owner };
|
|
545
542
|
}
|
|
546
543
|
// Only the row's own tenant key or a user key counts: another tenant's key is
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { executeRawQueryRead } from "./raw-sql.js";
|
|
2
2
|
// Called by createTenantDb with its inner runner; keeps raw SQL out of consumers.
|
|
3
|
+
// timestamptz(3): events.created_at has millisecond precision, a microsecond now() would compare later.
|
|
3
4
|
export async function queryDatabaseNow(runner) {
|
|
4
|
-
const [row] = await executeRawQueryRead(runner, "SELECT now() AS now");
|
|
5
|
+
const [row] = await executeRawQueryRead(runner, "SELECT now()::timestamptz(3) AS now");
|
|
5
6
|
if (!row)
|
|
6
7
|
throw new Error("SELECT now() returned no row");
|
|
7
8
|
return row.now;
|
package/dist/db/queries/ddl.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { DbConnection } from "../connection.ts";
|
|
2
|
+
import type { EntityTableMeta } from "../entity-table-meta.ts";
|
|
1
3
|
import type { AnyDb } from "../query.ts";
|
|
2
4
|
export declare function executeDdlStatement(db: AnyDb, sqlText: string): Promise<void>;
|
|
3
5
|
export declare function alterTableAddColumn(db: AnyDb, tableName: string, columnName: string, columnType: string, defaultClause: string, notNull: string, ifNotExists?: boolean): Promise<void>;
|
|
4
6
|
export declare function createIndexIfNotExists(db: AnyDb, indexKind: "UNIQUE INDEX" | "INDEX", indexName: string, tableName: string, columnList: string, whereSql?: string): Promise<void>;
|
|
7
|
+
export declare function applyTableRowLevelSecurity(db: DbConnection, meta: EntityTableMeta): Promise<void>;
|
package/dist/db/queries/ddl.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { asRawClient } from "../query.js";
|
|
1
|
+
import { asRawClient, transaction } from "../query.js";
|
|
2
|
+
import { renderTableRowLevelSecurityDdl, tenantRowLevelSecurityFor } from "../render-rls-ddl.js";
|
|
3
|
+
import { quoteIdent } from "../sql-identifier.js";
|
|
4
|
+
import { readRowLevelSecurityShape } from "./rls-shape.js";
|
|
2
5
|
import { quoteTableIdent } from "./table-ops.js";
|
|
3
6
|
// Generic DDL helpers used on the prod-boot path (db/table-push.ts's
|
|
4
7
|
// unsafePushTables, event-consumer-state.ts's multi-instance backfill) —
|
|
@@ -20,3 +23,33 @@ export async function createIndexIfNotExists(db, indexKind, indexName, tableName
|
|
|
20
23
|
const where = whereSql !== undefined ? ` WHERE ${whereSql}` : "";
|
|
21
24
|
await asRawClient(db).unsafe(`CREATE ${indexKind} IF NOT EXISTS ${quoteTableIdent(indexName)} ON ${quoteTableIdent(tableName)} (${columnList})${where}`);
|
|
22
25
|
}
|
|
26
|
+
// Policy texts are not compared: a hand-edited kumiko policy is the operator's to keep, and the
|
|
27
|
+
// catalog check of the boot path reports it. Foreign policies are ignored.
|
|
28
|
+
function hasKumikoRowLevelSecurity(state, meta) {
|
|
29
|
+
const spec = tenantRowLevelSecurityFor(meta);
|
|
30
|
+
if (state === undefined || spec === undefined || !state.enabled || !state.forced)
|
|
31
|
+
return false;
|
|
32
|
+
return spec.policies.every((expected) => state.policies.some((actual) => actual.name === expected.name &&
|
|
33
|
+
actual.kind === expected.kind &&
|
|
34
|
+
actual.command === expected.command));
|
|
35
|
+
}
|
|
36
|
+
export async function applyTableRowLevelSecurity(db, meta) {
|
|
37
|
+
const statements = renderTableRowLevelSecurityDdl(meta);
|
|
38
|
+
// skip: no tenant_id column, nothing to enforce
|
|
39
|
+
if (statements.length === 0)
|
|
40
|
+
return;
|
|
41
|
+
// skip: already applied, so a repeated push takes no lock and runs no DDL
|
|
42
|
+
if (hasKumikoRowLevelSecurity(await readRowLevelSecurityShape(db, meta.tableName), meta)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
await transaction(db, async (tx) => {
|
|
46
|
+
// Self-conflicting lock: parallel pushes queue up instead of failing CREATE POLICY with duplicate_object.
|
|
47
|
+
await tx.unsafe(`LOCK TABLE ${quoteIdent(meta.tableName)} IN SHARE ROW EXCLUSIVE MODE`);
|
|
48
|
+
// skip: a parallel push applied it while this one waited for the lock
|
|
49
|
+
if (hasKumikoRowLevelSecurity(await readRowLevelSecurityShape(tx, meta.tableName), meta)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
for (const statement of statements)
|
|
53
|
+
await tx.unsafe(statement);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createFallbackLogger } from "../../logging/utils.js";
|
|
2
2
|
import { constraintOf, isLockNotAvailable, isTableAlreadyExists, isUniqueViolation, } from "../pg-error.js";
|
|
3
3
|
import { asRawClient, runInNewTransaction, runnerCanBeginTransaction, unsafeReadRetrying, } from "../query.js";
|
|
4
|
+
import { innerOfScopedRunner, outsideRowLevelSecurityScope } from "../row-level-security-scope.js";
|
|
4
5
|
import { withoutStatementTimeout } from "../statement-timeout.js";
|
|
5
6
|
import { toPostgresArrayLiteral } from "./postgres-array-literal.js";
|
|
6
7
|
const log = createFallbackLogger("event-store");
|
|
@@ -51,8 +52,9 @@ async function indexValidity(client, indexName = IDEMPOTENCY_INDEX_NAME) {
|
|
|
51
52
|
`WHERE c.relname = $1 AND i.indrelid = '"kumiko_events"'::regclass`, [indexName]);
|
|
52
53
|
return rows[0]?.indisvalid;
|
|
53
54
|
}
|
|
55
|
+
// Index DDL carries no tenant data and CONCURRENTLY cannot run in a transaction, so both the pinned runner and the ambient scope are left.
|
|
54
56
|
export function ensureIdempotencyKeyIndex(db) {
|
|
55
|
-
return withoutStatementTimeout(db, buildIdempotencyKeyIndex);
|
|
57
|
+
return outsideRowLevelSecurityScope(() => withoutStatementTimeout(innerOfScopedRunner(db) ?? db, buildIdempotencyKeyIndex));
|
|
56
58
|
}
|
|
57
59
|
async function buildIdempotencyKeyIndex(db) {
|
|
58
60
|
const client = asRawClient(db);
|
|
@@ -96,7 +98,7 @@ async function buildIdempotencyKeyIndex(db) {
|
|
|
96
98
|
// tenant), which no other index covers: without this it scans every event.
|
|
97
99
|
// Same CONCURRENTLY + INVALID-rebuild handling as the idempotency index.
|
|
98
100
|
export function ensureMembershipPayloadTenantIndex(db) {
|
|
99
|
-
return withoutStatementTimeout(db, buildMembershipPayloadTenantIndex);
|
|
101
|
+
return outsideRowLevelSecurityScope(() => withoutStatementTimeout(innerOfScopedRunner(db) ?? db, buildMembershipPayloadTenantIndex));
|
|
100
102
|
}
|
|
101
103
|
async function buildMembershipPayloadTenantIndex(db) {
|
|
102
104
|
const client = asRawClient(db);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { unsafeReadRetrying } from "../query.js";
|
|
2
|
+
// Every tenant_id that owns at least one event. Loose index scan: one probe on
|
|
3
|
+
// events_tenant_type_idx (leading column tenant_id) per distinct value, so the cost follows the
|
|
4
|
+
// number of tenants, not the number of events.
|
|
5
|
+
export async function listEventTenantIds(db) {
|
|
6
|
+
const rows = await unsafeReadRetrying(db, `WITH RECURSIVE tenants AS (
|
|
7
|
+
(SELECT "tenant_id" FROM "kumiko_events" ORDER BY "tenant_id" LIMIT 1)
|
|
8
|
+
UNION ALL
|
|
9
|
+
SELECT (SELECT e."tenant_id" FROM "kumiko_events" e
|
|
10
|
+
WHERE e."tenant_id" > tenants."tenant_id"
|
|
11
|
+
ORDER BY e."tenant_id" LIMIT 1)
|
|
12
|
+
FROM tenants
|
|
13
|
+
WHERE tenants."tenant_id" IS NOT NULL
|
|
14
|
+
)
|
|
15
|
+
SELECT "tenant_id"::text AS tenant_id FROM tenants WHERE "tenant_id" IS NOT NULL`, []);
|
|
16
|
+
return rows.map((row) => row.tenant_id);
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// First statement of a rebuild tx. The rebuild runs in system scope, which the tenant isolation
|
|
2
|
+
// policy admits for every row; a table whose policies differ from the framework's shape could hide
|
|
3
|
+
// rows from the guards and let the swap drop them, so it is refused before anything is read.
|
|
4
|
+
import { asRawClient } from "../query.js";
|
|
5
|
+
import { TENANT_ISOLATION_POLICY } from "../render-rls-ddl.js";
|
|
6
|
+
import { readRowLevelSecurityShape } from "./rls-shape.js";
|
|
7
|
+
import { quoteTableIdent } from "./table-ops.js";
|
|
8
|
+
export async function assertRebuildableRowLevelSecurity(tx, tableName) {
|
|
9
|
+
// SHARE UPDATE EXCLUSIVE conflicts with CREATE/ALTER/DROP POLICY and ENABLE/FORCE (ACCESS EXCLUSIVE) but not with readers, and with itself: a second rebuild waits instead of both holding a shared lock and deadlocking on the swap's ACCESS EXCLUSIVE.
|
|
10
|
+
await asRawClient(tx).unsafe(`LOCK TABLE public.${quoteTableIdent(tableName)} IN SHARE UPDATE EXCLUSIVE MODE`);
|
|
11
|
+
const state = await readRowLevelSecurityShape(tx, tableName);
|
|
12
|
+
// skip: table has no row-level security, nothing to verify
|
|
13
|
+
if (state === undefined || !state.enabled)
|
|
14
|
+
return;
|
|
15
|
+
const isolation = state.policies.find((policy) => policy.name === TENANT_ISOLATION_POLICY);
|
|
16
|
+
const hasRestrictive = state.policies.some((policy) => policy.kind === "RESTRICTIVE");
|
|
17
|
+
const isolationIntact = isolation !== undefined &&
|
|
18
|
+
isolation.kind === "PERMISSIVE" &&
|
|
19
|
+
isolation.command === "ALL" &&
|
|
20
|
+
isolation.appliesToPublic;
|
|
21
|
+
// skip: framework policy intact, rebuild reads are safe
|
|
22
|
+
if (isolationIntact && !hasRestrictive)
|
|
23
|
+
return;
|
|
24
|
+
const listed = state.policies
|
|
25
|
+
.map((policy) => `${policy.name} (${policy.kind} ${policy.command})`)
|
|
26
|
+
.join(", ");
|
|
27
|
+
throw new Error(`rebuild: public.${tableName} has row level security that a system scope rebuild cannot rely on ` +
|
|
28
|
+
`(needs a PERMISSIVE ALL ${TENANT_ISOLATION_POLICY} policy for PUBLIC and no RESTRICTIVE policy); policies: ${listed || "(none)"}`);
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AnyDb } from "../query.ts";
|
|
2
|
+
export type ConnectedRoleAttributes = {
|
|
3
|
+
readonly roleName: string;
|
|
4
|
+
readonly superuser: boolean;
|
|
5
|
+
readonly bypassRowLevelSecurity: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type TenantTableRowLevelSecurity = {
|
|
8
|
+
readonly tableName: string;
|
|
9
|
+
readonly enabled: boolean;
|
|
10
|
+
readonly forced: boolean;
|
|
11
|
+
readonly hasIsolationPolicy: boolean;
|
|
12
|
+
readonly hasSystemReadPolicy: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare function readConnectedRoleAttributes(db: AnyDb): Promise<ConnectedRoleAttributes | undefined>;
|
|
15
|
+
export declare function readTenantTablesRowLevelSecurity(db: AnyDb): Promise<readonly TenantTableRowLevelSecurity[]>;
|
|
16
|
+
export declare function readTablesWithRowLevelSecurityEnabled(db: AnyDb): Promise<readonly string[]>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { asRawClient } from "../query.js";
|
|
2
|
+
import { SYSTEM_TENANT_READ_POLICY, TENANT_ISOLATION_POLICY } from "../render-rls-ddl.js";
|
|
3
|
+
export async function readConnectedRoleAttributes(db) {
|
|
4
|
+
const rows = await asRawClient(db).unsafe(`SELECT rolname, rolsuper, rolbypassrls FROM pg_roles WHERE rolname = current_user`);
|
|
5
|
+
const row = rows[0];
|
|
6
|
+
if (row === undefined)
|
|
7
|
+
return undefined;
|
|
8
|
+
return {
|
|
9
|
+
roleName: row.rolname,
|
|
10
|
+
superuser: row.rolsuper,
|
|
11
|
+
bypassRowLevelSecurity: row.rolbypassrls,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export async function readTenantTablesRowLevelSecurity(db) {
|
|
15
|
+
const rows = await asRawClient(db).unsafe(`SELECT c.relname, c.relrowsecurity, c.relforcerowsecurity,
|
|
16
|
+
EXISTS (SELECT 1 FROM pg_policies p
|
|
17
|
+
WHERE p.schemaname = 'public' AND p.tablename = c.relname AND p.policyname = $1) AS has_isolation,
|
|
18
|
+
EXISTS (SELECT 1 FROM pg_policies p
|
|
19
|
+
WHERE p.schemaname = 'public' AND p.tablename = c.relname AND p.policyname = $2) AS has_system_read
|
|
20
|
+
FROM pg_class c
|
|
21
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
22
|
+
WHERE n.nspname = 'public' AND c.relkind IN ('r', 'p')
|
|
23
|
+
AND EXISTS (SELECT 1 FROM pg_attribute a
|
|
24
|
+
WHERE a.attrelid = c.oid AND a.attname = 'tenant_id' AND a.attnum > 0 AND NOT a.attisdropped)
|
|
25
|
+
ORDER BY c.relname COLLATE "C"`, [TENANT_ISOLATION_POLICY, SYSTEM_TENANT_READ_POLICY]);
|
|
26
|
+
return rows.map((row) => ({
|
|
27
|
+
tableName: row.relname,
|
|
28
|
+
enabled: row.relrowsecurity,
|
|
29
|
+
forced: row.relforcerowsecurity,
|
|
30
|
+
hasIsolationPolicy: row.has_isolation,
|
|
31
|
+
hasSystemReadPolicy: row.has_system_read,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
export async function readTablesWithRowLevelSecurityEnabled(db) {
|
|
35
|
+
const rows = await asRawClient(db).unsafe(`SELECT c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
36
|
+
WHERE n.nspname = 'public' AND c.relkind IN ('r', 'p') AND c.relrowsecurity
|
|
37
|
+
ORDER BY c.relname COLLATE "C"`);
|
|
38
|
+
return rows.map((row) => row.relname);
|
|
39
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type AnyDb } from "../query.ts";
|
|
2
|
+
import { type RowLevelSecurityFlags, type TableRowLevelSecurity } from "../render-rls-ddl.ts";
|
|
3
|
+
export type RowLevelSecurityState = RowLevelSecurityFlags & TableRowLevelSecurity;
|
|
4
|
+
export declare function readRowLevelSecurityState(db: AnyDb, schemaName: string, tableName: string): Promise<RowLevelSecurityState | undefined>;
|
|
5
|
+
export declare function describeRowLevelSecurityDifference(expected: RowLevelSecurityState, actual: RowLevelSecurityState | undefined): string | undefined;
|
|
6
|
+
export declare function restoreRowLevelSecurityState(tx: AnyDb, tableName: string, expected: RowLevelSecurityState): Promise<void>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Reads and restores a table's row level security state from the Postgres catalog.
|
|
2
|
+
// The shadow swap rebuilds a table from EntityTableMeta, which carries no policies:
|
|
3
|
+
// the live state is read before the swap and re-applied (then verified) afterwards.
|
|
4
|
+
import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
5
|
+
import { asRawClient } from "../query.js";
|
|
6
|
+
import { POLICY_COMMANDS, POLICY_KINDS, renderCreatePolicy, } from "../render-rls-ddl.js";
|
|
7
|
+
import { quoteTableIdent } from "./table-ops.js";
|
|
8
|
+
function toPolicy(row) {
|
|
9
|
+
if (!isOneOf(POLICY_COMMANDS, row.cmd) || !isOneOf(POLICY_KINDS, row.permissive)) {
|
|
10
|
+
throw new Error(`row level security: policy "${row.policyname}" has unsupported command "${row.cmd}" or kind "${row.permissive}"`);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
name: row.policyname,
|
|
14
|
+
command: row.cmd,
|
|
15
|
+
kind: row.permissive,
|
|
16
|
+
rolesSql: row.roles_sql,
|
|
17
|
+
...(row.qual !== null && { using: row.qual }),
|
|
18
|
+
...(row.with_check !== null && { withCheck: row.with_check }),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export async function readRowLevelSecurityState(db, schemaName, tableName) {
|
|
22
|
+
const raw = asRawClient(db);
|
|
23
|
+
const flagRows = await raw.unsafe(`SELECT c.relrowsecurity, c.relforcerowsecurity
|
|
24
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
25
|
+
WHERE n.nspname = $1 AND c.relname = $2`, [schemaName, tableName]);
|
|
26
|
+
const flags = flagRows[0];
|
|
27
|
+
if (flags === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
const policyRows = await raw.unsafe(`SELECT policyname, permissive, cmd, qual, with_check,
|
|
30
|
+
array_to_string(ARRAY(SELECT CASE WHEN r = 'public' THEN 'PUBLIC' ELSE quote_ident(r) END
|
|
31
|
+
FROM unnest(roles) AS r ORDER BY r::text COLLATE "C"), ', ') AS roles_sql
|
|
32
|
+
FROM pg_policies WHERE schemaname = $1 AND tablename = $2
|
|
33
|
+
ORDER BY policyname COLLATE "C"`, [schemaName, tableName]);
|
|
34
|
+
return {
|
|
35
|
+
enabled: flags.relrowsecurity,
|
|
36
|
+
forced: flags.relforcerowsecurity,
|
|
37
|
+
policies: policyRows.map(toPolicy),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function describePolicyDifference(expected, actual) {
|
|
41
|
+
if (actual === undefined)
|
|
42
|
+
return `policy ${expected.name} missing`;
|
|
43
|
+
const fields = ["command", "kind", "rolesSql", "using", "withCheck"];
|
|
44
|
+
const differing = fields.filter((field) => expected[field] !== actual[field]);
|
|
45
|
+
return differing.length > 0
|
|
46
|
+
? `policy ${expected.name} differs in ${differing.join(", ")}`
|
|
47
|
+
: undefined;
|
|
48
|
+
}
|
|
49
|
+
export function describeRowLevelSecurityDifference(expected, actual) {
|
|
50
|
+
if (actual === undefined)
|
|
51
|
+
return "table missing";
|
|
52
|
+
const differences = [];
|
|
53
|
+
if (expected.enabled !== actual.enabled)
|
|
54
|
+
differences.push(`enabled ${expected.enabled} -> ${actual.enabled}`);
|
|
55
|
+
if (expected.forced !== actual.forced)
|
|
56
|
+
differences.push(`forced ${expected.forced} -> ${actual.forced}`);
|
|
57
|
+
const actualByName = new Map(actual.policies.map((policy) => [policy.name, policy]));
|
|
58
|
+
for (const policy of expected.policies) {
|
|
59
|
+
const difference = describePolicyDifference(policy, actualByName.get(policy.name));
|
|
60
|
+
if (difference !== undefined)
|
|
61
|
+
differences.push(difference);
|
|
62
|
+
}
|
|
63
|
+
const expectedNames = new Set(expected.policies.map((policy) => policy.name));
|
|
64
|
+
for (const policy of actual.policies) {
|
|
65
|
+
if (!expectedNames.has(policy.name))
|
|
66
|
+
differences.push(`unexpected policy ${policy.name}`);
|
|
67
|
+
}
|
|
68
|
+
return differences.length > 0 ? differences.join("; ") : undefined;
|
|
69
|
+
}
|
|
70
|
+
export async function restoreRowLevelSecurityState(tx, tableName, expected) {
|
|
71
|
+
const raw = asRawClient(tx);
|
|
72
|
+
const tableSql = `public.${quoteTableIdent(tableName)}`;
|
|
73
|
+
for (const policy of expected.policies) {
|
|
74
|
+
await raw.unsafe(renderCreatePolicy(tableSql, policy));
|
|
75
|
+
}
|
|
76
|
+
// One ALTER TABLE for both flags: each ALTER TABLE is a separate DDL command (and event-trigger firing) on the swap's critical path.
|
|
77
|
+
await raw.unsafe(`ALTER TABLE ${tableSql} ${expected.enabled ? "ENABLE" : "DISABLE"} ROW LEVEL SECURITY, ${expected.forced ? "FORCE" : "NO FORCE"} ROW LEVEL SECURITY`);
|
|
78
|
+
const difference = describeRowLevelSecurityDifference(expected, await readRowLevelSecurityState(tx, "public", tableName));
|
|
79
|
+
if (difference !== undefined) {
|
|
80
|
+
throw new Error(`shadow swap: row level security of public.${tableName} differs after the swap: ${difference}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { RowLevelSecurityScope } from "../row-level-security-scope.ts";
|
|
2
|
+
export declare function buildRowLevelSecurityScopeQuery(runner: unknown, scope: RowLevelSecurityScope | undefined): PromiseLike<unknown>;
|
|
3
|
+
export declare function applyRowLevelSecurityScope(runner: unknown, scope: RowLevelSecurityScope | undefined): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { asRawClient } from "../query.js";
|
|
2
|
+
import { SCOPE_SETTING, SYSTEM_SCOPE, TENANT_ID_SETTING } from "../render-rls-ddl.js";
|
|
3
|
+
function scopeSettings(scope) {
|
|
4
|
+
const tenantId = scope?.kind === "tenant" ? scope.tenantId : "";
|
|
5
|
+
const systemScope = scope?.kind === "system" ? SYSTEM_SCOPE : "";
|
|
6
|
+
return [TENANT_ID_SETTING, tenantId, SCOPE_SETTING, systemScope];
|
|
7
|
+
}
|
|
8
|
+
const SET_SCOPE_SQL = `SELECT set_config($1, $2, true), set_config($3, $4, true)`;
|
|
9
|
+
// Returns the lazy driver query without sending it, so a caller can pipeline it right behind BEGIN;
|
|
10
|
+
// the caller must start it itself and in order.
|
|
11
|
+
export function buildRowLevelSecurityScopeQuery(runner, scope) {
|
|
12
|
+
return asRawClient(runner).unsafe(SET_SCOPE_SQL, [...scopeSettings(scope)]);
|
|
13
|
+
}
|
|
14
|
+
// set_config(..., true) is transaction-local; undefined clears both settings to ''.
|
|
15
|
+
export async function applyRowLevelSecurityScope(runner, scope) {
|
|
16
|
+
await asRawClient(runner).unsafe(SET_SCOPE_SQL, [...scopeSettings(scope)]);
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type AnyDb } from "../query.ts";
|
|
2
|
+
import type { RowLevelSecurityPolicy } from "../render-rls-ddl.ts";
|
|
3
|
+
export type RowLevelSecurityShape = {
|
|
4
|
+
readonly enabled: boolean;
|
|
5
|
+
readonly forced: boolean;
|
|
6
|
+
readonly policies: readonly (Pick<RowLevelSecurityPolicy, "name" | "kind" | "command"> & {
|
|
7
|
+
readonly appliesToPublic: boolean;
|
|
8
|
+
})[];
|
|
9
|
+
};
|
|
10
|
+
export declare function readRowLevelSecurityShape(db: AnyDb, tableName: string): Promise<RowLevelSecurityShape | undefined>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Flags plus policy name, kind and command of a table, read from the catalogs without deparsing
|
|
2
|
+
// policy expressions. pg_get_expr (behind the pg_policies view) opens the table with ACCESS SHARE,
|
|
3
|
+
// which queues behind a pending ACCESS EXCLUSIVE; this read never touches the table itself.
|
|
4
|
+
import { asRawClient } from "../query.js";
|
|
5
|
+
const COMMAND_BY_CODE = {
|
|
6
|
+
"*": "ALL",
|
|
7
|
+
r: "SELECT",
|
|
8
|
+
a: "INSERT",
|
|
9
|
+
w: "UPDATE",
|
|
10
|
+
d: "DELETE",
|
|
11
|
+
};
|
|
12
|
+
function commandOf(code) {
|
|
13
|
+
const command = Object.entries(COMMAND_BY_CODE).find(([key]) => key === code)?.[1];
|
|
14
|
+
// Fail closed: a policy command this check does not know cannot be judged harmless.
|
|
15
|
+
if (command === undefined)
|
|
16
|
+
throw new Error(`unknown pg_policy.polcmd code "${code}"`);
|
|
17
|
+
return command;
|
|
18
|
+
}
|
|
19
|
+
export async function readRowLevelSecurityShape(db, tableName) {
|
|
20
|
+
const rows = await asRawClient(db).unsafe(`SELECT c.relrowsecurity, c.relforcerowsecurity, p.polname, p.polpermissive, p.polcmd::text AS polcmd, (p.polroles = '{0}'::oid[]) AS polpublic
|
|
21
|
+
FROM pg_class c
|
|
22
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
23
|
+
LEFT JOIN pg_policy p ON p.polrelid = c.oid
|
|
24
|
+
WHERE n.nspname = 'public' AND c.relname = $1`, [tableName]);
|
|
25
|
+
const first = rows[0];
|
|
26
|
+
if (first === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
const policies = rows.flatMap((row) => {
|
|
29
|
+
if (row.polname === null || row.polcmd === null)
|
|
30
|
+
return [];
|
|
31
|
+
const command = commandOf(row.polcmd);
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
name: row.polname,
|
|
35
|
+
kind: row.polpermissive ? "PERMISSIVE" : "RESTRICTIVE",
|
|
36
|
+
command,
|
|
37
|
+
appliesToPublic: row.polpublic === true,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
});
|
|
41
|
+
return { enabled: first.relrowsecurity, forced: first.relforcerowsecurity, policies };
|
|
42
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { asRawClient } from "../query.js";
|
|
2
|
+
// Blocks until the lock is free. The lock is session-scoped, so lock, work and
|
|
3
|
+
// unlock share one reserved connection; pool calls may land on different sessions.
|
|
4
|
+
export async function withSessionAdvisoryLock(db, key, fn) {
|
|
5
|
+
const client = asRawClient(db);
|
|
6
|
+
if (typeof client.reserve !== "function") {
|
|
7
|
+
throw new Error("withSessionAdvisoryLock: db has no .reserve() (need a Bun.SQL pool)");
|
|
8
|
+
}
|
|
9
|
+
const conn = await client.reserve();
|
|
10
|
+
try {
|
|
11
|
+
await conn.unsafe(`SELECT pg_advisory_lock($1)`, [key]);
|
|
12
|
+
try {
|
|
13
|
+
return await fn();
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
// A dropped session already released its lock; failing here would mask fn's outcome.
|
|
17
|
+
await conn.unsafe(`SELECT pg_advisory_unlock($1)`, [key]).catch(() => undefined);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
await conn.release();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -6,7 +6,6 @@ export declare const PROJECTION_REBUILD_SCHEMA = "kumiko_rebuild";
|
|
|
6
6
|
export declare function ensureRebuildSchema(db: AnyDb): Promise<void>;
|
|
7
7
|
export declare function rebuildMetaOrThrow(table: unknown, projectionName: string): EntityTableMeta;
|
|
8
8
|
export declare function assertLiveColumnsMatchMeta(db: DbConnection | DbTx, meta: EntityTableMeta, projectionName: string): Promise<void>;
|
|
9
|
-
export declare function assertLiveTableHasNoRowLevelSecurity(tx: AnyDb, tableName: string, projectionName?: string): Promise<void>;
|
|
10
9
|
export declare function buildShadowTable(tx: AnyDb, meta: EntityTableMeta): Promise<void>;
|
|
11
10
|
export declare function fenceLiveTable(tx: AnyDb, tableName: string, lockTimeoutMs: number): Promise<void>;
|
|
12
11
|
export declare function assertNoUnreachableLiveRows(tx: AnyDb, projectionName: string, tableName: string, aggregateTypes: readonly string[], derivedRowIds?: Readonly<Record<string, ProjectionRowIdOf>>, derivedRowIdBatchSize?: number): Promise<void>;
|
|
@@ -16,4 +15,4 @@ export type ColumnDriftResult = {
|
|
|
16
15
|
readonly sample: readonly string[];
|
|
17
16
|
};
|
|
18
17
|
export declare function countColumnDrift(tx: AnyDb, tableName: string, meta: EntityTableMeta): Promise<ColumnDriftResult>;
|
|
19
|
-
export declare function swapShadowIntoLive(tx:
|
|
18
|
+
export declare function swapShadowIntoLive(tx: DbTx, tableName: string): Promise<void>;
|