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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration pragmas — per-file declarations read from the migration's own
|
|
3
|
+
* comments.
|
|
4
|
+
*
|
|
5
|
+
* Authorization for a hazardous migration lives here rather than on the
|
|
6
|
+
* `pipework migrate` command line, and the distinction is the whole point.
|
|
7
|
+
* A CLI flag authorizes an *invocation*: it is written once, into a deploy
|
|
8
|
+
* workflow, by someone who cannot know what migration 0071 will contain nine
|
|
9
|
+
* months later. The first destructive migration that has to ship puts
|
|
10
|
+
* `--allow-destructive` in CI permanently, and from then on the gate is
|
|
11
|
+
* decorative — every later migration is pre-approved by a decision nobody
|
|
12
|
+
* revisits, and nothing about it shows up in review.
|
|
13
|
+
*
|
|
14
|
+
* A pragma authorizes a *file*. It is written by the person writing the
|
|
15
|
+
* destructive statement, at the moment they write it; it appears in the diff,
|
|
16
|
+
* so it is reviewed; it is versioned, so `git log` says who approved which
|
|
17
|
+
* drop; and it cannot go blanket — a pragma in 0065 says nothing about 0071.
|
|
18
|
+
* The deploy command stays a bare `pipework migrate` forever, which means
|
|
19
|
+
* there is no flag to erode.
|
|
20
|
+
*
|
|
21
|
+
* The migration tracker records a content hash per applied migration and
|
|
22
|
+
* refuses edited-but-applied files. Only `expect-*` lines are ignored by that
|
|
23
|
+
* hash, so annotating history is allowed and nothing else about it is.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export const PRAGMA_PREFIX = '-- pipework:'
|
|
27
|
+
|
|
28
|
+
/** Hazard classes that require a per-migration declaration. */
|
|
29
|
+
export type PragmaKind = 'destructive' | 'locking' | 'dynamic'
|
|
30
|
+
|
|
31
|
+
export const PRAGMA_FOR: Record<PragmaKind, string> = {
|
|
32
|
+
destructive: 'pipework:expect-destructive',
|
|
33
|
+
locking: 'pipework:expect-locking',
|
|
34
|
+
dynamic: 'pipework:expect-dynamic',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* True when `sql` declares the given pragma. Pragmas are recognized only on
|
|
39
|
+
* their own comment line, so prose that happens to mention one does not
|
|
40
|
+
* authorize anything.
|
|
41
|
+
*/
|
|
42
|
+
export function hasPragma(sql: string, kind: PragmaKind): boolean {
|
|
43
|
+
const wanted = PRAGMA_FOR[kind]
|
|
44
|
+
for (const line of sql.split('\n')) {
|
|
45
|
+
if (pragmaOfLine(line) === wanted) return true
|
|
46
|
+
}
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const DECLARED = new Set<string>(Object.values(PRAGMA_FOR))
|
|
51
|
+
|
|
52
|
+
/** The pragma a single line declares, or undefined when it declares none. */
|
|
53
|
+
function pragmaOfLine(line: string): string | undefined {
|
|
54
|
+
const trimmed = line.trim()
|
|
55
|
+
if (!trimmed.startsWith('--')) return undefined
|
|
56
|
+
return trimmed.slice(2).trim()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `sql` with every `expect-*` pragma line removed, line terminator included.
|
|
61
|
+
* A pragma declares a hazard; it does not change what the migration does, so
|
|
62
|
+
* the tracker's immutability check ignores it: an app may annotate an applied
|
|
63
|
+
* file so a fresh database can replay history through the gate.
|
|
64
|
+
*/
|
|
65
|
+
export function stripPragmaLines(sql: string): string {
|
|
66
|
+
const lines = sql.match(/[^\n]*\n|[^\n]+$/g) ?? []
|
|
67
|
+
return lines
|
|
68
|
+
.filter((line) => {
|
|
69
|
+
const pragma = pragmaOfLine(line)
|
|
70
|
+
return pragma === undefined || !DECLARED.has(pragma)
|
|
71
|
+
})
|
|
72
|
+
.join('')
|
|
73
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Provisions pipework's framework-owned partitioned tables — the trace family.
|
|
2
|
+
// `generate` deliberately keeps them out of application migrations (#313): a
|
|
3
|
+
// bare CREATE TABLE of a PARTITION BY RANGE table accepts no rows. pipework
|
|
4
|
+
// creates them itself — the parent tables, then their day partitions —
|
|
5
|
+
// wherever a database is provisioned: the test harness (src/test/setup.ts) and
|
|
6
|
+
// `migrate` (src/data/migrate/run.ts).
|
|
7
|
+
|
|
8
|
+
import { sql } from '../db/sql.js'
|
|
9
|
+
import { quoteIdentifier } from '../db/identifiers.js'
|
|
10
|
+
import type { DB } from '../db/index.js'
|
|
11
|
+
import type { Blueprint } from '../../core/config/schema.js'
|
|
12
|
+
import { syncTracePartitions, type TracePartitionConfig } from '../../trace/partition/index.js'
|
|
13
|
+
import { PARTITIONED_TRACE_TABLE_NAMES } from '../../trace/partition/tables.js'
|
|
14
|
+
import { AUDIT_ENTITY_INDEX_SQL } from '../../audit/schema/audit-record.js'
|
|
15
|
+
import { installAuditChain } from '../../audit/chain/install.js'
|
|
16
|
+
import { loadInternalDefinitions } from './internal-definitions.js'
|
|
17
|
+
import { buildSnapshot, EMPTY_SNAPSHOT } from './snapshot.js'
|
|
18
|
+
import type { DiffOp } from './diff.js'
|
|
19
|
+
import { emitSQL } from './sql-emit.js'
|
|
20
|
+
import { makeTablesUnlogged } from './apply.js'
|
|
21
|
+
import { installFrameworkPolicies, type FrameworkRls } from './framework-rls.js'
|
|
22
|
+
|
|
23
|
+
export interface FrameworkTablesResult {
|
|
24
|
+
/** Parent tables that did not exist and were created. */
|
|
25
|
+
created: string[]
|
|
26
|
+
/** `table.column` for every column added to a table that already existed. */
|
|
27
|
+
columnsAdded: string[]
|
|
28
|
+
/** Day partitions created. */
|
|
29
|
+
partitionsCreated: string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function frameworkTablesChanged(r: FrameworkTablesResult): boolean {
|
|
33
|
+
return r.created.length + r.columnsAdded.length + r.partitionsCreated.length > 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class FrameworkTableColumnError extends Error {
|
|
37
|
+
override readonly name = 'FrameworkTableColumnError'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create the framework-owned partitioned tables and their day partitions.
|
|
42
|
+
*
|
|
43
|
+
* Trace and audit storage rides the default database only, and only when the
|
|
44
|
+
* blueprint enables `trace` or `audit`. `valve_crossing` is framework-owned but
|
|
45
|
+
* not partitioned — it ships in the application migration like any ordinary
|
|
46
|
+
* table — so it is not provisioned here. Issue #313.
|
|
47
|
+
*
|
|
48
|
+
* Missing tables are created. A table that exists gains any column its
|
|
49
|
+
* definition has and it lacks; nothing is dropped, renamed or altered. A
|
|
50
|
+
* missing NOT NULL column with no default cannot be added to a table that has
|
|
51
|
+
* rows and fails naming the table and column. `unlogged` is for test databases;
|
|
52
|
+
* a real database gets logged tables. When the database configures `rls`, each
|
|
53
|
+
* table also gets the tenant policies of `framework-rls.ts`, replaced on every
|
|
54
|
+
* run so an existing database picks them up. Idempotent.
|
|
55
|
+
*/
|
|
56
|
+
export async function provisionFrameworkTables(
|
|
57
|
+
db: DB,
|
|
58
|
+
blueprint: Blueprint,
|
|
59
|
+
traceConfig: TracePartitionConfig,
|
|
60
|
+
isDefaultDatabase: boolean,
|
|
61
|
+
options: {
|
|
62
|
+
unlogged: boolean
|
|
63
|
+
rls?: FrameworkRls | undefined
|
|
64
|
+
/** Told as each step starts; a partition reports `step` 'partition' with `i/n name` as detail. */
|
|
65
|
+
onProgress?: ((step: string, detail?: string) => void) | undefined
|
|
66
|
+
} = { unlogged: true },
|
|
67
|
+
): Promise<FrameworkTablesResult> {
|
|
68
|
+
const result: FrameworkTablesResult = { created: [], columnsAdded: [], partitionsCreated: [] }
|
|
69
|
+
const progress = options.onProgress ?? (() => {})
|
|
70
|
+
if (!isDefaultDatabase) return result
|
|
71
|
+
if (blueprint.trace === undefined && blueprint.audit === undefined) return result
|
|
72
|
+
|
|
73
|
+
const definitions = await loadInternalDefinitions(blueprint, { isDefaultDatabase })
|
|
74
|
+
const snapshot = buildSnapshot(definitions, EMPTY_SNAPSHOT.id)
|
|
75
|
+
const tables = Object.values(snapshot.tables).filter(t => PARTITIONED_TRACE_TABLE_NAMES.has(t.name))
|
|
76
|
+
|
|
77
|
+
const live = await readLiveColumns(db, tables.map(t => t.name))
|
|
78
|
+
const createOps: DiffOp[] = []
|
|
79
|
+
const alterStatements: string[] = []
|
|
80
|
+
|
|
81
|
+
for (const table of tables) {
|
|
82
|
+
const liveColumns = live.get(table.name)
|
|
83
|
+
if (liveColumns === undefined) {
|
|
84
|
+
createOps.push({ type: 'create_table', table })
|
|
85
|
+
result.created.push(table.name)
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
for (const column of Object.values(table.columns)) {
|
|
89
|
+
if (liveColumns.has(column.name)) continue
|
|
90
|
+
if (column.notNull && column.default === undefined && column.identity === undefined && column.generated === undefined) {
|
|
91
|
+
const rows = await db.execute(sql.raw(`SELECT 1 FROM ${quoteIdentifier(table.name, 'table')} LIMIT 1`))
|
|
92
|
+
if (rows.length > 0) {
|
|
93
|
+
throw new FrameworkTableColumnError(
|
|
94
|
+
`Cannot add column "${column.name}" to framework table "${table.name}": it is NOT NULL with no default ` +
|
|
95
|
+
`and the table has rows. Backfill is a decision for the application — add the column by hand ` +
|
|
96
|
+
`(nullable, backfill, then SET NOT NULL) and run migrate again.`,
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const [statement] = emitSQL([{ type: 'add_column', tableName: table.name, tableSchema: table.schema, column }])
|
|
101
|
+
alterStatements.push(statement!.replace('ADD COLUMN', 'ADD COLUMN IF NOT EXISTS'))
|
|
102
|
+
result.columnsAdded.push(`${table.name}.${column.name}`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
progress('tables')
|
|
107
|
+
const createStatements = emitSQL(createOps)
|
|
108
|
+
for (const [i, statement] of createStatements.entries()) {
|
|
109
|
+
await db.execute(sql.raw(options.unlogged ? makeTablesUnlogged(statement) : statement))
|
|
110
|
+
progress('tables', `create ${i + 1}/${createStatements.length}`)
|
|
111
|
+
}
|
|
112
|
+
for (const [i, statement] of alterStatements.entries()) {
|
|
113
|
+
await db.execute(sql.raw(statement))
|
|
114
|
+
progress('tables', `alter ${i + 1}/${alterStatements.length}`)
|
|
115
|
+
}
|
|
116
|
+
if (tables.some(t => t.name === 'audit_record')) await db.execute(sql.raw(AUDIT_ENTITY_INDEX_SQL))
|
|
117
|
+
|
|
118
|
+
if (blueprint.audit?.chain === true) {
|
|
119
|
+
progress('audit-chain')
|
|
120
|
+
await installAuditChain(db)
|
|
121
|
+
}
|
|
122
|
+
if (options.rls !== undefined) {
|
|
123
|
+
progress('policies')
|
|
124
|
+
await installFrameworkPolicies(db, tables.map(t => t.name), options.rls)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const synced = await syncTracePartitions(db, traceConfig, undefined, {
|
|
128
|
+
onPartition: (name, index, total) => progress('partition', `${index}/${total} ${name}`),
|
|
129
|
+
})
|
|
130
|
+
result.partitionsCreated.push(...synced.created)
|
|
131
|
+
return result
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Column names of each named public table that exists; absent tables are not in the map. */
|
|
135
|
+
async function readLiveColumns(db: DB, names: string[]): Promise<Map<string, Set<string>>> {
|
|
136
|
+
const out = new Map<string, Set<string>>()
|
|
137
|
+
for (const name of names) {
|
|
138
|
+
const rows = await db.execute(sql`
|
|
139
|
+
SELECT a.attname AS name
|
|
140
|
+
FROM pg_attribute a
|
|
141
|
+
WHERE a.attrelid = to_regclass(${quoteIdentifier(name, 'table')})
|
|
142
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
143
|
+
`)
|
|
144
|
+
if (rows.length > 0) out.set(name, new Set(rows.map(r => String(r['name']))))
|
|
145
|
+
}
|
|
146
|
+
return out
|
|
147
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { DefinedTable, FieldKind } from '../domain/types.js'
|
|
2
|
+
import { camelToSnake } from '../domain/naming.js'
|
|
3
|
+
import { tenantIsolationPolicy, type TenantKeyType } from '../../auth/tenant/rls.js'
|
|
4
|
+
|
|
5
|
+
// A fresh matcher per call. A `g` regex carries lastIndex; sharing one instance
|
|
6
|
+
// corrupts the next call when a previous run exits mid-iteration (e.g.
|
|
7
|
+
// tenantKeyTypeForKind throws), making exec() resume mid-string and silently
|
|
8
|
+
// skip tables — dropping their RLS. Each call gets its own.
|
|
9
|
+
const createTableMatcher = (): RegExp => /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"([^"]+)"|([a-z_][a-z0-9_]*))/gi
|
|
10
|
+
|
|
11
|
+
export function generateRlsStatements(
|
|
12
|
+
migrationSql: string,
|
|
13
|
+
definitions: ReadonlyMap<string, DefinedTable>,
|
|
14
|
+
sessionVar: string,
|
|
15
|
+
force: boolean = true,
|
|
16
|
+
): string {
|
|
17
|
+
const rlsBlocks: string[] = []
|
|
18
|
+
let match: RegExpExecArray | null
|
|
19
|
+
|
|
20
|
+
const createTableRe = createTableMatcher()
|
|
21
|
+
while ((match = createTableRe.exec(migrationSql)) !== null) {
|
|
22
|
+
const tableName = match[1] ?? match[2]
|
|
23
|
+
if (tableName === undefined) continue
|
|
24
|
+
|
|
25
|
+
const definition = definitions.get(tableName)
|
|
26
|
+
if (definition === undefined) continue
|
|
27
|
+
|
|
28
|
+
const tenantFieldKey = Object.keys(definition.$fields).find(
|
|
29
|
+
(key) => definition.$fields[key]!._meta.tenant === true,
|
|
30
|
+
)
|
|
31
|
+
if (tenantFieldKey === undefined) continue
|
|
32
|
+
|
|
33
|
+
const tenantField = definition.$fields[tenantFieldKey]!
|
|
34
|
+
const columnName = camelToSnake(tenantFieldKey)
|
|
35
|
+
const keyType = tenantKeyTypeForKind(tenantField._meta.kind, tableName, tenantFieldKey)
|
|
36
|
+
|
|
37
|
+
if (migrationSql.includes(`"${tableName}" ENABLE ROW LEVEL SECURITY`) ||
|
|
38
|
+
migrationSql.includes(`${tableName} ENABLE ROW LEVEL SECURITY`)) {
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
rlsBlocks.push(tenantIsolationPolicy(tableName, columnName, keyType, sessionVar, force))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (rlsBlocks.length === 0) return migrationSql
|
|
46
|
+
|
|
47
|
+
return migrationSql + '\n\n' + rlsBlocks.join(';\n\n') + ';'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const TENANT_KEY_KINDS: ReadonlySet<string> = new Set(['uuid', 'text', 'integer', 'bigint'])
|
|
51
|
+
|
|
52
|
+
function tenantKeyTypeForKind(kind: FieldKind, table: string, field: string): TenantKeyType {
|
|
53
|
+
if (TENANT_KEY_KINDS.has(kind)) return kind as TenantKeyType
|
|
54
|
+
throw new Error(
|
|
55
|
+
`[pipework] Field "${field}" on table "${table}" is marked .tenant() but its kind is '${kind}'.\n\n` +
|
|
56
|
+
` A tenant discriminator must be uuid, text, integer, or bigint — the generated RLS\n` +
|
|
57
|
+
` policy casts current_setting() to that type. Mark the uuid/text tenant key with\n` +
|
|
58
|
+
` .tenant(), not a '${kind}' column.`,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { sql } from '../query/index.js'
|
|
2
|
+
import type { Manifold } from '../../core/pipework.js'
|
|
3
|
+
import type { ResolvedDatabase } from '../../core/config/index.js'
|
|
4
|
+
import { quoteIdentifier } from '../db/identifiers.js'
|
|
5
|
+
import { OWNER_LABEL_MIGRATE } from '../db/owner-labels.js'
|
|
6
|
+
import { checkMigrations, type MigrationCheckResult } from './check.js'
|
|
7
|
+
import { readAppliedState } from './applied.js'
|
|
8
|
+
import { applyMigrations } from './apply.js'
|
|
9
|
+
import { provisionFrameworkTables, type FrameworkTablesResult } from './provision-framework.js'
|
|
10
|
+
|
|
11
|
+
// Deliberately empty of hazard switches. Authorization for a hazardous
|
|
12
|
+
// migration is declared in that migration's own file (see pragma.ts) — an
|
|
13
|
+
// option here would be an invocation-level approval, which is exactly the
|
|
14
|
+
// thing that gets written into a deploy workflow once and never revisited.
|
|
15
|
+
export interface MigrateOptions {}
|
|
16
|
+
|
|
17
|
+
export interface MigrateResult {
|
|
18
|
+
database: string
|
|
19
|
+
migrationsRun: number
|
|
20
|
+
extensions: string[]
|
|
21
|
+
safetyCheck: MigrationCheckResult
|
|
22
|
+
/** No-transaction files picked up half applied and finished. Issue #378. */
|
|
23
|
+
resumed: ResumedMigration[]
|
|
24
|
+
/** Trace/audit tables, columns and day partitions this run provisioned. */
|
|
25
|
+
frameworkTables: FrameworkTablesResult
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ResumedMigration {
|
|
29
|
+
tag: string
|
|
30
|
+
/** 1-based statement the rerun started from. */
|
|
31
|
+
fromStatement: number
|
|
32
|
+
totalStatements: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class UnsafeMigrationError extends Error {
|
|
36
|
+
override readonly name = 'UnsafeMigrationError'
|
|
37
|
+
constructor(
|
|
38
|
+
public readonly check: MigrationCheckResult,
|
|
39
|
+
message: string,
|
|
40
|
+
) {
|
|
41
|
+
super(message)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function migrateAll(instance: Manifold, options: MigrateOptions = {}): Promise<MigrateResult[]> {
|
|
46
|
+
const results: MigrateResult[] = []
|
|
47
|
+
|
|
48
|
+
for (const [, dbConfig] of instance.config.databases()) {
|
|
49
|
+
if (dbConfig.migrations === undefined) continue
|
|
50
|
+
const result = await migrateOne(instance, dbConfig, options)
|
|
51
|
+
results.push(result)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return results
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function migrateOne(instance: Manifold, dbConfig: ResolvedDatabase, _options: MigrateOptions = {}): Promise<MigrateResult> {
|
|
58
|
+
const conn = instance.pool.getOrCreate(dbConfig, 'owner', OWNER_LABEL_MIGRATE)
|
|
59
|
+
|
|
60
|
+
// Scope the safety gate to pending migrations only — hazards in already-applied
|
|
61
|
+
// migrations are history, not a decision the operator still gets to make.
|
|
62
|
+
// Without this, a fully-current database with a destructive op anywhere in its
|
|
63
|
+
// history can never run a clean no-op `migrate`.
|
|
64
|
+
const safetyCheck = dbConfig.migrations !== undefined
|
|
65
|
+
? checkMigrations(dbConfig.migrations, await readAppliedState(conn.drizzle, dbConfig.migrationTable))
|
|
66
|
+
: { safe: true, hasDestructive: false, hasLocking: false, hasDynamic: false, declared: true, fileHazards: [] } as MigrationCheckResult
|
|
67
|
+
|
|
68
|
+
if (!safetyCheck.declared) {
|
|
69
|
+
const files = safetyCheck.fileHazards.filter(f => f.undeclared.length > 0)
|
|
70
|
+
const detail = files
|
|
71
|
+
.map(f => `${f.file} (${f.undeclared.join(', ')})`)
|
|
72
|
+
.join(', ')
|
|
73
|
+
throw new UnsafeMigrationError(
|
|
74
|
+
safetyCheck,
|
|
75
|
+
`Migration for "${dbConfig.name}" raises hazards that its own file does not declare: ${detail}. ` +
|
|
76
|
+
`Add the matching \`-- pipework:expect-*\` pragma to each migration and commit it.`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let extensionsCreated: string[] = []
|
|
81
|
+
|
|
82
|
+
if (dbConfig.extensions.length > 0) {
|
|
83
|
+
for (const ext of dbConfig.extensions) {
|
|
84
|
+
await conn.drizzle.execute(sql.raw(`CREATE EXTENSION IF NOT EXISTS ${quoteIdentifier(ext, 'extension')}`))
|
|
85
|
+
}
|
|
86
|
+
extensionsCreated = [...dbConfig.extensions]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const resumed: ResumedMigration[] = []
|
|
90
|
+
const migrationsRun = await applyMigrations({
|
|
91
|
+
migrationsFolder: dbConfig.migrations!,
|
|
92
|
+
migrationsTable: dbConfig.migrationTable,
|
|
93
|
+
connectionUrl: dbConfig.url,
|
|
94
|
+
onResume: (tag, fromStatement, totalStatements) => resumed.push({ tag, fromStatement, totalStatements }),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Trace and audit storage rides the default database; their partitioned
|
|
98
|
+
// tables are kept out of generated migrations (#313), so migrate provisions them.
|
|
99
|
+
const frameworkTables = await provisionFrameworkTables(
|
|
100
|
+
conn.drizzle,
|
|
101
|
+
instance.config.raw,
|
|
102
|
+
{
|
|
103
|
+
hotRetentionDays: instance.config.trace.hotRetentionDays,
|
|
104
|
+
errorRetentionDays: instance.config.trace.errorRetentionDays,
|
|
105
|
+
auditRetentionDays: instance.config.audit.retentionDays === 'keep' ? null : instance.config.audit.retentionDays,
|
|
106
|
+
partitionLeadDays: instance.config.trace.partitionLeadDays,
|
|
107
|
+
},
|
|
108
|
+
wantsFrameworkTables(instance) && dbConfig.name === instance.config.defaultDatabaseName(),
|
|
109
|
+
{ unlogged: false, rls: dbConfig.rls },
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
database: dbConfig.name,
|
|
114
|
+
migrationsRun,
|
|
115
|
+
extensions: extensionsCreated,
|
|
116
|
+
safetyCheck,
|
|
117
|
+
resumed,
|
|
118
|
+
frameworkTables,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function wantsFrameworkTables(instance: Manifold): boolean {
|
|
123
|
+
return instance.config.raw.trace !== undefined || instance.config.raw.audit !== undefined
|
|
124
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { scanStatements, blankLiterals } from './statements.js'
|
|
2
|
+
|
|
3
|
+
export type Severity = 'destructive' | 'locking' | 'dynamic' | 'safe'
|
|
4
|
+
|
|
5
|
+
export interface MigrationHazard {
|
|
6
|
+
severity: Severity
|
|
7
|
+
operation: string
|
|
8
|
+
statement: string
|
|
9
|
+
suggestion: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Rule {
|
|
13
|
+
pattern: RegExp
|
|
14
|
+
severity: Severity
|
|
15
|
+
operation: string
|
|
16
|
+
suggestion: string
|
|
17
|
+
except?: RegExp
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const RULES: Rule[] = [
|
|
21
|
+
// ── Destructive (data loss / backward-incompatible) ───────────────────
|
|
22
|
+
{
|
|
23
|
+
pattern: /\bDROP\s+TABLE\b/i,
|
|
24
|
+
severity: 'destructive',
|
|
25
|
+
operation: 'DROP TABLE',
|
|
26
|
+
suggestion: 'Rename the table first, deploy, verify no access, then drop in a follow-up migration.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
pattern: /\bDROP\s+COLUMN\b/i,
|
|
30
|
+
severity: 'destructive',
|
|
31
|
+
operation: 'DROP COLUMN',
|
|
32
|
+
suggestion: 'Stop reading the column in application code first, then drop in a follow-up migration.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
pattern: /\bALTER\s+COLUMN\b[^;]*\bSET\s+NOT\s+NULL\b/i,
|
|
36
|
+
severity: 'destructive',
|
|
37
|
+
operation: 'SET NOT NULL',
|
|
38
|
+
suggestion: 'Add a CHECK constraint with NOT VALID, validate it separately, then convert to NOT NULL.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
pattern: /\bALTER\s+COLUMN\b[^;]*\bTYPE\b/i,
|
|
42
|
+
severity: 'destructive',
|
|
43
|
+
operation: 'ALTER COLUMN TYPE',
|
|
44
|
+
suggestion: 'Add a new column with the target type, backfill, swap reads, then drop the old column.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
pattern: /\bTRUNCATE\b/i,
|
|
48
|
+
severity: 'destructive',
|
|
49
|
+
operation: 'TRUNCATE',
|
|
50
|
+
suggestion: 'Use DELETE with a WHERE clause if you need to remove rows in a migration.',
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// ── Locking (acquires heavy locks, risks downtime under load) ─────────
|
|
54
|
+
{
|
|
55
|
+
pattern: /\bCREATE\s+(?:UNIQUE\s+)?INDEX\b(?!\s+CONCURRENTLY)/i,
|
|
56
|
+
severity: 'locking',
|
|
57
|
+
operation: 'CREATE INDEX (non-concurrent)',
|
|
58
|
+
suggestion: 'Use CREATE INDEX CONCURRENTLY to avoid blocking writes.',
|
|
59
|
+
except: /\bCONCURRENTLY\b/i,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
pattern: /\bRENAME\s+COLUMN\b/i,
|
|
63
|
+
severity: 'locking',
|
|
64
|
+
operation: 'RENAME COLUMN',
|
|
65
|
+
suggestion: 'Add a new column, backfill, migrate reads, then drop the old column.',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
pattern: /\bRENAME\s+TABLE\b/i,
|
|
69
|
+
severity: 'locking',
|
|
70
|
+
operation: 'RENAME TABLE',
|
|
71
|
+
suggestion: 'Create a new table, migrate data, swap reads, then drop the old table.',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
pattern: /\bRENAME\s+TO\b/i,
|
|
75
|
+
severity: 'locking',
|
|
76
|
+
operation: 'RENAME TO',
|
|
77
|
+
suggestion: 'Renames acquire AccessExclusive locks. Consider a multi-step migration instead.',
|
|
78
|
+
except: /\bRENAME\s+COLUMN\b|\bRENAME\s+TABLE\b|\bALTER\s+INDEX\b/i,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
pattern: /\bADD\s+CONSTRAINT\b[^;]*\bFOREIGN\s+KEY\b(?![^;]*\bNOT\s+VALID\b)/i,
|
|
82
|
+
severity: 'locking',
|
|
83
|
+
operation: 'ADD FOREIGN KEY (without NOT VALID)',
|
|
84
|
+
suggestion: 'Add the constraint with NOT VALID, then VALIDATE CONSTRAINT in a separate statement.',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
// Locking rather than destructive: on a non-empty table this fails loudly
|
|
88
|
+
// instead of losing data, so it aborts the deploy rather than silently
|
|
89
|
+
// damaging it. It is still a gate-worthy hazard, hence the rule (#334).
|
|
90
|
+
pattern: /\bADD\s+COLUMN\b[^;]*\bNOT\s+NULL\b/i,
|
|
91
|
+
severity: 'locking',
|
|
92
|
+
operation: 'ADD COLUMN NOT NULL (without DEFAULT)',
|
|
93
|
+
suggestion:
|
|
94
|
+
'Add the column nullable, backfill, then SET NOT NULL — or give it a DEFAULT so existing rows can be filled.',
|
|
95
|
+
except: /\bDEFAULT\b/i,
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// ── Dynamic (SQL assembled at runtime — unanalyzable) ────────────────
|
|
99
|
+
{
|
|
100
|
+
// The gate reads text; `EXECUTE` runs text that does not exist yet. Rather
|
|
101
|
+
// than pretend to see through it, name it. This is also what lets string
|
|
102
|
+
// literals be treated as the inert data they almost always are: a literal
|
|
103
|
+
// is dangerous exactly when something EXECUTEs it, and that is structural
|
|
104
|
+
// rather than a guess about its contents (#334). `EXECUTE FUNCTION|PROCEDURE`
|
|
105
|
+
// (triggers) and `EXECUTE ON` (GRANT/REVOKE) name an existing function or
|
|
106
|
+
// privilege and build no SQL, so they are not this (#480).
|
|
107
|
+
pattern: /\bEXECUTE\b(?!\s+(?:FUNCTION|PROCEDURE|ON)\b)/i,
|
|
108
|
+
severity: 'dynamic',
|
|
109
|
+
operation: 'dynamic SQL (EXECUTE)',
|
|
110
|
+
suggestion:
|
|
111
|
+
'The gate cannot analyze SQL built at runtime. Prefer static DDL; if runtime assembly is required, declare it in the migration.',
|
|
112
|
+
},
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
export function analyzeSQL(sql: string): MigrationHazard[] {
|
|
116
|
+
const statements = scanStatements(sql)
|
|
117
|
+
const hazards: MigrationHazard[] = []
|
|
118
|
+
|
|
119
|
+
for (const stmt of statements) {
|
|
120
|
+
// Rules match against the statement with string-literal contents blanked:
|
|
121
|
+
// literals are data, and DDL keywords sitting in seed data execute
|
|
122
|
+
// nothing. Dollar-quoted bodies stay intact — those are code.
|
|
123
|
+
const subject = blankLiterals(stmt)
|
|
124
|
+
|
|
125
|
+
for (const rule of RULES) {
|
|
126
|
+
if (!rule.pattern.test(subject)) continue
|
|
127
|
+
if (rule.except !== undefined && rule.except.test(subject)) continue
|
|
128
|
+
|
|
129
|
+
hazards.push({
|
|
130
|
+
severity: rule.severity,
|
|
131
|
+
operation: rule.operation,
|
|
132
|
+
statement: stmt.trim().slice(0, 200),
|
|
133
|
+
suggestion: rule.suggestion,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return hazards
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function formatHazards(hazards: MigrationHazard[], file: string): string {
|
|
142
|
+
const lines: string[] = []
|
|
143
|
+
|
|
144
|
+
lines.push(` ${file}:`)
|
|
145
|
+
|
|
146
|
+
const label: Record<string, string> = {
|
|
147
|
+
destructive: '✗ DESTRUCTIVE',
|
|
148
|
+
locking: '⚠ LOCKING',
|
|
149
|
+
dynamic: '? DYNAMIC',
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const severity of ['destructive', 'locking', 'dynamic'] as const) {
|
|
153
|
+
for (const h of hazards.filter(x => x.severity === severity)) {
|
|
154
|
+
lines.push(` ${label[severity]}: ${h.operation}`)
|
|
155
|
+
lines.push(` ${truncate(h.statement, 120)}`)
|
|
156
|
+
lines.push(` → ${h.suggestion}`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return lines.join('\n')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function truncate(s: string, max: number): string {
|
|
164
|
+
const oneLine = s.replace(/\s+/g, ' ')
|
|
165
|
+
if (oneLine.length <= max) return oneLine
|
|
166
|
+
return oneLine.slice(0, max - 3) + '...'
|
|
167
|
+
}
|