@plumbus/core 0.7.0 → 0.8.0-beta.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 +46 -1
- package/README.md +2 -6
- package/dist/.tsbuildinfo +1 -1
- package/dist/admission/client.d.ts +9 -0
- package/dist/admission/client.d.ts.map +1 -0
- package/dist/admission/client.js +34 -0
- package/dist/admission/client.js.map +1 -0
- package/dist/admission/index.d.ts +5 -0
- package/dist/admission/index.d.ts.map +1 -0
- package/dist/admission/index.js +4 -0
- package/dist/admission/index.js.map +1 -0
- package/dist/admission/request-admission.d.ts +26 -0
- package/dist/admission/request-admission.d.ts.map +1 -0
- package/dist/admission/request-admission.js +85 -0
- package/dist/admission/request-admission.js.map +1 -0
- package/dist/admission/shared.d.ts +31 -0
- package/dist/admission/shared.d.ts.map +1 -0
- package/dist/admission/shared.js +30 -0
- package/dist/admission/shared.js.map +1 -0
- package/dist/ai/ai/provider.d.ts +5 -7
- package/dist/ai/ai-service.d.ts +8 -1
- package/dist/ai/ai-service.d.ts.map +1 -1
- package/dist/ai/ai-service.js +23 -13
- package/dist/ai/ai-service.js.map +1 -1
- package/dist/ai/errors/data-errors.d.ts +6 -0
- package/dist/ai/errors/index.d.ts +4 -1
- package/dist/ai/fill-prompt-template.d.ts +12 -0
- package/dist/ai/fill-prompt-template.d.ts.map +1 -0
- package/dist/ai/fill-prompt-template.js +19 -0
- package/dist/ai/fill-prompt-template.js.map +1 -0
- package/dist/ai/governed-artifacts.d.ts +49 -0
- package/dist/ai/governed-artifacts.d.ts.map +1 -0
- package/dist/ai/governed-artifacts.js +277 -0
- package/dist/ai/governed-artifacts.js.map +1 -0
- package/dist/ai/governed-budget.d.ts +17 -0
- package/dist/ai/governed-budget.d.ts.map +1 -0
- package/dist/ai/governed-budget.js +52 -0
- package/dist/ai/governed-budget.js.map +1 -0
- package/dist/ai/governed-host.d.ts +54 -0
- package/dist/ai/governed-host.d.ts.map +1 -0
- package/dist/ai/governed-host.js +8 -0
- package/dist/ai/governed-host.js.map +1 -0
- package/dist/ai/governed-invoke.d.ts +41 -0
- package/dist/ai/governed-invoke.d.ts.map +1 -0
- package/dist/ai/governed-invoke.js +102 -0
- package/dist/ai/governed-invoke.js.map +1 -0
- package/dist/ai/governed-runtime.d.ts +103 -0
- package/dist/ai/governed-runtime.d.ts.map +1 -0
- package/dist/ai/governed-runtime.js +151 -0
- package/dist/ai/governed-runtime.js.map +1 -0
- package/dist/ai/index.d.ts +12 -2
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +8 -2
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/prompt-registry.d.ts +6 -0
- package/dist/ai/prompt-registry.d.ts.map +1 -1
- package/dist/ai/prompt-registry.js +12 -0
- package/dist/ai/prompt-registry.js.map +1 -1
- package/dist/ai/provider.d.ts +5 -7
- package/dist/ai/provider.d.ts.map +1 -1
- package/dist/ai/provider.js +29 -0
- package/dist/ai/provider.js.map +1 -1
- package/dist/ai/types/enums.d.ts +2 -0
- package/dist/ai/types/fields.d.ts +7 -0
- package/dist/ai/types/prompt.d.ts +8 -0
- package/dist/ai/validation.d.ts.map +1 -1
- package/dist/ai/validation.js +10 -1
- package/dist/ai/validation.js.map +1 -1
- package/dist/api/exposure.d.ts +14 -0
- package/dist/api/exposure.d.ts.map +1 -1
- package/dist/api/exposure.js +16 -0
- package/dist/api/exposure.js.map +1 -1
- package/dist/api/idempotency.d.ts +67 -0
- package/dist/api/idempotency.d.ts.map +1 -0
- package/dist/api/idempotency.js +176 -0
- package/dist/api/idempotency.js.map +1 -0
- package/dist/api/index.d.ts +3 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +4 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/route-generator.d.ts +50 -6
- package/dist/api/route-generator.d.ts.map +1 -1
- package/dist/api/route-generator.js +254 -12
- package/dist/api/route-generator.js.map +1 -1
- package/dist/approvals/action-risk.d.ts +41 -0
- package/dist/approvals/action-risk.d.ts.map +1 -0
- package/dist/approvals/action-risk.js +56 -0
- package/dist/approvals/action-risk.js.map +1 -0
- package/dist/approvals/actors.d.ts +8 -0
- package/dist/approvals/actors.d.ts.map +1 -0
- package/dist/approvals/actors.js +26 -0
- package/dist/approvals/actors.js.map +1 -0
- package/dist/approvals/authorization.d.ts +5 -0
- package/dist/approvals/authorization.d.ts.map +1 -0
- package/dist/approvals/authorization.js +16 -0
- package/dist/approvals/authorization.js.map +1 -0
- package/dist/approvals/digest.d.ts +3 -0
- package/dist/approvals/digest.d.ts.map +1 -0
- package/dist/approvals/digest.js +21 -0
- package/dist/approvals/digest.js.map +1 -0
- package/dist/approvals/gate.d.ts +15 -0
- package/dist/approvals/gate.d.ts.map +1 -0
- package/dist/approvals/gate.js +102 -0
- package/dist/approvals/gate.js.map +1 -0
- package/dist/approvals/host-runtime.d.ts +16 -0
- package/dist/approvals/host-runtime.d.ts.map +1 -0
- package/dist/approvals/host-runtime.js +12 -0
- package/dist/approvals/host-runtime.js.map +1 -0
- package/dist/approvals/index.d.ts +19 -0
- package/dist/approvals/index.d.ts.map +1 -0
- package/dist/approvals/index.js +13 -0
- package/dist/approvals/index.js.map +1 -0
- package/dist/approvals/memory-store.d.ts +3 -0
- package/dist/approvals/memory-store.d.ts.map +1 -0
- package/dist/approvals/memory-store.js +33 -0
- package/dist/approvals/memory-store.js.map +1 -0
- package/dist/approvals/schema.d.ts +484 -0
- package/dist/approvals/schema.d.ts.map +1 -0
- package/dist/approvals/schema.js +55 -0
- package/dist/approvals/schema.js.map +1 -0
- package/dist/approvals/service.d.ts +17 -0
- package/dist/approvals/service.d.ts.map +1 -0
- package/dist/approvals/service.js +240 -0
- package/dist/approvals/service.js.map +1 -0
- package/dist/approvals/sql-store.d.ts +9 -0
- package/dist/approvals/sql-store.d.ts.map +1 -0
- package/dist/approvals/sql-store.js +179 -0
- package/dist/approvals/sql-store.js.map +1 -0
- package/dist/approvals/types.d.ts +156 -0
- package/dist/approvals/types.d.ts.map +1 -0
- package/dist/approvals/types.js +32 -0
- package/dist/approvals/types.js.map +1 -0
- package/dist/approvals/wait.d.ts +10 -0
- package/dist/approvals/wait.d.ts.map +1 -0
- package/dist/approvals/wait.js +11 -0
- package/dist/approvals/wait.js.map +1 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +2 -1
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/commands/api.d.ts +65 -0
- package/dist/cli/commands/api.d.ts.map +1 -1
- package/dist/cli/commands/api.js +79 -4
- package/dist/cli/commands/api.js.map +1 -1
- package/dist/cli/commands/compile-flows.d.ts +9 -0
- package/dist/cli/commands/compile-flows.d.ts.map +1 -0
- package/dist/cli/commands/compile-flows.js +55 -0
- package/dist/cli/commands/compile-flows.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +6 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +22 -5
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/e2e.d.ts +5 -0
- package/dist/cli/commands/e2e.d.ts.map +1 -1
- package/dist/cli/commands/e2e.js +42 -3
- package/dist/cli/commands/e2e.js.map +1 -1
- package/dist/cli/commands/flow.d.ts.map +1 -1
- package/dist/cli/commands/flow.js +12 -3
- package/dist/cli/commands/flow.js.map +1 -1
- package/dist/cli/commands/generate-openapi-from-zod.d.ts +26 -0
- package/dist/cli/commands/generate-openapi-from-zod.d.ts.map +1 -0
- package/dist/cli/commands/generate-openapi-from-zod.js +189 -0
- package/dist/cli/commands/generate-openapi-from-zod.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +10 -4
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +27 -70
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +2 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts +5 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +28 -2
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/migrate.d.ts +1 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +145 -34
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/commands/openapi-3-1.d.ts +34 -0
- package/dist/cli/commands/openapi-3-1.d.ts.map +1 -0
- package/dist/cli/commands/openapi-3-1.js +333 -0
- package/dist/cli/commands/openapi-3-1.js.map +1 -0
- package/dist/cli/commands/start.d.ts +5 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +20 -2
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/ui.d.ts +11 -0
- package/dist/cli/commands/ui.d.ts.map +1 -1
- package/dist/cli/commands/ui.js +105 -20
- package/dist/cli/commands/ui.js.map +1 -1
- package/dist/cli/commands/verify.d.ts.map +1 -1
- package/dist/cli/commands/verify.js +2 -0
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/commands/worker.d.ts +5 -0
- package/dist/cli/commands/worker.d.ts.map +1 -1
- package/dist/cli/commands/worker.js +18 -2
- package/dist/cli/commands/worker.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/credentials/catalog.d.ts +71 -0
- package/dist/credentials/catalog.d.ts.map +1 -0
- package/dist/credentials/catalog.js +293 -0
- package/dist/credentials/catalog.js.map +1 -0
- package/dist/credentials/index.d.ts +3 -0
- package/dist/credentials/index.d.ts.map +1 -0
- package/dist/credentials/index.js +2 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/data/drift-inspector.d.ts +1 -4
- package/dist/data/drift-inspector.d.ts.map +1 -1
- package/dist/data/drift-inspector.js +39 -5
- package/dist/data/drift-inspector.js.map +1 -1
- package/dist/data/field-encryption.d.ts +7 -0
- package/dist/data/field-encryption.d.ts.map +1 -1
- package/dist/data/field-encryption.js +35 -17
- package/dist/data/field-encryption.js.map +1 -1
- package/dist/data/framework-migration-rewriter.js +1 -1
- package/dist/data/framework-migration-rewriter.js.map +1 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +1 -1
- package/dist/data/index.js.map +1 -1
- package/dist/data/mask-fields.d.ts +2 -0
- package/dist/data/mask-fields.d.ts.map +1 -1
- package/dist/data/mask-fields.js +6 -0
- package/dist/data/mask-fields.js.map +1 -1
- package/dist/data/migration.d.ts.map +1 -1
- package/dist/data/migration.js +25 -1
- package/dist/data/migration.js.map +1 -1
- package/dist/data/repository.d.ts.map +1 -1
- package/dist/data/repository.js +15 -3
- package/dist/data/repository.js.map +1 -1
- package/dist/data/schema-generator.d.ts +95 -3
- package/dist/data/schema-generator.d.ts.map +1 -1
- package/dist/data/schema-generator.js +122 -6
- package/dist/data/schema-generator.js.map +1 -1
- package/dist/define/defineCapability.d.ts +7 -0
- package/dist/define/defineCapability.d.ts.map +1 -1
- package/dist/define/defineCapability.js +15 -0
- package/dist/define/defineCapability.js.map +1 -1
- package/dist/define/defineFlow.d.ts +3 -1
- package/dist/define/defineFlow.d.ts.map +1 -1
- package/dist/define/defineFlow.js.map +1 -1
- package/dist/durable/apply-ddl.d.ts +13 -0
- package/dist/durable/apply-ddl.d.ts.map +1 -0
- package/dist/durable/apply-ddl.js +278 -0
- package/dist/durable/apply-ddl.js.map +1 -0
- package/dist/durable/harness.d.ts +31 -0
- package/dist/durable/harness.d.ts.map +1 -0
- package/dist/durable/harness.js +136 -0
- package/dist/durable/harness.js.map +1 -0
- package/dist/durable/index.d.ts +18 -0
- package/dist/durable/index.d.ts.map +1 -0
- package/dist/durable/index.js +16 -0
- package/dist/durable/index.js.map +1 -0
- package/dist/durable/memory-store.d.ts +48 -0
- package/dist/durable/memory-store.d.ts.map +1 -0
- package/dist/durable/memory-store.js +260 -0
- package/dist/durable/memory-store.js.map +1 -0
- package/dist/durable/migrations-path.d.ts +5 -0
- package/dist/durable/migrations-path.d.ts.map +1 -0
- package/dist/durable/migrations-path.js +8 -0
- package/dist/durable/migrations-path.js.map +1 -0
- package/dist/durable/opaque-dispatch.d.ts +7 -0
- package/dist/durable/opaque-dispatch.d.ts.map +1 -0
- package/dist/durable/opaque-dispatch.js +67 -0
- package/dist/durable/opaque-dispatch.js.map +1 -0
- package/dist/durable/persist-before-ack.d.ts +52 -0
- package/dist/durable/persist-before-ack.d.ts.map +1 -0
- package/dist/durable/persist-before-ack.js +153 -0
- package/dist/durable/persist-before-ack.js.map +1 -0
- package/dist/durable/pg-env.d.ts +9 -0
- package/dist/durable/pg-env.d.ts.map +1 -0
- package/dist/durable/pg-env.js +20 -0
- package/dist/durable/pg-env.js.map +1 -0
- package/dist/durable/postgres-persist.d.ts +33 -0
- package/dist/durable/postgres-persist.d.ts.map +1 -0
- package/dist/durable/postgres-persist.js +239 -0
- package/dist/durable/postgres-persist.js.map +1 -0
- package/dist/durable/reconciliation.d.ts +20 -0
- package/dist/durable/reconciliation.d.ts.map +1 -0
- package/dist/durable/reconciliation.js +91 -0
- package/dist/durable/reconciliation.js.map +1 -0
- package/dist/durable/schema.d.ts +1448 -0
- package/dist/durable/schema.d.ts.map +1 -0
- package/dist/durable/schema.js +138 -0
- package/dist/durable/schema.js.map +1 -0
- package/dist/durable/spine-claim.d.ts +16 -0
- package/dist/durable/spine-claim.d.ts.map +1 -0
- package/dist/durable/spine-claim.js +131 -0
- package/dist/durable/spine-claim.js.map +1 -0
- package/dist/durable/types.d.ts +153 -0
- package/dist/durable/types.d.ts.map +1 -0
- package/dist/durable/types.js +114 -0
- package/dist/durable/types.js.map +1 -0
- package/dist/errors/data-errors.d.ts +6 -0
- package/dist/errors/data-errors.d.ts.map +1 -1
- package/dist/errors/data-errors.js +9 -0
- package/dist/errors/data-errors.js.map +1 -1
- package/dist/errors/index.d.ts +4 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +11 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/errors-entry.d.ts +1 -1
- package/dist/errors-entry.d.ts.map +1 -1
- package/dist/errors-entry.js +1 -1
- package/dist/errors-entry.js.map +1 -1
- package/dist/events/consumer-registry.d.ts +6 -0
- package/dist/events/consumer-registry.d.ts.map +1 -1
- package/dist/events/consumer-registry.js.map +1 -1
- package/dist/events/dispatcher.d.ts +19 -0
- package/dist/events/dispatcher.d.ts.map +1 -1
- package/dist/events/dispatcher.js +161 -114
- package/dist/events/dispatcher.js.map +1 -1
- package/dist/events/idempotency.d.ts +3 -2
- package/dist/events/idempotency.d.ts.map +1 -1
- package/dist/events/idempotency.js +3 -2
- package/dist/events/idempotency.js.map +1 -1
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +1 -0
- package/dist/events/index.js.map +1 -1
- package/dist/events/subscription.d.ts +29 -0
- package/dist/events/subscription.d.ts.map +1 -0
- package/dist/events/subscription.js +19 -0
- package/dist/events/subscription.js.map +1 -0
- package/dist/events/worker.d.ts +8 -1
- package/dist/events/worker.d.ts.map +1 -1
- package/dist/events/worker.js +32 -5
- package/dist/events/worker.js.map +1 -1
- package/dist/execution/capability-executor.d.ts +6 -3
- package/dist/execution/capability-executor.d.ts.map +1 -1
- package/dist/execution/capability-executor.js +34 -6
- package/dist/execution/capability-executor.js.map +1 -1
- package/dist/execution/context-deps.d.ts +2 -0
- package/dist/execution/context-deps.d.ts.map +1 -1
- package/dist/execution/context-deps.js +2 -1
- package/dist/execution/context-deps.js.map +1 -1
- package/dist/execution/context-factory.d.ts +38 -1
- package/dist/execution/context-factory.d.ts.map +1 -1
- package/dist/execution/context-factory.js +102 -4
- package/dist/execution/context-factory.js.map +1 -1
- package/dist/execution/index.d.ts +2 -2
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +1 -1
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/transactional-outbox.d.ts +10 -0
- package/dist/execution/transactional-outbox.d.ts.map +1 -1
- package/dist/execution/transactional-outbox.js +41 -2
- package/dist/execution/transactional-outbox.js.map +1 -1
- package/dist/flows/budget.d.ts +44 -0
- package/dist/flows/budget.d.ts.map +1 -0
- package/dist/flows/budget.js +81 -0
- package/dist/flows/budget.js.map +1 -0
- package/dist/flows/compile-flow.d.ts +23 -0
- package/dist/flows/compile-flow.d.ts.map +1 -0
- package/dist/flows/compile-flow.js +176 -0
- package/dist/flows/compile-flow.js.map +1 -0
- package/dist/flows/compiled-registry.d.ts +43 -0
- package/dist/flows/compiled-registry.d.ts.map +1 -0
- package/dist/flows/compiled-registry.js +198 -0
- package/dist/flows/compiled-registry.js.map +1 -0
- package/dist/flows/dead-letter.d.ts +16 -0
- package/dist/flows/dead-letter.d.ts.map +1 -1
- package/dist/flows/dead-letter.js +67 -0
- package/dist/flows/dead-letter.js.map +1 -1
- package/dist/flows/definition-strategy.d.ts +13 -0
- package/dist/flows/definition-strategy.d.ts.map +1 -0
- package/dist/flows/definition-strategy.js +30 -0
- package/dist/flows/definition-strategy.js.map +1 -0
- package/dist/flows/engine.d.ts +38 -1
- package/dist/flows/engine.d.ts.map +1 -1
- package/dist/flows/engine.js +375 -32
- package/dist/flows/engine.js.map +1 -1
- package/dist/flows/index.d.ts +12 -4
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +7 -2
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/scheduler.d.ts +26 -0
- package/dist/flows/scheduler.d.ts.map +1 -1
- package/dist/flows/scheduler.js +157 -37
- package/dist/flows/scheduler.js.map +1 -1
- package/dist/flows/schema.d.ts +34 -0
- package/dist/flows/schema.d.ts.map +1 -1
- package/dist/flows/schema.js +4 -0
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/step-executor.d.ts +3 -0
- package/dist/flows/step-executor.d.ts.map +1 -1
- package/dist/flows/step-executor.js +33 -0
- package/dist/flows/step-executor.js.map +1 -1
- package/dist/governance/capability-source-scan.d.ts.map +1 -1
- package/dist/governance/capability-source-scan.js +2 -29
- package/dist/governance/capability-source-scan.js.map +1 -1
- package/dist/governance/forbidden-path-scan.d.ts +7 -0
- package/dist/governance/forbidden-path-scan.d.ts.map +1 -0
- package/dist/governance/forbidden-path-scan.js +60 -0
- package/dist/governance/forbidden-path-scan.js.map +1 -0
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.d.ts.map +1 -1
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -1
- package/dist/governance/source-walk.d.ts +3 -0
- package/dist/governance/source-walk.d.ts.map +1 -0
- package/dist/governance/source-walk.js +33 -0
- package/dist/governance/source-walk.js.map +1 -0
- package/dist/index.d.ts +21 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -9
- package/dist/index.js.map +1 -1
- package/dist/runtime/bootstrap.d.ts +3 -1
- package/dist/runtime/bootstrap.d.ts.map +1 -1
- package/dist/runtime/load-extensions.d.ts.map +1 -1
- package/dist/runtime/load-extensions.js +11 -0
- package/dist/runtime/load-extensions.js.map +1 -1
- package/dist/runtime/register-consumers.d.ts +3 -0
- package/dist/runtime/register-consumers.d.ts.map +1 -1
- package/dist/runtime/register-consumers.js +4 -0
- package/dist/runtime/register-consumers.js.map +1 -1
- package/dist/runtime/start-worker-pool.d.ts.map +1 -1
- package/dist/runtime/start-worker-pool.js +1 -0
- package/dist/runtime/start-worker-pool.js.map +1 -1
- package/dist/runtime-entry.d.ts +4 -0
- package/dist/runtime-entry.d.ts.map +1 -0
- package/dist/runtime-entry.js +30 -0
- package/dist/runtime-entry.js.map +1 -0
- package/dist/server/bootstrap.d.ts +116 -8
- package/dist/server/bootstrap.d.ts.map +1 -1
- package/dist/server/bootstrap.js +139 -62
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/tenancy/data-plane-connection.d.ts +108 -0
- package/dist/tenancy/data-plane-connection.d.ts.map +1 -0
- package/dist/tenancy/data-plane-connection.js +320 -0
- package/dist/tenancy/data-plane-connection.js.map +1 -0
- package/dist/tenancy/data-plane-migrate.d.ts +30 -0
- package/dist/tenancy/data-plane-migrate.d.ts.map +1 -0
- package/dist/tenancy/data-plane-migrate.js +44 -0
- package/dist/tenancy/data-plane-migrate.js.map +1 -0
- package/dist/tenancy/data-plane-provisioning.d.ts +135 -0
- package/dist/tenancy/data-plane-provisioning.d.ts.map +1 -0
- package/dist/tenancy/data-plane-provisioning.js +639 -0
- package/dist/tenancy/data-plane-provisioning.js.map +1 -0
- package/dist/tenancy/data-plane-resolver.d.ts +37 -0
- package/dist/tenancy/data-plane-resolver.d.ts.map +1 -0
- package/dist/tenancy/data-plane-resolver.js +302 -0
- package/dist/tenancy/data-plane-resolver.js.map +1 -0
- package/dist/tenancy/index.d.ts +9 -0
- package/dist/tenancy/index.d.ts.map +1 -0
- package/dist/tenancy/index.js +22 -0
- package/dist/tenancy/index.js.map +1 -0
- package/dist/tenancy/types.d.ts +117 -0
- package/dist/tenancy/types.d.ts.map +1 -0
- package/dist/tenancy/types.js +8 -0
- package/dist/tenancy/types.js.map +1 -0
- package/dist/testing/context.d.ts.map +1 -1
- package/dist/testing/context.js +6 -1
- package/dist/testing/context.js.map +1 -1
- package/dist/testing/crash-matrix-simulation.d.ts +51 -0
- package/dist/testing/crash-matrix-simulation.d.ts.map +1 -0
- package/dist/testing/crash-matrix-simulation.js +350 -0
- package/dist/testing/crash-matrix-simulation.js.map +1 -0
- package/dist/testing/scaffolding.d.ts.map +1 -1
- package/dist/testing/scaffolding.js +4 -1
- package/dist/testing/scaffolding.js.map +1 -1
- package/dist/types/capability.d.ts +16 -0
- package/dist/types/capability.d.ts.map +1 -1
- package/dist/types/capability.js.map +1 -1
- package/dist/types/context.d.ts +62 -0
- package/dist/types/context.d.ts.map +1 -1
- package/dist/types/enums.d.ts +2 -0
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/enums.js +2 -0
- package/dist/types/enums.js.map +1 -1
- package/dist/types/fields.d.ts +7 -0
- package/dist/types/fields.d.ts.map +1 -1
- package/dist/types/flow.d.ts +96 -1
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/flow.js +7 -1
- package/dist/types/flow.js.map +1 -1
- package/dist/types/index.d.ts +9 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +5 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/prompt.d.ts +8 -0
- package/dist/types/prompt.d.ts.map +1 -1
- package/dist/types/prompt.js +7 -0
- package/dist/types/prompt.js.map +1 -1
- package/dist/worker/bootstrap.d.ts +100 -1
- package/dist/worker/bootstrap.d.ts.map +1 -1
- package/dist/worker/bootstrap.js +155 -14
- package/dist/worker/bootstrap.js.map +1 -1
- package/instructions/api.md +21 -0
- package/instructions/cli.md +15 -9
- package/instructions/deployment.md +27 -25
- package/instructions/flows.md +8 -1
- package/instructions/framework.md +2 -1
- package/instructions/mcp.md +1 -1
- package/instructions/peer-dependencies.md +16 -14
- package/instructions/prompts.md +3 -0
- package/instructions/security.md +4 -0
- package/instructions/testing.md +2 -2
- package/instructions/upgrading-security-release.md +20 -20
- package/migrations/durable-tenant/0000_durable_tenant.sql +92 -0
- package/migrations/durable-tenant/0001_human_task.sql +45 -0
- package/migrations/durable-tenant/meta/_journal.json +20 -0
- package/migrations/spine/0000_opaque_dispatch.sql +26 -0
- package/migrations/spine/meta/_journal.json +13 -0
- package/package.json +27 -10
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/** Thrown when an in-flight idempotency claim is aborted (e.g. the first request failed). */
|
|
2
|
+
export class IdempotencyAbortedError extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super('Idempotency claim aborted');
|
|
5
|
+
this.name = 'IdempotencyAbortedError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
const TTL_MULTIPLIERS = {
|
|
9
|
+
s: 1_000,
|
|
10
|
+
m: 60_000,
|
|
11
|
+
h: 3_600_000,
|
|
12
|
+
d: 86_400_000,
|
|
13
|
+
};
|
|
14
|
+
/** Parse manifest idempotency TTL strings such as `24h` or `30m` into milliseconds. */
|
|
15
|
+
export function parseIdempotencyTtl(ttl) {
|
|
16
|
+
const match = /^(\d+)([smhd])$/.exec(ttl.trim());
|
|
17
|
+
if (match === null) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const value = Number(match[1]);
|
|
21
|
+
const unit = match[2];
|
|
22
|
+
if (unit === undefined) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const multiplier = TTL_MULTIPLIERS[unit];
|
|
26
|
+
if (multiplier === undefined) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return value * multiplier;
|
|
30
|
+
}
|
|
31
|
+
export function buildIdempotencyStoreKey(operationId, principal, idempotencyKey) {
|
|
32
|
+
const tenantPart = principal.tenantId ?? '';
|
|
33
|
+
const userPart = principal.userId ?? '';
|
|
34
|
+
return `${operationId}:${tenantPart}:${userPart}:${idempotencyKey}`;
|
|
35
|
+
}
|
|
36
|
+
export function principalsMatch(a, b) {
|
|
37
|
+
return a.userId === b.userId && a.tenantId === b.tenantId;
|
|
38
|
+
}
|
|
39
|
+
/** True when neither userId nor tenantId is set (anonymous `::` principal). */
|
|
40
|
+
export function isAnonymousIdempotencyPrincipal(principal) {
|
|
41
|
+
return principal.userId === undefined && principal.tenantId === undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* How deep a payload may nest before fingerprinting refuses it.
|
|
45
|
+
*
|
|
46
|
+
* `canonicalize` recurses once per level, and a body that nests tens of thousands of levels
|
|
47
|
+
* (JSON.parse accepts it) took the whole request handler down with a `RangeError` — an
|
|
48
|
+
* unhandled 500 from a tiny payload. No real input comes close to this depth; a payload past
|
|
49
|
+
* it is refused as malformed rather than hashed.
|
|
50
|
+
*/
|
|
51
|
+
export const MAX_PAYLOAD_DEPTH = 64;
|
|
52
|
+
/** Thrown by {@link hashPayload} when a payload nests deeper than {@link MAX_PAYLOAD_DEPTH}. */
|
|
53
|
+
export class PayloadTooDeepError extends Error {
|
|
54
|
+
constructor() {
|
|
55
|
+
super(`Payload nests deeper than ${MAX_PAYLOAD_DEPTH} levels`);
|
|
56
|
+
this.name = 'PayloadTooDeepError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function canonicalize(value, depth) {
|
|
60
|
+
if (value === null || typeof value !== 'object') {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
if (depth >= MAX_PAYLOAD_DEPTH) {
|
|
64
|
+
throw new PayloadTooDeepError();
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
return value.map((item) => canonicalize(item, depth + 1));
|
|
68
|
+
}
|
|
69
|
+
const obj = value;
|
|
70
|
+
const sorted = Object.keys(obj).sort();
|
|
71
|
+
const result = {};
|
|
72
|
+
for (const key of sorted) {
|
|
73
|
+
result[key] = canonicalize(obj[key], depth + 1);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Stable JSON serialization for idempotency payload hashing (sorted object keys).
|
|
79
|
+
*
|
|
80
|
+
* Throws {@link PayloadTooDeepError} for a payload nested past {@link MAX_PAYLOAD_DEPTH}; a
|
|
81
|
+
* route answers that as a `validation` refusal, never as an unhandled error.
|
|
82
|
+
*/
|
|
83
|
+
export function hashPayload(payload) {
|
|
84
|
+
return JSON.stringify(canonicalize(payload, 0));
|
|
85
|
+
}
|
|
86
|
+
function isExpired(record) {
|
|
87
|
+
return record.expiresAt !== undefined && Date.now() > record.expiresAt;
|
|
88
|
+
}
|
|
89
|
+
function replayOrConflict(record, payloadHash, principal) {
|
|
90
|
+
if (!principalsMatch(record.principal, principal)) {
|
|
91
|
+
return { status: 'conflict', reason: 'principal' };
|
|
92
|
+
}
|
|
93
|
+
if (record.payloadHash !== payloadHash) {
|
|
94
|
+
return { status: 'conflict', reason: 'payload' };
|
|
95
|
+
}
|
|
96
|
+
return { status: 'replay', record };
|
|
97
|
+
}
|
|
98
|
+
function createInFlightEntry(payloadHash, principal) {
|
|
99
|
+
let resolve = () => { };
|
|
100
|
+
let reject = () => { };
|
|
101
|
+
const promise = new Promise((res, rej) => {
|
|
102
|
+
resolve = res;
|
|
103
|
+
reject = rej;
|
|
104
|
+
});
|
|
105
|
+
void promise.catch(() => {
|
|
106
|
+
/* absorb early abort before a route handler attaches its own catch */
|
|
107
|
+
});
|
|
108
|
+
return { payloadHash, principal, promise, resolve, reject };
|
|
109
|
+
}
|
|
110
|
+
function pruneExpiredEntries(store) {
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
for (const [key, entry] of store) {
|
|
113
|
+
if ('result' in entry && entry.expiresAt !== undefined && now > entry.expiresAt) {
|
|
114
|
+
store.delete(key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Dev-oriented in-memory idempotency store. Entries without TTL are never evicted;
|
|
120
|
+
* when `ttlMs` is supplied on complete, expired records are ignored on subsequent claims.
|
|
121
|
+
*/
|
|
122
|
+
export function createInMemoryIdempotencyStore() {
|
|
123
|
+
const store = new Map();
|
|
124
|
+
return {
|
|
125
|
+
async claim(key, payloadHash, principal, options) {
|
|
126
|
+
if (options?.ttlMs !== undefined) {
|
|
127
|
+
pruneExpiredEntries(store);
|
|
128
|
+
}
|
|
129
|
+
const existing = store.get(key);
|
|
130
|
+
if (existing === undefined) {
|
|
131
|
+
store.set(key, createInFlightEntry(payloadHash, principal));
|
|
132
|
+
return { status: 'new' };
|
|
133
|
+
}
|
|
134
|
+
if ('result' in existing) {
|
|
135
|
+
if (isExpired(existing)) {
|
|
136
|
+
store.delete(key);
|
|
137
|
+
store.set(key, createInFlightEntry(payloadHash, principal));
|
|
138
|
+
return { status: 'new' };
|
|
139
|
+
}
|
|
140
|
+
return replayOrConflict(existing, payloadHash, principal);
|
|
141
|
+
}
|
|
142
|
+
if (!principalsMatch(existing.principal, principal)) {
|
|
143
|
+
return { status: 'conflict', reason: 'principal' };
|
|
144
|
+
}
|
|
145
|
+
if (existing.payloadHash !== payloadHash) {
|
|
146
|
+
return { status: 'conflict', reason: 'payload' };
|
|
147
|
+
}
|
|
148
|
+
return { status: 'in-flight', wait: existing.promise };
|
|
149
|
+
},
|
|
150
|
+
async complete(key, result, options) {
|
|
151
|
+
const existing = store.get(key);
|
|
152
|
+
if (existing === undefined || 'result' in existing) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const record = {
|
|
156
|
+
payloadHash: existing.payloadHash,
|
|
157
|
+
result,
|
|
158
|
+
principal: existing.principal,
|
|
159
|
+
expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined,
|
|
160
|
+
};
|
|
161
|
+
store.set(key, record);
|
|
162
|
+
existing.resolve(record);
|
|
163
|
+
if (options?.ttlMs !== undefined) {
|
|
164
|
+
pruneExpiredEntries(store);
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
async abort(key) {
|
|
168
|
+
const existing = store.get(key);
|
|
169
|
+
if (existing !== undefined && !('result' in existing)) {
|
|
170
|
+
existing.reject(new IdempotencyAbortedError());
|
|
171
|
+
store.delete(key);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../src/api/idempotency.ts"],"names":[],"mappings":"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"}
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export { isApiExposed } from './exposure.js';
|
|
1
|
+
export { capabilityHttpMethod, isApiExposed } from './exposure.js';
|
|
2
2
|
export { authenticationFailureToHttp, buildAuthenticationRequest, } from './authentication-http.js';
|
|
3
3
|
export { parseCookieHeader } from './cookies.js';
|
|
4
4
|
export { LOCALE_COOKIE_NAME, registerAllRoutes, registerCapabilityRoute, registerStreamingRoute, resolveRequestLocale, } from './route-generator.js';
|
|
5
5
|
export type { DependencyOptions, ResolveRequestLocaleOptions, RouteGeneratorConfig, } from './route-generator.js';
|
|
6
|
+
export { buildIdempotencyStoreKey, createInMemoryIdempotencyStore, hashPayload, IdempotencyAbortedError, isAnonymousIdempotencyPrincipal, MAX_PAYLOAD_DEPTH, parseIdempotencyTtl, PayloadTooDeepError, principalsMatch, } from './idempotency.js';
|
|
7
|
+
export type { IdempotencyClaimResult, IdempotencyPrincipal, IdempotencyRecord, IdempotencyStore, IdempotencyStoreOptions, } from './idempotency.js';
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/api/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,iBAAiB,EACjB,2BAA2B,EAC3B,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,wBAAwB,EACxB,8BAA8B,EAC9B,WAAW,EACX,uBAAuB,EACvB,+BAA+B,EAC/B,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC"}
|
package/dist/api/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// ── API Module ──
|
|
2
2
|
// HTTP route generation: registers Fastify routes for capabilities.
|
|
3
|
-
// Maps capability
|
|
3
|
+
// Maps a capability to its HTTP method: the declared `api.method` where there is one,
|
|
4
|
+
// otherwise the kind's default (query→GET, action/job→POST).
|
|
4
5
|
//
|
|
5
6
|
// Key exports: registerAllRoutes, registerCapabilityRoute, isApiExposed
|
|
6
|
-
export { isApiExposed } from './exposure.js';
|
|
7
|
+
export { capabilityHttpMethod, isApiExposed } from './exposure.js';
|
|
7
8
|
export { authenticationFailureToHttp, buildAuthenticationRequest, } from './authentication-http.js';
|
|
8
9
|
export { parseCookieHeader } from './cookies.js';
|
|
9
10
|
export { LOCALE_COOKIE_NAME, registerAllRoutes, registerCapabilityRoute, registerStreamingRoute, resolveRequestLocale, } from './route-generator.js';
|
|
11
|
+
export { buildIdempotencyStoreKey, createInMemoryIdempotencyStore, hashPayload, IdempotencyAbortedError, isAnonymousIdempotencyPrincipal, MAX_PAYLOAD_DEPTH, parseIdempotencyTtl, PayloadTooDeepError, principalsMatch, } from './idempotency.js';
|
|
10
12
|
//# sourceMappingURL=index.js.map
|
package/dist/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,oEAAoE;AACpE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,oEAAoE;AACpE,sFAAsF;AACtF,6DAA6D;AAC7D,EAAE;AACF,wEAAwE;AAExE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EACL,wBAAwB,EACxB,8BAA8B,EAC9B,WAAW,EACX,uBAAuB,EACvB,+BAA+B,EAC/B,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAChB,MAAM,kBAAkB,CAAC"}
|
|
@@ -3,6 +3,7 @@ import type { FastifyInstance } from 'fastify';
|
|
|
3
3
|
import type { AuthAdapter } from '../auth/adapter.js';
|
|
4
4
|
import type { RequestAuthenticator } from '../auth/http-authentication.js';
|
|
5
5
|
import type { EventQueue } from '../events/queue.js';
|
|
6
|
+
import { type IdempotencyStore } from './idempotency.js';
|
|
6
7
|
import type { ContextDependencies } from '../execution/context-factory.js';
|
|
7
8
|
import type { CapabilityContract } from '../types/capability.js';
|
|
8
9
|
import type { ExecutionContext } from '../types/context.js';
|
|
@@ -12,8 +13,29 @@ export interface DependencyOptions {
|
|
|
12
13
|
/** Resolved locale for this request (from cookie / Accept-Language) */
|
|
13
14
|
locale?: string;
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* A request's dependencies together with the database they were wired against.
|
|
18
|
+
*
|
|
19
|
+
* The generator needs the database as well as the dependencies because two
|
|
20
|
+
* things happen outside the execution context — queue dispatch for `kind: 'job'`
|
|
21
|
+
* capabilities and the `onCapabilityError` hook — and both must address the same
|
|
22
|
+
* database the capability itself read and wrote. Returning them together makes
|
|
23
|
+
* that impossible to get wrong.
|
|
24
|
+
*/
|
|
25
|
+
export interface ResolvedRequestDependencies {
|
|
26
|
+
/** Dependencies for this request. */
|
|
27
|
+
dependencies: ContextDependencies;
|
|
28
|
+
/** The database `dependencies` were wired against. */
|
|
29
|
+
db: PostgresJsDatabase;
|
|
30
|
+
}
|
|
15
31
|
export interface RouteGeneratorConfig {
|
|
16
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Live Drizzle database connection used by the server.
|
|
34
|
+
*
|
|
35
|
+
* This is the database every request uses unless `resolveDependencies` is
|
|
36
|
+
* supplied, in which case it is only the fallback for requests that never
|
|
37
|
+
* reach dependency resolution.
|
|
38
|
+
*/
|
|
17
39
|
db: PostgresJsDatabase;
|
|
18
40
|
/** Auth adapter for extracting identity from requests */
|
|
19
41
|
authAdapter: AuthAdapter;
|
|
@@ -21,8 +43,35 @@ export interface RouteGeneratorConfig {
|
|
|
21
43
|
requestAuthenticator?: RequestAuthenticator;
|
|
22
44
|
/** Factory to build base context dependencies for each request */
|
|
23
45
|
createDependencies: (auth: NonNullable<Awaited<ReturnType<AuthAdapter['authenticate']>>>, options?: DependencyOptions) => ContextDependencies;
|
|
46
|
+
/**
|
|
47
|
+
* Optional async alternative to `createDependencies`, used for every request
|
|
48
|
+
* when present and ignored when absent.
|
|
49
|
+
*
|
|
50
|
+
* It exists for deployments that choose the request's database per request
|
|
51
|
+
* rather than per boot — a tenant's data plane resolved from its auth context.
|
|
52
|
+
* Leaving it unset keeps the synchronous `createDependencies` path exactly as
|
|
53
|
+
* it was, with no additional `await` between authentication and execution.
|
|
54
|
+
*
|
|
55
|
+
* Throwing rejects the request: the thrown value is mapped through the normal
|
|
56
|
+
* error-to-HTTP rules (a `PlumbusError` keeps its code and status, anything
|
|
57
|
+
* else becomes a generic 500), so a resolver that refuses an unknown or absent
|
|
58
|
+
* tenant fails closed instead of falling through to another tenant's data.
|
|
59
|
+
*/
|
|
60
|
+
resolveDependencies?: (auth: NonNullable<Awaited<ReturnType<AuthAdapter['authenticate']>>>, options?: DependencyOptions) => Promise<ResolvedRequestDependencies>;
|
|
24
61
|
/** Optional queue for dispatching async job capabilities */
|
|
25
62
|
jobQueue?: EventQueue;
|
|
63
|
+
/**
|
|
64
|
+
* Where `api.idempotency` claims are kept for the routes this config registers.
|
|
65
|
+
*
|
|
66
|
+
* A capability that declares `api: { idempotency: { required: true, header } }` is served
|
|
67
|
+
* only with that header present; the same key with the same payload replays the stored
|
|
68
|
+
* answer, the same key with a different payload or principal is refused with a conflict,
|
|
69
|
+
* and a failed execution releases the key so the caller may retry. Left unset, every route
|
|
70
|
+
* registered with this config shares one in-memory store — correct within a process and
|
|
71
|
+
* lost on restart; a deployment that needs replay protection across instances supplies a
|
|
72
|
+
* shared store here.
|
|
73
|
+
*/
|
|
74
|
+
idempotencyStore?: IdempotencyStore;
|
|
26
75
|
/** Default locale when request headers do not resolve one */
|
|
27
76
|
defaultLocale?: string;
|
|
28
77
|
/** Locales supported by registered translation definitions */
|
|
@@ -41,11 +90,6 @@ export interface RouteGeneratorConfig {
|
|
|
41
90
|
db?: PostgresJsDatabase;
|
|
42
91
|
}) => void | Promise<void>;
|
|
43
92
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Register Fastify routes for a single capability.
|
|
46
|
-
* HTTP method is derived from capability kind:
|
|
47
|
-
* query → GET, action → POST, job → POST (async), eventHandler → skipped
|
|
48
|
-
*/
|
|
49
93
|
export declare function registerCapabilityRoute(app: FastifyInstance, capability: CapabilityContract, config: RouteGeneratorConfig): void;
|
|
50
94
|
/**
|
|
51
95
|
* Register routes for multiple capabilities at once.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-generator.d.ts","sourceRoot":"","sources":["../../src/api/route-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAE7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"route-generator.d.ts","sourceRoot":"","sources":["../../src/api/route-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,22 +1,37 @@
|
|
|
1
1
|
import { authenticationFailureToHttp, buildAuthenticationRequest } from './authentication-http.js';
|
|
2
|
-
import { errorToHttpResponse, errorToSsePayload, unknownErrorToSsePayload, } from '../errors/http.js';
|
|
2
|
+
import { errorToHttpResponse, errorToSsePayload, GENERIC_INTERNAL_MESSAGE, unknownErrorToSsePayload, } from '../errors/http.js';
|
|
3
|
+
import { capabilityHttpMethod } from './exposure.js';
|
|
4
|
+
import { isPlumbusError } from '../errors/index.js';
|
|
3
5
|
import { logHookError } from '../errors/hook-log.js';
|
|
4
6
|
import { JobExecutionSource } from '../jobs/schema.js';
|
|
5
7
|
import { dispatchQueuedJob } from '../jobs/dispatch.js';
|
|
6
8
|
import { evaluateAccess } from '../execution/authorization.js';
|
|
7
9
|
import { getCanonicalCapabilityName } from '../execution/canonical-name.js';
|
|
8
|
-
import { executeCapability } from '../execution/capability-executor.js';
|
|
10
|
+
import { authorizeCapability, executeCapability } from '../execution/capability-executor.js';
|
|
11
|
+
import { buildIdempotencyStoreKey, createInMemoryIdempotencyStore, hashPayload, PayloadTooDeepError, IdempotencyAbortedError, isAnonymousIdempotencyPrincipal, parseIdempotencyTtl, } from './idempotency.js';
|
|
9
12
|
import { createExecutionContext } from '../execution/context-factory.js';
|
|
10
13
|
/**
|
|
11
14
|
* Register Fastify routes for a single capability.
|
|
12
15
|
* HTTP method is derived from capability kind:
|
|
13
16
|
* query → GET, action → POST, job → POST (async), eventHandler → skipped
|
|
14
17
|
*/
|
|
18
|
+
/** One in-memory store per route configuration, for deployments that supplied none. */
|
|
19
|
+
const defaultIdempotencyStores = new WeakMap();
|
|
20
|
+
function idempotencyStoreOf(config) {
|
|
21
|
+
if (config.idempotencyStore)
|
|
22
|
+
return config.idempotencyStore;
|
|
23
|
+
let store = defaultIdempotencyStores.get(config);
|
|
24
|
+
if (!store) {
|
|
25
|
+
store = createInMemoryIdempotencyStore();
|
|
26
|
+
defaultIdempotencyStores.set(config, store);
|
|
27
|
+
}
|
|
28
|
+
return store;
|
|
29
|
+
}
|
|
15
30
|
export function registerCapabilityRoute(app, capability, config) {
|
|
16
31
|
// Event handlers are internal-only, no HTTP route
|
|
17
32
|
if (capability.kind === 'eventHandler')
|
|
18
33
|
return;
|
|
19
|
-
const method = capability
|
|
34
|
+
const method = capabilityHttpMethod(capability);
|
|
20
35
|
const path = `/api/${capability.domain}/${toKebabCase(capability.name)}`;
|
|
21
36
|
const handler = async (request, reply) => {
|
|
22
37
|
const requestId = resolveRequestCorrelationId(request.headers);
|
|
@@ -78,7 +93,28 @@ export function registerCapabilityRoute(app, capability, config) {
|
|
|
78
93
|
defaultLocale: config.defaultLocale ?? 'en',
|
|
79
94
|
supportedLocales: config.supportedLocales ?? [config.defaultLocale ?? 'en'],
|
|
80
95
|
});
|
|
81
|
-
|
|
96
|
+
// The synchronous factory stays the only path when no resolver is supplied:
|
|
97
|
+
// nothing extra is awaited between authentication and execution.
|
|
98
|
+
let deps;
|
|
99
|
+
let requestDb = config.db;
|
|
100
|
+
if (config.resolveDependencies) {
|
|
101
|
+
try {
|
|
102
|
+
const resolved = await config.resolveDependencies(authContext, {
|
|
103
|
+
bypassTenantScope,
|
|
104
|
+
locale,
|
|
105
|
+
});
|
|
106
|
+
deps = resolved.dependencies;
|
|
107
|
+
requestDb = resolved.db;
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
const failure = dependencyResolutionToHttp(err, requestId);
|
|
111
|
+
reply.status(failure.statusCode).send(failure.body);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
deps = config.createDependencies(authContext, { bypassTenantScope, locale });
|
|
117
|
+
}
|
|
82
118
|
deps.correlationId = resolveRequestCorrelationId(request.headers);
|
|
83
119
|
deps.request = {
|
|
84
120
|
sourceIp: request.ip,
|
|
@@ -105,7 +141,7 @@ export function registerCapabilityRoute(app, capability, config) {
|
|
|
105
141
|
return reply.status(statusCode).send(body);
|
|
106
142
|
}
|
|
107
143
|
const jobId = await dispatchQueuedJob({
|
|
108
|
-
db:
|
|
144
|
+
db: requestDb,
|
|
109
145
|
jobQueue: config.jobQueue,
|
|
110
146
|
capability,
|
|
111
147
|
input: parsed.data,
|
|
@@ -115,6 +151,158 @@ export function registerCapabilityRoute(app, capability, config) {
|
|
|
115
151
|
reply.status(202).send({ data: { jobId, status: 'accepted' } });
|
|
116
152
|
return;
|
|
117
153
|
}
|
|
154
|
+
// 4b. Declared idempotency (`api.idempotency.required`): the key is demanded, and the
|
|
155
|
+
// outcome of one execution is what every later request with that key receives.
|
|
156
|
+
const idempotency = capability.api?.idempotency;
|
|
157
|
+
// A caller the capability refuses is answered by the executor as always (403, audited)
|
|
158
|
+
// before any header is looked at, so authorization never leaks behind a validation answer.
|
|
159
|
+
const idempotencyApplies = idempotency?.required === true &&
|
|
160
|
+
method !== 'GET' &&
|
|
161
|
+
evaluateAccess(capability.access, ctx.auth).allowed;
|
|
162
|
+
if (idempotencyApplies && idempotency) {
|
|
163
|
+
const headerName = (idempotency.header ?? 'Idempotency-Key').toLowerCase();
|
|
164
|
+
const rawKey = request.headers[headerName];
|
|
165
|
+
const idempotencyKey = Array.isArray(rawKey) ? rawKey[0] : rawKey;
|
|
166
|
+
if (typeof idempotencyKey !== 'string' || idempotencyKey.length === 0) {
|
|
167
|
+
const err = ctx.errors.validation(`Missing required header: ${idempotency.header ?? 'Idempotency-Key'}`, { capability: canonicalName, reason: 'idempotency-key-required' });
|
|
168
|
+
const { statusCode, body } = errorToHttpResponse(err);
|
|
169
|
+
reply.status(statusCode).send(body);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Only a request that would execute claims a key: invalid input is answered by the
|
|
173
|
+
// executor as always, and leaves nothing behind for a retry to hit.
|
|
174
|
+
const parsed = capability.input.safeParse(input);
|
|
175
|
+
const principal = {
|
|
176
|
+
userId: ctx.auth.userId,
|
|
177
|
+
tenantId: ctx.auth.tenantId,
|
|
178
|
+
};
|
|
179
|
+
if (parsed.success && !isAnonymousIdempotencyPrincipal(principal)) {
|
|
180
|
+
const store = idempotencyStoreOf(config);
|
|
181
|
+
const operationId = capability.api?.operationId ?? canonicalName;
|
|
182
|
+
const storeKey = buildIdempotencyStoreKey(operationId, principal, idempotencyKey);
|
|
183
|
+
const authorityOf = (auth) => ({
|
|
184
|
+
roles: [...auth.roles].sort(),
|
|
185
|
+
scopes: [...auth.scopes].sort(),
|
|
186
|
+
});
|
|
187
|
+
// A role change can change the shape of a permitted result (e.g. staff vs student).
|
|
188
|
+
// Never reuse that result across different authority, even for the same person/input.
|
|
189
|
+
const authority = hashPayload(authorityOf(ctx.auth));
|
|
190
|
+
let payloadHash;
|
|
191
|
+
try {
|
|
192
|
+
payloadHash = hashPayload({ input: parsed.data, authority });
|
|
193
|
+
}
|
|
194
|
+
catch (hashErr) {
|
|
195
|
+
if (!(hashErr instanceof PayloadTooDeepError))
|
|
196
|
+
throw hashErr;
|
|
197
|
+
// A body nested past the fingerprint's depth cap is malformed input, answered like
|
|
198
|
+
// any other validation failure — not an unhandled RangeError from the hasher.
|
|
199
|
+
const err = ctx.errors.validation('Request body nests too deeply', {
|
|
200
|
+
capability: canonicalName,
|
|
201
|
+
reason: 'payload-too-deep',
|
|
202
|
+
});
|
|
203
|
+
const { statusCode, body } = errorToHttpResponse(err);
|
|
204
|
+
reply.status(statusCode).send(body);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const ttlMs = idempotency.ttl !== undefined ? parseIdempotencyTtl(idempotency.ttl) : undefined;
|
|
208
|
+
const storeOptions = ttlMs !== undefined ? { ttlMs } : undefined;
|
|
209
|
+
// A cached result is still a disclosure. Refresh session/dependency authority, then
|
|
210
|
+
// run the capability's current resource checks without executing its handler again.
|
|
211
|
+
// In particular, the same check runs after an in-flight wait, not only before it.
|
|
212
|
+
const replay = async (result) => {
|
|
213
|
+
try {
|
|
214
|
+
const fresh = config.resolveDependencies
|
|
215
|
+
? (await config.resolveDependencies(authContext, { bypassTenantScope, locale }))
|
|
216
|
+
.dependencies
|
|
217
|
+
: config.createDependencies(authContext, { bypassTenantScope, locale });
|
|
218
|
+
fresh.correlationId = deps.correlationId;
|
|
219
|
+
fresh.request = deps.request;
|
|
220
|
+
const current = createExecutionContext(fresh);
|
|
221
|
+
if (current.auth.userId !== principal.userId ||
|
|
222
|
+
current.auth.tenantId !== principal.tenantId) {
|
|
223
|
+
throw current.errors.forbidden('Idempotency replay principal changed');
|
|
224
|
+
}
|
|
225
|
+
if (hashPayload(authorityOf(current.auth)) !== authority) {
|
|
226
|
+
throw current.errors.forbidden('Idempotency replay authority changed');
|
|
227
|
+
}
|
|
228
|
+
const authorized = await authorizeCapability(capability, current, parsed.data);
|
|
229
|
+
if (!authorized.success) {
|
|
230
|
+
const failure = errorToHttpResponse(authorized.error);
|
|
231
|
+
reply.status(failure.statusCode).send(failure.body);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
reply.status(200).send({ data: result });
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
const failure = dependencyResolutionToHttp(error, requestId);
|
|
238
|
+
reply.status(failure.statusCode).send(failure.body);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
let claim = await store.claim(storeKey, payloadHash, principal, storeOptions);
|
|
242
|
+
for (;;) {
|
|
243
|
+
if (claim.status === 'replay') {
|
|
244
|
+
await replay(claim.record.result);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (claim.status === 'conflict') {
|
|
248
|
+
const err = ctx.errors.conflict(claim.reason === 'principal'
|
|
249
|
+
? 'Idempotency key belongs to a different principal'
|
|
250
|
+
: 'Idempotency key reused with different input', { capability: canonicalName, reason: 'idempotency-conflict', conflict: claim.reason });
|
|
251
|
+
const { statusCode, body } = errorToHttpResponse(err);
|
|
252
|
+
reply.status(statusCode).send(body);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (claim.status === 'in-flight') {
|
|
256
|
+
try {
|
|
257
|
+
const record = await claim.wait;
|
|
258
|
+
await replay(record.result);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
catch (waitErr) {
|
|
262
|
+
if (!(waitErr instanceof IdempotencyAbortedError))
|
|
263
|
+
throw waitErr;
|
|
264
|
+
claim = await store.claim(storeKey, payloadHash, principal, storeOptions);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
let claimed;
|
|
271
|
+
try {
|
|
272
|
+
claimed = await executeCapability(capability, ctx, input);
|
|
273
|
+
}
|
|
274
|
+
catch (execErr) {
|
|
275
|
+
await store.abort(storeKey);
|
|
276
|
+
throw execErr;
|
|
277
|
+
}
|
|
278
|
+
if (claimed.success) {
|
|
279
|
+
await store.complete(storeKey, claimed.data, storeOptions);
|
|
280
|
+
reply.status(200).send({ data: claimed.data });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
await store.abort(storeKey);
|
|
284
|
+
const httpError = errorToHttpResponse(claimed.error);
|
|
285
|
+
reply.status(httpError.statusCode).send(httpError.body);
|
|
286
|
+
if (config.onCapabilityError) {
|
|
287
|
+
const failed = claimed.error;
|
|
288
|
+
void (async () => config.onCapabilityError?.({
|
|
289
|
+
capabilityName: capability.name,
|
|
290
|
+
domain: capability.domain,
|
|
291
|
+
errorCode: failed.code,
|
|
292
|
+
errorMessage: failed.message,
|
|
293
|
+
metadata: failed.metadata,
|
|
294
|
+
userId: ctx.auth.userId,
|
|
295
|
+
tenantId: ctx.auth.tenantId,
|
|
296
|
+
sourceIp: request.ip,
|
|
297
|
+
userAgent: request.headers['user-agent'],
|
|
298
|
+
db: requestDb,
|
|
299
|
+
}))().catch((hookErr) => {
|
|
300
|
+
logHookError('onCapabilityError', hookErr);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
118
306
|
const result = await executeCapability(capability, ctx, input);
|
|
119
307
|
if (result.success) {
|
|
120
308
|
reply.status(200).send({ data: result.data });
|
|
@@ -135,18 +323,33 @@ export function registerCapabilityRoute(app, capability, config) {
|
|
|
135
323
|
tenantId: ctx.auth.tenantId,
|
|
136
324
|
sourceIp: request.ip,
|
|
137
325
|
userAgent: request.headers['user-agent'],
|
|
138
|
-
db:
|
|
326
|
+
db: requestDb,
|
|
139
327
|
}))().catch((hookErr) => {
|
|
140
328
|
logHookError('onCapabilityError', hookErr);
|
|
141
329
|
});
|
|
142
330
|
}
|
|
143
331
|
}
|
|
144
332
|
};
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
333
|
+
/*
|
|
334
|
+
* Every method a capability may declare, not just the two the `kind` fallback produces. A
|
|
335
|
+
* declared `PATCH` that silently registered as `POST` is the same defect as a declared `POST`
|
|
336
|
+
* served as `GET`: the contract says one thing and the wire does another.
|
|
337
|
+
*/
|
|
338
|
+
switch (method) {
|
|
339
|
+
case 'GET':
|
|
340
|
+
app.get(path, handler);
|
|
341
|
+
break;
|
|
342
|
+
case 'PATCH':
|
|
343
|
+
app.patch(path, handler);
|
|
344
|
+
break;
|
|
345
|
+
case 'PUT':
|
|
346
|
+
app.put(path, handler);
|
|
347
|
+
break;
|
|
348
|
+
case 'DELETE':
|
|
349
|
+
app.delete(path, handler);
|
|
350
|
+
break;
|
|
351
|
+
default:
|
|
352
|
+
app.post(path, handler);
|
|
150
353
|
}
|
|
151
354
|
}
|
|
152
355
|
/**
|
|
@@ -226,7 +429,24 @@ export function registerStreamingRoute(app, capability, config, streamHandler) {
|
|
|
226
429
|
defaultLocale: config.defaultLocale ?? 'en',
|
|
227
430
|
supportedLocales: config.supportedLocales ?? [config.defaultLocale ?? 'en'],
|
|
228
431
|
});
|
|
229
|
-
|
|
432
|
+
let deps;
|
|
433
|
+
if (config.resolveDependencies) {
|
|
434
|
+
try {
|
|
435
|
+
const resolved = await config.resolveDependencies(authContext, {
|
|
436
|
+
bypassTenantScope,
|
|
437
|
+
locale,
|
|
438
|
+
});
|
|
439
|
+
deps = resolved.dependencies;
|
|
440
|
+
}
|
|
441
|
+
catch (err) {
|
|
442
|
+
const failure = dependencyResolutionToHttp(err, requestId);
|
|
443
|
+
reply.status(failure.statusCode).send(failure.body);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
deps = config.createDependencies(authContext, { bypassTenantScope, locale });
|
|
449
|
+
}
|
|
230
450
|
deps.correlationId = resolveRequestCorrelationId(request.headers);
|
|
231
451
|
deps.request = {
|
|
232
452
|
sourceIp: request.ip,
|
|
@@ -280,6 +500,28 @@ export function registerStreamingRoute(app, capability, config, streamHandler) {
|
|
|
280
500
|
reply.raw.end();
|
|
281
501
|
});
|
|
282
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Shape a failure from `resolveDependencies` into an HTTP response.
|
|
505
|
+
*
|
|
506
|
+
* A `PlumbusError` keeps its own code and status — a resolver that refuses an
|
|
507
|
+
* unknown tenant with `notFound`, or an untenanted request with `forbidden`,
|
|
508
|
+
* reaches the client as that. Anything else becomes a generic 500 so an
|
|
509
|
+
* infrastructure failure (a connection string, a routing table) is never echoed
|
|
510
|
+
* back to the caller. Streaming routes call this before any SSE header is
|
|
511
|
+
* written, so the refusal is a plain HTTP status there too.
|
|
512
|
+
*/
|
|
513
|
+
function dependencyResolutionToHttp(err, requestId) {
|
|
514
|
+
const mapped = isPlumbusError(err)
|
|
515
|
+
? errorToHttpResponse(err)
|
|
516
|
+
: {
|
|
517
|
+
statusCode: 500,
|
|
518
|
+
body: { error: { code: 'internal', message: GENERIC_INTERNAL_MESSAGE } },
|
|
519
|
+
};
|
|
520
|
+
return {
|
|
521
|
+
statusCode: mapped.statusCode,
|
|
522
|
+
body: { error: { ...mapped.body.error, ...(requestId ? { requestId } : {}) } },
|
|
523
|
+
};
|
|
524
|
+
}
|
|
283
525
|
function resolveRequestCorrelationId(headers) {
|
|
284
526
|
for (const key of ['x-correlation-id', 'x-request-id']) {
|
|
285
527
|
const value = headers[key];
|