@zackbart/connecta 0.34.1 → 0.36.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 +418 -0
- package/README.md +65 -105
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2044 -1161
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +751 -281
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +29 -7
- package/dist/connectors/api-connector.js +145 -57
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/remote-mcp.d.ts +10 -30
- package/dist/connectors/remote-mcp.js +955 -563
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +2 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +227 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +379 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +218 -85
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +531 -406
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/contract.d.ts +8 -3
- package/dist/operator-ui/generated.js +4 -4
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +65 -31
- package/dist/provider.js +116 -39
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +427 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +6 -210
- package/dist/providers/_shared/google/workspace.js +6 -425
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/kit.js +146 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +1 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +15 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +5 -22
- package/dist/providers/cloudflare/index.js +79 -197
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +4 -3
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +801 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1023 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +1 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +4 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/quickbooks/api.js +299 -0
- package/dist/providers/quickbooks/index.d.ts +13 -0
- package/dist/providers/quickbooks/index.js +93 -0
- package/dist/providers/quickbooks/lib/query.js +118 -0
- package/dist/providers/quickbooks/lib/writes.js +168 -0
- package/dist/providers/quickbooks/safety.js +222 -0
- package/dist/providers/quickbooks/skill.generated.js +5 -0
- package/dist/providers/quickbooks/tools.js +117 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +733 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +5 -27
- package/dist/providers/stripe/index.js +74 -210
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -11
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +17 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +9 -25
- package/dist/providers/vercel/index.js +65 -260
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +10 -5
- package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +21 -15
- package/dist/registry.js +409 -376
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +6 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +21 -20
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +552 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +322 -223
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +9 -109
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +0 -1
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +28 -7
- package/documentation/architecture.md +2409 -255
- package/documentation/auth.md +867 -114
- package/documentation/code-mode.md +49 -16
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +129 -144
- package/documentation/meta-tools.md +409 -32
- package/documentation/operating.md +27 -3
- package/documentation/operator-tests.md +3 -0
- package/documentation/operator-ui.md +84 -48
- package/documentation/provider-auth-migration.md +16 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +1347 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +30 -50
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -138
- package/dist/catalog-drift.js +0 -578
- package/dist/operator-ui/agents.d.ts +0 -74
- package/dist/operator-ui/agents.js +0 -104
- package/dist/operator-ui/config-summary.js +0 -565
- package/dist/operator-ui/connection-map.js +0 -376
- package/dist/operator-ui/endpoints.js +0 -221
- package/dist/operator-ui/fix-prompts.d.ts +0 -18
- package/dist/operator-ui/fix-prompts.js +0 -87
- package/dist/operator-ui/health.js +0 -52
- package/dist/operator-ui/member-page.js +0 -97
- package/dist/operator-ui/overview.js +0 -123
- package/dist/operator-ui/setup-commands.js +0 -51
- package/dist/operator-ui/view.d.ts +0 -401
- package/dist/operator-ui/view.js +0 -705
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/dispatch.js +0 -41
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/tools.js +0 -569
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -45
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1097
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/intercom/index.d.ts +0 -24
- package/dist/providers/intercom/index.js +0 -121
- package/dist/providers/intercom/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -537
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -59
- package/dist/providers/mixpanel/index.js +0 -472
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -67
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -51
- package/dist/providers/revenuecat/index.js +0 -697
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
// Operation and call services. Only the Promise view bridge is keyed on a
|
|
2
|
+
// context object; authority lives in Context values, shared by derived views.
|
|
3
|
+
import { Context, Effect } from "effect";
|
|
4
|
+
import { ConnectorCallError } from "../errors.js";
|
|
5
|
+
import { dispatchProviderRequest, } from "../provider-call.js";
|
|
6
|
+
import { SentSecrets, sentSecretsForRequest, registerCredentialValues } from "../sent-secrets.js";
|
|
7
|
+
import { recordRecovery } from "../call-recovery.js";
|
|
8
|
+
import { fromSignal, runEdge } from "./run.js";
|
|
9
|
+
import { RequestScope, requestOwner as requestOf } from "./request-scope.js";
|
|
10
|
+
/** Who admitted the operation; absent for a probe or a callback no request admitted. */
|
|
11
|
+
export class Caller extends Context.Service()("connecta/call/Caller") {
|
|
12
|
+
}
|
|
13
|
+
/** The deployment's root store, used only for its durable continuation-signing key. */
|
|
14
|
+
export const ContinuationStorage = Context.Reference("connecta/call/ContinuationStorage", {
|
|
15
|
+
defaultValue: () => undefined,
|
|
16
|
+
});
|
|
17
|
+
/** Admitted connection and credential owner or per-call identity; never derived from tool arguments. */
|
|
18
|
+
export const ContinuationBinding = Context.Reference("connecta/call/ContinuationBinding", { defaultValue: () => ({ connection: "", principal: null }) });
|
|
19
|
+
/** The credentials this call may send, already registered as sent secrets when read. */
|
|
20
|
+
export class Credentials extends Context.Service()("connecta/call/Credentials") {
|
|
21
|
+
}
|
|
22
|
+
/** Native operation credential access; custom contexts retain their Promise leaf. */
|
|
23
|
+
export const OperationCredentials = Context.Reference("connecta/operation/Credentials", { defaultValue: () => undefined });
|
|
24
|
+
/** The call's sent-secret set; every credential header and query value is registered here. */
|
|
25
|
+
export class Secrets extends Context.Service()("connecta/call/Secrets") {
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Everything the request sent, every connector's credentials included: the
|
|
29
|
+
* set that redacts downstream text whole before anything cuts, trims, or
|
|
30
|
+
* extracts from it (#911's `redactBeforeCut`).
|
|
31
|
+
*/
|
|
32
|
+
export class RequestSecrets extends Context.Service()("connecta/call/RequestSecrets") {
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The enclosing invocation's admission permit, as the one thing a provider
|
|
36
|
+
* may do with it: charge an auxiliary request to the same budget.
|
|
37
|
+
*/
|
|
38
|
+
export class Budget extends Context.Service()("connecta/call/Budget") {
|
|
39
|
+
}
|
|
40
|
+
/** Facts that let an agent reconcile a write whose outcome is unknown. */
|
|
41
|
+
export class Recovery extends Context.Service()("connecta/call/Recovery") {
|
|
42
|
+
}
|
|
43
|
+
/** The call's cancellation, handed to `fetch` as guarded-fetch handed `ctx.signal`. */
|
|
44
|
+
class SourceSignal extends Context.Service()("connecta/call/SourceSignal") {
|
|
45
|
+
}
|
|
46
|
+
export class Signal extends Context.Service()("connecta/call/Signal") {
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The call's served classification: deployment config's verdict when it
|
|
50
|
+
* gives one, else the tool's. The OAuth middleware and the write-outcome rule
|
|
51
|
+
* read it; neither the builder nor the HTTP method decides.
|
|
52
|
+
*/
|
|
53
|
+
export class ServedClassification extends Context.Service()("connecta/call/ServedClassification") {
|
|
54
|
+
}
|
|
55
|
+
export class Classification extends Context.Service()("connecta/call/Classification") {
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The core's request ledger for this call (src/provider-call.ts), which the
|
|
59
|
+
* `api()` boundary reads to decide every write verdict (INV-9). The transport
|
|
60
|
+
* opens an entry for each resource request with its method and any genuine
|
|
61
|
+
* idempotency key it carries, dispatches it synchronously before `fetch`
|
|
62
|
+
* (which refuses a request whose call has ended), and records its status
|
|
63
|
+
* when answered. A dispatch also joins the request to the invocation's
|
|
64
|
+
* logical call, so identities are captured when the request leaves.
|
|
65
|
+
*/
|
|
66
|
+
export class Ledger extends Context.Service()("connecta/call/Ledger") {
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A ledger no `api()` boundary reads: what a lease built without a context
|
|
70
|
+
* (a unit test) records into. It still marks each request dispatched.
|
|
71
|
+
*/
|
|
72
|
+
const detachedLedger = {
|
|
73
|
+
open: ({ auxiliary, provider, method, key }) => ({
|
|
74
|
+
dispatched: false,
|
|
75
|
+
auxiliary,
|
|
76
|
+
provider,
|
|
77
|
+
method,
|
|
78
|
+
...(key === undefined ? {} : { key }),
|
|
79
|
+
}),
|
|
80
|
+
dispatch: (request, headers, method) => dispatchProviderRequest(undefined, request, headers, method),
|
|
81
|
+
};
|
|
82
|
+
export class ProviderLedger extends Context.Service()("connecta/call/ProviderLedger") {
|
|
83
|
+
}
|
|
84
|
+
export class LogicalLedger extends Context.Service()("connecta/call/LogicalLedger") {
|
|
85
|
+
}
|
|
86
|
+
class CallMemo extends Context.Service()("connecta/call/Memo") {
|
|
87
|
+
}
|
|
88
|
+
/** Construction-owned keys keep provider memoization on the call, including derived views. */
|
|
89
|
+
export function callMemo(view, key, create) {
|
|
90
|
+
if (viewClosed(view))
|
|
91
|
+
throw ended();
|
|
92
|
+
let memo = serviceFor(view, CallMemo);
|
|
93
|
+
if (!memo) {
|
|
94
|
+
memo = new Map();
|
|
95
|
+
setViewService(view, CallMemo, memo);
|
|
96
|
+
}
|
|
97
|
+
if (!memo.has(key))
|
|
98
|
+
memo.set(key, create());
|
|
99
|
+
return memo.get(key);
|
|
100
|
+
}
|
|
101
|
+
export class Operation extends Context.Service()("connecta/call/Operation") {
|
|
102
|
+
}
|
|
103
|
+
export class ActiveCall extends Context.Service()("connecta/call/ActiveCall") {
|
|
104
|
+
}
|
|
105
|
+
function ended() {
|
|
106
|
+
return new ConnectorCallError("connector_call_failed", "This call has ended; a capability kept from it can no longer be used.", { retryable: false });
|
|
107
|
+
}
|
|
108
|
+
/** Build one call's services. Invocation calls this in the scoped attempt that owns the permit (B.1). */
|
|
109
|
+
export function openCall(inputs) {
|
|
110
|
+
let open = true;
|
|
111
|
+
const parentSignal = inputs.parent ? Context.get(inputs.parent.services, Signal) : undefined;
|
|
112
|
+
const parents = [inputs.signal, parentSignal, inputs.requestSignal].filter((signal) => signal !== undefined);
|
|
113
|
+
const controller = new AbortController();
|
|
114
|
+
const detachParents = [];
|
|
115
|
+
const failures = [];
|
|
116
|
+
const attempt = (step) => {
|
|
117
|
+
try {
|
|
118
|
+
step();
|
|
119
|
+
}
|
|
120
|
+
catch (failure) {
|
|
121
|
+
failures.push(failure);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
// Revocation never throws, including when reached by a parent's abort
|
|
125
|
+
// listener. Report detach failures at the next explicit close, once every
|
|
126
|
+
// capability and listener is revoked. Abort before touching any facade.
|
|
127
|
+
const revoke = (reason) => {
|
|
128
|
+
if (!open)
|
|
129
|
+
return;
|
|
130
|
+
open = false;
|
|
131
|
+
attempt(() => controller.abort(reason));
|
|
132
|
+
for (const detach of detachParents.splice(0))
|
|
133
|
+
attempt(detach);
|
|
134
|
+
};
|
|
135
|
+
// Forward structurally rather than passing a caller's signal to a native
|
|
136
|
+
// brand check: adapters can supply a signal facade owned by their request.
|
|
137
|
+
for (const signal of parents) {
|
|
138
|
+
if (!open)
|
|
139
|
+
break;
|
|
140
|
+
const abort = () => revoke(signal.reason);
|
|
141
|
+
if (signal.aborted)
|
|
142
|
+
abort();
|
|
143
|
+
else {
|
|
144
|
+
detachParents.push(() => signal.removeEventListener("abort", abort));
|
|
145
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Closure settles the Effect immediately and interrupts its Promise bridge.
|
|
149
|
+
// Inputs receive that cancellation too, so their owned I/O can be aborted.
|
|
150
|
+
const live = (work, fail, closed = ended) => Effect.suspend(() => open && !controller.signal.aborted
|
|
151
|
+
? Effect.raceFirst(Effect.suspend(() => work(controller.signal)).pipe(Effect.mapError(fail), Effect.catchDefect((error) => Effect.fail(fail(error)))), fromSignal(controller.signal).pipe(Effect.mapError(closed))).pipe(Effect.flatMap((value) => open && !controller.signal.aborted ? Effect.succeed(value) : Effect.fail(closed())))
|
|
152
|
+
: Effect.fail(closed()));
|
|
153
|
+
const owner = requestOf(inputs.operation
|
|
154
|
+
? (views.get(inputs.operation)?.request ?? inputs.operation.requestScope ?? inputs.operation)
|
|
155
|
+
: inputs);
|
|
156
|
+
sentSecretsForRequest(owner.identity, inputs.requestSecrets ?? inputs.secrets);
|
|
157
|
+
const services = Context.make(RequestScope, owner).pipe(Context.add(Caller, inputs.caller), Context.add(Credentials, {
|
|
158
|
+
values: live((signal) => (inputs.credentialsEffect ??
|
|
159
|
+
Effect.tryPromise({
|
|
160
|
+
try: (interrupted) => inputs.credentials(AbortSignal.any([signal, interrupted])),
|
|
161
|
+
catch: (error) => error,
|
|
162
|
+
})).pipe(Effect.tap((values) => Effect.sync(() => registerCredentialValues(inputs.secrets, values)))), (error) => open
|
|
163
|
+
? error instanceof ConnectorCallError
|
|
164
|
+
? error
|
|
165
|
+
: new ConnectorCallError("auth_required", "The connector's stored credential could not be read.", {
|
|
166
|
+
retryable: false,
|
|
167
|
+
})
|
|
168
|
+
: ended()),
|
|
169
|
+
}), Context.add(Secrets, inputs.secrets), Context.add(RequestSecrets, inputs.requestSecrets ?? inputs.secrets), Context.add(Budget, {
|
|
170
|
+
charge: live((signal) => inputs.chargeEffect?.(signal) ??
|
|
171
|
+
Effect.tryPromise({
|
|
172
|
+
try: (interrupted) => inputs.charge(AbortSignal.any([signal, interrupted])),
|
|
173
|
+
catch: (error) => error,
|
|
174
|
+
}), (error) => error, inputs.chargeClosed),
|
|
175
|
+
}), Context.add(Recovery, {
|
|
176
|
+
// Late facts are dropped: invocation has already read the record.
|
|
177
|
+
record: (facts) => Effect.sync(() => (open && !controller.signal.aborted ? inputs.record(facts) : undefined)),
|
|
178
|
+
recordNow: (facts) => {
|
|
179
|
+
if (open && !controller.signal.aborted)
|
|
180
|
+
inputs.record(facts);
|
|
181
|
+
},
|
|
182
|
+
}), Context.add(Signal, controller.signal), Context.add(SourceSignal, {
|
|
183
|
+
signal: inputs.operation ? callerSignal(inputs.operation) : inputs.signal,
|
|
184
|
+
requestSignal: Object.hasOwn(inputs, "requestSourceSignal")
|
|
185
|
+
? inputs.requestSourceSignal
|
|
186
|
+
: inputs.operation
|
|
187
|
+
? requestCallerSignal(inputs.operation)
|
|
188
|
+
: inputs.signal,
|
|
189
|
+
}), Context.add(Classification, inputs.classification), Context.add(ServedClassification, inputs.served), Context.add(Ledger, {
|
|
190
|
+
open: (facts) => {
|
|
191
|
+
if (!open || controller.signal.aborted)
|
|
192
|
+
throw ended();
|
|
193
|
+
return (inputs.ledger ?? detachedLedger).open(facts);
|
|
194
|
+
},
|
|
195
|
+
dispatch: (request, headers, method) => {
|
|
196
|
+
if (!open || controller.signal.aborted)
|
|
197
|
+
throw ended();
|
|
198
|
+
(inputs.ledger ?? detachedLedger).dispatch(request, headers, method);
|
|
199
|
+
},
|
|
200
|
+
}), Context.add(CallMemo, new Map()), Context.add(ProviderLedger, inputs.providerLedger), Context.add(LogicalLedger, inputs.operation ? serviceFor(inputs.operation, LogicalLedger) : undefined));
|
|
201
|
+
const lease = {
|
|
202
|
+
get services() {
|
|
203
|
+
return Context.add(Context.add(services, ActiveCall, lease), Operation, inputs.operation);
|
|
204
|
+
},
|
|
205
|
+
closed: () => !open || controller.signal.aborted,
|
|
206
|
+
close: () => {
|
|
207
|
+
revoke(ended());
|
|
208
|
+
if (failures.length)
|
|
209
|
+
throw new AggregateError(failures.splice(0), "Closing the call: signal cleanup failed.");
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
return lease;
|
|
213
|
+
}
|
|
214
|
+
const views = new WeakMap();
|
|
215
|
+
function entryOf(view) {
|
|
216
|
+
const entry = views.get(view);
|
|
217
|
+
return entry && requestOf(entry.request).open ? entry : undefined;
|
|
218
|
+
}
|
|
219
|
+
export function serviceFor(view, key) {
|
|
220
|
+
const entry = entryOf(view);
|
|
221
|
+
return entry ? Context.getOrUndefined(entry.services, key) : undefined;
|
|
222
|
+
}
|
|
223
|
+
/** Settlement retains facts after revocation; it never acquires a live capability. */
|
|
224
|
+
export function recordedService(view, key) {
|
|
225
|
+
const entry = views.get(view);
|
|
226
|
+
return entry ? Context.getOrUndefined(entry.services, key) : undefined;
|
|
227
|
+
}
|
|
228
|
+
export function viewClosed(view) {
|
|
229
|
+
const entry = views.get(view);
|
|
230
|
+
return entry !== undefined && (!requestOf(entry.request).open || entry.call?.closed() === true);
|
|
231
|
+
}
|
|
232
|
+
/** Request revocation is independent of call authority, including on an unregistered copy. */
|
|
233
|
+
export function requestClosed(view) {
|
|
234
|
+
const scope = views.get(view)?.request ?? view.requestScope ?? view;
|
|
235
|
+
return !requestOf(scope).open;
|
|
236
|
+
}
|
|
237
|
+
export function setViewService(view, key, value) {
|
|
238
|
+
const entry = ensureOperation(view);
|
|
239
|
+
if (requestOf(entry.request).open)
|
|
240
|
+
entry.services = Context.add(entry.services, key, value);
|
|
241
|
+
}
|
|
242
|
+
function ensureOperation(base, explicitOperation = false) {
|
|
243
|
+
const existing = views.get(base);
|
|
244
|
+
if (existing) {
|
|
245
|
+
// Custom contexts can acquire an explicit requestScope after their caller
|
|
246
|
+
// was attached. Bind that initial scope before deriving any call view;
|
|
247
|
+
// call views and contexts already bound to an explicit scope never move.
|
|
248
|
+
if (!existing.call &&
|
|
249
|
+
!existing.scopeBound &&
|
|
250
|
+
existing.request === base &&
|
|
251
|
+
requestOf(existing.request).open &&
|
|
252
|
+
base.requestScope &&
|
|
253
|
+
base.requestScope !== base) {
|
|
254
|
+
requestOf(existing.request).entries.delete(existing);
|
|
255
|
+
existing.request = base.requestScope;
|
|
256
|
+
const all = sentSecretsForRequest(existing.request);
|
|
257
|
+
all.include(Context.get(existing.services, Secrets));
|
|
258
|
+
existing.services = Context.add(Context.add(existing.services, RequestSecrets, all), RequestScope, requestOf(existing.request));
|
|
259
|
+
requestOf(existing.request).entries.add(existing);
|
|
260
|
+
}
|
|
261
|
+
return existing;
|
|
262
|
+
}
|
|
263
|
+
// A copied view has no authority. Request membership is evidence only that
|
|
264
|
+
// authority is required, never a way to recover the original call's state.
|
|
265
|
+
// Registry operations and anonymous probes are constructed explicitly.
|
|
266
|
+
if (!explicitOperation && callScopeKnown(base))
|
|
267
|
+
throw ended();
|
|
268
|
+
const request = base.requestScope ?? base;
|
|
269
|
+
const secrets = new SentSecrets();
|
|
270
|
+
const all = sentSecretsForRequest(request);
|
|
271
|
+
all.include(secrets);
|
|
272
|
+
const entry = {
|
|
273
|
+
services: Context.make(Caller, undefined).pipe(Context.add(Secrets, secrets), Context.add(RequestSecrets, all), Context.add(RequestScope, requestOf(request))),
|
|
274
|
+
request,
|
|
275
|
+
base,
|
|
276
|
+
};
|
|
277
|
+
views.set(base, entry);
|
|
278
|
+
requestOf(request).entries.add(entry);
|
|
279
|
+
return entry;
|
|
280
|
+
}
|
|
281
|
+
export function operationView(base, caller) {
|
|
282
|
+
const view = { ...base, requestScope: base.requestScope ?? {} };
|
|
283
|
+
const entry = ensureOperation(view, true);
|
|
284
|
+
if (requestOf(entry.request).open)
|
|
285
|
+
entry.services = Context.add(entry.services, Caller, caller);
|
|
286
|
+
return view;
|
|
287
|
+
}
|
|
288
|
+
export function deriveView(view, overrides) {
|
|
289
|
+
const entry = ensureOperation(view);
|
|
290
|
+
entry.scopeBound = true;
|
|
291
|
+
const derived = { ...view, ...overrides, requestScope: entry.request };
|
|
292
|
+
views.set(derived, entry);
|
|
293
|
+
return derived;
|
|
294
|
+
}
|
|
295
|
+
export function viewLease(view) {
|
|
296
|
+
return views.get(view)?.call;
|
|
297
|
+
}
|
|
298
|
+
export function viewServices(view) {
|
|
299
|
+
return ensureOperation(view).services;
|
|
300
|
+
}
|
|
301
|
+
export function contextView(lease, operation) {
|
|
302
|
+
const origin = ensureOperation(operation);
|
|
303
|
+
origin.scopeBound = true;
|
|
304
|
+
const values = Context.get(lease.services, Credentials).values;
|
|
305
|
+
const signal = Context.get(lease.services, Signal);
|
|
306
|
+
const view = {
|
|
307
|
+
...operation,
|
|
308
|
+
requestScope: origin.request,
|
|
309
|
+
...(signal ? { signal } : {}),
|
|
310
|
+
...(operation.credential
|
|
311
|
+
? {
|
|
312
|
+
credential: {
|
|
313
|
+
getAll: () => settleOnClose(view, () => runEdge(values)),
|
|
314
|
+
get: (field = "value") => settleOnClose(view, async () => {
|
|
315
|
+
const all = await runEdge(values);
|
|
316
|
+
return all && Object.hasOwn(all, field) ? all[field] : null;
|
|
317
|
+
}),
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
: {}),
|
|
321
|
+
};
|
|
322
|
+
const entry = {
|
|
323
|
+
services: Context.add(Context.merge(origin.services, lease.services), Caller, serviceFor(operation, Caller)),
|
|
324
|
+
request: origin.request,
|
|
325
|
+
base: origin.base,
|
|
326
|
+
call: lease,
|
|
327
|
+
credentialWrapped: true,
|
|
328
|
+
};
|
|
329
|
+
views.set(view, entry);
|
|
330
|
+
const request = requestOf(origin.request);
|
|
331
|
+
request.entries.add(entry);
|
|
332
|
+
request.calls.add(lease);
|
|
333
|
+
if (!request.open)
|
|
334
|
+
lease.close();
|
|
335
|
+
return view;
|
|
336
|
+
}
|
|
337
|
+
export function chargeView(view) {
|
|
338
|
+
const budget = recordedService(view, Budget);
|
|
339
|
+
return budget ? runEdge(budget.charge) : Promise.reject(ended());
|
|
340
|
+
}
|
|
341
|
+
/** Admission is sticky until request close; ending a call never makes its copies standalone. */
|
|
342
|
+
export function callScopeKnown(view) {
|
|
343
|
+
const scope = requestOf(views.get(view)?.request ?? view.requestScope ?? view);
|
|
344
|
+
return scope !== undefined && (!scope.open || scope.admitted);
|
|
345
|
+
}
|
|
346
|
+
export function viewSecrets(view) {
|
|
347
|
+
const entry = ensureOperation(view);
|
|
348
|
+
return Context.get(entry.services, Secrets);
|
|
349
|
+
}
|
|
350
|
+
/** Register operation credentials once; call credentials are supplied by the lease. */
|
|
351
|
+
export function wrapViewCredentials(view) {
|
|
352
|
+
const entry = ensureOperation(view);
|
|
353
|
+
if (!view.credential || entry.credentialWrapped)
|
|
354
|
+
return;
|
|
355
|
+
entry.credentialWrapped = true;
|
|
356
|
+
const access = view.credential;
|
|
357
|
+
view.credential = {
|
|
358
|
+
get: (field) => settleOnClose(view, async () => {
|
|
359
|
+
const value = await access.get(field);
|
|
360
|
+
registerCredentialValues(viewSecrets(view), value === null ? null : { [field ?? "value"]: value });
|
|
361
|
+
return value;
|
|
362
|
+
}, true),
|
|
363
|
+
getAll: () => settleOnClose(view, async () => {
|
|
364
|
+
const values = await access.getAll();
|
|
365
|
+
registerCredentialValues(viewSecrets(view), values);
|
|
366
|
+
return values;
|
|
367
|
+
}, true),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
/** Credentials for request-owned transports use the operation, never a closed call. */
|
|
371
|
+
export function operationContext(view) {
|
|
372
|
+
return views.get(view)?.base ?? view;
|
|
373
|
+
}
|
|
374
|
+
export function openRequest(scope) {
|
|
375
|
+
const state = requestOf(scope);
|
|
376
|
+
return { signal: state.controller.signal, own: (finalizer) => state.own(finalizer), close: () => state.close() };
|
|
377
|
+
}
|
|
378
|
+
/** Open a child lease from operation services, never read security side tables. */
|
|
379
|
+
export function callFromContext(ctx, classification, overrides = {}) {
|
|
380
|
+
const { admitted, anonymous, ...callOverrides } = overrides;
|
|
381
|
+
const entry = ensureOperation(ctx);
|
|
382
|
+
const scope = requestOf(entry.request);
|
|
383
|
+
// Direct api() calls on an unadmitted scope and explicit anonymous probes
|
|
384
|
+
// have no served invocation to charge. Once a permit has existed, however,
|
|
385
|
+
// no missing Budget may become a standalone call, even if an anonymous
|
|
386
|
+
// entry was registered before admission or the admitted call already ended.
|
|
387
|
+
if (scope.admitted && !recordedService(ctx, Budget) && !admitted && !anonymous)
|
|
388
|
+
throw ended();
|
|
389
|
+
if (admitted)
|
|
390
|
+
scope.admitted = true;
|
|
391
|
+
if (Object.hasOwn(callOverrides, "requestSourceSignal")) {
|
|
392
|
+
// The operation's credentials also serve request-owned transports. Keep
|
|
393
|
+
// their request source separate from the attempt's expiring deadline.
|
|
394
|
+
entry.services = Context.add(entry.services, SourceSignal, {
|
|
395
|
+
signal: callerSignal(ctx),
|
|
396
|
+
requestSignal: callOverrides.requestSourceSignal,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
const request = openRequest(entry.request);
|
|
400
|
+
const credential = ctx.credential;
|
|
401
|
+
const secrets = new SentSecrets();
|
|
402
|
+
secrets.include(Context.get(entry.services, Secrets));
|
|
403
|
+
Context.get(entry.services, RequestSecrets).include(secrets);
|
|
404
|
+
const lease = openCall({
|
|
405
|
+
caller: serviceFor(ctx, Caller),
|
|
406
|
+
classification,
|
|
407
|
+
served: serviceFor(ctx, ServedClassification),
|
|
408
|
+
credentials: async () => (credential ? credential.getAll() : null),
|
|
409
|
+
credentialsEffect: Object.hasOwn(callOverrides, "credentials")
|
|
410
|
+
? undefined
|
|
411
|
+
: entry.call
|
|
412
|
+
? recordedService(ctx, Credentials)?.values
|
|
413
|
+
: serviceFor(ctx, OperationCredentials),
|
|
414
|
+
charge: () => (serviceFor(ctx, Budget) ? chargeView(ctx) : Promise.resolve()),
|
|
415
|
+
chargeEffect: Object.hasOwn(callOverrides, "charge")
|
|
416
|
+
? undefined
|
|
417
|
+
: () => serviceFor(ctx, Budget)?.charge ?? Effect.void,
|
|
418
|
+
record: (facts) => recordRecovery(ctx, facts),
|
|
419
|
+
secrets,
|
|
420
|
+
requestSecrets: serviceFor(ctx, RequestSecrets),
|
|
421
|
+
signal: ctx.signal,
|
|
422
|
+
requestSignal: request.signal,
|
|
423
|
+
...(entry.call ? { parent: entry.call } : {}),
|
|
424
|
+
operation: ctx,
|
|
425
|
+
...callOverrides,
|
|
426
|
+
});
|
|
427
|
+
requestOf(entry.request).calls.add(lease);
|
|
428
|
+
if (!requestOf(entry.request).open)
|
|
429
|
+
lease.close();
|
|
430
|
+
return lease;
|
|
431
|
+
}
|
|
432
|
+
/** Cancellation is request-owned for operations and auxiliaries, call-owned otherwise. */
|
|
433
|
+
export function viewSignal(ctx, requestOwned = false) {
|
|
434
|
+
const state = requestOf(views.get(ctx)?.request ?? ctx.requestScope ?? ctx);
|
|
435
|
+
const request = state.controller.signal;
|
|
436
|
+
// The original caller signal is distinct from the revocable call signal:
|
|
437
|
+
// ending a call must not stop its request's session or auxiliary work.
|
|
438
|
+
const source = requestOwned ? requestCallerSignal(ctx) : ctx.signal;
|
|
439
|
+
const leaseSignal = requestOwned ? undefined : recordedService(ctx, Signal);
|
|
440
|
+
// A derived transport signal can add cancellation, never replace the lease
|
|
441
|
+
// that owns this wait. Both inputs here are native, even for source facades.
|
|
442
|
+
const owned = (signal) => leaseSignal && leaseSignal !== source ? AbortSignal.any([signal, leaseSignal]) : signal;
|
|
443
|
+
if (!source || source === request)
|
|
444
|
+
return owned(request);
|
|
445
|
+
let binding = state.signals.get(source);
|
|
446
|
+
if (!binding) {
|
|
447
|
+
const controller = new AbortController();
|
|
448
|
+
const failures = [];
|
|
449
|
+
let attached = false;
|
|
450
|
+
const detach = () => {
|
|
451
|
+
if (!attached)
|
|
452
|
+
return;
|
|
453
|
+
attached = false;
|
|
454
|
+
for (const remove of [
|
|
455
|
+
() => source.removeEventListener("abort", fromSource),
|
|
456
|
+
() => request.removeEventListener("abort", fromRequest),
|
|
457
|
+
]) {
|
|
458
|
+
try {
|
|
459
|
+
remove();
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
failures.push(error);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const revoke = (reason) => {
|
|
467
|
+
controller.abort(reason);
|
|
468
|
+
detach();
|
|
469
|
+
};
|
|
470
|
+
const fromSource = () => revoke(source.reason);
|
|
471
|
+
const fromRequest = () => revoke(request.reason);
|
|
472
|
+
// Read structurally, as openCall does: a request-owned facade need not
|
|
473
|
+
// pass AbortSignal.any's native brand check. Never share it across scopes.
|
|
474
|
+
const refresh = () => {
|
|
475
|
+
if (controller.signal.aborted)
|
|
476
|
+
return;
|
|
477
|
+
if (request.aborted)
|
|
478
|
+
fromRequest();
|
|
479
|
+
else if (source.aborted)
|
|
480
|
+
fromSource();
|
|
481
|
+
};
|
|
482
|
+
refresh();
|
|
483
|
+
if (!controller.signal.aborted) {
|
|
484
|
+
attached = true;
|
|
485
|
+
source.addEventListener("abort", fromSource, { once: true });
|
|
486
|
+
if (!controller.signal.aborted)
|
|
487
|
+
request.addEventListener("abort", fromRequest, { once: true });
|
|
488
|
+
state.finalizers.push(() => {
|
|
489
|
+
detach();
|
|
490
|
+
if (failures.length)
|
|
491
|
+
throw new AggregateError(failures.splice(0), "Closing the operation signal failed.");
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
binding = { signal: controller.signal, refresh };
|
|
495
|
+
state.signals.set(source, binding);
|
|
496
|
+
}
|
|
497
|
+
// Some adapters expose cancellation through a live getter without an abort
|
|
498
|
+
// event. Poll it at each boundary, including after an awaited operation.
|
|
499
|
+
binding.refresh();
|
|
500
|
+
return owned(binding.signal);
|
|
501
|
+
}
|
|
502
|
+
/** Compose native work under the same revocation guards as the Promise view. */
|
|
503
|
+
export function duringViewEffect(ctx, work, requestOwned = false) {
|
|
504
|
+
return Effect.suspend(() => {
|
|
505
|
+
const lease = views.get(ctx)?.call;
|
|
506
|
+
if (!requestOwned && lease?.closed())
|
|
507
|
+
return Effect.fail(Context.get(lease.services, Signal)?.reason ?? ended());
|
|
508
|
+
const signal = viewSignal(ctx, requestOwned);
|
|
509
|
+
if (signal.aborted)
|
|
510
|
+
return Effect.fail(signal.reason);
|
|
511
|
+
return Effect.raceFirst(Effect.suspend(() => work(signal)), fromSignal(signal)).pipe(Effect.flatMap((value) => {
|
|
512
|
+
const current = viewSignal(ctx, requestOwned);
|
|
513
|
+
return current.aborted ? Effect.fail(current.reason) : Effect.succeed(value);
|
|
514
|
+
}));
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Settle an entire Promise boundary when its owner closes, regardless of the
|
|
519
|
+
* internal seam currently awaiting. Interruption observes late rejections;
|
|
520
|
+
* detached work still has to pass the transport and lease guards before any
|
|
521
|
+
* dispatch or call-owned commit. Refresh coordinator ownership is independent
|
|
522
|
+
* of this caller's wait once its claim has been submitted.
|
|
523
|
+
*/
|
|
524
|
+
export async function settleOnClose(ctx, work, requestOwned = false) {
|
|
525
|
+
const lease = views.get(ctx)?.call;
|
|
526
|
+
if (!requestOwned && lease?.closed())
|
|
527
|
+
throw Context.get(lease.services, Signal)?.reason ?? ended();
|
|
528
|
+
const signal = viewSignal(ctx, requestOwned);
|
|
529
|
+
if (signal.aborted)
|
|
530
|
+
throw signal.reason;
|
|
531
|
+
// Enter synchronously after the guard, as the original Promise method did:
|
|
532
|
+
// shared-session waiter accounting and claim ownership precede cancellation.
|
|
533
|
+
const pending = work(signal);
|
|
534
|
+
void pending.catch(() => { });
|
|
535
|
+
const value = await runEdge(Effect.tryPromise({ try: () => pending, catch: (error) => error }), { signal });
|
|
536
|
+
const current = viewSignal(ctx, requestOwned);
|
|
537
|
+
if (current.aborted)
|
|
538
|
+
throw current.reason;
|
|
539
|
+
return value;
|
|
540
|
+
}
|
|
541
|
+
/** Also pass the resolved owner to I/O that can release resources on abort. */
|
|
542
|
+
export function duringViewCall(ctx, work, requestOwned = false) {
|
|
543
|
+
return settleOnClose(ctx, work, requestOwned);
|
|
544
|
+
}
|
|
545
|
+
export function callerSignal(view) {
|
|
546
|
+
const source = recordedService(view, SourceSignal);
|
|
547
|
+
return source ? source.signal : view.signal;
|
|
548
|
+
}
|
|
549
|
+
function requestCallerSignal(view) {
|
|
550
|
+
const source = recordedService(view, SourceSignal);
|
|
551
|
+
return source ? source.requestSignal : view.signal;
|
|
552
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// Effect caller that owns a Scope registers the close as that scope's
|
|
5
5
|
// finalizer instead, so teardown runs however the work inside it ends.
|
|
6
6
|
import { Duration, Effect } from "effect";
|
|
7
|
+
import { openRequest } from "./call.js";
|
|
7
8
|
import { detach } from "./run.js";
|
|
8
9
|
/** Enough for local transport abort/close without letting cleanup own latency. */
|
|
9
10
|
const CONNECTOR_SCOPE_CLOSE_BUDGET_MS = 100;
|
|
@@ -33,6 +34,12 @@ function waitAtMost(work, budgetMs) {
|
|
|
33
34
|
*/
|
|
34
35
|
export function closeScope(connector, ctx, defer) {
|
|
35
36
|
return Effect.suspend(() => {
|
|
37
|
+
try {
|
|
38
|
+
openRequest(ctx.requestScope ?? ctx).close();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* Every cleanup step already ran. */
|
|
42
|
+
}
|
|
36
43
|
let settled;
|
|
37
44
|
try {
|
|
38
45
|
const closing = connector.closeScope?.(ctx);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Candidate credential tests are anonymous read calls, never context copies
|
|
2
|
+
// that borrow an admitted caller, budget, or served write classification.
|
|
3
|
+
import { callFromContext, contextView, operationView, viewLease } from "./call.js";
|
|
4
|
+
function credentialProbeContext(ctx, values) {
|
|
5
|
+
const operation = operationView({
|
|
6
|
+
storage: ctx.storage,
|
|
7
|
+
logger: ctx.logger,
|
|
8
|
+
baseUrl: ctx.baseUrl,
|
|
9
|
+
...(ctx.publicUrl !== undefined ? { publicUrl: ctx.publicUrl } : {}),
|
|
10
|
+
...(ctx.oauthClientName !== undefined ? { oauthClientName: ctx.oauthClientName } : {}),
|
|
11
|
+
...(ctx.requestScope ? { requestScope: ctx.requestScope } : {}),
|
|
12
|
+
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
13
|
+
...(ctx.timeoutMs !== undefined ? { timeoutMs: ctx.timeoutMs } : {}),
|
|
14
|
+
...(ctx.defer ? { defer: ctx.defer } : {}),
|
|
15
|
+
credential: { getAll: async () => values, get: async (field = "value") => values[field] ?? null },
|
|
16
|
+
}, undefined);
|
|
17
|
+
const parent = viewLease(ctx);
|
|
18
|
+
const lease = callFromContext(operation, "read", {
|
|
19
|
+
anonymous: true,
|
|
20
|
+
charge: async () => { },
|
|
21
|
+
record: () => { },
|
|
22
|
+
...(parent ? { parent } : {}),
|
|
23
|
+
});
|
|
24
|
+
return contextView(lease, operation);
|
|
25
|
+
}
|
|
26
|
+
export async function credentialProbe(ctx, values, work) {
|
|
27
|
+
const view = credentialProbeContext(ctx, values);
|
|
28
|
+
try {
|
|
29
|
+
return await work(view);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
viewLease(view).close();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Private execution dispatch. Public shells call their own program directly;
|
|
2
|
+
// only consumers select by method identity, so wrappers may call saved shells.
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
const programs = new WeakMap();
|
|
5
|
+
export function provideExecutorProgram(target, run, logs) {
|
|
6
|
+
programs.set(target, { method: target.execute, run, ...(logs ? { logs } : {}) });
|
|
7
|
+
}
|
|
8
|
+
/** Queued ownership crosses a Promise so workerd resumes in the waiting request. */
|
|
9
|
+
export function awaitExecutorLease(acquire) {
|
|
10
|
+
return Effect.suspend(() => {
|
|
11
|
+
let pending;
|
|
12
|
+
return Effect.tryPromise({ try: (signal) => (pending = acquire(signal)), catch: (error) => error }).pipe(Effect.onInterrupt(() => Effect.sync(() => {
|
|
13
|
+
pending
|
|
14
|
+
?.then((lease) => lease.release(), () => { })
|
|
15
|
+
.catch(() => { });
|
|
16
|
+
})));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function executorProgram(target, code, providers, observe) {
|
|
20
|
+
return Effect.suspend(() => {
|
|
21
|
+
const native = programs.get(target);
|
|
22
|
+
return native?.method === target.execute
|
|
23
|
+
? native.run(code, providers).pipe(Effect.tap((outcome) => Effect.sync(() => observe?.result(outcome))), Effect.tapError((error) => Effect.sync(() => observe?.error(error))), Effect.onInterrupt(() => Effect.sync(() => {
|
|
24
|
+
if (native.logs)
|
|
25
|
+
observe?.interrupted(native.logs());
|
|
26
|
+
})))
|
|
27
|
+
: Effect.tryPromise({
|
|
28
|
+
// Observe a real Promise leaf even if its waiting fiber has lost a
|
|
29
|
+
// terminal race. Its synchronous log prefix still belongs to the run.
|
|
30
|
+
try: () => target.execute(code, providers).then((outcome) => {
|
|
31
|
+
observe?.result(outcome);
|
|
32
|
+
return outcome;
|
|
33
|
+
}, (error) => {
|
|
34
|
+
observe?.error(error);
|
|
35
|
+
throw error;
|
|
36
|
+
}),
|
|
37
|
+
catch: (error) => error,
|
|
38
|
+
});
|
|
39
|
+
}).pipe(Effect.catchDefect((error) => Effect.fail(error)));
|
|
40
|
+
}
|