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,111 @@
|
|
|
1
|
+
// Builds the SELECT projection inside the `audit_inserts` CTE — one row per
|
|
2
|
+
// (affected-db-row × `@touches` target). The verb composers (insert / update
|
|
3
|
+
// / delete / upsert) supply the data-source clause (`FROM ...`) and the
|
|
4
|
+
// `before` / `after` jsonb expressions; everything else — the actor/tenant/
|
|
5
|
+
// trace fields, the entity/verb literals, the captured_inputs literal — is
|
|
6
|
+
// constant across rows and lives here.
|
|
7
|
+
//
|
|
8
|
+
// `@touches Entity:verb` is multi-target: a single write can carry several
|
|
9
|
+
// targets, and each one emits its own audit row per affected db row. We
|
|
10
|
+
// model that as `UNION ALL` over one SELECT per target, sharing the same
|
|
11
|
+
// FROM. (Postgres folds these into a single insert plan.) Empty targets is
|
|
12
|
+
// a programmer error — the executor short-circuits to the vanilla branch.
|
|
13
|
+
|
|
14
|
+
import { sql } from '../../db/sql.js'
|
|
15
|
+
import type { SQL } from '../../db/types.js'
|
|
16
|
+
import type { ActiveAuditStep } from '../../../trace/runtime/step-buffer.js'
|
|
17
|
+
import type { MetaTouch } from '../../../trace/runtime/step-meta.js'
|
|
18
|
+
import { quoteIdentifier } from '../../db/identifiers.js'
|
|
19
|
+
import { camelToSnake } from '../../domain/naming.js'
|
|
20
|
+
import { declaredPrimaryKeyFieldKeys } from './primary-key.js'
|
|
21
|
+
import type { DefinedTable } from '../../domain/types.js'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* SQL for `audit_record.entity_id` read from CTE alias `alias`: the single key
|
|
25
|
+
* column cast to text, or a compact JSON array of the key columns as text for
|
|
26
|
+
* a composite key; null for a table with no declared key. Matches `auditEntityId` in `primary-key.ts`.
|
|
27
|
+
*/
|
|
28
|
+
export function buildEntityIdExpr(
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- see primaryKeyFieldKeys
|
|
30
|
+
table: DefinedTable<any>,
|
|
31
|
+
alias: string,
|
|
32
|
+
): SQL {
|
|
33
|
+
const keys = declaredPrimaryKeyFieldKeys(table)
|
|
34
|
+
if (keys.length === 0) return sql`NULL::text`
|
|
35
|
+
const cols = keys.map(k => `${alias}.${quoteIdentifier(camelToSnake(k), 'pk column')}::text`)
|
|
36
|
+
return cols.length === 1
|
|
37
|
+
? sql.raw(cols[0]!)
|
|
38
|
+
: sql.raw(`array_to_json(ARRAY[${cols.join(', ')}])::text`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Information about the active flow_step plus the actor/tenant the row is
|
|
43
|
+
* attributed to. The CTE compiler resolves these from the active `Flow` and
|
|
44
|
+
* passes them in — keeps the SQL composer free of context-store coupling.
|
|
45
|
+
*/
|
|
46
|
+
export interface AuditSelectContext {
|
|
47
|
+
readonly step: ActiveAuditStep
|
|
48
|
+
/** The touches the write is recorded under — one audit row per affected row per touch. */
|
|
49
|
+
readonly touches: readonly MetaTouch[]
|
|
50
|
+
/** Upsert only: which touches take newly inserted rows (`create`) and which take updated rows (`update`). Absent: every touch takes every row. */
|
|
51
|
+
readonly split?: { readonly create: readonly MetaTouch[]; readonly update: readonly MetaTouch[] } | undefined
|
|
52
|
+
/** The touches the executor marks written once the write lands. */
|
|
53
|
+
readonly written: readonly MetaTouch[]
|
|
54
|
+
/** `auth?.userId`, or null when the trace ran without an authenticated principal. */
|
|
55
|
+
readonly actorId: string | null
|
|
56
|
+
/** `flow.tenant`, or null when the entry was not tenant-scoped. */
|
|
57
|
+
readonly tenantId: string | null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Composes the `audit_inserts` CTE body: one `SELECT` per `@touches` target,
|
|
62
|
+
* joined by `UNION ALL`, projecting onto the `audit_record` column order.
|
|
63
|
+
*
|
|
64
|
+
* The shape is positional — matches `AUDIT_COLUMNS_SQL` exactly:
|
|
65
|
+
* id, trace_day, trace_id, trace_step_id, tenant_id, actor_id,
|
|
66
|
+
* entity, verb, entity_id, before, after, captured_inputs, occurred_at
|
|
67
|
+
*
|
|
68
|
+
* `from` is the verb's contributing FROM clause (e.g. `FROM updated u LEFT
|
|
69
|
+
* JOIN before b ON ...`), `before` / `after` are the per-row jsonb
|
|
70
|
+
* expressions to project (or `NULL::jsonb` when the half doesn't apply).
|
|
71
|
+
*/
|
|
72
|
+
export function buildAuditInserts(
|
|
73
|
+
ctx: AuditSelectContext,
|
|
74
|
+
from: SQL,
|
|
75
|
+
entityId: SQL,
|
|
76
|
+
before: SQL,
|
|
77
|
+
after: SQL,
|
|
78
|
+
/** Optional per-touch predicate appended to that touch's SELECT as a WHERE. */
|
|
79
|
+
rowFilter?: (touch: MetaTouch) => SQL | null,
|
|
80
|
+
): SQL {
|
|
81
|
+
const capturedInputs = ctx.step.capturedInputs
|
|
82
|
+
const capturedJson = capturedInputs === null
|
|
83
|
+
? sql`NULL::jsonb`
|
|
84
|
+
: sql`${JSON.stringify(capturedInputs)}::jsonb`
|
|
85
|
+
|
|
86
|
+
const selects = ctx.touches.map(t => sql`
|
|
87
|
+
SELECT
|
|
88
|
+
gen_random_uuid(),
|
|
89
|
+
${ctx.step.traceDay}::date,
|
|
90
|
+
${ctx.step.traceId}::uuid,
|
|
91
|
+
${ctx.step.stepId}::uuid,
|
|
92
|
+
${ctx.tenantId}::uuid,
|
|
93
|
+
${ctx.actorId},
|
|
94
|
+
${t.entity},
|
|
95
|
+
${t.verb},
|
|
96
|
+
${entityId},
|
|
97
|
+
${before},
|
|
98
|
+
${after},
|
|
99
|
+
${capturedJson},
|
|
100
|
+
now()
|
|
101
|
+
${from}
|
|
102
|
+
${filterFor(rowFilter, t)}
|
|
103
|
+
`)
|
|
104
|
+
|
|
105
|
+
return sql.join(selects, sql` UNION ALL `)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function filterFor(rowFilter: ((t: MetaTouch) => SQL | null) | undefined, t: MetaTouch): SQL {
|
|
109
|
+
const f = rowFilter?.(t) ?? null
|
|
110
|
+
return f === null ? sql`` : sql`WHERE ${f}`
|
|
111
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The Flow's open transaction on a database, for audit writers that must not
|
|
2
|
+
// wait on it from a second connection. Null when none is bound.
|
|
3
|
+
|
|
4
|
+
import type { DB } from '../../db/index.js'
|
|
5
|
+
import type { Flow } from '../../../request/context/types.js'
|
|
6
|
+
import { verifyTransactionBinding } from '../../../request/context/guard.js'
|
|
7
|
+
|
|
8
|
+
export function boundTransaction(flow: Flow, database?: string): DB | null {
|
|
9
|
+
const name = database ?? flow.instance.config.defaultDatabaseName()
|
|
10
|
+
const bound = flow.transactions.get(name)
|
|
11
|
+
if (bound === undefined) return null
|
|
12
|
+
verifyTransactionBinding(flow, bound, name)
|
|
13
|
+
return bound.drizzle
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Entry point for the write+audit CTE generator. Routes by `state.verb` to
|
|
2
|
+
// the per-verb composer. Empty `touches` is a programmer error — the caller
|
|
3
|
+
// (executor) is expected to fall through to the vanilla branch when the
|
|
4
|
+
// active step has no audit projection. We assert here so a regression in
|
|
5
|
+
// the executor surfaces immediately rather than producing an audit row
|
|
6
|
+
// with empty entity/verb.
|
|
7
|
+
|
|
8
|
+
import { compileInsertAudit } from './verb-insert.js'
|
|
9
|
+
import { compileUpdateAudit } from './verb-update.js'
|
|
10
|
+
import { compileDeleteAudit } from './verb-delete.js'
|
|
11
|
+
import { compileUpsertAudit } from './verb-upsert.js'
|
|
12
|
+
import type { SQL } from '../../db/types.js'
|
|
13
|
+
import type { WriteState } from '../state.js'
|
|
14
|
+
import type { AuditSelectContext } from './audit-select.js'
|
|
15
|
+
|
|
16
|
+
// 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
|
|
17
|
+
type AnyDb = any
|
|
18
|
+
|
|
19
|
+
export function compileAuditWrite(db: AnyDb, state: WriteState, ctx: AuditSelectContext): SQL {
|
|
20
|
+
if (ctx.touches.length === 0) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`[pipework] compileAuditWrite called with empty touches on "${state.table.$name}". ` +
|
|
23
|
+
`Active step "${ctx.step.stepId}" has no @touches matching this table — the executor should have taken the vanilla DML branch.`,
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
switch (state.verb) {
|
|
27
|
+
case 'insert': return compileInsertAudit(db, state, ctx)
|
|
28
|
+
case 'update': return compileUpdateAudit(db, state, ctx)
|
|
29
|
+
case 'delete': return compileDeleteAudit(db, state, ctx)
|
|
30
|
+
case 'upsert': return compileUpsertAudit(db, state, ctx)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Decodes the CTE statement's raw rows the way drizzle decodes a vanilla
|
|
2
|
+
// `.returning()`: each value is read from its physical column and run through
|
|
3
|
+
// the column's `mapFromDriverValue` (timestamps to Date, bigint, json, ...),
|
|
4
|
+
// nulls passing through. Keys are the table's field keys for `returning('all')`
|
|
5
|
+
// and the caller's aliases for a per-key projection.
|
|
6
|
+
|
|
7
|
+
import { physicalColumnName } from './final-select.js'
|
|
8
|
+
import type { WriteState } from '../state.js'
|
|
9
|
+
|
|
10
|
+
type Decoder = (value: unknown) => unknown
|
|
11
|
+
interface Slot { readonly key: string; readonly physical: string; readonly decode: Decoder | undefined }
|
|
12
|
+
|
|
13
|
+
export function decodeReturning(state: WriteState, rows: unknown): unknown {
|
|
14
|
+
const returning = state.returning
|
|
15
|
+
if (returning === undefined || returning === null) return undefined
|
|
16
|
+
if (!Array.isArray(rows)) return rows
|
|
17
|
+
const slots = slotsFor(state)
|
|
18
|
+
return rows.map(row => {
|
|
19
|
+
const raw = row as Record<string, unknown>
|
|
20
|
+
const out: Record<string, unknown> = {}
|
|
21
|
+
for (const slot of slots) {
|
|
22
|
+
const value = raw[slot.physical]
|
|
23
|
+
out[slot.key] = value === null || value === undefined || slot.decode === undefined ? value : slot.decode(value)
|
|
24
|
+
}
|
|
25
|
+
return out
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function slotsFor(state: WriteState): Slot[] {
|
|
30
|
+
const returning = state.returning
|
|
31
|
+
const table = state.table as unknown as Record<string, unknown>
|
|
32
|
+
if (returning === 'all' || returning === null || returning === undefined || Object.keys(returning).length === 0) {
|
|
33
|
+
return Object.keys(state.table.$fields).map(key => slot(key, key, table[key]))
|
|
34
|
+
}
|
|
35
|
+
// Per-key rows are aliased by the final select, so they are read by alias.
|
|
36
|
+
return Object.keys(returning).map(key => ({ key, physical: key, decode: decoderOf(returning[key]) }))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function slot(key: string, alias: string, column: unknown): Slot {
|
|
40
|
+
return { key, physical: physicalColumnName(alias, column), decode: decoderOf(column) }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function decoderOf(column: unknown): Decoder | undefined {
|
|
44
|
+
const fn = (column as { mapFromDriverValue?: unknown } | null)?.mapFromDriverValue
|
|
45
|
+
if (typeof fn !== 'function') return undefined
|
|
46
|
+
return (v: unknown) => (fn as Decoder).call(column, v)
|
|
47
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Builds the final SELECT of the CTE statement — the row(s) the caller sees.
|
|
2
|
+
// Mirrors the shape `vanilla DML` would have produced for the same `returning`:
|
|
3
|
+
// no `returning` → no rows (one placeholder row per written row under onConflict); `returning('all')` → SELECT *; per-key projection
|
|
4
|
+
// → aliased column list. Reads from the verb's post-image CTE (`inserted` /
|
|
5
|
+
// `updated` / `deleted` / `upserted`).
|
|
6
|
+
|
|
7
|
+
import { sql } from '../../db/sql.js'
|
|
8
|
+
import { quoteIdentifier } from '../../db/identifiers.js'
|
|
9
|
+
import { camelToSnake } from '../../domain/naming.js'
|
|
10
|
+
import type { SQL } from '../../db/types.js'
|
|
11
|
+
import type { WriteState } from '../state.js'
|
|
12
|
+
|
|
13
|
+
export function buildFinalSelect(state: WriteState, sourceCte: string): SQL {
|
|
14
|
+
const source = quoteIdentifier(sourceCte, 'CTE alias')
|
|
15
|
+
const returning = state.returning ?? null
|
|
16
|
+
if (returning === null) {
|
|
17
|
+
// Under onConflictDoNothing the rows written are counted from the rows returned.
|
|
18
|
+
if (state.onConflict !== undefined) return sql.raw(`SELECT 1 FROM ${source}`)
|
|
19
|
+
return sql.raw(`SELECT 1 FROM ${source} WHERE FALSE`)
|
|
20
|
+
}
|
|
21
|
+
if (returning === 'all') {
|
|
22
|
+
return sql.raw(`SELECT * FROM ${source}`)
|
|
23
|
+
}
|
|
24
|
+
const keys = Object.keys(returning)
|
|
25
|
+
if (keys.length === 0) return sql.raw(`SELECT * FROM ${source}`)
|
|
26
|
+
const cols = keys.map(k => {
|
|
27
|
+
const physical = quoteIdentifier(physicalColumnName(k, returning[k]), 'returning column')
|
|
28
|
+
const alias = quoteIdentifier(k, 'returning alias')
|
|
29
|
+
return `${source}.${physical} AS ${alias}`
|
|
30
|
+
}).join(', ')
|
|
31
|
+
return sql.raw(`SELECT ${cols} FROM ${source}`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The projected column's own SQL name when `value` is a column; otherwise the alias, snake-cased. */
|
|
35
|
+
export function physicalColumnName(alias: string, value: unknown): string {
|
|
36
|
+
const name = (value as { name?: unknown } | null)?.name
|
|
37
|
+
if (isColumn(value) && typeof name === 'string') return name
|
|
38
|
+
return camelToSnake(alias)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isColumn(value: unknown): value is { name: string; mapFromDriverValue?: (v: unknown) => unknown } {
|
|
42
|
+
return typeof value === 'object' && value !== null && 'columnType' in value
|
|
43
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Writes one audit_record for an infrastructure operation (access links,
|
|
2
|
+
// credits) into the open trace step, in the operation's own transaction.
|
|
3
|
+
// No trace or no open step: nothing is written. A failed write fails the
|
|
4
|
+
// operation: the error reaches the caller and its transaction rolls back, so
|
|
5
|
+
// no row commits without its audit record. The database error stays on `cause`.
|
|
6
|
+
|
|
7
|
+
import { sql } from '../../db/sql.js'
|
|
8
|
+
import type { DB } from '../../db/index.js'
|
|
9
|
+
import type { Flow } from '../../../request/context/types.js'
|
|
10
|
+
import { resolveActorId } from './actor.js'
|
|
11
|
+
|
|
12
|
+
export interface InfraAudit {
|
|
13
|
+
readonly tenantId: string
|
|
14
|
+
readonly entity: string
|
|
15
|
+
readonly verb: string
|
|
16
|
+
readonly entityId: string
|
|
17
|
+
readonly before?: Record<string, unknown>
|
|
18
|
+
readonly after: Record<string, unknown>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function writeInfraAudit(flow: Flow, tx: DB, record: InfraAudit): Promise<void> {
|
|
22
|
+
const trace = flow.trace
|
|
23
|
+
const stepId = trace?.buffer.openStepId ?? null
|
|
24
|
+
if (trace === null || stepId === null) return
|
|
25
|
+
try {
|
|
26
|
+
await tx.execute(sql`
|
|
27
|
+
INSERT INTO audit_record
|
|
28
|
+
(trace_day, trace_id, trace_step_id, tenant_id, actor_id, entity, verb, entity_id, before, after, occurred_at)
|
|
29
|
+
VALUES (${trace.openedDay}, ${trace.id}, ${stepId}, ${record.tenantId}, ${resolveActorId(flow)},
|
|
30
|
+
${record.entity}, ${record.verb}, ${record.entityId},
|
|
31
|
+
${record.before === undefined ? null : JSON.stringify(record.before)}::jsonb, ${JSON.stringify(record.after)}::jsonb, now())
|
|
32
|
+
`)
|
|
33
|
+
} catch (err) {
|
|
34
|
+
throw new Error(`Failed to write the ${record.entity} ${record.verb} audit record`, { cause: err })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Primary-key resolution for the audit CTE generator. The UPDATE / DELETE /
|
|
2
|
+
// UPSERT verbs need a key to join the `before` snapshot with the post-image
|
|
3
|
+
// (`updated` / `deleted` / `upserted`) so the audit row pairs the right
|
|
4
|
+
// pre/post pair per affected db row. We honor the two ways a `pipe.define()`
|
|
5
|
+
// table declares its key: composite via the `primaryKey` option, or single
|
|
6
|
+
// via a field-level `.primaryKey()`.
|
|
7
|
+
|
|
8
|
+
import type { DefinedTable, FieldRecord } from '../../domain/types.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the primary-key field keys in declaration order. Throws when the
|
|
12
|
+
* table declares no primary key — the audit CTE needs one to pair rows.
|
|
13
|
+
*/
|
|
14
|
+
export function primaryKeyFieldKeys(
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DefinedTable's field generics erase at this boundary; we only read $fields and $options
|
|
16
|
+
table: DefinedTable<any>,
|
|
17
|
+
): readonly string[] {
|
|
18
|
+
const keys = declaredPrimaryKeyFieldKeys(table)
|
|
19
|
+
if (keys.length === 0) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`[pipework] Cannot emit audit CTE for "${table.$name}": ` +
|
|
22
|
+
`the table declares no primary key.\n\n` +
|
|
23
|
+
` An audit-emitting write needs a primary key so the pre-image\n` +
|
|
24
|
+
` and post-image rows can be paired. Add .primaryKey() to a field\n` +
|
|
25
|
+
` or pass { primaryKey: ['col1', 'col2'] } to the table options.\n`,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
return keys
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The declared primary-key field keys in declaration order; empty when the table has none. */
|
|
32
|
+
export function declaredPrimaryKeyFieldKeys(
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- see primaryKeyFieldKeys
|
|
34
|
+
table: DefinedTable<any>,
|
|
35
|
+
): readonly string[] {
|
|
36
|
+
const composite = table.$options.primaryKey
|
|
37
|
+
if (composite !== undefined && composite.length > 0) return composite
|
|
38
|
+
const fields = table.$fields as FieldRecord
|
|
39
|
+
const single: string[] = []
|
|
40
|
+
for (const [key, descriptor] of Object.entries(fields)) {
|
|
41
|
+
if (descriptor._meta.primaryKey) single.push(key)
|
|
42
|
+
}
|
|
43
|
+
return single
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The audited row's primary key as `audit_record.entity_id` text: the single
|
|
48
|
+
* key's value, or for a composite key a compact JSON array of the key values
|
|
49
|
+
* as strings, in key order (`["a","7"]`).
|
|
50
|
+
*/
|
|
51
|
+
export function auditEntityId(
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- see primaryKeyFieldKeys
|
|
53
|
+
table: DefinedTable<any>,
|
|
54
|
+
row: Readonly<Record<string, unknown>>,
|
|
55
|
+
): string {
|
|
56
|
+
const keys = primaryKeyFieldKeys(table)
|
|
57
|
+
const values = keys.map(k => {
|
|
58
|
+
const v = row[k]
|
|
59
|
+
if (v === null || v === undefined) {
|
|
60
|
+
throw new Error(`audit entity id for "${table.$name}": primary key field "${k}" is missing from the row.`)
|
|
61
|
+
}
|
|
62
|
+
return v instanceof Date ? v.toISOString() : String(v)
|
|
63
|
+
})
|
|
64
|
+
return keys.length === 1 ? values[0]! : JSON.stringify(values)
|
|
65
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Server-side `.traceSafe()` projection. Builds a `jsonb_build_object(...)`
|
|
2
|
+
// fragment from the table's `traceSafeKeys` so the audit row's `before` /
|
|
3
|
+
// `after` columns are populated entirely inside the SQL — never by a
|
|
4
|
+
// round-trip to the runtime. The projection is fail-closed: only fields
|
|
5
|
+
// explicitly marked `.traceSafe()` participate; a new field is never
|
|
6
|
+
// audited by accident.
|
|
7
|
+
//
|
|
8
|
+
// `null` is returned when the table has no trace-safe fields. The verb
|
|
9
|
+
// composers turn that into `NULL::jsonb` in the audit row, so a write
|
|
10
|
+
// against an unprojectable table still emits an audit row — just with
|
|
11
|
+
// empty before/after halves.
|
|
12
|
+
|
|
13
|
+
import { sql } from '../../db/sql.js'
|
|
14
|
+
import { quoteIdentifier } from '../../db/identifiers.js'
|
|
15
|
+
import { camelToSnake } from '../../domain/naming.js'
|
|
16
|
+
import { traceSafeKeys } from '../../domain/trace-safe-projection.js'
|
|
17
|
+
import type { SQL } from '../../db/types.js'
|
|
18
|
+
import type { DefinedTable, FieldRecord } from '../../domain/types.js'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `jsonb_build_object('field', "source"."column", ...)` over each trace-safe
|
|
22
|
+
* field, qualified by `sourceAlias` (the CTE name the column resolves through:
|
|
23
|
+
* `before` for the pre-image, `updated` / `deleted` / `inserted` / `upserted`
|
|
24
|
+
* for the post-image). Returns `null` when the table has no trace-safe fields
|
|
25
|
+
* — the caller substitutes `NULL::jsonb`.
|
|
26
|
+
*/
|
|
27
|
+
export function buildTraceSafeProjection(
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DefinedTable's field generics erase at this boundary; we only read $fields
|
|
29
|
+
table: DefinedTable<any>,
|
|
30
|
+
sourceAlias: string,
|
|
31
|
+
): SQL | null {
|
|
32
|
+
const keys = traceSafeKeys(table.$fields as FieldRecord)
|
|
33
|
+
if (keys.length === 0) return null
|
|
34
|
+
const aliasIdent = quoteIdentifier(sourceAlias, 'CTE alias')
|
|
35
|
+
const parts: SQL[] = []
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
const col = quoteIdentifier(camelToSnake(key), 'trace-safe column')
|
|
38
|
+
parts.push(sql.raw(`'${key}', ${aliasIdent}.${col}`))
|
|
39
|
+
}
|
|
40
|
+
return sql`jsonb_build_object(${sql.join(parts, sql.raw(', '))})`
|
|
41
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Read-touches audit emission. A flow_step that declared `@touches Entity:verb`
|
|
2
|
+
// but performed no `pipe.*` write to its entity is recording a read, not a mutation: the step
|
|
3
|
+
// observed the entity in service of the request, and the audit substrate must
|
|
4
|
+
// record that observation for compliance and trail-reconstruction. The
|
|
5
|
+
// CTE-form write path covers the touches it wrote (recorded by
|
|
6
|
+
// `markWrote(touch)`); this module covers the rest.
|
|
7
|
+
//
|
|
8
|
+
// Atomicity: the emit goes through `pipeRaw()` so a bound transaction on the
|
|
9
|
+
// active Flow carries the insert. If the caller wrapped the step in
|
|
10
|
+
// `pipe.transaction(...)`, the audit row lands in that transaction; otherwise
|
|
11
|
+
// it's a standalone write. Either way it's not pool-direct (unlike trace_step
|
|
12
|
+
// flush), so a request-level rollback discards the audit too — read-touches is
|
|
13
|
+
// a record of what *actually* happened, not what was attempted.
|
|
14
|
+
//
|
|
15
|
+
// The emit uses raw drizzle (`db.insert(table).values(...)`) rather than
|
|
16
|
+
// `pipe.insert` to bypass the executor's audit branch — going through
|
|
17
|
+
// `pipe.insert(auditRecord)` while the active step has `@touches` would route
|
|
18
|
+
// through `compileAuditWrite` and try to project audit_record itself, which is
|
|
19
|
+
// nonsense.
|
|
20
|
+
|
|
21
|
+
import { auditRecord } from '../../../audit/schema/audit-record.js'
|
|
22
|
+
import { pipeRaw } from '../../db/db.js'
|
|
23
|
+
import { getBaseLogger } from '../../../core/logging/index.js'
|
|
24
|
+
import { recordFault, faultMessage } from '../../../infra/alerts/faults.js'
|
|
25
|
+
import type { Flow } from '../../../request/context/types.js'
|
|
26
|
+
import type { BegunStep } from '../../../trace/runtime/step-buffer.js'
|
|
27
|
+
import { resolveActorId } from './actor.js'
|
|
28
|
+
|
|
29
|
+
interface AuditRecordInsert {
|
|
30
|
+
readonly traceDay: string
|
|
31
|
+
readonly traceId: string
|
|
32
|
+
readonly traceStepId: string
|
|
33
|
+
readonly tenantId: string | null
|
|
34
|
+
readonly actorId: string | null
|
|
35
|
+
readonly entity: string
|
|
36
|
+
readonly verb: string
|
|
37
|
+
readonly before: null
|
|
38
|
+
readonly after: null
|
|
39
|
+
readonly capturedInputs: unknown
|
|
40
|
+
readonly occurredAt: Date
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Emits one `audit_record` row per `@touches` target that no write of the
|
|
45
|
+
* completed step audited. Resolves when the insert settles. Failures are
|
|
46
|
+
* logged at error level and swallowed — an audit-emit failure must not
|
|
47
|
+
* propagate to the user-visible step outcome (the step already succeeded).
|
|
48
|
+
*/
|
|
49
|
+
export async function emitReadTouches(flow: Flow, begun: BegunStep): Promise<void> {
|
|
50
|
+
const unwritten = begun.touches.filter(t => !begun.wroteTouches.has(t))
|
|
51
|
+
if (unwritten.length === 0) return
|
|
52
|
+
if (flow.trace === null) return
|
|
53
|
+
|
|
54
|
+
const traceDay = flow.trace.openedDay
|
|
55
|
+
const traceId = flow.trace.id
|
|
56
|
+
const tenantId = flow.tenant
|
|
57
|
+
const actorId = resolveActorId(flow)
|
|
58
|
+
const capturedInputs = begun.capturedInputs
|
|
59
|
+
const occurredAt = new Date()
|
|
60
|
+
|
|
61
|
+
const rows: AuditRecordInsert[] = unwritten.map(t => ({
|
|
62
|
+
traceDay,
|
|
63
|
+
traceId,
|
|
64
|
+
traceStepId: begun.stepId,
|
|
65
|
+
tenantId,
|
|
66
|
+
actorId,
|
|
67
|
+
entity: t.entity,
|
|
68
|
+
verb: t.verb,
|
|
69
|
+
before: null,
|
|
70
|
+
after: null,
|
|
71
|
+
capturedInputs,
|
|
72
|
+
occurredAt,
|
|
73
|
+
}))
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const db = pipeRaw(auditRecord.$options.database)
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Drizzle's insert needs the concrete runtime table type
|
|
78
|
+
await (db as any).insert(auditRecord.table()).values(rows)
|
|
79
|
+
} catch (err) {
|
|
80
|
+
getBaseLogger().error(
|
|
81
|
+
{ err, traceId, stepId: begun.stepId, touches: unwritten.length },
|
|
82
|
+
'Failed to emit read-touches audit row — the audit trail is missing a read observation',
|
|
83
|
+
)
|
|
84
|
+
void recordFault('audit-write', faultMessage(err), { entity: 'ReadTouch', touches: unwritten.length })
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Maps a flow step's `@touches Entity:verb` targets onto the table a write hits.
|
|
2
|
+
//
|
|
3
|
+
// A touch's `Entity` matches a table when:
|
|
4
|
+
// (a) the definition declares `define(name, fields, { auditEntity })` and it
|
|
5
|
+
// equals `Entity` exactly; otherwise
|
|
6
|
+
// (b) the normalized names agree: lowercase with underscores removed, with the
|
|
7
|
+
// table name optionally ending in `s` or `es` (`Version` matches
|
|
8
|
+
// `version` / `versions`; `DocumentVersion` matches `document_versions`).
|
|
9
|
+
//
|
|
10
|
+
// A write is audited only under the touches that match its table. A touch whose
|
|
11
|
+
// entity matches no registered definition cannot be audited by any write, so a
|
|
12
|
+
// write under it is refused rather than silently unaudited.
|
|
13
|
+
|
|
14
|
+
import { definitions } from '../../domain/define.js'
|
|
15
|
+
import type { DefinedTable } from '../../domain/types.js'
|
|
16
|
+
import type { MetaTouch } from '../../../trace/runtime/step-meta.js'
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- registry erases field-level generics
|
|
19
|
+
type AnyDefinition = DefinedTable<any>
|
|
20
|
+
|
|
21
|
+
function normalize(name: string): string {
|
|
22
|
+
return name.toLowerCase().replace(/_/g, '')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function touchMatchesTable(entity: string, table: AnyDefinition): boolean {
|
|
26
|
+
const declared = table.$options.auditEntity
|
|
27
|
+
if (declared !== undefined) return declared === entity
|
|
28
|
+
const want = normalize(entity)
|
|
29
|
+
const have = normalize(table.$name)
|
|
30
|
+
if (have === want) return true
|
|
31
|
+
return (have.endsWith('es') && have.slice(0, -2) === want) ||
|
|
32
|
+
(have.endsWith('s') && have.slice(0, -1) === want)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The touches a write to `table` is audited under. Throws when any of the
|
|
37
|
+
* step's touches names an entity no registered definition matches.
|
|
38
|
+
*/
|
|
39
|
+
export function touchesForTable(table: AnyDefinition, touches: readonly MetaTouch[]): MetaTouch[] {
|
|
40
|
+
const all = [...definitions().values()]
|
|
41
|
+
for (const t of touches) {
|
|
42
|
+
if (touchMatchesTable(t.entity, table)) continue
|
|
43
|
+
if (all.some(d => touchMatchesTable(t.entity, d))) continue
|
|
44
|
+
const near = closest(t.entity, all.map(d => d.$name))
|
|
45
|
+
throw new Error(
|
|
46
|
+
`[pipework] @touches entity "${t.entity}" matches no defined table, so a write under it cannot be audited. ` +
|
|
47
|
+
(near.length > 0 ? `Closest table names: ${near.join(', ')}. ` : '') +
|
|
48
|
+
'Name the entity after the table (case-insensitive, underscores ignored, plural allowed) ' +
|
|
49
|
+
"or set define(name, fields, { auditEntity: '<Entity>' }) on the table.",
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
return touches.filter(t => touchMatchesTable(t.entity, table))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function closest(entity: string, names: readonly string[]): string[] {
|
|
56
|
+
const want = normalize(entity)
|
|
57
|
+
return names
|
|
58
|
+
.map(n => ({ n, d: editDistance(want, normalize(n)) }))
|
|
59
|
+
.sort((a, b) => a.d - b.d)
|
|
60
|
+
.slice(0, 3)
|
|
61
|
+
.map(x => x.n)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function editDistance(a: string, b: string): number {
|
|
65
|
+
let prev = Array.from({ length: b.length + 1 }, (_, j) => j)
|
|
66
|
+
for (let i = 1; i <= a.length; i++) {
|
|
67
|
+
const cur = [i]
|
|
68
|
+
for (let j = 1; j <= b.length; j++) {
|
|
69
|
+
cur[j] = Math.min(
|
|
70
|
+
prev[j]! + 1,
|
|
71
|
+
cur[j - 1]! + 1,
|
|
72
|
+
prev[j - 1]! + (a[i - 1] === b[j - 1] ? 0 : 1),
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
prev = cur
|
|
76
|
+
}
|
|
77
|
+
return prev[b.length]!
|
|
78
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// DELETE under audit: snapshots the matched rows into `before`, runs the
|
|
2
|
+
// DELETE into `deleted`, then emits one audit row per deleted db row per
|
|
3
|
+
// `@touches` target. The audit's `after` is always `NULL::jsonb` — there is
|
|
4
|
+
// no post-image for a delete. The statement's snapshot keeps `before` and
|
|
5
|
+
// `deleted` consistent — no lock needed; `FOR UPDATE` inside a CTE conflicts
|
|
6
|
+
// with the sibling DELETE on the same row and silently yields zero rows from
|
|
7
|
+
// `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 compileDeleteAudit(db: AnyDb, state: WriteState, ctx: AuditSelectContext): SQL {
|
|
24
|
+
const pk = primaryKeyFieldKeys(state.table)
|
|
25
|
+
|
|
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 deleteChain = db.delete(state.table)
|
|
30
|
+
if (state.where !== undefined) deleteChain = deleteChain.where(state.where)
|
|
31
|
+
const deleteInner = deleteChain.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} = deleted.${col}`
|
|
41
|
+
})
|
|
42
|
+
.join(' AND ')
|
|
43
|
+
|
|
44
|
+
const before = buildTraceSafeProjection(state.table, 'before') ?? sql`NULL::jsonb`
|
|
45
|
+
// Source the audit from `deleted` — RETURNING * gives every column we need
|
|
46
|
+
// for the pre-image join, and `deleted` includes only the rows that actually
|
|
47
|
+
// matched. Joining onto `before` carries the FOR UPDATE lock.
|
|
48
|
+
const fromClause = sql.raw(`FROM deleted JOIN before ON ${joinOn}`)
|
|
49
|
+
const auditSelect = buildAuditInserts(ctx, fromClause, buildEntityIdExpr(state.table, 'deleted'), before, sql`NULL::jsonb`)
|
|
50
|
+
const finalSelect = buildFinalSelect(state, 'deleted')
|
|
51
|
+
|
|
52
|
+
return sql`
|
|
53
|
+
WITH before AS (${beforeInner}),
|
|
54
|
+
deleted AS (${deleteInner}),
|
|
55
|
+
audit_inserts AS (
|
|
56
|
+
INSERT INTO "audit_record" (${sql.raw(AUDIT_COLUMNS_SQL)})
|
|
57
|
+
${auditSelect}
|
|
58
|
+
RETURNING 1
|
|
59
|
+
)
|
|
60
|
+
${finalSelect}
|
|
61
|
+
`
|
|
62
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// INSERT under audit: no `before` snapshot. The audit row pairs `NULL::jsonb`
|
|
2
|
+
// with the `.traceSafe()` projection of the inserted row, drawn from the
|
|
3
|
+
// `inserted` CTE's RETURNING *. One audit row per inserted db row per
|
|
4
|
+
// `@touches` target.
|
|
5
|
+
|
|
6
|
+
import { sql } from '../../db/sql.js'
|
|
7
|
+
import { buildTraceSafeProjection } from './projection.js'
|
|
8
|
+
import { buildAuditInserts, buildEntityIdExpr, type AuditSelectContext } from './audit-select.js'
|
|
9
|
+
import { AUDIT_COLUMNS_SQL } from './audit-columns.js'
|
|
10
|
+
import { buildFinalSelect } from './final-select.js'
|
|
11
|
+
import { applyOnConflict } from '../conflict.js'
|
|
12
|
+
import type { SQL } from '../../db/types.js'
|
|
13
|
+
import type { WriteState } from '../state.js'
|
|
14
|
+
|
|
15
|
+
// 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
|
|
16
|
+
type AnyDb = any
|
|
17
|
+
|
|
18
|
+
export function compileInsertAudit(db: AnyDb, state: WriteState, ctx: AuditSelectContext): SQL {
|
|
19
|
+
// `.getSQL()` strips the auto-paren drizzle adds around SQLWrapper builders
|
|
20
|
+
// when they are templated — we add explicit parens at the CTE boundary so
|
|
21
|
+
// the WITH clause keeps a uniform shape across verbs.
|
|
22
|
+
const inner = applyOnConflict(db.insert(state.table).values(state.values ?? []), state).returning().getSQL()
|
|
23
|
+
const after = buildTraceSafeProjection(state.table, 'inserted') ?? sql`NULL::jsonb`
|
|
24
|
+
const auditSelect = buildAuditInserts(ctx, sql`FROM inserted`, buildEntityIdExpr(state.table, 'inserted'), sql`NULL::jsonb`, after)
|
|
25
|
+
const finalSelect = buildFinalSelect(state, 'inserted')
|
|
26
|
+
|
|
27
|
+
return sql`
|
|
28
|
+
WITH inserted AS (${inner}),
|
|
29
|
+
audit_inserts AS (
|
|
30
|
+
INSERT INTO "audit_record" (${sql.raw(AUDIT_COLUMNS_SQL)})
|
|
31
|
+
${auditSelect}
|
|
32
|
+
RETURNING 1
|
|
33
|
+
)
|
|
34
|
+
${finalSelect}
|
|
35
|
+
`
|
|
36
|
+
}
|