pipework 0.252.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 +5 -0
- 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,57 @@
|
|
|
1
|
+
import type { FieldDescriptor, FieldFlags, FieldRecord } from './types.js'
|
|
2
|
+
import type { InferFieldOutput } from './brand.js'
|
|
3
|
+
|
|
4
|
+
type FlagsOf<F> = F extends FieldDescriptor<unknown, infer TF> ? TF : FieldFlags
|
|
5
|
+
type HasFlag<F, K extends keyof FieldFlags> = FlagsOf<F>[K] extends true ? true : false
|
|
6
|
+
|
|
7
|
+
type InsertIncluded<F> =
|
|
8
|
+
HasFlag<F, 'autoManaged'> extends true ? false
|
|
9
|
+
: HasFlag<F, 'generated'> extends true ? false
|
|
10
|
+
: HasFlag<F, 'primaryKey'> extends true
|
|
11
|
+
? HasFlag<F, 'defaultRandom'> extends true ? false
|
|
12
|
+
: HasFlag<F, 'serial'> extends true ? false
|
|
13
|
+
: true
|
|
14
|
+
: true
|
|
15
|
+
|
|
16
|
+
type InsertOptional<F> =
|
|
17
|
+
HasFlag<F, 'nullable'> extends true ? true
|
|
18
|
+
: HasFlag<F, 'hasDefault'> extends true ? true
|
|
19
|
+
: false
|
|
20
|
+
|
|
21
|
+
type InsertRequiredKeys<TFields extends FieldRecord> = {
|
|
22
|
+
[K in keyof TFields]: InsertIncluded<TFields[K]> extends true
|
|
23
|
+
? InsertOptional<TFields[K]> extends true ? never : K
|
|
24
|
+
: never
|
|
25
|
+
}[keyof TFields]
|
|
26
|
+
|
|
27
|
+
type InsertOptionalKeys<TFields extends FieldRecord> = {
|
|
28
|
+
[K in keyof TFields]: InsertIncluded<TFields[K]> extends true
|
|
29
|
+
? InsertOptional<TFields[K]> extends true ? K : never
|
|
30
|
+
: never
|
|
31
|
+
}[keyof TFields]
|
|
32
|
+
|
|
33
|
+
/** Insert input shape: omits auto-managed and PK+(defaultRandom|serial); marks nullable/defaulted fields optional. */
|
|
34
|
+
export type InsertProjection<TFields extends FieldRecord> =
|
|
35
|
+
{ -readonly [K in InsertRequiredKeys<TFields>]: InferFieldOutput<TFields[K]> }
|
|
36
|
+
& { -readonly [K in InsertOptionalKeys<TFields>]?: InferFieldOutput<TFields[K]> }
|
|
37
|
+
|
|
38
|
+
/** Select output shape: includes every field with its nullable status preserved through TOutput. */
|
|
39
|
+
export type SelectProjection<TFields extends FieldRecord> = {
|
|
40
|
+
-readonly [K in keyof TFields]: InferFieldOutput<TFields[K]>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type UpdateIncluded<F> =
|
|
44
|
+
HasFlag<F, 'primaryKey'> extends true ? false
|
|
45
|
+
: HasFlag<F, 'readOnly'> extends true ? false
|
|
46
|
+
: HasFlag<F, 'autoManaged'> extends true ? false
|
|
47
|
+
: HasFlag<F, 'generated'> extends true ? false
|
|
48
|
+
: true
|
|
49
|
+
|
|
50
|
+
type UpdateIncludedKeys<TFields extends FieldRecord> = {
|
|
51
|
+
[K in keyof TFields]: UpdateIncluded<TFields[K]> extends true ? K : never
|
|
52
|
+
}[keyof TFields]
|
|
53
|
+
|
|
54
|
+
/** Update input shape: omits PK/readOnly/auto-managed; all remaining fields are optional. */
|
|
55
|
+
export type UpdateProjection<TFields extends FieldRecord> = {
|
|
56
|
+
-readonly [K in UpdateIncludedKeys<TFields>]?: InferFieldOutput<TFields[K]>
|
|
57
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// The trace-safe projection of a domain definition: the subset of its fields a
|
|
2
|
+
// trace step is allowed to capture into its input snapshot. A field is in the
|
|
3
|
+
// projection only if it was explicitly marked `.traceSafe()` — fail-closed, so
|
|
4
|
+
// a newly added field is never captured by accident.
|
|
5
|
+
|
|
6
|
+
import type { FieldDescriptor } from './types.js'
|
|
7
|
+
|
|
8
|
+
/** Field keys marked `.traceSafe()`, in declaration order. */
|
|
9
|
+
export function traceSafeKeys(fields: Record<string, FieldDescriptor>): readonly string[] {
|
|
10
|
+
return Object.keys(fields).filter(key => fields[key]!._meta.traceSafe)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Project a row down to its trace-safe fields.
|
|
15
|
+
*
|
|
16
|
+
* Keys not declared `.traceSafe()` are dropped. Keys that are trace-safe but
|
|
17
|
+
* absent from `row` are skipped rather than emitted as `undefined`, so a
|
|
18
|
+
* partial row projects to a partial result with no holes.
|
|
19
|
+
*/
|
|
20
|
+
export function projectTraceSafe(
|
|
21
|
+
fields: Record<string, FieldDescriptor>,
|
|
22
|
+
row: Record<string, unknown>,
|
|
23
|
+
): Record<string, unknown> {
|
|
24
|
+
const projected: Record<string, unknown> = {}
|
|
25
|
+
for (const key of traceSafeKeys(fields)) {
|
|
26
|
+
if (Object.prototype.hasOwnProperty.call(row, key)) {
|
|
27
|
+
projected[key] = row[key]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return projected
|
|
31
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { field } from './field.js'
|
|
2
|
+
import type { FieldDescriptor, TraitDefinition } from './types.js'
|
|
3
|
+
|
|
4
|
+
function autoManaged(f: FieldDescriptor): FieldDescriptor {
|
|
5
|
+
return { _meta: { ...f._meta, autoManaged: true } }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function autoManagedReadOnly(f: FieldDescriptor): FieldDescriptor {
|
|
9
|
+
return { _meta: { ...f._meta, autoManaged: true, readOnly: true } }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const effectiveDatedTrait: TraitDefinition = {
|
|
13
|
+
name: 'effectiveDated',
|
|
14
|
+
fields: {
|
|
15
|
+
effectiveFrom: autoManaged(field.timestamp().default('now')),
|
|
16
|
+
effectiveTo: autoManaged(field.timestamp().nullable()),
|
|
17
|
+
version: autoManaged(field.integer().default(1)),
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Versions that are only ever appended: each row is in force from its
|
|
23
|
+
* `effectiveFrom` until the next row's, so no column is ever set on an earlier
|
|
24
|
+
* version. Numbered per scope (the tenant field if the definition has one,
|
|
25
|
+
* otherwise the whole table). Write with `temporal.setVersion`, read with
|
|
26
|
+
* `temporal.versionAt`.
|
|
27
|
+
*
|
|
28
|
+
* `pipework migrate generate` adds a trigger refusing UPDATE and DELETE and a
|
|
29
|
+
* unique index on (scope, version) — only in the migration that has the
|
|
30
|
+
* CREATE TABLE for the table, like tenant RLS.
|
|
31
|
+
*/
|
|
32
|
+
export const appendOnlyVersionsTrait: TraitDefinition = {
|
|
33
|
+
name: 'appendOnlyVersions',
|
|
34
|
+
fields: {
|
|
35
|
+
effectiveFrom: autoManaged(field.timestamp()),
|
|
36
|
+
version: autoManaged(field.integer()),
|
|
37
|
+
setBy: autoManaged(field.text().nullable()),
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const auditedTrait: TraitDefinition = {
|
|
42
|
+
name: 'audited',
|
|
43
|
+
fields: {
|
|
44
|
+
createdAt: autoManagedReadOnly(field.timestamp().default('now')),
|
|
45
|
+
updatedAt: autoManaged(field.timestamp().default('now')),
|
|
46
|
+
createdBy: autoManagedReadOnly(field.text().nullable()),
|
|
47
|
+
updatedBy: autoManaged(field.text().nullable()),
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const softDeletedTrait: TraitDefinition = {
|
|
52
|
+
name: 'softDeleted',
|
|
53
|
+
fields: {
|
|
54
|
+
deletedAt: autoManaged(field.timestamp().nullable()),
|
|
55
|
+
deletedBy: autoManaged(field.text().nullable()),
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const TRAIT_CONFLICTS: ReadonlyArray<readonly [string, string, string]> = [
|
|
60
|
+
['appendOnlyVersions', 'effectiveDated',
|
|
61
|
+
'effectiveDated closes the previous version by UPDATE-ing effectiveTo and both define effectiveFrom and version, while appendOnlyVersions forbids every UPDATE.'],
|
|
62
|
+
['appendOnlyVersions', 'softDeleted',
|
|
63
|
+
'softDeleted marks a row deleted by UPDATE, while appendOnlyVersions forbids every UPDATE and DELETE.'],
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
export function applyTrait(
|
|
67
|
+
definitionName: string,
|
|
68
|
+
existingFields: Record<string, FieldDescriptor>,
|
|
69
|
+
existingTraits: string[],
|
|
70
|
+
trait: TraitDefinition,
|
|
71
|
+
): { fields: Record<string, FieldDescriptor>; traits: string[] } {
|
|
72
|
+
if (existingTraits.includes(trait.name)) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`[pipework] Trait "${trait.name}" is already applied to "${definitionName}".`,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const [a, b, why] of TRAIT_CONFLICTS) {
|
|
79
|
+
const other = trait.name === a ? b : trait.name === b ? a : undefined
|
|
80
|
+
if (other !== undefined && existingTraits.includes(other)) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`[pipework] Traits "${a}" and "${b}" cannot be combined on "${definitionName}".\n\n` +
|
|
83
|
+
` ${why}\n` +
|
|
84
|
+
` Use one of them.`,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const fieldName of Object.keys(trait.fields)) {
|
|
90
|
+
if (fieldName in existingFields) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`[pipework] Trait "${trait.name}" adds field "${fieldName}" which already exists in "${definitionName}".\n\n` +
|
|
93
|
+
` Remove the field from your definition or choose a different name.\n` +
|
|
94
|
+
` Trait "${trait.name}" provides: ${Object.keys(trait.fields).join(', ')}`,
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
fields: { ...existingFields, ...trait.fields },
|
|
101
|
+
traits: [...existingTraits, trait.name],
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The field versions are numbered within: the definition's tenant field, else undefined (the whole table). */
|
|
106
|
+
export function appendOnlyScopeField(definition: { readonly $fields: Record<string, FieldDescriptor> }): string | undefined {
|
|
107
|
+
return Object.keys(definition.$fields).find((key) => definition.$fields[key]!._meta.tenant === true)
|
|
108
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import type { CollationName } from '../query/collation.js'
|
|
2
|
+
import type { z } from 'zod/v4'
|
|
3
|
+
import type { SQL, Table } from '../db/types.js'
|
|
4
|
+
import type { InferSelect, InferFieldOutput } from './brand.js'
|
|
5
|
+
import type { AnyPgColumn, PgTable } from '../query/pg-core/index.js'
|
|
6
|
+
import type { AcceptReadonly } from '../db/readonly.js'
|
|
7
|
+
import type { InsertProjection, SelectProjection, UpdateProjection } from './projections.js'
|
|
8
|
+
|
|
9
|
+
export type FieldKind =
|
|
10
|
+
| 'uuid'
|
|
11
|
+
| 'text'
|
|
12
|
+
| 'integer'
|
|
13
|
+
| 'serial'
|
|
14
|
+
| 'bigint'
|
|
15
|
+
| 'boolean'
|
|
16
|
+
| 'timestamp'
|
|
17
|
+
| 'date'
|
|
18
|
+
| 'enum'
|
|
19
|
+
| 'jsonb'
|
|
20
|
+
| 'decimal'
|
|
21
|
+
| 'money'
|
|
22
|
+
| 'pgLsn'
|
|
23
|
+
| 'custom'
|
|
24
|
+
|
|
25
|
+
export interface FieldMeta {
|
|
26
|
+
readonly kind: FieldKind
|
|
27
|
+
readonly nullable: boolean
|
|
28
|
+
readonly hasDefault: boolean
|
|
29
|
+
readonly defaultValue?: unknown
|
|
30
|
+
readonly primaryKey: boolean
|
|
31
|
+
readonly defaultRandom: boolean
|
|
32
|
+
readonly unique: boolean
|
|
33
|
+
readonly readOnly: boolean
|
|
34
|
+
readonly tenant: boolean
|
|
35
|
+
/** A stored generated column: Postgres computes it from `as`; writers never send it. */
|
|
36
|
+
readonly generated?: { readonly as: SQL | (() => SQL) }
|
|
37
|
+
/** The column's collation (text fields only): `COLLATE "name"` in generated migrations. */
|
|
38
|
+
readonly collation?: CollationName
|
|
39
|
+
/** A list of the field's type (`uuid[]`, `text[]`); `.min/.max/...` apply to each element. */
|
|
40
|
+
readonly array: boolean
|
|
41
|
+
/**
|
|
42
|
+
* Whether this field may be captured into a trace step's input snapshot.
|
|
43
|
+
* Fail-closed: fields are trace-unsafe unless `.traceSafe()` opts them in,
|
|
44
|
+
* so a new field is never captured by accident.
|
|
45
|
+
*/
|
|
46
|
+
readonly traceSafe: boolean
|
|
47
|
+
readonly brand?: string
|
|
48
|
+
readonly reference?: {
|
|
49
|
+
/**
|
|
50
|
+
* Eager DefinedTable, or a thunk returning one. The thunk form lets a
|
|
51
|
+
* field reference its own table (self-reference) or a table defined later
|
|
52
|
+
* in the same import cycle (forward-reference) — the thunk is only
|
|
53
|
+
* resolved at table build / validation time, after the cycle has settled.
|
|
54
|
+
*/
|
|
55
|
+
readonly definition: DomainDefinition | (() => DomainDefinition)
|
|
56
|
+
readonly field: string
|
|
57
|
+
/**
|
|
58
|
+
* When `false`, the brand and the validator's brand-reuse check inherit
|
|
59
|
+
* from the referenced field, but no FK constraint is emitted in the
|
|
60
|
+
* generated DDL. Used for cross-partition references where an enforced
|
|
61
|
+
* FK would prevent independent partition lifecycle (e.g. `audit_record`
|
|
62
|
+
* keeping `traceId` after the trace partition is dropped). Default true.
|
|
63
|
+
*/
|
|
64
|
+
readonly enforced?: boolean
|
|
65
|
+
}
|
|
66
|
+
readonly min?: number
|
|
67
|
+
readonly max?: number
|
|
68
|
+
readonly autoManaged: boolean
|
|
69
|
+
readonly email: boolean
|
|
70
|
+
readonly url: boolean
|
|
71
|
+
readonly regex?: RegExp
|
|
72
|
+
readonly precision?: { readonly p: number; readonly s: number }
|
|
73
|
+
readonly enumValues?: readonly string[]
|
|
74
|
+
readonly jsonbShape?: z.ZodType
|
|
75
|
+
readonly serializedAs?: 'string'
|
|
76
|
+
readonly customColumn?: unknown
|
|
77
|
+
readonly customValidator?: z.ZodType
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Phantom flags tracked at the type level so insert/select/update projection
|
|
82
|
+
* types can omit auto-managed columns, mark primary-key+defaulted columns as
|
|
83
|
+
* optional, etc. The runtime reads from `_meta`; `_flags` exists only for the
|
|
84
|
+
* type system.
|
|
85
|
+
*/
|
|
86
|
+
export interface FieldFlags {
|
|
87
|
+
readonly nullable?: boolean
|
|
88
|
+
readonly hasDefault?: boolean
|
|
89
|
+
readonly primaryKey?: boolean
|
|
90
|
+
readonly defaultRandom?: boolean
|
|
91
|
+
readonly autoManaged?: boolean
|
|
92
|
+
readonly readOnly?: boolean
|
|
93
|
+
/** A stored generated column; omitted from insert and update shapes. */
|
|
94
|
+
readonly generated?: boolean
|
|
95
|
+
readonly serial?: boolean
|
|
96
|
+
/** The tenant column; a scoped write fills it from the flow, so inserts may omit it. */
|
|
97
|
+
readonly tenant?: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A field in a domain definition — carries runtime metadata and phantom output/flag types. */
|
|
101
|
+
export interface FieldDescriptor<TOutput = unknown, TFlags extends FieldFlags = FieldFlags> {
|
|
102
|
+
readonly _meta: FieldMeta
|
|
103
|
+
readonly _output?: TOutput
|
|
104
|
+
readonly _flags?: TFlags
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- variance-safe constraint for exactOptionalPropertyTypes
|
|
108
|
+
type AnyFieldDescriptor = FieldDescriptor<any, any>
|
|
109
|
+
|
|
110
|
+
/** Constraint alias for generic bounds — safe under exactOptionalPropertyTypes. */
|
|
111
|
+
export type FieldRecord = Record<string, AnyFieldDescriptor>
|
|
112
|
+
|
|
113
|
+
/** Referential action for a foreign key on UPDATE / DELETE. */
|
|
114
|
+
export type ForeignKeyAction = 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default'
|
|
115
|
+
|
|
116
|
+
/** A multi-column foreign key — the only way to reference a partitioned table's composite key. */
|
|
117
|
+
export interface CompositeForeignKey {
|
|
118
|
+
/** Local field keys forming the key, in order. */
|
|
119
|
+
readonly columns: readonly string[]
|
|
120
|
+
readonly references: {
|
|
121
|
+
/**
|
|
122
|
+
* Eager DefinedTable or a thunk returning one. The thunk form lets a
|
|
123
|
+
* composite FK target a table from the same import cycle (e.g.
|
|
124
|
+
* `trace_step` referencing `trace` where both modules import each other
|
|
125
|
+
* via field-level thunked references).
|
|
126
|
+
*/
|
|
127
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DefinedTable's PgTable base is invariant under exactOptionalPropertyTypes; the concrete field generics are irrelevant here
|
|
128
|
+
readonly definition: DefinedTable<any> | (() => DefinedTable<any>)
|
|
129
|
+
/** Foreign field keys, positionally matched to `columns`. */
|
|
130
|
+
readonly columns: readonly string[]
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* `{ setNull: [...] }` / `{ setDefault: [...] }` clear only the listed local
|
|
134
|
+
* fields (Postgres 15+), so a tenant column in the key stays in place.
|
|
135
|
+
*/
|
|
136
|
+
readonly onDelete?: ForeignKeyAction | { readonly setNull: readonly string[] } | { readonly setDefault: readonly string[] }
|
|
137
|
+
readonly onUpdate?: ForeignKeyAction
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Resolves a possibly-lazy definition reference to its concrete DefinedTable. */
|
|
141
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- structural pass-through; concrete generics flow at the call site
|
|
142
|
+
export function resolveDefinitionRef<T extends DefinedTable<any>>(ref: T | (() => T)): T {
|
|
143
|
+
return typeof ref === 'function' ? ref() : ref
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Declares a table as RANGE-partitioned. The partition column must be part of the primary key. */
|
|
147
|
+
export interface PartitionSpec {
|
|
148
|
+
readonly strategy: 'range'
|
|
149
|
+
/** Field key of the partition column. */
|
|
150
|
+
readonly column: string
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Options for pipe.define() — database affinity, virtual mode, custom indexes, keys, partitioning. */
|
|
154
|
+
export interface DefineOptions {
|
|
155
|
+
readonly database?: string
|
|
156
|
+
readonly virtual?: boolean
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Drizzle column/index types are deeply generic
|
|
158
|
+
readonly indexes?: (columns: Record<string, any>) => Record<string, any>
|
|
159
|
+
/** Composite primary key by field key. Mutually exclusive with field-level .primaryKey(). */
|
|
160
|
+
readonly primaryKey?: readonly string[]
|
|
161
|
+
/** Multi-column foreign keys. Single-column FKs use field-level .references(). */
|
|
162
|
+
readonly foreignKeys?: readonly CompositeForeignKey[]
|
|
163
|
+
/** Marks the table RANGE-partitioned. Partitions are managed outside the migration. */
|
|
164
|
+
readonly partitionBy?: PartitionSpec
|
|
165
|
+
/**
|
|
166
|
+
* The `@touches` entity name writes to this table are audited under. When set it
|
|
167
|
+
* must equal the touch's entity exactly; otherwise the touch matches by normalized
|
|
168
|
+
* name (case-insensitive, underscores ignored, table name optionally plural).
|
|
169
|
+
*/
|
|
170
|
+
readonly auditEntity?: string
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Column type projected from a FieldDescriptor — carries the right data type for Drizzle operators. */
|
|
174
|
+
export type FieldColumn<F extends FieldDescriptor, TName extends string = string> = AnyPgColumn<{
|
|
175
|
+
data: NonNullable<InferFieldOutput<F>>
|
|
176
|
+
notNull: null extends InferFieldOutput<F> ? false : true
|
|
177
|
+
// A tenant column counts as defaulted: the scoped `pipe()` DB fills it from the flow (auth/tenant/scoped-db.ts wrapInsertBuilder).
|
|
178
|
+
hasDefault: F extends FieldDescriptor<unknown, infer TF> ? (TF['hasDefault'] extends true ? true : TF['tenant'] extends true ? true : TF['generated'] extends true ? true : false) : false
|
|
179
|
+
tableName: TName
|
|
180
|
+
}> & {
|
|
181
|
+
// Plain columns are neither generated nor identity, so drizzle's insert model lists them as optional when nullable/defaulted;
|
|
182
|
+
// a `.generatedAlwaysAs()` field is generated, which drizzle's insert and update models omit.
|
|
183
|
+
readonly _: { readonly generated: F extends FieldDescriptor<unknown, infer TF> ? (TF['generated'] extends true ? { type: 'always' } : undefined) : undefined; readonly identity: undefined }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Maps field definitions to typed Drizzle column references. */
|
|
187
|
+
export type ProjectedColumns<TFields extends FieldRecord, TName extends string = string> = {
|
|
188
|
+
readonly [K in keyof TFields & string]: FieldColumn<TFields[K], TName>
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Definition metadata and projection methods — the non-table surface of a DefinedTable. */
|
|
192
|
+
export interface DefinitionMethods<TFields extends FieldRecord = FieldRecord, TName extends string = string> {
|
|
193
|
+
readonly $name: TName
|
|
194
|
+
readonly $fields: TFields
|
|
195
|
+
readonly $options: DefineOptions
|
|
196
|
+
readonly $traits: readonly string[]
|
|
197
|
+
table(): Table
|
|
198
|
+
insertShape(): z.ZodType<InsertProjection<TFields>>
|
|
199
|
+
selectShape(): z.ZodType<SelectProjection<TFields>>
|
|
200
|
+
updateShape(): z.ZodType<UpdateProjection<TFields>>
|
|
201
|
+
factory(): FactoryBuilder
|
|
202
|
+
effectiveDated(): DefinedTable<TFields & EffectiveDatedFields, TName>
|
|
203
|
+
/** Versions never updated or deleted; see `temporal.setVersion` / `temporal.versionAt`. Not combinable with effectiveDated or softDeleted. */
|
|
204
|
+
appendOnlyVersions(): DefinedTable<TFields & AppendOnlyVersionsFields, TName>
|
|
205
|
+
audited(): DefinedTable<TFields & AuditedFields, TName>
|
|
206
|
+
softDeleted(): DefinedTable<TFields & SoftDeletedFields, TName>
|
|
207
|
+
readonly Select: InferSelect<TFields>
|
|
208
|
+
readonly Insert: AcceptReadonly<InsertProjection<TFields>>
|
|
209
|
+
readonly Update: AcceptReadonly<UpdateProjection<TFields>>
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* A domain definition that IS a Drizzle PgTable — columns are direct properties,
|
|
214
|
+
* usable in from(), eq(), orderBy(), joins, and partial selects.
|
|
215
|
+
*
|
|
216
|
+
* Created by pipe.define(). Metadata uses $ prefix ($name, $fields, $options, $traits)
|
|
217
|
+
* to avoid collisions with column properties.
|
|
218
|
+
*
|
|
219
|
+
* The PgTable base is parameterized with ProjectedColumns so that Drizzle's
|
|
220
|
+
* _['columns'] carries named, branded column types. Without this, select()
|
|
221
|
+
* and returning() fall back to index-signature records.
|
|
222
|
+
*/
|
|
223
|
+
export type DefinedTable<TFields extends FieldRecord = FieldRecord, TName extends string = string> =
|
|
224
|
+
PgTable<{ name: TName; schema: string | undefined; columns: ProjectedColumns<TFields, TName>; dialect: 'pg' }>
|
|
225
|
+
& ProjectedColumns<TFields, TName>
|
|
226
|
+
& DefinitionMethods<TFields, TName>
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* A table or view owned by another service, declared with pipe.external. Readable
|
|
230
|
+
* with every query operator; not a DefinedTable, so the write builders reject it.
|
|
231
|
+
*/
|
|
232
|
+
export type ExternalTable<TFields extends FieldRecord = FieldRecord, TName extends string = string> =
|
|
233
|
+
PgTable<{ name: TName; schema: string; columns: ProjectedColumns<TFields, TName>; dialect: 'pg' }>
|
|
234
|
+
& ProjectedColumns<TFields, TName>
|
|
235
|
+
& {
|
|
236
|
+
readonly $name: TName
|
|
237
|
+
readonly $fields: TFields
|
|
238
|
+
readonly $external: { readonly schema: string; readonly table: string }
|
|
239
|
+
selectShape(): z.ZodType<SelectProjection<TFields>>
|
|
240
|
+
readonly Select: InferSelect<TFields>
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @deprecated Use DefinedTable instead. DomainDefinition is kept for backward compatibility
|
|
245
|
+
* with code that hasn't migrated to the direct-column-access API.
|
|
246
|
+
*/
|
|
247
|
+
export type DomainDefinition<TFields extends FieldRecord = FieldRecord, TName extends string = string> =
|
|
248
|
+
DefinedTable<TFields, TName>
|
|
249
|
+
|
|
250
|
+
/** A named set of fields that can be applied to a domain definition via trait methods. */
|
|
251
|
+
export interface TraitDefinition {
|
|
252
|
+
readonly name: string
|
|
253
|
+
readonly fields: FieldRecord
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface EffectiveDatedFields {
|
|
257
|
+
readonly effectiveFrom: FieldDescriptor<Date, { autoManaged: true; hasDefault: true }>
|
|
258
|
+
readonly effectiveTo: FieldDescriptor<Date | null, { autoManaged: true; nullable: true }>
|
|
259
|
+
readonly version: FieldDescriptor<number, { autoManaged: true; hasDefault: true }>
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface AppendOnlyVersionsFields {
|
|
263
|
+
readonly effectiveFrom: FieldDescriptor<Date, { autoManaged: true }>
|
|
264
|
+
readonly version: FieldDescriptor<number, { autoManaged: true }>
|
|
265
|
+
readonly setBy: FieldDescriptor<string | null, { autoManaged: true; nullable: true }>
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface AuditedFields {
|
|
269
|
+
readonly createdAt: FieldDescriptor<Date, { autoManaged: true; readOnly: true; hasDefault: true }>
|
|
270
|
+
readonly updatedAt: FieldDescriptor<Date, { autoManaged: true; hasDefault: true }>
|
|
271
|
+
readonly createdBy: FieldDescriptor<string | null, { autoManaged: true; readOnly: true; nullable: true }>
|
|
272
|
+
readonly updatedBy: FieldDescriptor<string | null, { autoManaged: true; nullable: true }>
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface SoftDeletedFields {
|
|
276
|
+
readonly deletedAt: FieldDescriptor<Date | null, { autoManaged: true; nullable: true }>
|
|
277
|
+
readonly deletedBy: FieldDescriptor<string | null, { autoManaged: true; nullable: true }>
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Test data factory — inserts rows and resolves FK dependencies automatically. */
|
|
281
|
+
export interface FactoryBuilder {
|
|
282
|
+
create(overrides?: Partial<Record<string, unknown>>, options?: FactoryOptions): Promise<Record<string, unknown>>
|
|
283
|
+
createMany(count: number, overrides?: Partial<Record<string, unknown>> | ((index: number) => Partial<Record<string, unknown>>), options?: FactoryOptions): Promise<Record<string, unknown>[]>
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export interface FactoryOptions {
|
|
287
|
+
readonly db?: unknown
|
|
288
|
+
readonly tenantId?: string
|
|
289
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SQL, Column } from '../db/types.js'
|
|
2
|
+
import { cast } from '../db/expressions.js'
|
|
3
|
+
import { pathText } from './query.js'
|
|
4
|
+
|
|
5
|
+
/** Extracts a boolean from a JSONB column: `(column->>'key')::boolean`. Supports nested paths. */
|
|
6
|
+
export function boolean(column: Column | SQL, ...keys: string[]): SQL<boolean> {
|
|
7
|
+
return cast<boolean>(pathText(column, ...keys), 'boolean')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Extracts a number from a JSONB column: `(column->>'key')::numeric`. Supports nested paths. */
|
|
11
|
+
export function number(column: Column | SQL, ...keys: string[]): SQL<number> {
|
|
12
|
+
return cast<number>(pathText(column, ...keys), 'numeric')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Extracts an integer from a JSONB column: `(column->>'key')::integer`. Supports nested paths. */
|
|
16
|
+
export function integer(column: Column | SQL, ...keys: string[]): SQL<number> {
|
|
17
|
+
return cast<number>(pathText(column, ...keys), 'integer')
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { contains, containedBy, hasKey } from './query.js'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { contains, merge, remove, containedBy, hasKey, text, json, path, pathText } from './query.js'
|
|
2
|
+
import { boolean, number, integer } from './composed.js'
|
|
3
|
+
|
|
4
|
+
/** JSONB query operators — typed helpers for PostgreSQL JSONB containment, key checks, and field extraction. */
|
|
5
|
+
export const jsonb = {
|
|
6
|
+
/** The @> containment operator — tests whether a JSONB column contains the given value. */
|
|
7
|
+
contains,
|
|
8
|
+
/** Merges an object into a JSONB column in `.set()`: `coalesce(col, '{}') || value`. Top-level keys overwrite; others are kept. */
|
|
9
|
+
merge,
|
|
10
|
+
/** Removes top-level keys from a JSONB column in `.set()`: `col - 'key'`. Composes as the column of `jsonb.merge`. */
|
|
11
|
+
remove,
|
|
12
|
+
/** The <@ contained-by operator — tests whether a JSONB column is contained by the given value. */
|
|
13
|
+
containedBy,
|
|
14
|
+
/** The ? key-existence operator — tests whether a JSONB column has the given top-level key. */
|
|
15
|
+
hasKey,
|
|
16
|
+
/** Extracts a text value: `column->>'key'`. Composable with pipe.filter operators. */
|
|
17
|
+
text,
|
|
18
|
+
/** Extracts a JSONB value: `column->'key'`. Returns JSONB — use with jsonb.contains() etc. */
|
|
19
|
+
json,
|
|
20
|
+
/** Extracts a nested JSONB value: `column->'a'->'b'`. Returns JSONB. */
|
|
21
|
+
path,
|
|
22
|
+
/** Extracts a nested text value: `column->'a'->>'b'`. Intermediate keys use `->`, final uses `->>`. */
|
|
23
|
+
pathText,
|
|
24
|
+
/** Extracts a boolean: `(column->>'key')::boolean`. Supports nested paths. */
|
|
25
|
+
boolean,
|
|
26
|
+
/** Extracts a number: `(column->>'key')::numeric`. Supports nested paths. */
|
|
27
|
+
number,
|
|
28
|
+
/** Extracts an integer: `(column->>'key')::integer`. Supports nested paths. */
|
|
29
|
+
integer,
|
|
30
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { sql } from '../db/sql.js'
|
|
2
|
+
import type { SQL, Column } from '../db/types.js'
|
|
3
|
+
|
|
4
|
+
export function contains(column: Column | SQL, value: unknown): SQL<boolean> {
|
|
5
|
+
return sql<boolean>`${column} @> ${JSON.stringify(value)}::jsonb`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function containedBy(column: Column | SQL, value: unknown): SQL<boolean> {
|
|
9
|
+
return sql<boolean>`${column} <@ ${JSON.stringify(value)}::jsonb`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function hasKey(column: Column | SQL, key: string): SQL<boolean> {
|
|
13
|
+
return sql<boolean>`${column} ? ${key}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Extracts a text value from a JSONB column: `column->>'key'`. Composable with pipe.filter operators (eq, ilike, etc.). */
|
|
17
|
+
export function text(column: Column | SQL, key: string): SQL<string> {
|
|
18
|
+
return sql<string>`${column}->>${key}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Extracts a JSONB value from a JSONB column: `column->'key'`. Returns JSONB, not text — use with jsonb.contains() etc. */
|
|
22
|
+
export function json(column: Column | SQL, key: string): SQL {
|
|
23
|
+
return sql`${column}->${key}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Extracts a nested JSONB value: `column->'a'->'b'`. Returns JSONB. For text output, use `pathText`. */
|
|
27
|
+
export function path(column: Column | SQL, ...keys: string[]): SQL {
|
|
28
|
+
if (keys.length === 0) return sql`${column}`
|
|
29
|
+
let result: SQL = sql`${column}`
|
|
30
|
+
for (const key of keys) {
|
|
31
|
+
result = sql`${result}->${key}`
|
|
32
|
+
}
|
|
33
|
+
return result
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Extracts a nested text value: `column->'a'->>'b'`. Intermediate keys use `->`, final key uses `->>`. */
|
|
37
|
+
export function pathText(column: Column | SQL, ...keys: string[]): SQL<string> {
|
|
38
|
+
if (keys.length === 0) return sql<string>`${column}`
|
|
39
|
+
if (keys.length === 1) return text(column, keys[0]!)
|
|
40
|
+
const intermediateKeys = keys.slice(0, -1)
|
|
41
|
+
const finalKey = keys[keys.length - 1]!
|
|
42
|
+
const intermediate = path(column, ...intermediateKeys)
|
|
43
|
+
return text(intermediate, finalKey)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Merges `value` into a JSONB column for `.set()`: `coalesce(col, '{}'::jsonb) || value::jsonb`. Top-level keys
|
|
48
|
+
* in `value` overwrite; others are kept; a null column becomes `value`. The merge happens in the statement,
|
|
49
|
+
* so concurrent updates that touch different keys both survive.
|
|
50
|
+
*/
|
|
51
|
+
export function merge(column: Column | SQL, value: Record<string, unknown>): SQL {
|
|
52
|
+
const checked: unknown = value
|
|
53
|
+
if (typeof checked !== 'object' || checked === null || Array.isArray(checked)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`[pipework] jsonb.merge() needs a plain object, got ${checked === null ? 'null' : Array.isArray(checked) ? 'an array' : typeof checked}.\n\n` +
|
|
56
|
+
' Only an object can be merged into an object.\n' +
|
|
57
|
+
" Fix: pass an object: jsonb.merge(col, { key: 'value' }).\n",
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
return sql`coalesce(${column}, '{}'::jsonb) || ${JSON.stringify(value)}::jsonb`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Removes top-level keys from a JSONB column for `.set()`: `col - key`, or `col - array[k1, k2]::text[]` for
|
|
65
|
+
* several. Missing keys are ignored; a null column stays null. Composes as the column of `jsonb.merge`.
|
|
66
|
+
*/
|
|
67
|
+
export function remove(column: Column | SQL, ...keys: string[]): SQL {
|
|
68
|
+
if (keys.length === 0) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'[pipework] jsonb.remove() needs at least one key.\n\n' +
|
|
71
|
+
' With no keys there is nothing to remove.\n' +
|
|
72
|
+
" Fix: pass the keys to drop: jsonb.remove(col, 'key').\n",
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
for (const key of keys as unknown[]) {
|
|
76
|
+
if (typeof key !== 'string') {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`[pipework] jsonb.remove() keys must be strings, got ${key === null ? 'null' : Array.isArray(key) ? 'an array' : typeof key}.\n\n` +
|
|
79
|
+
' Each key names a top-level key of the object.\n' +
|
|
80
|
+
" Fix: pass strings as separate arguments: jsonb.remove(col, 'a', 'b').\n",
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (keys.length === 1) return sql`${column} - ${keys[0]!}::text`
|
|
85
|
+
return sql`${column} - array[${sql.join(keys.map(k => sql`${k}`), sql`, `)}]::text[]`
|
|
86
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DefinedTable } from '../domain/types.js'
|
|
2
|
+
import { camelToSnake } from '../domain/naming.js'
|
|
3
|
+
import { appendOnlyScopeField } from '../domain/traits.js'
|
|
4
|
+
|
|
5
|
+
// A fresh matcher per call: a shared `g` regex carries lastIndex between runs.
|
|
6
|
+
const createTableMatcher = (): RegExp => /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"([^"]+)"|([a-z_][a-z0-9_]*))/gi
|
|
7
|
+
|
|
8
|
+
const REFUSE_FUNCTION =
|
|
9
|
+
`CREATE OR REPLACE FUNCTION pipework_refuse_version_rewrite() RETURNS trigger LANGUAGE plpgsql AS $$ ` +
|
|
10
|
+
`BEGIN RAISE EXCEPTION '% is append-only: % is not allowed', TG_TABLE_NAME, TG_OP ` +
|
|
11
|
+
`USING ERRCODE = 'restrict_violation'; END $$`
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* For each table the migration creates whose definition has the
|
|
15
|
+
* `.appendOnlyVersions()` trait, appends the trigger that refuses UPDATE and
|
|
16
|
+
* DELETE and the unique index on (scope, version). Only a CREATE TABLE in this
|
|
17
|
+
* migration triggers it.
|
|
18
|
+
*/
|
|
19
|
+
export function generateAppendOnlyStatements(
|
|
20
|
+
migrationSql: string,
|
|
21
|
+
definitions: ReadonlyMap<string, DefinedTable<any>>, // eslint-disable-line @typescript-eslint/no-explicit-any -- registry erases field generics
|
|
22
|
+
): string {
|
|
23
|
+
const blocks: string[] = []
|
|
24
|
+
const seen = new Set<string>()
|
|
25
|
+
const createTableRe = createTableMatcher()
|
|
26
|
+
let match: RegExpExecArray | null
|
|
27
|
+
while ((match = createTableRe.exec(migrationSql)) !== null) {
|
|
28
|
+
const tableName = match[1] ?? match[2]
|
|
29
|
+
if (tableName === undefined || seen.has(tableName)) continue
|
|
30
|
+
const definition = definitions.get(tableName)
|
|
31
|
+
if (definition === undefined || !definition.$traits.includes('appendOnlyVersions')) continue
|
|
32
|
+
seen.add(tableName)
|
|
33
|
+
|
|
34
|
+
const scope = appendOnlyScopeField(definition)
|
|
35
|
+
const indexColumns = scope === undefined
|
|
36
|
+
? `"version"`
|
|
37
|
+
: `"${camelToSnake(scope)}", "version"`
|
|
38
|
+
blocks.push(
|
|
39
|
+
`CREATE TRIGGER pipework_append_only BEFORE UPDATE OR DELETE ON "${tableName}" ` +
|
|
40
|
+
`FOR EACH ROW EXECUTE FUNCTION pipework_refuse_version_rewrite()`,
|
|
41
|
+
`CREATE UNIQUE INDEX "${tableName}_version_unique" ON "${tableName}" (${indexColumns})`,
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (blocks.length === 0) return migrationSql
|
|
46
|
+
return migrationSql + '\n\n' + [REFUSE_FUNCTION, ...blocks].join(';\n\n') + ';'
|
|
47
|
+
}
|