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,883 @@
|
|
|
1
|
+
import { sql } from '../../data/db/sql.js'
|
|
2
|
+
import { pipe } from '../../data/db/db.js'
|
|
3
|
+
import type { DB } from '../../data/db/index.js'
|
|
4
|
+
import { requireContext, getContext, runInContext } from '../../request/context/store.js'
|
|
5
|
+
import { writeInfraAudit } from '../../data/write/audit/infra-audit.js'
|
|
6
|
+
import { inTenant } from '../../auth/tenant/in-tenant.js'
|
|
7
|
+
import { bindTransaction } from '../../request/context/guard.js'
|
|
8
|
+
import type { Flow } from '../../request/context/types.js'
|
|
9
|
+
import { ValidationError } from '../../core/errors/index.js'
|
|
10
|
+
|
|
11
|
+
/** The span a cap sums settled work over. Calendar windows are cut in the config's `timeZone`; `{ from, to }` is half-open `[from, to)`. */
|
|
12
|
+
export type CapWindow = 'calendar_year' | 'calendar_month' | { readonly from: Date; readonly to: Date }
|
|
13
|
+
|
|
14
|
+
export interface CreditLineCap {
|
|
15
|
+
/** Unique within the line; this is what a refusal names and what an authorization's `replacesCaps` lists. */
|
|
16
|
+
readonly name: string
|
|
17
|
+
readonly cents: number
|
|
18
|
+
readonly window: CapWindow
|
|
19
|
+
/** Overrides the line's and the config's warn share for this cap. */
|
|
20
|
+
readonly warnShare?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CreditLineConfig {
|
|
24
|
+
/** The share of a limit at which it warns, for every limit that sets none. Default 0.8. */
|
|
25
|
+
readonly warnShare?: number
|
|
26
|
+
/** The zone calendar windows are cut in. Default `'UTC'`. */
|
|
27
|
+
readonly timeZone?: string
|
|
28
|
+
/**
|
|
29
|
+
* Awaited INSIDE the transaction of the write that took a limit's use from below its warn amount to
|
|
30
|
+
* at-or-above it (reserve, a settle above its reservation, openLine lowering a limit), in a Flow whose
|
|
31
|
+
* tenant is the write's tenant and where `pipe()` is that same transaction. If it throws, the write
|
|
32
|
+
* rolls back. A release, a payment or a raised limit re-arms the warning; nothing is stored.
|
|
33
|
+
*/
|
|
34
|
+
readonly onLineCrossed?: (event: LineCrossing) => void | Promise<void>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface LimitRef {
|
|
38
|
+
readonly kind: 'ceiling' | 'cap' | 'authorization'
|
|
39
|
+
/** `'ceiling'`, the cap's name, or the authorization's reference. */
|
|
40
|
+
readonly name: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface LineCrossing {
|
|
44
|
+
readonly tenantId: string
|
|
45
|
+
readonly limit: LimitRef
|
|
46
|
+
readonly usedCents: number
|
|
47
|
+
readonly limitCents: number
|
|
48
|
+
readonly warnAtCents: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface LineReservation {
|
|
52
|
+
readonly id: string
|
|
53
|
+
readonly tenantId: string
|
|
54
|
+
readonly jobId: string
|
|
55
|
+
readonly amountCents: number
|
|
56
|
+
readonly status: 'held' | 'settled' | 'released'
|
|
57
|
+
readonly settledAmountCents: number | null
|
|
58
|
+
/** The authorization in force when the job was reserved, if any. */
|
|
59
|
+
readonly authorizationId: string | null
|
|
60
|
+
readonly createdAt: Date
|
|
61
|
+
readonly settledAt: Date | null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type LineReserveResult =
|
|
65
|
+
| { readonly ok: true; readonly reservation: LineReservation }
|
|
66
|
+
| { readonly ok: false; readonly reason: 'no-line' }
|
|
67
|
+
| {
|
|
68
|
+
readonly ok: false; readonly reason: 'limit'; readonly limit: LimitRef
|
|
69
|
+
readonly limitCents: number; readonly usedCents: number; readonly requestedCents: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One settled job: unpaid work until payments clear it. */
|
|
73
|
+
export interface LineEntry {
|
|
74
|
+
readonly id: string
|
|
75
|
+
readonly tenantId: string
|
|
76
|
+
readonly jobId: string
|
|
77
|
+
readonly reservationId: string
|
|
78
|
+
readonly amountCents: number
|
|
79
|
+
readonly paidCents: number
|
|
80
|
+
readonly authorizationId: string | null
|
|
81
|
+
readonly at: Date
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface Payment {
|
|
85
|
+
readonly id: string
|
|
86
|
+
readonly tenantId: string
|
|
87
|
+
readonly reference: string
|
|
88
|
+
readonly amountCents: number
|
|
89
|
+
/** What no entry has cleared yet; the next settle draws on it. */
|
|
90
|
+
readonly unappliedCents: number
|
|
91
|
+
readonly at: Date
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface PaymentApplication {
|
|
95
|
+
readonly entryId: string
|
|
96
|
+
readonly jobId: string
|
|
97
|
+
readonly cents: number
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type PaymentWithEntries = Payment & { readonly applications: readonly PaymentApplication[] }
|
|
101
|
+
|
|
102
|
+
export interface Authorization {
|
|
103
|
+
readonly id: string
|
|
104
|
+
readonly tenantId: string
|
|
105
|
+
readonly reference: string
|
|
106
|
+
readonly amountCents: number
|
|
107
|
+
readonly startsAt: Date
|
|
108
|
+
readonly endsAt: Date
|
|
109
|
+
readonly replacesCaps: readonly string[]
|
|
110
|
+
readonly revokedAt: Date | null
|
|
111
|
+
readonly createdAt: Date
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type AuthorizationStatus = Authorization & {
|
|
115
|
+
/** Settled entries tagged with it. */
|
|
116
|
+
readonly usedCents: number
|
|
117
|
+
/** Held reservations tagged with it. */
|
|
118
|
+
readonly heldCents: number
|
|
119
|
+
readonly remainingCents: number
|
|
120
|
+
/** Started, not ended, not revoked. */
|
|
121
|
+
readonly inForce: boolean
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface LineCapState extends CreditLineCap {
|
|
125
|
+
readonly usedCents: number
|
|
126
|
+
/** The authorization in force replaces this cap. */
|
|
127
|
+
readonly replaced: boolean
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface LineState {
|
|
131
|
+
readonly tenantId: string
|
|
132
|
+
/** False when no line was ever opened, or it is closed. */
|
|
133
|
+
readonly open: boolean
|
|
134
|
+
readonly ceilingCents: number
|
|
135
|
+
readonly warnShare: number | null
|
|
136
|
+
/** Settled work not yet paid. */
|
|
137
|
+
readonly unpaidCents: number
|
|
138
|
+
/** Reservations still held. */
|
|
139
|
+
readonly heldCents: number
|
|
140
|
+
/** What a reserve could still take: the smallest remaining of the limits that apply now, at least 0. */
|
|
141
|
+
readonly availableCents: number
|
|
142
|
+
readonly caps: readonly LineCapState[]
|
|
143
|
+
readonly authorization: AuthorizationStatus | null
|
|
144
|
+
readonly openedAt: Date | null
|
|
145
|
+
readonly closedAt: Date | null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface Statement {
|
|
149
|
+
readonly from: Date
|
|
150
|
+
readonly to: Date
|
|
151
|
+
readonly entries: readonly LineEntry[]
|
|
152
|
+
readonly payments: readonly PaymentWithEntries[]
|
|
153
|
+
readonly totals: {
|
|
154
|
+
/** Sum of the entries in the period. */
|
|
155
|
+
readonly settledCents: number
|
|
156
|
+
/** Sum of the payments in the period. */
|
|
157
|
+
readonly paidCents: number
|
|
158
|
+
/** Everything unpaid as of now, not just this period's. */
|
|
159
|
+
readonly unpaidCents: number
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface OpenLineOptions {
|
|
164
|
+
readonly ceilingCents: number
|
|
165
|
+
readonly caps?: readonly CreditLineCap[]
|
|
166
|
+
readonly warnShare?: number
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface AuthorizeOptions {
|
|
170
|
+
readonly reference: string
|
|
171
|
+
readonly amountCents: number
|
|
172
|
+
readonly from: Date
|
|
173
|
+
readonly to: Date
|
|
174
|
+
/** Names of caps this authorization replaces while it is in force; every other cap still applies. */
|
|
175
|
+
readonly replacesCaps?: readonly string[]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface CreditLine {
|
|
179
|
+
/** Opens the tenant's line, or updates an open one (and reopens a closed one). Audited as `CreditLine` `open`. */
|
|
180
|
+
openLine(options: OpenLineOptions): Promise<LineState>
|
|
181
|
+
openLineFor(tenantId: string, options: OpenLineOptions): Promise<LineState>
|
|
182
|
+
/** New reserves fail `'no-line'`; held reservations may still settle or release; unpaid work stays. */
|
|
183
|
+
closeLine(): Promise<LineState>
|
|
184
|
+
closeLineFor(tenantId: string): Promise<LineState>
|
|
185
|
+
/**
|
|
186
|
+
* Holds `amountCents` for a job against the ceiling (or the authorization in force) and then each cap in
|
|
187
|
+
* order; the first limit it would pass is the refusal and nothing is written. A job id that already has a
|
|
188
|
+
* reservation returns it unchanged, whatever amount is asked this time.
|
|
189
|
+
*/
|
|
190
|
+
reserve(options: { jobId: string; amountCents: number }): Promise<LineReserveResult>
|
|
191
|
+
reserveFor(tenantId: string, options: { jobId: string; amountCents: number }): Promise<LineReserveResult>
|
|
192
|
+
/**
|
|
193
|
+
* Records the actual cost of a reserved job as an unpaid entry; never fails on limits. The same amount again
|
|
194
|
+
* is a no-op; a different amount, a released job or an unknown job throws. Payments with unapplied cents clear it, oldest first.
|
|
195
|
+
*/
|
|
196
|
+
settle(options: { jobId: string; amountCents: number }): Promise<LineReservation>
|
|
197
|
+
settleFor(tenantId: string, options: { jobId: string; amountCents: number }): Promise<LineReservation>
|
|
198
|
+
/** Gives up a held reservation. No reservation: `{ released: false, reservation: null }`; already released: `released: false`. A settled job throws. */
|
|
199
|
+
release(options: { jobId: string }): Promise<{ released: boolean; reservation: LineReservation | null }>
|
|
200
|
+
releaseFor(tenantId: string, options: { jobId: string }): Promise<{ released: boolean; reservation: LineReservation | null }>
|
|
201
|
+
/** Records a purchase order. Idempotent by reference. Dates that overlap an unrevoked authorization throw ValidationError. */
|
|
202
|
+
authorize(options: AuthorizeOptions): Promise<{ authorization: Authorization; created: boolean }>
|
|
203
|
+
authorizeFor(tenantId: string, options: AuthorizeOptions): Promise<{ authorization: Authorization; created: boolean }>
|
|
204
|
+
revokeAuthorization(reference: string): Promise<Authorization>
|
|
205
|
+
revokeAuthorizationFor(tenantId: string, reference: string): Promise<Authorization>
|
|
206
|
+
authorizations(): Promise<AuthorizationStatus[]>
|
|
207
|
+
authorizationsFor(tenantId: string): Promise<AuthorizationStatus[]>
|
|
208
|
+
/** Idempotent by reference. Clears unpaid entries oldest first and keeps any excess as `unappliedCents`. */
|
|
209
|
+
recordPayment(options: { amountCents: number; reference: string; at?: Date }): Promise<{ payment: Payment; created: boolean }>
|
|
210
|
+
recordPaymentFor(tenantId: string, options: { amountCents: number; reference: string; at?: Date }): Promise<{ payment: Payment; created: boolean }>
|
|
211
|
+
/** The entries and payments with `at` in `[from, to)`, each payment with the entries it cleared, and totals. */
|
|
212
|
+
statement(options: { from: Date; to: Date }): Promise<Statement>
|
|
213
|
+
statementFor(tenantId: string, options: { from: Date; to: Date }): Promise<Statement>
|
|
214
|
+
line(): Promise<LineState>
|
|
215
|
+
lineFor(tenantId: string): Promise<LineState>
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
type Row = Record<string, unknown>
|
|
219
|
+
|
|
220
|
+
const DEFAULT_WARN_SHARE = 0.8
|
|
221
|
+
const NOW = sql`(clock_timestamp() AT TIME ZONE 'UTC')`
|
|
222
|
+
|
|
223
|
+
function toDate(value: unknown): Date {
|
|
224
|
+
if (value instanceof Date) return value
|
|
225
|
+
const text = String(value)
|
|
226
|
+
return new Date(/(Z|[+-]\d\d(:?\d\d)?)$/.test(text) ? text : `${text.replace(' ', 'T')}Z`)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function toDateOrNull(value: unknown): Date | null {
|
|
230
|
+
return value === null || value === undefined ? null : toDate(value)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function num(value: unknown): number {
|
|
234
|
+
return Number(value)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function numOrNull(value: unknown): number | null {
|
|
238
|
+
return value === null || value === undefined ? null : Number(value)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function utc(date: Date) {
|
|
242
|
+
return sql`(${date.toISOString()}::timestamptz AT TIME ZONE 'UTC')`
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function rowToReservation(row: Row): LineReservation {
|
|
246
|
+
return {
|
|
247
|
+
id: row['id'] as string,
|
|
248
|
+
tenantId: row['tenant_id'] as string,
|
|
249
|
+
jobId: row['job_id'] as string,
|
|
250
|
+
amountCents: num(row['amount']),
|
|
251
|
+
status: row['status'] as LineReservation['status'],
|
|
252
|
+
settledAmountCents: numOrNull(row['settled_amount']),
|
|
253
|
+
authorizationId: (row['authorization_id'] as string | null) ?? null,
|
|
254
|
+
createdAt: toDate(row['created_at']),
|
|
255
|
+
settledAt: toDateOrNull(row['settled_at']),
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function rowToEntry(row: Row): LineEntry {
|
|
260
|
+
return {
|
|
261
|
+
id: row['id'] as string,
|
|
262
|
+
tenantId: row['tenant_id'] as string,
|
|
263
|
+
jobId: row['job_id'] as string,
|
|
264
|
+
reservationId: row['reservation_id'] as string,
|
|
265
|
+
amountCents: num(row['amount_cents']),
|
|
266
|
+
paidCents: num(row['paid_cents']),
|
|
267
|
+
authorizationId: (row['authorization_id'] as string | null) ?? null,
|
|
268
|
+
at: toDate(row['at']),
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function rowToPayment(row: Row): Payment {
|
|
273
|
+
return {
|
|
274
|
+
id: row['id'] as string,
|
|
275
|
+
tenantId: row['tenant_id'] as string,
|
|
276
|
+
reference: row['reference'] as string,
|
|
277
|
+
amountCents: num(row['amount_cents']),
|
|
278
|
+
unappliedCents: num(row['unapplied_cents']),
|
|
279
|
+
at: toDate(row['at']),
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function rowToAuthorization(row: Row): Authorization {
|
|
284
|
+
const names = row['replaces_caps']
|
|
285
|
+
return {
|
|
286
|
+
id: row['id'] as string,
|
|
287
|
+
tenantId: row['tenant_id'] as string,
|
|
288
|
+
reference: row['reference'] as string,
|
|
289
|
+
amountCents: num(row['amount_cents']),
|
|
290
|
+
startsAt: toDate(row['starts_at']),
|
|
291
|
+
endsAt: toDate(row['ends_at']),
|
|
292
|
+
replacesCaps: Array.isArray(names) ? (names as string[]) : [],
|
|
293
|
+
revokedAt: toDateOrNull(row['revoked_at']),
|
|
294
|
+
createdAt: toDate(row['created_at']),
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
interface StoredCap { name: string; cents: number; window: 'calendar_year' | 'calendar_month' | { from: string; to: string }; warnShare?: number }
|
|
299
|
+
|
|
300
|
+
function capsToJson(caps: readonly CreditLineCap[]): string {
|
|
301
|
+
return JSON.stringify(caps.map((c): StoredCap => ({
|
|
302
|
+
name: c.name,
|
|
303
|
+
cents: c.cents,
|
|
304
|
+
window: typeof c.window === 'string' ? c.window : { from: c.window.from.toISOString(), to: c.window.to.toISOString() },
|
|
305
|
+
...(c.warnShare === undefined ? {} : { warnShare: c.warnShare }),
|
|
306
|
+
})))
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function capsFromJson(value: unknown): CreditLineCap[] {
|
|
310
|
+
const list = Array.isArray(value) ? (value as StoredCap[]) : []
|
|
311
|
+
return list.map(c => ({
|
|
312
|
+
name: c.name,
|
|
313
|
+
cents: c.cents,
|
|
314
|
+
window: typeof c.window === 'string' ? c.window : { from: new Date(c.window.from), to: new Date(c.window.to) },
|
|
315
|
+
...(c.warnShare === undefined ? {} : { warnShare: c.warnShare }),
|
|
316
|
+
}))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function fail(field: string, detail: string, message: string): never {
|
|
320
|
+
throw new ValidationError({ [field]: [detail] }, message)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function requireCents(name: string, value: number, min: 0 | 1): void {
|
|
324
|
+
if (!Number.isSafeInteger(value) || value < min) {
|
|
325
|
+
const detail = min === 1 ? 'must be a positive integer' : 'must be a non-negative integer'
|
|
326
|
+
fail(name, detail, `Credit line ${name} ${detail} (whole cents).`)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function requireText(name: string, value: unknown): void {
|
|
331
|
+
if (typeof value !== 'string' || value === '') fail(name, 'must be a non-empty string', `Credit line ${name} must be a non-empty string.`)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function requireShare(name: string, value: number): void {
|
|
335
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0 || value > 1) {
|
|
336
|
+
fail(name, 'must be greater than 0 and at most 1', `Credit line ${name} must be greater than 0 and at most 1.`)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function requireSpan(name: string, from: Date, to: Date): void {
|
|
341
|
+
if (!(from instanceof Date) || !(to instanceof Date) || Number.isNaN(from.getTime()) || Number.isNaN(to.getTime()) || from.getTime() >= to.getTime()) {
|
|
342
|
+
fail(name, 'from must be a valid date before to', `Credit line ${name}: from must be a valid date before to.`)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function requireCaps(caps: readonly CreditLineCap[]): void {
|
|
347
|
+
const seen = new Set<string>()
|
|
348
|
+
for (const cap of caps) {
|
|
349
|
+
requireText('caps.name', cap.name)
|
|
350
|
+
if (seen.has(cap.name)) fail('caps', `name "${cap.name}" is used twice`, `Credit line cap names must be unique; "${cap.name}" appears twice.`)
|
|
351
|
+
seen.add(cap.name)
|
|
352
|
+
requireCents(`cap "${cap.name}" cents`, cap.cents, 1)
|
|
353
|
+
if (cap.warnShare !== undefined) requireShare(`cap "${cap.name}" warnShare`, cap.warnShare)
|
|
354
|
+
const window: unknown = cap.window
|
|
355
|
+
if (window === 'calendar_year' || window === 'calendar_month') continue
|
|
356
|
+
const span = (typeof window === 'object' && window !== null ? window : {}) as { from?: Date; to?: Date }
|
|
357
|
+
if (span.from === undefined || span.to === undefined) {
|
|
358
|
+
fail('caps.window', 'must be calendar_year, calendar_month or { from, to }', `Credit line cap "${cap.name}" window must be 'calendar_year', 'calendar_month' or { from, to }.`)
|
|
359
|
+
}
|
|
360
|
+
requireSpan(`cap "${cap.name}" window`, span.from, span.to)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
interface LineRow {
|
|
365
|
+
readonly id: string
|
|
366
|
+
readonly ceilingCents: number
|
|
367
|
+
readonly warnShare: number | null
|
|
368
|
+
readonly caps: CreditLineCap[]
|
|
369
|
+
readonly openedAt: Date
|
|
370
|
+
readonly closedAt: Date | null
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
interface Limit {
|
|
374
|
+
readonly ref: LimitRef
|
|
375
|
+
readonly limitCents: number
|
|
376
|
+
readonly usedCents: number
|
|
377
|
+
readonly warnAtCents: number
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
interface Snapshot {
|
|
381
|
+
readonly line: LineRow | null
|
|
382
|
+
readonly auth: AuthorizationStatus | null
|
|
383
|
+
readonly unpaid: number
|
|
384
|
+
readonly held: number
|
|
385
|
+
readonly caps: LineCapState[]
|
|
386
|
+
/** Limits in force, in check order: the ceiling or the authorization, then the caps not replaced. */
|
|
387
|
+
readonly limits: Limit[]
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function createCreditLine(config: CreditLineConfig = {}): CreditLine {
|
|
391
|
+
const defaultShare = config.warnShare ?? DEFAULT_WARN_SHARE
|
|
392
|
+
requireShare('config warnShare', defaultShare)
|
|
393
|
+
const timeZone = config.timeZone ?? 'UTC'
|
|
394
|
+
try {
|
|
395
|
+
new Intl.DateTimeFormat('en', { timeZone })
|
|
396
|
+
} catch {
|
|
397
|
+
throw new Error(`[pipework] creditLine timeZone "${timeZone}" is not a time zone name (use an IANA name such as "America/New_York").\n`)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
type InFlow = <R>(body: () => Promise<R>) => Promise<R>
|
|
401
|
+
|
|
402
|
+
async function run<T>(explicit: string | null, fn: (tx: DB, tenantId: string, inFlow: InFlow) => Promise<T>): Promise<T> {
|
|
403
|
+
if (explicit !== null) {
|
|
404
|
+
requireTenantId(explicit)
|
|
405
|
+
const flow = requireContext()
|
|
406
|
+
return inTenant(flow, explicit, tx => fn(tx, explicit, body => runForTenant(flow, explicit, tx, body)))
|
|
407
|
+
}
|
|
408
|
+
const flow = requireContext()
|
|
409
|
+
if (flow.tenant === null) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
'[pipework] This creditLine method needs a tenant in the Flow.\n\n' +
|
|
412
|
+
' Call it from an authenticated tenant route, or use the ...For(tenantId, ...) form\n' +
|
|
413
|
+
' (openLineFor, reserveFor, settleFor, ...) from a webhook or a job with no tenant.\n',
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
const tenantId = flow.tenant
|
|
417
|
+
return pipe.transaction((tx: DB) => fn(tx, tenantId, body => body()))
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function runForTenant<R>(flow: Flow, tenantId: string, tx: DB, body: () => Promise<R>): Promise<R> {
|
|
421
|
+
const dbName = flow.instance.config.defaultDatabaseName()
|
|
422
|
+
const child: Flow = { ...flow, tenant: tenantId, transactions: new Map(flow.transactions) }
|
|
423
|
+
child.transactions.set(dbName, bindTransaction(child, dbName, tx))
|
|
424
|
+
return runInContext(child, body)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function requireTenantId(tenantId: string): void {
|
|
428
|
+
requireText('tenantId', tenantId)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function lockTenant(tx: DB, tenantId: string): Promise<void> {
|
|
432
|
+
await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtextextended(${`pipework.credit_line:${tenantId}`}, 0))`)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function auditStep(tx: DB, tenantId: string, lineId: string, verb: string, after: Record<string, unknown>, before?: Record<string, unknown>): Promise<void> {
|
|
436
|
+
const flow = getContext()
|
|
437
|
+
if (flow === undefined) return
|
|
438
|
+
await writeInfraAudit(flow, tx, { tenantId, entity: 'CreditLine', verb, entityId: lineId, after, ...(before === undefined ? {} : { before }) })
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function loadLine(tx: DB, tenantId: string): Promise<LineRow | null> {
|
|
442
|
+
const rows = await tx.execute(sql`SELECT * FROM credit_line WHERE tenant_id = ${tenantId}`)
|
|
443
|
+
if (rows.length === 0) return null
|
|
444
|
+
const r = rows[0]!
|
|
445
|
+
return {
|
|
446
|
+
id: r['id'] as string,
|
|
447
|
+
ceilingCents: num(r['ceiling_cents']),
|
|
448
|
+
warnShare: numOrNull(r['warn_share']),
|
|
449
|
+
caps: capsFromJson(r['caps']),
|
|
450
|
+
openedAt: toDate(r['opened_at']),
|
|
451
|
+
closedAt: toDateOrNull(r['closed_at']),
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function requireLine(tx: DB, tenantId: string, what: string): Promise<LineRow> {
|
|
456
|
+
const line = await loadLine(tx, tenantId)
|
|
457
|
+
if (line === null) {
|
|
458
|
+
throw new Error(`[pipework] creditLine.${what}: tenant ${tenantId} has no line.\n\n Call openLine({ ceilingCents }) first.\n`)
|
|
459
|
+
}
|
|
460
|
+
return line
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function windowBounds(window: CapWindow) {
|
|
464
|
+
if (typeof window !== 'string') return { start: utc(window.from), end: utc(window.to) }
|
|
465
|
+
const unit = window === 'calendar_year' ? 'year' : 'month'
|
|
466
|
+
const local = sql`date_trunc(${sql.raw(`'${unit}'`)}, clock_timestamp() AT TIME ZONE ${timeZone}::text)`
|
|
467
|
+
return {
|
|
468
|
+
start: sql`((${local}) AT TIME ZONE ${timeZone}::text) AT TIME ZONE 'UTC'`,
|
|
469
|
+
end: sql`((${local} + ${sql.raw(`interval '1 ${unit}'`)}) AT TIME ZONE ${timeZone}::text) AT TIME ZONE 'UTC'`,
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function sumOf(tx: DB, query: ReturnType<typeof sql>): Promise<number> {
|
|
474
|
+
const rows = await tx.execute(query)
|
|
475
|
+
return num(rows[0]!['n'])
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function authStatus(row: Row, used: number, held: number, inForce: boolean): AuthorizationStatus {
|
|
479
|
+
const a = rowToAuthorization(row)
|
|
480
|
+
return { ...a, usedCents: used, heldCents: held, remainingCents: Math.max(0, a.amountCents - used - held), inForce }
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function statusOf(tx: DB, tenantId: string, row: Row): Promise<AuthorizationStatus> {
|
|
484
|
+
const id = row['id'] as string
|
|
485
|
+
const used = await sumOf(tx, sql`SELECT COALESCE(SUM(amount_cents), 0) AS n FROM credit_line_entry WHERE tenant_id = ${tenantId} AND authorization_id = ${id}`)
|
|
486
|
+
const held = await sumOf(tx, sql`SELECT COALESCE(SUM(amount), 0) AS n FROM credit_line_reservation WHERE tenant_id = ${tenantId} AND authorization_id = ${id} AND status = 'held'`)
|
|
487
|
+
const flag = await tx.execute(sql`
|
|
488
|
+
SELECT (revoked_at IS NULL AND starts_at <= ${NOW} AND ${NOW} < ends_at) AS in_force FROM credit_line_authorization WHERE id = ${id}
|
|
489
|
+
`)
|
|
490
|
+
return authStatus(row, used, held, flag[0]!['in_force'] === true)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Everything a limit check or a crossing needs, read in the operation's own transaction.
|
|
494
|
+
async function measure(tx: DB, tenantId: string): Promise<Snapshot> {
|
|
495
|
+
const line = await loadLine(tx, tenantId)
|
|
496
|
+
const unpaid = await sumOf(tx, sql`SELECT COALESCE(SUM(amount_cents - paid_cents), 0) AS n FROM credit_line_entry WHERE tenant_id = ${tenantId}`)
|
|
497
|
+
const held = await sumOf(tx, sql`SELECT COALESCE(SUM(amount), 0) AS n FROM credit_line_reservation WHERE tenant_id = ${tenantId} AND status = 'held'`)
|
|
498
|
+
if (line === null) return { line, auth: null, unpaid, held, caps: [], limits: [] }
|
|
499
|
+
const authRows = await tx.execute(sql`
|
|
500
|
+
SELECT * FROM credit_line_authorization
|
|
501
|
+
WHERE tenant_id = ${tenantId} AND revoked_at IS NULL AND starts_at <= ${NOW} AND ${NOW} < ends_at
|
|
502
|
+
ORDER BY starts_at DESC LIMIT 1
|
|
503
|
+
`)
|
|
504
|
+
const auth = authRows.length === 0 ? null : await statusOf(tx, tenantId, authRows[0]!)
|
|
505
|
+
const warnAt = (limit: number, own?: number | null): number =>
|
|
506
|
+
Math.floor(limit * (own ?? line.warnShare ?? defaultShare) + 1e-9)
|
|
507
|
+
const limits: Limit[] = auth === null
|
|
508
|
+
? [{ ref: { kind: 'ceiling', name: 'ceiling' }, limitCents: line.ceilingCents, usedCents: unpaid + held, warnAtCents: warnAt(line.ceilingCents) }]
|
|
509
|
+
: [{ ref: { kind: 'authorization', name: auth.reference }, limitCents: auth.amountCents, usedCents: auth.usedCents + auth.heldCents, warnAtCents: warnAt(auth.amountCents) }]
|
|
510
|
+
const caps: LineCapState[] = []
|
|
511
|
+
for (const cap of line.caps) {
|
|
512
|
+
const { start, end } = windowBounds(cap.window)
|
|
513
|
+
const settled = await sumOf(tx, sql`
|
|
514
|
+
SELECT COALESCE(SUM(amount_cents), 0) AS n FROM credit_line_entry
|
|
515
|
+
WHERE tenant_id = ${tenantId} AND "at" >= ${start} AND "at" < ${end}
|
|
516
|
+
`)
|
|
517
|
+
const used = settled + held
|
|
518
|
+
const replaced = auth !== null && auth.replacesCaps.includes(cap.name)
|
|
519
|
+
caps.push({ ...cap, usedCents: used, replaced })
|
|
520
|
+
if (!replaced) limits.push({ ref: { kind: 'cap', name: cap.name }, limitCents: cap.cents, usedCents: used, warnAtCents: warnAt(cap.cents, cap.warnShare) })
|
|
521
|
+
}
|
|
522
|
+
return { line, auth, unpaid, held, caps, limits }
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function stateOf(tenantId: string, s: Snapshot): LineState {
|
|
526
|
+
const remaining = s.limits.map(l => l.limitCents - l.usedCents)
|
|
527
|
+
return {
|
|
528
|
+
tenantId,
|
|
529
|
+
open: s.line !== null && s.line.closedAt === null,
|
|
530
|
+
ceilingCents: s.line?.ceilingCents ?? 0,
|
|
531
|
+
warnShare: s.line?.warnShare ?? null,
|
|
532
|
+
unpaidCents: s.unpaid,
|
|
533
|
+
heldCents: s.held,
|
|
534
|
+
availableCents: remaining.length === 0 ? 0 : Math.max(0, Math.min(...remaining)),
|
|
535
|
+
caps: s.caps,
|
|
536
|
+
authorization: s.auth,
|
|
537
|
+
openedAt: s.line?.openedAt ?? null,
|
|
538
|
+
closedAt: s.line?.closedAt ?? null,
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// A limit warns when a write takes its use from below its warn amount to at-or-above it. Compared
|
|
543
|
+
// within the write, so nothing is stored and a fall back below re-arms it. Awaited in the tenant's
|
|
544
|
+
// Flow, so a throwing hook rolls the write back.
|
|
545
|
+
async function fireCrossings(tenantId: string, inFlow: InFlow, before: Snapshot, after: Snapshot): Promise<void> {
|
|
546
|
+
const fn = config.onLineCrossed
|
|
547
|
+
if (fn === undefined) return
|
|
548
|
+
const key = (l: Limit): string => `${l.ref.kind}:${l.ref.name}`
|
|
549
|
+
const prior = new Map(before.limits.map(l => [key(l), l]))
|
|
550
|
+
for (const l of after.limits) {
|
|
551
|
+
const b = prior.get(key(l))
|
|
552
|
+
if (b === undefined || !(b.usedCents < b.warnAtCents && l.usedCents >= l.warnAtCents)) continue
|
|
553
|
+
const event: LineCrossing = { tenantId, limit: l.ref, usedCents: l.usedCents, limitCents: l.limitCents, warnAtCents: l.warnAtCents }
|
|
554
|
+
await inFlow(async () => { await fn(event) })
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async function findReservation(tx: DB, tenantId: string, jobId: string, lock: boolean): Promise<LineReservation | null> {
|
|
559
|
+
const rows = await tx.execute(lock
|
|
560
|
+
? sql`SELECT * FROM credit_line_reservation WHERE tenant_id = ${tenantId} AND job_id = ${jobId} FOR UPDATE`
|
|
561
|
+
: sql`SELECT * FROM credit_line_reservation WHERE tenant_id = ${tenantId} AND job_id = ${jobId}`)
|
|
562
|
+
return rows.length === 0 ? null : rowToReservation(rows[0]!)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Pays unpaid entries from a payment's unapplied cents, oldest entry first.
|
|
566
|
+
async function applyPayment(tx: DB, tenantId: string, paymentId: string, unapplied: number): Promise<number> {
|
|
567
|
+
const entries = await tx.execute(sql`
|
|
568
|
+
SELECT id, amount_cents, paid_cents FROM credit_line_entry
|
|
569
|
+
WHERE tenant_id = ${tenantId} AND amount_cents > paid_cents ORDER BY "at", id FOR UPDATE
|
|
570
|
+
`)
|
|
571
|
+
let left = unapplied
|
|
572
|
+
for (const e of entries) {
|
|
573
|
+
if (left === 0) break
|
|
574
|
+
const take = Math.min(num(e['amount_cents']) - num(e['paid_cents']), left)
|
|
575
|
+
await tx.execute(sql`UPDATE credit_line_entry SET paid_cents = paid_cents + ${take} WHERE id = ${e['id'] as string}`)
|
|
576
|
+
await tx.execute(sql`
|
|
577
|
+
INSERT INTO credit_line_payment_application (tenant_id, payment_id, entry_id, cents) VALUES (${tenantId}, ${paymentId}, ${e['id'] as string}, ${take})
|
|
578
|
+
`)
|
|
579
|
+
left -= take
|
|
580
|
+
}
|
|
581
|
+
await tx.execute(sql`UPDATE credit_line_payment SET unapplied_cents = ${left} WHERE id = ${paymentId}`)
|
|
582
|
+
return left
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Pays one new entry from payments with unapplied cents, oldest payment first.
|
|
586
|
+
async function clearEntry(tx: DB, tenantId: string, entryId: string, amount: number): Promise<number> {
|
|
587
|
+
const payments = await tx.execute(sql`
|
|
588
|
+
SELECT id, unapplied_cents FROM credit_line_payment
|
|
589
|
+
WHERE tenant_id = ${tenantId} AND unapplied_cents > 0 ORDER BY "at", id FOR UPDATE
|
|
590
|
+
`)
|
|
591
|
+
let need = amount
|
|
592
|
+
for (const p of payments) {
|
|
593
|
+
if (need === 0) break
|
|
594
|
+
const take = Math.min(num(p['unapplied_cents']), need)
|
|
595
|
+
await tx.execute(sql`UPDATE credit_line_payment SET unapplied_cents = unapplied_cents - ${take} WHERE id = ${p['id'] as string}`)
|
|
596
|
+
await tx.execute(sql`
|
|
597
|
+
INSERT INTO credit_line_payment_application (tenant_id, payment_id, entry_id, cents) VALUES (${tenantId}, ${p['id'] as string}, ${entryId}, ${take})
|
|
598
|
+
`)
|
|
599
|
+
need -= take
|
|
600
|
+
}
|
|
601
|
+
return amount - need
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async function doOpen(explicit: string | null, o: OpenLineOptions): Promise<LineState> {
|
|
605
|
+
requireCents('ceilingCents', o.ceilingCents, 1)
|
|
606
|
+
if (o.warnShare !== undefined) requireShare('warnShare', o.warnShare)
|
|
607
|
+
const caps = o.caps ?? []
|
|
608
|
+
requireCaps(caps)
|
|
609
|
+
return run(explicit, async (tx, tenantId, inFlow) => {
|
|
610
|
+
await lockTenant(tx, tenantId)
|
|
611
|
+
const before = await measure(tx, tenantId)
|
|
612
|
+
const rows = await tx.execute(sql`
|
|
613
|
+
INSERT INTO credit_line (tenant_id, ceiling_cents, warn_share, caps, opened_at, closed_at)
|
|
614
|
+
VALUES (${tenantId}, ${o.ceilingCents}, ${o.warnShare ?? null}, ${capsToJson(caps)}::jsonb, ${NOW}, NULL)
|
|
615
|
+
ON CONFLICT (tenant_id) DO UPDATE
|
|
616
|
+
SET ceiling_cents = EXCLUDED.ceiling_cents, warn_share = EXCLUDED.warn_share, caps = EXCLUDED.caps, closed_at = NULL
|
|
617
|
+
RETURNING id
|
|
618
|
+
`)
|
|
619
|
+
const after = await measure(tx, tenantId)
|
|
620
|
+
const prev = before.line
|
|
621
|
+
await auditStep(tx, tenantId, rows[0]!['id'] as string, 'open', {
|
|
622
|
+
ceilingCents: o.ceilingCents, warnShare: o.warnShare ?? null, caps: JSON.parse(capsToJson(caps)) as unknown,
|
|
623
|
+
}, prev === null ? undefined : {
|
|
624
|
+
ceilingCents: prev.ceilingCents, warnShare: prev.warnShare, caps: JSON.parse(capsToJson(prev.caps)) as unknown, closed: prev.closedAt !== null,
|
|
625
|
+
})
|
|
626
|
+
await fireCrossings(tenantId, inFlow, before, after)
|
|
627
|
+
return stateOf(tenantId, after)
|
|
628
|
+
})
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
async function doClose(explicit: string | null): Promise<LineState> {
|
|
632
|
+
return run(explicit, async (tx, tenantId) => {
|
|
633
|
+
await lockTenant(tx, tenantId)
|
|
634
|
+
const line = await requireLine(tx, tenantId, 'closeLine')
|
|
635
|
+
if (line.closedAt === null) {
|
|
636
|
+
await tx.execute(sql`UPDATE credit_line SET closed_at = ${NOW} WHERE id = ${line.id}`)
|
|
637
|
+
await auditStep(tx, tenantId, line.id, 'close', { closed: true })
|
|
638
|
+
}
|
|
639
|
+
return stateOf(tenantId, await measure(tx, tenantId))
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async function doReserve(explicit: string | null, o: { jobId: string; amountCents: number }): Promise<LineReserveResult> {
|
|
644
|
+
requireText('jobId', o.jobId)
|
|
645
|
+
requireCents('amountCents', o.amountCents, 1)
|
|
646
|
+
return run(explicit, async (tx, tenantId, inFlow): Promise<LineReserveResult> => {
|
|
647
|
+
await lockTenant(tx, tenantId)
|
|
648
|
+
const existing = await findReservation(tx, tenantId, o.jobId, false)
|
|
649
|
+
if (existing !== null) return { ok: true, reservation: existing }
|
|
650
|
+
const before = await measure(tx, tenantId)
|
|
651
|
+
if (before.line === null || before.line.closedAt !== null) return { ok: false, reason: 'no-line' }
|
|
652
|
+
for (const l of before.limits) {
|
|
653
|
+
if (l.usedCents + o.amountCents > l.limitCents) {
|
|
654
|
+
return { ok: false, reason: 'limit', limit: l.ref, limitCents: l.limitCents, usedCents: l.usedCents, requestedCents: o.amountCents }
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const inserted = await tx.execute(sql`
|
|
658
|
+
INSERT INTO credit_line_reservation (tenant_id, job_id, amount, status, authorization_id, created_at)
|
|
659
|
+
VALUES (${tenantId}, ${o.jobId}, ${o.amountCents}, 'held', ${before.auth?.id ?? null}, ${NOW})
|
|
660
|
+
ON CONFLICT (tenant_id, job_id) DO NOTHING
|
|
661
|
+
RETURNING *
|
|
662
|
+
`)
|
|
663
|
+
if (inserted.length === 0) return { ok: true, reservation: (await findReservation(tx, tenantId, o.jobId, false))! }
|
|
664
|
+
const reservation = rowToReservation(inserted[0]!)
|
|
665
|
+
await auditStep(tx, tenantId, before.line.id, 'reserve', {
|
|
666
|
+
jobId: o.jobId, amountCents: o.amountCents, reservationId: reservation.id, authorizationId: reservation.authorizationId,
|
|
667
|
+
})
|
|
668
|
+
await fireCrossings(tenantId, inFlow, before, await measure(tx, tenantId))
|
|
669
|
+
return { ok: true, reservation }
|
|
670
|
+
})
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function doSettle(explicit: string | null, o: { jobId: string; amountCents: number }): Promise<LineReservation> {
|
|
674
|
+
requireText('jobId', o.jobId)
|
|
675
|
+
requireCents('amountCents', o.amountCents, 0)
|
|
676
|
+
return run(explicit, async (tx, tenantId, inFlow) => {
|
|
677
|
+
await lockTenant(tx, tenantId)
|
|
678
|
+
const reservation = await findReservation(tx, tenantId, o.jobId, true)
|
|
679
|
+
if (reservation === null) {
|
|
680
|
+
throw new Error(`[pipework] creditLine.settle: no reservation for job "${o.jobId}".\n\n Call reserve({ jobId, amountCents }) before the job, with the same jobId.\n`)
|
|
681
|
+
}
|
|
682
|
+
if (reservation.status === 'released') {
|
|
683
|
+
throw new Error(`[pipework] creditLine.settle: the reservation for job "${o.jobId}" was released.\n`)
|
|
684
|
+
}
|
|
685
|
+
if (reservation.status === 'settled') {
|
|
686
|
+
if (reservation.settledAmountCents === o.amountCents) return reservation
|
|
687
|
+
throw new Error(
|
|
688
|
+
`[pipework] creditLine.settle: job "${o.jobId}" was already settled at ${reservation.settledAmountCents}, not ${o.amountCents}.\n`,
|
|
689
|
+
)
|
|
690
|
+
}
|
|
691
|
+
const line = await requireLine(tx, tenantId, 'settle')
|
|
692
|
+
const before = await measure(tx, tenantId)
|
|
693
|
+
const rows = await tx.execute(sql`
|
|
694
|
+
UPDATE credit_line_reservation SET status = 'settled', settled_amount = ${o.amountCents}, settled_at = ${NOW}
|
|
695
|
+
WHERE id = ${reservation.id} RETURNING *
|
|
696
|
+
`)
|
|
697
|
+
const entry = await tx.execute(sql`
|
|
698
|
+
INSERT INTO credit_line_entry (tenant_id, job_id, reservation_id, amount_cents, paid_cents, authorization_id, "at")
|
|
699
|
+
VALUES (${tenantId}, ${o.jobId}, ${reservation.id}, ${o.amountCents}, 0, ${reservation.authorizationId}, ${NOW})
|
|
700
|
+
RETURNING id
|
|
701
|
+
`)
|
|
702
|
+
const entryId = entry[0]!['id'] as string
|
|
703
|
+
const cleared = await clearEntry(tx, tenantId, entryId, o.amountCents)
|
|
704
|
+
if (cleared > 0) await tx.execute(sql`UPDATE credit_line_entry SET paid_cents = ${cleared} WHERE id = ${entryId}`)
|
|
705
|
+
await auditStep(tx, tenantId, line.id, 'settle', {
|
|
706
|
+
jobId: o.jobId, amountCents: o.amountCents, reservedCents: reservation.amountCents, reservationId: reservation.id,
|
|
707
|
+
entryId, authorizationId: reservation.authorizationId, clearedCents: cleared,
|
|
708
|
+
})
|
|
709
|
+
await fireCrossings(tenantId, inFlow, before, await measure(tx, tenantId))
|
|
710
|
+
return rowToReservation(rows[0]!)
|
|
711
|
+
})
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async function doRelease(explicit: string | null, o: { jobId: string }): Promise<{ released: boolean; reservation: LineReservation | null }> {
|
|
715
|
+
requireText('jobId', o.jobId)
|
|
716
|
+
return run(explicit, async (tx, tenantId) => {
|
|
717
|
+
await lockTenant(tx, tenantId)
|
|
718
|
+
const reservation = await findReservation(tx, tenantId, o.jobId, true)
|
|
719
|
+
if (reservation === null) return { released: false, reservation: null }
|
|
720
|
+
if (reservation.status === 'released') return { released: false, reservation }
|
|
721
|
+
if (reservation.status === 'settled') {
|
|
722
|
+
throw new Error(`[pipework] creditLine.release: job "${o.jobId}" was already settled; settled usage cannot be released.\n`)
|
|
723
|
+
}
|
|
724
|
+
const line = await requireLine(tx, tenantId, 'release')
|
|
725
|
+
const rows = await tx.execute(sql`
|
|
726
|
+
UPDATE credit_line_reservation SET status = 'released', settled_at = ${NOW} WHERE id = ${reservation.id} RETURNING *
|
|
727
|
+
`)
|
|
728
|
+
await auditStep(tx, tenantId, line.id, 'release', {
|
|
729
|
+
jobId: o.jobId, amountCents: reservation.amountCents, reservationId: reservation.id, authorizationId: reservation.authorizationId,
|
|
730
|
+
})
|
|
731
|
+
return { released: true, reservation: rowToReservation(rows[0]!) }
|
|
732
|
+
})
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async function doAuthorize(explicit: string | null, o: AuthorizeOptions): Promise<{ authorization: Authorization; created: boolean }> {
|
|
736
|
+
requireText('reference', o.reference)
|
|
737
|
+
requireCents('amountCents', o.amountCents, 1)
|
|
738
|
+
requireSpan('authorization dates', o.from, o.to)
|
|
739
|
+
const replaces = o.replacesCaps ?? []
|
|
740
|
+
for (const name of replaces) requireText('replacesCaps', name)
|
|
741
|
+
return run(explicit, async (tx, tenantId) => {
|
|
742
|
+
await lockTenant(tx, tenantId)
|
|
743
|
+
const line = await requireLine(tx, tenantId, 'authorize')
|
|
744
|
+
const found = await tx.execute(sql`SELECT * FROM credit_line_authorization WHERE tenant_id = ${tenantId} AND reference = ${o.reference}`)
|
|
745
|
+
if (found.length > 0) return { authorization: rowToAuthorization(found[0]!), created: false }
|
|
746
|
+
const clash = await tx.execute(sql`
|
|
747
|
+
SELECT reference FROM credit_line_authorization
|
|
748
|
+
WHERE tenant_id = ${tenantId} AND revoked_at IS NULL AND starts_at < ${utc(o.to)} AND ${utc(o.from)} < ends_at
|
|
749
|
+
LIMIT 1
|
|
750
|
+
`)
|
|
751
|
+
if (clash.length > 0) {
|
|
752
|
+
fail('from', `overlaps authorization "${clash[0]!['reference'] as string}"`,
|
|
753
|
+
`Credit line authorization "${o.reference}" overlaps "${clash[0]!['reference'] as string}"; revoke that one or choose dates after it ends.`)
|
|
754
|
+
}
|
|
755
|
+
const rows = await tx.execute(sql`
|
|
756
|
+
INSERT INTO credit_line_authorization (tenant_id, reference, amount_cents, starts_at, ends_at, replaces_caps, created_at)
|
|
757
|
+
VALUES (${tenantId}, ${o.reference}, ${o.amountCents}, ${utc(o.from)}, ${utc(o.to)}, ${JSON.stringify(replaces)}::jsonb, ${NOW})
|
|
758
|
+
RETURNING *
|
|
759
|
+
`)
|
|
760
|
+
const authorization = rowToAuthorization(rows[0]!)
|
|
761
|
+
await auditStep(tx, tenantId, line.id, 'authorize', {
|
|
762
|
+
reference: o.reference, amountCents: o.amountCents, from: o.from.toISOString(), to: o.to.toISOString(), replacesCaps: replaces,
|
|
763
|
+
authorizationId: authorization.id,
|
|
764
|
+
})
|
|
765
|
+
return { authorization, created: true }
|
|
766
|
+
})
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async function doRevoke(explicit: string | null, reference: string): Promise<Authorization> {
|
|
770
|
+
requireText('reference', reference)
|
|
771
|
+
return run(explicit, async (tx, tenantId) => {
|
|
772
|
+
await lockTenant(tx, tenantId)
|
|
773
|
+
const line = await requireLine(tx, tenantId, 'revokeAuthorization')
|
|
774
|
+
const found = await tx.execute(sql`SELECT * FROM credit_line_authorization WHERE tenant_id = ${tenantId} AND reference = ${reference} FOR UPDATE`)
|
|
775
|
+
if (found.length === 0) {
|
|
776
|
+
throw new Error(`[pipework] creditLine.revokeAuthorization: no authorization "${reference}" for tenant ${tenantId}.\n`)
|
|
777
|
+
}
|
|
778
|
+
const current = rowToAuthorization(found[0]!)
|
|
779
|
+
if (current.revokedAt !== null) return current
|
|
780
|
+
const rows = await tx.execute(sql`UPDATE credit_line_authorization SET revoked_at = ${NOW} WHERE id = ${current.id} RETURNING *`)
|
|
781
|
+
await auditStep(tx, tenantId, line.id, 'revoke_authorization', { reference, authorizationId: current.id })
|
|
782
|
+
return rowToAuthorization(rows[0]!)
|
|
783
|
+
})
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async function doAuthorizations(explicit: string | null): Promise<AuthorizationStatus[]> {
|
|
787
|
+
return run(explicit, async (tx, tenantId) => {
|
|
788
|
+
const rows = await tx.execute(sql`SELECT * FROM credit_line_authorization WHERE tenant_id = ${tenantId} ORDER BY created_at, id`)
|
|
789
|
+
const out: AuthorizationStatus[] = []
|
|
790
|
+
for (const row of rows) out.push(await statusOf(tx, tenantId, row))
|
|
791
|
+
return out
|
|
792
|
+
})
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
async function doPayment(explicit: string | null, o: { amountCents: number; reference: string; at?: Date }): Promise<{ payment: Payment; created: boolean }> {
|
|
796
|
+
requireText('reference', o.reference)
|
|
797
|
+
requireCents('amountCents', o.amountCents, 1)
|
|
798
|
+
if (o.at !== undefined && Number.isNaN(o.at.getTime())) fail('at', 'must be a valid date', 'Credit line payment at must be a valid date.')
|
|
799
|
+
return run(explicit, async (tx, tenantId) => {
|
|
800
|
+
await lockTenant(tx, tenantId)
|
|
801
|
+
const line = await requireLine(tx, tenantId, 'recordPayment')
|
|
802
|
+
const found = await tx.execute(sql`SELECT * FROM credit_line_payment WHERE tenant_id = ${tenantId} AND reference = ${o.reference}`)
|
|
803
|
+
if (found.length > 0) return { payment: rowToPayment(found[0]!), created: false }
|
|
804
|
+
const rows = await tx.execute(sql`
|
|
805
|
+
INSERT INTO credit_line_payment (tenant_id, reference, amount_cents, unapplied_cents, "at")
|
|
806
|
+
VALUES (${tenantId}, ${o.reference}, ${o.amountCents}, ${o.amountCents}, ${o.at === undefined ? NOW : utc(o.at)})
|
|
807
|
+
RETURNING id
|
|
808
|
+
`)
|
|
809
|
+
const id = rows[0]!['id'] as string
|
|
810
|
+
const left = await applyPayment(tx, tenantId, id, o.amountCents)
|
|
811
|
+
await auditStep(tx, tenantId, line.id, 'payment', {
|
|
812
|
+
reference: o.reference, amountCents: o.amountCents, clearedCents: o.amountCents - left, unappliedCents: left, paymentId: id,
|
|
813
|
+
})
|
|
814
|
+
const saved = await tx.execute(sql`SELECT * FROM credit_line_payment WHERE id = ${id}`)
|
|
815
|
+
return { payment: rowToPayment(saved[0]!), created: true }
|
|
816
|
+
})
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
async function doStatement(explicit: string | null, o: { from: Date; to: Date }): Promise<Statement> {
|
|
820
|
+
requireSpan('statement period', o.from, o.to)
|
|
821
|
+
return run(explicit, async (tx, tenantId): Promise<Statement> => {
|
|
822
|
+
const entries = (await tx.execute(sql`
|
|
823
|
+
SELECT * FROM credit_line_entry WHERE tenant_id = ${tenantId} AND "at" >= ${utc(o.from)} AND "at" < ${utc(o.to)} ORDER BY "at", id
|
|
824
|
+
`)).map(rowToEntry)
|
|
825
|
+
const paymentRows = await tx.execute(sql`
|
|
826
|
+
SELECT * FROM credit_line_payment WHERE tenant_id = ${tenantId} AND "at" >= ${utc(o.from)} AND "at" < ${utc(o.to)} ORDER BY "at", id
|
|
827
|
+
`)
|
|
828
|
+
const payments: PaymentWithEntries[] = []
|
|
829
|
+
for (const row of paymentRows) {
|
|
830
|
+
const apps = await tx.execute(sql`
|
|
831
|
+
SELECT a.entry_id, a.cents, e.job_id FROM credit_line_payment_application a
|
|
832
|
+
JOIN credit_line_entry e ON e.id = a.entry_id
|
|
833
|
+
WHERE a.tenant_id = ${tenantId} AND a.payment_id = ${row['id'] as string} ORDER BY e."at", e.id
|
|
834
|
+
`)
|
|
835
|
+
payments.push({
|
|
836
|
+
...rowToPayment(row),
|
|
837
|
+
applications: apps.map(a => ({ entryId: a['entry_id'] as string, jobId: a['job_id'] as string, cents: num(a['cents']) })),
|
|
838
|
+
})
|
|
839
|
+
}
|
|
840
|
+
const unpaid = await sumOf(tx, sql`SELECT COALESCE(SUM(amount_cents - paid_cents), 0) AS n FROM credit_line_entry WHERE tenant_id = ${tenantId}`)
|
|
841
|
+
return {
|
|
842
|
+
from: o.from,
|
|
843
|
+
to: o.to,
|
|
844
|
+
entries,
|
|
845
|
+
payments,
|
|
846
|
+
totals: {
|
|
847
|
+
settledCents: entries.reduce((s, e) => s + e.amountCents, 0),
|
|
848
|
+
paidCents: payments.reduce((s, p) => s + p.amountCents, 0),
|
|
849
|
+
unpaidCents: unpaid,
|
|
850
|
+
},
|
|
851
|
+
}
|
|
852
|
+
})
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function doLine(explicit: string | null): Promise<LineState> {
|
|
856
|
+
return run(explicit, async (tx, tenantId) => stateOf(tenantId, await measure(tx, tenantId)))
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
return {
|
|
860
|
+
openLine: o => doOpen(null, o),
|
|
861
|
+
openLineFor: (t, o) => doOpen(t, o),
|
|
862
|
+
closeLine: () => doClose(null),
|
|
863
|
+
closeLineFor: t => doClose(t),
|
|
864
|
+
reserve: o => doReserve(null, o),
|
|
865
|
+
reserveFor: (t, o) => doReserve(t, o),
|
|
866
|
+
settle: o => doSettle(null, o),
|
|
867
|
+
settleFor: (t, o) => doSettle(t, o),
|
|
868
|
+
release: o => doRelease(null, o),
|
|
869
|
+
releaseFor: (t, o) => doRelease(t, o),
|
|
870
|
+
authorize: o => doAuthorize(null, o),
|
|
871
|
+
authorizeFor: (t, o) => doAuthorize(t, o),
|
|
872
|
+
revokeAuthorization: r => doRevoke(null, r),
|
|
873
|
+
revokeAuthorizationFor: (t, r) => doRevoke(t, r),
|
|
874
|
+
authorizations: () => doAuthorizations(null),
|
|
875
|
+
authorizationsFor: t => doAuthorizations(t),
|
|
876
|
+
recordPayment: o => doPayment(null, o),
|
|
877
|
+
recordPaymentFor: (t, o) => doPayment(t, o),
|
|
878
|
+
statement: o => doStatement(null, o),
|
|
879
|
+
statementFor: (t, o) => doStatement(t, o),
|
|
880
|
+
line: () => doLine(null),
|
|
881
|
+
lineFor: t => doLine(t),
|
|
882
|
+
}
|
|
883
|
+
}
|