pipework 0.252.0 → 0.253.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/infra/support/handle.d.ts +8 -1
- package/dist/infra/support/handle.d.ts.map +1 -1
- package/dist/infra/support/handle.js +9 -2
- package/dist/infra/support/handle.js.map +1 -1
- package/dist/infra/support/namespace.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/async/jobs/abortable-delay.ts +18 -0
- package/src/async/jobs/claims.ts +52 -0
- package/src/async/jobs/cron.ts +179 -0
- package/src/async/jobs/errors.ts +56 -0
- package/src/async/jobs/exclusion.ts +117 -0
- package/src/async/jobs/execute.ts +219 -0
- package/src/async/jobs/index.ts +10 -0
- package/src/async/jobs/listener.ts +135 -0
- package/src/async/jobs/namespace.ts +20 -0
- package/src/async/jobs/queue-door.ts +44 -0
- package/src/async/jobs/queue.ts +1012 -0
- package/src/async/jobs/run-script.ts +67 -0
- package/src/async/jobs/scheduler.ts +218 -0
- package/src/async/jobs/schedules-table.ts +37 -0
- package/src/async/jobs/serialize-error.ts +48 -0
- package/src/async/jobs/table.ts +191 -0
- package/src/async/pipeline/define.ts +53 -0
- package/src/async/pipeline/errors.ts +35 -0
- package/src/async/pipeline/execute.ts +53 -0
- package/src/async/pipeline/index.ts +13 -0
- package/src/async/pipeline/namespace.ts +12 -0
- package/src/async/pipeline/resume.ts +67 -0
- package/src/async/pipeline/run-steps.ts +98 -0
- package/src/async/pipeline/types.ts +57 -0
- package/src/async/state-machine/define.ts +64 -0
- package/src/async/state-machine/errors.ts +29 -0
- package/src/async/state-machine/index.ts +3 -0
- package/src/async/state-machine/namespace.ts +12 -0
- package/src/async/state-machine/transition.ts +90 -0
- package/src/async/state-machine/types.ts +37 -0
- package/src/async/steps/errors.ts +31 -0
- package/src/async/steps/graph.ts +1214 -0
- package/src/async/steps/hash.ts +40 -0
- package/src/async/steps/index.ts +7 -0
- package/src/async/steps/namespace.ts +7 -0
- package/src/async/steps/table.ts +97 -0
- package/src/async/steps/types.ts +318 -0
- package/src/audit/chain/index.ts +9 -0
- package/src/audit/chain/install.ts +113 -0
- package/src/audit/chain/retire.ts +52 -0
- package/src/audit/chain/verify.ts +257 -0
- package/src/audit/query/cursor.ts +37 -0
- package/src/audit/query/index.ts +9 -0
- package/src/audit/query/query.ts +223 -0
- package/src/audit/schema/audit-record-fields.ts +68 -0
- package/src/audit/schema/audit-record.ts +23 -0
- package/src/auth/auth/audit.ts +31 -0
- package/src/auth/auth/chain.ts +84 -0
- package/src/auth/auth/cookie.ts +136 -0
- package/src/auth/auth/email-links.ts +311 -0
- package/src/auth/auth/guard.ts +128 -0
- package/src/auth/auth/index.ts +18 -0
- package/src/auth/auth/multi-org.ts +337 -0
- package/src/auth/auth/namespace.ts +34 -0
- package/src/auth/auth/parse-expiry.ts +13 -0
- package/src/auth/auth/passwords.ts +110 -0
- package/src/auth/auth/schema.ts +110 -0
- package/src/auth/auth/sessions.ts +565 -0
- package/src/auth/auth/sign-in.ts +156 -0
- package/src/auth/auth/signing-keys.ts +69 -0
- package/src/auth/auth/types.ts +26 -0
- package/src/auth/auth/uuid.ts +10 -0
- package/src/auth/rbac/cache.ts +55 -0
- package/src/auth/rbac/check.ts +30 -0
- package/src/auth/rbac/create.ts +35 -0
- package/src/auth/rbac/index.ts +5 -0
- package/src/auth/rbac/middleware.ts +20 -0
- package/src/auth/rbac/namespace.ts +15 -0
- package/src/auth/rbac/resolve.ts +31 -0
- package/src/auth/rbac/schema.sql +31 -0
- package/src/auth/rbac/seed.ts +67 -0
- package/src/auth/rbac/tables.ts +32 -0
- package/src/auth/rbac/types.ts +65 -0
- package/src/auth/tenant/extract.ts +33 -0
- package/src/auth/tenant/in-tenant.ts +49 -0
- package/src/auth/tenant/index.ts +7 -0
- package/src/auth/tenant/namespace.ts +23 -0
- package/src/auth/tenant/propagate.ts +20 -0
- package/src/auth/tenant/rls.ts +120 -0
- package/src/auth/tenant/scope.ts +33 -0
- package/src/auth/tenant/scoped-db.ts +581 -0
- package/src/auth/tenant/types.ts +14 -0
- package/src/auth/tenant/validate.ts +27 -0
- package/src/cli/commands/add.ts +158 -0
- package/src/cli/commands/alerts.ts +98 -0
- package/src/cli/commands/check.ts +1418 -0
- package/src/cli/commands/codemod.ts +78 -0
- package/src/cli/commands/db.ts +356 -0
- package/src/cli/commands/dev.ts +101 -0
- package/src/cli/commands/generate.ts +75 -0
- package/src/cli/commands/init.ts +36 -0
- package/src/cli/commands/install.ts +28 -0
- package/src/cli/commands/lint.ts +62 -0
- package/src/cli/commands/migrate.ts +132 -0
- package/src/cli/commands/openapi.ts +63 -0
- package/src/cli/commands/remove.ts +72 -0
- package/src/cli/commands/run.ts +50 -0
- package/src/cli/commands/scope.ts +54 -0
- package/src/cli/commands/serve.ts +68 -0
- package/src/cli/commands/shard.ts +31 -0
- package/src/cli/commands/shared/config-pipework-version.ts +28 -0
- package/src/cli/commands/shared/find-config.ts +22 -0
- package/src/cli/commands/shared/load-config.ts +35 -0
- package/src/cli/commands/shared/pnpm-install.ts +95 -0
- package/src/cli/commands/shared/report-removed.ts +8 -0
- package/src/cli/commands/sync.ts +32 -0
- package/src/cli/commands/test.ts +307 -0
- package/src/cli/commands/upgrade.ts +48 -0
- package/src/cli/dev-bootstrap.ts +24 -0
- package/src/cli/errors.ts +10 -0
- package/src/cli/index.ts +303 -0
- package/src/cli/output.ts +17 -0
- package/src/cli/port.ts +15 -0
- package/src/cli/surface-kinds.ts +20 -0
- package/src/client/index.ts +4 -0
- package/src/client/poll.ts +130 -0
- package/src/client/report-fault.ts +119 -0
- package/src/core/config/app-pool.ts +19 -0
- package/src/core/config/aws-env.ts +29 -0
- package/src/core/config/base-dir.ts +24 -0
- package/src/core/config/discover.ts +64 -0
- package/src/core/config/env-resolve.ts +87 -0
- package/src/core/config/env-types.ts +116 -0
- package/src/core/config/env.ts +55 -0
- package/src/core/config/errors.ts +9 -0
- package/src/core/config/import-config.ts +40 -0
- package/src/core/config/index.ts +8 -0
- package/src/core/config/load.ts +417 -0
- package/src/core/config/namespace.ts +14 -0
- package/src/core/config/resolve.ts +195 -0
- package/src/core/config/schema.ts +488 -0
- package/src/core/config/statement-observer.ts +48 -0
- package/src/core/config/ts-inline-hooks.ts +75 -0
- package/src/core/config/ts-register.ts +4 -0
- package/src/core/config/ts-resolve-hooks.ts +101 -0
- package/src/core/errors/base.ts +49 -0
- package/src/core/errors/http.ts +146 -0
- package/src/core/errors/index.ts +2 -0
- package/src/core/errors/namespace.ts +31 -0
- package/src/core/lifecycle/drain.ts +33 -0
- package/src/core/lifecycle/health.ts +72 -0
- package/src/core/lifecycle/index.ts +4 -0
- package/src/core/lifecycle/namespace.ts +7 -0
- package/src/core/lifecycle/startup.ts +102 -0
- package/src/core/lifecycle/types.ts +36 -0
- package/src/core/logging/context.ts +60 -0
- package/src/core/logging/error-chain.ts +68 -0
- package/src/core/logging/index.ts +5 -0
- package/src/core/logging/logger.ts +65 -0
- package/src/core/logging/namespace.ts +16 -0
- package/src/core/logging/proxy.ts +22 -0
- package/src/core/logging/redact-match.ts +24 -0
- package/src/core/logging/types.ts +47 -0
- package/src/core/pipework.ts +566 -0
- package/src/core/runtime/broker.ts +69 -0
- package/src/core/surface/http.ts +133 -0
- package/src/core/surface/index.ts +11 -0
- package/src/core/surface/job-timeout.ts +40 -0
- package/src/core/surface/namespace.ts +13 -0
- package/src/core/surface/script.ts +33 -0
- package/src/core/surface/start.ts +142 -0
- package/src/core/surface/types.ts +225 -0
- package/src/core/surface/validate.ts +88 -0
- package/src/core/surface/worker.ts +528 -0
- package/src/core/validation/connectivity.ts +49 -0
- package/src/core/validation/domain.ts +99 -0
- package/src/core/validation/index.ts +5 -0
- package/src/core/validation/queue-tables.ts +312 -0
- package/src/core/validation/rls-role.ts +138 -0
- package/src/core/validation/startup.ts +51 -0
- package/src/core/validation/surfaces.ts +65 -0
- package/src/core/validation/types.ts +25 -0
- package/src/data/db/advisory-lock.ts +55 -0
- package/src/data/db/aggregates.ts +172 -0
- package/src/data/db/array-elements.ts +111 -0
- package/src/data/db/bulk-set.ts +53 -0
- package/src/data/db/col/index.ts +14 -0
- package/src/data/db/composed.ts +53 -0
- package/src/data/db/db.ts +276 -0
- package/src/data/db/excluded.ts +16 -0
- package/src/data/db/expressions.ts +473 -0
- package/src/data/db/filter.ts +90 -0
- package/src/data/db/function-source.ts +114 -0
- package/src/data/db/identifiers.ts +16 -0
- package/src/data/db/idx/index.ts +9 -0
- package/src/data/db/in-list.ts +49 -0
- package/src/data/db/index.ts +9 -0
- package/src/data/db/insert-split.ts +137 -0
- package/src/data/db/instrumentation.ts +82 -0
- package/src/data/db/maintenance.ts +28 -0
- package/src/data/db/namespace.ts +107 -0
- package/src/data/db/owner-labels.ts +9 -0
- package/src/data/db/pool.ts +187 -0
- package/src/data/db/query-label.ts +13 -0
- package/src/data/db/query-watch.ts +116 -0
- package/src/data/db/raw-client-warning.ts +37 -0
- package/src/data/db/readonly.ts +23 -0
- package/src/data/db/reservation.ts +151 -0
- package/src/data/db/schema-features.ts +14 -0
- package/src/data/db/serializable.ts +83 -0
- package/src/data/db/sql.ts +1 -0
- package/src/data/db/timeouts.ts +76 -0
- package/src/data/db/types.ts +18 -0
- package/src/data/db/unbound.ts +33 -0
- package/src/data/db/values.ts +29 -0
- package/src/data/domain/brand.ts +15 -0
- package/src/data/domain/build-factory.ts +193 -0
- package/src/data/domain/build-schema.ts +161 -0
- package/src/data/domain/build-table.ts +355 -0
- package/src/data/domain/define.ts +239 -0
- package/src/data/domain/external-registry.ts +34 -0
- package/src/data/domain/external.ts +77 -0
- package/src/data/domain/field.ts +279 -0
- package/src/data/domain/index.ts +13 -0
- package/src/data/domain/naming.ts +6 -0
- package/src/data/domain/projections.ts +57 -0
- package/src/data/domain/trace-safe-projection.ts +31 -0
- package/src/data/domain/traits.ts +108 -0
- package/src/data/domain/types.ts +289 -0
- package/src/data/jsonb/composed.ts +18 -0
- package/src/data/jsonb/index.ts +1 -0
- package/src/data/jsonb/namespace.ts +30 -0
- package/src/data/jsonb/query.ts +86 -0
- package/src/data/migrate/append-only-generate.ts +47 -0
- package/src/data/migrate/applied.ts +47 -0
- package/src/data/migrate/apply.ts +309 -0
- package/src/data/migrate/check.ts +119 -0
- package/src/data/migrate/diff.ts +357 -0
- package/src/data/migrate/door-generate.ts +130 -0
- package/src/data/migrate/framework-rls.ts +58 -0
- package/src/data/migrate/generate.ts +201 -0
- package/src/data/migrate/history.ts +215 -0
- package/src/data/migrate/index.ts +9 -0
- package/src/data/migrate/internal-definitions.ts +175 -0
- package/src/data/migrate/post-process.ts +73 -0
- package/src/data/migrate/pragma.ts +73 -0
- package/src/data/migrate/provision-framework.ts +147 -0
- package/src/data/migrate/rls-generate.ts +61 -0
- package/src/data/migrate/run.ts +124 -0
- package/src/data/migrate/safety.ts +167 -0
- package/src/data/migrate/snapshot.ts +368 -0
- package/src/data/migrate/sql-emit.ts +392 -0
- package/src/data/migrate/statements.ts +312 -0
- package/src/data/query/alias.ts +126 -0
- package/src/data/query/batch.ts +8 -0
- package/src/data/query/cache/core/cache.ts +78 -0
- package/src/data/query/cache/core/index.ts +1 -0
- package/src/data/query/cache/core/types.ts +29 -0
- package/src/data/query/cache/readme.md +230 -0
- package/src/data/query/casing.ts +76 -0
- package/src/data/query/collation.ts +11 -0
- package/src/data/query/column-builder.ts +370 -0
- package/src/data/query/column.ts +150 -0
- package/src/data/query/entity.ts +42 -0
- package/src/data/query/errors.ts +33 -0
- package/src/data/query/index.ts +14 -0
- package/src/data/query/logger.ts +47 -0
- package/src/data/query/operations.ts +45 -0
- package/src/data/query/pg-core/alias.ts +12 -0
- package/src/data/query/pg-core/checks.ts +32 -0
- package/src/data/query/pg-core/columns/all.ts +71 -0
- package/src/data/query/pg-core/columns/bigint.ts +110 -0
- package/src/data/query/pg-core/columns/bigserial.ts +128 -0
- package/src/data/query/pg-core/columns/boolean.ts +43 -0
- package/src/data/query/pg-core/columns/char.ts +85 -0
- package/src/data/query/pg-core/columns/cidr.ts +43 -0
- package/src/data/query/pg-core/columns/common.ts +366 -0
- package/src/data/query/pg-core/columns/custom.ts +232 -0
- package/src/data/query/pg-core/columns/date.common.ts +15 -0
- package/src/data/query/pg-core/columns/date.ts +112 -0
- package/src/data/query/pg-core/columns/double-precision.ts +55 -0
- package/src/data/query/pg-core/columns/enum.ts +201 -0
- package/src/data/query/pg-core/columns/index.ts +35 -0
- package/src/data/query/pg-core/columns/inet.ts +43 -0
- package/src/data/query/pg-core/columns/int.common.ts +57 -0
- package/src/data/query/pg-core/columns/integer.ts +53 -0
- package/src/data/query/pg-core/columns/interval.ts +83 -0
- package/src/data/query/pg-core/columns/json.ts +58 -0
- package/src/data/query/pg-core/columns/jsonb.ts +56 -0
- package/src/data/query/pg-core/columns/line.ts +117 -0
- package/src/data/query/pg-core/columns/macaddr.ts +43 -0
- package/src/data/query/pg-core/columns/macaddr8.ts +43 -0
- package/src/data/query/pg-core/columns/numeric.ts +227 -0
- package/src/data/query/pg-core/columns/point.ts +127 -0
- package/src/data/query/pg-core/columns/postgis_extension/geometry.ts +119 -0
- package/src/data/query/pg-core/columns/postgis_extension/utils.ts +47 -0
- package/src/data/query/pg-core/columns/real.ts +58 -0
- package/src/data/query/pg-core/columns/serial.ts +55 -0
- package/src/data/query/pg-core/columns/smallint.ts +53 -0
- package/src/data/query/pg-core/columns/smallserial.ts +60 -0
- package/src/data/query/pg-core/columns/text.ts +67 -0
- package/src/data/query/pg-core/columns/time.ts +72 -0
- package/src/data/query/pg-core/columns/timestamp.ts +163 -0
- package/src/data/query/pg-core/columns/uuid.ts +51 -0
- package/src/data/query/pg-core/columns/varchar.ts +89 -0
- package/src/data/query/pg-core/columns/vector_extension/bit.ts +68 -0
- package/src/data/query/pg-core/columns/vector_extension/halfvec.ts +80 -0
- package/src/data/query/pg-core/columns/vector_extension/sparsevec.ts +67 -0
- package/src/data/query/pg-core/columns/vector_extension/vector.ts +80 -0
- package/src/data/query/pg-core/db.ts +674 -0
- package/src/data/query/pg-core/dialect.ts +1404 -0
- package/src/data/query/pg-core/expressions.ts +25 -0
- package/src/data/query/pg-core/foreign-keys.ts +130 -0
- package/src/data/query/pg-core/index.ts +21 -0
- package/src/data/query/pg-core/indexes.ts +276 -0
- package/src/data/query/pg-core/policies.ts +56 -0
- package/src/data/query/pg-core/primary-keys.ts +62 -0
- package/src/data/query/pg-core/query-builders/count.ts +86 -0
- package/src/data/query/pg-core/query-builders/delete.ts +293 -0
- package/src/data/query/pg-core/query-builders/index.ts +7 -0
- package/src/data/query/pg-core/query-builders/insert.ts +452 -0
- package/src/data/query/pg-core/query-builders/query-builder.ts +123 -0
- package/src/data/query/pg-core/query-builders/query.ts +157 -0
- package/src/data/query/pg-core/query-builders/raw.ts +49 -0
- package/src/data/query/pg-core/query-builders/refresh-materialized-view.ts +108 -0
- package/src/data/query/pg-core/query-builders/select.ts +1346 -0
- package/src/data/query/pg-core/query-builders/select.types.ts +437 -0
- package/src/data/query/pg-core/query-builders/update.ts +626 -0
- package/src/data/query/pg-core/roles.ts +41 -0
- package/src/data/query/pg-core/schema.ts +72 -0
- package/src/data/query/pg-core/sequence.ts +41 -0
- package/src/data/query/pg-core/session.ts +292 -0
- package/src/data/query/pg-core/subquery.ts +46 -0
- package/src/data/query/pg-core/table.ts +252 -0
- package/src/data/query/pg-core/unique-constraint.ts +73 -0
- package/src/data/query/pg-core/utils/array.ts +95 -0
- package/src/data/query/pg-core/utils/index.ts +1 -0
- package/src/data/query/pg-core/utils.ts +104 -0
- package/src/data/query/pg-core/view-base.ts +14 -0
- package/src/data/query/pg-core/view-common.ts +1 -0
- package/src/data/query/pg-core/view.ts +435 -0
- package/src/data/query/pg-core/with-builder.ts +41 -0
- package/src/data/query/postgres-js/README.md +46 -0
- package/src/data/query/postgres-js/array-parser.ts +45 -0
- package/src/data/query/postgres-js/driver-error-boundary.ts +149 -0
- package/src/data/query/postgres-js/driver.ts +163 -0
- package/src/data/query/postgres-js/index.ts +2 -0
- package/src/data/query/postgres-js/session.ts +234 -0
- package/src/data/query/primary-key.ts +11 -0
- package/src/data/query/query-builders/query-builder.ts +19 -0
- package/src/data/query/query-builders/select.types.ts +175 -0
- package/src/data/query/query-promise.ts +35 -0
- package/src/data/query/relations.ts +725 -0
- package/src/data/query/runnable-query.ts +12 -0
- package/src/data/query/selection-proxy.ts +121 -0
- package/src/data/query/session.ts +8 -0
- package/src/data/query/sql/expressions/conditions.ts +741 -0
- package/src/data/query/sql/expressions/index.ts +2 -0
- package/src/data/query/sql/expressions/select.ts +45 -0
- package/src/data/query/sql/functions/aggregate.ts +129 -0
- package/src/data/query/sql/functions/index.ts +2 -0
- package/src/data/query/sql/functions/vector.ts +178 -0
- package/src/data/query/sql/index.ts +3 -0
- package/src/data/query/sql/sql.ts +794 -0
- package/src/data/query/subquery.ts +53 -0
- package/src/data/query/table.ts +208 -0
- package/src/data/query/table.utils.ts +2 -0
- package/src/data/query/tracing-utils.ts +3 -0
- package/src/data/query/tracing.ts +11 -0
- package/src/data/query/utils.ts +357 -0
- package/src/data/query/version.ts +4 -0
- package/src/data/query/view-common.ts +1 -0
- package/src/data/schema/branded.ts +13 -0
- package/src/data/schema/generated/coerce.ts +5 -0
- package/src/data/schema/generated/combinator.ts +6 -0
- package/src/data/schema/generated/encoding.ts +5 -0
- package/src/data/schema/generated/error.ts +6 -0
- package/src/data/schema/generated/format.ts +5 -0
- package/src/data/schema/generated/type-constructor.ts +10 -0
- package/src/data/schema/generated/type.ts +6 -0
- package/src/data/schema/index.ts +6 -0
- package/src/data/schema/json-schema.ts +33 -0
- package/src/data/schema/namespace.ts +55 -0
- package/src/data/schema/parse.ts +39 -0
- package/src/data/schema/types.ts +39 -0
- package/src/data/temporal/append-only.ts +134 -0
- package/src/data/temporal/close.ts +27 -0
- package/src/data/temporal/columns.ts +10 -0
- package/src/data/temporal/definition-queries.ts +70 -0
- package/src/data/temporal/errors.ts +10 -0
- package/src/data/temporal/get-current.ts +22 -0
- package/src/data/temporal/index.ts +9 -0
- package/src/data/temporal/namespace.ts +36 -0
- package/src/data/temporal/query.ts +25 -0
- package/src/data/temporal/resolve.ts +17 -0
- package/src/data/temporal/revise.ts +67 -0
- package/src/data/temporal/types.ts +22 -0
- package/src/data/valve/artifact.ts +33 -0
- package/src/data/valve/contract-diff.ts +21 -0
- package/src/data/valve/cross.ts +57 -0
- package/src/data/valve/crossing-ledger.ts +50 -0
- package/src/data/valve/decision.ts +44 -0
- package/src/data/valve/durable-crossing.ts +52 -0
- package/src/data/valve/execute-crossing.ts +77 -0
- package/src/data/valve/faucet.ts +56 -0
- package/src/data/valve/gate.ts +114 -0
- package/src/data/valve/graph.ts +77 -0
- package/src/data/valve/index.ts +53 -0
- package/src/data/valve/namespace.ts +57 -0
- package/src/data/valve/registry.ts +50 -0
- package/src/data/valve/schema/valve-crossing-fields.ts +23 -0
- package/src/data/valve/schema/valve-crossing.ts +9 -0
- package/src/data/valve/supply.ts +154 -0
- package/src/data/valve/types.ts +143 -0
- package/src/data/vector/columns.ts +27 -0
- package/src/data/vector/index.ts +4 -0
- package/src/data/vector/namespace.ts +33 -0
- package/src/data/vector/query.ts +85 -0
- package/src/data/vector/validate.ts +16 -0
- package/src/data/write/audit/actor.ts +15 -0
- package/src/data/write/audit/audit-columns.ts +18 -0
- package/src/data/write/audit/audit-select.ts +111 -0
- package/src/data/write/audit/bound-transaction.ts +14 -0
- package/src/data/write/audit/cte-compile.ts +32 -0
- package/src/data/write/audit/decode-returning.ts +47 -0
- package/src/data/write/audit/final-select.ts +43 -0
- package/src/data/write/audit/infra-audit.ts +36 -0
- package/src/data/write/audit/primary-key.ts +65 -0
- package/src/data/write/audit/projection.ts +41 -0
- package/src/data/write/audit/read-touches.ts +86 -0
- package/src/data/write/audit/touch-match.ts +78 -0
- package/src/data/write/audit/verb-delete.ts +62 -0
- package/src/data/write/audit/verb-insert.ts +36 -0
- package/src/data/write/audit/verb-select.ts +47 -0
- package/src/data/write/audit/verb-update.ts +60 -0
- package/src/data/write/audit/verb-upsert.ts +104 -0
- package/src/data/write/conflict.ts +43 -0
- package/src/data/write/delete.ts +38 -0
- package/src/data/write/execute.ts +243 -0
- package/src/data/write/index.ts +11 -0
- package/src/data/write/insert.ts +63 -0
- package/src/data/write/state.ts +43 -0
- package/src/data/write/update.ts +54 -0
- package/src/data/write/upsert.ts +58 -0
- package/src/data/write/values.ts +19 -0
- package/src/index.ts +230 -0
- package/src/infra/access-links/code.ts +70 -0
- package/src/infra/access-links/handle.ts +892 -0
- package/src/infra/access-links/index.ts +9 -0
- package/src/infra/access-links/namespace.ts +30 -0
- package/src/infra/access-links/person-check.ts +177 -0
- package/src/infra/access-links/schema.ts +71 -0
- package/src/infra/alerts/async-sources.ts +231 -0
- package/src/infra/alerts/audit.ts +36 -0
- package/src/infra/alerts/beat.ts +123 -0
- package/src/infra/alerts/client-faults.ts +111 -0
- package/src/infra/alerts/faults.ts +156 -0
- package/src/infra/alerts/handle.ts +778 -0
- package/src/infra/alerts/http.ts +192 -0
- package/src/infra/alerts/index.ts +12 -0
- package/src/infra/alerts/infra-sources.ts +180 -0
- package/src/infra/alerts/namespace.ts +81 -0
- package/src/infra/alerts/redact.ts +21 -0
- package/src/infra/alerts/request-faults.ts +41 -0
- package/src/infra/alerts/schema.ts +124 -0
- package/src/infra/alerts/sinks.ts +125 -0
- package/src/infra/alerts/source-options.ts +26 -0
- package/src/infra/alerts/sources.ts +503 -0
- package/src/infra/alerts/subsystem-sources.ts +137 -0
- package/src/infra/audit/emit.ts +87 -0
- package/src/infra/audit/index.ts +2 -0
- package/src/infra/audit/namespace.ts +22 -0
- package/src/infra/audit/schema.sql +35 -0
- package/src/infra/audit/types.ts +18 -0
- package/src/infra/aws/credentials.ts +147 -0
- package/src/infra/aws/sigv4.ts +140 -0
- package/src/infra/cache/create.ts +58 -0
- package/src/infra/cache/entry.ts +81 -0
- package/src/infra/cache/index.ts +4 -0
- package/src/infra/cache/namespace.ts +10 -0
- package/src/infra/cache/tenant.ts +93 -0
- package/src/infra/cache/types.ts +24 -0
- package/src/infra/crawl/audit.ts +35 -0
- package/src/infra/crawl/errors.ts +25 -0
- package/src/infra/crawl/handle.ts +693 -0
- package/src/infra/crawl/index.ts +9 -0
- package/src/infra/crawl/links.ts +160 -0
- package/src/infra/crawl/namespace.ts +36 -0
- package/src/infra/crawl/robots.ts +100 -0
- package/src/infra/crawl/schema.ts +89 -0
- package/src/infra/credit-line/handle.ts +883 -0
- package/src/infra/credit-line/index.ts +6 -0
- package/src/infra/credit-line/namespace.ts +7 -0
- package/src/infra/credit-line/schema.ts +96 -0
- package/src/infra/credits/handle.ts +706 -0
- package/src/infra/credits/index.ts +5 -0
- package/src/infra/credits/namespace.ts +7 -0
- package/src/infra/credits/schema.ts +83 -0
- package/src/infra/email/audit.ts +36 -0
- package/src/infra/email/inbound.ts +622 -0
- package/src/infra/email/index.ts +11 -0
- package/src/infra/email/mail.ts +517 -0
- package/src/infra/email/mime.ts +447 -0
- package/src/infra/email/namespace.ts +22 -0
- package/src/infra/email/providers.ts +111 -0
- package/src/infra/email/schema.ts +96 -0
- package/src/infra/email/sns.ts +200 -0
- package/src/infra/email/unsubscribe.ts +44 -0
- package/src/infra/fetch/address.ts +88 -0
- package/src/infra/fetch/connect-retry.ts +87 -0
- package/src/infra/fetch/handle.ts +329 -0
- package/src/infra/fetch/index.ts +7 -0
- package/src/infra/fetch/namespace.ts +70 -0
- package/src/infra/idempotency/index.ts +3 -0
- package/src/infra/idempotency/middleware.ts +87 -0
- package/src/infra/idempotency/store.ts +92 -0
- package/src/infra/idempotency/types.ts +30 -0
- package/src/infra/join-requests/handle.ts +312 -0
- package/src/infra/join-requests/index.ts +2 -0
- package/src/infra/join-requests/namespace.ts +7 -0
- package/src/infra/join-requests/schema.ts +33 -0
- package/src/infra/outbox/core.ts +165 -0
- package/src/infra/quota/errors.ts +32 -0
- package/src/infra/quota/handle.ts +215 -0
- package/src/infra/quota/index.ts +5 -0
- package/src/infra/quota/namespace.ts +7 -0
- package/src/infra/quota/schema.ts +15 -0
- package/src/infra/storage/backend.ts +34 -0
- package/src/infra/storage/handle.ts +513 -0
- package/src/infra/storage/index.ts +9 -0
- package/src/infra/storage/local.ts +104 -0
- package/src/infra/storage/namespace.ts +13 -0
- package/src/infra/storage/s3.ts +257 -0
- package/src/infra/storage/schema.ts +24 -0
- package/src/infra/support/handle.ts +869 -0
- package/src/infra/support/http.ts +229 -0
- package/src/infra/support/index.ts +7 -0
- package/src/infra/support/namespace.ts +12 -0
- package/src/infra/support/schema.ts +96 -0
- package/src/infra/webhook/inbound.ts +45 -0
- package/src/infra/webhook/index.ts +5 -0
- package/src/infra/webhook/namespace.ts +24 -0
- package/src/infra/webhook/outbound.ts +315 -0
- package/src/infra/webhook/sign.ts +32 -0
- package/src/infra/webhook/verify.ts +82 -0
- package/src/invariants/assert.ts +26 -0
- package/src/invariants/index.ts +1 -0
- package/src/lint/config.ts +56 -0
- package/src/lint/doctrine.ts +163 -0
- package/src/lint/include.ts +97 -0
- package/src/lint/index.ts +3 -0
- package/src/lint/preset.ts +10 -0
- package/src/lint/resolver.ts +218 -0
- package/src/lint/rules/no-reexports.ts +46 -0
- package/src/request/behavior/audited.ts +85 -0
- package/src/request/behavior/cached.ts +62 -0
- package/src/request/behavior/compose.ts +28 -0
- package/src/request/behavior/index.ts +5 -0
- package/src/request/behavior/namespace.ts +16 -0
- package/src/request/behavior/types.ts +26 -0
- package/src/request/behavior/versioned.ts +54 -0
- package/src/request/context/create.ts +103 -0
- package/src/request/context/guard.ts +49 -0
- package/src/request/context/index.ts +4 -0
- package/src/request/context/run-in-transaction.ts +200 -0
- package/src/request/context/store.ts +28 -0
- package/src/request/context/types.ts +50 -0
- package/src/request/di/builder.ts +535 -0
- package/src/request/di/index.ts +8 -0
- package/src/request/di/resolve.ts +271 -0
- package/src/request/di/types.ts +262 -0
- package/src/request/fitting/namespace.ts +7 -0
- package/src/request/fixture/batch.ts +71 -0
- package/src/request/fixture/builder.ts +26 -0
- package/src/request/fixture/errors.ts +10 -0
- package/src/request/fixture/index.ts +8 -0
- package/src/request/fixture/namespace.ts +23 -0
- package/src/request/fixture/paginate.ts +44 -0
- package/src/request/fixture/to-handlers.ts +260 -0
- package/src/request/fixture/types.ts +79 -0
- package/src/request/flow/act-as.ts +78 -0
- package/src/request/flow/namespace.ts +17 -0
- package/src/request/http/error-step.ts +42 -0
- package/src/request/http/file.ts +141 -0
- package/src/request/http/index.ts +4 -0
- package/src/request/http/middleware.ts +141 -0
- package/src/request/http/namespace.ts +5 -0
- package/src/request/http/rate-limit-classes.ts +170 -0
- package/src/request/http/response.ts +82 -0
- package/src/request/http/server.ts +916 -0
- package/src/request/http/sniff.ts +261 -0
- package/src/request/http/types.ts +250 -0
- package/src/request/http/upload.ts +284 -0
- package/src/request/internal/text.ts +6 -0
- package/src/request/openapi/generate.ts +205 -0
- package/src/request/openapi/index.ts +2 -0
- package/src/request/openapi/namespace.ts +7 -0
- package/src/test/auth.ts +13 -0
- package/src/test/auto-setup.ts +45 -0
- package/src/test/context.ts +91 -0
- package/src/test/crawl-fixture.ts +157 -0
- package/src/test/discover-fork.ts +17 -0
- package/src/test/fingerprint.ts +135 -0
- package/src/test/globals.d.ts +7 -0
- package/src/test/index.ts +19 -0
- package/src/test/namespace.ts +87 -0
- package/src/test/plugin.ts +189 -0
- package/src/test/progress.ts +185 -0
- package/src/test/reap-after-run.ts +28 -0
- package/src/test/reap.ts +286 -0
- package/src/test/reset.ts +253 -0
- package/src/test/savepoints.ts +172 -0
- package/src/test/setup.ts +1422 -0
- package/src/test/system-channel.ts +50 -0
- package/src/test/vitest-plugin.ts +142 -0
- package/src/test/vitest-workspace.ts +507 -0
- package/src/test/vitest.ts +333 -0
- package/src/test/watch.ts +105 -0
- package/src/trace/capture/bounded-serializer.ts +146 -0
- package/src/trace/cardinality/analyze.ts +173 -0
- package/src/trace/cardinality/branch-context.ts +214 -0
- package/src/trace/cardinality/check.ts +33 -0
- package/src/trace/cardinality/classify-edge.ts +64 -0
- package/src/trace/cardinality/structural-max.ts +107 -0
- package/src/trace/check/coverage-check.ts +188 -0
- package/src/trace/check/flow-check.ts +103 -0
- package/src/trace/check/flow-step-site.ts +234 -0
- package/src/trace/check/io-pattern.ts +116 -0
- package/src/trace/codemod/add-cardinality.ts +152 -0
- package/src/trace/codemod/run.ts +80 -0
- package/src/trace/entry/child-trace.ts +21 -0
- package/src/trace/entry/close-trace.ts +87 -0
- package/src/trace/entry/finalize-trace.ts +28 -0
- package/src/trace/entry/open-trace.ts +138 -0
- package/src/trace/entry/parent-trace.ts +20 -0
- package/src/trace/entry/parse-traceparent.ts +39 -0
- package/src/trace/entry/process-start.ts +38 -0
- package/src/trace/entry/trace-context.ts +26 -0
- package/src/trace/entry/tracing-enabled.ts +10 -0
- package/src/trace/external/record.ts +195 -0
- package/src/trace/external/report.ts +66 -0
- package/src/trace/namespace.ts +19 -0
- package/src/trace/partition/day.ts +47 -0
- package/src/trace/partition/index.ts +14 -0
- package/src/trace/partition/partition-name.ts +23 -0
- package/src/trace/partition/plan.ts +82 -0
- package/src/trace/partition/schedule.ts +26 -0
- package/src/trace/partition/startup-hook.ts +39 -0
- package/src/trace/partition/sync.ts +154 -0
- package/src/trace/partition/tables.ts +53 -0
- package/src/trace/read/tree.ts +83 -0
- package/src/trace/retain/copy-on-error.ts +76 -0
- package/src/trace/runtime/expire-stale-traces.ts +54 -0
- package/src/trace/runtime/flow-step.ts +147 -0
- package/src/trace/runtime/flush-steps.ts +31 -0
- package/src/trace/runtime/pending-writes.ts +54 -0
- package/src/trace/runtime/step-buffer.ts +298 -0
- package/src/trace/runtime/step-error.ts +21 -0
- package/src/trace/runtime/step-meta.ts +55 -0
- package/src/trace/runtime/thenable.ts +12 -0
- package/src/trace/runtime/truncate-trace.ts +62 -0
- package/src/trace/schema/trace-fields.ts +52 -0
- package/src/trace/schema/trace-retained.ts +22 -0
- package/src/trace/schema/trace-step-fields.ts +44 -0
- package/src/trace/schema/trace-step-retained.ts +25 -0
- package/src/trace/schema/trace-step.ts +18 -0
- package/src/trace/schema/trace.ts +12 -0
- package/src/trace/transformer/apply.ts +79 -0
- package/src/trace/transformer/build-meta-literal.ts +30 -0
- package/src/trace/transformer/find-flow-step-sites.ts +85 -0
- package/src/trace/transformer/inject-flow-step-import.ts +39 -0
- package/src/trace/transformer/transform-error.ts +14 -0
- package/src/trace/transformer/transform-flow-steps.ts +123 -0
- package/src/trace/transformer/wrap-function-declaration.ts +62 -0
- package/src/trace/transformer/wrap-variable-declaration.ts +67 -0
- package/src/workspace/affected.ts +110 -0
- package/src/workspace/changed-files.ts +163 -0
- package/src/workspace/check-deps.ts +213 -0
- package/src/workspace/checksum.ts +20 -0
- package/src/workspace/dependency-graph.ts +67 -0
- package/src/workspace/detect-pipework-copies.ts +93 -0
- package/src/workspace/discover.ts +206 -0
- package/src/workspace/generate.ts +401 -0
- package/src/workspace/index.ts +7 -0
- package/src/workspace/module-imports-pipework.ts +39 -0
- package/src/workspace/module-package-name.ts +12 -0
- package/src/workspace/scrub-git-env.ts +23 -0
- package/src/workspace/source-paths.ts +36 -0
- package/src/workspace/sync.ts +182 -0
- package/dist/REFERENCE.md +0 -1477
- package/dist/infra/access-links/rate-limit.d.ts +0 -10
- package/dist/infra/access-links/rate-limit.d.ts.map +0 -1
- package/dist/infra/access-links/rate-limit.js +0 -28
- package/dist/infra/access-links/rate-limit.js.map +0 -1
- package/dist/infra/email/sigv4.d.ts +0 -24
- package/dist/infra/email/sigv4.d.ts.map +0 -1
- package/dist/infra/email/sigv4.js +0 -55
- package/dist/infra/email/sigv4.js.map +0 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Manifold } from '../../core/pipework.js'
|
|
2
|
+
import { runInContext } from '../../request/context/store.js'
|
|
3
|
+
import { createJobContext } from '../../request/context/create.js'
|
|
4
|
+
import { executeInTransaction } from '../../request/context/run-in-transaction.js'
|
|
5
|
+
import { isTracingEnabled } from '../../trace/entry/tracing-enabled.js'
|
|
6
|
+
import { openTrace } from '../../trace/entry/open-trace.js'
|
|
7
|
+
import { parentTraceRef } from '../../trace/entry/parent-trace.js'
|
|
8
|
+
import { closeTrace } from '../../trace/entry/close-trace.js'
|
|
9
|
+
import { stageProcessStart } from '../../trace/entry/process-start.js'
|
|
10
|
+
|
|
11
|
+
const SCRIPT_TRANSACTION_TIMEOUT_MS = 5000
|
|
12
|
+
|
|
13
|
+
export interface RunScriptOptions {
|
|
14
|
+
tenant?: string | null
|
|
15
|
+
auth?: unknown
|
|
16
|
+
jobType?: string
|
|
17
|
+
/** Names the process in the trace: stages the opening `external:pipework.process.start` step. */
|
|
18
|
+
process?: { phase: 'preStart' | 'script'; surfaces: readonly string[] }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Runs an operator script in the flow a job gets: a trace (entry kind `job`) that
|
|
23
|
+
* is closed `closed` or `error`, and, with a tenant, the default database's
|
|
24
|
+
* transaction bound to the flow with the tenant session variables set. The
|
|
25
|
+
* transaction commits when `fn` returns and rolls back when it throws. Without a
|
|
26
|
+
* tenant nothing is bound, as for a tenantless job.
|
|
27
|
+
*
|
|
28
|
+
* The trace close is awaited, so the row is written before the caller closes the pool.
|
|
29
|
+
*/
|
|
30
|
+
export async function runScript<T>(
|
|
31
|
+
instance: Manifold,
|
|
32
|
+
fn: () => T | Promise<T>,
|
|
33
|
+
options: RunScriptOptions = {},
|
|
34
|
+
): Promise<T> {
|
|
35
|
+
const tenant = options.tenant ?? null
|
|
36
|
+
const auth = options.auth ?? null
|
|
37
|
+
const jobType = options.jobType ?? 'script'
|
|
38
|
+
const databases = tenant === null ? [] : [instance.config.defaultDatabaseName()]
|
|
39
|
+
const trace = isTracingEnabled(instance)
|
|
40
|
+
? openTrace({
|
|
41
|
+
instance,
|
|
42
|
+
entryKind: 'job',
|
|
43
|
+
parent: parentTraceRef(),
|
|
44
|
+
tenant,
|
|
45
|
+
auth,
|
|
46
|
+
requestId: null,
|
|
47
|
+
sessionId: null,
|
|
48
|
+
})
|
|
49
|
+
: null
|
|
50
|
+
if (trace !== null && options.process !== undefined) {
|
|
51
|
+
stageProcessStart(trace, { ...options.process, release: instance.release })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ctx = createJobContext({ instance, jobType, auth, tenant, ...(trace !== null ? { trace } : {}) })
|
|
55
|
+
const run = async (): Promise<T> => fn()
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const result = tenant === null
|
|
59
|
+
? await runInContext(ctx, run)
|
|
60
|
+
: await executeInTransaction(ctx, databases, null, SCRIPT_TRANSACTION_TIMEOUT_MS, run)
|
|
61
|
+
await closeTrace(instance, trace, 'closed')
|
|
62
|
+
return result
|
|
63
|
+
} catch (err) {
|
|
64
|
+
await closeTrace(instance, trace, 'error')
|
|
65
|
+
throw err
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { sql } from '../../data/db/sql.js'
|
|
2
|
+
import type { DB } from '../../data/db/index.js'
|
|
3
|
+
import { parseCron, nextTick } from './cron.js'
|
|
4
|
+
import type { EnqueueOptions } from './queue.js'
|
|
5
|
+
import { describeError } from '../../core/logging/error-chain.js'
|
|
6
|
+
|
|
7
|
+
export interface Enqueuer {
|
|
8
|
+
enqueue(db: DB, opts: EnqueueOptions): Promise<string>
|
|
9
|
+
/** `enqueue` that also says whether it inserted a job (false: it returned the live job holding the `liveKey`). */
|
|
10
|
+
enqueueJob(db: DB, opts: EnqueueOptions): Promise<{ id: string; created: boolean }>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ScheduleSpec {
|
|
14
|
+
readonly name: string
|
|
15
|
+
readonly cron: string
|
|
16
|
+
readonly timezone?: string
|
|
17
|
+
readonly payload?: unknown
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ScheduleInfo {
|
|
21
|
+
readonly name: string
|
|
22
|
+
readonly cronExpression: string
|
|
23
|
+
readonly timezone: string
|
|
24
|
+
readonly payload: unknown
|
|
25
|
+
readonly lastRunAt: Date | null
|
|
26
|
+
readonly nextRunAt: Date
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SchedulerOptions {
|
|
30
|
+
readonly checkIntervalMs?: number
|
|
31
|
+
/** Fire only these schedules (by name). Omitted: every schedule in the table. */
|
|
32
|
+
readonly names?: readonly string[]
|
|
33
|
+
/** Called when a tick throws; without it the error is swallowed so the next tick still runs. */
|
|
34
|
+
readonly onError?: (err: unknown) => void
|
|
35
|
+
/** Called after every check, whether it succeeded or threw. */
|
|
36
|
+
readonly onCheck?: (outcome: { readonly ok: boolean; readonly error?: string }) => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Scheduler {
|
|
40
|
+
/** Upserts a schedule. An unchanged cron and timezone keep `next_run_at` and `last_run_at`. */
|
|
41
|
+
schedule(db: DB, spec: ScheduleSpec): Promise<void>
|
|
42
|
+
/** Upserts every declared spec and removes schedules named in `owned` that are not declared. Returns the removed names. */
|
|
43
|
+
sync(db: DB, declared: readonly ScheduleSpec[], owned: readonly string[]): Promise<string[]>
|
|
44
|
+
/** Enqueues the schedule's job now, as a tick would, without moving `next_run_at`. Returns the job id. */
|
|
45
|
+
fire(db: DB, name: string): Promise<string>
|
|
46
|
+
/**
|
|
47
|
+
* Fires every due schedule once and moves its `next_run_at` to the next future
|
|
48
|
+
* tick (a schedule missed by many ticks fires once, not once per missed tick).
|
|
49
|
+
* Each schedule is handled in its own transaction under `FOR UPDATE SKIP LOCKED`,
|
|
50
|
+
* so across processes a due schedule enqueues exactly one job. Returns the count fired.
|
|
51
|
+
*/
|
|
52
|
+
tick(db: DB, names?: readonly string[]): Promise<number>
|
|
53
|
+
unschedule(db: DB, name: string): Promise<boolean>
|
|
54
|
+
listSchedules(db: DB): Promise<ScheduleInfo[]>
|
|
55
|
+
start(db: DB, opts?: SchedulerOptions): void
|
|
56
|
+
stop(): void
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createScheduler(enqueuer: Enqueuer, jobsTableQ: string): Scheduler {
|
|
60
|
+
let intervalHandle: ReturnType<typeof setInterval> | null = null
|
|
61
|
+
let running = false
|
|
62
|
+
|
|
63
|
+
async function schedule(db: DB, spec: ScheduleSpec): Promise<void> {
|
|
64
|
+
const parsed = parseCron(spec.cron)
|
|
65
|
+
const tz = spec.timezone ?? 'UTC'
|
|
66
|
+
const nextRunAt = nextTick(parsed, new Date(), tz)
|
|
67
|
+
const payloadJson = spec.payload !== undefined ? JSON.stringify(spec.payload) : null
|
|
68
|
+
|
|
69
|
+
await db.execute(sql`
|
|
70
|
+
INSERT INTO pipework_schedules (name, cron_expression, timezone, payload, next_run_at)
|
|
71
|
+
VALUES (${spec.name}, ${spec.cron}, ${tz}, ${payloadJson !== null ? sql`${payloadJson}::jsonb` : sql`NULL`}, ${nextRunAt.toISOString()}::timestamptz)
|
|
72
|
+
ON CONFLICT (name) DO UPDATE SET
|
|
73
|
+
next_run_at = CASE
|
|
74
|
+
WHEN pipework_schedules.cron_expression IS DISTINCT FROM EXCLUDED.cron_expression
|
|
75
|
+
OR pipework_schedules.timezone IS DISTINCT FROM EXCLUDED.timezone
|
|
76
|
+
THEN EXCLUDED.next_run_at
|
|
77
|
+
ELSE pipework_schedules.next_run_at
|
|
78
|
+
END,
|
|
79
|
+
cron_expression = EXCLUDED.cron_expression,
|
|
80
|
+
timezone = EXCLUDED.timezone,
|
|
81
|
+
payload = EXCLUDED.payload,
|
|
82
|
+
updated_at = now()
|
|
83
|
+
`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function unschedule(db: DB, name: string): Promise<boolean> {
|
|
87
|
+
const rows = await db.execute(
|
|
88
|
+
sql`DELETE FROM pipework_schedules WHERE name = ${name} RETURNING name`,
|
|
89
|
+
)
|
|
90
|
+
return rows.length > 0
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function listSchedules(db: DB): Promise<ScheduleInfo[]> {
|
|
94
|
+
const rows = await db.execute(
|
|
95
|
+
sql`SELECT name, cron_expression, timezone, payload, last_run_at, next_run_at
|
|
96
|
+
FROM pipework_schedules ORDER BY name`,
|
|
97
|
+
)
|
|
98
|
+
return rows.map(row => ({
|
|
99
|
+
name: row['name'] as string,
|
|
100
|
+
cronExpression: row['cron_expression'] as string,
|
|
101
|
+
timezone: row['timezone'] as string,
|
|
102
|
+
payload: row['payload'] ?? null,
|
|
103
|
+
lastRunAt: row['last_run_at'] !== null && row['last_run_at'] !== undefined ? new Date(row['last_run_at'] as string) : null,
|
|
104
|
+
nextRunAt: new Date(row['next_run_at'] as string),
|
|
105
|
+
}))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function sync(db: DB, declared: readonly ScheduleSpec[], owned: readonly string[]): Promise<string[]> {
|
|
109
|
+
for (const spec of declared) await schedule(db, spec)
|
|
110
|
+
const keep = new Set(declared.map(spec => spec.name))
|
|
111
|
+
const removed: string[] = []
|
|
112
|
+
for (const info of await listSchedules(db)) {
|
|
113
|
+
if (owned.includes(info.name) && !keep.has(info.name)) {
|
|
114
|
+
if (await unschedule(db, info.name)) removed.push(info.name)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return removed
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function fire(db: DB, name: string): Promise<string> {
|
|
121
|
+
const rows = await db.execute(sql`SELECT payload FROM pipework_schedules WHERE name = ${name}`)
|
|
122
|
+
if (rows.length === 0) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`[pipework] No schedule named "${name}".\n\n` +
|
|
125
|
+
' fireSchedule() enqueues the job of a registered schedule; register it with\n' +
|
|
126
|
+
' queue.schedule() or declare it on the worker surface.\n',
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
return enqueuer.enqueue(db, { type: name, payload: rows[0]!['payload'] ?? {}, continueFlow: false })
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// One due schedule, locked for the transaction: a second process skips the
|
|
133
|
+
// locked row, and once the first commits next_run_at is in the future.
|
|
134
|
+
async function fireOneDue(db: DB, names: readonly string[] | undefined): Promise<'none' | 'skipped' | 'fired'> {
|
|
135
|
+
return db.transaction(async (tx: DB) => {
|
|
136
|
+
const filter = names !== undefined
|
|
137
|
+
? sql`AND name ${sql.inList(names)}`
|
|
138
|
+
: sql``
|
|
139
|
+
const due = await tx.execute(sql`
|
|
140
|
+
SELECT name, cron_expression, timezone, payload
|
|
141
|
+
FROM pipework_schedules
|
|
142
|
+
WHERE next_run_at <= now() ${filter}
|
|
143
|
+
ORDER BY next_run_at
|
|
144
|
+
LIMIT 1
|
|
145
|
+
FOR UPDATE SKIP LOCKED
|
|
146
|
+
`)
|
|
147
|
+
if (due.length === 0) return 'none'
|
|
148
|
+
|
|
149
|
+
const row = due[0]!
|
|
150
|
+
const name = row['name'] as string
|
|
151
|
+
const next = nextTick(parseCron(row['cron_expression'] as string), new Date(), row['timezone'] as string)
|
|
152
|
+
|
|
153
|
+
// A job of this type queued by other means (no key) also holds the schedule back. The key then
|
|
154
|
+
// makes concurrent ticks of this schedule atomic where the check alone is not.
|
|
155
|
+
const queued = await tx.execute(sql`
|
|
156
|
+
SELECT 1 FROM ${sql.raw(jobsTableQ)}
|
|
157
|
+
WHERE job_type = ${name} AND status IN ('pending', 'claimed')
|
|
158
|
+
LIMIT 1
|
|
159
|
+
`)
|
|
160
|
+
const created = queued.length === 0 && (await enqueuer.enqueueJob(tx, {
|
|
161
|
+
type: name, payload: row['payload'] ?? {}, continueFlow: false, liveKey: `schedule:${name}`,
|
|
162
|
+
})).created
|
|
163
|
+
if (!created) {
|
|
164
|
+
await tx.execute(sql`
|
|
165
|
+
UPDATE pipework_schedules SET next_run_at = ${next.toISOString()}::timestamptz, updated_at = now()
|
|
166
|
+
WHERE name = ${name}
|
|
167
|
+
`)
|
|
168
|
+
return 'skipped'
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await tx.execute(sql`
|
|
172
|
+
UPDATE pipework_schedules SET
|
|
173
|
+
last_run_at = now(),
|
|
174
|
+
next_run_at = ${next.toISOString()}::timestamptz,
|
|
175
|
+
updated_at = now()
|
|
176
|
+
WHERE name = ${name}
|
|
177
|
+
`)
|
|
178
|
+
return 'fired'
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function tick(db: DB, names?: readonly string[]): Promise<number> {
|
|
183
|
+
let fired = 0
|
|
184
|
+
// Every pass moves one schedule's next_run_at into the future, so the loop ends.
|
|
185
|
+
for (;;) {
|
|
186
|
+
const outcome = await fireOneDue(db, names)
|
|
187
|
+
if (outcome === 'none') return fired
|
|
188
|
+
if (outcome === 'fired') fired++
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function start(db: DB, opts?: SchedulerOptions): void {
|
|
193
|
+
if (running) return
|
|
194
|
+
running = true
|
|
195
|
+
const intervalMs = opts?.checkIntervalMs ?? 60_000
|
|
196
|
+
let inTick = false
|
|
197
|
+
intervalHandle = setInterval(() => {
|
|
198
|
+
if (inTick) return
|
|
199
|
+
inTick = true
|
|
200
|
+
tick(db, opts?.names).then(
|
|
201
|
+
() => { opts?.onCheck?.({ ok: true }) },
|
|
202
|
+
(err: unknown) => {
|
|
203
|
+
opts?.onError?.(err)
|
|
204
|
+
opts?.onCheck?.({ ok: false, error: describeError(err).message })
|
|
205
|
+
},
|
|
206
|
+
).finally(() => { inTick = false })
|
|
207
|
+
}, intervalMs)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function stop(): void {
|
|
211
|
+
if (!running || intervalHandle === null) return
|
|
212
|
+
clearInterval(intervalHandle)
|
|
213
|
+
intervalHandle = null
|
|
214
|
+
running = false
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { schedule, sync, fire, tick, unschedule, listSchedules, start, stop }
|
|
218
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The schedules table as a pipe.define() definition. The table used to be a
|
|
2
|
+
// hand-written schedules.schema.sql that `pipework generate` could not emit.
|
|
3
|
+
// A worker surface that declares schedules registers the definition at
|
|
4
|
+
// construction; the migration generator merges it through internalDefinitions.
|
|
5
|
+
// Its CREATE statements are IF NOT EXISTS (SCHEDULE_TABLE_NAMES in generate.ts),
|
|
6
|
+
// so apps that applied the old .sql by hand migrate cleanly.
|
|
7
|
+
|
|
8
|
+
import { define } from '../../data/domain/index.js'
|
|
9
|
+
import { field } from '../../data/domain/field.js'
|
|
10
|
+
import type { DefinedTable, FieldDescriptor } from '../../data/domain/types.js'
|
|
11
|
+
import { schema } from '../../data/schema/namespace.js'
|
|
12
|
+
|
|
13
|
+
export const SCHEDULE_TABLE_NAMES: ReadonlySet<string> = new Set(['pipework_schedules'])
|
|
14
|
+
|
|
15
|
+
const scheduleFields: Record<string, FieldDescriptor> = {
|
|
16
|
+
name: field.text().primaryKey(),
|
|
17
|
+
cronExpression: field.text(),
|
|
18
|
+
timezone: field.text().default('UTC'),
|
|
19
|
+
payload: field.jsonb(schema.check.json()).nullable(),
|
|
20
|
+
lastRunAt: field.timestamp().nullable(),
|
|
21
|
+
nextRunAt: field.timestamp(),
|
|
22
|
+
createdAt: field.timestamp().default('now'),
|
|
23
|
+
updatedAt: field.timestamp().default('now'),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const schedules: DefinedTable = define('pipework_schedules', scheduleFields)
|
|
27
|
+
|
|
28
|
+
let registered = false
|
|
29
|
+
|
|
30
|
+
/** Marks the schedules table as in use, so `pipework generate` emits it. */
|
|
31
|
+
export function registerSchedulesDefinition(): void {
|
|
32
|
+
registered = true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function schedulesDefinitions(): ReadonlyMap<string, DefinedTable> {
|
|
36
|
+
return registered ? new Map([['pipework_schedules', schedules]]) : new Map()
|
|
37
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Serialize an arbitrary thrown value for durable storage in a job's `error_payload`.
|
|
2
|
+
*
|
|
3
|
+
* A bare `{ message, stack }` capture is a diagnostics trap: wrapper errors hide the
|
|
4
|
+
* root cause. Drizzle throws `DrizzleQueryError` whose `.message` is only the failed
|
|
5
|
+
* SQL text; the real `PostgresError` — carrying `code`/`detail`/`constraint`/`table`
|
|
6
|
+
* — lives on `.cause`. This walks the full `cause` chain and copies each error's
|
|
7
|
+
* own-enumerable properties (where both Drizzle's `query` and Postgres' fields live),
|
|
8
|
+
* so a dead-lettered job records *why* it failed, not just *which query* failed.
|
|
9
|
+
*
|
|
10
|
+
* Parameters are never stored (`params`, `parameters`, `args`, `types`); `query` is cut
|
|
11
|
+
* to 2,000 characters and any other string to 4,000, ending `…[truncated N chars]`.
|
|
12
|
+
* Non-Error values pass through unchanged. Circular `cause` references are broken. */
|
|
13
|
+
export function serializeError(error: unknown): unknown {
|
|
14
|
+
return serialize(error, new Set<Error>())
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DROPPED_KEYS = new Set(['params', 'parameters', 'args', 'types'])
|
|
18
|
+
const MAX_QUERY_CHARS = 2_000
|
|
19
|
+
const MAX_STRING_CHARS = 4_000
|
|
20
|
+
|
|
21
|
+
function cut(text: string, max: number): string {
|
|
22
|
+
return text.length <= max ? text : `${text.slice(0, max)}…[truncated ${String(text.length - max)} chars]`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function serialize(value: unknown, seen: Set<Error>): unknown {
|
|
26
|
+
if (!(value instanceof Error)) return value
|
|
27
|
+
if (seen.has(value)) return { message: '[circular error reference]' }
|
|
28
|
+
seen.add(value)
|
|
29
|
+
|
|
30
|
+
// Drizzle's message (and so its stack) is `Failed query: <sql>\nparams: <values>`: keep the text before the values.
|
|
31
|
+
const paramsAt = Object.hasOwn(value, 'params') ? value.message.indexOf('\nparams: ') : -1
|
|
32
|
+
const message = paramsAt === -1 ? value.message : value.message.slice(0, paramsAt)
|
|
33
|
+
const stack = typeof value.stack === 'string' && paramsAt !== -1 ? value.stack.replace(value.message, message) : value.stack
|
|
34
|
+
const out: Record<string, unknown> = {
|
|
35
|
+
name: value.name,
|
|
36
|
+
message: cut(message, MAX_STRING_CHARS),
|
|
37
|
+
stack: typeof stack === 'string' ? cut(stack, MAX_STRING_CHARS) : stack,
|
|
38
|
+
}
|
|
39
|
+
for (const key of Object.keys(value)) {
|
|
40
|
+
if (key === 'name' || key === 'message' || key === 'stack' || key === 'cause' || DROPPED_KEYS.has(key)) continue
|
|
41
|
+
const v = (value as unknown as Record<string, unknown>)[key]
|
|
42
|
+
out[key] = typeof v === 'string' ? cut(v, key === 'query' ? MAX_QUERY_CHARS : MAX_STRING_CHARS) : v
|
|
43
|
+
}
|
|
44
|
+
if (value.cause !== undefined) {
|
|
45
|
+
out['cause'] = serialize(value.cause, seen)
|
|
46
|
+
}
|
|
47
|
+
return out
|
|
48
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// The queue table as a pipe.define() definition, built from queue config.
|
|
2
|
+
//
|
|
3
|
+
// The table shape used to live in a hand-written schema.sql that no TS code
|
|
4
|
+
// referenced, so `pipework generate` could not emit it and a regenerated
|
|
5
|
+
// baseline silently dropped the queue migration. Expressing it as a
|
|
6
|
+
// DefinedTable puts it on the same generate path as every other table:
|
|
7
|
+
// createQueue() registers the definition here, and the migration generator
|
|
8
|
+
// merges this registry through its internalDefinitions hook.
|
|
9
|
+
|
|
10
|
+
import { define } from '../../data/domain/index.js'
|
|
11
|
+
import { field } from '../../data/domain/field.js'
|
|
12
|
+
import type { DefinedTable, FieldDescriptor } from '../../data/domain/types.js'
|
|
13
|
+
import { schema } from '../../data/schema/namespace.js'
|
|
14
|
+
import { index, uniqueIndex } from '../../data/db/idx/index.js'
|
|
15
|
+
import { quoteIdentifier } from '../../data/db/identifiers.js'
|
|
16
|
+
import { sql } from '../../data/db/sql.js'
|
|
17
|
+
import type { ResolvedExclusion } from './exclusion.js'
|
|
18
|
+
|
|
19
|
+
export const QUEUE_STATUSES = ['pending', 'claimed', 'running', 'completed', 'failed', 'cancelled', 'dead_letter'] as const
|
|
20
|
+
|
|
21
|
+
/** Column names every queue table owns — exclusion columns must not collide with these. */
|
|
22
|
+
export const QUEUE_BASE_COLUMNS: ReadonlySet<string> = new Set([
|
|
23
|
+
'id', 'job_type', 'priority', 'status', 'payload', 'output', 'error_payload',
|
|
24
|
+
'created_lsn', 'queued_at', 'claimed_at', 'heartbeat_at', 'completed_at',
|
|
25
|
+
'worker_id', 'worker_release', 'claims', 'attempt_count', 'max_attempts', 'next_retry_at', 'claim_token',
|
|
26
|
+
'flow_trace_id', 'flow_root_id', 'flow_step_id', 'flow_tenant', 'flow_actor', 'live_key', 'pending_key',
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
/** The live-key index's tenant expression and predicate; enqueue's ON CONFLICT must repeat them exactly. */
|
|
30
|
+
export const LIVE_KEY_TENANT_EXPR = `coalesce(flow_tenant, '')`
|
|
31
|
+
export const LIVE_KEY_PREDICATE = `live_key IS NOT NULL AND status IN ('pending', 'claimed', 'running')`
|
|
32
|
+
export function liveKeyIndexName(tableName: string): string {
|
|
33
|
+
return `uq_${tableName}_live_key`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The pending-key index's tenant expression and predicate; enqueue's ON CONFLICT must repeat them exactly. */
|
|
37
|
+
export const PENDING_KEY_TENANT_EXPR = LIVE_KEY_TENANT_EXPR
|
|
38
|
+
export const PENDING_KEY_PREDICATE = `pending_key IS NOT NULL AND status = 'pending'`
|
|
39
|
+
export function pendingKeyIndexName(tableName: string): string {
|
|
40
|
+
return `uq_${tableName}_pending_key`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function queueFields(exclusion: ResolvedExclusion | undefined): Record<string, FieldDescriptor> {
|
|
44
|
+
const fields: Record<string, FieldDescriptor> = {
|
|
45
|
+
id: field.uuid().primaryKey().defaultRandom(),
|
|
46
|
+
jobType: field.text(),
|
|
47
|
+
priority: field.integer().default(0),
|
|
48
|
+
status: field.enum(QUEUE_STATUSES).default('pending'),
|
|
49
|
+
payload: field.jsonb(schema.check.json()),
|
|
50
|
+
output: field.jsonb(schema.check.json()).nullable(),
|
|
51
|
+
errorPayload: field.jsonb(schema.check.json()).nullable(),
|
|
52
|
+
createdLsn: field.pgLsn().default('currentWal'),
|
|
53
|
+
queuedAt: field.timestamp().default('now'),
|
|
54
|
+
claimedAt: field.timestamp().nullable(),
|
|
55
|
+
heartbeatAt: field.timestamp().nullable(),
|
|
56
|
+
completedAt: field.timestamp().nullable(),
|
|
57
|
+
workerId: field.text().nullable(),
|
|
58
|
+
// The release of the worker that last claimed the job. Unlike worker_id it
|
|
59
|
+
// survives completion, failure, hand-back and later.
|
|
60
|
+
workerRelease: field.text().nullable(),
|
|
61
|
+
// Append-only history of the job's claims: claim()'s UPDATE appends one
|
|
62
|
+
// element, every statement that ends a claim closes the last one.
|
|
63
|
+
claims: field.jsonb(schema.check.json()).default([]),
|
|
64
|
+
attemptCount: field.integer().default(0),
|
|
65
|
+
maxAttempts: field.integer().default(1),
|
|
66
|
+
nextRetryAt: field.timestamp().nullable(),
|
|
67
|
+
// Minted only inside claim()'s own UPDATE and nulled by every release, so
|
|
68
|
+
// a consumer can fence a claimant's writes on an exact uuid comparison.
|
|
69
|
+
claimToken: field.uuid().nullable(),
|
|
70
|
+
// The flow that enqueued the job, restored when a worker runs it: the trace
|
|
71
|
+
// (and open step) it continues, the tenant it runs under, and optionally
|
|
72
|
+
// the actor. All null for a job enqueued outside any Flow.
|
|
73
|
+
flowTraceId: field.uuid().nullable(),
|
|
74
|
+
flowRootId: field.uuid().nullable(),
|
|
75
|
+
flowStepId: field.uuid().nullable(),
|
|
76
|
+
flowTenant: field.text().nullable(),
|
|
77
|
+
flowActor: field.text().nullable(),
|
|
78
|
+
// enqueue's liveKey: at most one live job per (key, tenant), enforced by the partial unique index.
|
|
79
|
+
liveKey: field.text().nullable(),
|
|
80
|
+
// enqueue's pendingKey: at most one pending job per (key, tenant), enforced by the partial unique index.
|
|
81
|
+
pendingKey: field.text().nullable(),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (exclusion?.tenantColumn !== undefined) {
|
|
85
|
+
fields[exclusion.tenantColumn] = field.uuid().tenant()
|
|
86
|
+
}
|
|
87
|
+
if (exclusion?.arrayColumn !== undefined) {
|
|
88
|
+
const column = exclusion.arrayElementType === 'text'
|
|
89
|
+
? schema.col.text(exclusion.arrayColumn).array()
|
|
90
|
+
: schema.col.uuid(exclusion.arrayColumn).array()
|
|
91
|
+
fields[exclusion.arrayColumn] = field.custom(column, schema.check.array(schema.check.string())).nullable()
|
|
92
|
+
}
|
|
93
|
+
if (exclusion?.blocksAllColumn !== undefined) {
|
|
94
|
+
fields[exclusion.blocksAllColumn] = field.boolean().default(false)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return fields
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface QueueTableEntry {
|
|
101
|
+
readonly definition: DefinedTable
|
|
102
|
+
readonly configKey: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const queueTables = new Map<string, QueueTableEntry>()
|
|
106
|
+
const progressColumns = new Map<string, string>()
|
|
107
|
+
|
|
108
|
+
/** Records (or clears) the queue's configured progress column so startup validation can check the table has it. */
|
|
109
|
+
export function registerProgressColumn(tableName: string, column: string | undefined): void {
|
|
110
|
+
if (column === undefined) progressColumns.delete(tableName)
|
|
111
|
+
else progressColumns.set(tableName, column)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Progress columns configured per queue table. */
|
|
115
|
+
export function queueProgressColumns(): ReadonlyMap<string, string> {
|
|
116
|
+
return progressColumns
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Builds (or reuses) the DefinedTable for a queue. Cached per table name so
|
|
121
|
+
* repeated createQueue() calls share one definition; the same name with a
|
|
122
|
+
* different shape is a config error, not a silent overwrite.
|
|
123
|
+
*/
|
|
124
|
+
export function queueDefinition(tableName: string, exclusion: ResolvedExclusion | undefined): DefinedTable {
|
|
125
|
+
const configKey = JSON.stringify(exclusion ?? null)
|
|
126
|
+
|
|
127
|
+
const existing = queueTables.get(tableName)
|
|
128
|
+
if (existing !== undefined) {
|
|
129
|
+
if (existing.configKey !== configKey) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`[pipework] Queue table "${tableName}" is already defined with a different exclusion config.\n\n` +
|
|
132
|
+
` Existing: ${existing.configKey}\n` +
|
|
133
|
+
` New: ${configKey}\n` +
|
|
134
|
+
' One table, one shape — give the second queue its own table name.\n',
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
return existing.definition
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const definition = define(tableName, queueFields(exclusion), {
|
|
141
|
+
indexes: (t) => ({
|
|
142
|
+
claimable: index(`idx_${tableName}_claimable`)
|
|
143
|
+
.on(t['priority'].desc(), t['queuedAt'].asc())
|
|
144
|
+
.where(sql.raw(`status = 'pending'`)),
|
|
145
|
+
liveKey: uniqueIndex(liveKeyIndexName(tableName))
|
|
146
|
+
.on(t['liveKey'], sql.raw(LIVE_KEY_TENANT_EXPR))
|
|
147
|
+
.where(sql.raw(LIVE_KEY_PREDICATE)),
|
|
148
|
+
pendingKey: uniqueIndex(pendingKeyIndexName(tableName))
|
|
149
|
+
.on(t['pendingKey'], sql.raw(PENDING_KEY_TENANT_EXPR))
|
|
150
|
+
.where(sql.raw(PENDING_KEY_PREDICATE)),
|
|
151
|
+
stale: index(`idx_${tableName}_stale`)
|
|
152
|
+
.on(t['heartbeatAt'])
|
|
153
|
+
.where(sql.raw(`status IN ('claimed', 'running')`)),
|
|
154
|
+
retryable: index(`idx_${tableName}_retryable`)
|
|
155
|
+
.on(t['nextRetryAt'])
|
|
156
|
+
.where(sql.raw(`status = 'pending' AND next_retry_at IS NOT NULL`)),
|
|
157
|
+
// The claim query's exclusion checks run under SERIALIZABLE; without a
|
|
158
|
+
// matching index the overlap NOT EXISTS scans the whole claimed/running
|
|
159
|
+
// set and takes coarse predicate locks, so claimers with disjoint arrays
|
|
160
|
+
// SSI-conflict and starve. Partial GIN keeps the locks at the key level;
|
|
161
|
+
// fastupdate must be off — a GIN pending list forces relation-level
|
|
162
|
+
// predicate locks, voiding the granularity.
|
|
163
|
+
...(exclusion?.arrayColumn !== undefined ? {
|
|
164
|
+
exclusionArray: index(`idx_${tableName}_excl_arr`)
|
|
165
|
+
.using('gin', t[exclusion.arrayColumn]!)
|
|
166
|
+
.with({ fastupdate: 'off' })
|
|
167
|
+
.where(sql.raw(`status IN ('claimed', 'running')`)),
|
|
168
|
+
} : {}),
|
|
169
|
+
// Same starvation mechanism for the blocks-all NOT EXISTS: without its
|
|
170
|
+
// own index that check scans every claimed/running row. This partial
|
|
171
|
+
// index contains only active blocks-all jobs (normally none), so
|
|
172
|
+
// ordinary claims neither read beyond it nor insert into it — only a
|
|
173
|
+
// genuine blocks-all claim conflicts, which is the intended semantics.
|
|
174
|
+
...(exclusion?.blocksAllColumn !== undefined ? {
|
|
175
|
+
exclusionBlocks: index(`idx_${tableName}_excl_blocks`)
|
|
176
|
+
.on(t[exclusion.tenantColumn ?? exclusion.blocksAllColumn]!)
|
|
177
|
+
.where(sql.raw(
|
|
178
|
+
`status IN ('claimed', 'running') AND ${quoteIdentifier(exclusion.blocksAllColumn, 'exclusion column')} = true`,
|
|
179
|
+
)),
|
|
180
|
+
} : {}),
|
|
181
|
+
}),
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
queueTables.set(tableName, { definition, configKey })
|
|
185
|
+
return definition
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Registered queue definitions — merged into `pipework generate` via internalDefinitions. */
|
|
189
|
+
export function queueDefinitions(): ReadonlyMap<string, DefinedTable> {
|
|
190
|
+
return new Map([...queueTables].map(([name, entry]) => [name, entry.definition]))
|
|
191
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { DB } from '../../data/db/index.js'
|
|
2
|
+
import { sql } from '../../data/db/sql.js'
|
|
3
|
+
import type { PipelineConfig, Pipeline, PipelineRun, PipelineExecuteContext } from './types.js'
|
|
4
|
+
import { executePipeline } from './execute.js'
|
|
5
|
+
import { resumePipeline } from './resume.js'
|
|
6
|
+
import { refuseScopedHandle } from '../../auth/tenant/scoped-db.js'
|
|
7
|
+
|
|
8
|
+
export function definePipeline(config: PipelineConfig): Pipeline {
|
|
9
|
+
if (config.steps.length === 0) {
|
|
10
|
+
throw new Error(`[pipework] Pipeline "${config.name}" must have at least one step.`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const stepNames = new Set<string>()
|
|
14
|
+
for (const step of config.steps) {
|
|
15
|
+
if (stepNames.has(step.name)) {
|
|
16
|
+
throw new Error(`[pipework] Pipeline "${config.name}" has duplicate step name "${step.name}".`)
|
|
17
|
+
}
|
|
18
|
+
stepNames.add(step.name)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return refuseScopedHandle({
|
|
22
|
+
async execute(db: DB, input: unknown, context: PipelineExecuteContext): Promise<PipelineRun> {
|
|
23
|
+
return executePipeline(db, config, input, context)
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
async resume(db: DB, runId: string, stepOutput: unknown): Promise<PipelineRun> {
|
|
27
|
+
return resumePipeline(db, config, runId, stepOutput)
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
async getStatus(db: DB, runId: string): Promise<PipelineRun | null> {
|
|
31
|
+
const [row] = await db.execute(sql`
|
|
32
|
+
SELECT id, pipeline_name, status, current_step, input, started_at, completed_at, error
|
|
33
|
+
FROM pipework_pipeline_runs
|
|
34
|
+
WHERE id = ${runId}::uuid
|
|
35
|
+
`)
|
|
36
|
+
|
|
37
|
+
if (row === undefined) return null
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
id: String(row['id']),
|
|
41
|
+
pipelineName: String(row['pipeline_name']),
|
|
42
|
+
status: String(row['status']) as PipelineRun['status'],
|
|
43
|
+
currentStep: Number(row['current_step']),
|
|
44
|
+
input: row['input'],
|
|
45
|
+
startedAt: new Date(String(row['started_at'])),
|
|
46
|
+
completedAt: row['completed_at'] !== null ? new Date(String(row['completed_at'])) : null,
|
|
47
|
+
error: row['error'] ?? null,
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
definition: config,
|
|
52
|
+
}, `pipeline "${config.name}"`, 'its step handlers receive the connection it runs on and would bypass the tenant filter')
|
|
53
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { PipeworkError } from '../../core/errors/base.js'
|
|
2
|
+
|
|
3
|
+
export class PipelineStepError extends PipeworkError {
|
|
4
|
+
readonly code = 'PIPELINE_STEP_FAILED' as const
|
|
5
|
+
readonly httpStatus = 500 as const
|
|
6
|
+
readonly pipelineName: string
|
|
7
|
+
readonly stepName: string
|
|
8
|
+
readonly stepIndex: number
|
|
9
|
+
|
|
10
|
+
constructor(pipelineName: string, stepName: string, stepIndex: number, cause: unknown) {
|
|
11
|
+
const causeMsg = cause instanceof Error ? cause.message : String(cause)
|
|
12
|
+
super(
|
|
13
|
+
`Pipeline "${pipelineName}" failed at step ${stepIndex} ("${stepName}"): ${causeMsg}. ` +
|
|
14
|
+
`Prior steps may have committed. Check pipeline run status for details.`,
|
|
15
|
+
)
|
|
16
|
+
this.pipelineName = pipelineName
|
|
17
|
+
this.stepName = stepName
|
|
18
|
+
this.stepIndex = stepIndex
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class PipelineResumeError extends PipeworkError {
|
|
23
|
+
readonly code = 'PIPELINE_RESUME_FAILED' as const
|
|
24
|
+
readonly httpStatus = 409 as const
|
|
25
|
+
readonly runId: string
|
|
26
|
+
readonly reason: string
|
|
27
|
+
|
|
28
|
+
constructor(runId: string, reason: string) {
|
|
29
|
+
super(
|
|
30
|
+
`Cannot resume pipeline run "${runId}": ${reason}`,
|
|
31
|
+
)
|
|
32
|
+
this.runId = runId
|
|
33
|
+
this.reason = reason
|
|
34
|
+
}
|
|
35
|
+
}
|