@zackbart/connecta 0.34.1 → 0.35.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 +363 -0
- package/README.md +62 -106
- 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 +2051 -1160
- 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 +663 -271
- 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 +12 -7
- package/dist/connectors/api-connector.js +142 -56
- 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 +228 -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 +383 -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/agents.d.ts +5 -4
- package/dist/operator-ui/agents.js +17 -7
- package/dist/operator-ui/config-summary.js +5 -4
- package/dist/operator-ui/connection-map.js +7 -5
- package/dist/operator-ui/contract.d.ts +7 -3
- package/dist/operator-ui/endpoints.js +4 -13
- package/dist/operator-ui/fix-prompts.d.ts +1 -1
- package/dist/operator-ui/fix-prompts.js +0 -8
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/operator-ui/overview.js +5 -2
- package/dist/operator-ui/setup-commands.js +0 -4
- package/dist/operator-ui/view.d.ts +2 -25
- package/dist/operator-ui/view.js +2 -76
- 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 +537 -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/dispatch.js +3 -23
- package/dist/providers/_shared/rest/kit.js +546 -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/tools.js +101 -78
- 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 +802 -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 +1024 -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/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 +734 -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 +66 -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 +3 -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 +556 -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 -222
- 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 +7 -109
- 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 +27 -7
- package/documentation/architecture.md +2547 -226
- package/documentation/auth.md +821 -98
- 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 +68 -45
- 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 +965 -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 +27 -46
- 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/providers/_shared/request-token.js +0 -83
- 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/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,439 @@
|
|
|
1
|
+
import { confinementBase, REDIRECT_STATUSES, BODILESS_STATUSES } from "../http-policy.js";
|
|
2
|
+
import { guardedTransport, sendGuarded } from "../guarded-transport.js";
|
|
3
|
+
// Guarded HTTP transport (decision 0010, B.2). Unreachable from src/index.ts.
|
|
4
|
+
//
|
|
5
|
+
// The guarded transport as an Effect `HttpClient`, with the behaviour of
|
|
6
|
+
// src/connectors/guarded-fetch.ts: requests confined beneath one base URL,
|
|
7
|
+
// credentials applied last and registered as sent secrets, redirects refused,
|
|
8
|
+
// bodies read under a byte ceiling, and network failures normalized. It is
|
|
9
|
+
// built with `HttpClient.makeWith`, not `HttpClient.make`, so it carries no
|
|
10
|
+
// span, no `traceparent`/`b3` header, and no response registry: what leaves
|
|
11
|
+
// connecta is exactly what this file assembles. It is also the one place a
|
|
12
|
+
// request reaches `fetch`, so it keeps the call's dispatch ledger and refuses
|
|
13
|
+
// a second dispatch of any logical write, whatever above it asked for one.
|
|
14
|
+
// Each request enters the core's ledger (src/provider-call.ts) synchronously
|
|
15
|
+
// before `fetch`, with its method, any idempotency key it carries, and its
|
|
16
|
+
// status once answered: the facts `api()`'s boundary decides a write from.
|
|
17
|
+
//
|
|
18
|
+
// effect/http is annotated unstable in 4.0.5. Only client.ts, oauth.ts and
|
|
19
|
+
// vendor.ts import the HTTP area modules; providers never do.
|
|
20
|
+
import { Context, Effect, Stream } from "effect";
|
|
21
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
22
|
+
import * as HttpClientRequest from "effect/http/HttpClientRequest";
|
|
23
|
+
import * as HttpClientResponse from "effect/http/HttpClientResponse";
|
|
24
|
+
import * as Url from "effect/http/Url";
|
|
25
|
+
import { ConnectorCallError } from "../errors.js";
|
|
26
|
+
import { readRequest, recordProviderResponse } from "../provider-call.js";
|
|
27
|
+
import { recordRequestField } from "../provider-call-facts.js";
|
|
28
|
+
import { fromSignal } from "../runtime/run.js";
|
|
29
|
+
import { RedirectRefused, Refusal, RequestRefused, ResendRefused, ResponseTooLarge, TransportFailure, } from "./errors.js";
|
|
30
|
+
import { Classification, Dispatches, IdempotencyKey, Ledger, RequestFacts, Secrets, Signal, WriteCapacity, reserveWrite, writeRequestKey, } from "./services.js";
|
|
31
|
+
const decoder = new TextDecoder();
|
|
32
|
+
const responseEntries = new WeakMap();
|
|
33
|
+
/** The core's physical request, also available to direct HttpClient middleware. */
|
|
34
|
+
export function responseRequest(response) {
|
|
35
|
+
return responseEntries.get(response);
|
|
36
|
+
}
|
|
37
|
+
export function guardedClient(options) {
|
|
38
|
+
const { provider, maxResponseBytes: limit } = options;
|
|
39
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
40
|
+
throw new Error(`${provider} maxResponseBytes must be a whole number of bytes >= 1; received ${String(limit)}.`);
|
|
41
|
+
}
|
|
42
|
+
const unauthorizedLimit = options.maxUnauthorizedResponseBytes;
|
|
43
|
+
if (unauthorizedLimit !== undefined && (!Number.isInteger(unauthorizedLimit) || unauthorizedLimit < 1))
|
|
44
|
+
throw new Error("maxUnauthorizedResponseBytes must be a whole number of bytes >= 1.");
|
|
45
|
+
const responseLimit = (status) => (status === 401 ? Math.min(limit, unauthorizedLimit ?? limit) : limit);
|
|
46
|
+
if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1))
|
|
47
|
+
throw new Error("timeoutMs must be a positive whole number.");
|
|
48
|
+
const base = confinementBase(provider, options.baseUrl);
|
|
49
|
+
const basePath = base.pathname.replace(/\/+$/, "");
|
|
50
|
+
const constant = { ...options.headers };
|
|
51
|
+
const refuse = (message) => Effect.fail(new RequestRefused({ message }));
|
|
52
|
+
const preparedEntries = new WeakMap();
|
|
53
|
+
// Confine, then authenticate last, then register what will be sent.
|
|
54
|
+
const preprocess = (request) => Effect.gen(function* () {
|
|
55
|
+
const facts = yield* RequestFacts;
|
|
56
|
+
const idempotency = facts.effect === "call" ? yield* IdempotencyKey : undefined;
|
|
57
|
+
const entry = (yield* Ledger).open({
|
|
58
|
+
auxiliary: facts.effect === "read",
|
|
59
|
+
provider,
|
|
60
|
+
method: request.method,
|
|
61
|
+
...(idempotency ? { key: idempotency.key } : {}),
|
|
62
|
+
});
|
|
63
|
+
const path = request.url;
|
|
64
|
+
if (!path.startsWith("/")) {
|
|
65
|
+
return yield* refuse(`A ${provider} request path is provider-relative and must begin with "/".`);
|
|
66
|
+
}
|
|
67
|
+
if (path.includes("?") || path.includes("#") || request.hash._tag === "Some") {
|
|
68
|
+
return yield* refuse(`A ${provider} request path carries no query or fragment; put query parameters in the query argument.`);
|
|
69
|
+
}
|
|
70
|
+
let url;
|
|
71
|
+
try {
|
|
72
|
+
url = new URL(`${base.origin}${basePath}${path}`);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return yield* refuse(`A ${provider} request path did not resolve to a valid URL.`);
|
|
76
|
+
}
|
|
77
|
+
if (url.origin !== base.origin || !`${url.pathname}/`.startsWith(`${basePath}/`)) {
|
|
78
|
+
return yield* refuse(`A ${provider} request path escaped the connector's base URL.`);
|
|
79
|
+
}
|
|
80
|
+
let confined = HttpClientRequest.setUrl(request, url.href);
|
|
81
|
+
if (options.admit)
|
|
82
|
+
yield* options.admit(wireUrl(confined), {
|
|
83
|
+
method: confined.method,
|
|
84
|
+
...(bodyText(confined) === undefined ? {} : { body: bodyText(confined) }),
|
|
85
|
+
});
|
|
86
|
+
for (const [name, value] of Object.entries(constant)) {
|
|
87
|
+
if (confined.headers[name.toLowerCase()] === undefined)
|
|
88
|
+
confined = HttpClientRequest.setHeader(confined, name, value);
|
|
89
|
+
}
|
|
90
|
+
const secrets = yield* Secrets;
|
|
91
|
+
for (const [name, value] of Object.entries(yield* options.authenticate)) {
|
|
92
|
+
if (confined.headers[name.toLowerCase()] !== undefined) {
|
|
93
|
+
return yield* refuse(`A ${provider} request may not set the ${name.toLowerCase()} header; authentication is connector-owned.`);
|
|
94
|
+
}
|
|
95
|
+
confined = HttpClientRequest.setHeader(confined, name, value);
|
|
96
|
+
secrets.header(value);
|
|
97
|
+
}
|
|
98
|
+
// Registered as guarded-fetch registers: the final URL, headers, and a
|
|
99
|
+
// text body (a form body's secrets are read from it).
|
|
100
|
+
const text = bodyText(confined);
|
|
101
|
+
secrets.request(wireUrl(confined), {
|
|
102
|
+
headers: Object.fromEntries(Object.entries(confined.headers)),
|
|
103
|
+
...(text === undefined ? {} : { body: text }),
|
|
104
|
+
});
|
|
105
|
+
preparedEntries.set(confined, entry);
|
|
106
|
+
return confined;
|
|
107
|
+
});
|
|
108
|
+
const postprocess = (prepared) => Effect.gen(function* () {
|
|
109
|
+
const callSignal = yield* Signal;
|
|
110
|
+
const live = () => callSignal?.aborted ? refuse("This call has ended; no transport work may continue.") : Effect.void;
|
|
111
|
+
yield* live();
|
|
112
|
+
const facts = yield* RequestFacts;
|
|
113
|
+
const ledger = yield* Dispatches;
|
|
114
|
+
// A new attempt: until it is handed to `fetch`, no failure is its answer.
|
|
115
|
+
if (facts.sent)
|
|
116
|
+
delete facts.sent.dispatch;
|
|
117
|
+
const earlyWrite = (yield* Classification) === "write";
|
|
118
|
+
const capacity = yield* WriteCapacity;
|
|
119
|
+
// Claim the early Send reservation (or reserve a direct HttpClient call)
|
|
120
|
+
// atomically, before evaluating `prepared`. A wrapper cannot reuse it.
|
|
121
|
+
const claim = () => {
|
|
122
|
+
const reserved = facts.sent?.reservation ?? reserveWrite(ledger, capacity, facts);
|
|
123
|
+
if (reserved instanceof RequestRefused || reserved instanceof ResendRefused)
|
|
124
|
+
return reserved;
|
|
125
|
+
if (reserved.state !== "reserved")
|
|
126
|
+
return new ResendRefused({ first: reserved });
|
|
127
|
+
reserved.state = "preparing";
|
|
128
|
+
return reserved;
|
|
129
|
+
};
|
|
130
|
+
const initial = yield* Effect.sync(() => (earlyWrite ? claim() : undefined));
|
|
131
|
+
if (initial instanceof RequestRefused || initial instanceof ResendRefused)
|
|
132
|
+
return yield* Effect.fail(initial);
|
|
133
|
+
let slot = initial;
|
|
134
|
+
return yield* Effect.gen(function* () {
|
|
135
|
+
const preparation = callSignal
|
|
136
|
+
? Effect.raceFirst(prepared, fromSignal(callSignal).pipe(Effect.mapError(() => new RequestRefused({ message: "This call has ended; no transport work may continue." }))))
|
|
137
|
+
: prepared;
|
|
138
|
+
const request = yield* preparation;
|
|
139
|
+
const url = wireUrl(request);
|
|
140
|
+
yield* live();
|
|
141
|
+
const headers = Object.fromEntries(Object.entries(request.headers).filter(([name]) => name !== "content-length"));
|
|
142
|
+
// A genuine key the vendor honors rides only on the tool's own request,
|
|
143
|
+
// added after sent-secret registration like guarded-fetch's visible
|
|
144
|
+
// headers. Only a key sent here is recorded, so only it can make the
|
|
145
|
+
// write repeatable or earn key-reuse advice (`keyProtectsCall`).
|
|
146
|
+
const idempotency = facts.effect === "call" ? yield* IdempotencyKey : undefined;
|
|
147
|
+
if (idempotency) {
|
|
148
|
+
const name = idempotency.header.toLowerCase();
|
|
149
|
+
if (Object.keys(headers).some((header) => header.toLowerCase() === name)) {
|
|
150
|
+
return yield* refuse(`A ${provider} request sets the ${name} header twice.`);
|
|
151
|
+
}
|
|
152
|
+
headers[idempotency.header] = idempotency.key;
|
|
153
|
+
}
|
|
154
|
+
const core = yield* Ledger;
|
|
155
|
+
const entry = preparedEntries.get(request) ??
|
|
156
|
+
core.open({
|
|
157
|
+
auxiliary: facts.effect === "read",
|
|
158
|
+
provider,
|
|
159
|
+
method: request.method,
|
|
160
|
+
...(idempotency ? { key: idempotency.key } : {}),
|
|
161
|
+
});
|
|
162
|
+
const write = !readRequest({ classification: entry.classification, method: request.method }, headers);
|
|
163
|
+
// Preparation can reveal a write in a served read or auxiliary request.
|
|
164
|
+
// The core's actual facts and final headers decide before any dispatch.
|
|
165
|
+
if (write && !slot) {
|
|
166
|
+
const reserved = yield* Effect.sync(claim);
|
|
167
|
+
if (reserved instanceof RequestRefused || reserved instanceof ResendRefused)
|
|
168
|
+
return yield* Effect.fail(reserved);
|
|
169
|
+
slot = reserved;
|
|
170
|
+
}
|
|
171
|
+
const expected = slot ? capacity.get(slot.step)?.get(slot.item) : undefined;
|
|
172
|
+
if (expected) {
|
|
173
|
+
const text = bodyText(request);
|
|
174
|
+
const contentType = request.headers["content-type"]?.split(";")[0]?.trim();
|
|
175
|
+
if (request.body._tag !== "Empty" &&
|
|
176
|
+
(text === undefined ||
|
|
177
|
+
!["application/x-www-form-urlencoded", "application/json"].includes(contentType ?? ""))) {
|
|
178
|
+
return yield* refuse("A bulk item request must use its declared form or JSON body.");
|
|
179
|
+
}
|
|
180
|
+
const actual = yield* Effect.try({
|
|
181
|
+
try: () => writeRequestKey({
|
|
182
|
+
method: request.method,
|
|
183
|
+
path: url.pathname.slice(basePath.length),
|
|
184
|
+
template: expected.template,
|
|
185
|
+
query: [...url.searchParams],
|
|
186
|
+
...(text === undefined
|
|
187
|
+
? {}
|
|
188
|
+
: contentType === "application/json"
|
|
189
|
+
? { json: text }
|
|
190
|
+
: { form: [...new URLSearchParams(text)] }),
|
|
191
|
+
...(expected.multiValue ? { multiValue: expected.multiValue } : {}),
|
|
192
|
+
}),
|
|
193
|
+
catch: () => new RequestRefused({ message: "Duplicate or invalid identifying content in this write item's request." }),
|
|
194
|
+
});
|
|
195
|
+
if (actual !== writeRequestKey(expected))
|
|
196
|
+
return yield* refuse("This write item's request differs from its declared identifying content.");
|
|
197
|
+
}
|
|
198
|
+
// Resolve the runtime-owned sender before recording a physical dispatch.
|
|
199
|
+
const transport = yield* Effect.try({
|
|
200
|
+
try: () => guardedTransport(write),
|
|
201
|
+
catch: (error) => new Refusal({ error: error }),
|
|
202
|
+
});
|
|
203
|
+
const body = bodyInit(request);
|
|
204
|
+
// A reservation is never released, including preparation failures and
|
|
205
|
+
// failures to connect. Only a new deliberate call gets capacity.
|
|
206
|
+
const dispatch = slot ?? {
|
|
207
|
+
write: false,
|
|
208
|
+
...(facts.operation ? { operation: facts.operation } : {}),
|
|
209
|
+
state: "pending",
|
|
210
|
+
};
|
|
211
|
+
if (!slot)
|
|
212
|
+
ledger.push(dispatch);
|
|
213
|
+
dispatch.request = entry;
|
|
214
|
+
if (facts.sent)
|
|
215
|
+
facts.sent.dispatch = dispatch;
|
|
216
|
+
const refused = (failure) => {
|
|
217
|
+
dispatch.failure = failure;
|
|
218
|
+
responseEntries.set(failure, entry);
|
|
219
|
+
return Effect.fail(failure);
|
|
220
|
+
};
|
|
221
|
+
const response = yield* Effect.tryPromise({
|
|
222
|
+
try: (interrupted) => {
|
|
223
|
+
// Synchronous with the send: from here the core counts it as left,
|
|
224
|
+
// and it refuses a request whose call has already ended.
|
|
225
|
+
if (facts.effect === "read" && !readRequest({ classification: "read", method: request.method }, headers))
|
|
226
|
+
recordRequestField(entry, "blocksAuxiliaryProof", true);
|
|
227
|
+
dispatch.state = "pending";
|
|
228
|
+
return sendGuarded(transport, url, {
|
|
229
|
+
method: request.method,
|
|
230
|
+
headers,
|
|
231
|
+
...body,
|
|
232
|
+
// A redirect re-sends the credential wherever `Location` points.
|
|
233
|
+
redirect: "manual",
|
|
234
|
+
signal: AbortSignal.any([
|
|
235
|
+
interrupted,
|
|
236
|
+
...(callSignal ? [callSignal] : []),
|
|
237
|
+
...(options.timeoutMs === undefined ? [] : [AbortSignal.timeout(options.timeoutMs)]),
|
|
238
|
+
]),
|
|
239
|
+
}, () => core.dispatch(entry, headers, request.method));
|
|
240
|
+
},
|
|
241
|
+
catch: (failure) => {
|
|
242
|
+
if (!entry.dispatched) {
|
|
243
|
+
return failure instanceof ConnectorCallError
|
|
244
|
+
? new Refusal({ error: failure })
|
|
245
|
+
: new RequestRefused({ message: "This call has ended; no transport work may continue." });
|
|
246
|
+
}
|
|
247
|
+
// No rejection proves a write unsent: one fetch can hide several
|
|
248
|
+
// attempts (a 421 or HTTP/2 refused-stream resend, a retrying
|
|
249
|
+
// dispatcher), whatever the last one reports (#902 round 8).
|
|
250
|
+
dispatch.state = "lost";
|
|
251
|
+
return (dispatch.failure = new TransportFailure({ failure, host: url.href }));
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
dispatch.state = "answered";
|
|
255
|
+
dispatch.status = response.status;
|
|
256
|
+
recordProviderResponse(entry, response.status);
|
|
257
|
+
dispatch.headers = response.headers;
|
|
258
|
+
if (REDIRECT_STATUSES.has(response.status)) {
|
|
259
|
+
yield* cancel(response);
|
|
260
|
+
return yield* refused(new RedirectRefused({ status: response.status }));
|
|
261
|
+
}
|
|
262
|
+
const declared = Number(response.headers.get("content-length"));
|
|
263
|
+
const ceiling = responseLimit(response.status);
|
|
264
|
+
if (Number.isFinite(declared) &&
|
|
265
|
+
declared > ceiling &&
|
|
266
|
+
request.method !== "HEAD" &&
|
|
267
|
+
!(facts.successPrefix && response.ok)) {
|
|
268
|
+
yield* cancel(response);
|
|
269
|
+
return yield* refused(tooLarge(provider, ceiling, `a declared ${declared} bytes`));
|
|
270
|
+
}
|
|
271
|
+
const answer = HttpClientResponse.fromWeb(request, response);
|
|
272
|
+
responseEntries.set(answer, entry);
|
|
273
|
+
return answer;
|
|
274
|
+
}).pipe(Effect.tapError((failure) => Effect.sync(() => {
|
|
275
|
+
if (slot && slot.failure === undefined && failure._tag !== "ResendRefused")
|
|
276
|
+
slot.failure = failure;
|
|
277
|
+
})));
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
provider,
|
|
281
|
+
client: HttpClient.makeWith(postprocess, preprocess),
|
|
282
|
+
read: (response, maxBytes, format, prefix) => {
|
|
283
|
+
if (maxBytes !== undefined && (!Number.isInteger(maxBytes) || maxBytes < 1))
|
|
284
|
+
throw new Error("A read ceiling must be a whole number of bytes >= 1.");
|
|
285
|
+
if (prefix && response.status >= 200 && response.status <= 299) {
|
|
286
|
+
if (!Number.isSafeInteger(prefix.maxBytes) || prefix.maxBytes < (format === "bytes" ? 0 : 1))
|
|
287
|
+
throw new Error("A success prefix needs a whole byte ceiling >= 0 for bytes or >= 1 for text.");
|
|
288
|
+
if (prefix.timeoutMs !== undefined && (!Number.isSafeInteger(prefix.timeoutMs) || prefix.timeoutMs < 1))
|
|
289
|
+
throw new Error("A success prefix timeout must be a positive whole number.");
|
|
290
|
+
return readSuccessPrefix(response, {
|
|
291
|
+
...prefix,
|
|
292
|
+
maxBytes: Math.min(limit, maxBytes ?? limit, prefix.maxBytes),
|
|
293
|
+
...(prefix.timeoutMs === undefined
|
|
294
|
+
? {}
|
|
295
|
+
: {
|
|
296
|
+
timeoutMs: Math.min(prefix.timeoutMs, options.timeoutMs ?? Infinity),
|
|
297
|
+
}),
|
|
298
|
+
}, format);
|
|
299
|
+
}
|
|
300
|
+
return readReply(provider, Math.min(responseLimit(response.status), maxBytes ?? limit), response, format);
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/** The exact URL `fetch` receives: confined path plus encoded query. */
|
|
305
|
+
function wireUrl(request) {
|
|
306
|
+
const built = Url.make(request.url, request.urlParams, undefined);
|
|
307
|
+
if (built._tag === "Failure")
|
|
308
|
+
throw new Error("A confined request URL failed to build.");
|
|
309
|
+
return built.success;
|
|
310
|
+
}
|
|
311
|
+
/** A JSON, form, or text body as the string guarded-fetch would have sent. */
|
|
312
|
+
function bodyText(request) {
|
|
313
|
+
const body = request.body;
|
|
314
|
+
if (body._tag === "Uint8Array")
|
|
315
|
+
return body.text;
|
|
316
|
+
return body._tag === "Raw" && typeof body.body === "string" ? body.body : undefined;
|
|
317
|
+
}
|
|
318
|
+
function bodyInit(request) {
|
|
319
|
+
const body = request.body;
|
|
320
|
+
switch (body._tag) {
|
|
321
|
+
case "Empty":
|
|
322
|
+
return {};
|
|
323
|
+
case "Raw":
|
|
324
|
+
return { body: body.body };
|
|
325
|
+
case "Uint8Array":
|
|
326
|
+
// The retained text when there is one, so the wire carries exactly the
|
|
327
|
+
// string guarded-fetch sent and goldens record the same body.
|
|
328
|
+
return { body: (body.text ?? body.body) };
|
|
329
|
+
case "FormData":
|
|
330
|
+
return { body: body.formData };
|
|
331
|
+
case "Stream":
|
|
332
|
+
// A stream cannot be read twice or measured; no kit operation sends one.
|
|
333
|
+
throw new Error("The guarded client does not send stream bodies.");
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function tooLarge(provider, limit, detail) {
|
|
337
|
+
return new ResponseTooLarge({
|
|
338
|
+
detail: `${provider} returned ${detail}, past this connector's ${limit}-byte response ceiling.`,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
function cancel(response) {
|
|
342
|
+
return Effect.promise(() => response.body?.cancel().catch(() => { }) ?? Promise.resolve());
|
|
343
|
+
}
|
|
344
|
+
/** Stream finalization cancels the unread body on every early stop or interruption. */
|
|
345
|
+
function readSuccessPrefix(response, prefix, format) {
|
|
346
|
+
return Effect.suspend(() => {
|
|
347
|
+
const parts = [];
|
|
348
|
+
let total = 0;
|
|
349
|
+
let stopped = "end";
|
|
350
|
+
const source = response.stream.pipe(Stream.catch((error) => error.reason._tag === "EmptyBodyError"
|
|
351
|
+
? Stream.fromEffect(response.arrayBuffer.pipe(Effect.map((buffer) => new Uint8Array(buffer))))
|
|
352
|
+
: Stream.fail(error)));
|
|
353
|
+
const consume = Stream.runForEachWhile(source, (chunk) => {
|
|
354
|
+
const kept = chunk.subarray(0, Math.max(0, prefix.maxBytes - total));
|
|
355
|
+
parts.push(kept);
|
|
356
|
+
total += kept.byteLength;
|
|
357
|
+
if (kept.byteLength < chunk.byteLength) {
|
|
358
|
+
stopped = "bytes";
|
|
359
|
+
return Effect.succeed(false);
|
|
360
|
+
}
|
|
361
|
+
// A callback closes over no Effect services from the invoking request.
|
|
362
|
+
return Effect.sync(() => prefix.stop?.(kept.slice()) === true).pipe(Effect.setContext(Context.empty()), Effect.map((stop) => {
|
|
363
|
+
if (stop)
|
|
364
|
+
stopped = "stop";
|
|
365
|
+
return !stop;
|
|
366
|
+
}));
|
|
367
|
+
});
|
|
368
|
+
const timed = prefix.timeoutMs === undefined
|
|
369
|
+
? consume
|
|
370
|
+
: Effect.raceFirst(consume, Effect.sleep(prefix.timeoutMs).pipe(Effect.tap(() => Effect.sync(() => {
|
|
371
|
+
stopped = "time";
|
|
372
|
+
}))));
|
|
373
|
+
return timed.pipe(Effect.match({
|
|
374
|
+
onFailure: () => ({
|
|
375
|
+
status: response.status,
|
|
376
|
+
headers: new Headers(Object.entries(response.headers)),
|
|
377
|
+
text: undefined,
|
|
378
|
+
}),
|
|
379
|
+
onSuccess: () => {
|
|
380
|
+
const bytes = new Uint8Array(total);
|
|
381
|
+
let offset = 0;
|
|
382
|
+
for (const part of parts) {
|
|
383
|
+
bytes.set(part, offset);
|
|
384
|
+
offset += part.byteLength;
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
status: response.status,
|
|
388
|
+
headers: new Headers(Object.entries(response.headers)),
|
|
389
|
+
...(format === "bytes" ? { text: undefined, bytes } : { text: decoder.decode(bytes) }),
|
|
390
|
+
prefix: { truncated: stopped !== "end", bytes: total, stopped },
|
|
391
|
+
};
|
|
392
|
+
},
|
|
393
|
+
}));
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* The body as UTF-8 decoded from bounded bytes (never `text()`, whose runtime
|
|
398
|
+
* log can quote a non-text Content-Type, INV-6). The stream is abandoned at
|
|
399
|
+
* the chunk that crosses the ceiling, and its reader is cancelled when the
|
|
400
|
+
* read ends either way. A runtime with no body stream has buffered already,
|
|
401
|
+
* so the buffer is measured instead. Any other read failure leaves `text`
|
|
402
|
+
* undefined, as guarded-fetch's `jsonResult` reported a parse failure.
|
|
403
|
+
*/
|
|
404
|
+
function readReply(provider, limit, response, format) {
|
|
405
|
+
const reply = (text, bytes, readFailed) => ({
|
|
406
|
+
status: response.status,
|
|
407
|
+
headers: new Headers(Object.entries(response.headers)),
|
|
408
|
+
text,
|
|
409
|
+
...(bytes === undefined ? {} : { bytes }),
|
|
410
|
+
...(readFailed ? { readFailed } : {}),
|
|
411
|
+
});
|
|
412
|
+
if (BODILESS_STATUSES.has(response.status) || response.request.method === "HEAD")
|
|
413
|
+
return Effect.succeed(format === "bytes" ? reply(undefined, new Uint8Array()) : reply(""));
|
|
414
|
+
const buffered = response.arrayBuffer.pipe(Effect.matchEffect({
|
|
415
|
+
onFailure: () => Effect.succeed(reply(undefined, undefined, true)),
|
|
416
|
+
onSuccess: (buffer) => buffer.byteLength > limit
|
|
417
|
+
? Effect.fail(tooLarge(provider, limit, `${buffer.byteLength} bytes`))
|
|
418
|
+
: Effect.succeed(format === "bytes" ? reply(undefined, new Uint8Array(buffer)) : reply(decoder.decode(buffer))),
|
|
419
|
+
}));
|
|
420
|
+
return response.stream.pipe(Stream.runFoldEffect(() => ({ parts: [], total: 0 }), (read, chunk) => {
|
|
421
|
+
read.total += chunk.byteLength;
|
|
422
|
+
if (read.total > limit)
|
|
423
|
+
return Effect.fail(tooLarge(provider, limit, "more bytes than it may"));
|
|
424
|
+
read.parts.push(chunk);
|
|
425
|
+
return Effect.succeed(read);
|
|
426
|
+
}), Effect.map(({ parts, total }) => {
|
|
427
|
+
const bytes = new Uint8Array(total);
|
|
428
|
+
let offset = 0;
|
|
429
|
+
for (const part of parts) {
|
|
430
|
+
bytes.set(part, offset);
|
|
431
|
+
offset += part.byteLength;
|
|
432
|
+
}
|
|
433
|
+
return format === "bytes" ? reply(undefined, bytes) : reply(decoder.decode(bytes));
|
|
434
|
+
}), Effect.catch((error) => error._tag === "ResponseTooLarge"
|
|
435
|
+
? Effect.fail(error)
|
|
436
|
+
: error.reason._tag === "EmptyBodyError"
|
|
437
|
+
? buffered
|
|
438
|
+
: Effect.succeed(reply(undefined, undefined, true))));
|
|
439
|
+
}
|