@zackbart/connecta 0.34.0 → 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 +393 -0
- package/README.md +62 -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 +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 +15 -7
- package/dist/connectors/api-connector.js +147 -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/option-shapes.js +1 -1
- package/dist/connectors/remote-mcp.d.ts +12 -30
- package/dist/connectors/remote-mcp.js +960 -564
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +5 -1
- package/dist/describe-config.js +3 -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 +221 -87
- 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 +534 -408
- 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 +8 -4
- package/dist/operator-ui/connection-map.js +9 -6
- package/dist/operator-ui/contract.d.ts +7 -3
- package/dist/operator-ui/endpoints.js +5 -14
- 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/health.js +52 -0
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/operator-ui/overview.js +11 -19
- package/dist/operator-ui/setup-commands.js +0 -4
- package/dist/operator-ui/view.d.ts +5 -29
- package/dist/operator-ui/view.js +3 -59
- 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 +71 -32
- package/dist/provider.js +134 -40
- 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 +8 -209
- package/dist/providers/_shared/google/workspace.js +7 -426
- 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 +3 -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 +17 -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 +7 -22
- package/dist/providers/cloudflare/index.js +80 -194
- 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 +3299 -3208
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +538 -18
- 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 +3 -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 +6 -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 +7 -27
- package/dist/providers/stripe/index.js +75 -191
- 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 -10
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +19 -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 +11 -25
- package/dist/providers/vercel/index.js +67 -261
- 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 +398 -376
- package/dist/providers/vercel/{value-safety.js → safety.js} +261 -42
- 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 +30 -28
- package/dist/registry.js +446 -413
- 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 +29 -22
- 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 +330 -224
- 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 +11 -99
- 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 +2550 -186
- package/documentation/auth.md +821 -98
- package/documentation/code-mode.md +56 -21
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +144 -105
- package/documentation/meta-tools.md +411 -34
- package/documentation/operating.md +28 -4
- package/documentation/operator-tests.md +11 -2
- package/documentation/operator-ui.md +103 -33
- package/documentation/provider-auth-migration.md +38 -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 -42
- 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 -134
- package/dist/catalog-drift.js +0 -493
- 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 -43
- 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 -1078
- 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/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -352
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -57
- package/dist/providers/mixpanel/index.js +0 -468
- 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 -65
- 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 -49
- package/dist/providers/revenuecat/index.js +0 -557
- 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,546 @@
|
|
|
1
|
+
// Step-1 REST bridge: pinned contracts and legacy hatch bytes on the provider kit.
|
|
2
|
+
// The index remains the hatch validator; every network answer comes from Connection.
|
|
3
|
+
import { Effect, Schema } from "effect";
|
|
4
|
+
import { IDEMPOTENCY_KEY_MAX_LENGTH } from "../../../call-recovery.js";
|
|
5
|
+
import { ConnectorCallError, keyResendAdvice } from "../../../errors.js";
|
|
6
|
+
import { providerFailureMessage } from "../../../write-outcome.js";
|
|
7
|
+
import { Refusal, toConnectorCallError } from "../../../http/errors.js";
|
|
8
|
+
import { Integer, isMinChars, isMaxChars } from "../../../http/json-schema.js";
|
|
9
|
+
import { Classification, Dispatches, IdempotencyKey, Recovery, Signal, } from "../../../http/services.js";
|
|
10
|
+
import { read, write } from "../../../http/tools.js";
|
|
11
|
+
import { vendor as declareVendor, get, head, post, put, patch, del, } from "../../../http/vendor.js";
|
|
12
|
+
const MAX_SELECT = 50;
|
|
13
|
+
const READ = { readOnlyHint: true };
|
|
14
|
+
const DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true };
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
function invalid(message) {
|
|
19
|
+
throw new ConnectorCallError("invalid_args", message);
|
|
20
|
+
}
|
|
21
|
+
export function checked(run) {
|
|
22
|
+
return Effect.try({ try: run, catch: (error) => error }).pipe(Effect.catch((error) => error instanceof ConnectorCallError ? Effect.fail(new Refusal({ error })) : Effect.die(error)));
|
|
23
|
+
}
|
|
24
|
+
/** Index matches retain encoded segments; HttpApiClient uses encodeURIComponent. */
|
|
25
|
+
export function typedParams(params) {
|
|
26
|
+
return Object.fromEntries(Object.entries(params).map(([name, encoded]) => {
|
|
27
|
+
let value;
|
|
28
|
+
try {
|
|
29
|
+
value = decodeURIComponent(encoded);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return invalid(`Indexed path parameter ${name} is not valid URI encoding.`);
|
|
33
|
+
}
|
|
34
|
+
if (encodeURIComponent(value) !== encoded)
|
|
35
|
+
invalid(`Indexed path parameter ${name} does not round-trip through the typed path encoder.`);
|
|
36
|
+
return [name, value];
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
/** Only the JSON forms the pinned index and legacy hatch catalogs publish. No vendor encoders run here. */
|
|
40
|
+
export function schema(node) {
|
|
41
|
+
const n = node;
|
|
42
|
+
let out;
|
|
43
|
+
const types = n["type"];
|
|
44
|
+
if (n["truncated"] === true)
|
|
45
|
+
out = Schema.Unknown;
|
|
46
|
+
else if (Array.isArray(n["enum"]))
|
|
47
|
+
out = n["enum"].length === 1 ? Schema.Literal(n["enum"][0]) : Schema.Literals(n["enum"]);
|
|
48
|
+
else if (Array.isArray(n["anyOf"]))
|
|
49
|
+
out = Schema.Union(n["anyOf"].map((v) => schema(v)));
|
|
50
|
+
else if (Array.isArray(types))
|
|
51
|
+
out = Schema.Union(types.map((type) => schema({ ...n, type, description: undefined })));
|
|
52
|
+
else if (types === "object" || isRecord(n["properties"])) {
|
|
53
|
+
const required = n["required"];
|
|
54
|
+
const fields = Object.fromEntries(Object.entries((n["properties"] ?? {})).map(([key, value]) => {
|
|
55
|
+
const part = schema(value);
|
|
56
|
+
const annotated = part.ast._tag === "Unknown" && isRecord(value) && typeof value["description"] === "string"
|
|
57
|
+
? part.pipe(Schema.annotateKey({ description: value["description"] }))
|
|
58
|
+
: part;
|
|
59
|
+
return [key, required?.includes(key) ? annotated : Schema.optionalKey(annotated)];
|
|
60
|
+
}));
|
|
61
|
+
out =
|
|
62
|
+
n["properties"] && n["additionalProperties"] !== true
|
|
63
|
+
? Schema.Struct(fields)
|
|
64
|
+
: Schema.StructWithRest(Schema.Struct(fields), [
|
|
65
|
+
Schema.Record(Schema.String, isRecord(n["additionalProperties"]) ? schema(n["additionalProperties"]) : Schema.Unknown),
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
else if (types === "array") {
|
|
69
|
+
let array = Schema.Array(schema((n["items"] ?? {})));
|
|
70
|
+
if (typeof n["minItems"] === "number")
|
|
71
|
+
array = array.check(Schema.isMinLength(n["minItems"]));
|
|
72
|
+
if (typeof n["maxItems"] === "number")
|
|
73
|
+
array = array.check(Schema.isMaxLength(n["maxItems"]));
|
|
74
|
+
if (n["uniqueItems"] === true)
|
|
75
|
+
array = array.check(Schema.isUnique());
|
|
76
|
+
out = array;
|
|
77
|
+
}
|
|
78
|
+
else if (types === "string") {
|
|
79
|
+
let string = Schema.String;
|
|
80
|
+
if (typeof n["minLength"] === "number")
|
|
81
|
+
string = string.check(isMinChars(n["minLength"]));
|
|
82
|
+
if (typeof n["maxLength"] === "number")
|
|
83
|
+
string = string.check(isMaxChars(n["maxLength"]));
|
|
84
|
+
out = string;
|
|
85
|
+
}
|
|
86
|
+
else if (types === "integer" || types === "number") {
|
|
87
|
+
let number = types === "integer" ? Integer : Schema.Finite;
|
|
88
|
+
if (typeof n["minimum"] === "number")
|
|
89
|
+
number = number.check(Schema.isGreaterThanOrEqualTo(n["minimum"]));
|
|
90
|
+
if (typeof n["maximum"] === "number")
|
|
91
|
+
number = number.check(Schema.isLessThanOrEqualTo(n["maximum"]));
|
|
92
|
+
out = number;
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
out = types === "boolean" ? Schema.Boolean : types === "null" ? Schema.Null : Schema.Unknown;
|
|
96
|
+
if (n["nullable"] === true)
|
|
97
|
+
out = Schema.Union([out, Schema.Null]);
|
|
98
|
+
if (n["writeOnly"] === true)
|
|
99
|
+
out = out.annotate({ writeOnly: true });
|
|
100
|
+
return typeof n["description"] === "string" ? out.annotate({ description: n["description"] }) : out;
|
|
101
|
+
}
|
|
102
|
+
/** Declare each indexed operation once; request schema subtrees stay suspended until manifest/client use. */
|
|
103
|
+
export function indexedVendor(name, index, named = []) {
|
|
104
|
+
for (const op of index.ops) {
|
|
105
|
+
if ([...op.path.matchAll(/\{([^}]*)\}/g)].some((match) => !/^[A-Za-z0-9_-]+$/.test(match[1])) ||
|
|
106
|
+
/[{}]/.test(op.path.replace(/\{[^}]*\}/g, "")))
|
|
107
|
+
throw new Error(`Indexed operation ${op.method} ${op.path} has an unsupported path parameter name.`);
|
|
108
|
+
}
|
|
109
|
+
const constructors = { GET: get, HEAD: head, POST: post, PUT: put, PATCH: patch, DELETE: del };
|
|
110
|
+
const overrides = new Map(named.map((op) => {
|
|
111
|
+
const declared = op;
|
|
112
|
+
return [`${declared.method} ${declared.path.replace(/:([^/]+)/g, "{$1}")}`, op];
|
|
113
|
+
}));
|
|
114
|
+
for (const key of overrides.keys())
|
|
115
|
+
if (!index.ops.some((op) => `${op.method} ${op.path}` === key))
|
|
116
|
+
throw new Error(`Named operation ${key} is absent from the pinned index.`);
|
|
117
|
+
const cache = new Map();
|
|
118
|
+
const cached = (node) => {
|
|
119
|
+
const key = JSON.stringify(node);
|
|
120
|
+
let found = cache.get(key);
|
|
121
|
+
if (!found) {
|
|
122
|
+
found = schema(node);
|
|
123
|
+
cache.set(key, found);
|
|
124
|
+
}
|
|
125
|
+
return found;
|
|
126
|
+
};
|
|
127
|
+
const build = () => {
|
|
128
|
+
const endpoints = index.ops.map((op) => overrides.get(`${op.method} ${op.path}`) ??
|
|
129
|
+
constructors[op.method]("indexed" + op.row,
|
|
130
|
+
// These declarations feed only the manifest. The live client uses `named` below.
|
|
131
|
+
op.path, {
|
|
132
|
+
// Parameter names come directly from the vendor template; hatch validation still uses index.check.
|
|
133
|
+
params: Schema.Struct(Object.fromEntries([...op.path.matchAll(/\{([^}]+)\}/g)].map((match) => [match[1], Schema.String]))),
|
|
134
|
+
query: Schema.suspend(() => {
|
|
135
|
+
const parameters = index.contract(op).parameters.filter((p) => p.in === "query");
|
|
136
|
+
return Schema.Struct(Object.fromEntries(parameters.map((p) => [p.name, p.required ? cached(p.schema) : Schema.optionalKey(cached(p.schema))])));
|
|
137
|
+
}),
|
|
138
|
+
...(op.method === "GET" || op.method === "HEAD"
|
|
139
|
+
? {}
|
|
140
|
+
: { payload: Schema.suspend(() => cached(index.contract(op).body?.schema ?? {})) }),
|
|
141
|
+
success: Schema.Unknown,
|
|
142
|
+
}));
|
|
143
|
+
if (!endpoints.length)
|
|
144
|
+
throw new Error("An indexed vendor needs operations.");
|
|
145
|
+
return declareVendor(name, endpoints[0], ...endpoints.slice(1)).api;
|
|
146
|
+
};
|
|
147
|
+
if (!named.length)
|
|
148
|
+
throw new Error("An indexed vendor declares at least its typed credential probe.");
|
|
149
|
+
let built;
|
|
150
|
+
return {
|
|
151
|
+
...declareVendor(name, named[0], ...named.slice(1)),
|
|
152
|
+
indexed: { operations: index.ops.map((op) => `${op.method} ${op.path}`).sort(), api: () => (built ??= build()) },
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function project(value, paths) {
|
|
156
|
+
const root = new Map();
|
|
157
|
+
for (const path of paths) {
|
|
158
|
+
let node = root;
|
|
159
|
+
const parts = path.split(".");
|
|
160
|
+
for (const [index, part] of parts.entries()) {
|
|
161
|
+
const existing = node.get(part);
|
|
162
|
+
// A shorter path already keeps this whole subtree.
|
|
163
|
+
if (existing === true)
|
|
164
|
+
break;
|
|
165
|
+
if (index === parts.length - 1) {
|
|
166
|
+
node.set(part, true);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
const next = existing ?? new Map();
|
|
170
|
+
node.set(part, next);
|
|
171
|
+
node = next;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const pick = (current, tree) => {
|
|
175
|
+
if (Array.isArray(current))
|
|
176
|
+
return current.map((item) => pick(item, tree));
|
|
177
|
+
if (!isRecord(current))
|
|
178
|
+
return current;
|
|
179
|
+
const out = {};
|
|
180
|
+
for (const [key, child] of tree) {
|
|
181
|
+
if (!Object.hasOwn(current, key))
|
|
182
|
+
continue;
|
|
183
|
+
out[key] = child === true ? current[key] : pick(current[key], child);
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
};
|
|
187
|
+
return pick(value, root);
|
|
188
|
+
}
|
|
189
|
+
function selected(args, result) {
|
|
190
|
+
const select = args["select"];
|
|
191
|
+
if (!Array.isArray(select) || select.length === 0)
|
|
192
|
+
return result;
|
|
193
|
+
return { ...result, data: project(result.data, select) };
|
|
194
|
+
}
|
|
195
|
+
function pathSchema(example = "/v1/customers/cus_123") {
|
|
196
|
+
return {
|
|
197
|
+
type: "string",
|
|
198
|
+
minLength: 2,
|
|
199
|
+
maxLength: 2048,
|
|
200
|
+
description: `Concrete path from search with ids filled in, e.g. ${example}. No query string.`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const QUERY = {
|
|
204
|
+
type: "object",
|
|
205
|
+
description: "Query parameters as JSON, named as details lists them.",
|
|
206
|
+
};
|
|
207
|
+
const SELECT = {
|
|
208
|
+
type: "array",
|
|
209
|
+
maxItems: MAX_SELECT,
|
|
210
|
+
items: { type: "string", minLength: 1, maxLength: 200 },
|
|
211
|
+
description: "Dot paths to keep from data, e.g. data.id; a path through a list applies to each item.",
|
|
212
|
+
};
|
|
213
|
+
const PAGE = {
|
|
214
|
+
type: "object",
|
|
215
|
+
description: "Present on lists: pass next as the param query parameter (a body field when in is body) for the next page. hasMore without next: no cursor exists.",
|
|
216
|
+
properties: {
|
|
217
|
+
hasMore: { type: "boolean" },
|
|
218
|
+
next: { type: "string" },
|
|
219
|
+
param: { type: "string" },
|
|
220
|
+
in: { type: "string", enum: ["query", "body"] },
|
|
221
|
+
path: { type: "string", description: "GET this path for the next page, when it differs from this call's." },
|
|
222
|
+
},
|
|
223
|
+
required: ["hasMore"],
|
|
224
|
+
};
|
|
225
|
+
function envelope(extra = {}) {
|
|
226
|
+
return {
|
|
227
|
+
type: "object",
|
|
228
|
+
properties: {
|
|
229
|
+
status: { type: "integer", description: "HTTP status." },
|
|
230
|
+
data: {
|
|
231
|
+
description: "Response body: JSON, text, NDJSON rows, { contentType, bytes, base64 }, or HEAD's headers.",
|
|
232
|
+
},
|
|
233
|
+
page: PAGE,
|
|
234
|
+
...extra,
|
|
235
|
+
},
|
|
236
|
+
required: ["status", "data"],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function closed(properties, required) {
|
|
240
|
+
return { type: "object", properties, required, additionalProperties: false };
|
|
241
|
+
}
|
|
242
|
+
/** Restore declaration key order only after proving the kit generated the identical schema. */
|
|
243
|
+
export function catalogOrder(actual, legacy) {
|
|
244
|
+
if (Array.isArray(legacy)) {
|
|
245
|
+
if (!Array.isArray(actual) || actual.length !== legacy.length)
|
|
246
|
+
throw new Error("Kit catalog array differs from the legacy declaration.");
|
|
247
|
+
return legacy.map((value, index) => catalogOrder(actual[index], value));
|
|
248
|
+
}
|
|
249
|
+
if (isRecord(legacy)) {
|
|
250
|
+
if (!isRecord(actual) ||
|
|
251
|
+
Object.keys(actual).length !== Object.keys(legacy).length ||
|
|
252
|
+
Object.keys(legacy).some((key) => !Object.hasOwn(actual, key)))
|
|
253
|
+
throw new Error("Kit catalog keys differ from the legacy declaration.");
|
|
254
|
+
return Object.fromEntries(Object.entries(legacy).map(([key, value]) => [key, catalogOrder(actual[key], value)]));
|
|
255
|
+
}
|
|
256
|
+
if (actual !== legacy)
|
|
257
|
+
throw new Error("Kit catalog value differs from the legacy declaration.");
|
|
258
|
+
return actual;
|
|
259
|
+
}
|
|
260
|
+
/** Four legacy tools, with kit leases, capacity, mapping and dispatch. */
|
|
261
|
+
export function restKitTools(vendor) {
|
|
262
|
+
const writeSteps = vendor.writeSteps === undefined ? undefined : Object.freeze({ ...vendor.writeSteps });
|
|
263
|
+
const { index, title } = vendor;
|
|
264
|
+
const tool = (kind) => `${vendor.vendor}_api_${kind}`;
|
|
265
|
+
const PATH = pathSchema(vendor.pathExample);
|
|
266
|
+
const headerNames = Object.keys(vendor.headers ?? {});
|
|
267
|
+
const HEADERS = headerNames.length
|
|
268
|
+
? {
|
|
269
|
+
headers: {
|
|
270
|
+
type: "object",
|
|
271
|
+
description: "Optional request headers this vendor's operations read; nothing else is accepted.",
|
|
272
|
+
properties: Object.fromEntries(Object.entries(vendor.headers).map(([name, description]) => [
|
|
273
|
+
name,
|
|
274
|
+
{ type: "string", minLength: 1, maxLength: 200, description },
|
|
275
|
+
])),
|
|
276
|
+
additionalProperties: false,
|
|
277
|
+
},
|
|
278
|
+
}
|
|
279
|
+
: {};
|
|
280
|
+
const headersOf = (args) => isRecord(args["headers"]) ? { headers: args["headers"] } : {};
|
|
281
|
+
const readPosts = vendor.readPosts ?? [];
|
|
282
|
+
for (const [method, path] of readPosts) {
|
|
283
|
+
// A reviewed exception the index no longer carries is a stale review.
|
|
284
|
+
if (!index.operation(method, path)) {
|
|
285
|
+
throw new Error(`${title} reviewed read-only ${method} ${path} is not in the pinned API index.`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const reads = (op) => op.method === "GET" ||
|
|
289
|
+
op.method === "HEAD" ||
|
|
290
|
+
readPosts.some(([method, path]) => method === op.method && path === op.path);
|
|
291
|
+
const writeMethods = index.methods.filter((method) => method !== "GET" && method !== "HEAD");
|
|
292
|
+
// The methods an idempotency key is sent with: never DELETE (see the handler).
|
|
293
|
+
const keyed = vendor.idempotencyHeader ? writeMethods.filter((method) => method !== "DELETE") : [];
|
|
294
|
+
const keyedText = keyed.join(" or ");
|
|
295
|
+
const head = index.methods.includes("HEAD");
|
|
296
|
+
const readMethods = [...new Set(["GET", ...(head ? ["HEAD"] : []), ...readPosts.map(([method]) => method)])];
|
|
297
|
+
const routeOf = (op) => (reads(op) ? tool("read") : tool("write"));
|
|
298
|
+
const guard = (call) => {
|
|
299
|
+
const server = vendor.checkServer?.(call.op.server);
|
|
300
|
+
if (server)
|
|
301
|
+
invalid(server);
|
|
302
|
+
const refusal = vendor.valueSafety.refusal(call.method, call.op.path) ?? vendor.refuse?.(call);
|
|
303
|
+
if (refusal)
|
|
304
|
+
invalid(refusal);
|
|
305
|
+
};
|
|
306
|
+
const declarations = [
|
|
307
|
+
{
|
|
308
|
+
name: tool("search"),
|
|
309
|
+
description: `Find ${title} API operations by keyword or path in the pinned API index. Returns method, path, and summary; reads no account data.`,
|
|
310
|
+
annotations: READ,
|
|
311
|
+
inputSchema: closed({
|
|
312
|
+
query: {
|
|
313
|
+
type: "string",
|
|
314
|
+
minLength: 1,
|
|
315
|
+
maxLength: 200,
|
|
316
|
+
description: "Keywords or a path, e.g. refund charge.",
|
|
317
|
+
},
|
|
318
|
+
method: { type: "string", enum: [...index.methods], description: "Only operations with this method." },
|
|
319
|
+
limit: { type: "integer", minimum: 1, maximum: 50, description: "Most operations to return; default 10." },
|
|
320
|
+
}, ["query"]),
|
|
321
|
+
outputSchema: {
|
|
322
|
+
type: "object",
|
|
323
|
+
properties: {
|
|
324
|
+
operations: {
|
|
325
|
+
type: "array",
|
|
326
|
+
items: {
|
|
327
|
+
type: "object",
|
|
328
|
+
properties: {
|
|
329
|
+
method: { type: "string" },
|
|
330
|
+
path: { type: "string" },
|
|
331
|
+
summary: { type: "string" },
|
|
332
|
+
tool: { type: "string", description: "The tool that calls it." },
|
|
333
|
+
},
|
|
334
|
+
required: ["method", "path", "tool"],
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
revision: { type: "string", description: "Pinned index revision." },
|
|
338
|
+
},
|
|
339
|
+
required: ["operations", "revision"],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: tool("details"),
|
|
344
|
+
description: `Read one ${title} API operation's request contract: path, query, and body parameters with types, enums, and required names. Reads no account data.`,
|
|
345
|
+
annotations: READ,
|
|
346
|
+
inputSchema: closed({
|
|
347
|
+
method: { type: "string", enum: [...index.methods], description: "The operation's HTTP method." },
|
|
348
|
+
path: { ...PATH, description: "The path template from search, or a concrete path." },
|
|
349
|
+
param: { type: "string", minLength: 1, maxLength: 200, description: "Return only this parameter." },
|
|
350
|
+
}, ["method", "path"]),
|
|
351
|
+
outputSchema: {
|
|
352
|
+
type: "object",
|
|
353
|
+
properties: {
|
|
354
|
+
method: { type: "string" },
|
|
355
|
+
path: { type: "string" },
|
|
356
|
+
tool: { type: "string", description: "The tool that calls it." },
|
|
357
|
+
parameters: { type: "array", description: "Path and query parameters." },
|
|
358
|
+
body: {
|
|
359
|
+
type: "object",
|
|
360
|
+
description: "Body content type and schema; truncated: true marks unexpanded levels.",
|
|
361
|
+
},
|
|
362
|
+
revision: { type: "string" },
|
|
363
|
+
},
|
|
364
|
+
required: ["method", "path", "tool", "parameters", "revision"],
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
name: tool("read"),
|
|
369
|
+
description: `Call a ${title} API GET${head ? " or HEAD" : ""} operation${readPosts.length ? ", or a reviewed read-only POST" : ""}. ` +
|
|
370
|
+
"Arguments are checked against the pinned index before anything is sent.",
|
|
371
|
+
annotations: READ,
|
|
372
|
+
inputSchema: closed({
|
|
373
|
+
path: PATH,
|
|
374
|
+
...(readMethods.length > 1
|
|
375
|
+
? {
|
|
376
|
+
method: {
|
|
377
|
+
type: "string",
|
|
378
|
+
enum: readMethods,
|
|
379
|
+
description: "GET by default; another method only for an operation search routes here.",
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
: {}),
|
|
383
|
+
...(readPosts.length
|
|
384
|
+
? {
|
|
385
|
+
body: {
|
|
386
|
+
type: vendor.textBodies ? ["object", "string"] : "object",
|
|
387
|
+
description: `Body for a reviewed read-only POST${vendor.textBodies ? "; a string for a text-framed operation" : ""}.`,
|
|
388
|
+
},
|
|
389
|
+
}
|
|
390
|
+
: {}),
|
|
391
|
+
query: QUERY,
|
|
392
|
+
...HEADERS,
|
|
393
|
+
select: SELECT,
|
|
394
|
+
}, ["path"]),
|
|
395
|
+
outputSchema: envelope(),
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: tool("write"),
|
|
399
|
+
description: `Call a ${title} API write (${writeMethods.join(", ")}); method and path are stated for approval. ` +
|
|
400
|
+
`Checked against the pinned index first.${keyed.length ? ` Pass idempotencyKey to retry a ${keyedText} safely${keyed.length < writeMethods.length ? "; a DELETE sends no key" : ""}.` : ""}`,
|
|
401
|
+
annotations: DESTRUCTIVE,
|
|
402
|
+
inputSchema: closed({
|
|
403
|
+
method: { type: "string", enum: writeMethods, description: "The operation's HTTP method." },
|
|
404
|
+
path: PATH,
|
|
405
|
+
query: QUERY,
|
|
406
|
+
body: {
|
|
407
|
+
type: vendor.textBodies ? ["object", "array", "string"] : ["object", "array"],
|
|
408
|
+
description: `Request body as JSON${vendor.bodyHint ? `; ${vendor.bodyHint}` : ""}.`,
|
|
409
|
+
},
|
|
410
|
+
...HEADERS,
|
|
411
|
+
...(vendor.idempotencyHeader
|
|
412
|
+
? {
|
|
413
|
+
idempotencyKey: {
|
|
414
|
+
type: "string",
|
|
415
|
+
minLength: 1,
|
|
416
|
+
maxLength: IDEMPOTENCY_KEY_MAX_LENGTH,
|
|
417
|
+
description: `Reuse to retry this exact ${keyedText}; one is generated and returned when omitted.`,
|
|
418
|
+
},
|
|
419
|
+
}
|
|
420
|
+
: {}),
|
|
421
|
+
select: SELECT,
|
|
422
|
+
}, ["method", "path"]),
|
|
423
|
+
outputSchema: envelope(vendor.idempotencyHeader
|
|
424
|
+
? {
|
|
425
|
+
idempotencyKey: {
|
|
426
|
+
type: "string",
|
|
427
|
+
description: "The key sent; reuse it, with the same arguments, while the vendor retains it.",
|
|
428
|
+
},
|
|
429
|
+
}
|
|
430
|
+
: {}),
|
|
431
|
+
},
|
|
432
|
+
];
|
|
433
|
+
const resolve = (args, method) => {
|
|
434
|
+
const path = vendor.path ? vendor.path(String(args["path"])) : String(args["path"]);
|
|
435
|
+
const { op, params } = index.resolve(method, path);
|
|
436
|
+
const call = {
|
|
437
|
+
method: op.method,
|
|
438
|
+
path,
|
|
439
|
+
op,
|
|
440
|
+
params,
|
|
441
|
+
query: isRecord(args["query"]) ? args["query"] : {},
|
|
442
|
+
body: args["body"],
|
|
443
|
+
...headersOf(args),
|
|
444
|
+
};
|
|
445
|
+
return vendor.scope ? vendor.scope(call) : call;
|
|
446
|
+
};
|
|
447
|
+
const search = (args) => ({
|
|
448
|
+
operations: index
|
|
449
|
+
.search(String(args["query"]), {
|
|
450
|
+
...(typeof args["method"] === "string" ? { method: args["method"] } : {}),
|
|
451
|
+
limit: typeof args["limit"] === "number" ? args["limit"] : 10,
|
|
452
|
+
})
|
|
453
|
+
.map((op) => ({
|
|
454
|
+
method: op.method,
|
|
455
|
+
path: op.path,
|
|
456
|
+
operationId: op.operationId,
|
|
457
|
+
summary: op.summary,
|
|
458
|
+
tag: op.tag,
|
|
459
|
+
tool: routeOf(op),
|
|
460
|
+
})),
|
|
461
|
+
revision: index.revision,
|
|
462
|
+
});
|
|
463
|
+
const details = (args) => {
|
|
464
|
+
const method = String(args["method"]), path = String(args["path"]);
|
|
465
|
+
const op = index.operation(method, path) ?? index.resolve(method, path).op;
|
|
466
|
+
return {
|
|
467
|
+
...index.contract(op, typeof args["param"] === "string" ? args["param"] : undefined),
|
|
468
|
+
tool: routeOf(op),
|
|
469
|
+
revision: index.revision,
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
const execute = (args, writing) => checked(() => {
|
|
473
|
+
const call = resolve(args, (typeof args["method"] === "string" ? args["method"] : "GET"));
|
|
474
|
+
if (writing && (call.method === "GET" || call.method === "HEAD"))
|
|
475
|
+
invalid(`${call.method} operations are reads; call them with ${tool("read")}.`);
|
|
476
|
+
if (!writing && !reads(call.op))
|
|
477
|
+
invalid(`${call.method} ${call.op.path} is not a reviewed read; call it with ${tool("write")}.`);
|
|
478
|
+
if (!writing && (call.method === "GET" || call.method === "HEAD") && call.body !== undefined)
|
|
479
|
+
invalid(`A ${call.method} operation takes query, not body.`);
|
|
480
|
+
guard(call);
|
|
481
|
+
const framing = index.bodyType(call.op);
|
|
482
|
+
if (writing && framing && /^multipart\/|octet-stream/.test(framing))
|
|
483
|
+
invalid(`${call.method} ${call.op.path} takes a ${framing} upload, which ${tool("write")} does not send.` +
|
|
484
|
+
(vendor.upload ? ` ${vendor.upload}` : ""));
|
|
485
|
+
index.check(call.op, call.query, call.body);
|
|
486
|
+
return call;
|
|
487
|
+
}).pipe(Effect.flatMap((call) => Effect.gen(function* () {
|
|
488
|
+
const header = writing && call.method !== "DELETE" ? vendor.idempotencyHeader : undefined;
|
|
489
|
+
const given = typeof args["idempotencyKey"] === "string" ? args["idempotencyKey"] : undefined;
|
|
490
|
+
if (writing && given !== undefined)
|
|
491
|
+
yield* checked(() => {
|
|
492
|
+
const key = given.replace(/^[\t\n\r ]+|[\t\n\r ]+$/g, "");
|
|
493
|
+
if (!key)
|
|
494
|
+
invalid("idempotencyKey is blank; pass a key with at least one non-whitespace character, or omit it.");
|
|
495
|
+
if (!/^[\t\x20-\x7e\x80-\xff]*$/.test(key))
|
|
496
|
+
invalid("idempotencyKey contains a character an HTTP header cannot carry (a control character, or one past Latin-1); pass a printable key, or omit it.");
|
|
497
|
+
});
|
|
498
|
+
const key = header ? (given ?? crypto.randomUUID()) : undefined;
|
|
499
|
+
if (key)
|
|
500
|
+
yield* (yield* Recovery).record({ idempotencyKey: key });
|
|
501
|
+
const classification = yield* Classification, dispatches = yield* Dispatches, signal = yield* Signal;
|
|
502
|
+
return yield* vendor.send(call).pipe(Effect.provideService(IdempotencyKey, key && header ? { header, key } : undefined), Effect.map((result) => ({ ...selected(args, result), ...(key ? { idempotencyKey: key } : {}) })), Effect.catch((failure) => {
|
|
503
|
+
if (!key || given)
|
|
504
|
+
return Effect.fail(failure);
|
|
505
|
+
const error = toConnectorCallError(failure, vendor.connection.errors, {
|
|
506
|
+
classification,
|
|
507
|
+
dispatches,
|
|
508
|
+
withholds: vendor.connection.safety.withholdsErrors,
|
|
509
|
+
signal,
|
|
510
|
+
});
|
|
511
|
+
const diagnostic = providerFailureMessage(error, `${error.message} ${header}: ${key}.`);
|
|
512
|
+
const dispatched = dispatches.some((d) => d.request?.dispatched && d.request.key === key);
|
|
513
|
+
return Effect.fail(new Refusal({
|
|
514
|
+
error: dispatched
|
|
515
|
+
? keyResendAdvice(diagnostic, key, " Reusing it with the exact original arguments retries this write without repeating it only while the vendor retains the key; after that, look the object up before retrying.")
|
|
516
|
+
: diagnostic,
|
|
517
|
+
}));
|
|
518
|
+
}));
|
|
519
|
+
})));
|
|
520
|
+
return declarations.map((declaration, i) => {
|
|
521
|
+
const spec = {
|
|
522
|
+
...(writeSteps === undefined ? {} : { writeSteps }),
|
|
523
|
+
name: declaration.name,
|
|
524
|
+
description: declaration.description,
|
|
525
|
+
input: schema(declaration.inputSchema),
|
|
526
|
+
output: schema(declaration.outputSchema),
|
|
527
|
+
run: (args) => i === 0
|
|
528
|
+
? checked(() => search(args))
|
|
529
|
+
: i === 1
|
|
530
|
+
? checked(() => details(args))
|
|
531
|
+
: execute(args, i === 3),
|
|
532
|
+
};
|
|
533
|
+
const built = i === 3
|
|
534
|
+
? write(vendor.connection, { ...spec, destructive: true })
|
|
535
|
+
: read(vendor.connection, {
|
|
536
|
+
...spec,
|
|
537
|
+
review: readPosts.map(([, path, reason]) => `${path}: ${reason}`).join("\n") ||
|
|
538
|
+
"Only GET and HEAD operations are admitted.",
|
|
539
|
+
});
|
|
540
|
+
Object.assign(built, {
|
|
541
|
+
inputSchema: catalogOrder(built.inputSchema, declaration.inputSchema),
|
|
542
|
+
outputSchema: catalogOrder(built.outputSchema, declaration.outputSchema),
|
|
543
|
+
});
|
|
544
|
+
return built;
|
|
545
|
+
});
|
|
546
|
+
}
|