@zackbart/connecta 0.34.1 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +418 -0
- package/README.md +65 -105
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2044 -1161
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +751 -281
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +29 -7
- package/dist/connectors/api-connector.js +145 -57
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/remote-mcp.d.ts +10 -30
- package/dist/connectors/remote-mcp.js +955 -563
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +2 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +227 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +379 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +218 -85
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +531 -406
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/contract.d.ts +8 -3
- package/dist/operator-ui/generated.js +4 -4
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +65 -31
- package/dist/provider.js +116 -39
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +427 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +6 -210
- package/dist/providers/_shared/google/workspace.js +6 -425
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/kit.js +146 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +1 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +15 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +5 -22
- package/dist/providers/cloudflare/index.js +79 -197
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +4 -3
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +801 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1023 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +1 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +4 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/quickbooks/api.js +299 -0
- package/dist/providers/quickbooks/index.d.ts +13 -0
- package/dist/providers/quickbooks/index.js +93 -0
- package/dist/providers/quickbooks/lib/query.js +118 -0
- package/dist/providers/quickbooks/lib/writes.js +168 -0
- package/dist/providers/quickbooks/safety.js +222 -0
- package/dist/providers/quickbooks/skill.generated.js +5 -0
- package/dist/providers/quickbooks/tools.js +117 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +733 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +5 -27
- package/dist/providers/stripe/index.js +74 -210
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -11
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +17 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +9 -25
- package/dist/providers/vercel/index.js +65 -260
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +10 -5
- package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +21 -15
- package/dist/registry.js +409 -376
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +6 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +21 -20
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +552 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +322 -223
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +9 -109
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +0 -1
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +28 -7
- package/documentation/architecture.md +2409 -255
- package/documentation/auth.md +867 -114
- package/documentation/code-mode.md +49 -16
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +129 -144
- package/documentation/meta-tools.md +409 -32
- package/documentation/operating.md +27 -3
- package/documentation/operator-tests.md +3 -0
- package/documentation/operator-ui.md +84 -48
- package/documentation/provider-auth-migration.md +16 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +1347 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +30 -50
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -138
- package/dist/catalog-drift.js +0 -578
- package/dist/operator-ui/agents.d.ts +0 -74
- package/dist/operator-ui/agents.js +0 -104
- package/dist/operator-ui/config-summary.js +0 -565
- package/dist/operator-ui/connection-map.js +0 -376
- package/dist/operator-ui/endpoints.js +0 -221
- package/dist/operator-ui/fix-prompts.d.ts +0 -18
- package/dist/operator-ui/fix-prompts.js +0 -87
- package/dist/operator-ui/health.js +0 -52
- package/dist/operator-ui/member-page.js +0 -97
- package/dist/operator-ui/overview.js +0 -123
- package/dist/operator-ui/setup-commands.js +0 -51
- package/dist/operator-ui/view.d.ts +0 -401
- package/dist/operator-ui/view.js +0 -705
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/dispatch.js +0 -41
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/tools.js +0 -569
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -45
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1097
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/intercom/index.d.ts +0 -24
- package/dist/providers/intercom/index.js +0 -121
- package/dist/providers/intercom/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -537
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -59
- package/dist/providers/mixpanel/index.js +0 -472
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -67
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -51
- package/dist/providers/revenuecat/index.js +0 -697
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Shared REST call shapes, pinned-schema conversion and indexed vendor declarations.
|
|
2
|
+
import { Effect, Schema } from "effect";
|
|
3
|
+
import { ConnectorCallError } from "../../../errors.js";
|
|
4
|
+
import { Refusal } from "../../../http/errors.js";
|
|
5
|
+
import { Integer, isMinChars, isMaxChars } from "../../../http/json-schema.js";
|
|
6
|
+
import { vendor as declareVendor, get, head, post, put, patch, del, } from "../../../http/vendor.js";
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function invalid(message) {
|
|
11
|
+
throw new ConnectorCallError("invalid_args", message);
|
|
12
|
+
}
|
|
13
|
+
export function checked(run) {
|
|
14
|
+
return Effect.try({ try: run, catch: (error) => error }).pipe(Effect.catch((error) => error instanceof ConnectorCallError ? Effect.fail(new Refusal({ error })) : Effect.die(error)));
|
|
15
|
+
}
|
|
16
|
+
/** Index matches retain encoded segments; HttpApiClient uses encodeURIComponent. */
|
|
17
|
+
export function typedParams(params) {
|
|
18
|
+
return Object.fromEntries(Object.entries(params).map(([name, encoded]) => {
|
|
19
|
+
let value;
|
|
20
|
+
try {
|
|
21
|
+
value = decodeURIComponent(encoded);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return invalid(`Indexed path parameter ${name} is not valid URI encoding.`);
|
|
25
|
+
}
|
|
26
|
+
if (encodeURIComponent(value) !== encoded)
|
|
27
|
+
invalid(`Indexed path parameter ${name} does not round-trip through the typed path encoder.`);
|
|
28
|
+
return [name, value];
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
/** Convert the JSON forms published by pinned indexes. No vendor encoders run here. */
|
|
32
|
+
export function schema(node) {
|
|
33
|
+
const n = node;
|
|
34
|
+
let out;
|
|
35
|
+
const types = n["type"];
|
|
36
|
+
if (n["truncated"] === true)
|
|
37
|
+
out = Schema.Unknown;
|
|
38
|
+
else if (Array.isArray(n["enum"]))
|
|
39
|
+
out = n["enum"].length === 1 ? Schema.Literal(n["enum"][0]) : Schema.Literals(n["enum"]);
|
|
40
|
+
else if (Array.isArray(n["anyOf"]))
|
|
41
|
+
out = Schema.Union(n["anyOf"].map((v) => schema(v)));
|
|
42
|
+
else if (Array.isArray(types))
|
|
43
|
+
out = Schema.Union(types.map((type) => schema({ ...n, type, description: undefined })));
|
|
44
|
+
else if (types === "object" || isRecord(n["properties"])) {
|
|
45
|
+
const required = n["required"];
|
|
46
|
+
const fields = Object.fromEntries(Object.entries((n["properties"] ?? {})).map(([key, value]) => {
|
|
47
|
+
const part = schema(value);
|
|
48
|
+
const annotated = part.ast._tag === "Unknown" && isRecord(value) && typeof value["description"] === "string"
|
|
49
|
+
? part.pipe(Schema.annotateKey({ description: value["description"] }))
|
|
50
|
+
: part;
|
|
51
|
+
return [key, required?.includes(key) ? annotated : Schema.optionalKey(annotated)];
|
|
52
|
+
}));
|
|
53
|
+
out =
|
|
54
|
+
n["properties"] && n["additionalProperties"] !== true
|
|
55
|
+
? Schema.Struct(fields)
|
|
56
|
+
: Schema.StructWithRest(Schema.Struct(fields), [
|
|
57
|
+
Schema.Record(Schema.String, isRecord(n["additionalProperties"]) ? schema(n["additionalProperties"]) : Schema.Unknown),
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
else if (types === "array") {
|
|
61
|
+
let array = Schema.Array(schema((n["items"] ?? {})));
|
|
62
|
+
if (typeof n["minItems"] === "number")
|
|
63
|
+
array = array.check(Schema.isMinLength(n["minItems"]));
|
|
64
|
+
if (typeof n["maxItems"] === "number")
|
|
65
|
+
array = array.check(Schema.isMaxLength(n["maxItems"]));
|
|
66
|
+
if (n["uniqueItems"] === true)
|
|
67
|
+
array = array.check(Schema.isUnique());
|
|
68
|
+
out = array;
|
|
69
|
+
}
|
|
70
|
+
else if (types === "string") {
|
|
71
|
+
let string = Schema.String;
|
|
72
|
+
if (typeof n["minLength"] === "number")
|
|
73
|
+
string = string.check(isMinChars(n["minLength"]));
|
|
74
|
+
if (typeof n["maxLength"] === "number")
|
|
75
|
+
string = string.check(isMaxChars(n["maxLength"]));
|
|
76
|
+
out = string;
|
|
77
|
+
}
|
|
78
|
+
else if (types === "integer" || types === "number") {
|
|
79
|
+
let number = types === "integer" ? Integer : Schema.Finite;
|
|
80
|
+
if (typeof n["minimum"] === "number")
|
|
81
|
+
number = number.check(Schema.isGreaterThanOrEqualTo(n["minimum"]));
|
|
82
|
+
if (typeof n["maximum"] === "number")
|
|
83
|
+
number = number.check(Schema.isLessThanOrEqualTo(n["maximum"]));
|
|
84
|
+
out = number;
|
|
85
|
+
}
|
|
86
|
+
else
|
|
87
|
+
out = types === "boolean" ? Schema.Boolean : types === "null" ? Schema.Null : Schema.Unknown;
|
|
88
|
+
if (n["nullable"] === true)
|
|
89
|
+
out = Schema.Union([out, Schema.Null]);
|
|
90
|
+
if (n["writeOnly"] === true)
|
|
91
|
+
out = out.annotate({ writeOnly: true });
|
|
92
|
+
return typeof n["description"] === "string" ? out.annotate({ description: n["description"] }) : out;
|
|
93
|
+
}
|
|
94
|
+
/** Declare each indexed operation once; request schema subtrees stay suspended until manifest/client use. */
|
|
95
|
+
export function indexedVendor(name, index, named = []) {
|
|
96
|
+
for (const op of index.ops) {
|
|
97
|
+
if ([...op.path.matchAll(/\{([^}]*)\}/g)].some((match) => !/^[A-Za-z0-9_-]+$/.test(match[1])) ||
|
|
98
|
+
/[{}]/.test(op.path.replace(/\{[^}]*\}/g, "")))
|
|
99
|
+
throw new Error(`Indexed operation ${op.method} ${op.path} has an unsupported path parameter name.`);
|
|
100
|
+
}
|
|
101
|
+
const constructors = { GET: get, HEAD: head, POST: post, PUT: put, PATCH: patch, DELETE: del };
|
|
102
|
+
const overrides = new Map(named.map((op) => {
|
|
103
|
+
const declared = op;
|
|
104
|
+
return [`${declared.method} ${declared.path.replace(/:([^/]+)/g, "{$1}")}`, op];
|
|
105
|
+
}));
|
|
106
|
+
for (const key of overrides.keys())
|
|
107
|
+
if (!index.ops.some((op) => `${op.method} ${op.path}` === key))
|
|
108
|
+
throw new Error(`Named operation ${key} is absent from the pinned index.`);
|
|
109
|
+
const cache = new Map();
|
|
110
|
+
const cached = (node) => {
|
|
111
|
+
const key = JSON.stringify(node);
|
|
112
|
+
let found = cache.get(key);
|
|
113
|
+
if (!found) {
|
|
114
|
+
found = schema(node);
|
|
115
|
+
cache.set(key, found);
|
|
116
|
+
}
|
|
117
|
+
return found;
|
|
118
|
+
};
|
|
119
|
+
const build = () => {
|
|
120
|
+
const endpoints = index.ops.map((op) => overrides.get(`${op.method} ${op.path}`) ??
|
|
121
|
+
constructors[op.method]("indexed" + op.row,
|
|
122
|
+
// These declarations feed only the manifest. The live client uses `named` below.
|
|
123
|
+
op.path, {
|
|
124
|
+
// Parameter names come directly from the vendor template; hatch validation still uses index.check.
|
|
125
|
+
params: Schema.Struct(Object.fromEntries([...op.path.matchAll(/\{([^}]+)\}/g)].map((match) => [match[1], Schema.String]))),
|
|
126
|
+
query: Schema.suspend(() => {
|
|
127
|
+
const parameters = index.contract(op).parameters.filter((p) => p.in === "query");
|
|
128
|
+
return Schema.Struct(Object.fromEntries(parameters.map((p) => [p.name, p.required ? cached(p.schema) : Schema.optionalKey(cached(p.schema))])));
|
|
129
|
+
}),
|
|
130
|
+
...(op.method === "GET" || op.method === "HEAD"
|
|
131
|
+
? {}
|
|
132
|
+
: { payload: Schema.suspend(() => cached(index.contract(op).body?.schema ?? {})) }),
|
|
133
|
+
success: Schema.Unknown,
|
|
134
|
+
}));
|
|
135
|
+
if (!endpoints.length)
|
|
136
|
+
throw new Error("An indexed vendor needs operations.");
|
|
137
|
+
return declareVendor(name, endpoints[0], ...endpoints.slice(1)).api;
|
|
138
|
+
};
|
|
139
|
+
if (!named.length)
|
|
140
|
+
throw new Error("An indexed vendor declares at least its typed credential probe.");
|
|
141
|
+
let built;
|
|
142
|
+
return {
|
|
143
|
+
...declareVendor(name, named[0], ...named.slice(1)),
|
|
144
|
+
indexed: { operations: index.ops.map((op) => `${op.method} ${op.path}`).sort(), api: () => (built ??= build()) },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { canonicalJson } from "../../../canonical-json.js";
|
|
2
|
+
import { parseIndexDetails } from "./compact-index.js";
|
|
1
3
|
// The pinned operation index a REST connector validates against before it
|
|
2
4
|
// sends anything. `scripts/generate-openapi.mjs` compiles a vendor's OpenAPI
|
|
3
5
|
// document into `OpenApiData` (search rows plus request-side details as one
|
|
@@ -114,16 +116,26 @@ export class OperationIndex {
|
|
|
114
116
|
#vendor;
|
|
115
117
|
#segments;
|
|
116
118
|
#slashParams;
|
|
119
|
+
#uriTemplates;
|
|
120
|
+
get pathMatching() {
|
|
121
|
+
return this.#uriTemplates ? "uri-template" : "segments";
|
|
122
|
+
}
|
|
117
123
|
#details;
|
|
124
|
+
#fields;
|
|
125
|
+
#requestSchemas;
|
|
118
126
|
#words;
|
|
119
127
|
/**
|
|
120
128
|
* `vendor` is the tool-name prefix; `title` the display name in refusals.
|
|
121
129
|
* `slashParams` names path parameters whose values may carry an encoded
|
|
122
130
|
* "/" (`%2F`), such as an object key, and only as the path's last segment.
|
|
131
|
+
* `pathMatching: "uri-template"` opts into decoded literals and parameters
|
|
132
|
+
* embedded in a segment, such as Google's `{id}:batchUpdate`. The default
|
|
133
|
+
* retains exact literal segments and whole-segment parameters.
|
|
123
134
|
*/
|
|
124
135
|
constructor(data, names) {
|
|
125
136
|
const { title } = names;
|
|
126
137
|
this.#slashParams = new Set(names.slashParams ?? []);
|
|
138
|
+
this.#uriTemplates = names.pathMatching === "uri-template";
|
|
127
139
|
this.#data = data;
|
|
128
140
|
this.#title = title;
|
|
129
141
|
this.#vendor = names.vendor;
|
|
@@ -145,6 +157,46 @@ export class OperationIndex {
|
|
|
145
157
|
this.#segments = this.ops.map((op) => op.path.slice(1).split("/"));
|
|
146
158
|
this.methods = [...METHODS].filter((method) => this.ops.some((op) => op.method === method));
|
|
147
159
|
}
|
|
160
|
+
/** Content binding for discovery continuations; labels alone never identify an index. */
|
|
161
|
+
async cursorRevision() {
|
|
162
|
+
const bytes = new TextEncoder().encode(canonicalJson([
|
|
163
|
+
this.#data,
|
|
164
|
+
this.revision,
|
|
165
|
+
[...this.#slashParams].sort(),
|
|
166
|
+
...(this.#uriTemplates ? ["uri-template"] : []),
|
|
167
|
+
]));
|
|
168
|
+
return Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
169
|
+
}
|
|
170
|
+
/** The request and response properties this operation declares; none when the index carries no fields. */
|
|
171
|
+
fields(op) {
|
|
172
|
+
if (!this.#fields) {
|
|
173
|
+
const data = this.#data.fields;
|
|
174
|
+
if (typeof data === "string") {
|
|
175
|
+
// Generated REST indexes share response paths and complete rows. Kit
|
|
176
|
+
// vendor indexes retain their directly declared body/response fields.
|
|
177
|
+
const { p, d, o } = JSON.parse(data);
|
|
178
|
+
const rows = d.map((ids) => ids.map((id) => ({ in: "response", path: p[id] })));
|
|
179
|
+
this.#fields = o.map((id) => rows[id]);
|
|
180
|
+
}
|
|
181
|
+
else
|
|
182
|
+
this.#fields = data ?? [];
|
|
183
|
+
}
|
|
184
|
+
return this.#fields[op.row] ?? [];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The names a result's first dot-path segment may take: the success
|
|
188
|
+
* response's own properties, or its items' properties when it is an array.
|
|
189
|
+
* Undefined when the operation declares no response contract.
|
|
190
|
+
*/
|
|
191
|
+
responseRoots(op) {
|
|
192
|
+
const paths = this.fields(op).filter((field) => field.in === "response");
|
|
193
|
+
if (paths.length === 0)
|
|
194
|
+
return undefined;
|
|
195
|
+
return new Set(paths.map((field) => field.path
|
|
196
|
+
.replace(/^\[\]\./, "")
|
|
197
|
+
.split(".")[0]
|
|
198
|
+
.replace(/\[\]$/, "")));
|
|
199
|
+
}
|
|
148
200
|
/** The operation with this exact method and path template, if any. */
|
|
149
201
|
operation(method, template) {
|
|
150
202
|
return this.ops.find((op) => op.method === method && op.path === template);
|
|
@@ -165,11 +217,29 @@ export class OperationIndex {
|
|
|
165
217
|
return;
|
|
166
218
|
let score = 0;
|
|
167
219
|
for (const [index, part] of template.entries()) {
|
|
168
|
-
if (
|
|
220
|
+
if (!this.#uriTemplates) {
|
|
221
|
+
if (part.startsWith("{"))
|
|
222
|
+
continue;
|
|
223
|
+
if (part !== segments[index])
|
|
224
|
+
return;
|
|
225
|
+
score += 1;
|
|
169
226
|
continue;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
227
|
+
}
|
|
228
|
+
const parameter = /^(.*)\{([^{}]+)\}(.*)$/.exec(part);
|
|
229
|
+
const value = decodeURIComponent(segments[index]);
|
|
230
|
+
if (parameter) {
|
|
231
|
+
if (!value.startsWith(parameter[1]) ||
|
|
232
|
+
!value.endsWith(parameter[3]) ||
|
|
233
|
+
value.length <= parameter[1].length + parameter[3].length)
|
|
234
|
+
return;
|
|
235
|
+
if (parameter[1] || parameter[3])
|
|
236
|
+
score += 0.5;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
if (part !== value)
|
|
240
|
+
return;
|
|
241
|
+
score += 1;
|
|
242
|
+
}
|
|
173
243
|
}
|
|
174
244
|
if (score > best.score)
|
|
175
245
|
best = { score, rows: [row] };
|
|
@@ -195,9 +265,34 @@ export class OperationIndex {
|
|
|
195
265
|
}
|
|
196
266
|
const params = {};
|
|
197
267
|
template.forEach((part, index) => {
|
|
198
|
-
if (
|
|
199
|
-
|
|
268
|
+
if (!this.#uriTemplates) {
|
|
269
|
+
if (part.startsWith("{"))
|
|
270
|
+
params[part.slice(1, -1)] = segments[index];
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const parameter = /^(.*)\{([^{}]+)\}(.*)$/.exec(part);
|
|
274
|
+
if (parameter) {
|
|
275
|
+
const raw = segments[index];
|
|
276
|
+
const prefix = parameter[1], suffix = parameter[3];
|
|
277
|
+
params[parameter[2]] =
|
|
278
|
+
raw.startsWith(prefix) && raw.endsWith(suffix)
|
|
279
|
+
? raw.slice(prefix.length, suffix ? -suffix.length : undefined)
|
|
280
|
+
: encodeURIComponent(decodeURIComponent(raw).slice(prefix.length, suffix ? -suffix.length : undefined));
|
|
281
|
+
}
|
|
200
282
|
});
|
|
283
|
+
const issues = [];
|
|
284
|
+
const repair = [];
|
|
285
|
+
for (const [name, at, , schema] of this.#row(op)?.[0] ?? []) {
|
|
286
|
+
if (at === "path" && params[name] !== undefined)
|
|
287
|
+
this.#check(schema, decodeURIComponent(params[name]), `/path/${name}`, issues, repair);
|
|
288
|
+
}
|
|
289
|
+
if (issues.length) {
|
|
290
|
+
const shown = issues
|
|
291
|
+
.slice(0, 5)
|
|
292
|
+
.map((issue) => `${issue.path} (${issue.code}: ${issue.expected})`)
|
|
293
|
+
.join("; ");
|
|
294
|
+
invalid(`Not sent: the path does not match ${op.method} ${op.path} in the pinned API index: ${shown}. Substitute real ids matching ${this.#vendorTool("details")}.${issues.some((issue) => issue.expected?.includes("safe integer")) ? " Integer ids use canonical ASCII decimal notation within the safe-integer range." : ""}`, issues.slice(0, MAX_ISSUES));
|
|
295
|
+
}
|
|
201
296
|
return { op, params };
|
|
202
297
|
}
|
|
203
298
|
/** Up to five operations whose paths most resemble a missed one. */
|
|
@@ -231,6 +326,9 @@ export class OperationIndex {
|
|
|
231
326
|
* the path, summary, operationId, or tag (plural-insensitive, prefixes of
|
|
232
327
|
* three letters count), and path matches weigh most. A query that starts
|
|
233
328
|
* with "/" also matches the templates that fit it.
|
|
329
|
+
* Standard hatches opt into partial matches, ordered by matched term count
|
|
330
|
+
* before relevance, including the person/people alias. Legacy callers keep
|
|
331
|
+
* the all-term contract.
|
|
234
332
|
*/
|
|
235
333
|
search(query, options) {
|
|
236
334
|
this.#words ??= this.ops.map((op) => ({
|
|
@@ -261,6 +359,12 @@ export class OperationIndex {
|
|
|
261
359
|
if (set.has(term))
|
|
262
360
|
return 2;
|
|
263
361
|
for (const word of set) {
|
|
362
|
+
if (options.partial && ((term === "person" && word === "people") || (term === "people" && word === "person")))
|
|
363
|
+
return 2;
|
|
364
|
+
if (options.partial &&
|
|
365
|
+
((term.endsWith("y") && word === `${term.slice(0, -1)}ies`) ||
|
|
366
|
+
(word.endsWith("y") && term === `${word.slice(0, -1)}ies`)))
|
|
367
|
+
return 2;
|
|
264
368
|
if (word === `${term}s` || term === `${word}s` || word === `${term}es` || term === `${word}es`)
|
|
265
369
|
return 2;
|
|
266
370
|
if (term.length >= 3 && word.startsWith(term))
|
|
@@ -280,12 +384,13 @@ export class OperationIndex {
|
|
|
280
384
|
matched += 1;
|
|
281
385
|
score += path * 3 + summary * 1.5 + id;
|
|
282
386
|
}
|
|
283
|
-
if (!fits.has(op.row) && (terms.length === 0 || matched < terms.length))
|
|
387
|
+
if (!fits.has(op.row) && (terms.length === 0 || matched < (options.partial ? 1 : terms.length)))
|
|
284
388
|
return [];
|
|
285
|
-
return [{ op, score }];
|
|
389
|
+
return [{ op, score, matched }];
|
|
286
390
|
});
|
|
287
391
|
return ranked
|
|
288
|
-
.sort((a, b) => b.
|
|
392
|
+
.sort((a, b) => (options.partial ? b.matched - a.matched : 0) ||
|
|
393
|
+
b.score - a.score ||
|
|
289
394
|
a.op.path.split("/").length - b.op.path.split("/").length ||
|
|
290
395
|
a.op.path.localeCompare(b.op.path))
|
|
291
396
|
.slice(0, options.limit)
|
|
@@ -302,8 +407,16 @@ export class OperationIndex {
|
|
|
302
407
|
const declared = row ? row[0] : [];
|
|
303
408
|
// A generator may omit plain-string path parameters (`pathParams:
|
|
304
409
|
// "typed"`); the template still names them, so the contract does too.
|
|
305
|
-
const implied = this.#segments[op.row].
|
|
306
|
-
|
|
410
|
+
const implied = this.#segments[op.row].flatMap((part) => {
|
|
411
|
+
const name = this.#uriTemplates
|
|
412
|
+
? /\{([^{}]+)\}/.exec(part)?.[1]
|
|
413
|
+
: part.startsWith("{")
|
|
414
|
+
? part.slice(1, -1)
|
|
415
|
+
: undefined;
|
|
416
|
+
return name !== undefined && !declared.some(([key, at]) => at === "path" && key === name)
|
|
417
|
+
? [[name, "path", 1, { t: "string" }]]
|
|
418
|
+
: [];
|
|
419
|
+
});
|
|
307
420
|
const parameters = [...implied, ...declared]
|
|
308
421
|
.filter(([name]) => only === undefined || name === only)
|
|
309
422
|
.map(([name, at, required, schema, description]) => ({
|
|
@@ -317,7 +430,22 @@ export class OperationIndex {
|
|
|
317
430
|
if (row && row[1] !== 0) {
|
|
318
431
|
const [contentType, schema] = row[1];
|
|
319
432
|
const node = this.#deref(schema);
|
|
320
|
-
|
|
433
|
+
const full = this.#requestSchema(op);
|
|
434
|
+
if (full && only === undefined) {
|
|
435
|
+
body = { contentType, ...(row[1][2] === 1 ? { required: true } : {}), schema: full };
|
|
436
|
+
}
|
|
437
|
+
else if (full && only !== undefined && full["properties"]?.[only]) {
|
|
438
|
+
body = {
|
|
439
|
+
contentType,
|
|
440
|
+
schema: {
|
|
441
|
+
type: "object",
|
|
442
|
+
properties: { [only]: full["properties"][only] },
|
|
443
|
+
...(Array.isArray(full["required"]) && full["required"].includes(only) ? { required: [only] } : {}),
|
|
444
|
+
...(full["$defs"] ? { $defs: full["$defs"] } : {}),
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
else if (only !== undefined && node.p?.[only] !== undefined) {
|
|
321
449
|
body = {
|
|
322
450
|
contentType,
|
|
323
451
|
schema: {
|
|
@@ -402,8 +530,40 @@ export class OperationIndex {
|
|
|
402
530
|
#vendorTool(kind) {
|
|
403
531
|
return `${this.#vendor}_api_${kind}`;
|
|
404
532
|
}
|
|
533
|
+
#requestSchema(op) {
|
|
534
|
+
if (!this.#data.requestSchemas)
|
|
535
|
+
return undefined;
|
|
536
|
+
this.#requestSchemas ??= JSON.parse(this.#data.requestSchemas);
|
|
537
|
+
const graph = this.#requestSchemas;
|
|
538
|
+
const root = graph.roots[op.row];
|
|
539
|
+
if (!root)
|
|
540
|
+
return undefined;
|
|
541
|
+
const definitions = {};
|
|
542
|
+
const visit = (value) => {
|
|
543
|
+
if (!value || typeof value !== "object")
|
|
544
|
+
return;
|
|
545
|
+
if (Array.isArray(value)) {
|
|
546
|
+
for (const child of value)
|
|
547
|
+
visit(child);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const object = value;
|
|
551
|
+
if (typeof object["$ref"] === "string" && object["$ref"].startsWith("#/$defs/")) {
|
|
552
|
+
const name = object["$ref"].slice(8);
|
|
553
|
+
if (!Object.hasOwn(definitions, name)) {
|
|
554
|
+
definitions[name] = graph.definitions[name] ?? {};
|
|
555
|
+
visit(definitions[name]);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
for (const child of Object.values(object))
|
|
559
|
+
visit(child);
|
|
560
|
+
};
|
|
561
|
+
visit(root);
|
|
562
|
+
const resolved = typeof root["$ref"] === "string" ? graph.definitions[root["$ref"].slice(8)] : root;
|
|
563
|
+
return { ...resolved, ...(Object.keys(definitions).length ? { $defs: definitions } : {}) };
|
|
564
|
+
}
|
|
405
565
|
#row(op) {
|
|
406
|
-
this.#details ??=
|
|
566
|
+
this.#details ??= parseIndexDetails(this.#data);
|
|
407
567
|
const row = this.#details.o[op.row];
|
|
408
568
|
return row === 0 ? undefined : row;
|
|
409
569
|
}
|
|
@@ -427,6 +587,12 @@ export class OperationIndex {
|
|
|
427
587
|
out["format"] = resolved.f;
|
|
428
588
|
if (resolved.n === 1)
|
|
429
589
|
out["nullable"] = true;
|
|
590
|
+
if (resolved.l !== undefined)
|
|
591
|
+
out["minimum"] = resolved.l;
|
|
592
|
+
if (resolved.u !== undefined)
|
|
593
|
+
out["maximum"] = resolved.u;
|
|
594
|
+
if (resolved.deprecated === true)
|
|
595
|
+
out["deprecated"] = true;
|
|
430
596
|
if (resolved.a)
|
|
431
597
|
out["anyOf"] = resolved.a.map((branch) => this.#schema(branch));
|
|
432
598
|
if (resolved.p) {
|
|
@@ -446,6 +612,35 @@ export class OperationIndex {
|
|
|
446
612
|
if (issues.length >= MAX_ISSUES)
|
|
447
613
|
return;
|
|
448
614
|
const schema = this.#deref(node);
|
|
615
|
+
const path = at.startsWith("/path/");
|
|
616
|
+
const types = typesOf(schema);
|
|
617
|
+
if (path && !schema.a && !types.includes("string") && (types.includes("integer") || types.includes("number"))) {
|
|
618
|
+
const integer = types.includes("integer") && !types.includes("number");
|
|
619
|
+
const numeric = typeof value === "string" ? Number(value) : NaN;
|
|
620
|
+
const syntax = integer ? /^(?:0|-?[1-9][0-9]*)$/ : /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/;
|
|
621
|
+
if (typeof value !== "string" ||
|
|
622
|
+
!syntax.test(value) ||
|
|
623
|
+
Object.is(numeric, -0) ||
|
|
624
|
+
!Number.isFinite(numeric) ||
|
|
625
|
+
Math.abs(numeric) > Number.MAX_SAFE_INTEGER ||
|
|
626
|
+
(integer && !Number.isSafeInteger(numeric))) {
|
|
627
|
+
issues.push({
|
|
628
|
+
path: at,
|
|
629
|
+
code: "type",
|
|
630
|
+
expected: integer ? "a canonical ASCII decimal safe integer" : "a canonical ASCII decimal number",
|
|
631
|
+
});
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
value = numeric;
|
|
635
|
+
}
|
|
636
|
+
else if (path && types.length && !types.includes("string") && !schema.a) {
|
|
637
|
+
if (types.includes("boolean") && (value === "true" || value === "false"))
|
|
638
|
+
value = value === "true";
|
|
639
|
+
else {
|
|
640
|
+
issues.push({ path: at, code: "type", expected: types.join(" or ") });
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
449
644
|
if (schema.x === 1)
|
|
450
645
|
return;
|
|
451
646
|
// Conflicting composed constraints (disjoint enums or types) admit nothing,
|
|
@@ -454,6 +649,28 @@ export class OperationIndex {
|
|
|
454
649
|
issues.push({ path: at, code: "enum", expected: "no value: this field's combined constraints admit none" });
|
|
455
650
|
return;
|
|
456
651
|
}
|
|
652
|
+
// Only newly opted-in bounded schemas enforce scalar numeric values;
|
|
653
|
+
// existing indexes retain their previous validation behavior. HTTP query
|
|
654
|
+
// strings may represent numbers, but must match the declared numeric type.
|
|
655
|
+
if (schema.l !== undefined || schema.u !== undefined) {
|
|
656
|
+
if (value === null && schema.n === 1)
|
|
657
|
+
return;
|
|
658
|
+
const integer = typesOf(schema).includes("integer");
|
|
659
|
+
const syntax = integer ? /^-?\d+$/ : /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
660
|
+
const numeric = typeof value === "number"
|
|
661
|
+
? value
|
|
662
|
+
: at.startsWith("/query/") && typeof value === "string" && syntax.test(value)
|
|
663
|
+
? Number(value)
|
|
664
|
+
: NaN;
|
|
665
|
+
if (!Number.isFinite(numeric) || (integer && !Number.isSafeInteger(numeric))) {
|
|
666
|
+
issues.push({ path: at, code: "type", expected: integer ? "integer" : "finite number" });
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (schema.l !== undefined && numeric < schema.l)
|
|
670
|
+
issues.push({ path: at, code: "minimum", expected: `at least ${schema.l}` });
|
|
671
|
+
if (schema.u !== undefined && numeric > schema.u)
|
|
672
|
+
issues.push({ path: at, code: "maximum", expected: `at most ${schema.u}` });
|
|
673
|
+
}
|
|
457
674
|
if (value === null || value === "")
|
|
458
675
|
return;
|
|
459
676
|
if (schema.a) {
|
|
@@ -472,7 +689,6 @@ export class OperationIndex {
|
|
|
472
689
|
}
|
|
473
690
|
return;
|
|
474
691
|
}
|
|
475
|
-
const types = typesOf(schema);
|
|
476
692
|
if (schema.e && (typeof value === "string" || typeof value === "number" || typeof value === "boolean")) {
|
|
477
693
|
if (!schema.e.some((option) => option === value || String(option) === String(value))) {
|
|
478
694
|
issues.push({ path: at, code: "enum", expected: oneOf(schema.e.map(String)) });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Match a reviewed non-JSON success media type only on its pinned operation. */
|
|
2
|
+
export function responseMedia(media, status, operation, contentType = "") {
|
|
3
|
+
if (status < 200 || status > 299)
|
|
4
|
+
return undefined;
|
|
5
|
+
const type = contentType.split(";", 1)[0].trim().toLowerCase();
|
|
6
|
+
const path = operation.template.replace(/:([^/]+)/g, "{$1}");
|
|
7
|
+
if (!type ||
|
|
8
|
+
!media[`${operation.method} ${path}`]?.some((declared) => declared === type || (declared.endsWith("/*") && type.startsWith(declared.slice(0, -1)))))
|
|
9
|
+
return undefined;
|
|
10
|
+
return /^text\/|\bxml\b|\bcsv\b|\b(?:x-ndjson|jsonl|stream\+json)\b/.test(type) ? "text" : "binary";
|
|
11
|
+
}
|