pipework 0.252.0 → 0.252.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/package.json +4 -3
- package/src/async/jobs/abortable-delay.ts +18 -0
- package/src/async/jobs/claims.ts +52 -0
- package/src/async/jobs/cron.ts +179 -0
- package/src/async/jobs/errors.ts +56 -0
- package/src/async/jobs/exclusion.ts +117 -0
- package/src/async/jobs/execute.ts +219 -0
- package/src/async/jobs/index.ts +10 -0
- package/src/async/jobs/listener.ts +135 -0
- package/src/async/jobs/namespace.ts +20 -0
- package/src/async/jobs/queue-door.ts +44 -0
- package/src/async/jobs/queue.ts +1012 -0
- package/src/async/jobs/run-script.ts +67 -0
- package/src/async/jobs/scheduler.ts +218 -0
- package/src/async/jobs/schedules-table.ts +37 -0
- package/src/async/jobs/serialize-error.ts +48 -0
- package/src/async/jobs/table.ts +191 -0
- package/src/async/pipeline/define.ts +53 -0
- package/src/async/pipeline/errors.ts +35 -0
- package/src/async/pipeline/execute.ts +53 -0
- package/src/async/pipeline/index.ts +13 -0
- package/src/async/pipeline/namespace.ts +12 -0
- package/src/async/pipeline/resume.ts +67 -0
- package/src/async/pipeline/run-steps.ts +98 -0
- package/src/async/pipeline/types.ts +57 -0
- package/src/async/state-machine/define.ts +64 -0
- package/src/async/state-machine/errors.ts +29 -0
- package/src/async/state-machine/index.ts +3 -0
- package/src/async/state-machine/namespace.ts +12 -0
- package/src/async/state-machine/transition.ts +90 -0
- package/src/async/state-machine/types.ts +37 -0
- package/src/async/steps/errors.ts +31 -0
- package/src/async/steps/graph.ts +1214 -0
- package/src/async/steps/hash.ts +40 -0
- package/src/async/steps/index.ts +7 -0
- package/src/async/steps/namespace.ts +7 -0
- package/src/async/steps/table.ts +97 -0
- package/src/async/steps/types.ts +318 -0
- package/src/audit/chain/index.ts +9 -0
- package/src/audit/chain/install.ts +113 -0
- package/src/audit/chain/retire.ts +52 -0
- package/src/audit/chain/verify.ts +257 -0
- package/src/audit/query/cursor.ts +37 -0
- package/src/audit/query/index.ts +9 -0
- package/src/audit/query/query.ts +223 -0
- package/src/audit/schema/audit-record-fields.ts +68 -0
- package/src/audit/schema/audit-record.ts +23 -0
- package/src/auth/auth/audit.ts +31 -0
- package/src/auth/auth/chain.ts +84 -0
- package/src/auth/auth/cookie.ts +136 -0
- package/src/auth/auth/email-links.ts +311 -0
- package/src/auth/auth/guard.ts +128 -0
- package/src/auth/auth/index.ts +18 -0
- package/src/auth/auth/multi-org.ts +337 -0
- package/src/auth/auth/namespace.ts +34 -0
- package/src/auth/auth/parse-expiry.ts +13 -0
- package/src/auth/auth/passwords.ts +110 -0
- package/src/auth/auth/schema.ts +110 -0
- package/src/auth/auth/sessions.ts +565 -0
- package/src/auth/auth/sign-in.ts +156 -0
- package/src/auth/auth/signing-keys.ts +69 -0
- package/src/auth/auth/types.ts +26 -0
- package/src/auth/auth/uuid.ts +10 -0
- package/src/auth/rbac/cache.ts +55 -0
- package/src/auth/rbac/check.ts +30 -0
- package/src/auth/rbac/create.ts +35 -0
- package/src/auth/rbac/index.ts +5 -0
- package/src/auth/rbac/middleware.ts +20 -0
- package/src/auth/rbac/namespace.ts +15 -0
- package/src/auth/rbac/resolve.ts +31 -0
- package/src/auth/rbac/schema.sql +31 -0
- package/src/auth/rbac/seed.ts +67 -0
- package/src/auth/rbac/tables.ts +32 -0
- package/src/auth/rbac/types.ts +65 -0
- package/src/auth/tenant/extract.ts +33 -0
- package/src/auth/tenant/in-tenant.ts +49 -0
- package/src/auth/tenant/index.ts +7 -0
- package/src/auth/tenant/namespace.ts +23 -0
- package/src/auth/tenant/propagate.ts +20 -0
- package/src/auth/tenant/rls.ts +120 -0
- package/src/auth/tenant/scope.ts +33 -0
- package/src/auth/tenant/scoped-db.ts +581 -0
- package/src/auth/tenant/types.ts +14 -0
- package/src/auth/tenant/validate.ts +27 -0
- package/src/cli/commands/add.ts +158 -0
- package/src/cli/commands/alerts.ts +98 -0
- package/src/cli/commands/check.ts +1418 -0
- package/src/cli/commands/codemod.ts +78 -0
- package/src/cli/commands/db.ts +356 -0
- package/src/cli/commands/dev.ts +101 -0
- package/src/cli/commands/generate.ts +75 -0
- package/src/cli/commands/init.ts +36 -0
- package/src/cli/commands/install.ts +28 -0
- package/src/cli/commands/lint.ts +62 -0
- package/src/cli/commands/migrate.ts +132 -0
- package/src/cli/commands/openapi.ts +63 -0
- package/src/cli/commands/remove.ts +72 -0
- package/src/cli/commands/run.ts +50 -0
- package/src/cli/commands/scope.ts +54 -0
- package/src/cli/commands/serve.ts +68 -0
- package/src/cli/commands/shard.ts +31 -0
- package/src/cli/commands/shared/config-pipework-version.ts +28 -0
- package/src/cli/commands/shared/find-config.ts +22 -0
- package/src/cli/commands/shared/load-config.ts +35 -0
- package/src/cli/commands/shared/pnpm-install.ts +95 -0
- package/src/cli/commands/shared/report-removed.ts +8 -0
- package/src/cli/commands/sync.ts +32 -0
- package/src/cli/commands/test.ts +307 -0
- package/src/cli/commands/upgrade.ts +48 -0
- package/src/cli/dev-bootstrap.ts +24 -0
- package/src/cli/errors.ts +10 -0
- package/src/cli/index.ts +303 -0
- package/src/cli/output.ts +17 -0
- package/src/cli/port.ts +15 -0
- package/src/cli/surface-kinds.ts +20 -0
- package/src/client/index.ts +4 -0
- package/src/client/poll.ts +130 -0
- package/src/client/report-fault.ts +119 -0
- package/src/core/config/app-pool.ts +19 -0
- package/src/core/config/aws-env.ts +29 -0
- package/src/core/config/base-dir.ts +24 -0
- package/src/core/config/discover.ts +64 -0
- package/src/core/config/env-resolve.ts +87 -0
- package/src/core/config/env-types.ts +116 -0
- package/src/core/config/env.ts +55 -0
- package/src/core/config/errors.ts +9 -0
- package/src/core/config/import-config.ts +40 -0
- package/src/core/config/index.ts +8 -0
- package/src/core/config/load.ts +417 -0
- package/src/core/config/namespace.ts +14 -0
- package/src/core/config/resolve.ts +195 -0
- package/src/core/config/schema.ts +488 -0
- package/src/core/config/statement-observer.ts +48 -0
- package/src/core/config/ts-inline-hooks.ts +75 -0
- package/src/core/config/ts-register.ts +4 -0
- package/src/core/config/ts-resolve-hooks.ts +101 -0
- package/src/core/errors/base.ts +49 -0
- package/src/core/errors/http.ts +146 -0
- package/src/core/errors/index.ts +2 -0
- package/src/core/errors/namespace.ts +31 -0
- package/src/core/lifecycle/drain.ts +33 -0
- package/src/core/lifecycle/health.ts +72 -0
- package/src/core/lifecycle/index.ts +4 -0
- package/src/core/lifecycle/namespace.ts +7 -0
- package/src/core/lifecycle/startup.ts +102 -0
- package/src/core/lifecycle/types.ts +36 -0
- package/src/core/logging/context.ts +60 -0
- package/src/core/logging/error-chain.ts +68 -0
- package/src/core/logging/index.ts +5 -0
- package/src/core/logging/logger.ts +65 -0
- package/src/core/logging/namespace.ts +16 -0
- package/src/core/logging/proxy.ts +22 -0
- package/src/core/logging/redact-match.ts +24 -0
- package/src/core/logging/types.ts +47 -0
- package/src/core/pipework.ts +566 -0
- package/src/core/runtime/broker.ts +69 -0
- package/src/core/surface/http.ts +133 -0
- package/src/core/surface/index.ts +11 -0
- package/src/core/surface/job-timeout.ts +40 -0
- package/src/core/surface/namespace.ts +13 -0
- package/src/core/surface/script.ts +33 -0
- package/src/core/surface/start.ts +142 -0
- package/src/core/surface/types.ts +225 -0
- package/src/core/surface/validate.ts +88 -0
- package/src/core/surface/worker.ts +528 -0
- package/src/core/validation/connectivity.ts +49 -0
- package/src/core/validation/domain.ts +99 -0
- package/src/core/validation/index.ts +5 -0
- package/src/core/validation/queue-tables.ts +312 -0
- package/src/core/validation/rls-role.ts +138 -0
- package/src/core/validation/startup.ts +51 -0
- package/src/core/validation/surfaces.ts +65 -0
- package/src/core/validation/types.ts +25 -0
- package/src/data/db/advisory-lock.ts +55 -0
- package/src/data/db/aggregates.ts +172 -0
- package/src/data/db/array-elements.ts +111 -0
- package/src/data/db/bulk-set.ts +53 -0
- package/src/data/db/col/index.ts +14 -0
- package/src/data/db/composed.ts +53 -0
- package/src/data/db/db.ts +276 -0
- package/src/data/db/excluded.ts +16 -0
- package/src/data/db/expressions.ts +473 -0
- package/src/data/db/filter.ts +90 -0
- package/src/data/db/function-source.ts +114 -0
- package/src/data/db/identifiers.ts +16 -0
- package/src/data/db/idx/index.ts +9 -0
- package/src/data/db/in-list.ts +49 -0
- package/src/data/db/index.ts +9 -0
- package/src/data/db/insert-split.ts +137 -0
- package/src/data/db/instrumentation.ts +82 -0
- package/src/data/db/maintenance.ts +28 -0
- package/src/data/db/namespace.ts +107 -0
- package/src/data/db/owner-labels.ts +9 -0
- package/src/data/db/pool.ts +187 -0
- package/src/data/db/query-label.ts +13 -0
- package/src/data/db/query-watch.ts +116 -0
- package/src/data/db/raw-client-warning.ts +37 -0
- package/src/data/db/readonly.ts +23 -0
- package/src/data/db/reservation.ts +151 -0
- package/src/data/db/schema-features.ts +14 -0
- package/src/data/db/serializable.ts +83 -0
- package/src/data/db/sql.ts +1 -0
- package/src/data/db/timeouts.ts +76 -0
- package/src/data/db/types.ts +18 -0
- package/src/data/db/unbound.ts +33 -0
- package/src/data/db/values.ts +29 -0
- package/src/data/domain/brand.ts +15 -0
- package/src/data/domain/build-factory.ts +193 -0
- package/src/data/domain/build-schema.ts +161 -0
- package/src/data/domain/build-table.ts +355 -0
- package/src/data/domain/define.ts +239 -0
- package/src/data/domain/external-registry.ts +34 -0
- package/src/data/domain/external.ts +77 -0
- package/src/data/domain/field.ts +279 -0
- package/src/data/domain/index.ts +13 -0
- package/src/data/domain/naming.ts +6 -0
- package/src/data/domain/projections.ts +57 -0
- package/src/data/domain/trace-safe-projection.ts +31 -0
- package/src/data/domain/traits.ts +108 -0
- package/src/data/domain/types.ts +289 -0
- package/src/data/jsonb/composed.ts +18 -0
- package/src/data/jsonb/index.ts +1 -0
- package/src/data/jsonb/namespace.ts +30 -0
- package/src/data/jsonb/query.ts +86 -0
- package/src/data/migrate/append-only-generate.ts +47 -0
- package/src/data/migrate/applied.ts +47 -0
- package/src/data/migrate/apply.ts +309 -0
- package/src/data/migrate/check.ts +119 -0
- package/src/data/migrate/diff.ts +357 -0
- package/src/data/migrate/door-generate.ts +130 -0
- package/src/data/migrate/framework-rls.ts +58 -0
- package/src/data/migrate/generate.ts +201 -0
- package/src/data/migrate/history.ts +215 -0
- package/src/data/migrate/index.ts +9 -0
- package/src/data/migrate/internal-definitions.ts +175 -0
- package/src/data/migrate/post-process.ts +73 -0
- package/src/data/migrate/pragma.ts +73 -0
- package/src/data/migrate/provision-framework.ts +147 -0
- package/src/data/migrate/rls-generate.ts +61 -0
- package/src/data/migrate/run.ts +124 -0
- package/src/data/migrate/safety.ts +167 -0
- package/src/data/migrate/snapshot.ts +368 -0
- package/src/data/migrate/sql-emit.ts +392 -0
- package/src/data/migrate/statements.ts +312 -0
- package/src/data/query/alias.ts +126 -0
- package/src/data/query/batch.ts +8 -0
- package/src/data/query/cache/core/cache.ts +78 -0
- package/src/data/query/cache/core/index.ts +1 -0
- package/src/data/query/cache/core/types.ts +29 -0
- package/src/data/query/cache/readme.md +230 -0
- package/src/data/query/casing.ts +76 -0
- package/src/data/query/collation.ts +11 -0
- package/src/data/query/column-builder.ts +370 -0
- package/src/data/query/column.ts +150 -0
- package/src/data/query/entity.ts +42 -0
- package/src/data/query/errors.ts +33 -0
- package/src/data/query/index.ts +14 -0
- package/src/data/query/logger.ts +47 -0
- package/src/data/query/operations.ts +45 -0
- package/src/data/query/pg-core/alias.ts +12 -0
- package/src/data/query/pg-core/checks.ts +32 -0
- package/src/data/query/pg-core/columns/all.ts +71 -0
- package/src/data/query/pg-core/columns/bigint.ts +110 -0
- package/src/data/query/pg-core/columns/bigserial.ts +128 -0
- package/src/data/query/pg-core/columns/boolean.ts +43 -0
- package/src/data/query/pg-core/columns/char.ts +85 -0
- package/src/data/query/pg-core/columns/cidr.ts +43 -0
- package/src/data/query/pg-core/columns/common.ts +366 -0
- package/src/data/query/pg-core/columns/custom.ts +232 -0
- package/src/data/query/pg-core/columns/date.common.ts +15 -0
- package/src/data/query/pg-core/columns/date.ts +112 -0
- package/src/data/query/pg-core/columns/double-precision.ts +55 -0
- package/src/data/query/pg-core/columns/enum.ts +201 -0
- package/src/data/query/pg-core/columns/index.ts +35 -0
- package/src/data/query/pg-core/columns/inet.ts +43 -0
- package/src/data/query/pg-core/columns/int.common.ts +57 -0
- package/src/data/query/pg-core/columns/integer.ts +53 -0
- package/src/data/query/pg-core/columns/interval.ts +83 -0
- package/src/data/query/pg-core/columns/json.ts +58 -0
- package/src/data/query/pg-core/columns/jsonb.ts +56 -0
- package/src/data/query/pg-core/columns/line.ts +117 -0
- package/src/data/query/pg-core/columns/macaddr.ts +43 -0
- package/src/data/query/pg-core/columns/macaddr8.ts +43 -0
- package/src/data/query/pg-core/columns/numeric.ts +227 -0
- package/src/data/query/pg-core/columns/point.ts +127 -0
- package/src/data/query/pg-core/columns/postgis_extension/geometry.ts +119 -0
- package/src/data/query/pg-core/columns/postgis_extension/utils.ts +47 -0
- package/src/data/query/pg-core/columns/real.ts +58 -0
- package/src/data/query/pg-core/columns/serial.ts +55 -0
- package/src/data/query/pg-core/columns/smallint.ts +53 -0
- package/src/data/query/pg-core/columns/smallserial.ts +60 -0
- package/src/data/query/pg-core/columns/text.ts +67 -0
- package/src/data/query/pg-core/columns/time.ts +72 -0
- package/src/data/query/pg-core/columns/timestamp.ts +163 -0
- package/src/data/query/pg-core/columns/uuid.ts +51 -0
- package/src/data/query/pg-core/columns/varchar.ts +89 -0
- package/src/data/query/pg-core/columns/vector_extension/bit.ts +68 -0
- package/src/data/query/pg-core/columns/vector_extension/halfvec.ts +80 -0
- package/src/data/query/pg-core/columns/vector_extension/sparsevec.ts +67 -0
- package/src/data/query/pg-core/columns/vector_extension/vector.ts +80 -0
- package/src/data/query/pg-core/db.ts +674 -0
- package/src/data/query/pg-core/dialect.ts +1404 -0
- package/src/data/query/pg-core/expressions.ts +25 -0
- package/src/data/query/pg-core/foreign-keys.ts +130 -0
- package/src/data/query/pg-core/index.ts +21 -0
- package/src/data/query/pg-core/indexes.ts +276 -0
- package/src/data/query/pg-core/policies.ts +56 -0
- package/src/data/query/pg-core/primary-keys.ts +62 -0
- package/src/data/query/pg-core/query-builders/count.ts +86 -0
- package/src/data/query/pg-core/query-builders/delete.ts +293 -0
- package/src/data/query/pg-core/query-builders/index.ts +7 -0
- package/src/data/query/pg-core/query-builders/insert.ts +452 -0
- package/src/data/query/pg-core/query-builders/query-builder.ts +123 -0
- package/src/data/query/pg-core/query-builders/query.ts +157 -0
- package/src/data/query/pg-core/query-builders/raw.ts +49 -0
- package/src/data/query/pg-core/query-builders/refresh-materialized-view.ts +108 -0
- package/src/data/query/pg-core/query-builders/select.ts +1346 -0
- package/src/data/query/pg-core/query-builders/select.types.ts +437 -0
- package/src/data/query/pg-core/query-builders/update.ts +626 -0
- package/src/data/query/pg-core/roles.ts +41 -0
- package/src/data/query/pg-core/schema.ts +72 -0
- package/src/data/query/pg-core/sequence.ts +41 -0
- package/src/data/query/pg-core/session.ts +292 -0
- package/src/data/query/pg-core/subquery.ts +46 -0
- package/src/data/query/pg-core/table.ts +252 -0
- package/src/data/query/pg-core/unique-constraint.ts +73 -0
- package/src/data/query/pg-core/utils/array.ts +95 -0
- package/src/data/query/pg-core/utils/index.ts +1 -0
- package/src/data/query/pg-core/utils.ts +104 -0
- package/src/data/query/pg-core/view-base.ts +14 -0
- package/src/data/query/pg-core/view-common.ts +1 -0
- package/src/data/query/pg-core/view.ts +435 -0
- package/src/data/query/pg-core/with-builder.ts +41 -0
- package/src/data/query/postgres-js/README.md +46 -0
- package/src/data/query/postgres-js/array-parser.ts +45 -0
- package/src/data/query/postgres-js/driver-error-boundary.ts +149 -0
- package/src/data/query/postgres-js/driver.ts +163 -0
- package/src/data/query/postgres-js/index.ts +2 -0
- package/src/data/query/postgres-js/session.ts +234 -0
- package/src/data/query/primary-key.ts +11 -0
- package/src/data/query/query-builders/query-builder.ts +19 -0
- package/src/data/query/query-builders/select.types.ts +175 -0
- package/src/data/query/query-promise.ts +35 -0
- package/src/data/query/relations.ts +725 -0
- package/src/data/query/runnable-query.ts +12 -0
- package/src/data/query/selection-proxy.ts +121 -0
- package/src/data/query/session.ts +8 -0
- package/src/data/query/sql/expressions/conditions.ts +741 -0
- package/src/data/query/sql/expressions/index.ts +2 -0
- package/src/data/query/sql/expressions/select.ts +45 -0
- package/src/data/query/sql/functions/aggregate.ts +129 -0
- package/src/data/query/sql/functions/index.ts +2 -0
- package/src/data/query/sql/functions/vector.ts +178 -0
- package/src/data/query/sql/index.ts +3 -0
- package/src/data/query/sql/sql.ts +794 -0
- package/src/data/query/subquery.ts +53 -0
- package/src/data/query/table.ts +208 -0
- package/src/data/query/table.utils.ts +2 -0
- package/src/data/query/tracing-utils.ts +3 -0
- package/src/data/query/tracing.ts +11 -0
- package/src/data/query/utils.ts +357 -0
- package/src/data/query/version.ts +4 -0
- package/src/data/query/view-common.ts +1 -0
- package/src/data/schema/branded.ts +13 -0
- package/src/data/schema/generated/coerce.ts +5 -0
- package/src/data/schema/generated/combinator.ts +6 -0
- package/src/data/schema/generated/encoding.ts +5 -0
- package/src/data/schema/generated/error.ts +6 -0
- package/src/data/schema/generated/format.ts +5 -0
- package/src/data/schema/generated/type-constructor.ts +10 -0
- package/src/data/schema/generated/type.ts +6 -0
- package/src/data/schema/index.ts +6 -0
- package/src/data/schema/json-schema.ts +33 -0
- package/src/data/schema/namespace.ts +55 -0
- package/src/data/schema/parse.ts +39 -0
- package/src/data/schema/types.ts +39 -0
- package/src/data/temporal/append-only.ts +134 -0
- package/src/data/temporal/close.ts +27 -0
- package/src/data/temporal/columns.ts +10 -0
- package/src/data/temporal/definition-queries.ts +70 -0
- package/src/data/temporal/errors.ts +10 -0
- package/src/data/temporal/get-current.ts +22 -0
- package/src/data/temporal/index.ts +9 -0
- package/src/data/temporal/namespace.ts +36 -0
- package/src/data/temporal/query.ts +25 -0
- package/src/data/temporal/resolve.ts +17 -0
- package/src/data/temporal/revise.ts +67 -0
- package/src/data/temporal/types.ts +22 -0
- package/src/data/valve/artifact.ts +33 -0
- package/src/data/valve/contract-diff.ts +21 -0
- package/src/data/valve/cross.ts +57 -0
- package/src/data/valve/crossing-ledger.ts +50 -0
- package/src/data/valve/decision.ts +44 -0
- package/src/data/valve/durable-crossing.ts +52 -0
- package/src/data/valve/execute-crossing.ts +77 -0
- package/src/data/valve/faucet.ts +56 -0
- package/src/data/valve/gate.ts +114 -0
- package/src/data/valve/graph.ts +77 -0
- package/src/data/valve/index.ts +53 -0
- package/src/data/valve/namespace.ts +57 -0
- package/src/data/valve/registry.ts +50 -0
- package/src/data/valve/schema/valve-crossing-fields.ts +23 -0
- package/src/data/valve/schema/valve-crossing.ts +9 -0
- package/src/data/valve/supply.ts +154 -0
- package/src/data/valve/types.ts +143 -0
- package/src/data/vector/columns.ts +27 -0
- package/src/data/vector/index.ts +4 -0
- package/src/data/vector/namespace.ts +33 -0
- package/src/data/vector/query.ts +85 -0
- package/src/data/vector/validate.ts +16 -0
- package/src/data/write/audit/actor.ts +15 -0
- package/src/data/write/audit/audit-columns.ts +18 -0
- package/src/data/write/audit/audit-select.ts +111 -0
- package/src/data/write/audit/bound-transaction.ts +14 -0
- package/src/data/write/audit/cte-compile.ts +32 -0
- package/src/data/write/audit/decode-returning.ts +47 -0
- package/src/data/write/audit/final-select.ts +43 -0
- package/src/data/write/audit/infra-audit.ts +36 -0
- package/src/data/write/audit/primary-key.ts +65 -0
- package/src/data/write/audit/projection.ts +41 -0
- package/src/data/write/audit/read-touches.ts +86 -0
- package/src/data/write/audit/touch-match.ts +78 -0
- package/src/data/write/audit/verb-delete.ts +62 -0
- package/src/data/write/audit/verb-insert.ts +36 -0
- package/src/data/write/audit/verb-select.ts +47 -0
- package/src/data/write/audit/verb-update.ts +60 -0
- package/src/data/write/audit/verb-upsert.ts +104 -0
- package/src/data/write/conflict.ts +43 -0
- package/src/data/write/delete.ts +38 -0
- package/src/data/write/execute.ts +243 -0
- package/src/data/write/index.ts +11 -0
- package/src/data/write/insert.ts +63 -0
- package/src/data/write/state.ts +43 -0
- package/src/data/write/update.ts +54 -0
- package/src/data/write/upsert.ts +58 -0
- package/src/data/write/values.ts +19 -0
- package/src/index.ts +230 -0
- package/src/infra/access-links/code.ts +70 -0
- package/src/infra/access-links/handle.ts +892 -0
- package/src/infra/access-links/index.ts +9 -0
- package/src/infra/access-links/namespace.ts +30 -0
- package/src/infra/access-links/person-check.ts +177 -0
- package/src/infra/access-links/schema.ts +71 -0
- package/src/infra/alerts/async-sources.ts +231 -0
- package/src/infra/alerts/audit.ts +36 -0
- package/src/infra/alerts/beat.ts +123 -0
- package/src/infra/alerts/client-faults.ts +111 -0
- package/src/infra/alerts/faults.ts +156 -0
- package/src/infra/alerts/handle.ts +778 -0
- package/src/infra/alerts/http.ts +192 -0
- package/src/infra/alerts/index.ts +12 -0
- package/src/infra/alerts/infra-sources.ts +180 -0
- package/src/infra/alerts/namespace.ts +81 -0
- package/src/infra/alerts/redact.ts +21 -0
- package/src/infra/alerts/request-faults.ts +41 -0
- package/src/infra/alerts/schema.ts +124 -0
- package/src/infra/alerts/sinks.ts +125 -0
- package/src/infra/alerts/source-options.ts +26 -0
- package/src/infra/alerts/sources.ts +503 -0
- package/src/infra/alerts/subsystem-sources.ts +137 -0
- package/src/infra/audit/emit.ts +87 -0
- package/src/infra/audit/index.ts +2 -0
- package/src/infra/audit/namespace.ts +22 -0
- package/src/infra/audit/schema.sql +35 -0
- package/src/infra/audit/types.ts +18 -0
- package/src/infra/aws/credentials.ts +147 -0
- package/src/infra/aws/sigv4.ts +140 -0
- package/src/infra/cache/create.ts +58 -0
- package/src/infra/cache/entry.ts +81 -0
- package/src/infra/cache/index.ts +4 -0
- package/src/infra/cache/namespace.ts +10 -0
- package/src/infra/cache/tenant.ts +93 -0
- package/src/infra/cache/types.ts +24 -0
- package/src/infra/crawl/audit.ts +35 -0
- package/src/infra/crawl/errors.ts +25 -0
- package/src/infra/crawl/handle.ts +693 -0
- package/src/infra/crawl/index.ts +9 -0
- package/src/infra/crawl/links.ts +160 -0
- package/src/infra/crawl/namespace.ts +36 -0
- package/src/infra/crawl/robots.ts +100 -0
- package/src/infra/crawl/schema.ts +89 -0
- package/src/infra/credit-line/handle.ts +883 -0
- package/src/infra/credit-line/index.ts +6 -0
- package/src/infra/credit-line/namespace.ts +7 -0
- package/src/infra/credit-line/schema.ts +96 -0
- package/src/infra/credits/handle.ts +706 -0
- package/src/infra/credits/index.ts +5 -0
- package/src/infra/credits/namespace.ts +7 -0
- package/src/infra/credits/schema.ts +83 -0
- package/src/infra/email/audit.ts +36 -0
- package/src/infra/email/inbound.ts +622 -0
- package/src/infra/email/index.ts +11 -0
- package/src/infra/email/mail.ts +517 -0
- package/src/infra/email/mime.ts +447 -0
- package/src/infra/email/namespace.ts +22 -0
- package/src/infra/email/providers.ts +111 -0
- package/src/infra/email/schema.ts +96 -0
- package/src/infra/email/sns.ts +200 -0
- package/src/infra/email/unsubscribe.ts +44 -0
- package/src/infra/fetch/address.ts +88 -0
- package/src/infra/fetch/connect-retry.ts +87 -0
- package/src/infra/fetch/handle.ts +329 -0
- package/src/infra/fetch/index.ts +7 -0
- package/src/infra/fetch/namespace.ts +70 -0
- package/src/infra/idempotency/index.ts +3 -0
- package/src/infra/idempotency/middleware.ts +87 -0
- package/src/infra/idempotency/store.ts +92 -0
- package/src/infra/idempotency/types.ts +30 -0
- package/src/infra/join-requests/handle.ts +312 -0
- package/src/infra/join-requests/index.ts +2 -0
- package/src/infra/join-requests/namespace.ts +7 -0
- package/src/infra/join-requests/schema.ts +33 -0
- package/src/infra/outbox/core.ts +165 -0
- package/src/infra/quota/errors.ts +32 -0
- package/src/infra/quota/handle.ts +215 -0
- package/src/infra/quota/index.ts +5 -0
- package/src/infra/quota/namespace.ts +7 -0
- package/src/infra/quota/schema.ts +15 -0
- package/src/infra/storage/backend.ts +34 -0
- package/src/infra/storage/handle.ts +513 -0
- package/src/infra/storage/index.ts +9 -0
- package/src/infra/storage/local.ts +104 -0
- package/src/infra/storage/namespace.ts +13 -0
- package/src/infra/storage/s3.ts +257 -0
- package/src/infra/storage/schema.ts +24 -0
- package/src/infra/support/handle.ts +854 -0
- package/src/infra/support/http.ts +229 -0
- package/src/infra/support/index.ts +7 -0
- package/src/infra/support/namespace.ts +12 -0
- package/src/infra/support/schema.ts +96 -0
- package/src/infra/webhook/inbound.ts +45 -0
- package/src/infra/webhook/index.ts +5 -0
- package/src/infra/webhook/namespace.ts +24 -0
- package/src/infra/webhook/outbound.ts +315 -0
- package/src/infra/webhook/sign.ts +32 -0
- package/src/infra/webhook/verify.ts +82 -0
- package/src/invariants/assert.ts +26 -0
- package/src/invariants/index.ts +1 -0
- package/src/lint/config.ts +56 -0
- package/src/lint/doctrine.ts +163 -0
- package/src/lint/include.ts +97 -0
- package/src/lint/index.ts +3 -0
- package/src/lint/preset.ts +10 -0
- package/src/lint/resolver.ts +218 -0
- package/src/lint/rules/no-reexports.ts +46 -0
- package/src/request/behavior/audited.ts +85 -0
- package/src/request/behavior/cached.ts +62 -0
- package/src/request/behavior/compose.ts +28 -0
- package/src/request/behavior/index.ts +5 -0
- package/src/request/behavior/namespace.ts +16 -0
- package/src/request/behavior/types.ts +26 -0
- package/src/request/behavior/versioned.ts +54 -0
- package/src/request/context/create.ts +103 -0
- package/src/request/context/guard.ts +49 -0
- package/src/request/context/index.ts +4 -0
- package/src/request/context/run-in-transaction.ts +200 -0
- package/src/request/context/store.ts +28 -0
- package/src/request/context/types.ts +50 -0
- package/src/request/di/builder.ts +535 -0
- package/src/request/di/index.ts +8 -0
- package/src/request/di/resolve.ts +271 -0
- package/src/request/di/types.ts +262 -0
- package/src/request/fitting/namespace.ts +7 -0
- package/src/request/fixture/batch.ts +71 -0
- package/src/request/fixture/builder.ts +26 -0
- package/src/request/fixture/errors.ts +10 -0
- package/src/request/fixture/index.ts +8 -0
- package/src/request/fixture/namespace.ts +23 -0
- package/src/request/fixture/paginate.ts +44 -0
- package/src/request/fixture/to-handlers.ts +260 -0
- package/src/request/fixture/types.ts +79 -0
- package/src/request/flow/act-as.ts +78 -0
- package/src/request/flow/namespace.ts +17 -0
- package/src/request/http/error-step.ts +42 -0
- package/src/request/http/file.ts +141 -0
- package/src/request/http/index.ts +4 -0
- package/src/request/http/middleware.ts +141 -0
- package/src/request/http/namespace.ts +5 -0
- package/src/request/http/rate-limit-classes.ts +170 -0
- package/src/request/http/response.ts +82 -0
- package/src/request/http/server.ts +916 -0
- package/src/request/http/sniff.ts +261 -0
- package/src/request/http/types.ts +250 -0
- package/src/request/http/upload.ts +284 -0
- package/src/request/internal/text.ts +6 -0
- package/src/request/openapi/generate.ts +205 -0
- package/src/request/openapi/index.ts +2 -0
- package/src/request/openapi/namespace.ts +7 -0
- package/src/test/auth.ts +13 -0
- package/src/test/auto-setup.ts +45 -0
- package/src/test/context.ts +91 -0
- package/src/test/crawl-fixture.ts +157 -0
- package/src/test/discover-fork.ts +17 -0
- package/src/test/fingerprint.ts +135 -0
- package/src/test/globals.d.ts +7 -0
- package/src/test/index.ts +19 -0
- package/src/test/namespace.ts +87 -0
- package/src/test/plugin.ts +189 -0
- package/src/test/progress.ts +185 -0
- package/src/test/reap-after-run.ts +28 -0
- package/src/test/reap.ts +286 -0
- package/src/test/reset.ts +253 -0
- package/src/test/savepoints.ts +172 -0
- package/src/test/setup.ts +1422 -0
- package/src/test/system-channel.ts +50 -0
- package/src/test/vitest-plugin.ts +142 -0
- package/src/test/vitest-workspace.ts +507 -0
- package/src/test/vitest.ts +333 -0
- package/src/test/watch.ts +105 -0
- package/src/trace/capture/bounded-serializer.ts +146 -0
- package/src/trace/cardinality/analyze.ts +173 -0
- package/src/trace/cardinality/branch-context.ts +214 -0
- package/src/trace/cardinality/check.ts +33 -0
- package/src/trace/cardinality/classify-edge.ts +64 -0
- package/src/trace/cardinality/structural-max.ts +107 -0
- package/src/trace/check/coverage-check.ts +188 -0
- package/src/trace/check/flow-check.ts +103 -0
- package/src/trace/check/flow-step-site.ts +234 -0
- package/src/trace/check/io-pattern.ts +116 -0
- package/src/trace/codemod/add-cardinality.ts +152 -0
- package/src/trace/codemod/run.ts +80 -0
- package/src/trace/entry/child-trace.ts +21 -0
- package/src/trace/entry/close-trace.ts +87 -0
- package/src/trace/entry/finalize-trace.ts +28 -0
- package/src/trace/entry/open-trace.ts +138 -0
- package/src/trace/entry/parent-trace.ts +20 -0
- package/src/trace/entry/parse-traceparent.ts +39 -0
- package/src/trace/entry/process-start.ts +38 -0
- package/src/trace/entry/trace-context.ts +26 -0
- package/src/trace/entry/tracing-enabled.ts +10 -0
- package/src/trace/external/record.ts +195 -0
- package/src/trace/external/report.ts +66 -0
- package/src/trace/namespace.ts +19 -0
- package/src/trace/partition/day.ts +47 -0
- package/src/trace/partition/index.ts +14 -0
- package/src/trace/partition/partition-name.ts +23 -0
- package/src/trace/partition/plan.ts +82 -0
- package/src/trace/partition/schedule.ts +26 -0
- package/src/trace/partition/startup-hook.ts +39 -0
- package/src/trace/partition/sync.ts +154 -0
- package/src/trace/partition/tables.ts +53 -0
- package/src/trace/read/tree.ts +83 -0
- package/src/trace/retain/copy-on-error.ts +76 -0
- package/src/trace/runtime/expire-stale-traces.ts +54 -0
- package/src/trace/runtime/flow-step.ts +147 -0
- package/src/trace/runtime/flush-steps.ts +31 -0
- package/src/trace/runtime/pending-writes.ts +54 -0
- package/src/trace/runtime/step-buffer.ts +298 -0
- package/src/trace/runtime/step-error.ts +21 -0
- package/src/trace/runtime/step-meta.ts +55 -0
- package/src/trace/runtime/thenable.ts +12 -0
- package/src/trace/runtime/truncate-trace.ts +62 -0
- package/src/trace/schema/trace-fields.ts +52 -0
- package/src/trace/schema/trace-retained.ts +22 -0
- package/src/trace/schema/trace-step-fields.ts +44 -0
- package/src/trace/schema/trace-step-retained.ts +25 -0
- package/src/trace/schema/trace-step.ts +18 -0
- package/src/trace/schema/trace.ts +12 -0
- package/src/trace/transformer/apply.ts +79 -0
- package/src/trace/transformer/build-meta-literal.ts +30 -0
- package/src/trace/transformer/find-flow-step-sites.ts +85 -0
- package/src/trace/transformer/inject-flow-step-import.ts +39 -0
- package/src/trace/transformer/transform-error.ts +14 -0
- package/src/trace/transformer/transform-flow-steps.ts +123 -0
- package/src/trace/transformer/wrap-function-declaration.ts +62 -0
- package/src/trace/transformer/wrap-variable-declaration.ts +67 -0
- package/src/workspace/affected.ts +110 -0
- package/src/workspace/changed-files.ts +163 -0
- package/src/workspace/check-deps.ts +213 -0
- package/src/workspace/checksum.ts +20 -0
- package/src/workspace/dependency-graph.ts +67 -0
- package/src/workspace/detect-pipework-copies.ts +93 -0
- package/src/workspace/discover.ts +206 -0
- package/src/workspace/generate.ts +401 -0
- package/src/workspace/index.ts +7 -0
- package/src/workspace/module-imports-pipework.ts +39 -0
- package/src/workspace/module-package-name.ts +12 -0
- package/src/workspace/scrub-git-env.ts +23 -0
- package/src/workspace/source-paths.ts +36 -0
- package/src/workspace/sync.ts +182 -0
- package/dist/REFERENCE.md +0 -1477
- package/dist/infra/access-links/rate-limit.d.ts +0 -10
- package/dist/infra/access-links/rate-limit.d.ts.map +0 -1
- package/dist/infra/access-links/rate-limit.js +0 -28
- package/dist/infra/access-links/rate-limit.js.map +0 -1
- package/dist/infra/email/sigv4.d.ts +0 -24
- package/dist/infra/email/sigv4.d.ts.map +0 -1
- package/dist/infra/email/sigv4.js +0 -55
- package/dist/infra/email/sigv4.js.map +0 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { createPublicKey, verify as cryptoVerify } from 'node:crypto'
|
|
2
|
+
import { sql } from '../../data/db/sql.js'
|
|
3
|
+
import { pipeSystem } from '../../data/db/db.js'
|
|
4
|
+
import { Builder } from '../../request/di/builder.js'
|
|
5
|
+
import { UnauthorizedError, ValidationError } from '../../core/errors/index.js'
|
|
6
|
+
import { getBaseLogger } from '../../core/logging/index.js'
|
|
7
|
+
import type { Handler } from '../../request/di/types.js'
|
|
8
|
+
import { __flowStep } from '../../trace/runtime/flow-step.js'
|
|
9
|
+
import { auditEmail } from './audit.js'
|
|
10
|
+
import { normalizeAddress } from './mail.js'
|
|
11
|
+
|
|
12
|
+
export interface SnsWebhookConfig {
|
|
13
|
+
readonly path: string
|
|
14
|
+
/** The SNS topics whose messages are accepted. SNS signs every topic, so without this anyone could subscribe the endpoint to their own topic. */
|
|
15
|
+
readonly topicArns: readonly string[]
|
|
16
|
+
/** The SNS region; certificate and SubscribeURL hosts must be `sns.<region>.amazonaws.com`. Default: any AWS region. */
|
|
17
|
+
readonly region?: string
|
|
18
|
+
/** Fetches the signing certificate PEM. Default: `fetch`. Tests serve their own. */
|
|
19
|
+
readonly fetchCert?: (url: string) => Promise<string>
|
|
20
|
+
/** Confirms a subscription by GET of its SubscribeURL. Default: `fetch`. */
|
|
21
|
+
readonly confirm?: (url: string) => Promise<void>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type SnsMessage = Record<string, string | undefined>
|
|
25
|
+
|
|
26
|
+
const NOTIFICATION_FIELDS = ['Message', 'MessageId', 'Subject', 'Timestamp', 'TopicArn', 'Type']
|
|
27
|
+
const CONFIRMATION_FIELDS = ['Message', 'MessageId', 'SubscribeURL', 'Timestamp', 'Token', 'TopicArn', 'Type']
|
|
28
|
+
|
|
29
|
+
export function snsStringToSign(msg: SnsMessage): string {
|
|
30
|
+
const names = msg['Type'] === 'Notification' ? NOTIFICATION_FIELDS : CONFIRMATION_FIELDS
|
|
31
|
+
return names.filter(n => msg[n] !== undefined).map(n => `${n}\n${msg[n]!}\n`).join('')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isSnsHost(url: string, region: string | undefined): boolean {
|
|
35
|
+
let u: URL
|
|
36
|
+
try { u = new URL(url) } catch { return false }
|
|
37
|
+
if (u.protocol !== 'https:' || u.username !== '' || u.password !== '' || u.port !== '') return false
|
|
38
|
+
const host = region !== undefined ? `sns.${region}.amazonaws.com` : null
|
|
39
|
+
return host !== null ? u.hostname === host : /^sns\.[a-z0-9-]+\.amazonaws\.com$/.test(u.hostname)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function defaultFetchCert(url: string): Promise<string> {
|
|
43
|
+
const res = await fetch(url, { redirect: 'error' })
|
|
44
|
+
if (!res.ok) throw new Error(`certificate fetch answered HTTP ${res.status}`)
|
|
45
|
+
return res.text()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function defaultConfirm(url: string): Promise<void> {
|
|
49
|
+
const res = await fetch(url, { redirect: 'error' })
|
|
50
|
+
if (!res.ok) throw new Error(`subscription confirmation answered HTTP ${res.status}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function refuse(reason: string): never {
|
|
54
|
+
getBaseLogger().warn({ event: 'sns message refused', reason }, 'SNS message refused')
|
|
55
|
+
throw new UnauthorizedError('SNS message verification failed.\n')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface Recipient { address: string; bounceType: string | null; kind: 'bounce' | 'complaint' }
|
|
59
|
+
|
|
60
|
+
function recipientsOf(event: Record<string, unknown>): { recipients: Recipient[]; sesMessageId: string | null } {
|
|
61
|
+
const type = String(event['eventType'] ?? event['notificationType'] ?? '').toLowerCase()
|
|
62
|
+
const mailPart = event['mail'] as { messageId?: unknown } | undefined
|
|
63
|
+
const sesMessageId = typeof mailPart?.messageId === 'string' ? mailPart.messageId : null
|
|
64
|
+
const list = (v: unknown): string[] =>
|
|
65
|
+
Array.isArray(v) ? (v as ({ emailAddress?: unknown } | null)[]).flatMap(r => (typeof r?.emailAddress === 'string' ? [r.emailAddress] : [])) : []
|
|
66
|
+
if (type === 'bounce') {
|
|
67
|
+
const bounce = event['bounce'] as { bounceType?: unknown; bouncedRecipients?: unknown } | undefined
|
|
68
|
+
const bounceType = typeof bounce?.bounceType === 'string' ? bounce.bounceType : null
|
|
69
|
+
return { recipients: list(bounce?.bouncedRecipients).map(address => ({ address, bounceType, kind: 'bounce' as const })), sesMessageId }
|
|
70
|
+
}
|
|
71
|
+
if (type === 'complaint') {
|
|
72
|
+
const complaint = event['complaint'] as { complainedRecipients?: unknown } | undefined
|
|
73
|
+
return { recipients: list(complaint?.complainedRecipients).map(address => ({ address, bounceType: null, kind: 'complaint' as const })), sesMessageId }
|
|
74
|
+
}
|
|
75
|
+
return { recipients: [], sesMessageId }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const processEvent = __flowStep({ ref: 'pipework/email:notification' }, async (event: Record<string, unknown>): Promise<void> => {
|
|
79
|
+
const { recipients, sesMessageId } = recipientsOf(event)
|
|
80
|
+
const db = pipeSystem()
|
|
81
|
+
for (const r of recipients) {
|
|
82
|
+
const address = normalizeAddress(r.address)
|
|
83
|
+
const found = sesMessageId === null ? [] : await db.execute(sql`
|
|
84
|
+
SELECT id, trace_id FROM pipework_email_messages WHERE provider_message_id = ${sesMessageId}`)
|
|
85
|
+
const messageId = (found[0]?.['id'] as string | undefined) ?? null
|
|
86
|
+
const messageTraceId = (found[0]?.['trace_id'] as string | undefined) ?? null
|
|
87
|
+
const permanent = r.kind === 'complaint' || r.bounceType === 'Permanent'
|
|
88
|
+
if (permanent) {
|
|
89
|
+
await db.execute(sql`
|
|
90
|
+
INSERT INTO pipework_email_suppressions (address, reason, detail)
|
|
91
|
+
VALUES (${address}, ${r.kind}, ${JSON.stringify({ bounceType: r.bounceType, sesMessageId })}::jsonb)
|
|
92
|
+
ON CONFLICT (address) DO UPDATE SET reason = EXCLUDED.reason, detail = EXCLUDED.detail`)
|
|
93
|
+
getBaseLogger().warn({ event: 'email suppressed', address, reason: r.kind }, 'email suppressed')
|
|
94
|
+
} else {
|
|
95
|
+
getBaseLogger().info({ event: 'email bounce not suppressed', address, bounceType: r.bounceType }, 'transient bounce: address kept')
|
|
96
|
+
}
|
|
97
|
+
await auditEmail(r.kind, messageId, { address, bounceType: r.bounceType, suppressed: permanent, messageTraceId })
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
export interface SnsHandlerConfig {
|
|
102
|
+
readonly path: string
|
|
103
|
+
readonly topicArns: readonly string[]
|
|
104
|
+
readonly region?: string
|
|
105
|
+
readonly fetchCert?: (url: string) => Promise<string>
|
|
106
|
+
readonly confirm?: (url: string) => Promise<void>
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The shared SNS endpoint: parses the body, verifies the signature (SignatureVersion 1 or 2) against the
|
|
111
|
+
* certificate at `SigningCertURL`, checks `TopicArn` against `topicArns`, confirms subscriptions, and
|
|
112
|
+
* passes each verified `Notification`'s `Message` text to `onNotification`. Anything unverified is a 401
|
|
113
|
+
* before `onNotification` runs. Used by `snsWebhook` and the inbound mail webhook.
|
|
114
|
+
*/
|
|
115
|
+
export function snsHandler(
|
|
116
|
+
config: SnsHandlerConfig,
|
|
117
|
+
onNotification: (message: string) => Promise<Record<string, unknown> | void>,
|
|
118
|
+
): Handler<Record<string, unknown>, unknown> {
|
|
119
|
+
if (config.topicArns.length === 0) throw new Error('[pipework] An SNS webhook needs at least one topic ARN in topicArns.')
|
|
120
|
+
const fetchCert = config.fetchCert ?? defaultFetchCert
|
|
121
|
+
const confirm = config.confirm ?? defaultConfirm
|
|
122
|
+
const certs = new Map<string, string>()
|
|
123
|
+
|
|
124
|
+
async function verifySignature(msg: SnsMessage): Promise<void> {
|
|
125
|
+
const url = msg['SigningCertURL']
|
|
126
|
+
const signature = msg['Signature']
|
|
127
|
+
const version = msg['SignatureVersion']
|
|
128
|
+
if (url === undefined || signature === undefined) refuse('missing signature fields')
|
|
129
|
+
if (version !== '1' && version !== '2') refuse('unknown SignatureVersion')
|
|
130
|
+
if (!isSnsHost(url, config.region) || !new URL(url).pathname.endsWith('.pem')) refuse('SigningCertURL is not an SNS certificate URL')
|
|
131
|
+
let pem = certs.get(url)
|
|
132
|
+
if (pem === undefined) {
|
|
133
|
+
try { pem = await fetchCert(url) } catch { refuse('certificate could not be fetched') }
|
|
134
|
+
certs.set(url, pem)
|
|
135
|
+
}
|
|
136
|
+
let ok = false
|
|
137
|
+
try {
|
|
138
|
+
ok = cryptoVerify(version === '1' ? 'RSA-SHA1' : 'RSA-SHA256', Buffer.from(snsStringToSign(msg)), createPublicKey(pem), Buffer.from(signature, 'base64'))
|
|
139
|
+
} catch { ok = false }
|
|
140
|
+
if (!ok) refuse('signature does not match')
|
|
141
|
+
if (msg['TopicArn'] === undefined || !config.topicArns.includes(msg['TopicArn'])) refuse('TopicArn is not a configured topic')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return new Builder()
|
|
145
|
+
.public()
|
|
146
|
+
.rawBody()
|
|
147
|
+
.request()
|
|
148
|
+
.route('POST', config.path)
|
|
149
|
+
.fit(async ({ rawBody }) => {
|
|
150
|
+
let parsed: unknown
|
|
151
|
+
try {
|
|
152
|
+
parsed = JSON.parse(rawBody.toString('utf-8'))
|
|
153
|
+
} catch {
|
|
154
|
+
throw new ValidationError({ body: ['is not JSON'] }, 'SNS message is not JSON.')
|
|
155
|
+
}
|
|
156
|
+
if (typeof parsed !== 'object' || parsed === null) throw new ValidationError({ body: ['is not an object'] }, 'SNS message is not an object.')
|
|
157
|
+
const msg = parsed as SnsMessage
|
|
158
|
+
await verifySignature(msg)
|
|
159
|
+
|
|
160
|
+
if (msg['Type'] === 'SubscriptionConfirmation') {
|
|
161
|
+
const subscribeUrl = msg['SubscribeURL']
|
|
162
|
+
if (subscribeUrl === undefined || !isSnsHost(subscribeUrl, config.region)) refuse('SubscribeURL is not an SNS URL')
|
|
163
|
+
await confirm(subscribeUrl)
|
|
164
|
+
return { status: 200, body: { ok: true, confirmed: true } }
|
|
165
|
+
}
|
|
166
|
+
if (msg['Type'] === 'Notification') {
|
|
167
|
+
const extra = await onNotification(msg['Message'] ?? '')
|
|
168
|
+
return { status: 200, body: { ok: true, ...(extra ?? {}) } }
|
|
169
|
+
}
|
|
170
|
+
return { status: 200, body: { ok: true } }
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Receives SES bounce and complaint events delivered through SNS (HTTP/S subscription).
|
|
176
|
+
* A permanent bounce or any complaint puts the address (lowercased) on
|
|
177
|
+
* `pipework_email_suppressions`, which the send job checks; a transient or undetermined
|
|
178
|
+
* bounce is only logged. Each one is a trace step with an `Email:bounce` / `Email:complaint`
|
|
179
|
+
* audit record whose `after` carries `messageTraceId`, the trace of the original send.
|
|
180
|
+
* A suppression is logged at warn as `email suppressed`; an app raises an alert for it with `alerts.create(...).raise`.
|
|
181
|
+
*
|
|
182
|
+
* Only messages from the configured `topicArns` are accepted (401 otherwise, before any subscription is
|
|
183
|
+
* confirmed or event processed): SNS signs every topic, including an attacker's own.
|
|
184
|
+
* Every message's signature (SignatureVersion 1 or 2) is checked against the certificate
|
|
185
|
+
* at `SigningCertURL`, which must be `https://sns.<region>.amazonaws.com/...`; a failure is a 401.
|
|
186
|
+
* A SubscriptionConfirmation is confirmed by fetching its `SubscribeURL`.
|
|
187
|
+
* SES events delivered through EventBridge are not supported.
|
|
188
|
+
*/
|
|
189
|
+
export function snsWebhook(config: SnsWebhookConfig): Handler<Record<string, unknown>, unknown> {
|
|
190
|
+
if (config.topicArns.length === 0) throw new Error('[pipework] mail.snsWebhook needs at least one topic ARN in topicArns.')
|
|
191
|
+
return snsHandler(config, async (message) => {
|
|
192
|
+
let event: Record<string, unknown>
|
|
193
|
+
try {
|
|
194
|
+
event = JSON.parse(message) as Record<string, unknown>
|
|
195
|
+
} catch {
|
|
196
|
+
return { ignored: true }
|
|
197
|
+
}
|
|
198
|
+
await processEvent(event)
|
|
199
|
+
})
|
|
200
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
/** A list name: what `SendInput.list` accepts. */
|
|
4
|
+
export const LIST_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i
|
|
5
|
+
export const MIN_UNSUBSCRIBE_SECRET = 32
|
|
6
|
+
|
|
7
|
+
const DOMAIN = 'pipework mail unsubscribe\0'
|
|
8
|
+
|
|
9
|
+
const b64 = (data: Buffer | string): string => Buffer.from(data).toString('base64url')
|
|
10
|
+
|
|
11
|
+
function signature(secret: string, payload: string): Buffer {
|
|
12
|
+
return createHmac('sha256', secret).update(DOMAIN + payload).digest()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The signed token naming a normalized address and a list; it never expires. */
|
|
16
|
+
export function signUnsubscribeToken(secret: string, address: string, list: string): string {
|
|
17
|
+
const payload = b64(JSON.stringify({ a: address, l: list }))
|
|
18
|
+
return `${payload}.${b64(signature(secret, payload))}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The address and list a token names, or null for anything malformed, tampered or signed with another secret. Never throws. */
|
|
22
|
+
export function verifyUnsubscribeToken(secret: string, token: unknown): { readonly address: string; readonly list: string } | null {
|
|
23
|
+
if (typeof token !== 'string') return null
|
|
24
|
+
const parts = token.split('.')
|
|
25
|
+
if (parts.length !== 2) return null
|
|
26
|
+
const [payload, given] = parts as [string, string]
|
|
27
|
+
const expected = signature(secret, payload)
|
|
28
|
+
const got = Buffer.from(given, 'base64url')
|
|
29
|
+
if (got.length !== expected.length || !timingSafeEqual(got, expected)) return null
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as { a?: unknown; l?: unknown }
|
|
32
|
+
if (typeof parsed.a !== 'string' || typeof parsed.l !== 'string' || parsed.a === '' || !LIST_RE.test(parsed.l)) return null
|
|
33
|
+
return { address: parsed.a, list: parsed.l }
|
|
34
|
+
} catch {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The unsubscribe link for a token: `url` with `t` set. */
|
|
40
|
+
export function unsubscribeLink(url: string, token: string): string {
|
|
41
|
+
const u = new URL(url)
|
|
42
|
+
u.searchParams.set('t', token)
|
|
43
|
+
return u.toString()
|
|
44
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { isIP } from 'node:net'
|
|
2
|
+
|
|
3
|
+
type Cidr4 = readonly [number, number]
|
|
4
|
+
|
|
5
|
+
/** IPv4 ranges a stranger's URL may not reach: [first address as uint32, prefix length]. */
|
|
6
|
+
const REFUSED_V4: readonly Cidr4[] = [
|
|
7
|
+
[0x00000000, 8], [0x0a000000, 8], [0x64400000, 10], [0x7f000000, 8], [0xa9fe0000, 16],
|
|
8
|
+
[0xac100000, 12], [0xc0000000, 24], [0xc0a80000, 16], [0xc6120000, 15], [0xe0000000, 4], [0xf0000000, 4],
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
function parseV4(text: string): number[] | null {
|
|
12
|
+
const parts = text.split('.')
|
|
13
|
+
if (parts.length !== 4) return null
|
|
14
|
+
const out: number[] = []
|
|
15
|
+
for (const p of parts) {
|
|
16
|
+
if (!/^\d{1,3}$/.test(p)) return null
|
|
17
|
+
const n = Number(p)
|
|
18
|
+
if (n > 255) return null
|
|
19
|
+
out.push(n)
|
|
20
|
+
}
|
|
21
|
+
return out
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The 16 bytes of an IPv6 address (dotted tail and zone id allowed), or null when it is not one. */
|
|
25
|
+
function parseV6(input: string): number[] | null {
|
|
26
|
+
let text = input
|
|
27
|
+
const zone = text.indexOf('%')
|
|
28
|
+
if (zone !== -1) text = text.slice(0, zone)
|
|
29
|
+
let tail: number[] = []
|
|
30
|
+
const lastColon = text.lastIndexOf(':')
|
|
31
|
+
if (text.includes('.')) {
|
|
32
|
+
const v4 = parseV4(text.slice(lastColon + 1))
|
|
33
|
+
if (v4 === null) return null
|
|
34
|
+
tail = v4
|
|
35
|
+
text = text.slice(0, lastColon + 1) + '0:0'
|
|
36
|
+
}
|
|
37
|
+
const halves = text.split('::')
|
|
38
|
+
if (halves.length > 2) return null
|
|
39
|
+
const groups = (s: string): string[] => (s === '' ? [] : s.split(':'))
|
|
40
|
+
const head = groups(halves[0]!)
|
|
41
|
+
const rest = halves.length === 2 ? groups(halves[1]!) : []
|
|
42
|
+
const missing = 8 - head.length - rest.length
|
|
43
|
+
if (halves.length === 2 ? missing < 1 : missing !== 0) return null
|
|
44
|
+
const all = [...head, ...(halves.length === 2 ? new Array<string>(missing).fill('0') : []), ...rest]
|
|
45
|
+
const bytes: number[] = []
|
|
46
|
+
for (const g of all) {
|
|
47
|
+
if (!/^[0-9a-f]{1,4}$/i.test(g)) return null
|
|
48
|
+
const n = parseInt(g, 16)
|
|
49
|
+
bytes.push(n >> 8, n & 0xff)
|
|
50
|
+
}
|
|
51
|
+
if (tail.length === 4) bytes.splice(12, 4, ...tail)
|
|
52
|
+
return bytes
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function v4Refused(b: readonly number[]): boolean {
|
|
56
|
+
const n = ((b[0]! << 24) | (b[1]! << 16) | (b[2]! << 8) | b[3]!) >>> 0
|
|
57
|
+
return REFUSED_V4.some(([base, len]) => {
|
|
58
|
+
const mask = len === 0 ? 0 : (0xffffffff << (32 - len)) >>> 0
|
|
59
|
+
return ((n & mask) >>> 0) === base
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* True when `address` (IPv4 or IPv6 text, no brackets) is a public address the fetch may connect to.
|
|
65
|
+
* Anything that is not an IP address, and every private, loopback, link-local, CGNAT, unique-local, site-local,
|
|
66
|
+
* multicast, reserved, unspecified, documentation or IPv4-embedded form of those, is not public.
|
|
67
|
+
*/
|
|
68
|
+
export function isPublicAddress(address: string): boolean {
|
|
69
|
+
const kind = isIP(address)
|
|
70
|
+
if (kind === 4) {
|
|
71
|
+
const b = parseV4(address)
|
|
72
|
+
return b !== null && !v4Refused(b)
|
|
73
|
+
}
|
|
74
|
+
if (kind !== 6) return false
|
|
75
|
+
const b = parseV6(address)
|
|
76
|
+
if (b === null) return false
|
|
77
|
+
const first96Zero = b.slice(0, 12).every(x => x === 0)
|
|
78
|
+
if (first96Zero) return false // ::, ::1 and the deprecated IPv4-compatible block
|
|
79
|
+
const mapped = b.slice(0, 10).every(x => x === 0) && b[10] === 0xff && b[11] === 0xff
|
|
80
|
+
const nat64 = b[0] === 0 && b[1] === 0x64 && b[2] === 0xff && b[3] === 0x9b && b.slice(4, 12).every(x => x === 0)
|
|
81
|
+
if (mapped || nat64) return !v4Refused(b.slice(12, 16))
|
|
82
|
+
const hi = b[0]!
|
|
83
|
+
if (hi === 0xff) return false // ff00::/8
|
|
84
|
+
if ((hi & 0xfe) === 0xfc) return false // fc00::/7
|
|
85
|
+
if (hi === 0xfe && (b[1]! & 0xc0) >= 0x80) return false // fe80::/10 and fec0::/10
|
|
86
|
+
if (hi === 0x20 && b[1] === 0x01 && b[2] === 0x0d && b[3] === 0xb8) return false // 2001:db8::/32
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getBaseLogger } from '../../core/logging/index.js'
|
|
2
|
+
|
|
3
|
+
export interface ConnectRetryOptions {
|
|
4
|
+
/** Waits between tries; the number of tries is delaysMs.length + 1. Default [5_000, 15_000, 40_000] (4 tries over ~60 s). */
|
|
5
|
+
delaysMs?: readonly number[]
|
|
6
|
+
/** Stops waiting and throws the last fault when aborted (checked before each try and during each wait). */
|
|
7
|
+
signal?: AbortSignal
|
|
8
|
+
/** Called before each wait with the fault, the 1-based try that failed, and the wait. */
|
|
9
|
+
onRetry?: (info: { error: unknown; attempt: number; delayMs: number }) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const DEFAULT_DELAYS: readonly number[] = [5_000, 15_000, 40_000]
|
|
13
|
+
const MAX_DEPTH = 8
|
|
14
|
+
|
|
15
|
+
const UNSENT_CODES = new Set(['UND_ERR_CONNECT_TIMEOUT', 'EAI_AGAIN', 'ENOTFOUND', 'ECONNREFUSED', 'ENETUNREACH', 'EHOSTUNREACH'])
|
|
16
|
+
const SENT_CODES = new Set(['ECONNRESET', 'UND_ERR_SOCKET', 'EPIPE', 'UND_ERR_HEADERS_TIMEOUT', 'UND_ERR_BODY_TIMEOUT'])
|
|
17
|
+
|
|
18
|
+
/** The first unsent-phase code found in the error's chain, or null when it is not an unsent fault. */
|
|
19
|
+
function unsentCode(err: unknown): string | null {
|
|
20
|
+
const state: { found: string | null; sent: boolean } = { found: null, sent: false }
|
|
21
|
+
const seen = new Set<unknown>()
|
|
22
|
+
const walk = (node: unknown, depth: number): void => {
|
|
23
|
+
if (typeof node !== 'object' || node === null || depth > MAX_DEPTH || seen.has(node)) return
|
|
24
|
+
seen.add(node)
|
|
25
|
+
const { code, name, cause, errors } = node as { code?: unknown; name?: unknown; cause?: unknown; errors?: unknown }
|
|
26
|
+
if (name === 'AbortError' || name === 'TimeoutError') state.sent = true
|
|
27
|
+
if (typeof code === 'string') {
|
|
28
|
+
if (SENT_CODES.has(code)) state.sent = true
|
|
29
|
+
else if (UNSENT_CODES.has(code) && state.found === null) state.found = code
|
|
30
|
+
}
|
|
31
|
+
walk(cause, depth + 1)
|
|
32
|
+
if (Array.isArray(errors)) for (const e of errors) walk(e, depth + 1)
|
|
33
|
+
}
|
|
34
|
+
walk(err, 0)
|
|
35
|
+
return state.sent ? null : state.found
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** True when the fault happened while connecting (nothing was sent), so the request can be sent again safely. */
|
|
39
|
+
export function isUnsentFault(err: unknown): boolean {
|
|
40
|
+
return unsentCode(err) !== null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const aborted = (signal: AbortSignal | undefined): boolean => signal?.aborted === true
|
|
44
|
+
|
|
45
|
+
function wait(ms: number, signal: AbortSignal | undefined): Promise<void> {
|
|
46
|
+
return new Promise<void>((resolve) => {
|
|
47
|
+
if (signal?.aborted === true) { resolve(); return }
|
|
48
|
+
const done = (): void => { clearTimeout(timer); signal?.removeEventListener('abort', done); resolve() }
|
|
49
|
+
const timer = setTimeout(done, ms)
|
|
50
|
+
signal?.addEventListener('abort', done, { once: true })
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Runs `send`, and sends again after a wait when it failed in the connect phase. Any other fault is thrown at once.
|
|
56
|
+
* When the tries run out, or `signal` aborts, the last fault is thrown unchanged.
|
|
57
|
+
*/
|
|
58
|
+
export async function retryUnsent<R>(send: () => Promise<R>, opts: ConnectRetryOptions = {}): Promise<R> {
|
|
59
|
+
const delays = opts.delaysMs ?? DEFAULT_DELAYS
|
|
60
|
+
for (let attempt = 1; ; attempt++) {
|
|
61
|
+
try {
|
|
62
|
+
return await send()
|
|
63
|
+
} catch (error) {
|
|
64
|
+
const code = unsentCode(error)
|
|
65
|
+
const delayMs = delays[attempt - 1]
|
|
66
|
+
if (code === null || delayMs === undefined || aborted(opts.signal)) throw error
|
|
67
|
+
getBaseLogger().warn({ code, attempt, delayMs }, 'connect failed before anything was sent; trying again')
|
|
68
|
+
opts.onRetry?.({ error, attempt, delayMs })
|
|
69
|
+
await wait(delayMs, opts.signal)
|
|
70
|
+
if (aborted(opts.signal)) throw error
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** `fetch` that is tried again only when the connection could not be made. A response of any status is returned. */
|
|
76
|
+
export function fetchRetryingUnsent(
|
|
77
|
+
input: string | URL | Request,
|
|
78
|
+
init?: RequestInit,
|
|
79
|
+
opts: ConnectRetryOptions & { fetch?: typeof fetch } = {},
|
|
80
|
+
): Promise<Response> {
|
|
81
|
+
const doFetch = opts.fetch ?? fetch
|
|
82
|
+
const signal = opts.signal ?? init?.signal ?? undefined
|
|
83
|
+
return retryUnsent(
|
|
84
|
+
() => doFetch(input instanceof Request ? input.clone() : input, init),
|
|
85
|
+
{ ...opts, ...(signal !== undefined ? { signal } : {}) },
|
|
86
|
+
)
|
|
87
|
+
}
|