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,88 @@
|
|
|
1
|
+
import type { ResolvedConfig } from '../config/load.js'
|
|
2
|
+
import type { Surface, SurfaceMap } from './types.js'
|
|
3
|
+
import { DEFAULT_DB } from '../../request/di/types.js'
|
|
4
|
+
|
|
5
|
+
export function validateSurfaces(config: ResolvedConfig, surfaces: SurfaceMap): void {
|
|
6
|
+
const problems: string[] = []
|
|
7
|
+
const configuredDbs = new Set(config.databaseNames())
|
|
8
|
+
|
|
9
|
+
for (const [name, surface] of Object.entries(surfaces)) {
|
|
10
|
+
for (const db of surface.manifest.databases) {
|
|
11
|
+
if (db === DEFAULT_DB) {
|
|
12
|
+
if (configuredDbs.size > 1) {
|
|
13
|
+
try {
|
|
14
|
+
config.defaultDatabaseName()
|
|
15
|
+
} catch {
|
|
16
|
+
problems.push(
|
|
17
|
+
`Surface "${name}" uses the default database, but multiple databases are configured.\n` +
|
|
18
|
+
` Configured: ${[...configuredDbs].join(', ')}\n` +
|
|
19
|
+
` Fix: Pass an explicit database name — e.g., surface.worker({ database: "${[...configuredDbs][0]}" })`,
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} else if (!configuredDbs.has(db)) {
|
|
24
|
+
const available = [...configuredDbs].join(', ')
|
|
25
|
+
problems.push(
|
|
26
|
+
`Surface "${name}" requires database "${db}" which is not configured.\n` +
|
|
27
|
+
` Configured databases: ${available || '(none)'}\n` +
|
|
28
|
+
` Fix: Add "${db}" to your databases config, or correct the name in the surface definition.`,
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
validateSurfaceKind(name, surface, problems)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (problems.length > 0) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`[pipework] Surface validation failed:\n\n${problems.map((p, i) => ` ${i + 1}. ${p}`).join('\n\n')}\n`,
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateSurfaceKind(name: string, surface: Surface, problems: string[]): void {
|
|
44
|
+
if (surface.kind === 'worker') {
|
|
45
|
+
if (surface.manifest.jobTypes.length === 0) {
|
|
46
|
+
problems.push(
|
|
47
|
+
`Surface "${name}" is a worker with no job handlers.\n` +
|
|
48
|
+
' Fix: Pass at least one job handler — surface.worker({ jobs: [myJob] })',
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const seen = new Set<string>()
|
|
53
|
+
for (const jobType of surface.manifest.jobTypes) {
|
|
54
|
+
if (seen.has(jobType)) {
|
|
55
|
+
problems.push(
|
|
56
|
+
`Surface "${name}" has duplicate job type "${jobType}".\n` +
|
|
57
|
+
' Each job type must have exactly one handler.',
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
seen.add(jobType)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function validateNoJobTypeDuplicates(surfaces: SurfaceMap): void {
|
|
66
|
+
const owners = new Map<string, string>()
|
|
67
|
+
const problems: string[] = []
|
|
68
|
+
|
|
69
|
+
for (const [name, surface] of Object.entries(surfaces)) {
|
|
70
|
+
for (const jobType of surface.manifest.jobTypes) {
|
|
71
|
+
const existing = owners.get(jobType)
|
|
72
|
+
if (existing !== undefined) {
|
|
73
|
+
problems.push(
|
|
74
|
+
`Job type "${jobType}" is handled by both "${existing}" and "${name}".\n` +
|
|
75
|
+
' Each job type must be handled by exactly one worker surface.',
|
|
76
|
+
)
|
|
77
|
+
} else {
|
|
78
|
+
owners.set(jobType, name)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (problems.length > 0) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`[pipework] Job type conflict:\n\n${problems.map((p, i) => ` ${i + 1}. ${p}`).join('\n\n')}\n`,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import type { SurfaceBuilder, SurfaceHandle, WorkerSurfaceConfig, SurfaceContext, JobEvent } from './types.js'
|
|
3
|
+
import type { Handler } from '../../request/di/types.js'
|
|
4
|
+
import { DEFAULT_DB } from '../../request/di/types.js'
|
|
5
|
+
import { createQueue } from '../../async/jobs/queue.js'
|
|
6
|
+
import type { ClaimedFlow, ClaimedJob } from '../../async/jobs/queue.js'
|
|
7
|
+
import { abortableDelay } from '../../async/jobs/abortable-delay.js'
|
|
8
|
+
import type { ListenClient, ListenHandle } from '../../async/jobs/listener.js'
|
|
9
|
+
import { executeJob } from '../../async/jobs/execute.js'
|
|
10
|
+
import { JobLaterError, JobPermanentError } from '../../async/jobs/errors.js'
|
|
11
|
+
import { QuotaExhaustedError } from '../../infra/quota/errors.js'
|
|
12
|
+
import { executeWithTimeout } from './job-timeout.js'
|
|
13
|
+
import { createJobContext } from '../../request/context/create.js'
|
|
14
|
+
import { runInContext } from '../../request/context/store.js'
|
|
15
|
+
import { executeInTransaction } from '../../request/context/run-in-transaction.js'
|
|
16
|
+
import { isTracingEnabled } from '../../trace/entry/tracing-enabled.js'
|
|
17
|
+
import { openTrace } from '../../trace/entry/open-trace.js'
|
|
18
|
+
import { finalizeTrace } from '../../trace/entry/finalize-trace.js'
|
|
19
|
+
import type { Manifold } from '../pipework.js'
|
|
20
|
+
import { getBaseLogger } from '../logging/index.js'
|
|
21
|
+
import { pgErrorCode } from '../../data/db/serializable.js'
|
|
22
|
+
import { sql } from '../../data/db/sql.js'
|
|
23
|
+
import { frameworkDoorDb } from '../../data/db/db.js'
|
|
24
|
+
import { recordBeat, beatsEnabled } from '../../infra/alerts/beat.js'
|
|
25
|
+
import { recordFault, faultMessage, useFaultTarget } from '../../infra/alerts/faults.js'
|
|
26
|
+
import { parseCron } from '../../async/jobs/cron.js'
|
|
27
|
+
import { registerSchedulesDefinition } from '../../async/jobs/schedules-table.js'
|
|
28
|
+
import { describeError } from '../logging/error-chain.js'
|
|
29
|
+
|
|
30
|
+
export function createWorkerSurface(config: WorkerSurfaceConfig): SurfaceBuilder {
|
|
31
|
+
const selections = (config.graphs ?? []).map(g => 'graph' in g ? g : { graph: g, classes: undefined })
|
|
32
|
+
const graphs = [...new Set(selections.map(g => g.graph))]
|
|
33
|
+
const graphHandlers = selections.flatMap(g => g.graph.worker.handlers(config.database, g.classes))
|
|
34
|
+
const allJobs = [...config.jobs, ...graphHandlers]
|
|
35
|
+
const databases = collectWorkerDatabases({ ...config, jobs: allJobs })
|
|
36
|
+
const jobTypes = collectJobTypes(allJobs)
|
|
37
|
+
validateJobHandlers(allJobs)
|
|
38
|
+
const deps: Record<string, string> = {}
|
|
39
|
+
// Created at surface construction, not start(): createQueue() registers the
|
|
40
|
+
// queue table definition, and `pipework generate` discovers surfaces without
|
|
41
|
+
// starting them — construction time is what makes the DDL reach migrations.
|
|
42
|
+
const queue = createQueue(config.queue)
|
|
43
|
+
for (const g of graphs) {
|
|
44
|
+
const graphTable = (g.queue.definition as unknown as { $name: string }).$name
|
|
45
|
+
const workerTable = (queue.definition as unknown as { $name: string }).$name
|
|
46
|
+
if (graphTable !== workerTable) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`[pipework] Step graph "${g.name}" enqueues on queue table "${graphTable}", but this worker surface reads "${workerTable}".\n\n` +
|
|
49
|
+
` Set the worker's queue to the graph's: createWorkerSurface({ graphs, queue: { table: "${graphTable}" } }).\n`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const schedules = config.schedules
|
|
54
|
+
if (schedules !== undefined) {
|
|
55
|
+
validateSchedules(schedules, jobTypes)
|
|
56
|
+
registerSchedulesDefinition()
|
|
57
|
+
}
|
|
58
|
+
const scheduledJobs = new Set((schedules ?? []).map(s => s.job))
|
|
59
|
+
|
|
60
|
+
const surface: SurfaceBuilder = {
|
|
61
|
+
kind: 'worker',
|
|
62
|
+
manifest: { databases, jobTypes, dependencies: deps },
|
|
63
|
+
|
|
64
|
+
dependencies(d: Record<string, string>) {
|
|
65
|
+
Object.assign(deps, d)
|
|
66
|
+
return surface
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async start(context: SurfaceContext): Promise<SurfaceHandle> {
|
|
70
|
+
const logger = getBaseLogger()
|
|
71
|
+
const dbName = config.database ?? context.config.defaultDatabaseName()
|
|
72
|
+
const dbConfig = context.config.database(dbName)
|
|
73
|
+
const conn = context.pool.getOrCreate(dbConfig)
|
|
74
|
+
const db = conn.drizzle
|
|
75
|
+
|
|
76
|
+
await db.execute(sql`SELECT 1`)
|
|
77
|
+
|
|
78
|
+
// A tenant-column queue is row-secured: on a platform-mode database its claim, reap, heartbeat and
|
|
79
|
+
// completion run through the queue's framework door (generated by `pipework generate`), which opens
|
|
80
|
+
// that one table. Everything else the surface does (beats, faults, schedules) needs no door.
|
|
81
|
+
const queueDb = (dbConfig as { readonly rls?: { readonly platformRole?: string } }).rls?.platformRole !== undefined && queue.door !== null && (context.pool as { isOverridden?(name: string): boolean }).isOverridden?.(dbName) !== true
|
|
82
|
+
? frameworkDoorDb(db, queue.door)
|
|
83
|
+
: db
|
|
84
|
+
|
|
85
|
+
// Loop beats go on the loop's own connection, outside any job transaction, and only where the blueprint has `alerts`.
|
|
86
|
+
const beatsOn = beatsEnabled(context.config)
|
|
87
|
+
const beat = (name: string, outcome: { ok: boolean; error?: string }, everyMs: number): void => {
|
|
88
|
+
if (beatsOn) void recordBeat(db, name, { ...outcome, everyMs })
|
|
89
|
+
}
|
|
90
|
+
// Failures this surface only logs are recorded for `alerts.source.faults`, again only where the blueprint has `alerts`.
|
|
91
|
+
const releaseFaults = beatsOn ? useFaultTarget(db) : (): void => undefined
|
|
92
|
+
const CLAIM_BEAT_EVERY_MS = 60_000
|
|
93
|
+
let lastClaimBeatAt = 0
|
|
94
|
+
const claimBeat = (outcome: { ok: boolean; error?: string }): void => {
|
|
95
|
+
const now = Date.now()
|
|
96
|
+
if (!beatsOn || now - lastClaimBeatAt < CLAIM_BEAT_EVERY_MS) return
|
|
97
|
+
lastClaimBeatAt = now
|
|
98
|
+
beat('jobs.claim', outcome, CLAIM_BEAT_EVERY_MS)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (config.listen === true) {
|
|
102
|
+
const rawConn = conn as unknown as { readonly client: { listen(channel: string, cb: (payload: string) => void): Promise<{ unlisten(): Promise<void> }> } }
|
|
103
|
+
const listenClient: ListenClient = {
|
|
104
|
+
async listen(channel: string, cb: (payload: string) => void): Promise<ListenHandle> {
|
|
105
|
+
const sub = await rawConn.client.listen(channel, cb)
|
|
106
|
+
return { unlisten: () => sub.unlisten() }
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
await queue.startListener(listenClient)
|
|
110
|
+
logger.info('LISTEN/NOTIFY active for job completion')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Schedules are in place before any claim loop runs, so a job the scheduler
|
|
114
|
+
// enqueues is never claimed ahead of its registration.
|
|
115
|
+
if (schedules !== undefined) {
|
|
116
|
+
const removed = await queue.syncSchedules(
|
|
117
|
+
db,
|
|
118
|
+
schedules.map(s => ({
|
|
119
|
+
name: s.job,
|
|
120
|
+
cron: s.cron,
|
|
121
|
+
...(s.timezone !== undefined ? { timezone: s.timezone } : {}),
|
|
122
|
+
...(s.payload !== undefined ? { payload: s.payload } : {}),
|
|
123
|
+
})),
|
|
124
|
+
jobTypes,
|
|
125
|
+
)
|
|
126
|
+
if (removed.length > 0) logger.info({ removed }, 'Removed schedules no longer declared')
|
|
127
|
+
queue.startScheduler(db, {
|
|
128
|
+
checkIntervalMs: config.schedulerIntervalMs ?? 15_000,
|
|
129
|
+
names: [...scheduledJobs],
|
|
130
|
+
onError: (err) => { logger.error({ err }, 'Scheduler tick failed'); void recordFault('scheduler', faultMessage(err)) },
|
|
131
|
+
onCheck: (outcome) => { beat('jobs.scheduler', outcome, config.schedulerIntervalMs ?? 15_000) },
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const dispatch = buildDispatchTable(allJobs)
|
|
136
|
+
const handledTypes = [...dispatch.keys()]
|
|
137
|
+
const instance = context as unknown as Manifold
|
|
138
|
+
const releaseGraphs = graphs.map(g => queue.onDeadLetter(
|
|
139
|
+
(tx, job) => g.worker.onDeadLetter(tx, job, { instance, database: config.database }),
|
|
140
|
+
))
|
|
141
|
+
const concurrency = config.concurrency ?? 1
|
|
142
|
+
const pollIntervalMs = config.pollIntervalMs ?? 1000
|
|
143
|
+
const shutdownDeadlineMs = config.shutdownDeadlineMs ?? 30_000
|
|
144
|
+
const heartbeatIntervalMs = config.heartbeatIntervalMs ?? 30_000
|
|
145
|
+
const priorityTimeouts = config.timeouts ?? {}
|
|
146
|
+
|
|
147
|
+
const maxConsecutiveClaimFailures = config.maxConsecutiveClaimFailures ?? 10
|
|
148
|
+
|
|
149
|
+
const shouldRun = { value: true }
|
|
150
|
+
// claim() retries transient conflicts until clean (unbounded), so
|
|
151
|
+
// shouldRun alone cannot end a loop stuck under contention — the abort
|
|
152
|
+
// signal is what makes shutdown observable inside claim and the sleeps.
|
|
153
|
+
const shutdown = new AbortController()
|
|
154
|
+
// Jobs this worker is running: the claim each holds and the controller that ends its handler.
|
|
155
|
+
// `endedBy` is set before the abort, so the loop knows the job was taken from it and must not fail it.
|
|
156
|
+
interface Running { readonly job: ClaimedJob; readonly controller: AbortController; endedBy: 'handed-back' | 'claim-lost' | null }
|
|
157
|
+
const running = new Map<string, Running>()
|
|
158
|
+
const eventJob = (job: ClaimedJob): JobEvent['job'] => ({
|
|
159
|
+
id: job.id, type: job.type, attempt: job.attempt, maxAttempts: job.maxAttempts, tenant: job.flow.tenant,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// Runs the app's callback as the job's tenant and actor, in its own transaction, as a child of the job's
|
|
163
|
+
// trace. Nothing it does reaches the caller: a throw is logged.
|
|
164
|
+
const emit = async (event: JobEvent, flow: ClaimedFlow): Promise<void> => {
|
|
165
|
+
const callback = config.onJobEvent
|
|
166
|
+
if (callback === undefined) return
|
|
167
|
+
try {
|
|
168
|
+
const auth = flow.actor !== null ? { userId: flow.actor } : null
|
|
169
|
+
const trace = isTracingEnabled(instance)
|
|
170
|
+
? openTrace({
|
|
171
|
+
instance,
|
|
172
|
+
entryKind: 'job',
|
|
173
|
+
parent: flow.traceId !== null && flow.rootId !== null
|
|
174
|
+
? { id: flow.traceId, rootId: flow.rootId, stepId: flow.stepId }
|
|
175
|
+
: null,
|
|
176
|
+
tenant: flow.tenant,
|
|
177
|
+
auth,
|
|
178
|
+
requestId: event.job.id,
|
|
179
|
+
sessionId: null,
|
|
180
|
+
})
|
|
181
|
+
: null
|
|
182
|
+
const ctx = createJobContext({
|
|
183
|
+
instance, jobId: event.job.id, jobType: event.job.type, attempt: event.job.attempt, maxAttempts: event.job.maxAttempts, tenant: flow.tenant, auth, trace,
|
|
184
|
+
})
|
|
185
|
+
const run = async (): Promise<void> => { await callback(event) }
|
|
186
|
+
const work = ctx.tenant === null
|
|
187
|
+
? runInContext(ctx, run)
|
|
188
|
+
: executeInTransaction(ctx, [dbName], null, 5000, run)
|
|
189
|
+
await finalizeTrace(instance, ctx.trace, work)
|
|
190
|
+
} catch (err) {
|
|
191
|
+
logger.error({ err, kind: event.kind, jobId: event.job.id }, 'onJobEvent callback failed')
|
|
192
|
+
void recordFault('job-event-callback', faultMessage(err), { kind: event.kind, jobId: event.job.id })
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// A claim that throws is never a quiet queue: it is logged with its
|
|
196
|
+
// Postgres code and counted. A successful claim clears the count; enough
|
|
197
|
+
// consecutive failures end the surface rather than let it poll forever.
|
|
198
|
+
let consecutiveClaimFailures = 0
|
|
199
|
+
|
|
200
|
+
const claimLoop = async () => {
|
|
201
|
+
const workerId = randomUUID()
|
|
202
|
+
while (shouldRun.value) {
|
|
203
|
+
try {
|
|
204
|
+
const claimed = await queue.claim(queueDb, workerId, shutdown.signal, handledTypes, instance.release)
|
|
205
|
+
consecutiveClaimFailures = 0
|
|
206
|
+
claimBeat({ ok: true })
|
|
207
|
+
if (claimed === null) {
|
|
208
|
+
await abortableDelay(pollIntervalMs, shutdown.signal)
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const handler = dispatch.get(claimed.type)
|
|
213
|
+
|
|
214
|
+
if (handler === undefined) {
|
|
215
|
+
const failedNoHandler = await queue.failAttempt(queueDb, claimed.id, new Error(
|
|
216
|
+
`[pipework] No handler registered for job type "${claimed.type}".\n\n` +
|
|
217
|
+
` Registered types: ${[...dispatch.keys()].join(', ')}\n` +
|
|
218
|
+
' This job was enqueued with a type that no handler recognizes.\n'
|
|
219
|
+
), claimed.claimToken)
|
|
220
|
+
if (failedNoHandler?.outcome === 'dead_letter') {
|
|
221
|
+
await emit({ kind: 'dead-lettered', cause: 'no-handler', error: failedNoHandler.error, job: eventJob(claimed) }, claimed.flow)
|
|
222
|
+
}
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const entry: Running = { job: claimed, controller: new AbortController(), endedBy: null }
|
|
227
|
+
running.set(claimed.id, entry)
|
|
228
|
+
const heartbeatInterval = setInterval(() => {
|
|
229
|
+
queue.heartbeat(queueDb, claimed.id, claimed.claimToken).then((held) => {
|
|
230
|
+
// A loop busy with a long job is alive.
|
|
231
|
+
claimBeat({ ok: true })
|
|
232
|
+
if (held || entry.endedBy !== null) return
|
|
233
|
+
entry.endedBy = 'claim-lost'
|
|
234
|
+
entry.controller.abort(new Error(
|
|
235
|
+
`[pipework] Job "${claimed.type}" (${claimed.id}) lost its claim: the row was reaped or taken by another worker.`,
|
|
236
|
+
))
|
|
237
|
+
return emit({ kind: 'claim-lost', job: eventJob(claimed) }, claimed.flow)
|
|
238
|
+
}).catch((err: unknown) => {
|
|
239
|
+
logger.error({ err, jobId: claimed.id }, 'Job heartbeat failed')
|
|
240
|
+
void recordFault('queue-heartbeat', faultMessage(err), { jobId: claimed.id })
|
|
241
|
+
})
|
|
242
|
+
}, heartbeatIntervalMs)
|
|
243
|
+
|
|
244
|
+
const timedOut = { value: false }
|
|
245
|
+
try {
|
|
246
|
+
const timeoutMs = resolvePriorityTimeout(claimed, handler, priorityTimeouts)
|
|
247
|
+
const result = await executeWithTimeout(
|
|
248
|
+
(timeoutSignal) => {
|
|
249
|
+
if (timeoutSignal !== undefined) {
|
|
250
|
+
timeoutSignal.addEventListener('abort', () => {
|
|
251
|
+
timedOut.value = true
|
|
252
|
+
entry.controller.abort(timeoutSignal.reason)
|
|
253
|
+
}, { once: true })
|
|
254
|
+
}
|
|
255
|
+
return executeJob(handler, {
|
|
256
|
+
instance,
|
|
257
|
+
jobId: claimed.id,
|
|
258
|
+
jobType: claimed.type,
|
|
259
|
+
attempt: claimed.attempt,
|
|
260
|
+
maxAttempts: claimed.maxAttempts,
|
|
261
|
+
// A scheduled type enqueued with no flow came from the scheduler: its own root trace.
|
|
262
|
+
entryKind: scheduledJobs.has(claimed.type) && claimed.flow.traceId === null && claimed.flow.tenant === null
|
|
263
|
+
? 'cron'
|
|
264
|
+
: 'job',
|
|
265
|
+
input: claimed.payload,
|
|
266
|
+
tenant: claimed.flow.tenant,
|
|
267
|
+
parent: claimed.flow.traceId !== null && claimed.flow.rootId !== null
|
|
268
|
+
? { id: claimed.flow.traceId, rootId: claimed.flow.rootId, stepId: claimed.flow.stepId }
|
|
269
|
+
: null,
|
|
270
|
+
...(claimed.flow.actor !== null ? { auth: { userId: claimed.flow.actor } } : {}),
|
|
271
|
+
...(claimed.previous !== null
|
|
272
|
+
? { restart: { reason: claimed.previous.reason, previousWorkerId: claimed.previous.workerId } }
|
|
273
|
+
: {}),
|
|
274
|
+
signal: entry.controller.signal,
|
|
275
|
+
})
|
|
276
|
+
},
|
|
277
|
+
timeoutMs,
|
|
278
|
+
claimed.type,
|
|
279
|
+
claimed.id,
|
|
280
|
+
)
|
|
281
|
+
await queue.complete(queueDb, claimed.id, result, claimed.claimToken)
|
|
282
|
+
} catch (err) {
|
|
283
|
+
// A job taken from this worker (handed back, claim lost) is not its failure to record.
|
|
284
|
+
if (entry.endedBy === null) {
|
|
285
|
+
if (err instanceof JobLaterError) await queue.later(queueDb, claimed.id, err, claimed.claimToken)
|
|
286
|
+
else if (err instanceof QuotaExhaustedError) {
|
|
287
|
+
const delayMs = Math.max(0, err.resetAt.getTime() - Date.now())
|
|
288
|
+
await queue.later(queueDb, claimed.id, new JobLaterError(err.message, { delayMs, cause: err }), claimed.claimToken)
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
const permanent = err instanceof JobPermanentError
|
|
292
|
+
const failed = await queue.failAttempt(queueDb, claimed.id, err, claimed.claimToken, permanent ? { permanent: true } : undefined)
|
|
293
|
+
if (failed?.outcome === 'dead_letter') {
|
|
294
|
+
const cause = permanent ? 'permanent' : timedOut.value ? 'timeout' : 'failed'
|
|
295
|
+
await emit({ kind: 'dead-lettered', cause, error: failed.error, job: eventJob(claimed) }, claimed.flow)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} finally {
|
|
300
|
+
clearInterval(heartbeatInterval)
|
|
301
|
+
running.delete(claimed.id)
|
|
302
|
+
}
|
|
303
|
+
} catch (err) {
|
|
304
|
+
if (shutdown.signal.aborted) return
|
|
305
|
+
|
|
306
|
+
consecutiveClaimFailures++
|
|
307
|
+
claimBeat({ ok: false, error: describeError(err).message })
|
|
308
|
+
logger.error(
|
|
309
|
+
{ err, pgCode: pgErrorCode(err), consecutiveFailures: consecutiveClaimFailures, maxConsecutiveClaimFailures },
|
|
310
|
+
'Job claim failed',
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
if (consecutiveClaimFailures >= maxConsecutiveClaimFailures) {
|
|
314
|
+
logger.fatal(
|
|
315
|
+
{ err, pgCode: pgErrorCode(err), consecutiveFailures: consecutiveClaimFailures },
|
|
316
|
+
`Worker surface stopping: ${String(consecutiveClaimFailures)} consecutive claim failures`,
|
|
317
|
+
)
|
|
318
|
+
shouldRun.value = false
|
|
319
|
+
shutdown.abort()
|
|
320
|
+
process.exitCode = 1
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
await abortableDelay(pollIntervalMs, shutdown.signal)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const workers = Array.from({ length: concurrency }, () => claimLoop())
|
|
330
|
+
|
|
331
|
+
let reaperTimer: ReturnType<typeof setInterval> | null = null
|
|
332
|
+
const reaperByDefault = config.reaper === undefined && scheduledJobs.size > 0
|
|
333
|
+
if (reaperByDefault) logger.info('Reaper on: this worker declares schedules (set reaper: false to turn it off)')
|
|
334
|
+
if (reaperByDefault || (config.reaper !== undefined && config.reaper !== false)) {
|
|
335
|
+
const reaperIntervalMs = typeof config.reaper === 'object'
|
|
336
|
+
? (config.reaper.intervalMs ?? 60_000)
|
|
337
|
+
: 60_000
|
|
338
|
+
reaperTimer = setInterval(() => {
|
|
339
|
+
void queue.reap(queueDb).then(async (reaped) => {
|
|
340
|
+
beat('jobs.reaper', { ok: true }, reaperIntervalMs)
|
|
341
|
+
if (reaped.length === 0) return
|
|
342
|
+
logger.info({ reaped: reaped.length }, `Reaped ${String(reaped.length)} stale job(s)`)
|
|
343
|
+
for (const r of reaped) {
|
|
344
|
+
await emit({
|
|
345
|
+
kind: 'reaped',
|
|
346
|
+
outcome: r.outcome,
|
|
347
|
+
job: { id: r.jobId, type: r.type, attempt: r.attempt, maxAttempts: r.maxAttempts, tenant: r.flow.tenant },
|
|
348
|
+
}, r.flow)
|
|
349
|
+
}
|
|
350
|
+
}).catch((err) => {
|
|
351
|
+
beat('jobs.reaper', { ok: false, error: describeError(err).message }, reaperIntervalMs)
|
|
352
|
+
logger.error({ err }, 'Reaper failed')
|
|
353
|
+
void recordFault('queue-reaper', faultMessage(err))
|
|
354
|
+
})
|
|
355
|
+
}, reaperIntervalMs)
|
|
356
|
+
reaperTimer.unref()
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
kind: 'worker',
|
|
361
|
+
|
|
362
|
+
fireSchedule(job: string): Promise<string> {
|
|
363
|
+
if (!scheduledJobs.has(job)) {
|
|
364
|
+
return Promise.reject(new Error(
|
|
365
|
+
`[pipework] No schedule declared for job "${job}" on this worker surface.\n\n` +
|
|
366
|
+
` Declared: ${[...scheduledJobs].join(', ') || '(none)'}\n`,
|
|
367
|
+
))
|
|
368
|
+
}
|
|
369
|
+
return queue.fireSchedule(db, job)
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
async stop(force?: AbortSignal) {
|
|
373
|
+
logger.info({ inFlight: running.size, deadlineMs: shutdownDeadlineMs }, 'Worker surface draining')
|
|
374
|
+
queue.stopScheduler()
|
|
375
|
+
shouldRun.value = false
|
|
376
|
+
shutdown.abort()
|
|
377
|
+
|
|
378
|
+
if (reaperTimer !== null) {
|
|
379
|
+
clearInterval(reaperTimer)
|
|
380
|
+
reaperTimer = null
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (config.listen === true) {
|
|
384
|
+
await queue.stopListener()
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const deadline = Date.now() + shutdownDeadlineMs
|
|
388
|
+
|
|
389
|
+
// Defense in depth behind the abort signal: a claim loop that
|
|
390
|
+
// somehow ignores cancellation must not wedge stop(). The deadline
|
|
391
|
+
// leg is aborted when the loops win so its timer cannot hold the
|
|
392
|
+
// event loop open after a clean shutdown.
|
|
393
|
+
const deadlineAc = new AbortController()
|
|
394
|
+
const waitEnd = AbortSignal.any(force === undefined ? [deadlineAc.signal] : [deadlineAc.signal, force])
|
|
395
|
+
const loopsExited = await Promise.race([
|
|
396
|
+
Promise.allSettled(workers).then(() => { deadlineAc.abort(); return true }),
|
|
397
|
+
abortableDelay(shutdownDeadlineMs, waitEnd).then(() => false),
|
|
398
|
+
])
|
|
399
|
+
if (!loopsExited) {
|
|
400
|
+
logger.warn('Claim loops did not exit within the shutdown deadline')
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
while (running.size > 0 && Date.now() < deadline && force?.aborted !== true) {
|
|
404
|
+
logger.info({ inFlight: running.size, remainingMs: deadline - Date.now() }, 'Waiting for in-flight jobs')
|
|
405
|
+
await abortableDelay(Math.min(500, deadline - Date.now()), force)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (running.size > 0) {
|
|
409
|
+
const stranded = [...running.values()]
|
|
410
|
+
logger.warn({ inFlight: stranded.length }, 'Shutdown deadline reached with in-flight jobs; handing them back')
|
|
411
|
+
for (const entry of stranded) {
|
|
412
|
+
entry.endedBy = 'handed-back'
|
|
413
|
+
entry.controller.abort(new Error(
|
|
414
|
+
`[pipework] Job "${entry.job.type}" (${entry.job.id}) aborted: the worker is stopping and handed it back to the queue.`,
|
|
415
|
+
))
|
|
416
|
+
}
|
|
417
|
+
for (const { job } of stranded) {
|
|
418
|
+
try {
|
|
419
|
+
if (await queue.handBack(queueDb, job.id, job.claimToken)) {
|
|
420
|
+
await emit({ kind: 'handed-back', job: eventJob(job) }, job.flow)
|
|
421
|
+
}
|
|
422
|
+
} catch (err) {
|
|
423
|
+
logger.error({ err, jobId: job.id }, 'Handing back a job failed')
|
|
424
|
+
void recordFault('queue-handback', faultMessage(err), { jobId: job.id })
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
logger.info('Worker surface drained cleanly')
|
|
429
|
+
}
|
|
430
|
+
for (const release of releaseGraphs) release()
|
|
431
|
+
releaseFaults()
|
|
432
|
+
},
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return surface
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function buildDispatchTable(
|
|
441
|
+
jobs: readonly Handler<Record<string, unknown>, unknown>[],
|
|
442
|
+
): Map<string, Handler<Record<string, unknown>, unknown>> {
|
|
443
|
+
const table = new Map<string, Handler<Record<string, unknown>, unknown>>()
|
|
444
|
+
for (const job of jobs) {
|
|
445
|
+
const jobType = job.meta.jobType
|
|
446
|
+
if (typeof jobType === 'string') {
|
|
447
|
+
table.set(jobType, job)
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return table
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function validateJobHandlers(jobs: readonly Handler<Record<string, unknown>, unknown>[]): void {
|
|
454
|
+
const seen = new Set<string>()
|
|
455
|
+
for (const job of jobs) {
|
|
456
|
+
if (!job.meta.isJob || job.meta.jobType === null) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
'[pipework] All handlers passed to surface.worker({ jobs }) must be created with .job("type-name").\n\n' +
|
|
459
|
+
' Example:\n' +
|
|
460
|
+
' fitting().use().job("send-email").input(schema).fit(handler)\n'
|
|
461
|
+
)
|
|
462
|
+
}
|
|
463
|
+
if (seen.has(job.meta.jobType)) {
|
|
464
|
+
throw new Error(
|
|
465
|
+
`[pipework] Duplicate job type "${job.meta.jobType}" in worker surface.\n\n` +
|
|
466
|
+
' Each job type must have exactly one handler. If you need multiple handlers,\n' +
|
|
467
|
+
' create separate worker surfaces.\n'
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
seen.add(job.meta.jobType)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function validateSchedules(
|
|
475
|
+
schedules: readonly { readonly job: string; readonly cron: string }[],
|
|
476
|
+
jobTypes: readonly string[],
|
|
477
|
+
): void {
|
|
478
|
+
const seen = new Set<string>()
|
|
479
|
+
for (const s of schedules) {
|
|
480
|
+
if (!jobTypes.includes(s.job)) {
|
|
481
|
+
throw new Error(
|
|
482
|
+
`[pipework] Schedule for job "${s.job}", but this worker surface handles no such job type.\n\n` +
|
|
483
|
+
` Handled types: ${jobTypes.join(', ') || '(none)'}\n` +
|
|
484
|
+
' A schedule fires a job this surface runs; put the schedule on the surface that handles it.\n',
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
if (seen.has(s.job)) {
|
|
488
|
+
throw new Error(`[pipework] Job "${s.job}" is scheduled twice on this worker surface; a job type has one schedule.\n`)
|
|
489
|
+
}
|
|
490
|
+
seen.add(s.job)
|
|
491
|
+
parseCron(s.cron)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function collectWorkerDatabases(config: WorkerSurfaceConfig): string[] {
|
|
496
|
+
const dbs = new Set<string>()
|
|
497
|
+
if (config.database !== undefined) {
|
|
498
|
+
dbs.add(config.database)
|
|
499
|
+
} else {
|
|
500
|
+
dbs.add(DEFAULT_DB)
|
|
501
|
+
}
|
|
502
|
+
for (const job of config.jobs) {
|
|
503
|
+
for (const db of job.meta.databases) {
|
|
504
|
+
dbs.add(db)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return [...dbs]
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function collectJobTypes(jobs: readonly Handler<Record<string, unknown>, unknown>[]): string[] {
|
|
511
|
+
const types: string[] = []
|
|
512
|
+
for (const j of jobs) {
|
|
513
|
+
if (typeof j.meta.jobType === 'string') {
|
|
514
|
+
types.push(j.meta.jobType)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return types
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function resolvePriorityTimeout(
|
|
521
|
+
claimed: { readonly priority: number },
|
|
522
|
+
handler: Handler<Record<string, unknown>, unknown>,
|
|
523
|
+
priorityTimeouts: Readonly<Record<number, number>>,
|
|
524
|
+
): number | undefined {
|
|
525
|
+
const fromPriority = priorityTimeouts[claimed.priority]
|
|
526
|
+
if (fromPriority !== undefined) return fromPriority
|
|
527
|
+
return handler.meta.jobTimeout ?? undefined
|
|
528
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ValidationIssue } from './types.js'
|
|
2
|
+
import type { ResolvedDatabase } from '../config/load.js'
|
|
3
|
+
|
|
4
|
+
export async function validateConnectivity(
|
|
5
|
+
databases: ReadonlyMap<string, ResolvedDatabase>,
|
|
6
|
+
timeoutMs = 5000,
|
|
7
|
+
): Promise<ValidationIssue[]> {
|
|
8
|
+
const issues: ValidationIssue[] = []
|
|
9
|
+
|
|
10
|
+
const checks = [...databases.entries()].map(async ([name, db]) => {
|
|
11
|
+
try {
|
|
12
|
+
// A platform-mode database is reached at runtime as the platform role; the owner is not dialled.
|
|
13
|
+
await testConnection(db.rls?.platformRole !== undefined && db.systemUrl !== undefined ? db.systemUrl : db.url, timeoutMs)
|
|
14
|
+
} catch (err) {
|
|
15
|
+
issues.push({
|
|
16
|
+
phase: 'database',
|
|
17
|
+
message: `cannot connect to database "${name}": ${err instanceof Error ? err.message : String(err)}`,
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
await Promise.all(checks)
|
|
23
|
+
return issues
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function testConnection(url: string, timeoutMs: number): Promise<void> {
|
|
27
|
+
const { default: postgres } = await import('postgres')
|
|
28
|
+
|
|
29
|
+
const sql = postgres(url, {
|
|
30
|
+
max: 1,
|
|
31
|
+
idle_timeout: 1,
|
|
32
|
+
connect_timeout: Math.ceil(timeoutMs / 1000),
|
|
33
|
+
onnotice: () => {},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await sql`SELECT 1`
|
|
38
|
+
} finally {
|
|
39
|
+
await sql.end({ timeout: 1 })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The probe's `connect_timeout` option. A number pins it; `null` omits it so
|
|
45
|
+
* postgres.js reads PGCONNECT_TIMEOUT (seconds), else its 30 s default.
|
|
46
|
+
*/
|
|
47
|
+
export function probeConnectTimeout(timeoutMs: number | null): { connect_timeout?: number } {
|
|
48
|
+
return timeoutMs === null ? {} : { connect_timeout: Math.ceil(timeoutMs / 1000) }
|
|
49
|
+
}
|