pipework 0.251.0 → 0.252.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/core/config/load.d.ts +2 -0
- package/dist/core/config/load.d.ts.map +1 -1
- package/dist/core/config/load.js +1 -0
- package/dist/core/config/load.js.map +1 -1
- package/dist/core/config/namespace.d.ts +1 -0
- package/dist/core/config/namespace.d.ts.map +1 -1
- package/dist/core/config/schema.d.ts +2 -0
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +1 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/surface/types.d.ts +1 -0
- package/dist/core/surface/types.d.ts.map +1 -1
- package/dist/data/db/namespace.d.ts +3 -0
- package/dist/data/db/namespace.d.ts.map +1 -1
- package/dist/data/db/namespace.js +3 -0
- package/dist/data/db/namespace.js.map +1 -1
- package/dist/data/db/pool.d.ts.map +1 -1
- package/dist/data/db/pool.js +6 -1
- package/dist/data/db/pool.js.map +1 -1
- package/dist/data/db/query-label.d.ts +5 -0
- package/dist/data/db/query-label.d.ts.map +1 -0
- package/dist/data/db/query-label.js +11 -0
- package/dist/data/db/query-label.js.map +1 -0
- package/dist/data/db/query-watch.d.ts +21 -0
- package/dist/data/db/query-watch.d.ts.map +1 -0
- package/dist/data/db/query-watch.js +90 -0
- package/dist/data/db/query-watch.js.map +1 -0
- package/dist/data/query/postgres-js/driver.js +1 -1
- package/dist/data/query/postgres-js/driver.js.map +1 -1
- package/dist/data/query/postgres-js/session.d.ts +5 -1
- package/dist/data/query/postgres-js/session.d.ts.map +1 -1
- package/dist/data/query/postgres-js/session.js +16 -11
- package/dist/data/query/postgres-js/session.js.map +1 -1
- package/dist/data/query/utils.d.ts +2 -0
- package/dist/data/query/utils.d.ts.map +1 -1
- package/dist/data/query/utils.js.map +1 -1
- package/dist/infra/credits/schema.d.ts +1 -1
- package/dist/test/plugin.d.ts.map +1 -1
- package/dist/test/plugin.js +10 -4
- package/dist/test/plugin.js.map +1 -1
- package/package.json +4 -3
- package/src/async/jobs/abortable-delay.ts +18 -0
- package/src/async/jobs/claims.ts +52 -0
- package/src/async/jobs/cron.ts +179 -0
- package/src/async/jobs/errors.ts +56 -0
- package/src/async/jobs/exclusion.ts +117 -0
- package/src/async/jobs/execute.ts +219 -0
- package/src/async/jobs/index.ts +10 -0
- package/src/async/jobs/listener.ts +135 -0
- package/src/async/jobs/namespace.ts +20 -0
- package/src/async/jobs/queue-door.ts +44 -0
- package/src/async/jobs/queue.ts +1012 -0
- package/src/async/jobs/run-script.ts +67 -0
- package/src/async/jobs/scheduler.ts +218 -0
- package/src/async/jobs/schedules-table.ts +37 -0
- package/src/async/jobs/serialize-error.ts +48 -0
- package/src/async/jobs/table.ts +191 -0
- package/src/async/pipeline/define.ts +53 -0
- package/src/async/pipeline/errors.ts +35 -0
- package/src/async/pipeline/execute.ts +53 -0
- package/src/async/pipeline/index.ts +13 -0
- package/src/async/pipeline/namespace.ts +12 -0
- package/src/async/pipeline/resume.ts +67 -0
- package/src/async/pipeline/run-steps.ts +98 -0
- package/src/async/pipeline/types.ts +57 -0
- package/src/async/state-machine/define.ts +64 -0
- package/src/async/state-machine/errors.ts +29 -0
- package/src/async/state-machine/index.ts +3 -0
- package/src/async/state-machine/namespace.ts +12 -0
- package/src/async/state-machine/transition.ts +90 -0
- package/src/async/state-machine/types.ts +37 -0
- package/src/async/steps/errors.ts +31 -0
- package/src/async/steps/graph.ts +1214 -0
- package/src/async/steps/hash.ts +40 -0
- package/src/async/steps/index.ts +7 -0
- package/src/async/steps/namespace.ts +7 -0
- package/src/async/steps/table.ts +97 -0
- package/src/async/steps/types.ts +318 -0
- package/src/audit/chain/index.ts +9 -0
- package/src/audit/chain/install.ts +113 -0
- package/src/audit/chain/retire.ts +52 -0
- package/src/audit/chain/verify.ts +257 -0
- package/src/audit/query/cursor.ts +37 -0
- package/src/audit/query/index.ts +9 -0
- package/src/audit/query/query.ts +223 -0
- package/src/audit/schema/audit-record-fields.ts +68 -0
- package/src/audit/schema/audit-record.ts +23 -0
- package/src/auth/auth/audit.ts +31 -0
- package/src/auth/auth/chain.ts +84 -0
- package/src/auth/auth/cookie.ts +136 -0
- package/src/auth/auth/email-links.ts +311 -0
- package/src/auth/auth/guard.ts +128 -0
- package/src/auth/auth/index.ts +18 -0
- package/src/auth/auth/multi-org.ts +337 -0
- package/src/auth/auth/namespace.ts +34 -0
- package/src/auth/auth/parse-expiry.ts +13 -0
- package/src/auth/auth/passwords.ts +110 -0
- package/src/auth/auth/schema.ts +110 -0
- package/src/auth/auth/sessions.ts +565 -0
- package/src/auth/auth/sign-in.ts +156 -0
- package/src/auth/auth/signing-keys.ts +69 -0
- package/src/auth/auth/types.ts +26 -0
- package/src/auth/auth/uuid.ts +10 -0
- package/src/auth/rbac/cache.ts +55 -0
- package/src/auth/rbac/check.ts +30 -0
- package/src/auth/rbac/create.ts +35 -0
- package/src/auth/rbac/index.ts +5 -0
- package/src/auth/rbac/middleware.ts +20 -0
- package/src/auth/rbac/namespace.ts +15 -0
- package/src/auth/rbac/resolve.ts +31 -0
- package/src/auth/rbac/schema.sql +31 -0
- package/src/auth/rbac/seed.ts +67 -0
- package/src/auth/rbac/tables.ts +32 -0
- package/src/auth/rbac/types.ts +65 -0
- package/src/auth/tenant/extract.ts +33 -0
- package/src/auth/tenant/in-tenant.ts +49 -0
- package/src/auth/tenant/index.ts +7 -0
- package/src/auth/tenant/namespace.ts +23 -0
- package/src/auth/tenant/propagate.ts +20 -0
- package/src/auth/tenant/rls.ts +120 -0
- package/src/auth/tenant/scope.ts +33 -0
- package/src/auth/tenant/scoped-db.ts +581 -0
- package/src/auth/tenant/types.ts +14 -0
- package/src/auth/tenant/validate.ts +27 -0
- package/src/cli/commands/add.ts +158 -0
- package/src/cli/commands/alerts.ts +98 -0
- package/src/cli/commands/check.ts +1418 -0
- package/src/cli/commands/codemod.ts +78 -0
- package/src/cli/commands/db.ts +356 -0
- package/src/cli/commands/dev.ts +101 -0
- package/src/cli/commands/generate.ts +75 -0
- package/src/cli/commands/init.ts +36 -0
- package/src/cli/commands/install.ts +28 -0
- package/src/cli/commands/lint.ts +62 -0
- package/src/cli/commands/migrate.ts +132 -0
- package/src/cli/commands/openapi.ts +63 -0
- package/src/cli/commands/remove.ts +72 -0
- package/src/cli/commands/run.ts +50 -0
- package/src/cli/commands/scope.ts +54 -0
- package/src/cli/commands/serve.ts +68 -0
- package/src/cli/commands/shard.ts +31 -0
- package/src/cli/commands/shared/config-pipework-version.ts +28 -0
- package/src/cli/commands/shared/find-config.ts +22 -0
- package/src/cli/commands/shared/load-config.ts +35 -0
- package/src/cli/commands/shared/pnpm-install.ts +95 -0
- package/src/cli/commands/shared/report-removed.ts +8 -0
- package/src/cli/commands/sync.ts +32 -0
- package/src/cli/commands/test.ts +307 -0
- package/src/cli/commands/upgrade.ts +48 -0
- package/src/cli/dev-bootstrap.ts +24 -0
- package/src/cli/errors.ts +10 -0
- package/src/cli/index.ts +303 -0
- package/src/cli/output.ts +17 -0
- package/src/cli/port.ts +15 -0
- package/src/cli/surface-kinds.ts +20 -0
- package/src/client/index.ts +4 -0
- package/src/client/poll.ts +130 -0
- package/src/client/report-fault.ts +119 -0
- package/src/core/config/app-pool.ts +19 -0
- package/src/core/config/aws-env.ts +29 -0
- package/src/core/config/base-dir.ts +24 -0
- package/src/core/config/discover.ts +64 -0
- package/src/core/config/env-resolve.ts +87 -0
- package/src/core/config/env-types.ts +116 -0
- package/src/core/config/env.ts +55 -0
- package/src/core/config/errors.ts +9 -0
- package/src/core/config/import-config.ts +40 -0
- package/src/core/config/index.ts +8 -0
- package/src/core/config/load.ts +417 -0
- package/src/core/config/namespace.ts +14 -0
- package/src/core/config/resolve.ts +195 -0
- package/src/core/config/schema.ts +488 -0
- package/src/core/config/statement-observer.ts +48 -0
- package/src/core/config/ts-inline-hooks.ts +75 -0
- package/src/core/config/ts-register.ts +4 -0
- package/src/core/config/ts-resolve-hooks.ts +101 -0
- package/src/core/errors/base.ts +49 -0
- package/src/core/errors/http.ts +146 -0
- package/src/core/errors/index.ts +2 -0
- package/src/core/errors/namespace.ts +31 -0
- package/src/core/lifecycle/drain.ts +33 -0
- package/src/core/lifecycle/health.ts +72 -0
- package/src/core/lifecycle/index.ts +4 -0
- package/src/core/lifecycle/namespace.ts +7 -0
- package/src/core/lifecycle/startup.ts +102 -0
- package/src/core/lifecycle/types.ts +36 -0
- package/src/core/logging/context.ts +60 -0
- package/src/core/logging/error-chain.ts +68 -0
- package/src/core/logging/index.ts +5 -0
- package/src/core/logging/logger.ts +65 -0
- package/src/core/logging/namespace.ts +16 -0
- package/src/core/logging/proxy.ts +22 -0
- package/src/core/logging/redact-match.ts +24 -0
- package/src/core/logging/types.ts +47 -0
- package/src/core/pipework.ts +566 -0
- package/src/core/runtime/broker.ts +69 -0
- package/src/core/surface/http.ts +133 -0
- package/src/core/surface/index.ts +11 -0
- package/src/core/surface/job-timeout.ts +40 -0
- package/src/core/surface/namespace.ts +13 -0
- package/src/core/surface/script.ts +33 -0
- package/src/core/surface/start.ts +142 -0
- package/src/core/surface/types.ts +225 -0
- package/src/core/surface/validate.ts +88 -0
- package/src/core/surface/worker.ts +528 -0
- package/src/core/validation/connectivity.ts +49 -0
- package/src/core/validation/domain.ts +99 -0
- package/src/core/validation/index.ts +5 -0
- package/src/core/validation/queue-tables.ts +312 -0
- package/src/core/validation/rls-role.ts +138 -0
- package/src/core/validation/startup.ts +51 -0
- package/src/core/validation/surfaces.ts +65 -0
- package/src/core/validation/types.ts +25 -0
- package/src/data/db/advisory-lock.ts +55 -0
- package/src/data/db/aggregates.ts +172 -0
- package/src/data/db/array-elements.ts +111 -0
- package/src/data/db/bulk-set.ts +53 -0
- package/src/data/db/col/index.ts +14 -0
- package/src/data/db/composed.ts +53 -0
- package/src/data/db/db.ts +276 -0
- package/src/data/db/excluded.ts +16 -0
- package/src/data/db/expressions.ts +473 -0
- package/src/data/db/filter.ts +90 -0
- package/src/data/db/function-source.ts +114 -0
- package/src/data/db/identifiers.ts +16 -0
- package/src/data/db/idx/index.ts +9 -0
- package/src/data/db/in-list.ts +49 -0
- package/src/data/db/index.ts +9 -0
- package/src/data/db/insert-split.ts +137 -0
- package/src/data/db/instrumentation.ts +82 -0
- package/src/data/db/maintenance.ts +28 -0
- package/src/data/db/namespace.ts +107 -0
- package/src/data/db/owner-labels.ts +9 -0
- package/src/data/db/pool.ts +187 -0
- package/src/data/db/query-label.ts +13 -0
- package/src/data/db/query-watch.ts +116 -0
- package/src/data/db/raw-client-warning.ts +37 -0
- package/src/data/db/readonly.ts +23 -0
- package/src/data/db/reservation.ts +151 -0
- package/src/data/db/schema-features.ts +14 -0
- package/src/data/db/serializable.ts +83 -0
- package/src/data/db/sql.ts +1 -0
- package/src/data/db/timeouts.ts +76 -0
- package/src/data/db/types.ts +18 -0
- package/src/data/db/unbound.ts +33 -0
- package/src/data/db/values.ts +29 -0
- package/src/data/domain/brand.ts +15 -0
- package/src/data/domain/build-factory.ts +193 -0
- package/src/data/domain/build-schema.ts +161 -0
- package/src/data/domain/build-table.ts +355 -0
- package/src/data/domain/define.ts +239 -0
- package/src/data/domain/external-registry.ts +34 -0
- package/src/data/domain/external.ts +77 -0
- package/src/data/domain/field.ts +279 -0
- package/src/data/domain/index.ts +13 -0
- package/src/data/domain/naming.ts +6 -0
- package/src/data/domain/projections.ts +57 -0
- package/src/data/domain/trace-safe-projection.ts +31 -0
- package/src/data/domain/traits.ts +108 -0
- package/src/data/domain/types.ts +289 -0
- package/src/data/jsonb/composed.ts +18 -0
- package/src/data/jsonb/index.ts +1 -0
- package/src/data/jsonb/namespace.ts +30 -0
- package/src/data/jsonb/query.ts +86 -0
- package/src/data/migrate/append-only-generate.ts +47 -0
- package/src/data/migrate/applied.ts +47 -0
- package/src/data/migrate/apply.ts +309 -0
- package/src/data/migrate/check.ts +119 -0
- package/src/data/migrate/diff.ts +357 -0
- package/src/data/migrate/door-generate.ts +130 -0
- package/src/data/migrate/framework-rls.ts +58 -0
- package/src/data/migrate/generate.ts +201 -0
- package/src/data/migrate/history.ts +215 -0
- package/src/data/migrate/index.ts +9 -0
- package/src/data/migrate/internal-definitions.ts +175 -0
- package/src/data/migrate/post-process.ts +73 -0
- package/src/data/migrate/pragma.ts +73 -0
- package/src/data/migrate/provision-framework.ts +147 -0
- package/src/data/migrate/rls-generate.ts +61 -0
- package/src/data/migrate/run.ts +124 -0
- package/src/data/migrate/safety.ts +167 -0
- package/src/data/migrate/snapshot.ts +368 -0
- package/src/data/migrate/sql-emit.ts +392 -0
- package/src/data/migrate/statements.ts +312 -0
- package/src/data/query/alias.ts +126 -0
- package/src/data/query/batch.ts +8 -0
- package/src/data/query/cache/core/cache.ts +78 -0
- package/src/data/query/cache/core/index.ts +1 -0
- package/src/data/query/cache/core/types.ts +29 -0
- package/src/data/query/cache/readme.md +230 -0
- package/src/data/query/casing.ts +76 -0
- package/src/data/query/collation.ts +11 -0
- package/src/data/query/column-builder.ts +370 -0
- package/src/data/query/column.ts +150 -0
- package/src/data/query/entity.ts +42 -0
- package/src/data/query/errors.ts +33 -0
- package/src/data/query/index.ts +14 -0
- package/src/data/query/logger.ts +47 -0
- package/src/data/query/operations.ts +45 -0
- package/src/data/query/pg-core/alias.ts +12 -0
- package/src/data/query/pg-core/checks.ts +32 -0
- package/src/data/query/pg-core/columns/all.ts +71 -0
- package/src/data/query/pg-core/columns/bigint.ts +110 -0
- package/src/data/query/pg-core/columns/bigserial.ts +128 -0
- package/src/data/query/pg-core/columns/boolean.ts +43 -0
- package/src/data/query/pg-core/columns/char.ts +85 -0
- package/src/data/query/pg-core/columns/cidr.ts +43 -0
- package/src/data/query/pg-core/columns/common.ts +366 -0
- package/src/data/query/pg-core/columns/custom.ts +232 -0
- package/src/data/query/pg-core/columns/date.common.ts +15 -0
- package/src/data/query/pg-core/columns/date.ts +112 -0
- package/src/data/query/pg-core/columns/double-precision.ts +55 -0
- package/src/data/query/pg-core/columns/enum.ts +201 -0
- package/src/data/query/pg-core/columns/index.ts +35 -0
- package/src/data/query/pg-core/columns/inet.ts +43 -0
- package/src/data/query/pg-core/columns/int.common.ts +57 -0
- package/src/data/query/pg-core/columns/integer.ts +53 -0
- package/src/data/query/pg-core/columns/interval.ts +83 -0
- package/src/data/query/pg-core/columns/json.ts +58 -0
- package/src/data/query/pg-core/columns/jsonb.ts +56 -0
- package/src/data/query/pg-core/columns/line.ts +117 -0
- package/src/data/query/pg-core/columns/macaddr.ts +43 -0
- package/src/data/query/pg-core/columns/macaddr8.ts +43 -0
- package/src/data/query/pg-core/columns/numeric.ts +227 -0
- package/src/data/query/pg-core/columns/point.ts +127 -0
- package/src/data/query/pg-core/columns/postgis_extension/geometry.ts +119 -0
- package/src/data/query/pg-core/columns/postgis_extension/utils.ts +47 -0
- package/src/data/query/pg-core/columns/real.ts +58 -0
- package/src/data/query/pg-core/columns/serial.ts +55 -0
- package/src/data/query/pg-core/columns/smallint.ts +53 -0
- package/src/data/query/pg-core/columns/smallserial.ts +60 -0
- package/src/data/query/pg-core/columns/text.ts +67 -0
- package/src/data/query/pg-core/columns/time.ts +72 -0
- package/src/data/query/pg-core/columns/timestamp.ts +163 -0
- package/src/data/query/pg-core/columns/uuid.ts +51 -0
- package/src/data/query/pg-core/columns/varchar.ts +89 -0
- package/src/data/query/pg-core/columns/vector_extension/bit.ts +68 -0
- package/src/data/query/pg-core/columns/vector_extension/halfvec.ts +80 -0
- package/src/data/query/pg-core/columns/vector_extension/sparsevec.ts +67 -0
- package/src/data/query/pg-core/columns/vector_extension/vector.ts +80 -0
- package/src/data/query/pg-core/db.ts +674 -0
- package/src/data/query/pg-core/dialect.ts +1404 -0
- package/src/data/query/pg-core/expressions.ts +25 -0
- package/src/data/query/pg-core/foreign-keys.ts +130 -0
- package/src/data/query/pg-core/index.ts +21 -0
- package/src/data/query/pg-core/indexes.ts +276 -0
- package/src/data/query/pg-core/policies.ts +56 -0
- package/src/data/query/pg-core/primary-keys.ts +62 -0
- package/src/data/query/pg-core/query-builders/count.ts +86 -0
- package/src/data/query/pg-core/query-builders/delete.ts +293 -0
- package/src/data/query/pg-core/query-builders/index.ts +7 -0
- package/src/data/query/pg-core/query-builders/insert.ts +452 -0
- package/src/data/query/pg-core/query-builders/query-builder.ts +123 -0
- package/src/data/query/pg-core/query-builders/query.ts +157 -0
- package/src/data/query/pg-core/query-builders/raw.ts +49 -0
- package/src/data/query/pg-core/query-builders/refresh-materialized-view.ts +108 -0
- package/src/data/query/pg-core/query-builders/select.ts +1346 -0
- package/src/data/query/pg-core/query-builders/select.types.ts +437 -0
- package/src/data/query/pg-core/query-builders/update.ts +626 -0
- package/src/data/query/pg-core/roles.ts +41 -0
- package/src/data/query/pg-core/schema.ts +72 -0
- package/src/data/query/pg-core/sequence.ts +41 -0
- package/src/data/query/pg-core/session.ts +292 -0
- package/src/data/query/pg-core/subquery.ts +46 -0
- package/src/data/query/pg-core/table.ts +252 -0
- package/src/data/query/pg-core/unique-constraint.ts +73 -0
- package/src/data/query/pg-core/utils/array.ts +95 -0
- package/src/data/query/pg-core/utils/index.ts +1 -0
- package/src/data/query/pg-core/utils.ts +104 -0
- package/src/data/query/pg-core/view-base.ts +14 -0
- package/src/data/query/pg-core/view-common.ts +1 -0
- package/src/data/query/pg-core/view.ts +435 -0
- package/src/data/query/pg-core/with-builder.ts +41 -0
- package/src/data/query/postgres-js/README.md +46 -0
- package/src/data/query/postgres-js/array-parser.ts +45 -0
- package/src/data/query/postgres-js/driver-error-boundary.ts +149 -0
- package/src/data/query/postgres-js/driver.ts +163 -0
- package/src/data/query/postgres-js/index.ts +2 -0
- package/src/data/query/postgres-js/session.ts +234 -0
- package/src/data/query/primary-key.ts +11 -0
- package/src/data/query/query-builders/query-builder.ts +19 -0
- package/src/data/query/query-builders/select.types.ts +175 -0
- package/src/data/query/query-promise.ts +35 -0
- package/src/data/query/relations.ts +725 -0
- package/src/data/query/runnable-query.ts +12 -0
- package/src/data/query/selection-proxy.ts +121 -0
- package/src/data/query/session.ts +8 -0
- package/src/data/query/sql/expressions/conditions.ts +741 -0
- package/src/data/query/sql/expressions/index.ts +2 -0
- package/src/data/query/sql/expressions/select.ts +45 -0
- package/src/data/query/sql/functions/aggregate.ts +129 -0
- package/src/data/query/sql/functions/index.ts +2 -0
- package/src/data/query/sql/functions/vector.ts +178 -0
- package/src/data/query/sql/index.ts +3 -0
- package/src/data/query/sql/sql.ts +794 -0
- package/src/data/query/subquery.ts +53 -0
- package/src/data/query/table.ts +208 -0
- package/src/data/query/table.utils.ts +2 -0
- package/src/data/query/tracing-utils.ts +3 -0
- package/src/data/query/tracing.ts +11 -0
- package/src/data/query/utils.ts +357 -0
- package/src/data/query/version.ts +4 -0
- package/src/data/query/view-common.ts +1 -0
- package/src/data/schema/branded.ts +13 -0
- package/src/data/schema/generated/coerce.ts +5 -0
- package/src/data/schema/generated/combinator.ts +6 -0
- package/src/data/schema/generated/encoding.ts +5 -0
- package/src/data/schema/generated/error.ts +6 -0
- package/src/data/schema/generated/format.ts +5 -0
- package/src/data/schema/generated/type-constructor.ts +10 -0
- package/src/data/schema/generated/type.ts +6 -0
- package/src/data/schema/index.ts +6 -0
- package/src/data/schema/json-schema.ts +33 -0
- package/src/data/schema/namespace.ts +55 -0
- package/src/data/schema/parse.ts +39 -0
- package/src/data/schema/types.ts +39 -0
- package/src/data/temporal/append-only.ts +134 -0
- package/src/data/temporal/close.ts +27 -0
- package/src/data/temporal/columns.ts +10 -0
- package/src/data/temporal/definition-queries.ts +70 -0
- package/src/data/temporal/errors.ts +10 -0
- package/src/data/temporal/get-current.ts +22 -0
- package/src/data/temporal/index.ts +9 -0
- package/src/data/temporal/namespace.ts +36 -0
- package/src/data/temporal/query.ts +25 -0
- package/src/data/temporal/resolve.ts +17 -0
- package/src/data/temporal/revise.ts +67 -0
- package/src/data/temporal/types.ts +22 -0
- package/src/data/valve/artifact.ts +33 -0
- package/src/data/valve/contract-diff.ts +21 -0
- package/src/data/valve/cross.ts +57 -0
- package/src/data/valve/crossing-ledger.ts +50 -0
- package/src/data/valve/decision.ts +44 -0
- package/src/data/valve/durable-crossing.ts +52 -0
- package/src/data/valve/execute-crossing.ts +77 -0
- package/src/data/valve/faucet.ts +56 -0
- package/src/data/valve/gate.ts +114 -0
- package/src/data/valve/graph.ts +77 -0
- package/src/data/valve/index.ts +53 -0
- package/src/data/valve/namespace.ts +57 -0
- package/src/data/valve/registry.ts +50 -0
- package/src/data/valve/schema/valve-crossing-fields.ts +23 -0
- package/src/data/valve/schema/valve-crossing.ts +9 -0
- package/src/data/valve/supply.ts +154 -0
- package/src/data/valve/types.ts +143 -0
- package/src/data/vector/columns.ts +27 -0
- package/src/data/vector/index.ts +4 -0
- package/src/data/vector/namespace.ts +33 -0
- package/src/data/vector/query.ts +85 -0
- package/src/data/vector/validate.ts +16 -0
- package/src/data/write/audit/actor.ts +15 -0
- package/src/data/write/audit/audit-columns.ts +18 -0
- package/src/data/write/audit/audit-select.ts +111 -0
- package/src/data/write/audit/bound-transaction.ts +14 -0
- package/src/data/write/audit/cte-compile.ts +32 -0
- package/src/data/write/audit/decode-returning.ts +47 -0
- package/src/data/write/audit/final-select.ts +43 -0
- package/src/data/write/audit/infra-audit.ts +36 -0
- package/src/data/write/audit/primary-key.ts +65 -0
- package/src/data/write/audit/projection.ts +41 -0
- package/src/data/write/audit/read-touches.ts +86 -0
- package/src/data/write/audit/touch-match.ts +78 -0
- package/src/data/write/audit/verb-delete.ts +62 -0
- package/src/data/write/audit/verb-insert.ts +36 -0
- package/src/data/write/audit/verb-select.ts +47 -0
- package/src/data/write/audit/verb-update.ts +60 -0
- package/src/data/write/audit/verb-upsert.ts +104 -0
- package/src/data/write/conflict.ts +43 -0
- package/src/data/write/delete.ts +38 -0
- package/src/data/write/execute.ts +243 -0
- package/src/data/write/index.ts +11 -0
- package/src/data/write/insert.ts +63 -0
- package/src/data/write/state.ts +43 -0
- package/src/data/write/update.ts +54 -0
- package/src/data/write/upsert.ts +58 -0
- package/src/data/write/values.ts +19 -0
- package/src/index.ts +230 -0
- package/src/infra/access-links/code.ts +70 -0
- package/src/infra/access-links/handle.ts +892 -0
- package/src/infra/access-links/index.ts +9 -0
- package/src/infra/access-links/namespace.ts +30 -0
- package/src/infra/access-links/person-check.ts +177 -0
- package/src/infra/access-links/schema.ts +71 -0
- package/src/infra/alerts/async-sources.ts +231 -0
- package/src/infra/alerts/audit.ts +36 -0
- package/src/infra/alerts/beat.ts +123 -0
- package/src/infra/alerts/client-faults.ts +111 -0
- package/src/infra/alerts/faults.ts +156 -0
- package/src/infra/alerts/handle.ts +778 -0
- package/src/infra/alerts/http.ts +192 -0
- package/src/infra/alerts/index.ts +12 -0
- package/src/infra/alerts/infra-sources.ts +180 -0
- package/src/infra/alerts/namespace.ts +81 -0
- package/src/infra/alerts/redact.ts +21 -0
- package/src/infra/alerts/request-faults.ts +41 -0
- package/src/infra/alerts/schema.ts +124 -0
- package/src/infra/alerts/sinks.ts +125 -0
- package/src/infra/alerts/source-options.ts +26 -0
- package/src/infra/alerts/sources.ts +503 -0
- package/src/infra/alerts/subsystem-sources.ts +137 -0
- package/src/infra/audit/emit.ts +87 -0
- package/src/infra/audit/index.ts +2 -0
- package/src/infra/audit/namespace.ts +22 -0
- package/src/infra/audit/schema.sql +35 -0
- package/src/infra/audit/types.ts +18 -0
- package/src/infra/aws/credentials.ts +147 -0
- package/src/infra/aws/sigv4.ts +140 -0
- package/src/infra/cache/create.ts +58 -0
- package/src/infra/cache/entry.ts +81 -0
- package/src/infra/cache/index.ts +4 -0
- package/src/infra/cache/namespace.ts +10 -0
- package/src/infra/cache/tenant.ts +93 -0
- package/src/infra/cache/types.ts +24 -0
- package/src/infra/crawl/audit.ts +35 -0
- package/src/infra/crawl/errors.ts +25 -0
- package/src/infra/crawl/handle.ts +693 -0
- package/src/infra/crawl/index.ts +9 -0
- package/src/infra/crawl/links.ts +160 -0
- package/src/infra/crawl/namespace.ts +36 -0
- package/src/infra/crawl/robots.ts +100 -0
- package/src/infra/crawl/schema.ts +89 -0
- package/src/infra/credit-line/handle.ts +883 -0
- package/src/infra/credit-line/index.ts +6 -0
- package/src/infra/credit-line/namespace.ts +7 -0
- package/src/infra/credit-line/schema.ts +96 -0
- package/src/infra/credits/handle.ts +706 -0
- package/src/infra/credits/index.ts +5 -0
- package/src/infra/credits/namespace.ts +7 -0
- package/src/infra/credits/schema.ts +83 -0
- package/src/infra/email/audit.ts +36 -0
- package/src/infra/email/inbound.ts +622 -0
- package/src/infra/email/index.ts +11 -0
- package/src/infra/email/mail.ts +517 -0
- package/src/infra/email/mime.ts +447 -0
- package/src/infra/email/namespace.ts +22 -0
- package/src/infra/email/providers.ts +111 -0
- package/src/infra/email/schema.ts +96 -0
- package/src/infra/email/sns.ts +200 -0
- package/src/infra/email/unsubscribe.ts +44 -0
- package/src/infra/fetch/address.ts +88 -0
- package/src/infra/fetch/connect-retry.ts +87 -0
- package/src/infra/fetch/handle.ts +329 -0
- package/src/infra/fetch/index.ts +7 -0
- package/src/infra/fetch/namespace.ts +70 -0
- package/src/infra/idempotency/index.ts +3 -0
- package/src/infra/idempotency/middleware.ts +87 -0
- package/src/infra/idempotency/store.ts +92 -0
- package/src/infra/idempotency/types.ts +30 -0
- package/src/infra/join-requests/handle.ts +312 -0
- package/src/infra/join-requests/index.ts +2 -0
- package/src/infra/join-requests/namespace.ts +7 -0
- package/src/infra/join-requests/schema.ts +33 -0
- package/src/infra/outbox/core.ts +165 -0
- package/src/infra/quota/errors.ts +32 -0
- package/src/infra/quota/handle.ts +215 -0
- package/src/infra/quota/index.ts +5 -0
- package/src/infra/quota/namespace.ts +7 -0
- package/src/infra/quota/schema.ts +15 -0
- package/src/infra/storage/backend.ts +34 -0
- package/src/infra/storage/handle.ts +513 -0
- package/src/infra/storage/index.ts +9 -0
- package/src/infra/storage/local.ts +104 -0
- package/src/infra/storage/namespace.ts +13 -0
- package/src/infra/storage/s3.ts +257 -0
- package/src/infra/storage/schema.ts +24 -0
- package/src/infra/support/handle.ts +854 -0
- package/src/infra/support/http.ts +229 -0
- package/src/infra/support/index.ts +7 -0
- package/src/infra/support/namespace.ts +12 -0
- package/src/infra/support/schema.ts +96 -0
- package/src/infra/webhook/inbound.ts +45 -0
- package/src/infra/webhook/index.ts +5 -0
- package/src/infra/webhook/namespace.ts +24 -0
- package/src/infra/webhook/outbound.ts +315 -0
- package/src/infra/webhook/sign.ts +32 -0
- package/src/infra/webhook/verify.ts +82 -0
- package/src/invariants/assert.ts +26 -0
- package/src/invariants/index.ts +1 -0
- package/src/lint/config.ts +56 -0
- package/src/lint/doctrine.ts +163 -0
- package/src/lint/include.ts +97 -0
- package/src/lint/index.ts +3 -0
- package/src/lint/preset.ts +10 -0
- package/src/lint/resolver.ts +218 -0
- package/src/lint/rules/no-reexports.ts +46 -0
- package/src/request/behavior/audited.ts +85 -0
- package/src/request/behavior/cached.ts +62 -0
- package/src/request/behavior/compose.ts +28 -0
- package/src/request/behavior/index.ts +5 -0
- package/src/request/behavior/namespace.ts +16 -0
- package/src/request/behavior/types.ts +26 -0
- package/src/request/behavior/versioned.ts +54 -0
- package/src/request/context/create.ts +103 -0
- package/src/request/context/guard.ts +49 -0
- package/src/request/context/index.ts +4 -0
- package/src/request/context/run-in-transaction.ts +200 -0
- package/src/request/context/store.ts +28 -0
- package/src/request/context/types.ts +50 -0
- package/src/request/di/builder.ts +535 -0
- package/src/request/di/index.ts +8 -0
- package/src/request/di/resolve.ts +271 -0
- package/src/request/di/types.ts +262 -0
- package/src/request/fitting/namespace.ts +7 -0
- package/src/request/fixture/batch.ts +71 -0
- package/src/request/fixture/builder.ts +26 -0
- package/src/request/fixture/errors.ts +10 -0
- package/src/request/fixture/index.ts +8 -0
- package/src/request/fixture/namespace.ts +23 -0
- package/src/request/fixture/paginate.ts +44 -0
- package/src/request/fixture/to-handlers.ts +260 -0
- package/src/request/fixture/types.ts +79 -0
- package/src/request/flow/act-as.ts +78 -0
- package/src/request/flow/namespace.ts +17 -0
- package/src/request/http/error-step.ts +42 -0
- package/src/request/http/file.ts +141 -0
- package/src/request/http/index.ts +4 -0
- package/src/request/http/middleware.ts +141 -0
- package/src/request/http/namespace.ts +5 -0
- package/src/request/http/rate-limit-classes.ts +170 -0
- package/src/request/http/response.ts +82 -0
- package/src/request/http/server.ts +916 -0
- package/src/request/http/sniff.ts +261 -0
- package/src/request/http/types.ts +250 -0
- package/src/request/http/upload.ts +284 -0
- package/src/request/internal/text.ts +6 -0
- package/src/request/openapi/generate.ts +205 -0
- package/src/request/openapi/index.ts +2 -0
- package/src/request/openapi/namespace.ts +7 -0
- package/src/test/auth.ts +13 -0
- package/src/test/auto-setup.ts +45 -0
- package/src/test/context.ts +91 -0
- package/src/test/crawl-fixture.ts +157 -0
- package/src/test/discover-fork.ts +17 -0
- package/src/test/fingerprint.ts +135 -0
- package/src/test/globals.d.ts +7 -0
- package/src/test/index.ts +19 -0
- package/src/test/namespace.ts +87 -0
- package/src/test/plugin.ts +189 -0
- package/src/test/progress.ts +185 -0
- package/src/test/reap-after-run.ts +28 -0
- package/src/test/reap.ts +286 -0
- package/src/test/reset.ts +253 -0
- package/src/test/savepoints.ts +172 -0
- package/src/test/setup.ts +1422 -0
- package/src/test/system-channel.ts +50 -0
- package/src/test/vitest-plugin.ts +142 -0
- package/src/test/vitest-workspace.ts +507 -0
- package/src/test/vitest.ts +333 -0
- package/src/test/watch.ts +105 -0
- package/src/trace/capture/bounded-serializer.ts +146 -0
- package/src/trace/cardinality/analyze.ts +173 -0
- package/src/trace/cardinality/branch-context.ts +214 -0
- package/src/trace/cardinality/check.ts +33 -0
- package/src/trace/cardinality/classify-edge.ts +64 -0
- package/src/trace/cardinality/structural-max.ts +107 -0
- package/src/trace/check/coverage-check.ts +188 -0
- package/src/trace/check/flow-check.ts +103 -0
- package/src/trace/check/flow-step-site.ts +234 -0
- package/src/trace/check/io-pattern.ts +116 -0
- package/src/trace/codemod/add-cardinality.ts +152 -0
- package/src/trace/codemod/run.ts +80 -0
- package/src/trace/entry/child-trace.ts +21 -0
- package/src/trace/entry/close-trace.ts +87 -0
- package/src/trace/entry/finalize-trace.ts +28 -0
- package/src/trace/entry/open-trace.ts +138 -0
- package/src/trace/entry/parent-trace.ts +20 -0
- package/src/trace/entry/parse-traceparent.ts +39 -0
- package/src/trace/entry/process-start.ts +38 -0
- package/src/trace/entry/trace-context.ts +26 -0
- package/src/trace/entry/tracing-enabled.ts +10 -0
- package/src/trace/external/record.ts +195 -0
- package/src/trace/external/report.ts +66 -0
- package/src/trace/namespace.ts +19 -0
- package/src/trace/partition/day.ts +47 -0
- package/src/trace/partition/index.ts +14 -0
- package/src/trace/partition/partition-name.ts +23 -0
- package/src/trace/partition/plan.ts +82 -0
- package/src/trace/partition/schedule.ts +26 -0
- package/src/trace/partition/startup-hook.ts +39 -0
- package/src/trace/partition/sync.ts +154 -0
- package/src/trace/partition/tables.ts +53 -0
- package/src/trace/read/tree.ts +83 -0
- package/src/trace/retain/copy-on-error.ts +76 -0
- package/src/trace/runtime/expire-stale-traces.ts +54 -0
- package/src/trace/runtime/flow-step.ts +147 -0
- package/src/trace/runtime/flush-steps.ts +31 -0
- package/src/trace/runtime/pending-writes.ts +54 -0
- package/src/trace/runtime/step-buffer.ts +298 -0
- package/src/trace/runtime/step-error.ts +21 -0
- package/src/trace/runtime/step-meta.ts +55 -0
- package/src/trace/runtime/thenable.ts +12 -0
- package/src/trace/runtime/truncate-trace.ts +62 -0
- package/src/trace/schema/trace-fields.ts +52 -0
- package/src/trace/schema/trace-retained.ts +22 -0
- package/src/trace/schema/trace-step-fields.ts +44 -0
- package/src/trace/schema/trace-step-retained.ts +25 -0
- package/src/trace/schema/trace-step.ts +18 -0
- package/src/trace/schema/trace.ts +12 -0
- package/src/trace/transformer/apply.ts +79 -0
- package/src/trace/transformer/build-meta-literal.ts +30 -0
- package/src/trace/transformer/find-flow-step-sites.ts +85 -0
- package/src/trace/transformer/inject-flow-step-import.ts +39 -0
- package/src/trace/transformer/transform-error.ts +14 -0
- package/src/trace/transformer/transform-flow-steps.ts +123 -0
- package/src/trace/transformer/wrap-function-declaration.ts +62 -0
- package/src/trace/transformer/wrap-variable-declaration.ts +67 -0
- package/src/workspace/affected.ts +110 -0
- package/src/workspace/changed-files.ts +163 -0
- package/src/workspace/check-deps.ts +213 -0
- package/src/workspace/checksum.ts +20 -0
- package/src/workspace/dependency-graph.ts +67 -0
- package/src/workspace/detect-pipework-copies.ts +93 -0
- package/src/workspace/discover.ts +206 -0
- package/src/workspace/generate.ts +401 -0
- package/src/workspace/index.ts +7 -0
- package/src/workspace/module-imports-pipework.ts +39 -0
- package/src/workspace/module-package-name.ts +12 -0
- package/src/workspace/scrub-git-env.ts +23 -0
- package/src/workspace/source-paths.ts +36 -0
- package/src/workspace/sync.ts +182 -0
- package/dist/REFERENCE.md +0 -1477
- package/dist/infra/access-links/rate-limit.d.ts +0 -10
- package/dist/infra/access-links/rate-limit.d.ts.map +0 -1
- package/dist/infra/access-links/rate-limit.js +0 -28
- package/dist/infra/access-links/rate-limit.js.map +0 -1
- package/dist/infra/email/sigv4.d.ts +0 -24
- package/dist/infra/email/sigv4.d.ts.map +0 -1
- package/dist/infra/email/sigv4.js +0 -55
- package/dist/infra/email/sigv4.js.map +0 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Chooses which of a step's entity-matched touches a write is recorded under.
|
|
2
|
+
//
|
|
3
|
+
// A row's verb is `create` (insert, or an upsert row `before` did not match),
|
|
4
|
+
// `update`, or `delete`. Among the matched touches:
|
|
5
|
+
// 1. those whose verb is exactly the row's verb, if any;
|
|
6
|
+
// 2. else those whose verb is not create/update/delete/read (custom verbs);
|
|
7
|
+
// 3. else all matched touches but `read` ones, or all when only `read` ones match.
|
|
8
|
+
// A write marks every matched touch written, so none of them also gets a read row.
|
|
9
|
+
|
|
10
|
+
import type { MetaTouch } from '../../../trace/runtime/step-meta.js'
|
|
11
|
+
|
|
12
|
+
export type RowVerb = 'create' | 'update' | 'delete'
|
|
13
|
+
|
|
14
|
+
const NOT_CUSTOM: ReadonlySet<string> = new Set(['create', 'update', 'delete', 'read'])
|
|
15
|
+
|
|
16
|
+
export function selectTouches(matched: readonly MetaTouch[], verb: RowVerb): MetaTouch[] {
|
|
17
|
+
const exact = matched.filter(t => t.verb === verb)
|
|
18
|
+
if (exact.length > 0) return exact
|
|
19
|
+
const custom = matched.filter(t => !NOT_CUSTOM.has(t.verb))
|
|
20
|
+
if (custom.length > 0) return custom
|
|
21
|
+
const writes = matched.filter(t => t.verb !== 'read')
|
|
22
|
+
return writes.length > 0 ? writes : [...matched]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface WriteSelection {
|
|
26
|
+
/** Touches that get audit rows, in declaration order. */
|
|
27
|
+
readonly touches: MetaTouch[]
|
|
28
|
+
/** Upsert only: the touches that take rows `before` did not match / did match. */
|
|
29
|
+
readonly split?: { readonly create: readonly MetaTouch[]; readonly update: readonly MetaTouch[] } | undefined
|
|
30
|
+
/** Touches to mark as written: every matched touch. */
|
|
31
|
+
readonly written: MetaTouch[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function selectForWrite(matched: readonly MetaTouch[], writeVerb: 'insert' | 'update' | 'delete' | 'upsert'): WriteSelection {
|
|
35
|
+
let touches: MetaTouch[]
|
|
36
|
+
let split: WriteSelection['split']
|
|
37
|
+
if (writeVerb === 'upsert') {
|
|
38
|
+
const create = selectTouches(matched, 'create')
|
|
39
|
+
const update = selectTouches(matched, 'update')
|
|
40
|
+
touches = matched.filter(t => create.includes(t) || update.includes(t))
|
|
41
|
+
split = { create, update }
|
|
42
|
+
} else {
|
|
43
|
+
touches = selectTouches(matched, writeVerb === 'insert' ? 'create' : writeVerb)
|
|
44
|
+
}
|
|
45
|
+
const written = [...matched]
|
|
46
|
+
return { touches, split, written }
|
|
47
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// UPDATE under audit: snapshots the matched rows into `before`, runs the
|
|
2
|
+
// UPDATE into `updated`, then emits one audit row per affected db row per
|
|
3
|
+
// `@touches` target, joining `before` and `updated` on the primary key so
|
|
4
|
+
// the pre/post pair lines up. The single statement's snapshot keeps `before`
|
|
5
|
+
// and `updated` consistent — no lock needed; in fact, `FOR UPDATE` inside a
|
|
6
|
+
// CTE conflicts with the sibling UPDATE on the same row and silently yields
|
|
7
|
+
// zero rows from `before`, suppressing the audit insert.
|
|
8
|
+
|
|
9
|
+
import { sql } from '../../db/sql.js'
|
|
10
|
+
import { quoteIdentifier } from '../../db/identifiers.js'
|
|
11
|
+
import { camelToSnake } from '../../domain/naming.js'
|
|
12
|
+
import { buildTraceSafeProjection } from './projection.js'
|
|
13
|
+
import { buildAuditInserts, buildEntityIdExpr, type AuditSelectContext } from './audit-select.js'
|
|
14
|
+
import { primaryKeyFieldKeys } from './primary-key.js'
|
|
15
|
+
import { AUDIT_COLUMNS_SQL } from './audit-columns.js'
|
|
16
|
+
import { buildFinalSelect } from './final-select.js'
|
|
17
|
+
import type { SQL } from '../../db/types.js'
|
|
18
|
+
import type { WriteState } from '../state.js'
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- drizzle's DB type is deeply generic; the executor passes the resolved per-database handle through unchanged
|
|
21
|
+
type AnyDb = any
|
|
22
|
+
|
|
23
|
+
export function compileUpdateAudit(db: AnyDb, state: WriteState, ctx: AuditSelectContext): SQL {
|
|
24
|
+
const pk = primaryKeyFieldKeys(state.table)
|
|
25
|
+
const set = state.set ?? {}
|
|
26
|
+
// `.getSQL()` strips the auto-paren drizzle adds around SQLWrapper builders
|
|
27
|
+
// when they are templated — we add explicit parens at the CTE boundary so
|
|
28
|
+
// the WITH clause keeps a uniform shape across verbs.
|
|
29
|
+
let updateChain = db.update(state.table).set(set)
|
|
30
|
+
if (state.where !== undefined) updateChain = updateChain.where(state.where)
|
|
31
|
+
const updateInner = updateChain.returning().getSQL()
|
|
32
|
+
|
|
33
|
+
let beforeChain = db.select().from(state.table)
|
|
34
|
+
if (state.where !== undefined) beforeChain = beforeChain.where(state.where)
|
|
35
|
+
const beforeInner = beforeChain.getSQL()
|
|
36
|
+
|
|
37
|
+
const joinOn = pk
|
|
38
|
+
.map(k => {
|
|
39
|
+
const col = quoteIdentifier(camelToSnake(k), 'pk column')
|
|
40
|
+
return `before.${col} = updated.${col}`
|
|
41
|
+
})
|
|
42
|
+
.join(' AND ')
|
|
43
|
+
|
|
44
|
+
const before = buildTraceSafeProjection(state.table, 'before') ?? sql`NULL::jsonb`
|
|
45
|
+
const after = buildTraceSafeProjection(state.table, 'updated') ?? sql`NULL::jsonb`
|
|
46
|
+
const fromClause = sql.raw(`FROM updated JOIN before ON ${joinOn}`)
|
|
47
|
+
const auditSelect = buildAuditInserts(ctx, fromClause, buildEntityIdExpr(state.table, 'updated'), before, after)
|
|
48
|
+
const finalSelect = buildFinalSelect(state, 'updated')
|
|
49
|
+
|
|
50
|
+
return sql`
|
|
51
|
+
WITH before AS (${beforeInner}),
|
|
52
|
+
updated AS (${updateInner}),
|
|
53
|
+
audit_inserts AS (
|
|
54
|
+
INSERT INTO "audit_record" (${sql.raw(AUDIT_COLUMNS_SQL)})
|
|
55
|
+
${auditSelect}
|
|
56
|
+
RETURNING 1
|
|
57
|
+
)
|
|
58
|
+
${finalSelect}
|
|
59
|
+
`
|
|
60
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// UPSERT under audit: a single INSERT ... ON CONFLICT DO UPDATE / DO NOTHING
|
|
2
|
+
// that either inserts a new row or updates the conflicting one. Audit rows
|
|
3
|
+
// distinguish create vs update by left-joining the upserted row with the
|
|
4
|
+
// `before` snapshot (pre-write rows sharing the upserted rows' conflict-target
|
|
5
|
+
// values, whoever supplied them): a NULL `before` row
|
|
6
|
+
// means the upserted row was freshly inserted; non-NULL means it updated
|
|
7
|
+
// an existing row. The audit's `before` jsonb is wrapped in a CASE that
|
|
8
|
+
// returns NULL::jsonb for the insert branch.
|
|
9
|
+
|
|
10
|
+
import { sql } from '../../db/sql.js'
|
|
11
|
+
import { quoteIdentifier } from '../../db/identifiers.js'
|
|
12
|
+
import { camelToSnake } from '../../domain/naming.js'
|
|
13
|
+
import { buildTraceSafeProjection } from './projection.js'
|
|
14
|
+
import { buildAuditInserts, buildEntityIdExpr, type AuditSelectContext } from './audit-select.js'
|
|
15
|
+
import { AUDIT_COLUMNS_SQL } from './audit-columns.js'
|
|
16
|
+
import { buildFinalSelect } from './final-select.js'
|
|
17
|
+
import type { SQL } from '../../db/types.js'
|
|
18
|
+
import type { WriteState } from '../state.js'
|
|
19
|
+
import type { MetaTouch } from '../../../trace/runtime/step-meta.js'
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- drizzle's DB type is deeply generic; the executor passes the resolved per-database handle through unchanged
|
|
22
|
+
type AnyDb = any
|
|
23
|
+
|
|
24
|
+
export function compileUpsertAudit(db: AnyDb, state: WriteState, ctx: AuditSelectContext): SQL {
|
|
25
|
+
if (state.onConflictColumns === undefined || state.onConflictColumns.length === 0) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
'pipe.upsert requires .onConflict([...columns]). ' +
|
|
28
|
+
'Pass the conflict-target columns that determine "this row already exists" — ' +
|
|
29
|
+
'usually the primary key or a unique index.',
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const conflictCols = state.onConflictColumns
|
|
34
|
+
const targetColumns = conflictCols.map(key => {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DefinedTable columns are indexed by field key; the lookup was validated at the builder surface
|
|
36
|
+
const col = (state.table as any)[key]
|
|
37
|
+
if (col === undefined) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`pipe.upsert: onConflict column "${key}" is not a field of "${state.table.$name}". ` +
|
|
40
|
+
`Use a field key declared on the table.`,
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
return col as unknown
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
let upsertChain = db.insert(state.table).values(state.values ?? [])
|
|
47
|
+
if (state.set === undefined) {
|
|
48
|
+
upsertChain = upsertChain.onConflictDoNothing({ target: targetColumns })
|
|
49
|
+
} else {
|
|
50
|
+
upsertChain = upsertChain.onConflictDoUpdate({ target: targetColumns, set: state.set })
|
|
51
|
+
}
|
|
52
|
+
// `.getSQL()` strips the auto-paren drizzle adds around SQLWrapper builders
|
|
53
|
+
// when they are templated — we add explicit parens at the CTE boundary so
|
|
54
|
+
// the WITH clause keeps a uniform shape across verbs.
|
|
55
|
+
const upsertInner = upsertChain.returning().getSQL()
|
|
56
|
+
|
|
57
|
+
const tableName = quoteIdentifier(state.table.$name, 'target table')
|
|
58
|
+
const conflictColsSql = conflictCols.map(k => quoteIdentifier(camelToSnake(k), 'conflict column')).join(', ')
|
|
59
|
+
// `before` is keyed on the rows `upserted` wrote, not the caller's values, so
|
|
60
|
+
// conflict columns pipework fills in (tenant) or defaults match too. All CTEs
|
|
61
|
+
// see the pre-statement snapshot: `before` reads the replaced row. No `FOR
|
|
62
|
+
// UPDATE`: locking inside a CTE conflicts with the sibling write on the row.
|
|
63
|
+
const beforeSelect = sql`
|
|
64
|
+
SELECT * FROM ${sql.raw(tableName)}
|
|
65
|
+
WHERE (${sql.raw(conflictColsSql)}) IN (SELECT ${sql.raw(conflictColsSql)} FROM upserted)
|
|
66
|
+
`
|
|
67
|
+
|
|
68
|
+
const joinOn = conflictCols
|
|
69
|
+
.map(k => {
|
|
70
|
+
const col = quoteIdentifier(camelToSnake(k), 'conflict column')
|
|
71
|
+
return `before.${col} = upserted.${col}`
|
|
72
|
+
})
|
|
73
|
+
.join(' AND ')
|
|
74
|
+
|
|
75
|
+
// Pivot: the conflict-key column of `before` is non-null iff the upserted
|
|
76
|
+
// row matched an existing row. Use the first conflict col as the discriminator.
|
|
77
|
+
const pivotCol = quoteIdentifier(camelToSnake(conflictCols[0]!), 'conflict column')
|
|
78
|
+
const beforeProj = buildTraceSafeProjection(state.table, 'before') ?? sql`NULL::jsonb`
|
|
79
|
+
const after = buildTraceSafeProjection(state.table, 'upserted') ?? sql`NULL::jsonb`
|
|
80
|
+
const beforeExpr = sql`CASE WHEN ${sql.raw(`before.${pivotCol}`)} IS NULL THEN NULL::jsonb ELSE (${beforeProj}) END`
|
|
81
|
+
const fromClause = sql.raw(`FROM upserted LEFT JOIN before ON ${joinOn}`)
|
|
82
|
+
// A touch in only the create set takes rows `before` did not match; only the
|
|
83
|
+
// update set, rows it did; in both (or no split), every row.
|
|
84
|
+
const split = ctx.split
|
|
85
|
+
const rowFilter = split === undefined ? undefined : (t: MetaTouch): SQL | null => {
|
|
86
|
+
const c = split.create.includes(t)
|
|
87
|
+
const u = split.update.includes(t)
|
|
88
|
+
if (c === u) return null
|
|
89
|
+
return sql.raw(`before.${pivotCol} IS ${c ? '' : 'NOT '}NULL`)
|
|
90
|
+
}
|
|
91
|
+
const auditSelect = buildAuditInserts(ctx, fromClause, buildEntityIdExpr(state.table, 'upserted'), beforeExpr, after, rowFilter)
|
|
92
|
+
const finalSelect = buildFinalSelect(state, 'upserted')
|
|
93
|
+
|
|
94
|
+
return sql`
|
|
95
|
+
WITH upserted AS (${upsertInner}),
|
|
96
|
+
before AS (${beforeSelect}),
|
|
97
|
+
audit_inserts AS (
|
|
98
|
+
INSERT INTO "audit_record" (${sql.raw(AUDIT_COLUMNS_SQL)})
|
|
99
|
+
${auditSelect}
|
|
100
|
+
RETURNING 1
|
|
101
|
+
)
|
|
102
|
+
${finalSelect}
|
|
103
|
+
`
|
|
104
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// `ON CONFLICT DO NOTHING` on `pipe.insert`: the target lookup shared by the
|
|
2
|
+
// vanilla and audited paths, and the trace event naming the rows left alone.
|
|
3
|
+
|
|
4
|
+
import { event } from '../../trace/external/record.js'
|
|
5
|
+
import type { WriteState } from './state.js'
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- drizzle's insert builder is deeply generic; the clause is applied to whatever chain the caller holds
|
|
8
|
+
type AnyChain = any
|
|
9
|
+
|
|
10
|
+
/** Applies the state's `ON CONFLICT DO NOTHING` to an insert chain; the chain is returned unchanged when the state has none. */
|
|
11
|
+
export function applyOnConflict(q: AnyChain, state: WriteState): AnyChain {
|
|
12
|
+
const onConflict = state.onConflict
|
|
13
|
+
if (onConflict === undefined) return q
|
|
14
|
+
if (onConflict.target === undefined) return q.onConflictDoNothing()
|
|
15
|
+
const target = onConflict.target.map(key => {
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DefinedTable columns are indexed by field key; the lookup is validated here
|
|
17
|
+
const col = (state.table as any)[key]
|
|
18
|
+
if (col === undefined) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`pipe.insert: onConflictDoNothing target "${key}" is not a field of "${state.table.$name}". ` +
|
|
21
|
+
`Use a field key declared on the table.`,
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
return col as unknown
|
|
25
|
+
})
|
|
26
|
+
return q.onConflictDoNothing({ target })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Records the rows an `onConflictDoNothing` insert skipped; nothing when none were. */
|
|
30
|
+
export function reportSkipped(state: WriteState, written: number): void {
|
|
31
|
+
if (state.onConflict === undefined) return
|
|
32
|
+
const attempted = state.values?.length ?? 0
|
|
33
|
+
if (written >= attempted) return
|
|
34
|
+
event('pipework.insert.conflict', {
|
|
35
|
+
capture: {
|
|
36
|
+
table: state.table.$name,
|
|
37
|
+
target: state.onConflict.target ?? null,
|
|
38
|
+
attempted,
|
|
39
|
+
written,
|
|
40
|
+
skipped: attempted - written,
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// `pipe.delete(table)` — the pipework-owned delete builder. Same intent-based
|
|
2
|
+
// design as UpdateBuilder; see update.ts for the rationale.
|
|
3
|
+
|
|
4
|
+
import type { PgTable } from '../query/pg-core/index.js'
|
|
5
|
+
import type { SQL } from '../db/types.js'
|
|
6
|
+
import { assertNotExternal } from '../domain/external-registry.js'
|
|
7
|
+
import type { DefinedTable } from '../domain/types.js'
|
|
8
|
+
import type { WriteState } from './state.js'
|
|
9
|
+
import { executeWrite } from './execute.js'
|
|
10
|
+
|
|
11
|
+
export class DeleteBuilder<T extends PgTable, TResult = unknown> implements PromiseLike<TResult> {
|
|
12
|
+
constructor(private readonly state: WriteState) {}
|
|
13
|
+
|
|
14
|
+
where(condition: SQL): DeleteBuilder<T, TResult> {
|
|
15
|
+
return new DeleteBuilder<T, TResult>({ ...this.state, where: condition })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
returning(): DeleteBuilder<T, Record<string, unknown>[]>
|
|
19
|
+
returning<TShape extends Record<string, unknown>>(shape: TShape): DeleteBuilder<T, Record<string, unknown>[]>
|
|
20
|
+
returning(shape?: Record<string, unknown>): DeleteBuilder<T, Record<string, unknown>[]> {
|
|
21
|
+
const returning = shape === undefined ? 'all' as const : shape
|
|
22
|
+
return new DeleteBuilder<T, Record<string, unknown>[]>({ ...this.state, returning })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
then<TFulfilled = TResult, TRejected = never>(
|
|
26
|
+
onfulfilled?: ((value: TResult) => TFulfilled | PromiseLike<TFulfilled>) | null,
|
|
27
|
+
onrejected?: ((reason: unknown) => TRejected | PromiseLike<TRejected>) | null,
|
|
28
|
+
): PromiseLike<TFulfilled | TRejected> {
|
|
29
|
+
return executeWrite(this.state).then(onfulfilled as (value: unknown) => TFulfilled, onrejected) as PromiseLike<TFulfilled | TRejected>
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Starts a pipework-owned delete against `table`. Chain with `.where(...).returning(...)`. */
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
export function remove<T extends DefinedTable<any> & PgTable>(table: T): DeleteBuilder<T, any> {
|
|
36
|
+
assertNotExternal(table, 'delete')
|
|
37
|
+
return new DeleteBuilder<T, unknown>({ verb: 'delete', table })
|
|
38
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// Execution path for the write surface. Two branches: vanilla DML via drizzle
|
|
2
|
+
// when there is no active audit-emitting step, and a single statement with
|
|
3
|
+
// `before` / `updated|inserted|deleted|upserted` / `audit_inserts` CTEs
|
|
4
|
+
// when the current `flow_step` carries a `@touches` target for the written
|
|
5
|
+
// table. The two branches produce the same observable return (the CTE rows are
|
|
6
|
+
// decoded through the table's columns) — the difference is whether an audit_record
|
|
7
|
+
// row lands atomically with the write.
|
|
8
|
+
//
|
|
9
|
+
// Bulk-write chunking (Q8 / Phase 6c): when an insert/upsert under audit
|
|
10
|
+
// would emit more than `trace.auditChunkSize` audit rows
|
|
11
|
+
// (`values.length × touches.length`), the executor splits `state.values`
|
|
12
|
+
// into chunks and runs each one as its own write+audit CTE, wrapped in a
|
|
13
|
+
// transaction so the chunks commit or abort together. Update/delete are not
|
|
14
|
+
// chunked — affected row counts are dynamic at SQL-generation time, so
|
|
15
|
+
// chunking would need a separate strategy.
|
|
16
|
+
|
|
17
|
+
import type { WriteState } from './state.js'
|
|
18
|
+
import type { Flow } from '../../request/context/types.js'
|
|
19
|
+
import { pipe, pipeRaw } from '../db/db.js'
|
|
20
|
+
import { getContext } from '../../request/context/store.js'
|
|
21
|
+
import { compileAuditWrite } from './audit/cte-compile.js'
|
|
22
|
+
import { touchesForTable } from './audit/touch-match.js'
|
|
23
|
+
import { selectForWrite } from './audit/verb-select.js'
|
|
24
|
+
import { decodeReturning } from './audit/decode-returning.js'
|
|
25
|
+
import { resolveActorId } from './audit/actor.js'
|
|
26
|
+
import type { AuditSelectContext } from './audit/audit-select.js'
|
|
27
|
+
import { rowsPerStatement } from '../db/insert-split.js'
|
|
28
|
+
import { applyOnConflict, reportSkipped } from './conflict.js'
|
|
29
|
+
|
|
30
|
+
export async function executeWrite(state: WriteState): Promise<unknown> {
|
|
31
|
+
const db = pipe(state.table.$options.database)
|
|
32
|
+
const flow = getContext()
|
|
33
|
+
if (flow !== undefined && flow.trace !== null) {
|
|
34
|
+
const stepCtx = flow.trace.buffer.currentStep()
|
|
35
|
+
// Only the touches whose entity is this table audit the write; with none,
|
|
36
|
+
// the write takes the vanilla branch below.
|
|
37
|
+
const matched = stepCtx === null ? [] : touchesForTable(state.table, stepCtx.touches)
|
|
38
|
+
if (stepCtx !== null && matched.length > 0) {
|
|
39
|
+
// Each row is recorded under the touches whose verb matches it (verb-select.ts).
|
|
40
|
+
const selection = selectForWrite(matched, state.verb)
|
|
41
|
+
const auditCtx: AuditSelectContext = {
|
|
42
|
+
step: stepCtx,
|
|
43
|
+
touches: selection.touches,
|
|
44
|
+
split: selection.split,
|
|
45
|
+
written: selection.written,
|
|
46
|
+
actorId: resolveActorId(flow),
|
|
47
|
+
tenantId: flow.tenant,
|
|
48
|
+
}
|
|
49
|
+
// The union bounds the audit rows one value can produce.
|
|
50
|
+
const chunkValueSize = computeChunkValueSize(flow, state, selection.touches.length, rowsPerStatement(state.table))
|
|
51
|
+
if (chunkValueSize !== null) {
|
|
52
|
+
return executeChunkedAuditWrite(state, auditCtx, chunkValueSize)
|
|
53
|
+
}
|
|
54
|
+
const stmt = compileAuditWrite(db, state, auditCtx)
|
|
55
|
+
// The compiled CTE is already tenant-scoped at compile time (the user
|
|
56
|
+
// write inside the CTE was built against the scoped `pipe()` DB, so it
|
|
57
|
+
// carries the tenant filter). Execute it against the raw handle — the
|
|
58
|
+
// scoped proxy refuses `.execute()` because CTEs would otherwise bypass
|
|
59
|
+
// its per-table filtering.
|
|
60
|
+
const rawDb = pipeRaw(state.table.$options.database)
|
|
61
|
+
const rows = await rawDb.execute(stmt)
|
|
62
|
+
for (const touch of selection.written) stepCtx.markWrote(touch)
|
|
63
|
+
if (state.onConflict !== undefined) reportSkipped(state, Array.isArray(rows) ? rows.length : 0)
|
|
64
|
+
return decodeReturning(state, rows)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if ((state.verb === 'insert' || state.verb === 'upsert') && state.values !== undefined) {
|
|
68
|
+
const size = rowsPerStatement(state.table)
|
|
69
|
+
if (state.values.length > size) return executeChunkedWrite(state, size)
|
|
70
|
+
}
|
|
71
|
+
switch (state.verb) {
|
|
72
|
+
case 'update': return executeUpdate(db, state)
|
|
73
|
+
case 'insert': return executeInsert(db, state)
|
|
74
|
+
case 'delete': return executeDelete(db, state)
|
|
75
|
+
case 'upsert': return executeUpsert(db, state)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns the per-chunk `values.length` to slice at, or `null` when the write
|
|
81
|
+
* fits in a single statement. Only insert/upsert are chunkable: update/delete
|
|
82
|
+
* affected-row counts depend on the `WHERE` clause, unknown at compile time.
|
|
83
|
+
*/
|
|
84
|
+
function computeChunkValueSize(
|
|
85
|
+
flow: Flow,
|
|
86
|
+
state: WriteState,
|
|
87
|
+
touches: number,
|
|
88
|
+
parameterSize: number,
|
|
89
|
+
): number | null {
|
|
90
|
+
if (state.verb !== 'insert' && state.verb !== 'upsert') return null
|
|
91
|
+
if (state.values === undefined) return null
|
|
92
|
+
if (touches === 0) return null
|
|
93
|
+
|
|
94
|
+
const limit = flow.instance.config.trace.auditChunkSize
|
|
95
|
+
const projectedAuditRows = state.values.length * touches
|
|
96
|
+
// Floor by touches so the per-chunk audit row count never exceeds `limit`;
|
|
97
|
+
// at least one row per chunk so single-touch writes still make progress.
|
|
98
|
+
const auditSize = Math.max(1, Math.floor(limit / touches))
|
|
99
|
+
const size = Math.min(auditSize, parameterSize)
|
|
100
|
+
if (projectedAuditRows <= limit && state.values.length <= size) return null
|
|
101
|
+
return size
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Vanilla insert/upsert past the parameter limit: one statement per slice of
|
|
106
|
+
* rows, in one transaction (a savepoint when nested), returned rows in input order.
|
|
107
|
+
*/
|
|
108
|
+
async function executeChunkedWrite(state: WriteState, size: number): Promise<unknown> {
|
|
109
|
+
const allValues = state.values ?? []
|
|
110
|
+
const collectsRows = state.returning !== undefined && state.returning !== null
|
|
111
|
+
const collected: unknown[] = []
|
|
112
|
+
let written = 0
|
|
113
|
+
const dbName = state.table.$options.database
|
|
114
|
+
const runChunks = async (): Promise<void> => {
|
|
115
|
+
for (let i = 0; i < allValues.length; i += size) {
|
|
116
|
+
const chunkState: WriteState = { ...state, values: allValues.slice(i, i + size) }
|
|
117
|
+
const db = pipe(dbName)
|
|
118
|
+
if (state.verb === 'insert') {
|
|
119
|
+
const out = await runInsert(db, chunkState)
|
|
120
|
+
written += out.written
|
|
121
|
+
if (collectsRows && Array.isArray(out.result)) for (const row of out.result) collected.push(row)
|
|
122
|
+
} else {
|
|
123
|
+
const rows = await executeUpsert(db, chunkState)
|
|
124
|
+
if (collectsRows && Array.isArray(rows)) for (const row of rows) collected.push(row)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (dbName === undefined) await pipe.transaction(runChunks)
|
|
129
|
+
else await pipe.transaction(dbName, runChunks)
|
|
130
|
+
if (state.verb === 'insert') reportSkipped(state, written)
|
|
131
|
+
return collectsRows ? collected : undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function executeChunkedAuditWrite(
|
|
135
|
+
state: WriteState,
|
|
136
|
+
ctx: AuditSelectContext,
|
|
137
|
+
chunkValueSize: number,
|
|
138
|
+
): Promise<unknown> {
|
|
139
|
+
const allValues = state.values ?? []
|
|
140
|
+
const collectsRows = state.returning !== undefined && state.returning !== null
|
|
141
|
+
const collected: unknown[] = []
|
|
142
|
+
let written = 0
|
|
143
|
+
const dbName = state.table.$options.database
|
|
144
|
+
|
|
145
|
+
// `pipe.transaction` opens a transaction when none is active and a
|
|
146
|
+
// savepoint when nested — so chunked execution is atomic in both call sites
|
|
147
|
+
// (HTTP fittings with transactionPerRequest enabled, and ad-hoc callers).
|
|
148
|
+
const runChunks = async (): Promise<void> => {
|
|
149
|
+
for (let i = 0; i < allValues.length; i += chunkValueSize) {
|
|
150
|
+
const chunk = allValues.slice(i, i + chunkValueSize)
|
|
151
|
+
const chunkState: WriteState = { ...state, values: chunk }
|
|
152
|
+
const scoped = pipe(dbName)
|
|
153
|
+
const stmt = compileAuditWrite(scoped, chunkState, ctx)
|
|
154
|
+
const rawDb = pipeRaw(dbName)
|
|
155
|
+
const rows = await rawDb.execute(stmt)
|
|
156
|
+
if (Array.isArray(rows)) written += rows.length
|
|
157
|
+
if (collectsRows && Array.isArray(rows)) {
|
|
158
|
+
for (const row of rows) collected.push(row)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (dbName === undefined) {
|
|
163
|
+
await pipe.transaction(runChunks)
|
|
164
|
+
} else {
|
|
165
|
+
await pipe.transaction(dbName, runChunks)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// An upsert marks the touches of both verb sets: which rows took which is not known here.
|
|
169
|
+
for (const touch of ctx.written) ctx.step.markWrote(touch)
|
|
170
|
+
if (state.onConflict !== undefined) reportSkipped(state, written)
|
|
171
|
+
return collectsRows ? decodeReturning(state, collected) : undefined
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- drizzle's DB/builder types are deeply generic; the WriteState shape has already validated the caller's input at the builder surface
|
|
175
|
+
type AnyDb = any
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
|
+
type AnyChain = any
|
|
178
|
+
|
|
179
|
+
async function executeUpdate(db: AnyDb, state: WriteState): Promise<unknown> {
|
|
180
|
+
let q: AnyChain = db.update(state.table).set(state.set ?? {})
|
|
181
|
+
if (state.where !== undefined) q = q.where(state.where)
|
|
182
|
+
return runWithReturning(q, state.returning)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function executeInsert(db: AnyDb, state: WriteState): Promise<unknown> {
|
|
186
|
+
const out = await runInsert(db, state)
|
|
187
|
+
reportSkipped(state, out.written)
|
|
188
|
+
return out.result
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* One insert statement. Under `onConflictDoNothing` the rows written are the
|
|
193
|
+
* rows the statement returns, so it asks for them when the caller did not; the
|
|
194
|
+
* caller still gets what it asked for.
|
|
195
|
+
*/
|
|
196
|
+
async function runInsert(db: AnyDb, state: WriteState): Promise<{ result: unknown; written: number }> {
|
|
197
|
+
const q: AnyChain = applyOnConflict(db.insert(state.table).values(state.values ?? []), state)
|
|
198
|
+
if (state.onConflict === undefined) return { result: await runWithReturning(q, state.returning), written: 0 }
|
|
199
|
+
const rows = await runWithReturning(q, state.returning ?? 'all') as unknown[]
|
|
200
|
+
const caller = state.returning === undefined || state.returning === null
|
|
201
|
+
return { result: caller ? [] : rows, written: rows.length }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function executeDelete(db: AnyDb, state: WriteState): Promise<unknown> {
|
|
205
|
+
let q: AnyChain = db.delete(state.table)
|
|
206
|
+
if (state.where !== undefined) q = q.where(state.where)
|
|
207
|
+
return runWithReturning(q, state.returning)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function executeUpsert(db: AnyDb, state: WriteState): Promise<unknown> {
|
|
211
|
+
if (state.onConflictColumns === undefined || state.onConflictColumns.length === 0) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
'pipe.upsert requires .onConflict([...columns]). ' +
|
|
214
|
+
'Pass the conflict-target columns that determine "this row already exists" — ' +
|
|
215
|
+
'usually the primary key or a unique index.',
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
const targetColumns = state.onConflictColumns.map(key => {
|
|
219
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DefinedTable columns are indexed by field key; the lookup is validated at the builder surface
|
|
220
|
+
const col = (state.table as any)[key]
|
|
221
|
+
if (col === undefined) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`pipe.upsert: onConflict column "${key}" is not a field of "${state.table.$name}". ` +
|
|
224
|
+
`Use a field key declared on the table.`,
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
return col as unknown
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
let q: AnyChain = db.insert(state.table).values(state.values ?? [])
|
|
231
|
+
if (state.set === undefined) {
|
|
232
|
+
q = q.onConflictDoNothing({ target: targetColumns })
|
|
233
|
+
} else {
|
|
234
|
+
q = q.onConflictDoUpdate({ target: targetColumns, set: state.set })
|
|
235
|
+
}
|
|
236
|
+
return runWithReturning(q, state.returning)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function runWithReturning(q: AnyChain, returning: WriteState['returning']): Promise<unknown> {
|
|
240
|
+
if (returning === undefined || returning === null) return q as Promise<unknown>
|
|
241
|
+
if (returning === 'all') return q.returning() as Promise<unknown>
|
|
242
|
+
return q.returning(returning) as Promise<unknown>
|
|
243
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Re-exports the pipework-owned write surface. Wired into the `pipe` namespace
|
|
2
|
+
// in src/data/db/namespace.ts as `pipe.update / pipe.insert / pipe.delete /
|
|
3
|
+
// pipe.upsert` — the only consumer-facing way to mutate pipework-managed
|
|
4
|
+
// tables (Q8). The raw-write lint rule (#47) enforces that callers don't
|
|
5
|
+
// reach past this surface into drizzle directly.
|
|
6
|
+
|
|
7
|
+
export { update, UpdateBuilder } from './update.js'
|
|
8
|
+
export { insert, InsertBuilder } from './insert.js'
|
|
9
|
+
export { remove, DeleteBuilder } from './delete.js'
|
|
10
|
+
export { upsert, UpsertBuilder } from './upsert.js'
|
|
11
|
+
export type { WriteState, WriteVerb, ReturningShape } from './state.js'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// `pipe.insert(table)` — the pipework-owned insert builder. Same intent-based
|
|
2
|
+
// design as UpdateBuilder; see update.ts for the rationale.
|
|
3
|
+
|
|
4
|
+
import type { PgTable } from '../query/pg-core/index.js'
|
|
5
|
+
import { assertNotExternal } from '../domain/external-registry.js'
|
|
6
|
+
import type { DefinedTable } from '../domain/types.js'
|
|
7
|
+
import type { WriteState } from './state.js'
|
|
8
|
+
import type { InsertValues } from './values.js'
|
|
9
|
+
import { executeWrite } from './execute.js'
|
|
10
|
+
|
|
11
|
+
export class InsertBuilder<T extends PgTable, TResult = unknown> implements PromiseLike<TResult> {
|
|
12
|
+
constructor(private readonly state: WriteState) {}
|
|
13
|
+
|
|
14
|
+
values(rows: InsertValues<T> | ReadonlyArray<InsertValues<T>>): InsertBuilder<T, TResult> {
|
|
15
|
+
const normalized = Array.isArray(rows) ? rows : [rows]
|
|
16
|
+
return new InsertBuilder<T, TResult>({
|
|
17
|
+
...this.state,
|
|
18
|
+
values: normalized as ReadonlyArray<Record<string, unknown>>,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Leaves a row that conflicts with an existing one and inserts the rest.
|
|
24
|
+
* Without `target`, any unique or exclusion constraint conflict is skipped.
|
|
25
|
+
* `.returning()` yields only the rows written, so a fully skipped insert
|
|
26
|
+
* returns `[]`. Under `@touches`, a skipped row gets no audit row; a traced
|
|
27
|
+
* flow records an `external:pipework.insert.conflict` step with the counts.
|
|
28
|
+
*/
|
|
29
|
+
onConflictDoNothing(options?: { target?: readonly (keyof T['$inferSelect'] & string)[] }): InsertBuilder<T, TResult> {
|
|
30
|
+
const target = options?.target
|
|
31
|
+
if (target !== undefined && target.length === 0) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
'pipe.insert onConflictDoNothing target must not be empty. ' +
|
|
34
|
+
'Pass the field keys of the unique constraint, or omit target to skip a conflict on any constraint.',
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
return new InsertBuilder<T, TResult>({
|
|
38
|
+
...this.state,
|
|
39
|
+
onConflict: target === undefined ? { action: 'nothing' } : { action: 'nothing', target: target as readonly string[] },
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
returning(): InsertBuilder<T, Record<string, unknown>[]>
|
|
44
|
+
returning<TShape extends Record<string, unknown>>(shape: TShape): InsertBuilder<T, Record<string, unknown>[]>
|
|
45
|
+
returning(shape?: Record<string, unknown>): InsertBuilder<T, Record<string, unknown>[]> {
|
|
46
|
+
const returning = shape === undefined ? 'all' as const : shape
|
|
47
|
+
return new InsertBuilder<T, Record<string, unknown>[]>({ ...this.state, returning })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
then<TFulfilled = TResult, TRejected = never>(
|
|
51
|
+
onfulfilled?: ((value: TResult) => TFulfilled | PromiseLike<TFulfilled>) | null,
|
|
52
|
+
onrejected?: ((reason: unknown) => TRejected | PromiseLike<TRejected>) | null,
|
|
53
|
+
): PromiseLike<TFulfilled | TRejected> {
|
|
54
|
+
return executeWrite(this.state).then(onfulfilled as (value: unknown) => TFulfilled, onrejected) as PromiseLike<TFulfilled | TRejected>
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Starts a pipework-owned insert against `table`. Chain with `.values(...).returning(...)`. */
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
export function insert<T extends DefinedTable<any> & PgTable>(table: T): InsertBuilder<T, any> {
|
|
61
|
+
assertNotExternal(table, 'insert')
|
|
62
|
+
return new InsertBuilder<T, unknown>({ verb: 'insert', table })
|
|
63
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// The intent of a pending write — the data shape every write builder hands to
|
|
2
|
+
// the executor. Today (#44) the executor compiles it to vanilla DML through
|
|
3
|
+
// drizzle. The CTE generator (#45) reads the same shape to compile to a single
|
|
4
|
+
// statement with `before` / `updated` / `diffs` / `audit_inserts` CTEs when an
|
|
5
|
+
// active flow_step carries `@touches`. Keeping the intent on a plain data
|
|
6
|
+
// object — never on the builder's drizzle handle — is what makes both paths
|
|
7
|
+
// produce the same observable behavior.
|
|
8
|
+
|
|
9
|
+
import type { SQL } from '../db/types.js'
|
|
10
|
+
import type { DefinedTable, FieldRecord } from '../domain/types.js'
|
|
11
|
+
|
|
12
|
+
export type WriteVerb = 'insert' | 'update' | 'delete' | 'upsert'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What the caller asked the write to return.
|
|
16
|
+
*
|
|
17
|
+
* - `null` (default) — no `RETURNING` clause; the executor resolves to `void`.
|
|
18
|
+
* - `'all'` — `RETURNING *`; the executor resolves to every column on the row.
|
|
19
|
+
* - `Record<string, unknown>` — a per-field projection, as accepted by
|
|
20
|
+
* drizzle's `.returning(...)` (column references, sql expressions, etc.).
|
|
21
|
+
*/
|
|
22
|
+
export type ReturningShape = Record<string, unknown> | 'all' | null
|
|
23
|
+
|
|
24
|
+
export interface WriteState {
|
|
25
|
+
readonly verb: WriteVerb
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DefinedTable's field generics erase at the write boundary; the executor only needs the table-level surface ($options, drizzle columns)
|
|
27
|
+
readonly table: DefinedTable<any>
|
|
28
|
+
/** `set` clause for `update` / `upsert`. */
|
|
29
|
+
readonly set?: Record<string, unknown>
|
|
30
|
+
/** Row values for `insert` / `upsert`. */
|
|
31
|
+
readonly values?: ReadonlyArray<Record<string, unknown>>
|
|
32
|
+
/** `where` clause for `update` / `delete`. */
|
|
33
|
+
readonly where?: SQL
|
|
34
|
+
/** Conflict-target columns for `upsert`. */
|
|
35
|
+
readonly onConflictColumns?: readonly string[]
|
|
36
|
+
/** `ON CONFLICT DO NOTHING` for `insert`; `target` is field keys, absent for the bare clause. */
|
|
37
|
+
readonly onConflict?: { readonly action: 'nothing'; readonly target?: readonly string[] }
|
|
38
|
+
/** What `RETURNING` should produce. `null` (default) emits no RETURNING. */
|
|
39
|
+
readonly returning?: ReturningShape
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Carrier type used by the builders' chain methods to mutate `WriteState` immutably. */
|
|
43
|
+
export type FieldRecordTable = DefinedTable<FieldRecord>
|