pipework 0.252.0 → 0.253.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/CHANGELOG.md +10 -0
- package/dist/infra/support/handle.d.ts +8 -1
- package/dist/infra/support/handle.d.ts.map +1 -1
- package/dist/infra/support/handle.js +9 -2
- package/dist/infra/support/handle.js.map +1 -1
- package/dist/infra/support/namespace.d.ts.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 +869 -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,116 @@
|
|
|
1
|
+
import postgres from 'postgres'
|
|
2
|
+
import { getLogger } from '../../core/logging/logger.js'
|
|
3
|
+
import { currentQueryLabel } from './query-label.js'
|
|
4
|
+
import type { StatementWatch } from '../query/postgres-js/session.js'
|
|
5
|
+
|
|
6
|
+
const LABEL_SQL_CHARS = 120
|
|
7
|
+
const SAMPLE_TIMEOUT_MS = 2_000
|
|
8
|
+
const PG_TRUNCATED_QUERY_LENGTH = 1_000
|
|
9
|
+
|
|
10
|
+
interface BackendSample {
|
|
11
|
+
pid: number | null
|
|
12
|
+
state: string | null
|
|
13
|
+
waitEventType: string | null
|
|
14
|
+
waitEvent: string | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const UNKNOWN_BACKEND: BackendSample = { pid: null, state: null, waitEventType: null, waitEvent: null }
|
|
18
|
+
|
|
19
|
+
/** What a pool's watch needs: where it logs, how slow is slow, and how to reach the server for a wait-event sample. */
|
|
20
|
+
export interface StatementWatchOptions {
|
|
21
|
+
readonly database: string
|
|
22
|
+
readonly url: string
|
|
23
|
+
readonly slowQueryMs: number
|
|
24
|
+
/** Undefined: no timer is ever created. */
|
|
25
|
+
readonly inFlightQueryMs: number | undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface StatementWatchHandle {
|
|
29
|
+
readonly watch: StatementWatch
|
|
30
|
+
close(): Promise<void>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function labelFor(sqlText: string): string {
|
|
34
|
+
return currentQueryLabel() ?? sqlText.replace(/\s+/g, ' ').trim().slice(0, LABEL_SQL_CHARS)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Wraps each statement of one database's pool: a `query:slow` warning when it settles past
|
|
39
|
+
* `slowQueryMs`, and, when `inFlightQueryMs` is set, a `query:in-flight` warning at every
|
|
40
|
+
* multiple of it while the statement is still outstanding. Backend details are read over a
|
|
41
|
+
* dedicated connection, never the statement's own and never the app pool.
|
|
42
|
+
*/
|
|
43
|
+
export function createStatementWatch(options: StatementWatchOptions): StatementWatchHandle {
|
|
44
|
+
const { database, url, slowQueryMs, inFlightQueryMs } = options
|
|
45
|
+
let sampler: postgres.Sql | undefined
|
|
46
|
+
let sampling = false
|
|
47
|
+
let closed = false
|
|
48
|
+
|
|
49
|
+
function sampleBackend(sqlText: string, startedMs: number): Promise<BackendSample> {
|
|
50
|
+
if (sampling || closed) return Promise.resolve(UNKNOWN_BACKEND)
|
|
51
|
+
sampling = true
|
|
52
|
+
sampler ??= postgres(url, { max: 1, idle_timeout: 3, onnotice: () => {}, connect_timeout: 5 })
|
|
53
|
+
const client = sampler
|
|
54
|
+
const run = client.unsafe<Array<{ pid: number; state: string | null; wait_event_type: string | null; wait_event: string | null }>>(
|
|
55
|
+
`SELECT pid, state, wait_event_type, wait_event FROM pg_stat_activity
|
|
56
|
+
WHERE datname = current_database() AND pid <> pg_backend_pid() AND state <> 'idle'
|
|
57
|
+
AND (query = $1 OR (length(query) >= ${String(PG_TRUNCATED_QUERY_LENGTH)} AND left($1, length(query)) = query))
|
|
58
|
+
ORDER BY abs(extract(epoch FROM query_start) - $2::float8) LIMIT 1`,
|
|
59
|
+
[sqlText, startedMs / 1000],
|
|
60
|
+
).then(rows => {
|
|
61
|
+
const row = rows[0]
|
|
62
|
+
return row === undefined
|
|
63
|
+
? UNKNOWN_BACKEND
|
|
64
|
+
: { pid: row.pid, state: row.state, waitEventType: row.wait_event_type, waitEvent: row.wait_event }
|
|
65
|
+
})
|
|
66
|
+
void run.then(() => { sampling = false }, () => { sampling = false })
|
|
67
|
+
let timer: NodeJS.Timeout | undefined
|
|
68
|
+
const timeout = new Promise<BackendSample>(resolve => {
|
|
69
|
+
timer = setTimeout(() => { resolve(UNKNOWN_BACKEND) }, SAMPLE_TIMEOUT_MS)
|
|
70
|
+
timer.unref()
|
|
71
|
+
})
|
|
72
|
+
return Promise.race([run, timeout]).catch(() => UNKNOWN_BACKEND).finally(() => { clearTimeout(timer) })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const watch: StatementWatch = async <T>(sqlText: string, run: () => Promise<T>): Promise<T> => {
|
|
76
|
+
const startedMs = Date.now()
|
|
77
|
+
let interval: NodeJS.Timeout | undefined
|
|
78
|
+
let settled = false
|
|
79
|
+
if (inFlightQueryMs !== undefined) {
|
|
80
|
+
const log = getLogger()
|
|
81
|
+
const label = labelFor(sqlText)
|
|
82
|
+
interval = setInterval(() => {
|
|
83
|
+
const elapsedS = Math.round((Date.now() - startedMs) / 100) / 10
|
|
84
|
+
void sampleBackend(sqlText, startedMs).then(backend => {
|
|
85
|
+
if (settled) return
|
|
86
|
+
log.warn({
|
|
87
|
+
label, elapsedS, database,
|
|
88
|
+
pid: backend.pid, state: backend.state,
|
|
89
|
+
waitEventType: backend.waitEventType, waitEvent: backend.waitEvent,
|
|
90
|
+
}, 'query:in-flight')
|
|
91
|
+
})
|
|
92
|
+
}, inFlightQueryMs)
|
|
93
|
+
interval.unref()
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
return await run()
|
|
97
|
+
} finally {
|
|
98
|
+
settled = true
|
|
99
|
+
if (interval !== undefined) clearInterval(interval)
|
|
100
|
+
const durationMs = Date.now() - startedMs
|
|
101
|
+
if (slowQueryMs > 0 && durationMs > slowQueryMs) {
|
|
102
|
+
getLogger().warn({ label: labelFor(sqlText), durationMs, database }, 'query:slow')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
watch,
|
|
109
|
+
async close() {
|
|
110
|
+
closed = true
|
|
111
|
+
const client = sampler
|
|
112
|
+
sampler = undefined
|
|
113
|
+
if (client) await client.end({ timeout: 1 }).catch(() => {})
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getBaseLogger } from '../../core/logging/logger.js'
|
|
2
|
+
|
|
3
|
+
const warned = new Set<string>()
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Announces the one place a statement observer does not carry its
|
|
7
|
+
* error-sanitization guarantee.
|
|
8
|
+
*
|
|
9
|
+
* `ManagedConnection.client` is the sanctioned raw-driver door, and it stays
|
|
10
|
+
* raw on purpose — queries issued through it bypass the driver error boundary,
|
|
11
|
+
* so on an observed connection their errors serialize the bind parameters
|
|
12
|
+
* postgres.js attached. Everything reached through `.drizzle` (which is every
|
|
13
|
+
* `pipe()` call, every `pipe.*` write, every transaction and savepoint) is
|
|
14
|
+
* inside the boundary and unaffected.
|
|
15
|
+
*
|
|
16
|
+
* Warns once per database name: this is a design fact about the code holding
|
|
17
|
+
* the connection, not a per-query event, and repeating it would train people to
|
|
18
|
+
* filter it out.
|
|
19
|
+
*/
|
|
20
|
+
export function warnRawClientIsUnsanitized(database: string): void {
|
|
21
|
+
if (warned.has(database)) return
|
|
22
|
+
warned.add(database)
|
|
23
|
+
|
|
24
|
+
getBaseLogger().warn(
|
|
25
|
+
{ database },
|
|
26
|
+
'[pipework] raw .client read on a connection with observability.onStatement configured. ' +
|
|
27
|
+
'Queries issued through .client bypass pipework\'s driver error boundary, so their errors ' +
|
|
28
|
+
'serialize bind parameters (postgres.js marks query/parameters/args enumerable whenever its ' +
|
|
29
|
+
'debug hook is installed). Use connection.drizzle — or pipe() inside a managed context — to ' +
|
|
30
|
+
'stay inside the boundary. This warning is logged once per database.',
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Test seam: forget which databases have already warned. */
|
|
35
|
+
export function _resetRawClientWarnings(): void {
|
|
36
|
+
warned.clear()
|
|
37
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Column } from '../query/column.js'
|
|
2
|
+
import type { Placeholder, SQL } from '../query/sql/sql.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A value type widened to also accept the readonly form of every tuple, array
|
|
6
|
+
* and plain object inside it, for parameters that are read and never changed.
|
|
7
|
+
* Dates, SQL, placeholders, columns, functions, maps, sets, regular expressions,
|
|
8
|
+
* typed arrays and primitives pass through; unions distribute.
|
|
9
|
+
*
|
|
10
|
+
* The result is the original type or its widened form, so anything the plain
|
|
11
|
+
* type accepted is still accepted (including a value typed by an unresolved
|
|
12
|
+
* generic such as `T['$inferInsert']`). The widening stops after a fixed number
|
|
13
|
+
* of levels, so wide nested json columns cannot exhaust instantiation depth.
|
|
14
|
+
*/
|
|
15
|
+
export type AcceptReadonly<V> = V | Widen<V, []>
|
|
16
|
+
|
|
17
|
+
type Widen<V, D extends unknown[]> =
|
|
18
|
+
unknown extends V ? V
|
|
19
|
+
: D['length'] extends 4 ? V
|
|
20
|
+
: V extends Date | SQL | Placeholder | Column | Map<unknown, unknown> | Set<unknown> | RegExp | ArrayBufferView | ((...args: never[]) => unknown) ? V
|
|
21
|
+
: V extends readonly unknown[] ? Readonly<{ [I in keyof V]: Widen<V[I], [...D, unknown]> }>
|
|
22
|
+
: V extends object ? { [K in keyof V]: Widen<V[K], [...D, unknown]> }
|
|
23
|
+
: V
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import postgres from 'postgres'
|
|
3
|
+
import { getBaseLogger } from '../../core/logging/index.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What this process has reserved on each Postgres server, published to the
|
|
7
|
+
* server itself: one presence connection per server whose `application_name`
|
|
8
|
+
* is `pipework:<id>:<reservation>`, plus pool connections tagged
|
|
9
|
+
* `pipework:<id>`. The session is the liveness signal: a dead process's
|
|
10
|
+
* presence row leaves pg_stat_activity with its connection.
|
|
11
|
+
*/
|
|
12
|
+
export interface ReservationPublisher {
|
|
13
|
+
readonly id: string
|
|
14
|
+
/** application_name for a publishing pool's connections. */
|
|
15
|
+
readonly poolTag: string
|
|
16
|
+
/** Adds `max` to the reservation on the URL's server; the returned function subtracts it (once). */
|
|
17
|
+
reserve(url: string, max: number): () => void
|
|
18
|
+
/** Settles when every publication started so far has landed. */
|
|
19
|
+
settled(): Promise<void>
|
|
20
|
+
/** Ends every presence connection. */
|
|
21
|
+
closeAll(): Promise<void>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ServerEntry {
|
|
25
|
+
url: string
|
|
26
|
+
reservation: number
|
|
27
|
+
client: postgres.Sql | undefined
|
|
28
|
+
chain: Promise<void>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function serverKey(url: string): string {
|
|
32
|
+
const parsed = new URL(url)
|
|
33
|
+
return `${parsed.hostname}:${parsed.port === '' ? '5432' : parsed.port}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createReservationPublisher(): ReservationPublisher {
|
|
37
|
+
const id = randomUUID()
|
|
38
|
+
const servers = new Map<string, ServerEntry>()
|
|
39
|
+
const name = (reservation: number): string => `pipework:${id}:${String(reservation)}`
|
|
40
|
+
|
|
41
|
+
const publish = (entry: ServerEntry): void => {
|
|
42
|
+
entry.chain = entry.chain.then(async () => {
|
|
43
|
+
try {
|
|
44
|
+
if (entry.reservation <= 0) {
|
|
45
|
+
const client = entry.client
|
|
46
|
+
entry.client = undefined
|
|
47
|
+
if (client) await client.end({ timeout: 5 })
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
const value = name(entry.reservation)
|
|
51
|
+
if (!entry.client) {
|
|
52
|
+
entry.client = postgres(entry.url, {
|
|
53
|
+
max: 1,
|
|
54
|
+
idle_timeout: 0,
|
|
55
|
+
onnotice: () => {},
|
|
56
|
+
connection: { application_name: value },
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
// A reconnect re-sends the startup packet; keep it current.
|
|
60
|
+
entry.client.options.connection['application_name'] = value
|
|
61
|
+
await entry.client.unsafe('SELECT set_config(\'application_name\', $1, false)', [value])
|
|
62
|
+
} catch (err) {
|
|
63
|
+
getBaseLogger().warn({ err }, 'Could not publish the connection reservation')
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
id,
|
|
70
|
+
poolTag: `pipework:${id}`,
|
|
71
|
+
reserve(url, max) {
|
|
72
|
+
const key = serverKey(url)
|
|
73
|
+
let entry = servers.get(key)
|
|
74
|
+
if (!entry) {
|
|
75
|
+
entry = { url, reservation: 0, client: undefined, chain: Promise.resolve() }
|
|
76
|
+
servers.set(key, entry)
|
|
77
|
+
}
|
|
78
|
+
const target = entry
|
|
79
|
+
target.reservation += max
|
|
80
|
+
publish(target)
|
|
81
|
+
let released = false
|
|
82
|
+
return () => {
|
|
83
|
+
if (released) return
|
|
84
|
+
released = true
|
|
85
|
+
target.reservation -= max
|
|
86
|
+
publish(target)
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
async settled() {
|
|
90
|
+
await Promise.all([...servers.values()].map(e => e.chain))
|
|
91
|
+
},
|
|
92
|
+
async closeAll() {
|
|
93
|
+
for (const entry of servers.values()) {
|
|
94
|
+
entry.reservation = 0
|
|
95
|
+
publish(entry)
|
|
96
|
+
}
|
|
97
|
+
await this.settled()
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ConnectionBudget {
|
|
103
|
+
/** The server's `max_connections`. */
|
|
104
|
+
readonly maxConnections: number
|
|
105
|
+
/** `superuser_reserved_connections` plus `reserved_connections` (PG16+; 0 before). */
|
|
106
|
+
readonly reserved: number
|
|
107
|
+
/** Client backends open now, across the whole server, plus other roles' background workers that the reading role cannot tell apart. */
|
|
108
|
+
readonly held: number
|
|
109
|
+
/** For every other live publishing pipework process on this server: its reservation minus the connections it holds (never below 0), summed. */
|
|
110
|
+
readonly othersUnopened: number
|
|
111
|
+
/** `maxConnections - reserved - held - othersUnopened`. */
|
|
112
|
+
readonly free: number
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const PRESENCE = /^pipework:([0-9a-f-]{36}):(\d+)$/
|
|
116
|
+
const POOL_TAG = /^pipework:([0-9a-f-]{36})$/
|
|
117
|
+
|
|
118
|
+
export const BUDGET_QUERY = `
|
|
119
|
+
SELECT current_setting('max_connections')::int AS max_connections,
|
|
120
|
+
current_setting('superuser_reserved_connections')::int + coalesce(current_setting('reserved_connections', true)::int, 0) AS reserved,
|
|
121
|
+
(SELECT count(*)::int FROM pg_stat_activity WHERE datid IS NOT NULL AND (backend_type = 'client backend' OR backend_type IS NULL)) AS held,
|
|
122
|
+
coalesce((SELECT array_agg(application_name::text) FROM pg_stat_activity WHERE application_name LIKE 'pipework:%'), '{}') AS names`
|
|
123
|
+
|
|
124
|
+
export function computeBudget(
|
|
125
|
+
row: { max_connections: number; reserved: number; held: number; names: readonly string[] },
|
|
126
|
+
ownId: string,
|
|
127
|
+
): ConnectionBudget {
|
|
128
|
+
const reservation = new Map<string, number>()
|
|
129
|
+
const holding = new Map<string, number>()
|
|
130
|
+
for (const n of row.names) {
|
|
131
|
+
const presence = PRESENCE.exec(n)
|
|
132
|
+
if (presence) {
|
|
133
|
+
reservation.set(presence[1]!, Number(presence[2]))
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
const pool = POOL_TAG.exec(n)
|
|
137
|
+
if (pool) holding.set(pool[1]!, (holding.get(pool[1]!) ?? 0) + 1)
|
|
138
|
+
}
|
|
139
|
+
let othersUnopened = 0
|
|
140
|
+
for (const [pid, reserved] of reservation) {
|
|
141
|
+
if (pid === ownId) continue
|
|
142
|
+
othersUnopened += Math.max(0, reserved - (holding.get(pid) ?? 0))
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
maxConnections: row.max_connections,
|
|
146
|
+
reserved: row.reserved,
|
|
147
|
+
held: row.held,
|
|
148
|
+
othersUnopened,
|
|
149
|
+
free: row.max_connections - row.reserved - row.held - othersUnopened,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Schema-level features — manual layer (wrappable)
|
|
2
|
+
// e.g., `table` could auto-inject tenant_id columns for tenant-scoped tables.
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
pgTable as table,
|
|
6
|
+
pgView as view,
|
|
7
|
+
pgMaterializedView as materializedView,
|
|
8
|
+
pgSchema,
|
|
9
|
+
pgSequence as sequence,
|
|
10
|
+
pgPolicy as policy,
|
|
11
|
+
pgRole as role,
|
|
12
|
+
pgTableCreator as tableCreator,
|
|
13
|
+
withReplicas,
|
|
14
|
+
} from '../query/pg-core/index.js'
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { DB } from './types.js'
|
|
2
|
+
|
|
3
|
+
/** Retry policy for {@link serializable}: how many attempts on a serialization failure / deadlock, and the base backoff between them. */
|
|
4
|
+
export interface SerializableOptions {
|
|
5
|
+
readonly maxAttempts?: number
|
|
6
|
+
readonly backoffMs?: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const PG_SERIALIZATION_FAILURE = '40001'
|
|
10
|
+
const PG_DEADLOCK_DETECTED = '40P01'
|
|
11
|
+
const PG_SET_TRANSACTION_AFTER_QUERY = '25001'
|
|
12
|
+
|
|
13
|
+
/** The Postgres SQLSTATE carried by an error (or by its cause), or undefined for a non-Postgres error. */
|
|
14
|
+
export function pgErrorCode(err: unknown): unknown {
|
|
15
|
+
if (typeof err !== 'object' || err === null) return undefined
|
|
16
|
+
if ('code' in err) return (err as { code: unknown }).code
|
|
17
|
+
if ('cause' in err) return pgErrorCode((err as { cause: unknown }).cause)
|
|
18
|
+
return undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** True for Postgres serialization failures (40001) and deadlocks (40P01) — errors a retry can resolve. */
|
|
22
|
+
export function isTransientConcurrencyError(err: unknown): boolean {
|
|
23
|
+
const code = pgErrorCode(err)
|
|
24
|
+
return code === PG_SERIALIZATION_FAILURE || code === PG_DEADLOCK_DETECTED
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Only a postgres.js transaction client exposes savepoint(), so its presence
|
|
28
|
+
// on the session's client means this db already sits inside an open
|
|
29
|
+
// transaction — a drizzle transaction object included.
|
|
30
|
+
function insideTransaction(db: unknown): boolean {
|
|
31
|
+
const session = (db as { session?: { client?: { savepoint?: unknown } } }).session
|
|
32
|
+
return typeof session?.client?.savepoint === 'function'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function nestedSerializableError(cause?: unknown): Error {
|
|
36
|
+
return new Error(
|
|
37
|
+
'[pipework] serializable() cannot run inside an open transaction.\n\n' +
|
|
38
|
+
' Postgres refuses to raise the isolation level of a transaction that has\n' +
|
|
39
|
+
' already run a query (25001), and a nested transaction is a savepoint that\n' +
|
|
40
|
+
' inherits the outer, weaker level. Open the SERIALIZABLE transaction at the\n' +
|
|
41
|
+
' outermost level instead of inside another one.\n',
|
|
42
|
+
cause !== undefined ? { cause } : undefined,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Runs `fn` in a SERIALIZABLE transaction, retrying on serialization failures (40001) and deadlocks (40P01) with exponential backoff. */
|
|
47
|
+
export async function serializable<T>(
|
|
48
|
+
db: DB,
|
|
49
|
+
fn: (tx: DB) => Promise<T>,
|
|
50
|
+
options?: SerializableOptions,
|
|
51
|
+
): Promise<T> {
|
|
52
|
+
const maxAttempts = options?.maxAttempts ?? 3
|
|
53
|
+
const backoffMs = options?.backoffMs ?? 50
|
|
54
|
+
|
|
55
|
+
if (maxAttempts < 1) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
'[pipework] serializable() requires maxAttempts >= 1.\n\n' +
|
|
58
|
+
' pipe.serializable(db, fn) — default 3 attempts\n' +
|
|
59
|
+
' pipe.serializable(db, fn, { maxAttempts: 5 }) — custom attempts\n',
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (insideTransaction(db)) throw nestedSerializableError()
|
|
64
|
+
|
|
65
|
+
let lastError: unknown
|
|
66
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
67
|
+
if (attempt > 0) {
|
|
68
|
+
await new Promise<void>(resolve => setTimeout(resolve, backoffMs * 2 ** (attempt - 1)))
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- drizzle's transaction generic requires concrete schema types; the DB alias erases them
|
|
72
|
+
return await (db as any).transaction(fn, { isolationLevel: 'serializable' })
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (pgErrorCode(err) === PG_SET_TRANSACTION_AFTER_QUERY) throw nestedSerializableError(err)
|
|
75
|
+
if (isTransientConcurrencyError(err) && attempt < maxAttempts - 1) {
|
|
76
|
+
lastError = err
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
throw err
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw lastError
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { sql } from '../query/index.js'
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { sql } from './sql.js'
|
|
2
|
+
import { pgErrorCode } from './serializable.js'
|
|
3
|
+
import type { DB } from './types.js'
|
|
4
|
+
import { unscopedExecutor } from '../../auth/tenant/scoped-db.js'
|
|
5
|
+
|
|
6
|
+
const PG_QUERY_CANCELED = '57014'
|
|
7
|
+
const PG_LOCK_NOT_AVAILABLE = '55P03'
|
|
8
|
+
|
|
9
|
+
/** Which timeouts {@link withTimeouts} sets; each a positive integer number of milliseconds. */
|
|
10
|
+
export interface TimeoutOptions {
|
|
11
|
+
readonly statementTimeoutMs?: number
|
|
12
|
+
readonly lockTimeoutMs?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A statement was cancelled by Postgres for running past the statement timeout a {@link withTimeouts} call set. `cause` is the driver error. */
|
|
16
|
+
export class StatementTimeoutError extends Error {
|
|
17
|
+
constructor(readonly statementTimeoutMs: number, cause: unknown) {
|
|
18
|
+
super(`[pipework] a statement ran past the ${statementTimeoutMs} ms statement timeout set by withTimeouts`, { cause })
|
|
19
|
+
this.name = 'StatementTimeoutError'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A statement gave up waiting for a lock past the lock timeout a {@link withTimeouts} call set. `cause` is the driver error. */
|
|
24
|
+
export class LockTimeoutError extends Error {
|
|
25
|
+
constructor(readonly lockTimeoutMs: number, cause: unknown) {
|
|
26
|
+
super(`[pipework] a statement waited past the ${lockTimeoutMs} ms lock timeout set by withTimeouts`, { cause })
|
|
27
|
+
this.name = 'LockTimeoutError'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function positiveInteger(name: string, value: number | undefined): void {
|
|
32
|
+
if (value === undefined) return
|
|
33
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`[pipework] withTimeouts: ${name} must be a positive integer number of milliseconds, got ${String(value)}.\n\n` +
|
|
36
|
+
' Fix: pipe.withTimeouts(db, { statementTimeoutMs: 5000, lockTimeoutMs: 1000 }, fn)\n',
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Sets `statement_timeout` and/or `lock_timeout` for the current transaction in one statement, then runs `fn`.
|
|
43
|
+
* `db` is the transaction-bound DB the caller already has (`pipe(name)` inside a flow, or a `tx`).
|
|
44
|
+
*
|
|
45
|
+
* The settings hold until the transaction ends, as `SET LOCAL` does; nothing is restored after `fn`, so statements
|
|
46
|
+
* after it in the same transaction keep the timeouts. Outside a transaction `set_config(..., true)` has no
|
|
47
|
+
* effect, so call this only inside one.
|
|
48
|
+
*
|
|
49
|
+
* A statement Postgres cancels for a timeout this call set rejects with {@link StatementTimeoutError} (SQLSTATE 57014)
|
|
50
|
+
* or {@link LockTimeoutError} (55P03), the driver error as `cause`. Any other error passes through unchanged.
|
|
51
|
+
*/
|
|
52
|
+
export async function withTimeouts<T>(db: DB, options: TimeoutOptions, fn: () => Promise<T>): Promise<T> {
|
|
53
|
+
const { statementTimeoutMs, lockTimeoutMs } = options
|
|
54
|
+
if (statementTimeoutMs === undefined && lockTimeoutMs === undefined) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'[pipework] withTimeouts: give statementTimeoutMs, lockTimeoutMs, or both.\n\n' +
|
|
57
|
+
' Fix: pipe.withTimeouts(db, { statementTimeoutMs: 5000 }, fn)\n',
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
positiveInteger('statementTimeoutMs', statementTimeoutMs)
|
|
61
|
+
positiveInteger('lockTimeoutMs', lockTimeoutMs)
|
|
62
|
+
|
|
63
|
+
const settings = []
|
|
64
|
+
if (statementTimeoutMs !== undefined) settings.push(sql`set_config('statement_timeout', ${`${statementTimeoutMs}ms`}, true)`)
|
|
65
|
+
if (lockTimeoutMs !== undefined) settings.push(sql`set_config('lock_timeout', ${`${lockTimeoutMs}ms`}, true)`)
|
|
66
|
+
await unscopedExecutor(db).db.execute(sql`SELECT ${sql.join(settings, sql`, `)}`)
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
return await fn()
|
|
70
|
+
} catch (err) {
|
|
71
|
+
const code = pgErrorCode(err)
|
|
72
|
+
if (code === PG_QUERY_CANCELED && statementTimeoutMs !== undefined) throw new StatementTimeoutError(statementTimeoutMs, err)
|
|
73
|
+
if (code === PG_LOCK_NOT_AVAILABLE && lockTimeoutMs !== undefined) throw new LockTimeoutError(lockTimeoutMs, err)
|
|
74
|
+
throw err
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SQL as _SQL } from '../query/index.js'
|
|
2
|
+
import type { PgTable, PgColumn } from '../query/pg-core/index.js'
|
|
3
|
+
|
|
4
|
+
/** Drizzle database instance — use pipe() to get one inside a managed context. */
|
|
5
|
+
export type { PostgresJsDatabase as DB } from '../query/postgres-js/index.js'
|
|
6
|
+
/** SQL expression type — created by pipe.sql template tag or query operators. */
|
|
7
|
+
export type SQL<T = unknown> = _SQL<T>
|
|
8
|
+
/** Drizzle table definition type — created by schema.table(). */
|
|
9
|
+
export type Table = PgTable
|
|
10
|
+
/** Drizzle column type — created by schema.col.* builders. */
|
|
11
|
+
export type Column = PgColumn
|
|
12
|
+
|
|
13
|
+
/** Options for pipe.transaction(). Nested transactions (savepoints) inherit the outer transaction's isolation level — isolationLevel is silently dropped when nested. */
|
|
14
|
+
export interface TransactionConfig {
|
|
15
|
+
isolationLevel?: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable'
|
|
16
|
+
accessMode?: 'read only' | 'read write'
|
|
17
|
+
deferrable?: boolean
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DB } from './index.js'
|
|
2
|
+
import type { Flow } from '../../request/context/types.js'
|
|
3
|
+
import type { ResolvedDatabase } from '../../core/config/load.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The database handle for a Flow with no bound transaction on `dbName`: the
|
|
7
|
+
* pooled connection.
|
|
8
|
+
*
|
|
9
|
+
* On a platform-mode database that connection is the platform role, which sees
|
|
10
|
+
* no tenant row until the tenant setting is set, and the setting can only be
|
|
11
|
+
* set for a transaction. A builder from `pipe()` runs lazily on the handle it
|
|
12
|
+
* was built from, with nothing wrapping the statement, so there is no per-statement
|
|
13
|
+
* place to set it (src/data/query/postgres-js/session.ts sends each statement on
|
|
14
|
+
* the pool client). A Flow with a tenant is therefore refused here, naming the fix;
|
|
15
|
+
* a Flow with no tenant gets the connection as is and tenant tables return nothing.
|
|
16
|
+
* Under a test connection override the test's own connection is used.
|
|
17
|
+
*/
|
|
18
|
+
export function unboundDb(ctx: Flow, dbName: string, dbConfig: ResolvedDatabase, caller: string): DB {
|
|
19
|
+
if (
|
|
20
|
+
dbConfig.rls?.platformRole !== undefined
|
|
21
|
+
&& ctx.tenant !== null
|
|
22
|
+
&& !ctx.instance.pool.isOverridden(dbName)
|
|
23
|
+
) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`[pipework] ${caller} has no transaction bound for database "${dbName}", and this Flow has a tenant.\n\n` +
|
|
26
|
+
` "${dbName}" is in platform mode (rls.platformRole): its pooled connection is the platform role, which\n` +
|
|
27
|
+
' sees no tenant row, and the tenant setting can only be set per transaction.\n' +
|
|
28
|
+
' Fix: run the code in a request or job (each binds a tenant-scoped transaction), or wrap it in\n' +
|
|
29
|
+
' pipe.transaction(async (tx) => { ... }).\n',
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
return ctx.instance.pool.getOrCreate(dbConfig).drizzle
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { sql } from './sql.js'
|
|
2
|
+
import type { SQL } from './types.js'
|
|
3
|
+
|
|
4
|
+
type PgCastType = 'uuid' | 'text' | 'integer' | 'bigint' | 'boolean' | 'jsonb' | 'timestamptz' | 'timestamp' | 'date' | 'numeric' | 'real' | 'double precision' | 'smallint' | 'interval'
|
|
5
|
+
|
|
6
|
+
/** One column of a typed `VALUES` clause: its name and the PostgreSQL type each value in that column is cast to. See {@link values}. */
|
|
7
|
+
export interface ValueColumnDef {
|
|
8
|
+
readonly name: string
|
|
9
|
+
readonly type: PgCastType
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Produces a typed VALUES clause: `(VALUES ($1::uuid, $2::text), ($3::uuid, $4::text))`. Each value is cast to its declared type. JSONB values are JSON-stringified and Dates sent as ISO text, as a column does on insert; null is SQL NULL. */
|
|
13
|
+
export function values(rows: ReadonlyArray<readonly unknown[]>, columns: readonly ValueColumnDef[]): SQL {
|
|
14
|
+
if (rows.length === 0) throw new Error('[pipework] values() requires at least one row')
|
|
15
|
+
if (columns.length === 0) throw new Error('[pipework] values() requires at least one column definition')
|
|
16
|
+
|
|
17
|
+
const valueRows: SQL[] = rows.map(row => {
|
|
18
|
+
const cells = columns.map((col, i) => {
|
|
19
|
+
const val = row[i]
|
|
20
|
+
if (val === null || val === undefined) return sql`${null}::${sql.raw(col.type)}`
|
|
21
|
+
if (col.type === 'jsonb') return sql`${JSON.stringify(val)}::jsonb`
|
|
22
|
+
const bound = val instanceof Date ? val.toISOString() : val
|
|
23
|
+
return sql`${bound as string | number | boolean | bigint}::${sql.raw(col.type)}`
|
|
24
|
+
})
|
|
25
|
+
return sql`(${sql.join(cells, sql`, `)})`
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return sql`(VALUES ${sql.join(valueRows, sql`, `)})`
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const __brand: unique symbol
|
|
2
|
+
|
|
3
|
+
/** Phantom brand marker — attaches a nominal tag to a base type. */
|
|
4
|
+
export type Brand<B extends string> = { readonly [__brand]: B }
|
|
5
|
+
|
|
6
|
+
/** Nominal type — `Branded<string, 'UserId'>` is not assignable to `Branded<string, 'PostId'>`. */
|
|
7
|
+
export type Branded<T, B extends string> = T & Brand<B>
|
|
8
|
+
|
|
9
|
+
/** Extracts the phantom output type from a FieldDescriptor. */
|
|
10
|
+
export type InferFieldOutput<F> = F extends { readonly _output?: infer O } ? O : unknown
|
|
11
|
+
|
|
12
|
+
/** Projects field output types into a readonly record — used for DomainDefinition.Select. */
|
|
13
|
+
export type InferSelect<T extends Record<string, { readonly _output?: unknown }>> = {
|
|
14
|
+
readonly [K in keyof T]: InferFieldOutput<T[K]>
|
|
15
|
+
}
|