pipework 0.251.0 → 0.252.1
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/CHANGELOG.md +11 -0
- package/dist/core/config/load.d.ts +2 -0
- package/dist/core/config/load.d.ts.map +1 -1
- package/dist/core/config/load.js +1 -0
- package/dist/core/config/load.js.map +1 -1
- package/dist/core/config/namespace.d.ts +1 -0
- package/dist/core/config/namespace.d.ts.map +1 -1
- package/dist/core/config/schema.d.ts +2 -0
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +1 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/surface/types.d.ts +1 -0
- package/dist/core/surface/types.d.ts.map +1 -1
- package/dist/data/db/namespace.d.ts +3 -0
- package/dist/data/db/namespace.d.ts.map +1 -1
- package/dist/data/db/namespace.js +3 -0
- package/dist/data/db/namespace.js.map +1 -1
- package/dist/data/db/pool.d.ts.map +1 -1
- package/dist/data/db/pool.js +6 -1
- package/dist/data/db/pool.js.map +1 -1
- package/dist/data/db/query-label.d.ts +5 -0
- package/dist/data/db/query-label.d.ts.map +1 -0
- package/dist/data/db/query-label.js +11 -0
- package/dist/data/db/query-label.js.map +1 -0
- package/dist/data/db/query-watch.d.ts +21 -0
- package/dist/data/db/query-watch.d.ts.map +1 -0
- package/dist/data/db/query-watch.js +90 -0
- package/dist/data/db/query-watch.js.map +1 -0
- package/dist/data/query/postgres-js/driver.js +1 -1
- package/dist/data/query/postgres-js/driver.js.map +1 -1
- package/dist/data/query/postgres-js/session.d.ts +5 -1
- package/dist/data/query/postgres-js/session.d.ts.map +1 -1
- package/dist/data/query/postgres-js/session.js +16 -11
- package/dist/data/query/postgres-js/session.js.map +1 -1
- package/dist/data/query/utils.d.ts +2 -0
- package/dist/data/query/utils.d.ts.map +1 -1
- package/dist/data/query/utils.js.map +1 -1
- package/dist/infra/credits/schema.d.ts +1 -1
- package/dist/test/plugin.d.ts.map +1 -1
- package/dist/test/plugin.js +10 -4
- package/dist/test/plugin.js.map +1 -1
- package/package.json +4 -3
- package/src/async/jobs/abortable-delay.ts +18 -0
- package/src/async/jobs/claims.ts +52 -0
- package/src/async/jobs/cron.ts +179 -0
- package/src/async/jobs/errors.ts +56 -0
- package/src/async/jobs/exclusion.ts +117 -0
- package/src/async/jobs/execute.ts +219 -0
- package/src/async/jobs/index.ts +10 -0
- package/src/async/jobs/listener.ts +135 -0
- package/src/async/jobs/namespace.ts +20 -0
- package/src/async/jobs/queue-door.ts +44 -0
- package/src/async/jobs/queue.ts +1012 -0
- package/src/async/jobs/run-script.ts +67 -0
- package/src/async/jobs/scheduler.ts +218 -0
- package/src/async/jobs/schedules-table.ts +37 -0
- package/src/async/jobs/serialize-error.ts +48 -0
- package/src/async/jobs/table.ts +191 -0
- package/src/async/pipeline/define.ts +53 -0
- package/src/async/pipeline/errors.ts +35 -0
- package/src/async/pipeline/execute.ts +53 -0
- package/src/async/pipeline/index.ts +13 -0
- package/src/async/pipeline/namespace.ts +12 -0
- package/src/async/pipeline/resume.ts +67 -0
- package/src/async/pipeline/run-steps.ts +98 -0
- package/src/async/pipeline/types.ts +57 -0
- package/src/async/state-machine/define.ts +64 -0
- package/src/async/state-machine/errors.ts +29 -0
- package/src/async/state-machine/index.ts +3 -0
- package/src/async/state-machine/namespace.ts +12 -0
- package/src/async/state-machine/transition.ts +90 -0
- package/src/async/state-machine/types.ts +37 -0
- package/src/async/steps/errors.ts +31 -0
- package/src/async/steps/graph.ts +1214 -0
- package/src/async/steps/hash.ts +40 -0
- package/src/async/steps/index.ts +7 -0
- package/src/async/steps/namespace.ts +7 -0
- package/src/async/steps/table.ts +97 -0
- package/src/async/steps/types.ts +318 -0
- package/src/audit/chain/index.ts +9 -0
- package/src/audit/chain/install.ts +113 -0
- package/src/audit/chain/retire.ts +52 -0
- package/src/audit/chain/verify.ts +257 -0
- package/src/audit/query/cursor.ts +37 -0
- package/src/audit/query/index.ts +9 -0
- package/src/audit/query/query.ts +223 -0
- package/src/audit/schema/audit-record-fields.ts +68 -0
- package/src/audit/schema/audit-record.ts +23 -0
- package/src/auth/auth/audit.ts +31 -0
- package/src/auth/auth/chain.ts +84 -0
- package/src/auth/auth/cookie.ts +136 -0
- package/src/auth/auth/email-links.ts +311 -0
- package/src/auth/auth/guard.ts +128 -0
- package/src/auth/auth/index.ts +18 -0
- package/src/auth/auth/multi-org.ts +337 -0
- package/src/auth/auth/namespace.ts +34 -0
- package/src/auth/auth/parse-expiry.ts +13 -0
- package/src/auth/auth/passwords.ts +110 -0
- package/src/auth/auth/schema.ts +110 -0
- package/src/auth/auth/sessions.ts +565 -0
- package/src/auth/auth/sign-in.ts +156 -0
- package/src/auth/auth/signing-keys.ts +69 -0
- package/src/auth/auth/types.ts +26 -0
- package/src/auth/auth/uuid.ts +10 -0
- package/src/auth/rbac/cache.ts +55 -0
- package/src/auth/rbac/check.ts +30 -0
- package/src/auth/rbac/create.ts +35 -0
- package/src/auth/rbac/index.ts +5 -0
- package/src/auth/rbac/middleware.ts +20 -0
- package/src/auth/rbac/namespace.ts +15 -0
- package/src/auth/rbac/resolve.ts +31 -0
- package/src/auth/rbac/schema.sql +31 -0
- package/src/auth/rbac/seed.ts +67 -0
- package/src/auth/rbac/tables.ts +32 -0
- package/src/auth/rbac/types.ts +65 -0
- package/src/auth/tenant/extract.ts +33 -0
- package/src/auth/tenant/in-tenant.ts +49 -0
- package/src/auth/tenant/index.ts +7 -0
- package/src/auth/tenant/namespace.ts +23 -0
- package/src/auth/tenant/propagate.ts +20 -0
- package/src/auth/tenant/rls.ts +120 -0
- package/src/auth/tenant/scope.ts +33 -0
- package/src/auth/tenant/scoped-db.ts +581 -0
- package/src/auth/tenant/types.ts +14 -0
- package/src/auth/tenant/validate.ts +27 -0
- package/src/cli/commands/add.ts +158 -0
- package/src/cli/commands/alerts.ts +98 -0
- package/src/cli/commands/check.ts +1418 -0
- package/src/cli/commands/codemod.ts +78 -0
- package/src/cli/commands/db.ts +356 -0
- package/src/cli/commands/dev.ts +101 -0
- package/src/cli/commands/generate.ts +75 -0
- package/src/cli/commands/init.ts +36 -0
- package/src/cli/commands/install.ts +28 -0
- package/src/cli/commands/lint.ts +62 -0
- package/src/cli/commands/migrate.ts +132 -0
- package/src/cli/commands/openapi.ts +63 -0
- package/src/cli/commands/remove.ts +72 -0
- package/src/cli/commands/run.ts +50 -0
- package/src/cli/commands/scope.ts +54 -0
- package/src/cli/commands/serve.ts +68 -0
- package/src/cli/commands/shard.ts +31 -0
- package/src/cli/commands/shared/config-pipework-version.ts +28 -0
- package/src/cli/commands/shared/find-config.ts +22 -0
- package/src/cli/commands/shared/load-config.ts +35 -0
- package/src/cli/commands/shared/pnpm-install.ts +95 -0
- package/src/cli/commands/shared/report-removed.ts +8 -0
- package/src/cli/commands/sync.ts +32 -0
- package/src/cli/commands/test.ts +307 -0
- package/src/cli/commands/upgrade.ts +48 -0
- package/src/cli/dev-bootstrap.ts +24 -0
- package/src/cli/errors.ts +10 -0
- package/src/cli/index.ts +303 -0
- package/src/cli/output.ts +17 -0
- package/src/cli/port.ts +15 -0
- package/src/cli/surface-kinds.ts +20 -0
- package/src/client/index.ts +4 -0
- package/src/client/poll.ts +130 -0
- package/src/client/report-fault.ts +119 -0
- package/src/core/config/app-pool.ts +19 -0
- package/src/core/config/aws-env.ts +29 -0
- package/src/core/config/base-dir.ts +24 -0
- package/src/core/config/discover.ts +64 -0
- package/src/core/config/env-resolve.ts +87 -0
- package/src/core/config/env-types.ts +116 -0
- package/src/core/config/env.ts +55 -0
- package/src/core/config/errors.ts +9 -0
- package/src/core/config/import-config.ts +40 -0
- package/src/core/config/index.ts +8 -0
- package/src/core/config/load.ts +417 -0
- package/src/core/config/namespace.ts +14 -0
- package/src/core/config/resolve.ts +195 -0
- package/src/core/config/schema.ts +488 -0
- package/src/core/config/statement-observer.ts +48 -0
- package/src/core/config/ts-inline-hooks.ts +75 -0
- package/src/core/config/ts-register.ts +4 -0
- package/src/core/config/ts-resolve-hooks.ts +101 -0
- package/src/core/errors/base.ts +49 -0
- package/src/core/errors/http.ts +146 -0
- package/src/core/errors/index.ts +2 -0
- package/src/core/errors/namespace.ts +31 -0
- package/src/core/lifecycle/drain.ts +33 -0
- package/src/core/lifecycle/health.ts +72 -0
- package/src/core/lifecycle/index.ts +4 -0
- package/src/core/lifecycle/namespace.ts +7 -0
- package/src/core/lifecycle/startup.ts +102 -0
- package/src/core/lifecycle/types.ts +36 -0
- package/src/core/logging/context.ts +60 -0
- package/src/core/logging/error-chain.ts +68 -0
- package/src/core/logging/index.ts +5 -0
- package/src/core/logging/logger.ts +65 -0
- package/src/core/logging/namespace.ts +16 -0
- package/src/core/logging/proxy.ts +22 -0
- package/src/core/logging/redact-match.ts +24 -0
- package/src/core/logging/types.ts +47 -0
- package/src/core/pipework.ts +566 -0
- package/src/core/runtime/broker.ts +69 -0
- package/src/core/surface/http.ts +133 -0
- package/src/core/surface/index.ts +11 -0
- package/src/core/surface/job-timeout.ts +40 -0
- package/src/core/surface/namespace.ts +13 -0
- package/src/core/surface/script.ts +33 -0
- package/src/core/surface/start.ts +142 -0
- package/src/core/surface/types.ts +225 -0
- package/src/core/surface/validate.ts +88 -0
- package/src/core/surface/worker.ts +528 -0
- package/src/core/validation/connectivity.ts +49 -0
- package/src/core/validation/domain.ts +99 -0
- package/src/core/validation/index.ts +5 -0
- package/src/core/validation/queue-tables.ts +312 -0
- package/src/core/validation/rls-role.ts +138 -0
- package/src/core/validation/startup.ts +51 -0
- package/src/core/validation/surfaces.ts +65 -0
- package/src/core/validation/types.ts +25 -0
- package/src/data/db/advisory-lock.ts +55 -0
- package/src/data/db/aggregates.ts +172 -0
- package/src/data/db/array-elements.ts +111 -0
- package/src/data/db/bulk-set.ts +53 -0
- package/src/data/db/col/index.ts +14 -0
- package/src/data/db/composed.ts +53 -0
- package/src/data/db/db.ts +276 -0
- package/src/data/db/excluded.ts +16 -0
- package/src/data/db/expressions.ts +473 -0
- package/src/data/db/filter.ts +90 -0
- package/src/data/db/function-source.ts +114 -0
- package/src/data/db/identifiers.ts +16 -0
- package/src/data/db/idx/index.ts +9 -0
- package/src/data/db/in-list.ts +49 -0
- package/src/data/db/index.ts +9 -0
- package/src/data/db/insert-split.ts +137 -0
- package/src/data/db/instrumentation.ts +82 -0
- package/src/data/db/maintenance.ts +28 -0
- package/src/data/db/namespace.ts +107 -0
- package/src/data/db/owner-labels.ts +9 -0
- package/src/data/db/pool.ts +187 -0
- package/src/data/db/query-label.ts +13 -0
- package/src/data/db/query-watch.ts +116 -0
- package/src/data/db/raw-client-warning.ts +37 -0
- package/src/data/db/readonly.ts +23 -0
- package/src/data/db/reservation.ts +151 -0
- package/src/data/db/schema-features.ts +14 -0
- package/src/data/db/serializable.ts +83 -0
- package/src/data/db/sql.ts +1 -0
- package/src/data/db/timeouts.ts +76 -0
- package/src/data/db/types.ts +18 -0
- package/src/data/db/unbound.ts +33 -0
- package/src/data/db/values.ts +29 -0
- package/src/data/domain/brand.ts +15 -0
- package/src/data/domain/build-factory.ts +193 -0
- package/src/data/domain/build-schema.ts +161 -0
- package/src/data/domain/build-table.ts +355 -0
- package/src/data/domain/define.ts +239 -0
- package/src/data/domain/external-registry.ts +34 -0
- package/src/data/domain/external.ts +77 -0
- package/src/data/domain/field.ts +279 -0
- package/src/data/domain/index.ts +13 -0
- package/src/data/domain/naming.ts +6 -0
- package/src/data/domain/projections.ts +57 -0
- package/src/data/domain/trace-safe-projection.ts +31 -0
- package/src/data/domain/traits.ts +108 -0
- package/src/data/domain/types.ts +289 -0
- package/src/data/jsonb/composed.ts +18 -0
- package/src/data/jsonb/index.ts +1 -0
- package/src/data/jsonb/namespace.ts +30 -0
- package/src/data/jsonb/query.ts +86 -0
- package/src/data/migrate/append-only-generate.ts +47 -0
- package/src/data/migrate/applied.ts +47 -0
- package/src/data/migrate/apply.ts +309 -0
- package/src/data/migrate/check.ts +119 -0
- package/src/data/migrate/diff.ts +357 -0
- package/src/data/migrate/door-generate.ts +130 -0
- package/src/data/migrate/framework-rls.ts +58 -0
- package/src/data/migrate/generate.ts +201 -0
- package/src/data/migrate/history.ts +215 -0
- package/src/data/migrate/index.ts +9 -0
- package/src/data/migrate/internal-definitions.ts +175 -0
- package/src/data/migrate/post-process.ts +73 -0
- package/src/data/migrate/pragma.ts +73 -0
- package/src/data/migrate/provision-framework.ts +147 -0
- package/src/data/migrate/rls-generate.ts +61 -0
- package/src/data/migrate/run.ts +124 -0
- package/src/data/migrate/safety.ts +167 -0
- package/src/data/migrate/snapshot.ts +368 -0
- package/src/data/migrate/sql-emit.ts +392 -0
- package/src/data/migrate/statements.ts +312 -0
- package/src/data/query/alias.ts +126 -0
- package/src/data/query/batch.ts +8 -0
- package/src/data/query/cache/core/cache.ts +78 -0
- package/src/data/query/cache/core/index.ts +1 -0
- package/src/data/query/cache/core/types.ts +29 -0
- package/src/data/query/cache/readme.md +230 -0
- package/src/data/query/casing.ts +76 -0
- package/src/data/query/collation.ts +11 -0
- package/src/data/query/column-builder.ts +370 -0
- package/src/data/query/column.ts +150 -0
- package/src/data/query/entity.ts +42 -0
- package/src/data/query/errors.ts +33 -0
- package/src/data/query/index.ts +14 -0
- package/src/data/query/logger.ts +47 -0
- package/src/data/query/operations.ts +45 -0
- package/src/data/query/pg-core/alias.ts +12 -0
- package/src/data/query/pg-core/checks.ts +32 -0
- package/src/data/query/pg-core/columns/all.ts +71 -0
- package/src/data/query/pg-core/columns/bigint.ts +110 -0
- package/src/data/query/pg-core/columns/bigserial.ts +128 -0
- package/src/data/query/pg-core/columns/boolean.ts +43 -0
- package/src/data/query/pg-core/columns/char.ts +85 -0
- package/src/data/query/pg-core/columns/cidr.ts +43 -0
- package/src/data/query/pg-core/columns/common.ts +366 -0
- package/src/data/query/pg-core/columns/custom.ts +232 -0
- package/src/data/query/pg-core/columns/date.common.ts +15 -0
- package/src/data/query/pg-core/columns/date.ts +112 -0
- package/src/data/query/pg-core/columns/double-precision.ts +55 -0
- package/src/data/query/pg-core/columns/enum.ts +201 -0
- package/src/data/query/pg-core/columns/index.ts +35 -0
- package/src/data/query/pg-core/columns/inet.ts +43 -0
- package/src/data/query/pg-core/columns/int.common.ts +57 -0
- package/src/data/query/pg-core/columns/integer.ts +53 -0
- package/src/data/query/pg-core/columns/interval.ts +83 -0
- package/src/data/query/pg-core/columns/json.ts +58 -0
- package/src/data/query/pg-core/columns/jsonb.ts +56 -0
- package/src/data/query/pg-core/columns/line.ts +117 -0
- package/src/data/query/pg-core/columns/macaddr.ts +43 -0
- package/src/data/query/pg-core/columns/macaddr8.ts +43 -0
- package/src/data/query/pg-core/columns/numeric.ts +227 -0
- package/src/data/query/pg-core/columns/point.ts +127 -0
- package/src/data/query/pg-core/columns/postgis_extension/geometry.ts +119 -0
- package/src/data/query/pg-core/columns/postgis_extension/utils.ts +47 -0
- package/src/data/query/pg-core/columns/real.ts +58 -0
- package/src/data/query/pg-core/columns/serial.ts +55 -0
- package/src/data/query/pg-core/columns/smallint.ts +53 -0
- package/src/data/query/pg-core/columns/smallserial.ts +60 -0
- package/src/data/query/pg-core/columns/text.ts +67 -0
- package/src/data/query/pg-core/columns/time.ts +72 -0
- package/src/data/query/pg-core/columns/timestamp.ts +163 -0
- package/src/data/query/pg-core/columns/uuid.ts +51 -0
- package/src/data/query/pg-core/columns/varchar.ts +89 -0
- package/src/data/query/pg-core/columns/vector_extension/bit.ts +68 -0
- package/src/data/query/pg-core/columns/vector_extension/halfvec.ts +80 -0
- package/src/data/query/pg-core/columns/vector_extension/sparsevec.ts +67 -0
- package/src/data/query/pg-core/columns/vector_extension/vector.ts +80 -0
- package/src/data/query/pg-core/db.ts +674 -0
- package/src/data/query/pg-core/dialect.ts +1404 -0
- package/src/data/query/pg-core/expressions.ts +25 -0
- package/src/data/query/pg-core/foreign-keys.ts +130 -0
- package/src/data/query/pg-core/index.ts +21 -0
- package/src/data/query/pg-core/indexes.ts +276 -0
- package/src/data/query/pg-core/policies.ts +56 -0
- package/src/data/query/pg-core/primary-keys.ts +62 -0
- package/src/data/query/pg-core/query-builders/count.ts +86 -0
- package/src/data/query/pg-core/query-builders/delete.ts +293 -0
- package/src/data/query/pg-core/query-builders/index.ts +7 -0
- package/src/data/query/pg-core/query-builders/insert.ts +452 -0
- package/src/data/query/pg-core/query-builders/query-builder.ts +123 -0
- package/src/data/query/pg-core/query-builders/query.ts +157 -0
- package/src/data/query/pg-core/query-builders/raw.ts +49 -0
- package/src/data/query/pg-core/query-builders/refresh-materialized-view.ts +108 -0
- package/src/data/query/pg-core/query-builders/select.ts +1346 -0
- package/src/data/query/pg-core/query-builders/select.types.ts +437 -0
- package/src/data/query/pg-core/query-builders/update.ts +626 -0
- package/src/data/query/pg-core/roles.ts +41 -0
- package/src/data/query/pg-core/schema.ts +72 -0
- package/src/data/query/pg-core/sequence.ts +41 -0
- package/src/data/query/pg-core/session.ts +292 -0
- package/src/data/query/pg-core/subquery.ts +46 -0
- package/src/data/query/pg-core/table.ts +252 -0
- package/src/data/query/pg-core/unique-constraint.ts +73 -0
- package/src/data/query/pg-core/utils/array.ts +95 -0
- package/src/data/query/pg-core/utils/index.ts +1 -0
- package/src/data/query/pg-core/utils.ts +104 -0
- package/src/data/query/pg-core/view-base.ts +14 -0
- package/src/data/query/pg-core/view-common.ts +1 -0
- package/src/data/query/pg-core/view.ts +435 -0
- package/src/data/query/pg-core/with-builder.ts +41 -0
- package/src/data/query/postgres-js/README.md +46 -0
- package/src/data/query/postgres-js/array-parser.ts +45 -0
- package/src/data/query/postgres-js/driver-error-boundary.ts +149 -0
- package/src/data/query/postgres-js/driver.ts +163 -0
- package/src/data/query/postgres-js/index.ts +2 -0
- package/src/data/query/postgres-js/session.ts +234 -0
- package/src/data/query/primary-key.ts +11 -0
- package/src/data/query/query-builders/query-builder.ts +19 -0
- package/src/data/query/query-builders/select.types.ts +175 -0
- package/src/data/query/query-promise.ts +35 -0
- package/src/data/query/relations.ts +725 -0
- package/src/data/query/runnable-query.ts +12 -0
- package/src/data/query/selection-proxy.ts +121 -0
- package/src/data/query/session.ts +8 -0
- package/src/data/query/sql/expressions/conditions.ts +741 -0
- package/src/data/query/sql/expressions/index.ts +2 -0
- package/src/data/query/sql/expressions/select.ts +45 -0
- package/src/data/query/sql/functions/aggregate.ts +129 -0
- package/src/data/query/sql/functions/index.ts +2 -0
- package/src/data/query/sql/functions/vector.ts +178 -0
- package/src/data/query/sql/index.ts +3 -0
- package/src/data/query/sql/sql.ts +794 -0
- package/src/data/query/subquery.ts +53 -0
- package/src/data/query/table.ts +208 -0
- package/src/data/query/table.utils.ts +2 -0
- package/src/data/query/tracing-utils.ts +3 -0
- package/src/data/query/tracing.ts +11 -0
- package/src/data/query/utils.ts +357 -0
- package/src/data/query/version.ts +4 -0
- package/src/data/query/view-common.ts +1 -0
- package/src/data/schema/branded.ts +13 -0
- package/src/data/schema/generated/coerce.ts +5 -0
- package/src/data/schema/generated/combinator.ts +6 -0
- package/src/data/schema/generated/encoding.ts +5 -0
- package/src/data/schema/generated/error.ts +6 -0
- package/src/data/schema/generated/format.ts +5 -0
- package/src/data/schema/generated/type-constructor.ts +10 -0
- package/src/data/schema/generated/type.ts +6 -0
- package/src/data/schema/index.ts +6 -0
- package/src/data/schema/json-schema.ts +33 -0
- package/src/data/schema/namespace.ts +55 -0
- package/src/data/schema/parse.ts +39 -0
- package/src/data/schema/types.ts +39 -0
- package/src/data/temporal/append-only.ts +134 -0
- package/src/data/temporal/close.ts +27 -0
- package/src/data/temporal/columns.ts +10 -0
- package/src/data/temporal/definition-queries.ts +70 -0
- package/src/data/temporal/errors.ts +10 -0
- package/src/data/temporal/get-current.ts +22 -0
- package/src/data/temporal/index.ts +9 -0
- package/src/data/temporal/namespace.ts +36 -0
- package/src/data/temporal/query.ts +25 -0
- package/src/data/temporal/resolve.ts +17 -0
- package/src/data/temporal/revise.ts +67 -0
- package/src/data/temporal/types.ts +22 -0
- package/src/data/valve/artifact.ts +33 -0
- package/src/data/valve/contract-diff.ts +21 -0
- package/src/data/valve/cross.ts +57 -0
- package/src/data/valve/crossing-ledger.ts +50 -0
- package/src/data/valve/decision.ts +44 -0
- package/src/data/valve/durable-crossing.ts +52 -0
- package/src/data/valve/execute-crossing.ts +77 -0
- package/src/data/valve/faucet.ts +56 -0
- package/src/data/valve/gate.ts +114 -0
- package/src/data/valve/graph.ts +77 -0
- package/src/data/valve/index.ts +53 -0
- package/src/data/valve/namespace.ts +57 -0
- package/src/data/valve/registry.ts +50 -0
- package/src/data/valve/schema/valve-crossing-fields.ts +23 -0
- package/src/data/valve/schema/valve-crossing.ts +9 -0
- package/src/data/valve/supply.ts +154 -0
- package/src/data/valve/types.ts +143 -0
- package/src/data/vector/columns.ts +27 -0
- package/src/data/vector/index.ts +4 -0
- package/src/data/vector/namespace.ts +33 -0
- package/src/data/vector/query.ts +85 -0
- package/src/data/vector/validate.ts +16 -0
- package/src/data/write/audit/actor.ts +15 -0
- package/src/data/write/audit/audit-columns.ts +18 -0
- package/src/data/write/audit/audit-select.ts +111 -0
- package/src/data/write/audit/bound-transaction.ts +14 -0
- package/src/data/write/audit/cte-compile.ts +32 -0
- package/src/data/write/audit/decode-returning.ts +47 -0
- package/src/data/write/audit/final-select.ts +43 -0
- package/src/data/write/audit/infra-audit.ts +36 -0
- package/src/data/write/audit/primary-key.ts +65 -0
- package/src/data/write/audit/projection.ts +41 -0
- package/src/data/write/audit/read-touches.ts +86 -0
- package/src/data/write/audit/touch-match.ts +78 -0
- package/src/data/write/audit/verb-delete.ts +62 -0
- package/src/data/write/audit/verb-insert.ts +36 -0
- package/src/data/write/audit/verb-select.ts +47 -0
- package/src/data/write/audit/verb-update.ts +60 -0
- package/src/data/write/audit/verb-upsert.ts +104 -0
- package/src/data/write/conflict.ts +43 -0
- package/src/data/write/delete.ts +38 -0
- package/src/data/write/execute.ts +243 -0
- package/src/data/write/index.ts +11 -0
- package/src/data/write/insert.ts +63 -0
- package/src/data/write/state.ts +43 -0
- package/src/data/write/update.ts +54 -0
- package/src/data/write/upsert.ts +58 -0
- package/src/data/write/values.ts +19 -0
- package/src/index.ts +230 -0
- package/src/infra/access-links/code.ts +70 -0
- package/src/infra/access-links/handle.ts +892 -0
- package/src/infra/access-links/index.ts +9 -0
- package/src/infra/access-links/namespace.ts +30 -0
- package/src/infra/access-links/person-check.ts +177 -0
- package/src/infra/access-links/schema.ts +71 -0
- package/src/infra/alerts/async-sources.ts +231 -0
- package/src/infra/alerts/audit.ts +36 -0
- package/src/infra/alerts/beat.ts +123 -0
- package/src/infra/alerts/client-faults.ts +111 -0
- package/src/infra/alerts/faults.ts +156 -0
- package/src/infra/alerts/handle.ts +778 -0
- package/src/infra/alerts/http.ts +192 -0
- package/src/infra/alerts/index.ts +12 -0
- package/src/infra/alerts/infra-sources.ts +180 -0
- package/src/infra/alerts/namespace.ts +81 -0
- package/src/infra/alerts/redact.ts +21 -0
- package/src/infra/alerts/request-faults.ts +41 -0
- package/src/infra/alerts/schema.ts +124 -0
- package/src/infra/alerts/sinks.ts +125 -0
- package/src/infra/alerts/source-options.ts +26 -0
- package/src/infra/alerts/sources.ts +503 -0
- package/src/infra/alerts/subsystem-sources.ts +137 -0
- package/src/infra/audit/emit.ts +87 -0
- package/src/infra/audit/index.ts +2 -0
- package/src/infra/audit/namespace.ts +22 -0
- package/src/infra/audit/schema.sql +35 -0
- package/src/infra/audit/types.ts +18 -0
- package/src/infra/aws/credentials.ts +147 -0
- package/src/infra/aws/sigv4.ts +140 -0
- package/src/infra/cache/create.ts +58 -0
- package/src/infra/cache/entry.ts +81 -0
- package/src/infra/cache/index.ts +4 -0
- package/src/infra/cache/namespace.ts +10 -0
- package/src/infra/cache/tenant.ts +93 -0
- package/src/infra/cache/types.ts +24 -0
- package/src/infra/crawl/audit.ts +35 -0
- package/src/infra/crawl/errors.ts +25 -0
- package/src/infra/crawl/handle.ts +693 -0
- package/src/infra/crawl/index.ts +9 -0
- package/src/infra/crawl/links.ts +160 -0
- package/src/infra/crawl/namespace.ts +36 -0
- package/src/infra/crawl/robots.ts +100 -0
- package/src/infra/crawl/schema.ts +89 -0
- package/src/infra/credit-line/handle.ts +883 -0
- package/src/infra/credit-line/index.ts +6 -0
- package/src/infra/credit-line/namespace.ts +7 -0
- package/src/infra/credit-line/schema.ts +96 -0
- package/src/infra/credits/handle.ts +706 -0
- package/src/infra/credits/index.ts +5 -0
- package/src/infra/credits/namespace.ts +7 -0
- package/src/infra/credits/schema.ts +83 -0
- package/src/infra/email/audit.ts +36 -0
- package/src/infra/email/inbound.ts +622 -0
- package/src/infra/email/index.ts +11 -0
- package/src/infra/email/mail.ts +517 -0
- package/src/infra/email/mime.ts +447 -0
- package/src/infra/email/namespace.ts +22 -0
- package/src/infra/email/providers.ts +111 -0
- package/src/infra/email/schema.ts +96 -0
- package/src/infra/email/sns.ts +200 -0
- package/src/infra/email/unsubscribe.ts +44 -0
- package/src/infra/fetch/address.ts +88 -0
- package/src/infra/fetch/connect-retry.ts +87 -0
- package/src/infra/fetch/handle.ts +329 -0
- package/src/infra/fetch/index.ts +7 -0
- package/src/infra/fetch/namespace.ts +70 -0
- package/src/infra/idempotency/index.ts +3 -0
- package/src/infra/idempotency/middleware.ts +87 -0
- package/src/infra/idempotency/store.ts +92 -0
- package/src/infra/idempotency/types.ts +30 -0
- package/src/infra/join-requests/handle.ts +312 -0
- package/src/infra/join-requests/index.ts +2 -0
- package/src/infra/join-requests/namespace.ts +7 -0
- package/src/infra/join-requests/schema.ts +33 -0
- package/src/infra/outbox/core.ts +165 -0
- package/src/infra/quota/errors.ts +32 -0
- package/src/infra/quota/handle.ts +215 -0
- package/src/infra/quota/index.ts +5 -0
- package/src/infra/quota/namespace.ts +7 -0
- package/src/infra/quota/schema.ts +15 -0
- package/src/infra/storage/backend.ts +34 -0
- package/src/infra/storage/handle.ts +513 -0
- package/src/infra/storage/index.ts +9 -0
- package/src/infra/storage/local.ts +104 -0
- package/src/infra/storage/namespace.ts +13 -0
- package/src/infra/storage/s3.ts +257 -0
- package/src/infra/storage/schema.ts +24 -0
- package/src/infra/support/handle.ts +854 -0
- package/src/infra/support/http.ts +229 -0
- package/src/infra/support/index.ts +7 -0
- package/src/infra/support/namespace.ts +12 -0
- package/src/infra/support/schema.ts +96 -0
- package/src/infra/webhook/inbound.ts +45 -0
- package/src/infra/webhook/index.ts +5 -0
- package/src/infra/webhook/namespace.ts +24 -0
- package/src/infra/webhook/outbound.ts +315 -0
- package/src/infra/webhook/sign.ts +32 -0
- package/src/infra/webhook/verify.ts +82 -0
- package/src/invariants/assert.ts +26 -0
- package/src/invariants/index.ts +1 -0
- package/src/lint/config.ts +56 -0
- package/src/lint/doctrine.ts +163 -0
- package/src/lint/include.ts +97 -0
- package/src/lint/index.ts +3 -0
- package/src/lint/preset.ts +10 -0
- package/src/lint/resolver.ts +218 -0
- package/src/lint/rules/no-reexports.ts +46 -0
- package/src/request/behavior/audited.ts +85 -0
- package/src/request/behavior/cached.ts +62 -0
- package/src/request/behavior/compose.ts +28 -0
- package/src/request/behavior/index.ts +5 -0
- package/src/request/behavior/namespace.ts +16 -0
- package/src/request/behavior/types.ts +26 -0
- package/src/request/behavior/versioned.ts +54 -0
- package/src/request/context/create.ts +103 -0
- package/src/request/context/guard.ts +49 -0
- package/src/request/context/index.ts +4 -0
- package/src/request/context/run-in-transaction.ts +200 -0
- package/src/request/context/store.ts +28 -0
- package/src/request/context/types.ts +50 -0
- package/src/request/di/builder.ts +535 -0
- package/src/request/di/index.ts +8 -0
- package/src/request/di/resolve.ts +271 -0
- package/src/request/di/types.ts +262 -0
- package/src/request/fitting/namespace.ts +7 -0
- package/src/request/fixture/batch.ts +71 -0
- package/src/request/fixture/builder.ts +26 -0
- package/src/request/fixture/errors.ts +10 -0
- package/src/request/fixture/index.ts +8 -0
- package/src/request/fixture/namespace.ts +23 -0
- package/src/request/fixture/paginate.ts +44 -0
- package/src/request/fixture/to-handlers.ts +260 -0
- package/src/request/fixture/types.ts +79 -0
- package/src/request/flow/act-as.ts +78 -0
- package/src/request/flow/namespace.ts +17 -0
- package/src/request/http/error-step.ts +42 -0
- package/src/request/http/file.ts +141 -0
- package/src/request/http/index.ts +4 -0
- package/src/request/http/middleware.ts +141 -0
- package/src/request/http/namespace.ts +5 -0
- package/src/request/http/rate-limit-classes.ts +170 -0
- package/src/request/http/response.ts +82 -0
- package/src/request/http/server.ts +916 -0
- package/src/request/http/sniff.ts +261 -0
- package/src/request/http/types.ts +250 -0
- package/src/request/http/upload.ts +284 -0
- package/src/request/internal/text.ts +6 -0
- package/src/request/openapi/generate.ts +205 -0
- package/src/request/openapi/index.ts +2 -0
- package/src/request/openapi/namespace.ts +7 -0
- package/src/test/auth.ts +13 -0
- package/src/test/auto-setup.ts +45 -0
- package/src/test/context.ts +91 -0
- package/src/test/crawl-fixture.ts +157 -0
- package/src/test/discover-fork.ts +17 -0
- package/src/test/fingerprint.ts +135 -0
- package/src/test/globals.d.ts +7 -0
- package/src/test/index.ts +19 -0
- package/src/test/namespace.ts +87 -0
- package/src/test/plugin.ts +189 -0
- package/src/test/progress.ts +185 -0
- package/src/test/reap-after-run.ts +28 -0
- package/src/test/reap.ts +286 -0
- package/src/test/reset.ts +253 -0
- package/src/test/savepoints.ts +172 -0
- package/src/test/setup.ts +1422 -0
- package/src/test/system-channel.ts +50 -0
- package/src/test/vitest-plugin.ts +142 -0
- package/src/test/vitest-workspace.ts +507 -0
- package/src/test/vitest.ts +333 -0
- package/src/test/watch.ts +105 -0
- package/src/trace/capture/bounded-serializer.ts +146 -0
- package/src/trace/cardinality/analyze.ts +173 -0
- package/src/trace/cardinality/branch-context.ts +214 -0
- package/src/trace/cardinality/check.ts +33 -0
- package/src/trace/cardinality/classify-edge.ts +64 -0
- package/src/trace/cardinality/structural-max.ts +107 -0
- package/src/trace/check/coverage-check.ts +188 -0
- package/src/trace/check/flow-check.ts +103 -0
- package/src/trace/check/flow-step-site.ts +234 -0
- package/src/trace/check/io-pattern.ts +116 -0
- package/src/trace/codemod/add-cardinality.ts +152 -0
- package/src/trace/codemod/run.ts +80 -0
- package/src/trace/entry/child-trace.ts +21 -0
- package/src/trace/entry/close-trace.ts +87 -0
- package/src/trace/entry/finalize-trace.ts +28 -0
- package/src/trace/entry/open-trace.ts +138 -0
- package/src/trace/entry/parent-trace.ts +20 -0
- package/src/trace/entry/parse-traceparent.ts +39 -0
- package/src/trace/entry/process-start.ts +38 -0
- package/src/trace/entry/trace-context.ts +26 -0
- package/src/trace/entry/tracing-enabled.ts +10 -0
- package/src/trace/external/record.ts +195 -0
- package/src/trace/external/report.ts +66 -0
- package/src/trace/namespace.ts +19 -0
- package/src/trace/partition/day.ts +47 -0
- package/src/trace/partition/index.ts +14 -0
- package/src/trace/partition/partition-name.ts +23 -0
- package/src/trace/partition/plan.ts +82 -0
- package/src/trace/partition/schedule.ts +26 -0
- package/src/trace/partition/startup-hook.ts +39 -0
- package/src/trace/partition/sync.ts +154 -0
- package/src/trace/partition/tables.ts +53 -0
- package/src/trace/read/tree.ts +83 -0
- package/src/trace/retain/copy-on-error.ts +76 -0
- package/src/trace/runtime/expire-stale-traces.ts +54 -0
- package/src/trace/runtime/flow-step.ts +147 -0
- package/src/trace/runtime/flush-steps.ts +31 -0
- package/src/trace/runtime/pending-writes.ts +54 -0
- package/src/trace/runtime/step-buffer.ts +298 -0
- package/src/trace/runtime/step-error.ts +21 -0
- package/src/trace/runtime/step-meta.ts +55 -0
- package/src/trace/runtime/thenable.ts +12 -0
- package/src/trace/runtime/truncate-trace.ts +62 -0
- package/src/trace/schema/trace-fields.ts +52 -0
- package/src/trace/schema/trace-retained.ts +22 -0
- package/src/trace/schema/trace-step-fields.ts +44 -0
- package/src/trace/schema/trace-step-retained.ts +25 -0
- package/src/trace/schema/trace-step.ts +18 -0
- package/src/trace/schema/trace.ts +12 -0
- package/src/trace/transformer/apply.ts +79 -0
- package/src/trace/transformer/build-meta-literal.ts +30 -0
- package/src/trace/transformer/find-flow-step-sites.ts +85 -0
- package/src/trace/transformer/inject-flow-step-import.ts +39 -0
- package/src/trace/transformer/transform-error.ts +14 -0
- package/src/trace/transformer/transform-flow-steps.ts +123 -0
- package/src/trace/transformer/wrap-function-declaration.ts +62 -0
- package/src/trace/transformer/wrap-variable-declaration.ts +67 -0
- package/src/workspace/affected.ts +110 -0
- package/src/workspace/changed-files.ts +163 -0
- package/src/workspace/check-deps.ts +213 -0
- package/src/workspace/checksum.ts +20 -0
- package/src/workspace/dependency-graph.ts +67 -0
- package/src/workspace/detect-pipework-copies.ts +93 -0
- package/src/workspace/discover.ts +206 -0
- package/src/workspace/generate.ts +401 -0
- package/src/workspace/index.ts +7 -0
- package/src/workspace/module-imports-pipework.ts +39 -0
- package/src/workspace/module-package-name.ts +12 -0
- package/src/workspace/scrub-git-env.ts +23 -0
- package/src/workspace/source-paths.ts +36 -0
- package/src/workspace/sync.ts +182 -0
- package/dist/REFERENCE.md +0 -1477
- package/dist/infra/access-links/rate-limit.d.ts +0 -10
- package/dist/infra/access-links/rate-limit.d.ts.map +0 -1
- package/dist/infra/access-links/rate-limit.js +0 -28
- package/dist/infra/access-links/rate-limit.js.map +0 -1
- package/dist/infra/email/sigv4.d.ts +0 -24
- package/dist/infra/email/sigv4.d.ts.map +0 -1
- package/dist/infra/email/sigv4.js +0 -55
- package/dist/infra/email/sigv4.js.map +0 -1
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import type { PgTable } from '../../data/query/pg-core/index.js'
|
|
2
|
+
import type { DB } from '../../data/db/types.js'
|
|
3
|
+
import type { SQL } from '../../data/query/index.js'
|
|
4
|
+
import { eq, and, or, isNull, is, SQL as SQLClass, getTableColumns, getTableName } from '../../data/query/index.js'
|
|
5
|
+
import { getScopeConfig } from './scope.js'
|
|
6
|
+
|
|
7
|
+
type AnyFn = (...args: unknown[]) => unknown
|
|
8
|
+
type Obj = Record<string, AnyFn>
|
|
9
|
+
type Column = ReturnType<typeof getTableColumns>[string]
|
|
10
|
+
|
|
11
|
+
/** What a connection does with a table: filter it to the tenant, or refuse it. */
|
|
12
|
+
interface Policy {
|
|
13
|
+
/** Marks the builders this connection made; a CTE body or set operand must carry it. */
|
|
14
|
+
readonly owner: string | symbol
|
|
15
|
+
/** The tenant to inject into inserts; null on the guard, which refuses tenant tables. */
|
|
16
|
+
readonly tenantId: string | null
|
|
17
|
+
/** The tenant filter for a tenant table, null for any other source. The guard throws. */
|
|
18
|
+
scope(table: unknown, operation: string): { cond: SQL; column: Column; columnKey: string } | null
|
|
19
|
+
readonly refuseExecute: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const GUARD = Symbol('tenant-guard')
|
|
23
|
+
const made = new WeakMap<object, Policy['owner']>()
|
|
24
|
+
/** Every tenant-scoped handle (and each nested transaction's) → the raw handle under it and its tenant. */
|
|
25
|
+
const scopedHandles = new WeakMap<object, { readonly raw: DB; readonly tenantId: string }>()
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The handle a framework API runs its raw SQL on. A tenant-scoped handle yields the raw handle under it (same
|
|
29
|
+
* connection or transaction, so it commits and rolls back with the caller and keeps its session tenant) and its
|
|
30
|
+
* tenant; any other handle comes back unchanged with a null tenant. Internal: not part of the package surface.
|
|
31
|
+
*/
|
|
32
|
+
export function unscopedExecutor(db: DB): { readonly db: DB; readonly tenant: string | null } {
|
|
33
|
+
const scoped = scopedHandles.get(db as object)
|
|
34
|
+
return scoped === undefined ? { db, tenant: null } : { db: scoped.raw, tenant: scoped.tenantId }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The API's name and why it cannot be confined to one tenant, for the error a scoped handle gets. */
|
|
38
|
+
function refuseScopedMessage(api: string, tenant: string, reason: string): string {
|
|
39
|
+
return (
|
|
40
|
+
`[pipework] ${api} was given a tenant-scoped database connection (tenant "${tenant}"), but ${reason}.\n\n` +
|
|
41
|
+
' Pass an unscoped handle instead: the worker\'s own, pipe.system(), or\n' +
|
|
42
|
+
' pool.getOrCreate(...).drizzle.\n'
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Throws unless `db` is an unscoped handle; for APIs that act across tenants and cannot be confined to one. */
|
|
47
|
+
export function requireUnscopedExecutor(db: DB, api: string, reason: string): DB {
|
|
48
|
+
const { db: raw, tenant } = unscopedExecutor(db)
|
|
49
|
+
if (tenant !== null) throw new Error(refuseScopedMessage(api, tenant, reason))
|
|
50
|
+
return raw
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* For an API that acts on one tenant's rows and knows which: the raw handle and the tenant to act for. A scoped
|
|
55
|
+
* handle whose tenant differs from `tenant` throws; with no `tenant` given the call is confined to the handle's.
|
|
56
|
+
*/
|
|
57
|
+
export function scopedExecutor(db: DB, tenant: string | null, api: string): { readonly db: DB; readonly tenant: string | null } {
|
|
58
|
+
const { db: raw, tenant: own } = unscopedExecutor(db)
|
|
59
|
+
if (own === null) return { db: raw, tenant }
|
|
60
|
+
if (tenant !== null && tenant !== own) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`[pipework] ${api} was given a handle scoped to tenant "${own}" but acts on tenant "${tenant}".\n\n` +
|
|
63
|
+
' A tenant-scoped connection reaches only its own tenant. For another tenant use an unscoped handle:\n' +
|
|
64
|
+
' the worker\'s own, pipe.system(), or pool.getOrCreate(...).drizzle.\n',
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
return { db: raw, tenant: own }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Wraps an API object whose methods act across tenants: a call with a tenant-scoped handle among its arguments
|
|
72
|
+
* throws the actionable error before running, any other call goes through unchanged.
|
|
73
|
+
*/
|
|
74
|
+
export function refuseScopedHandle<T extends object>(api: T, name: string, reason: string, except: readonly string[] = []): T {
|
|
75
|
+
return new Proxy(api, {
|
|
76
|
+
get(target, prop, receiver) {
|
|
77
|
+
const value: unknown = Reflect.get(target, prop, receiver)
|
|
78
|
+
if (typeof value !== 'function' || typeof prop !== 'string' || except.includes(prop)) return value
|
|
79
|
+
return (...args: unknown[]): unknown => {
|
|
80
|
+
for (const arg of args) {
|
|
81
|
+
const scoped = typeof arg === 'object' && arg !== null ? scopedHandles.get(arg) : undefined
|
|
82
|
+
if (scoped !== undefined) throw new Error(refuseScopedMessage(`${name}.${prop}()`, scoped.tenantId, reason))
|
|
83
|
+
}
|
|
84
|
+
return (value as AnyFn).apply(target, args)
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const JOINS: Record<string, string> = {
|
|
91
|
+
leftJoin: 'left', leftJoinLateral: 'left', rightJoin: 'right', innerJoin: 'inner',
|
|
92
|
+
innerJoinLateral: 'inner', fullJoin: 'full', crossJoin: 'cross', crossJoinLateral: 'cross',
|
|
93
|
+
}
|
|
94
|
+
const SET_OPERATORS = ['union', 'unionAll', 'intersect', 'intersectAll', 'except', 'exceptAll']
|
|
95
|
+
const SELECTS = ['select', 'selectDistinct', 'selectDistinctOn']
|
|
96
|
+
|
|
97
|
+
function throwTenantRequired(tableName: string, operation: string): never {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`[pipework] Cannot ${operation} tenant-scoped table "${tableName}" without a tenant in context.\n\n` +
|
|
100
|
+
' This table has a .tenant() field, which means all operations require\n' +
|
|
101
|
+
' an active tenant in the request/job context.\n\n' +
|
|
102
|
+
' If this is a system-level operation that legitimately needs cross-tenant\n' +
|
|
103
|
+
' access — pre-tenant auth (e.g. resolving org membership during login),\n' +
|
|
104
|
+
' public webhook ingress, admin tooling — use pipe.system() instead. It is\n' +
|
|
105
|
+
' the explicit unscoped channel: owner-role connection, outside the request\n' +
|
|
106
|
+
' transaction, never tenant-filtered. Every query through it must be\n' +
|
|
107
|
+
' deliberately cross-tenant-safe.\n',
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function scopedPolicy(tenantId: string): Policy {
|
|
112
|
+
return {
|
|
113
|
+
owner: tenantId,
|
|
114
|
+
tenantId,
|
|
115
|
+
refuseExecute: true,
|
|
116
|
+
scope(table) {
|
|
117
|
+
const config = getScopeConfig(table as PgTable)
|
|
118
|
+
if (!config) return null
|
|
119
|
+
const column = getTableColumns(table as PgTable)[config.columnKey]!
|
|
120
|
+
return { cond: eq(column, tenantId), column, columnKey: config.columnKey }
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const guardPolicy: Policy = {
|
|
126
|
+
owner: GUARD,
|
|
127
|
+
tenantId: null,
|
|
128
|
+
refuseExecute: false,
|
|
129
|
+
scope(table, operation) {
|
|
130
|
+
if (getScopeConfig(table as PgTable)) throwTenantRequired(getTableName(table as PgTable), operation)
|
|
131
|
+
return null
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const SYSTEM = Symbol('platform-system')
|
|
136
|
+
|
|
137
|
+
/** The platform-role system channel: it reads no tenant table, whatever the statement. */
|
|
138
|
+
const systemPolicy: Policy = {
|
|
139
|
+
owner: SYSTEM,
|
|
140
|
+
tenantId: null,
|
|
141
|
+
refuseExecute: false,
|
|
142
|
+
scope(table, operation) {
|
|
143
|
+
if (!getScopeConfig(table as PgTable)) return null
|
|
144
|
+
throw new Error(
|
|
145
|
+
`[pipework] Cannot ${operation} tenant-scoped table "${getTableName(table as PgTable)}" through pipe.system().\n\n` +
|
|
146
|
+
' This database runs its system channel on a platform role (rls.platformRole) that has no\n' +
|
|
147
|
+
' blanket access to tenant rows. Declare a door for the table under `doors` in\n' +
|
|
148
|
+
' pipework.config.ts, and read it with pipe.door(name, fn).\n',
|
|
149
|
+
)
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const DOOR_READS: ReadonlySet<string> = new Set(['select from', 'join', 'count', 'update from'])
|
|
154
|
+
|
|
155
|
+
function doorPolicy(door: { readonly name: string; readonly table: string; readonly ops?: readonly string[] }): Policy {
|
|
156
|
+
const reads = door.ops?.includes('delete') === true ? new Set([...DOOR_READS, 'delete']) : DOOR_READS
|
|
157
|
+
return {
|
|
158
|
+
owner: Symbol(`door:${door.name}`),
|
|
159
|
+
tenantId: null,
|
|
160
|
+
refuseExecute: false,
|
|
161
|
+
scope(table, operation) {
|
|
162
|
+
if (!getScopeConfig(table as PgTable)) return null
|
|
163
|
+
const name = getTableName(table as PgTable)
|
|
164
|
+
if (name !== door.table) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`[pipework] Cannot ${operation} tenant-scoped table "${name}" inside door "${door.name}".\n\n` +
|
|
167
|
+
` The door opens "${door.table}" only. Declare a door for "${name}" under \`doors\` in\n` +
|
|
168
|
+
' pipework.config.ts and read it with its own pipe.door(name, fn).\n',
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
if (!reads.has(operation)) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`[pipework] Cannot ${operation} "${name}" inside door "${door.name}": a door is read-only.\n\n` +
|
|
174
|
+
' Write tenant rows from a request or job with a tenant in context.\n',
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
return null
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function refuse(what: string, why: string): never {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`[pipework] ${what} is not allowed on this database connection: ${why}\n\n` +
|
|
185
|
+
' A tenant-scoped connection filters every tenant table its own builders read.\n' +
|
|
186
|
+
' Build the statement with this connection (select, insert, update or delete, or\n' +
|
|
187
|
+
' the callback form of $with) so the filter is part of its SQL. A raw sql fragment\n' +
|
|
188
|
+
' or a builder from another connection (pipe.system(), an unscoped db) cannot be\n' +
|
|
189
|
+
' filtered, so it is refused.\n',
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function checkBuilder(policy: Policy, what: string, builder: unknown): void {
|
|
194
|
+
if (builder != null && typeof builder === 'object' && made.get(builder) === policy.owner) return
|
|
195
|
+
if (is(builder, SQLClass)) refuse(what, 'a raw sql fragment cannot be scoped to the tenant.')
|
|
196
|
+
refuse(what, 'it was not built by this connection.')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function withCond(on: unknown, cond: SQL): unknown {
|
|
200
|
+
if (typeof on === 'function') return (...args: unknown[]) => and((on as AnyFn)(...args) as SQL | undefined, cond)
|
|
201
|
+
return on === undefined ? cond : and(on as SQL, cond)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface Entry {
|
|
205
|
+
cond: SQL
|
|
206
|
+
column: Column
|
|
207
|
+
position: number
|
|
208
|
+
/** Kept as written even when a later right/full join null-extends the table. */
|
|
209
|
+
strict: boolean
|
|
210
|
+
/** Null-extended by its own right/full join: its null rows are not another tenant's. */
|
|
211
|
+
tolerant: boolean
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The where clause of one statement: the user's condition plus the tenant
|
|
216
|
+
* condition of every tenant table the statement reads. It is rewritten onto the
|
|
217
|
+
* builder after every call that changes it, so the filter is in the SQL as soon
|
|
218
|
+
* as the table is named. A table a right or full join can null-extend accepts
|
|
219
|
+
* its null rows (the tenant column is NOT NULL, so null means no row there).
|
|
220
|
+
*/
|
|
221
|
+
class WhereScope {
|
|
222
|
+
private user: SQL | undefined
|
|
223
|
+
private readonly entries: Entry[] = []
|
|
224
|
+
private position = 0
|
|
225
|
+
private widest = -1
|
|
226
|
+
|
|
227
|
+
constructor(private readonly target: Obj, private readonly policy: Policy) {}
|
|
228
|
+
|
|
229
|
+
table(table: unknown, operation: string, strict: boolean): void {
|
|
230
|
+
this.position++
|
|
231
|
+
const scope = this.policy.scope(table, operation)
|
|
232
|
+
if (scope) this.entries.push({ cond: scope.cond, column: scope.column, position: this.position, strict, tolerant: false })
|
|
233
|
+
this.refresh()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
join(table: unknown, type: string, on: unknown): unknown {
|
|
237
|
+
this.position++
|
|
238
|
+
const wide = type === 'right' || type === 'full'
|
|
239
|
+
const scope = this.policy.scope(table, 'join')
|
|
240
|
+
const conds: SQL[] = []
|
|
241
|
+
if (scope && type !== 'cross') conds.push(scope.cond)
|
|
242
|
+
// A right or full join keeps unmatched rows of the tables before it: a row of
|
|
243
|
+
// another tenant must not match, so those tables' conditions go in its ON too.
|
|
244
|
+
if (wide) conds.push(...this.entries.map(e => or(e.cond, isNull(e.column))!))
|
|
245
|
+
if (scope && (wide || type === 'cross')) {
|
|
246
|
+
this.entries.push({ cond: scope.cond, column: scope.column, position: this.position, strict: false, tolerant: wide })
|
|
247
|
+
}
|
|
248
|
+
if (wide) this.widest = this.position
|
|
249
|
+
return conds.length > 0 ? withCond(on, and(...conds)!) : on
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
where(user: SQL | undefined): void {
|
|
253
|
+
this.user = user
|
|
254
|
+
this.refresh()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
refresh(): void {
|
|
258
|
+
if (this.user === undefined && this.entries.length === 0) return
|
|
259
|
+
const conds = this.entries.map(e =>
|
|
260
|
+
e.tolerant || (!e.strict && this.widest > e.position) ? or(e.cond, isNull(e.column)) : e.cond)
|
|
261
|
+
this.target['where']!(and(this.user, ...conds))
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Wraps a builder so the calls that return it return the wrapper, and `hooks` replace named methods. */
|
|
266
|
+
function wrap(
|
|
267
|
+
target: object,
|
|
268
|
+
policy: Policy,
|
|
269
|
+
hooksFor: (self: object, t: Obj) => Record<string, AnyFn>,
|
|
270
|
+
): object {
|
|
271
|
+
let hooks: Record<string, AnyFn> | undefined
|
|
272
|
+
const self: object = new Proxy(target, {
|
|
273
|
+
get(t, prop) {
|
|
274
|
+
if (typeof prop === 'string') {
|
|
275
|
+
hooks ??= hooksFor(self, t as Obj)
|
|
276
|
+
if (Object.hasOwn(hooks, prop)) return hooks[prop]
|
|
277
|
+
}
|
|
278
|
+
const value: unknown = Reflect.get(t, prop, t)
|
|
279
|
+
if (typeof value !== 'function') return value
|
|
280
|
+
return (...args: unknown[]) => {
|
|
281
|
+
const result = (value as AnyFn).apply(t, args)
|
|
282
|
+
return result === t ? self : result
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
made.set(self, policy.owner)
|
|
287
|
+
return self
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function whereHook(scope: WhereScope, self: object): AnyFn {
|
|
291
|
+
return (user) => {
|
|
292
|
+
scope.where(user as SQL | undefined)
|
|
293
|
+
return self
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function joinHooks(scope: WhereScope, self: object, t: Obj): Record<string, AnyFn> {
|
|
298
|
+
const hooks: Record<string, AnyFn> = {}
|
|
299
|
+
for (const [name, type] of Object.entries(JOINS)) {
|
|
300
|
+
hooks[name] = (table, on) => {
|
|
301
|
+
const scopedOn = scope.join(table, type, on)
|
|
302
|
+
t[name]!(...(scopedOn === undefined ? [table] : [table, scopedOn]))
|
|
303
|
+
scope.refresh()
|
|
304
|
+
return self
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return hooks
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function selectBuilder(builder: object, table: unknown, policy: Policy): object {
|
|
311
|
+
const t = builder as Obj
|
|
312
|
+
const scope = new WhereScope(t, policy)
|
|
313
|
+
scope.table(table, 'select from', false)
|
|
314
|
+
return wrap(builder, policy, (self) => ({
|
|
315
|
+
where: whereHook(scope, self),
|
|
316
|
+
...joinHooks(scope, self, t),
|
|
317
|
+
...Object.fromEntries(SET_OPERATORS.map(name => [name, (right: unknown) => {
|
|
318
|
+
const checked = typeof right === 'function'
|
|
319
|
+
? (...ops: unknown[]) => {
|
|
320
|
+
const resolved = (right as AnyFn)(...ops)
|
|
321
|
+
checkBuilder(policy, `${name}()`, resolved)
|
|
322
|
+
return resolved
|
|
323
|
+
}
|
|
324
|
+
: right
|
|
325
|
+
if (typeof right !== 'function') checkBuilder(policy, `${name}()`, right)
|
|
326
|
+
t[name]!(checked)
|
|
327
|
+
return self
|
|
328
|
+
}])),
|
|
329
|
+
}))
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function selectStart(builder: object, policy: Policy): object {
|
|
333
|
+
return new Proxy(builder, {
|
|
334
|
+
get(t, prop) {
|
|
335
|
+
if (prop === 'from') {
|
|
336
|
+
return (table: unknown, ...rest: unknown[]) =>
|
|
337
|
+
selectBuilder((t as Obj)['from']!(table, ...rest) as object, table, policy)
|
|
338
|
+
}
|
|
339
|
+
const value: unknown = Reflect.get(t, prop, t)
|
|
340
|
+
return typeof value === 'function' ? (value as AnyFn).bind(t) : value
|
|
341
|
+
},
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function updateBuilder(builder: object, table: unknown, policy: Policy): object {
|
|
346
|
+
const t = builder as Obj
|
|
347
|
+
const scope = new WhereScope(t, policy)
|
|
348
|
+
scope.table(table, 'update', true)
|
|
349
|
+
return wrap(builder, policy, (self) => ({
|
|
350
|
+
where: whereHook(scope, self),
|
|
351
|
+
...joinHooks(scope, self, t),
|
|
352
|
+
from: (source) => {
|
|
353
|
+
t['from']!(source)
|
|
354
|
+
scope.table(source, 'update from', false)
|
|
355
|
+
return self
|
|
356
|
+
},
|
|
357
|
+
}))
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function updateStart(builder: object, table: unknown, policy: Policy): object {
|
|
361
|
+
return new Proxy(builder, {
|
|
362
|
+
get(t, prop) {
|
|
363
|
+
if (prop === 'set') {
|
|
364
|
+
return (...args: unknown[]) => updateBuilder((t as Obj)['set']!(...args) as object, table, policy)
|
|
365
|
+
}
|
|
366
|
+
const value: unknown = Reflect.get(t, prop, t)
|
|
367
|
+
return typeof value === 'function' ? (value as AnyFn).bind(t) : value
|
|
368
|
+
},
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function deleteBuilder(builder: object, table: unknown, policy: Policy): object {
|
|
373
|
+
const scope = new WhereScope(builder as Obj, policy)
|
|
374
|
+
scope.table(table, 'delete', true)
|
|
375
|
+
return wrap(builder, policy, (self) => ({ where: whereHook(scope, self) }))
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function insertBuilder(builder: object, table: unknown, policy: Policy): object {
|
|
379
|
+
const t = builder as Obj
|
|
380
|
+
const scope = policy.scope(table, 'insert')
|
|
381
|
+
// A conflict with another tenant's row must update nothing: the existing row has to be ours.
|
|
382
|
+
const finish = (result: unknown): object =>
|
|
383
|
+
wrap(result as object, policy, (self, base) => ({
|
|
384
|
+
...(scope && {
|
|
385
|
+
onConflictDoUpdate: (config: unknown) => {
|
|
386
|
+
const { where, setWhere, ...rest } = config as { where?: SQL; setWhere?: SQL }
|
|
387
|
+
base['onConflictDoUpdate']!({ ...rest, setWhere: and(where, setWhere, scope.cond) })
|
|
388
|
+
return self
|
|
389
|
+
},
|
|
390
|
+
}),
|
|
391
|
+
}))
|
|
392
|
+
return wrap(builder, policy, (self) => ({
|
|
393
|
+
values: (values) => {
|
|
394
|
+
if (!scope) return finish(t['values']!(values))
|
|
395
|
+
const { columnKey } = scope
|
|
396
|
+
const tenantId = policy.tenantId!
|
|
397
|
+
const inject = (row: Record<string, unknown>): Record<string, unknown> => {
|
|
398
|
+
const existing = row[columnKey]
|
|
399
|
+
if (existing !== undefined && existing !== tenantId) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
`[pipework] Tenant-scoped insert: value has ${columnKey} "${String(existing)}" ` +
|
|
402
|
+
`but the current context tenant is "${tenantId}".\n\n` +
|
|
403
|
+
` Remove the explicit ${columnKey} from your insert values — it's set automatically.\n` +
|
|
404
|
+
' If you need cross-tenant writes, file an issue on the pipework repo.\n',
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
return { ...row, [columnKey]: tenantId }
|
|
408
|
+
}
|
|
409
|
+
const scoped = Array.isArray(values)
|
|
410
|
+
? (values as Record<string, unknown>[]).map(inject)
|
|
411
|
+
: inject(values as Record<string, unknown>)
|
|
412
|
+
return finish(t['values']!(scoped))
|
|
413
|
+
},
|
|
414
|
+
select: (source) => {
|
|
415
|
+
if (scope) {
|
|
416
|
+
refuse(
|
|
417
|
+
`insert().select() into tenant-scoped table "${getTableName(table as PgTable)}"`,
|
|
418
|
+
`the tenant column ${scope.columnKey} cannot be set from a select.`,
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
if (typeof source === 'function') {
|
|
422
|
+
return finish(t['select']!((qb: unknown) => {
|
|
423
|
+
const body = (source as AnyFn)(scopedQueryBuilder(qb as object, policy))
|
|
424
|
+
checkBuilder(policy, 'insert().select()', body)
|
|
425
|
+
return body
|
|
426
|
+
}))
|
|
427
|
+
}
|
|
428
|
+
checkBuilder(policy, 'insert().select()', source)
|
|
429
|
+
return finish(t['select']!(source))
|
|
430
|
+
},
|
|
431
|
+
overridingSystemValue: () => {
|
|
432
|
+
t['overridingSystemValue']!()
|
|
433
|
+
return self
|
|
434
|
+
},
|
|
435
|
+
}))
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function scopedWith(source: Obj, policy: Policy): AnyFn {
|
|
439
|
+
return (alias, selection) => {
|
|
440
|
+
const raw = source['$with']!(alias, selection) as {
|
|
441
|
+
as: AnyFn
|
|
442
|
+
materialized: () => { as: AnyFn }
|
|
443
|
+
notMaterialized: () => { as: AnyFn }
|
|
444
|
+
}
|
|
445
|
+
const build = (as: AnyFn) => (body: unknown) => {
|
|
446
|
+
const cte = typeof body === 'function'
|
|
447
|
+
? as((qb: unknown) => {
|
|
448
|
+
const result = (body as AnyFn)(scopedQueryBuilder(qb as object, policy))
|
|
449
|
+
checkBuilder(policy, '$with() body', result)
|
|
450
|
+
return result
|
|
451
|
+
})
|
|
452
|
+
: (checkBuilder(policy, '$with() body', body), as(body))
|
|
453
|
+
made.set(cte as object, policy.owner)
|
|
454
|
+
return cte
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
as: build(raw.as),
|
|
458
|
+
materialized: () => ({ as: build(raw.materialized().as) }),
|
|
459
|
+
notMaterialized: () => ({ as: build(raw.notMaterialized().as) }),
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** The statement starters of a connection, a `with(...)` result or a query builder. */
|
|
465
|
+
function starters(source: Obj, policy: Policy): Record<string, AnyFn> {
|
|
466
|
+
const hooks: Record<string, AnyFn> = {}
|
|
467
|
+
for (const name of SELECTS) {
|
|
468
|
+
if (typeof source[name] !== 'function') continue
|
|
469
|
+
hooks[name] = (...args) => selectStart(source[name]!(...args) as object, policy)
|
|
470
|
+
}
|
|
471
|
+
if (typeof source['insert'] === 'function') {
|
|
472
|
+
hooks['insert'] = (table) => {
|
|
473
|
+
policy.scope(table, 'insert')
|
|
474
|
+
return insertBuilder(source['insert']!(table) as object, table, policy)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (typeof source['update'] === 'function') {
|
|
478
|
+
hooks['update'] = (table) => {
|
|
479
|
+
policy.scope(table, 'update')
|
|
480
|
+
return updateStart(source['update']!(table) as object, table, policy)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (typeof source['delete'] === 'function') {
|
|
484
|
+
hooks['delete'] = (table) => {
|
|
485
|
+
policy.scope(table, 'delete')
|
|
486
|
+
return deleteBuilder(source['delete']!(table) as object, table, policy)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return hooks
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function withHooks(source: Obj, policy: Policy): Record<string, AnyFn> {
|
|
493
|
+
return {
|
|
494
|
+
$with: scopedWith(source, policy),
|
|
495
|
+
with: (...ctes) => {
|
|
496
|
+
for (const cte of ctes) {
|
|
497
|
+
if (cte == null || typeof cte !== 'object' || made.get(cte) !== policy.owner) {
|
|
498
|
+
refuse('with()', 'every CTE must come from $with() on this connection.')
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return starters(source['with']!(...ctes) as Obj, policy)
|
|
502
|
+
},
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function scopedQueryBuilder(qb: object, policy: Policy): object {
|
|
507
|
+
const source = qb as Obj
|
|
508
|
+
const hooks = { ...starters(source, policy), ...withHooks(source, policy) }
|
|
509
|
+
return new Proxy(qb, {
|
|
510
|
+
get(t, prop) {
|
|
511
|
+
if (typeof prop === 'string' && Object.hasOwn(hooks, prop)) return hooks[prop]
|
|
512
|
+
const value: unknown = Reflect.get(t, prop, t)
|
|
513
|
+
return typeof value === 'function' ? (value as AnyFn).bind(t) : value
|
|
514
|
+
},
|
|
515
|
+
})
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function createDb(db: DB, policy: Policy): DB {
|
|
519
|
+
const source = db as unknown as Obj
|
|
520
|
+
const hooks: Record<string, AnyFn> = {
|
|
521
|
+
...starters(source, policy),
|
|
522
|
+
...withHooks(source, policy),
|
|
523
|
+
$count: (table, filters) => {
|
|
524
|
+
const scope = policy.scope(table, 'count')
|
|
525
|
+
const combined = scope ? (filters !== undefined ? and(filters as SQL, scope.cond) : scope.cond) : filters
|
|
526
|
+
return source['$count']!(table, combined)
|
|
527
|
+
},
|
|
528
|
+
transaction: (callback, config) =>
|
|
529
|
+
source['transaction']!((tx: unknown) => (callback as AnyFn)(createDb(tx as DB, policy)), config),
|
|
530
|
+
}
|
|
531
|
+
if (policy.refuseExecute) {
|
|
532
|
+
hooks['execute'] = () => {
|
|
533
|
+
throw new Error(
|
|
534
|
+
'[pipework] execute() is not available on a tenant-scoped database connection.\n\n' +
|
|
535
|
+
' Tenant-scoped connections only support select, insert, update, and delete\n' +
|
|
536
|
+
' operations, which are automatically filtered by tenant.\n\n' +
|
|
537
|
+
' If you need to run a query that the builder cannot express, file an issue\n' +
|
|
538
|
+
' on the pipework repo describing your use case.\n',
|
|
539
|
+
)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const proxy = new Proxy(db, {
|
|
543
|
+
get(target, prop, receiver) {
|
|
544
|
+
if (typeof prop === 'string') {
|
|
545
|
+
if (Object.hasOwn(hooks, prop)) return hooks[prop]
|
|
546
|
+
if (prop === 'query') {
|
|
547
|
+
const query = Reflect.get(target, prop, receiver) as object | undefined
|
|
548
|
+
if (query && Object.keys(query).length > 0) {
|
|
549
|
+
return new Proxy({}, {
|
|
550
|
+
get() {
|
|
551
|
+
refuse('A relational query (db.query)', 'it reads tables without the tenant filter.')
|
|
552
|
+
},
|
|
553
|
+
})
|
|
554
|
+
}
|
|
555
|
+
return query
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return Reflect.get(target, prop, receiver)
|
|
559
|
+
},
|
|
560
|
+
})
|
|
561
|
+
if (policy.refuseExecute && policy.tenantId !== null) scopedHandles.set(proxy, { raw: db, tenantId: policy.tenantId })
|
|
562
|
+
return proxy
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export function createTenantGuardDb(db: DB): DB {
|
|
566
|
+
return createDb(db, guardPolicy)
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export function createScopedDb(db: DB, tenantId: string): DB {
|
|
570
|
+
return createDb(db, scopedPolicy(tenantId))
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** The system channel's handle on a platform role: every tenant table is refused at build time. */
|
|
574
|
+
export function createSystemGuardDb(db: DB): DB {
|
|
575
|
+
return createDb(db, systemPolicy)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** A door's handle: SELECT from the door's table and from non-tenant tables; any other tenant table, and any write to the door's table, is refused at build time. */
|
|
579
|
+
export function createDoorDb(db: DB, door: { readonly name: string; readonly table: string; readonly ops?: readonly string[] }): DB {
|
|
580
|
+
return createDb(db, doorPolicy(door))
|
|
581
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Configuration for tenant extraction and isolation strategy. */
|
|
2
|
+
export interface TenantConfig {
|
|
3
|
+
readonly extract: (auth: unknown) => string | null
|
|
4
|
+
readonly sessionVars?: Record<string, (ctx: TenantContext) => string>
|
|
5
|
+
readonly validate?: (tenantId: string) => boolean
|
|
6
|
+
readonly required?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Resolved tenant context — tenant ID, request ID, and user ID. */
|
|
10
|
+
export interface TenantContext {
|
|
11
|
+
readonly tenant: string
|
|
12
|
+
readonly requestId: string
|
|
13
|
+
readonly userId: string | null
|
|
14
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
2
|
+
|
|
3
|
+
const SAFE_VAR_NAME = /^[a-z][a-z0-9_.]*$/
|
|
4
|
+
|
|
5
|
+
export function validateTenantId(value: string): boolean {
|
|
6
|
+
return UUID_PATTERN.test(value)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function validateSessionVarName(name: string): void {
|
|
10
|
+
if (!SAFE_VAR_NAME.test(name)) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`[pipework] Invalid session variable name: "${name}". ` +
|
|
13
|
+
`Variable names must match /^[a-z][a-z0-9_.]*$/. ` +
|
|
14
|
+
`This prevents SQL injection in SET LOCAL statements.`,
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function validateSessionVarValue(value: string): void {
|
|
20
|
+
if (/[;'"\n\r]/.test(value)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`[pipework] Unsafe session variable value rejected. ` +
|
|
23
|
+
`Values must not contain semicolons, quotes, or newlines. ` +
|
|
24
|
+
`Received: "${value.slice(0, 40)}${value.length > 40 ? '...' : ''}"`,
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|