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,141 @@
|
|
|
1
|
+
import { Readable } from 'node:stream'
|
|
2
|
+
import type { FileConfig } from '../di/types.js'
|
|
3
|
+
import { mediaTypeAllowed } from './upload.js'
|
|
4
|
+
|
|
5
|
+
const FILE_RESPONSE = Symbol.for('pipework.fileResponse')
|
|
6
|
+
|
|
7
|
+
/** What `http.file()` takes. */
|
|
8
|
+
export interface FileResponseInit {
|
|
9
|
+
/** The bytes, or a stream that is read as it is sent. */
|
|
10
|
+
readonly body: Buffer | Uint8Array | Readable
|
|
11
|
+
/** Content-Type of the response, e.g. `application/pdf`. */
|
|
12
|
+
readonly mediaType: string
|
|
13
|
+
/** Name the browser saves the file under. Any characters; the header is escaped. */
|
|
14
|
+
readonly filename?: string
|
|
15
|
+
/** `'attachment'` (default) downloads; `'inline'` lets the browser display it. */
|
|
16
|
+
readonly disposition?: 'attachment' | 'inline'
|
|
17
|
+
/** Byte length of a stream body, sent as Content-Length. Ignored for a Buffer, whose length is known. */
|
|
18
|
+
readonly length?: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A file for a route to answer with, made by `http.file()`. Return it from a
|
|
23
|
+
* handler declared with `.file()`; it is sent as the response body, streamed
|
|
24
|
+
* when `body` is a Readable.
|
|
25
|
+
*/
|
|
26
|
+
export interface FileResponse {
|
|
27
|
+
readonly [FILE_RESPONSE]: true
|
|
28
|
+
readonly body: Buffer | Readable
|
|
29
|
+
readonly mediaType: string
|
|
30
|
+
readonly filename: string | undefined
|
|
31
|
+
readonly disposition: 'attachment' | 'inline'
|
|
32
|
+
readonly length: number | undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Builds the value a handler returns to answer with a file: `body` (Buffer,
|
|
37
|
+
* Uint8Array or Readable), `mediaType`, and optionally `filename`,
|
|
38
|
+
* `disposition` (`'attachment'` default, or `'inline'`) and `length` (the byte
|
|
39
|
+
* length of a stream, sent as Content-Length; without it a stream is sent
|
|
40
|
+
* chunked).
|
|
41
|
+
*
|
|
42
|
+
* Declare the route with `.file({ mediaTypes })` so OpenAPI describes it. The
|
|
43
|
+
* response carries Content-Type, Content-Length (Buffer, or stream with
|
|
44
|
+
* `length`), Content-Disposition with an ASCII `filename=` fallback and the
|
|
45
|
+
* exact name in `filename*=UTF-8''...`, and `X-Content-Type-Options: nosniff`.
|
|
46
|
+
*
|
|
47
|
+
* The request transaction (auto mode) has committed before the first byte is
|
|
48
|
+
* sent, so a stream must not need it. A stream that fails before its first
|
|
49
|
+
* byte gets a 500; one that fails after destroys the connection and logs the
|
|
50
|
+
* error, so the client sees an aborted download, never a complete-looking
|
|
51
|
+
* short one. Range requests are not supported.
|
|
52
|
+
*/
|
|
53
|
+
export function file(init: FileResponseInit): FileResponse {
|
|
54
|
+
if (typeof init.mediaType !== 'string' || init.mediaType.trim() === '' || /[\r\n]/.test(init.mediaType)) {
|
|
55
|
+
throw new Error('[pipework] http.file() needs a mediaType such as "application/pdf" (one line, not empty).')
|
|
56
|
+
}
|
|
57
|
+
const disposition: string = init.disposition ?? 'attachment'
|
|
58
|
+
if (disposition !== 'attachment' && disposition !== 'inline') {
|
|
59
|
+
throw new Error(`[pipework] http.file() disposition must be 'attachment' or 'inline', got ${String(disposition)}.`)
|
|
60
|
+
}
|
|
61
|
+
if (init.length !== undefined && (!Number.isInteger(init.length) || init.length < 0)) {
|
|
62
|
+
throw new Error(`[pipework] http.file() length must be a non-negative integer number of bytes, got ${String(init.length)}.`)
|
|
63
|
+
}
|
|
64
|
+
let body: Buffer | Readable
|
|
65
|
+
if (init.body instanceof Readable) body = init.body
|
|
66
|
+
else if (Buffer.isBuffer(init.body)) body = init.body
|
|
67
|
+
else if (init.body instanceof Uint8Array) body = Buffer.from(init.body.buffer, init.body.byteOffset, init.body.byteLength)
|
|
68
|
+
else throw new Error('[pipework] http.file() body must be a Buffer, Uint8Array or Readable stream.')
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
[FILE_RESPONSE]: true as const,
|
|
71
|
+
body,
|
|
72
|
+
mediaType: init.mediaType.trim(),
|
|
73
|
+
filename: init.filename,
|
|
74
|
+
disposition,
|
|
75
|
+
length: init.length,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** True for a value made by `http.file()`. */
|
|
80
|
+
export function isFileResponse(value: unknown): value is FileResponse {
|
|
81
|
+
return value !== null && typeof value === 'object' && (value as Record<symbol, unknown>)[FILE_RESPONSE] === true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The reason a file response cannot be sent for a route declared with `declared`, or null. */
|
|
85
|
+
export function fileResponseProblem(declared: FileConfig | null, file: FileResponse): string | null {
|
|
86
|
+
if (declared === null) return null
|
|
87
|
+
if (!mediaTypeAllowed(declared.mediaTypes, file.mediaType.split(';')[0]!.trim().toLowerCase())) {
|
|
88
|
+
return `The file's mediaType ${file.mediaType} is not in the route's .file() list (${declared.mediaTypes.join(', ')}).`
|
|
89
|
+
}
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Content-Disposition value per RFC 6266 / RFC 5987: ASCII fallback plus the exact name percent-encoded. */
|
|
94
|
+
export function contentDisposition(disposition: 'attachment' | 'inline', filename: string | undefined): string {
|
|
95
|
+
if (filename === undefined || filename === '') return disposition
|
|
96
|
+
const wellFormed = filename.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, '\uFFFD')
|
|
97
|
+
let fallback = ''
|
|
98
|
+
for (const ch of wellFormed) {
|
|
99
|
+
const code = ch.codePointAt(0)!
|
|
100
|
+
if (code > 0x7e) fallback += '_'
|
|
101
|
+
else if (code < 0x20 || code === 0x7f || ch === '"' || ch === '\\' || ch === '/') continue
|
|
102
|
+
else fallback += ch
|
|
103
|
+
}
|
|
104
|
+
if (fallback.trim() === '') fallback = 'file'
|
|
105
|
+
const encoded = encodeURIComponent(wellFormed).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)
|
|
106
|
+
return `${disposition}; filename="${fallback}"; filename*=UTF-8''${encoded}`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Reads a stream's first chunk before anything is sent, so a stream that fails
|
|
111
|
+
* at once can still become a normal error response. Returns the stream to send
|
|
112
|
+
* (the same bytes, replayed), which fails after `length` mismatches or on error.
|
|
113
|
+
*/
|
|
114
|
+
export async function primeStream(source: Readable, length: number | undefined): Promise<Readable> {
|
|
115
|
+
const iterator = source[Symbol.asyncIterator]()
|
|
116
|
+
let first: IteratorResult<unknown>
|
|
117
|
+
try {
|
|
118
|
+
first = await iterator.next()
|
|
119
|
+
} catch (error) {
|
|
120
|
+
source.destroy()
|
|
121
|
+
throw error
|
|
122
|
+
}
|
|
123
|
+
async function* replay(): AsyncGenerator<Buffer> {
|
|
124
|
+
let sent = 0
|
|
125
|
+
try {
|
|
126
|
+
let step = first
|
|
127
|
+
while (step.done !== true) {
|
|
128
|
+
const chunk = typeof step.value === 'string' ? Buffer.from(step.value) : (step.value as Buffer)
|
|
129
|
+
sent += chunk.length
|
|
130
|
+
yield chunk
|
|
131
|
+
step = await iterator.next()
|
|
132
|
+
}
|
|
133
|
+
if (length !== undefined && sent !== length) {
|
|
134
|
+
throw new Error(`[pipework] The file stream ended after ${sent} bytes but http.file() declared length ${length}.`)
|
|
135
|
+
}
|
|
136
|
+
} finally {
|
|
137
|
+
if (!source.destroyed) source.destroy()
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return Readable.from(replay(), { objectMode: false })
|
|
141
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createServer, type PipeworkServer } from './server.js'
|
|
2
|
+
export type { ServerConfig, RouteRegistration, ErrorResponse, HandlerResponse, InjectOptions, InjectResponse, HttpRequest, HttpResponse, CorsConfig, SecurityConfig, RateLimitConfig, RateLimitClassesConfig, RateLimitClass, RateLimitWindow, RateLimitRefusal, InternalErrorFault } from './types.js'
|
|
3
|
+
export type { FileResponse, FileResponseInit } from './file.js'
|
|
4
|
+
export { buildErrorResponse, buildInternalErrorResponse } from './response.js'
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { FastifyRequest } from 'fastify'
|
|
2
|
+
import type { Manifold } from '../../core/pipework.js'
|
|
3
|
+
import type { AuthStrategy, AuthRequest, AuthContext } from '../../auth/auth/types.js'
|
|
4
|
+
import { parseCookies } from '../../auth/auth/cookie.js'
|
|
5
|
+
import type { AuthEnricher, TenantResolver } from '../../auth/auth/chain.js'
|
|
6
|
+
import type { AuthRequirement } from '../di/types.js'
|
|
7
|
+
import type { TenantConfig } from '../../auth/tenant/types.js'
|
|
8
|
+
import type { LoggingConfig } from '../../core/logging/types.js'
|
|
9
|
+
import { createRequestContext, type Flow } from '../context/index.js'
|
|
10
|
+
import { runAuthChain } from '../../auth/auth/chain.js'
|
|
11
|
+
import { extractTenant } from '../../auth/tenant/index.js'
|
|
12
|
+
import { validateTenantId } from '../../auth/tenant/validate.js'
|
|
13
|
+
import { UnauthorizedError } from '../../core/errors/index.js'
|
|
14
|
+
import { isTracingEnabled } from '../../trace/entry/tracing-enabled.js'
|
|
15
|
+
import { openTrace, type ParentTraceRef } from '../../trace/entry/open-trace.js'
|
|
16
|
+
import { parentTraceRef } from '../../trace/entry/parent-trace.js'
|
|
17
|
+
import { parseTraceparent } from '../../trace/entry/parse-traceparent.js'
|
|
18
|
+
|
|
19
|
+
export function buildAuthRequest(req: FastifyRequest): AuthRequest {
|
|
20
|
+
return {
|
|
21
|
+
headers: req.headers as Record<string, string | undefined>,
|
|
22
|
+
cookies: parseCookies(req.headers['cookie']),
|
|
23
|
+
method: req.method.toUpperCase(),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ContextOptions {
|
|
28
|
+
instance: Manifold
|
|
29
|
+
strategies: AuthStrategy<unknown>[]
|
|
30
|
+
enrichers: AuthEnricher<unknown>[]
|
|
31
|
+
tenantResolver: TenantResolver<unknown> | undefined
|
|
32
|
+
tenantConfig: TenantConfig | null
|
|
33
|
+
authOptional: boolean
|
|
34
|
+
logging: LoggingConfig | null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function buildContext(
|
|
38
|
+
req: FastifyRequest,
|
|
39
|
+
options: ContextOptions,
|
|
40
|
+
authRequirement?: AuthRequirement,
|
|
41
|
+
): Promise<Flow> {
|
|
42
|
+
let auth: unknown = null
|
|
43
|
+
let tenant: string | null = null
|
|
44
|
+
|
|
45
|
+
const isPublic = authRequirement === 'none'
|
|
46
|
+
const isOptional = authRequirement === 'optional' || (authRequirement !== 'required' && options.authOptional)
|
|
47
|
+
|
|
48
|
+
if (options.strategies.length > 0 && !isPublic) {
|
|
49
|
+
const authReq = buildAuthRequest(req)
|
|
50
|
+
const authContext = buildAuthContext(options.instance)
|
|
51
|
+
const result = await runAuthChain(
|
|
52
|
+
{
|
|
53
|
+
strategies: options.strategies,
|
|
54
|
+
...(options.enrichers.length > 0 ? { enrichers: options.enrichers } : {}),
|
|
55
|
+
...(options.tenantResolver ? { tenantResolver: options.tenantResolver } : {}),
|
|
56
|
+
},
|
|
57
|
+
authReq,
|
|
58
|
+
{ optional: isOptional },
|
|
59
|
+
authContext,
|
|
60
|
+
)
|
|
61
|
+
auth = result.auth
|
|
62
|
+
tenant = result.tenant
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (options.tenantConfig) {
|
|
66
|
+
if (tenant === null && auth !== null) {
|
|
67
|
+
const extracted = extractTenant(auth, options.tenantConfig)
|
|
68
|
+
tenant = extracted.tenant
|
|
69
|
+
} else if (tenant !== null) {
|
|
70
|
+
const validator = options.tenantConfig.validate ?? validateTenantId
|
|
71
|
+
if (!validator(tenant)) {
|
|
72
|
+
throw new UnauthorizedError(
|
|
73
|
+
`Invalid tenant identifier: "${tenant.slice(0, 40)}${tenant.length > 40 ? '...' : ''}". ` +
|
|
74
|
+
`Tenant IDs must be valid UUIDs (or pass your custom validate function).`,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const loggingCfg = options.logging
|
|
81
|
+
const traceHeader = loggingCfg?.traceHeader ?? 'x-trace-id'
|
|
82
|
+
const requestIdHeader = loggingCfg?.requestIdHeader ?? 'x-request-id'
|
|
83
|
+
const sessionHeader = loggingCfg?.sessionHeader ?? 'x-session-id'
|
|
84
|
+
|
|
85
|
+
const traceId = (req.headers[traceHeader] as string | undefined)
|
|
86
|
+
?? (req.headers[requestIdHeader] as string | undefined)
|
|
87
|
+
?? req.id
|
|
88
|
+
|
|
89
|
+
const sessionId = (req.headers[sessionHeader] as string | undefined) ?? null
|
|
90
|
+
|
|
91
|
+
const trace = isTracingEnabled(options.instance)
|
|
92
|
+
? openTrace({
|
|
93
|
+
instance: options.instance,
|
|
94
|
+
entryKind: 'http',
|
|
95
|
+
parent: resolveParent(req),
|
|
96
|
+
tenant,
|
|
97
|
+
auth,
|
|
98
|
+
requestId: String(req.id),
|
|
99
|
+
sessionId,
|
|
100
|
+
})
|
|
101
|
+
: null
|
|
102
|
+
|
|
103
|
+
return createRequestContext({
|
|
104
|
+
instance: options.instance,
|
|
105
|
+
requestId: String(req.id),
|
|
106
|
+
auth,
|
|
107
|
+
tenant,
|
|
108
|
+
tenantConfig: options.tenantConfig,
|
|
109
|
+
trace,
|
|
110
|
+
metadata: {
|
|
111
|
+
method: req.method,
|
|
112
|
+
path: req.url,
|
|
113
|
+
startedAt: Date.now(),
|
|
114
|
+
traceId: String(traceId),
|
|
115
|
+
sessionId,
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Picks the parent trace for an inbound HTTP request. An incoming W3C
|
|
122
|
+
* `traceparent` (Q10) wins — it's the explicit cross-system propagation
|
|
123
|
+
* signal — and yields a synthesized parent whose `id` and `rootId` both
|
|
124
|
+
* equal the header's trace-id (the frontend's distributed trace is the root
|
|
125
|
+
* of pipework's tree). If the header is absent or malformed, we fall back to
|
|
126
|
+
* the in-process parent for nested entries (HTTP-via-inject inside a Flow).
|
|
127
|
+
*/
|
|
128
|
+
function resolveParent(req: FastifyRequest): ParentTraceRef | null {
|
|
129
|
+
const parsed = parseTraceparent(req.headers['traceparent'])
|
|
130
|
+
if (parsed !== null) return { id: parsed.traceId, rootId: parsed.traceId }
|
|
131
|
+
return parentTraceRef()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildAuthContext(instance: Manifold): AuthContext {
|
|
135
|
+
return {
|
|
136
|
+
db(name?: string) {
|
|
137
|
+
const dbConfig = instance.config.database(name)
|
|
138
|
+
return instance.pool.getOrCreate(dbConfig).drizzle
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { file } from './file.js'
|
|
2
|
+
import { detectMediaType, resolveMediaType, SNIFF_BYTES } from './sniff.js'
|
|
3
|
+
|
|
4
|
+
/** HTTP namespace: `http.file()` builds a file response; `http.detectMediaType()` names bytes the way uploads do; the server itself is managed by manifold.start(). */
|
|
5
|
+
export const http = { file, detectMediaType, resolveMediaType, SNIFF_BYTES } as const
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { FastifyRequest } from 'fastify'
|
|
2
|
+
import type { Manifold } from '../../core/pipework.js'
|
|
3
|
+
import { parseRouteInputs } from '../di/resolve.js'
|
|
4
|
+
import type { HandlerMeta } from '../di/types.js'
|
|
5
|
+
import type { Flow } from '../context/index.js'
|
|
6
|
+
import { runInContext } from '../context/store.js'
|
|
7
|
+
import type { DB } from '../../data/db/index.js'
|
|
8
|
+
import { sql } from '../../data/db/sql.js'
|
|
9
|
+
import { define } from '../../data/domain/index.js'
|
|
10
|
+
import { field } from '../../data/domain/field.js'
|
|
11
|
+
import { TooManyRequestsError } from '../../core/errors/index.js'
|
|
12
|
+
import type { RateLimitClassesConfig, RateLimitRefusal, RateLimitWindow, RateLimitWindowName } from './types.js'
|
|
13
|
+
|
|
14
|
+
// Counts behind `ServerConfig.rateLimits`; `key` is '<class>:<limit>:<value>' for a key with one window,
|
|
15
|
+
// '<class>:<limit>:<windowSeconds>s:<value>' for a key with an array, and value '*' for `total`.
|
|
16
|
+
export const rateLimitCounts = define('pipework_rate_limit_counts', {
|
|
17
|
+
key: field.text().primaryKey(),
|
|
18
|
+
count: field.integer().default(0),
|
|
19
|
+
windowStart: field.timestamp().default('now'),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const WINDOWS: readonly RateLimitWindowName[] = ['perUser', 'perTenant', 'perAddress', 'total']
|
|
23
|
+
|
|
24
|
+
function windowsOf(win: RateLimitWindow | readonly RateLimitWindow[] | undefined): readonly RateLimitWindow[] {
|
|
25
|
+
return win === undefined ? [] : Array.isArray(win) ? (win as readonly RateLimitWindow[]) : [win as RateLimitWindow]
|
|
26
|
+
}
|
|
27
|
+
const CLEANUP_EVERY_MS = 60_000
|
|
28
|
+
|
|
29
|
+
export interface RateLimitClasses {
|
|
30
|
+
/** Throws at construction for a route that names a class the config does not have. */
|
|
31
|
+
checkRoute(method: string, path: string, meta: HandlerMeta): void
|
|
32
|
+
/** Counts the request against its class and throws a 429 when any window is over. */
|
|
33
|
+
check(req: FastifyRequest, ctx: Flow, meta: HandlerMeta, route: { method: string; url: string }): Promise<void>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createRateLimitClasses(instance: Manifold, config: RateLimitClassesConfig): RateLimitClasses {
|
|
37
|
+
const classes = config.classes
|
|
38
|
+
let longest = 0
|
|
39
|
+
for (const [name, cls] of Object.entries(classes)) {
|
|
40
|
+
for (const w of WINDOWS) {
|
|
41
|
+
const raw = cls[w]
|
|
42
|
+
if (Array.isArray(raw)) {
|
|
43
|
+
const seen = new Set<number>()
|
|
44
|
+
if (raw.length === 0) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`[pipework] rateLimits.classes.${name}.${w} is an empty array.\n` +
|
|
47
|
+
' Fix: Give it at least one window, or remove the key.',
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
for (const win of raw) {
|
|
51
|
+
if (seen.has(win.windowSeconds)) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`[pipework] rateLimits.classes.${name}.${w} has two windows of ${win.windowSeconds} seconds.\n` +
|
|
54
|
+
' Fix: Keep one window for each windowSeconds in a key.',
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
seen.add(win.windowSeconds)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const win of windowsOf(raw)) if (win.windowSeconds > longest) longest = win.windowSeconds
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let lastCleanup = 0
|
|
64
|
+
|
|
65
|
+
const database = (): DB => instance.pool.getOrCreate(instance.config.database(instance.config.defaultDatabaseName())).drizzle
|
|
66
|
+
|
|
67
|
+
function classOf(meta: HandlerMeta, signedIn: boolean): { name: string; explicit: boolean } | null {
|
|
68
|
+
const own = meta.rateLimit ?? null
|
|
69
|
+
if (own === false) return null
|
|
70
|
+
if (own !== null) return { name: own, explicit: true }
|
|
71
|
+
const req = meta.authRequirement
|
|
72
|
+
const name = req === 'required' ? 'signedIn' : req === 'none' ? 'public' : signedIn ? 'signedIn' : 'public'
|
|
73
|
+
return { name, explicit: false }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Counts the request against a key; atomic per key. Runs on the system pool,
|
|
77
|
+
// outside the request's transaction, so a refused or rolled-back request still counts.
|
|
78
|
+
async function count(db: DB, key: string, windowSeconds: number): Promise<{ count: number; retryAfter: number }> {
|
|
79
|
+
const rows = await db.execute(sql`
|
|
80
|
+
INSERT INTO pipework_rate_limit_counts AS r (key, count, window_start)
|
|
81
|
+
VALUES (${key}, 1, now())
|
|
82
|
+
ON CONFLICT (key) DO UPDATE SET
|
|
83
|
+
count = CASE WHEN r.window_start < now() - ${windowSeconds}::int * interval '1 second' THEN 1 ELSE r.count + 1 END,
|
|
84
|
+
window_start = CASE WHEN r.window_start < now() - ${windowSeconds}::int * interval '1 second' THEN now() ELSE r.window_start END
|
|
85
|
+
RETURNING count, ceil(extract(epoch FROM (window_start + ${windowSeconds}::int * interval '1 second' - now())))::int AS retry`)
|
|
86
|
+
const row = rows[0]!
|
|
87
|
+
return { count: Number(row['count']), retryAfter: Math.max(1, Number(row['retry'])) }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cleanup(db: DB, req: FastifyRequest): void {
|
|
91
|
+
const now = Date.now()
|
|
92
|
+
if (now - lastCleanup < CLEANUP_EVERY_MS) return
|
|
93
|
+
lastCleanup = now
|
|
94
|
+
void Promise.resolve(db.execute(sql`DELETE FROM pipework_rate_limit_counts WHERE window_start < now() - ${longest}::int * interval '1 second'`))
|
|
95
|
+
.catch((error: unknown) => req.log.error(error, 'Rate limit cleanup failed'))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function report(req: FastifyRequest, ctx: Flow, event: RateLimitRefusal): void {
|
|
99
|
+
const hook = config.onRefused
|
|
100
|
+
if (hook === undefined) return
|
|
101
|
+
try {
|
|
102
|
+
const result = runInContext(ctx, () => hook(event))
|
|
103
|
+
if (result !== undefined && typeof (result as Promise<void>).catch === 'function') {
|
|
104
|
+
;(result as Promise<void>).catch((error: unknown) => req.log.error(error, 'rateLimits.onRefused threw'))
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
req.log.error(error, 'rateLimits.onRefused threw')
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
checkRoute(method, path, meta) {
|
|
113
|
+
const own = meta.rateLimit ?? null
|
|
114
|
+
if (typeof own === 'string' && classes[own] === undefined) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`[pipework] ${method} ${path}: rate limit class "${own}" is not in rateLimits.classes.\n` +
|
|
117
|
+
` Configured classes: ${Object.keys(classes).join(', ') || '(none)'}.\n` +
|
|
118
|
+
' Fix: Add the class to rateLimits.classes, or use another class name, or .rateLimit(false).',
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
async check(req, ctx, meta, route) {
|
|
124
|
+
const auth = ctx.auth as Record<string, unknown> | null
|
|
125
|
+
const rawUser = auth?.['userId']
|
|
126
|
+
const userId = rawUser === undefined || rawUser === null || rawUser === '' ? null : String(rawUser)
|
|
127
|
+
const resolved = classOf(meta, userId !== null)
|
|
128
|
+
if (resolved === null) return
|
|
129
|
+
const cls = classes[resolved.name]
|
|
130
|
+
if (cls === undefined) return
|
|
131
|
+
|
|
132
|
+
const values: Record<RateLimitWindowName, string | null> = {
|
|
133
|
+
perUser: userId,
|
|
134
|
+
perTenant: ctx.tenant ?? null,
|
|
135
|
+
perAddress: req.ip,
|
|
136
|
+
total: '*',
|
|
137
|
+
}
|
|
138
|
+
const db = database()
|
|
139
|
+
const refused: RateLimitRefusal[] = []
|
|
140
|
+
// Parsed only once a window refuses: most requests pass and never need them.
|
|
141
|
+
let inputs: ReturnType<typeof parseRouteInputs> | undefined
|
|
142
|
+
for (const limit of WINDOWS) {
|
|
143
|
+
const raw = cls[limit]
|
|
144
|
+
const value = values[limit]
|
|
145
|
+
if (raw === undefined || value === null) continue
|
|
146
|
+
const wins = windowsOf(raw)
|
|
147
|
+
const many = Array.isArray(raw)
|
|
148
|
+
for (const win of wins) {
|
|
149
|
+
const key = many ? `${resolved.name}:${limit}:${win.windowSeconds}s:${value}` : `${resolved.name}:${limit}:${value}`
|
|
150
|
+
const got = await count(db, key, win.windowSeconds)
|
|
151
|
+
if (got.count > win.max) {
|
|
152
|
+
refused.push({
|
|
153
|
+
class: resolved.name, limit, key: value, tenantId: ctx.tenant ?? null, userId,
|
|
154
|
+
requestId: ctx.requestId, traceId: ctx.trace?.id ?? null, count: got.count, max: win.max,
|
|
155
|
+
windowSeconds: win.windowSeconds, retryAfterSeconds: got.retryAfter, route,
|
|
156
|
+
...(inputs ??= parseRouteInputs(meta, req.query, req.params)),
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// A refused request is not counted against the later keys: one user over
|
|
161
|
+
// their own limit does not spend the tenant's.
|
|
162
|
+
if (refused.length > 0) break
|
|
163
|
+
}
|
|
164
|
+
cleanup(db, req)
|
|
165
|
+
if (refused.length === 0) return
|
|
166
|
+
for (const event of refused) report(req, ctx, event)
|
|
167
|
+
throw new TooManyRequestsError(Math.max(...refused.map((e) => e.retryAfterSeconds)))
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { PipeworkError, ValidationError } from '../../core/errors/index.js'
|
|
2
|
+
import type { FastifyReply } from 'fastify'
|
|
3
|
+
import type { ErrorResponse } from './types.js'
|
|
4
|
+
|
|
5
|
+
export function buildErrorResponse(error: PipeworkError): ErrorResponse {
|
|
6
|
+
const response: ErrorResponse = {
|
|
7
|
+
error: {
|
|
8
|
+
code: error.code,
|
|
9
|
+
message: error.message,
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (error instanceof ValidationError) {
|
|
14
|
+
response.error.fields = error.fieldErrors
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const wait = (error as { retryAfterSeconds?: unknown }).retryAfterSeconds
|
|
18
|
+
if (typeof wait === 'number' && Number.isFinite(wait)) response.error.retryAfterSeconds = Math.max(0, Math.ceil(wait))
|
|
19
|
+
|
|
20
|
+
return response
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function buildInternalErrorResponse(error?: unknown, exposeDetails?: boolean): ErrorResponse {
|
|
24
|
+
const response: ErrorResponse = {
|
|
25
|
+
error: {
|
|
26
|
+
code: 'INTERNAL_ERROR',
|
|
27
|
+
message: 'An unexpected error occurred',
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (exposeDetails === true && error !== undefined) {
|
|
32
|
+
const root = unwrapCause(error)
|
|
33
|
+
if (root !== null) {
|
|
34
|
+
response.error.details = { name: root.name, message: root.message }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return response
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function unwrapCause(error: unknown): { name: string; message: string } | null {
|
|
42
|
+
if (!(error instanceof Error)) return null
|
|
43
|
+
let current: Error = error
|
|
44
|
+
const seen = new Set<Error>()
|
|
45
|
+
while (current.cause instanceof Error && !seen.has(current.cause)) {
|
|
46
|
+
seen.add(current)
|
|
47
|
+
current = current.cause
|
|
48
|
+
}
|
|
49
|
+
return { name: current.name, message: current.message }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Sends an error reply whole. The per-reply serializer bypasses the route's
|
|
54
|
+
* declared response schema, which describes only what the handler returns.
|
|
55
|
+
*/
|
|
56
|
+
export function sendError(reply: FastifyReply, status: number, body: unknown): FastifyReply {
|
|
57
|
+
void reply.serializer(JSON.stringify)
|
|
58
|
+
void reply.header('content-type', 'application/json; charset=utf-8')
|
|
59
|
+
return reply.status(status).send(body)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The codes of the error classes in core/errors/http.ts, by status.
|
|
63
|
+
const CLIENT_ERROR_CODES: Readonly<Record<number, string>> = {
|
|
64
|
+
401: 'UNAUTHORIZED',
|
|
65
|
+
403: 'FORBIDDEN',
|
|
66
|
+
404: 'NOT_FOUND',
|
|
67
|
+
409: 'CONFLICT',
|
|
68
|
+
413: 'PAYLOAD_TOO_LARGE',
|
|
69
|
+
415: 'UNSUPPORTED_MEDIA_TYPE',
|
|
70
|
+
429: 'TOO_MANY_REQUESTS',
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Maps a client error (4xx) raised by Fastify itself to Pipework's error shape. */
|
|
74
|
+
export function buildClientErrorResponse(status: number, error: unknown, retryAfter?: unknown): ErrorResponse {
|
|
75
|
+
const code = CLIENT_ERROR_CODES[status] ?? 'BAD_REQUEST'
|
|
76
|
+
const raw = (error as { message?: unknown } | null)?.message
|
|
77
|
+
const response: ErrorResponse = { error: { code, message: typeof raw === 'string' && raw !== '' ? raw : 'Bad request' } }
|
|
78
|
+
// @fastify/rate-limit sets Retry-After on the reply before it throws.
|
|
79
|
+
const wait = Number(retryAfter)
|
|
80
|
+
if (status === 429 && retryAfter !== undefined && retryAfter !== '' && Number.isFinite(wait)) response.error.retryAfterSeconds = Math.max(0, Math.ceil(wait))
|
|
81
|
+
return response
|
|
82
|
+
}
|