@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,496 @@
|
|
|
1
|
+
import { CCB_MEDIA_TYPE, CCB_ENVIRONMENTS, CCB_READ_SCOPES, CCB_WRITE_SCOPES, CCB_SENSITIVE_FIELDS, } from "./lib/constants.js";
|
|
2
|
+
// Pushpay ChMS v2 JSON contracts, OAuth, status meanings, admission and privacy.
|
|
3
|
+
// The legacy per-church XML API is outside this provider's scope.
|
|
4
|
+
import { Effect, Schema } from "effect";
|
|
5
|
+
import { ConnectorCallError } from "../../errors.js";
|
|
6
|
+
import { OperationIndex } from "../_shared/rest/operation-index.js";
|
|
7
|
+
import { checked, indexedVendor } from "../_shared/rest/kit.js";
|
|
8
|
+
import { valueSafety } from "../_shared/rest/value-safety.js";
|
|
9
|
+
import { restSafety } from "../../http/safety.js";
|
|
10
|
+
import { openapi } from "./openapi.generated.js";
|
|
11
|
+
import { CCB_VALUE_SAFETY } from "./safety.js";
|
|
12
|
+
import { CCB_VERDICTS } from "./lib/verdicts.js";
|
|
13
|
+
export { CCB_VERDICTS };
|
|
14
|
+
import { bearer } from "../../http/oauth.js";
|
|
15
|
+
import { get } from "../../http/vendor.js";
|
|
16
|
+
export const authenticate = bearer;
|
|
17
|
+
const SCOPE_SHAPE = /^(read|write|delete):[a-z_]+$/;
|
|
18
|
+
/** CCB's own minimum, which is also the cheapest honest first read. */
|
|
19
|
+
export const DEFAULT_PER_PAGE = 25;
|
|
20
|
+
/**
|
|
21
|
+
* A ceiling on absurdity rather than a budget: at 100 rows per page the
|
|
22
|
+
* fattest documented list (individuals with addresses) stays far below it.
|
|
23
|
+
*/
|
|
24
|
+
export const CCB_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
25
|
+
/**
|
|
26
|
+
* CCB meters the v2 API per endpoint and per API client: one request a second
|
|
27
|
+
* sustained with a burst of 60 (docs.pushpay.io/chms-v2/docs/general). Three
|
|
28
|
+
* endpoints are stricter and allow no burst — `/individuals/{id}` at one a
|
|
29
|
+
* second, `/scheduling/categories/{id}/schedules` at one per two seconds, and
|
|
30
|
+
* `/search/individuals/results` at one per five.
|
|
31
|
+
*
|
|
32
|
+
* What ships is one rolling window of 60 calls per 60 seconds, partitioned by
|
|
33
|
+
* endpoint (method plus path template), which is the documented default
|
|
34
|
+
* transcribed as faithfully as one rule can: it never admits more than the
|
|
35
|
+
* token bucket would in any minute, because a bucket of 60 refilling at one a
|
|
36
|
+
* second can pass up to 120 in a minute and the window passes 60. Partitioning
|
|
37
|
+
* matters — a single connector-wide window would throttle a healthy program
|
|
38
|
+
* that touches ten endpoints to a tenth of what CCB allows.
|
|
39
|
+
*
|
|
40
|
+
* What it cannot express is the three no-burst overrides: this release
|
|
41
|
+
* enforces one rule, and a rule has one budget for every partition. Those are
|
|
42
|
+
* left to CCB's own 429, which carries `retry-after` and maps to a
|
|
43
|
+
* `rate_limited` failure with that wait; the guide says to space those calls.
|
|
44
|
+
* No `maxConcurrency` is declared: per-endpoint concurrency would not keep a
|
|
45
|
+
* fast loop under one a second, and the window already bounds the burst.
|
|
46
|
+
*
|
|
47
|
+
* Per runtime, and per API client only as far as one runtime is the whole
|
|
48
|
+
* client: other deployments, isolates, or integrations using the same client
|
|
49
|
+
* spend from CCB's bucket without spending from this window.
|
|
50
|
+
*/
|
|
51
|
+
export const CCB_ADMISSION = {
|
|
52
|
+
rules: [
|
|
53
|
+
{
|
|
54
|
+
budget: { kind: "rolling-window", maxCalls: 60, windowMs: 60_000 },
|
|
55
|
+
partitionKey: ({ toolName, args }) => endpointPartition(toolName, args),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
function asRecord(value) {
|
|
60
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
61
|
+
}
|
|
62
|
+
function asArray(value) {
|
|
63
|
+
return Array.isArray(value) ? value : [];
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Admission partitions
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/** The method and path template each named tool calls. */
|
|
69
|
+
const NAMED_ENDPOINTS = {
|
|
70
|
+
get_me: "GET /me",
|
|
71
|
+
list_individuals: "GET /individuals",
|
|
72
|
+
get_individual: "GET /individuals/{id}",
|
|
73
|
+
list_families: "GET /families",
|
|
74
|
+
get_family: "GET /families/{id}",
|
|
75
|
+
list_groups: "GET /groups",
|
|
76
|
+
get_group: "GET /groups/{id}",
|
|
77
|
+
list_group_members: "GET /groups/{id}/members",
|
|
78
|
+
list_individual_groups: "GET /individuals/{id}/groups",
|
|
79
|
+
list_events: "GET /events",
|
|
80
|
+
get_event: "GET /events/{id}",
|
|
81
|
+
get_event_attendance: "GET /events/{id}/attendance/{occurrence}",
|
|
82
|
+
list_event_attendees: "GET /events/{id}/attendance/{occurrence}/attendees",
|
|
83
|
+
list_individual_notes: "GET /individuals/{id}/notes",
|
|
84
|
+
get_giving_metrics: (args) => args["familyId"] !== undefined ? "GET /families/{id}/metrics/giving" : "GET /individuals/{id}/metrics/giving",
|
|
85
|
+
search_records: (args) => `POST /search/${typeof args["domain"] === "string" ? args["domain"] : "?"}/results`,
|
|
86
|
+
};
|
|
87
|
+
/** `/individuals/123/notes` → `/individuals/{id}/notes`; the key never holds an id. */
|
|
88
|
+
function pathTemplate(path) {
|
|
89
|
+
return path
|
|
90
|
+
.split("/")
|
|
91
|
+
.map((segment) => (/^\d+$/.test(segment) ? "{id}" : segment))
|
|
92
|
+
.join("/");
|
|
93
|
+
}
|
|
94
|
+
const MAX_PARTITION_KEY_CHARS = 120;
|
|
95
|
+
/**
|
|
96
|
+
* The CCB endpoint one call will spend from, as a bounded, id-free key. Runs
|
|
97
|
+
* before argument validation, so it reads arguments defensively and never
|
|
98
|
+
* throws: a malformed hatch path falls into one shared partition and is then
|
|
99
|
+
* refused by the handler.
|
|
100
|
+
*/
|
|
101
|
+
function endpointPartition(toolName, raw) {
|
|
102
|
+
const args = asRecord(raw);
|
|
103
|
+
const named = NAMED_ENDPOINTS[toolName];
|
|
104
|
+
let key;
|
|
105
|
+
if (typeof named === "string")
|
|
106
|
+
key = named;
|
|
107
|
+
else if (named)
|
|
108
|
+
key = named(args);
|
|
109
|
+
else {
|
|
110
|
+
const method = toolName === "ccb_api_read"
|
|
111
|
+
? typeof args["method"] === "string"
|
|
112
|
+
? args["method"]
|
|
113
|
+
: "GET"
|
|
114
|
+
: typeof args["method"] === "string"
|
|
115
|
+
? args["method"]
|
|
116
|
+
: "?";
|
|
117
|
+
const path = typeof args["path"] === "string" ? args["path"] : "";
|
|
118
|
+
key = `${method} ${pathTemplate(path)}`;
|
|
119
|
+
}
|
|
120
|
+
// ASCII-only after templating in every realistic case; the slice keeps an
|
|
121
|
+
// absurd path inside the admission controller's 128-byte key limit anyway.
|
|
122
|
+
return [...key]
|
|
123
|
+
.filter((c) => c.charCodeAt(0) < 128)
|
|
124
|
+
.join("")
|
|
125
|
+
.slice(0, MAX_PARTITION_KEY_CHARS);
|
|
126
|
+
}
|
|
127
|
+
export function resolveScopes(options) {
|
|
128
|
+
if (options.scopes !== undefined && options.access !== undefined) {
|
|
129
|
+
throw new Error("ccb() takes access or scopes, not both: scopes is the exact list and replaces access.");
|
|
130
|
+
}
|
|
131
|
+
if (options.scopes === undefined) {
|
|
132
|
+
const access = options.access ?? "read";
|
|
133
|
+
if (access !== "read" && access !== "read-write") {
|
|
134
|
+
throw new Error('ccb() access must be "read" or "read-write".');
|
|
135
|
+
}
|
|
136
|
+
return access === "read" ? CCB_READ_SCOPES : [...CCB_READ_SCOPES, ...CCB_WRITE_SCOPES];
|
|
137
|
+
}
|
|
138
|
+
if (!Array.isArray(options.scopes) || options.scopes.length === 0) {
|
|
139
|
+
throw new Error("ccb() scopes must be a non-empty list.");
|
|
140
|
+
}
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
for (const scope of options.scopes) {
|
|
143
|
+
if (typeof scope !== "string" || !SCOPE_SHAPE.test(scope)) {
|
|
144
|
+
throw new Error(`ccb() scope "${String(scope)}" is not a CCB scope; CCB scopes look like read:individuals.`);
|
|
145
|
+
}
|
|
146
|
+
if (seen.has(scope))
|
|
147
|
+
throw new Error(`ccb() scope "${scope}" is listed twice.`);
|
|
148
|
+
seen.add(scope);
|
|
149
|
+
}
|
|
150
|
+
return [...options.scopes];
|
|
151
|
+
}
|
|
152
|
+
export const SEARCH_DOMAINS = [
|
|
153
|
+
"assignments",
|
|
154
|
+
"categories",
|
|
155
|
+
"department_detail",
|
|
156
|
+
"groups",
|
|
157
|
+
"individual_event_attendance",
|
|
158
|
+
"individual_groups",
|
|
159
|
+
"individuals",
|
|
160
|
+
"organizers",
|
|
161
|
+
"positions",
|
|
162
|
+
"schedules",
|
|
163
|
+
"schedules_totals",
|
|
164
|
+
"scheduling_files",
|
|
165
|
+
"scheduling_individuals",
|
|
166
|
+
"songs",
|
|
167
|
+
"volunteers",
|
|
168
|
+
];
|
|
169
|
+
const summaries = {
|
|
170
|
+
"GET /individuals": "List people (person profiles, individuals), search names, email and phone",
|
|
171
|
+
"GET /individuals/{id}": "Get a person profile (individual) for identity or update reconciliation",
|
|
172
|
+
"PUT /individuals/{id}": "Update or change a person's email, edit an individual profile, name or change phone number",
|
|
173
|
+
"POST /individuals": "Create or add a person profile, new individual",
|
|
174
|
+
"POST /individuals/{id}/notes": "Add or create a pastoral note for a person (individual)",
|
|
175
|
+
"GET /families": "List households and families",
|
|
176
|
+
"GET /groups/{id}/members": "List church group members and leaders, roster",
|
|
177
|
+
"POST /groups/{id}/members": "Add person to group, create group membership",
|
|
178
|
+
"PUT /groups/{group_id}/members/roles": "Change group role, update member or leader roles",
|
|
179
|
+
"POST /events/{id}/attendance/{occurrence}": "Record attendance, take attendance for an event occurrence",
|
|
180
|
+
"GET /individuals/{id}/metrics/giving": "Get individual giving metrics, donations and contributions counts",
|
|
181
|
+
"GET /families/{id}/metrics/giving": "Get family giving metrics, donations and contributions counts",
|
|
182
|
+
"POST /search/individuals/results": "Search people and individual profiles with advanced configuration and filters",
|
|
183
|
+
"GET /campuses": "List church campuses, timezone and public gift message",
|
|
184
|
+
};
|
|
185
|
+
const details = JSON.parse(openapi.details);
|
|
186
|
+
// These thirteen wildcard-labelled schemas describe twelve JSON objects and
|
|
187
|
+
// one JSON array. Their byte-pinned contracts were reviewed for JSON dispatch.
|
|
188
|
+
const jsonBodies = new Set([
|
|
189
|
+
"POST /forms/{id}/responses",
|
|
190
|
+
"PUT /individuals/{id}/groups/{group_id}",
|
|
191
|
+
"PUT /individuals/{id}/privacy_settings",
|
|
192
|
+
"POST /church/privacy_defaults",
|
|
193
|
+
"PUT /messages/{id}",
|
|
194
|
+
"POST /messages/{id}/comments",
|
|
195
|
+
"PUT /messages/{id}/comments/{comment_id}",
|
|
196
|
+
"POST /scheduling/categories/{id}/notifications",
|
|
197
|
+
"PUT /individuals/{id}/assignments",
|
|
198
|
+
"PUT /scheduling/families/{id}/assignments",
|
|
199
|
+
"PUT /individuals/{id}/groups/bulk",
|
|
200
|
+
"POST /search/save",
|
|
201
|
+
"PUT /search/save/{id}",
|
|
202
|
+
]);
|
|
203
|
+
for (const [i, row] of details.o.entries()) {
|
|
204
|
+
const op = openapi.ops[i];
|
|
205
|
+
if (jsonBodies.has(`${op[0]} ${op[1]}`)) {
|
|
206
|
+
if (!row || !row[1] || row[1][0] !== "*/*")
|
|
207
|
+
throw new Error("CCB JSON request review is stale.");
|
|
208
|
+
row[1][0] = "application/json";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
for (const row of details.o) {
|
|
212
|
+
if (row)
|
|
213
|
+
for (const parameter of row[0])
|
|
214
|
+
if (!parameter[4])
|
|
215
|
+
parameter[4] =
|
|
216
|
+
{
|
|
217
|
+
id: "Record id from a prior read; resolve the target before acting.",
|
|
218
|
+
page: "Vendor page number restored from the signed continuation.",
|
|
219
|
+
per_page: "Page size: 25, 50, 75 or 100; defaults to 25.",
|
|
220
|
+
name: "Partial name search.",
|
|
221
|
+
}[parameter[0]] ?? `${parameter[0].replaceAll("_", " ")} ${parameter[1]} parameter from the vendor contract.`;
|
|
222
|
+
}
|
|
223
|
+
export const CCB_INDEX = new OperationIndex({
|
|
224
|
+
...openapi,
|
|
225
|
+
details: JSON.stringify(details),
|
|
226
|
+
// The public campus directory is church setup; retain both resource
|
|
227
|
+
// words so conversational searches distinguish it from campus subresources.
|
|
228
|
+
tags: [...openapi.tags, "church campuses"],
|
|
229
|
+
ops: openapi.ops.map((row) => {
|
|
230
|
+
const summary = summaries[`${row[0]} ${row[1]}`];
|
|
231
|
+
const label = summary ? `${summary}; ${row[3]}` : row[3];
|
|
232
|
+
const tag = row[0] === "GET" && row[1] === "/campuses" ? openapi.tags.length : row[4];
|
|
233
|
+
return row[5] === undefined
|
|
234
|
+
? [row[0], row[1], row[2], label, tag]
|
|
235
|
+
: [row[0], row[1], row[2], label, tag, row[5]];
|
|
236
|
+
}),
|
|
237
|
+
}, { vendor: "ccb", title: "CCB" });
|
|
238
|
+
export const getMe = get("getMe", "/me", { success: Schema.Unknown });
|
|
239
|
+
export const CcbApi = indexedVendor("CCB", CCB_INDEX, [getMe]);
|
|
240
|
+
const safety = valueSafety(CCB_VALUE_SAFETY, () => CCB_INDEX);
|
|
241
|
+
export const CCB_PAGE_HEADERS = ["x-page", "x-next-page", "x-total-pages", "x-total"];
|
|
242
|
+
export function ccbPageInfo(headers, page, size, rows) {
|
|
243
|
+
const number = (name) => {
|
|
244
|
+
const raw = headers[name];
|
|
245
|
+
if (raw === undefined || raw === "")
|
|
246
|
+
return undefined;
|
|
247
|
+
if (!/^(0|[1-9]\d*)$/.test(raw) || !Number.isSafeInteger(Number(raw)))
|
|
248
|
+
throw new ConnectorCallError("connector_call_failed", "CCB returned invalid paging metadata.", {
|
|
249
|
+
retryable: false,
|
|
250
|
+
});
|
|
251
|
+
return Number(raw);
|
|
252
|
+
};
|
|
253
|
+
const current = number("x-page") ?? page;
|
|
254
|
+
if (current !== page)
|
|
255
|
+
throw new ConnectorCallError("connector_call_failed", "CCB returned a different page than requested.", {
|
|
256
|
+
retryable: false,
|
|
257
|
+
});
|
|
258
|
+
let next;
|
|
259
|
+
if (Object.hasOwn(headers, "x-next-page"))
|
|
260
|
+
next = number("x-next-page") ?? null;
|
|
261
|
+
else {
|
|
262
|
+
const pages = number("x-total-pages");
|
|
263
|
+
next = pages === undefined ? (rows >= size ? page + 1 : null) : page < pages ? page + 1 : null;
|
|
264
|
+
}
|
|
265
|
+
if (next !== null && (!rows || next <= page))
|
|
266
|
+
throw new ConnectorCallError("connector_call_failed", "CCB returned a nonadvancing or empty continuing page.", {
|
|
267
|
+
retryable: false,
|
|
268
|
+
});
|
|
269
|
+
return next;
|
|
270
|
+
}
|
|
271
|
+
const pageAdapter = {
|
|
272
|
+
param: "page",
|
|
273
|
+
in: "query",
|
|
274
|
+
headers: CCB_PAGE_HEADERS,
|
|
275
|
+
decode: (cursor) => {
|
|
276
|
+
const value = Number(cursor);
|
|
277
|
+
if (!/^[1-9]\d*$/.test(String(cursor)) || !Number.isSafeInteger(value))
|
|
278
|
+
throw new ConnectorCallError("invalid_args", "This CCB cursor contains no valid page number.");
|
|
279
|
+
return value;
|
|
280
|
+
},
|
|
281
|
+
next: (data, call) => {
|
|
282
|
+
const rows = Array.isArray(data)
|
|
283
|
+
? data
|
|
284
|
+
: Array.isArray(asRecord(data)["items"])
|
|
285
|
+
? asRecord(data)["items"]
|
|
286
|
+
: undefined;
|
|
287
|
+
if (!rows && !Object.hasOwn(call.headers, "x-next-page") && !Object.hasOwn(call.headers, "x-total-pages"))
|
|
288
|
+
return undefined;
|
|
289
|
+
return ccbPageInfo(call.headers, Number(call.query["page"] ?? 1), Number(call.query["per_page"] ?? 25), rows?.length ?? 1);
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
export const CCB_PAGES = Object.fromEntries(CCB_INDEX.ops
|
|
293
|
+
.filter((op) => CCB_VERDICTS[`${op.method} ${op.path}`] === "read" &&
|
|
294
|
+
op.path !== "/search/schedules_totals/results" &&
|
|
295
|
+
CCB_INDEX.contract(op).parameters.some((p) => p.in === "query" && p.name === "page"))
|
|
296
|
+
.map((op) => [`${op.method} ${op.path}`, pageAdapter]));
|
|
297
|
+
export const CCB_NAMED = {
|
|
298
|
+
"GET /me": "get_me",
|
|
299
|
+
"GET /individuals": "list_individuals",
|
|
300
|
+
"GET /individuals/{id}": "get_individual",
|
|
301
|
+
"GET /families": "list_families",
|
|
302
|
+
"GET /families/{id}": "get_family",
|
|
303
|
+
"GET /groups": "list_groups",
|
|
304
|
+
"GET /groups/{id}": "get_group",
|
|
305
|
+
"GET /groups/{id}/members": "list_group_members",
|
|
306
|
+
"GET /individuals/{id}/groups": "list_individual_groups",
|
|
307
|
+
"GET /events": "list_events",
|
|
308
|
+
"GET /events/{id}": "get_event",
|
|
309
|
+
"GET /events/{id}/attendance/{occurrence}": "get_event_attendance",
|
|
310
|
+
"GET /events/{id}/attendance/{occurrence}/attendees": "list_event_attendees",
|
|
311
|
+
"GET /individuals/{id}/notes": "list_individual_notes",
|
|
312
|
+
"GET /individuals/{id}/metrics/giving": "get_giving_metrics",
|
|
313
|
+
"GET /families/{id}/metrics/giving": "get_giving_metrics",
|
|
314
|
+
...Object.fromEntries(SEARCH_DOMAINS.map((domain) => [`POST /search/${domain}/results`, "search_records"])),
|
|
315
|
+
};
|
|
316
|
+
function pathProblem(path) {
|
|
317
|
+
return !path.startsWith("/") ||
|
|
318
|
+
path.startsWith("//") ||
|
|
319
|
+
/[%;\\?#]/.test(path) ||
|
|
320
|
+
path.split("/").some((p) => p === "." || p === "..") ||
|
|
321
|
+
/^\/oauth(?:\/|$)/i.test(path)
|
|
322
|
+
? "CCB paths must be canonical indexed API paths; OAuth and encoded or traversal paths are refused."
|
|
323
|
+
: undefined;
|
|
324
|
+
}
|
|
325
|
+
function bodyRefusal(op) {
|
|
326
|
+
const content = CCB_INDEX.bodyType(op);
|
|
327
|
+
return content && !/^application\/(?:[^/]+\+)?json$/.test(content)
|
|
328
|
+
? `CCB's JSON hatch cannot send ${content} request bodies; use the vendor application for this operation.`
|
|
329
|
+
: undefined;
|
|
330
|
+
}
|
|
331
|
+
export const ccbBinding = ({ connection, options, }) => ({
|
|
332
|
+
review: "CCB v2 uses JSON bodies, repeated form-array query parameters and deep-object bracket keys on its fixed environment origin. The index, safety and OAuth grant own admission and dispatch.",
|
|
333
|
+
refusal: (op) => safety.refusal(op.method, op.path) ?? bodyRefusal(op),
|
|
334
|
+
refuse: (call) => {
|
|
335
|
+
const denied = safety.refusal(call.method, call.op.path);
|
|
336
|
+
if (denied)
|
|
337
|
+
return denied;
|
|
338
|
+
const path = pathProblem(call.path);
|
|
339
|
+
if (path)
|
|
340
|
+
return path;
|
|
341
|
+
if (CCB_VERDICTS[`${call.method} ${call.op.path}`] !== "read" &&
|
|
342
|
+
!resolveScopes(options).some((scope) => !scope.startsWith("read:")))
|
|
343
|
+
return "This CCB connection has only read scopes; writes are refused.";
|
|
344
|
+
const size = call.query["per_page"];
|
|
345
|
+
if (size !== undefined && !/^(25|50|75|100)$/.test(String(size)))
|
|
346
|
+
return "CCB per_page must be one of 25, 50, 75, 100.";
|
|
347
|
+
const page = call.query["page"];
|
|
348
|
+
if (page !== undefined && (!/^[1-9]\d*$/.test(String(page)) || !Number.isSafeInteger(Number(page))))
|
|
349
|
+
return "CCB page must be a positive whole number.";
|
|
350
|
+
return bodyRefusal(call.op);
|
|
351
|
+
},
|
|
352
|
+
send: (call, settings) => connection.sendPath({
|
|
353
|
+
method: call.method,
|
|
354
|
+
path: call.path,
|
|
355
|
+
template: call.op.path,
|
|
356
|
+
query: queryPairs(call.query),
|
|
357
|
+
...(call.body === undefined ? {} : { json: JSON.stringify(call.body) }),
|
|
358
|
+
}, settings),
|
|
359
|
+
});
|
|
360
|
+
function queryPairs(query) {
|
|
361
|
+
const out = [];
|
|
362
|
+
const add = (name, value) => {
|
|
363
|
+
if (Array.isArray(value))
|
|
364
|
+
for (const item of value)
|
|
365
|
+
add(name, item);
|
|
366
|
+
else if (value !== null && typeof value === "object")
|
|
367
|
+
for (const [key, item] of Object.entries(value))
|
|
368
|
+
add(`${name}[${key}]`, item);
|
|
369
|
+
else if (value !== undefined)
|
|
370
|
+
out.push([name, String(value ?? "")]);
|
|
371
|
+
};
|
|
372
|
+
for (const [name, value] of Object.entries(query))
|
|
373
|
+
add(name, value);
|
|
374
|
+
return out;
|
|
375
|
+
}
|
|
376
|
+
/** Uniform v2 privacy review, including the legacy hatches' unindexed paths. */
|
|
377
|
+
export function ccbSafety(options) {
|
|
378
|
+
return {
|
|
379
|
+
reviewed: restSafety(safety, CCB_INDEX),
|
|
380
|
+
sensitiveFields: { names: CCB_SENSITIVE_FIELDS, choice: options.sensitiveFields ?? "drop" },
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
export function callCcb(connection, request) {
|
|
384
|
+
return Effect.gen(function* () {
|
|
385
|
+
const query = Object.fromEntries(Object.entries(request.query ?? {}).filter(([, value]) => value !== undefined));
|
|
386
|
+
const op = yield* checked(() => {
|
|
387
|
+
const problem = pathProblem(request.path);
|
|
388
|
+
if (problem)
|
|
389
|
+
throw new ConnectorCallError("invalid_args", problem);
|
|
390
|
+
const { op } = CCB_INDEX.resolve(request.method, request.path);
|
|
391
|
+
CCB_INDEX.check(op, query, request.body);
|
|
392
|
+
return op;
|
|
393
|
+
});
|
|
394
|
+
const answer = yield* connection.sendPath({
|
|
395
|
+
method: request.method,
|
|
396
|
+
path: request.path,
|
|
397
|
+
template: op.path,
|
|
398
|
+
query: queryPairs(query),
|
|
399
|
+
...(request.body === undefined ? {} : { json: JSON.stringify(request.body) }),
|
|
400
|
+
}, { responseHeaders: CCB_PAGE_HEADERS });
|
|
401
|
+
return { body: answer.data, headers: new Headers(answer.headers) };
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
export function oauth(options) {
|
|
405
|
+
const endpoints = CCB_ENVIRONMENTS[options.environment];
|
|
406
|
+
return {
|
|
407
|
+
authorizationEndpoint: endpoints.authorizationEndpoint,
|
|
408
|
+
tokenEndpoint: `${endpoints.apiOrigin}/oauth/token`,
|
|
409
|
+
clientId: options.clientId,
|
|
410
|
+
clientSecret: options.clientSecret,
|
|
411
|
+
scope: resolveScopes(options).join(" "),
|
|
412
|
+
authorizationParams: {
|
|
413
|
+
...(options.mode === "identity" ? { resource_owner_auth: "true" } : {}),
|
|
414
|
+
...(options.subdomain?.trim() ? { subdomain: options.subdomain.trim() } : {}),
|
|
415
|
+
},
|
|
416
|
+
tokenRequestHeaders: { Accept: CCB_MEDIA_TYPE },
|
|
417
|
+
apiOrigins: [endpoints.apiOrigin],
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function errorDetail(payload, status) {
|
|
421
|
+
const root = asRecord(payload);
|
|
422
|
+
const parts = [];
|
|
423
|
+
for (const key of ["message", "error_description", "error"]) {
|
|
424
|
+
const value = root[key];
|
|
425
|
+
if (typeof value === "string" && value.trim()) {
|
|
426
|
+
parts.push(value.trim());
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
for (const item of asArray(root["errors"]).slice(0, 5)) {
|
|
431
|
+
if (typeof item === "string" && item.trim())
|
|
432
|
+
parts.push(item.trim());
|
|
433
|
+
else {
|
|
434
|
+
const message = asRecord(item)["message"];
|
|
435
|
+
if (typeof message === "string" && message.trim())
|
|
436
|
+
parts.push(message.trim());
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const detail = parts.join("; ").slice(0, 500);
|
|
440
|
+
return detail ? `CCB HTTP ${status}: ${detail}${/[.!?]$/.test(detail) ? "" : "."}` : `CCB returned HTTP ${status}.`;
|
|
441
|
+
}
|
|
442
|
+
export const errors = {
|
|
443
|
+
meanings: { unknownId: "not_found", provesAbsence: true, conflicts: [] },
|
|
444
|
+
bodyFailures: {
|
|
445
|
+
statusOnly: [],
|
|
446
|
+
review: "An incomplete response body does not establish malformed JSON. Preserve CCB OAuth and write settlement semantics.",
|
|
447
|
+
readFailed: "CCB's response body could not be read completely.",
|
|
448
|
+
},
|
|
449
|
+
numericFacts: {
|
|
450
|
+
retryAfterMs: {
|
|
451
|
+
header: "retry-after",
|
|
452
|
+
format: "retry-after",
|
|
453
|
+
review: "HTTP Retry-After delay for failure recovery.",
|
|
454
|
+
},
|
|
455
|
+
resetAfterMs: {
|
|
456
|
+
header: "x-ratelimit-reset",
|
|
457
|
+
format: "epoch-seconds",
|
|
458
|
+
review: "CCB endpoint rate-limit reset time in epoch seconds.",
|
|
459
|
+
},
|
|
460
|
+
bodyRetryAfterMs: {
|
|
461
|
+
path: ["retry_after"],
|
|
462
|
+
format: "seconds",
|
|
463
|
+
review: "CCB rate-limit response body delay in seconds.",
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
provider: "CCB",
|
|
467
|
+
facts: () => ({}),
|
|
468
|
+
detail: ({ body, status }) => errorDetail(body, status),
|
|
469
|
+
unreadable: "CCB returned a successful response that is not JSON.",
|
|
470
|
+
statuses: {
|
|
471
|
+
401: () => ({
|
|
472
|
+
code: "auth_required",
|
|
473
|
+
words: (detail) => `${detail} CCB rejected the connection's access token. Call authorize_connector for this connector.`,
|
|
474
|
+
advice: ", then retry",
|
|
475
|
+
}),
|
|
476
|
+
403: () => ({
|
|
477
|
+
code: "provider_permission_denied",
|
|
478
|
+
retryable: false,
|
|
479
|
+
words: (detail) => `${detail} The grant lacks this endpoint's scope, or (Identity Auth) the signed-in person lacks the permission in CCB. Ask the CCB administrator to grant this endpoint's scope or the signed-in person's permission. Re-authorizing alone will not fix it.`,
|
|
480
|
+
}),
|
|
481
|
+
404: () => ({
|
|
482
|
+
code: "not_found",
|
|
483
|
+
words: (detail) => `${detail} Re-read the id from its list tool; CCB reports a permission gap as 403, so this record does not exist.`,
|
|
484
|
+
}),
|
|
485
|
+
412: () => ({ code: "invalid_args" }),
|
|
486
|
+
429: ({ retryAfterMs, numbers }) => {
|
|
487
|
+
const wait = retryAfterMs ?? numbers["resetAfterMs"] ?? numbers["bodyRetryAfterMs"];
|
|
488
|
+
return {
|
|
489
|
+
code: "rate_limited",
|
|
490
|
+
retryAfterMs: wait,
|
|
491
|
+
words: (detail) => `${detail} CCB limits each endpoint per API client.`,
|
|
492
|
+
advice: "; wait before calling this endpoint again",
|
|
493
|
+
};
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
};
|
|
@@ -1,37 +1,6 @@
|
|
|
1
|
+
import { CCB_ENVIRONMENTS } from "./lib/constants.js";
|
|
1
2
|
import type { ConnectorCallAdmissionPolicy } from "../../types.js";
|
|
2
|
-
|
|
3
|
-
export declare const CCB_MEDIA_TYPE = "application/vnd.ccbchurch.v2+json";
|
|
4
|
-
/**
|
|
5
|
-
* The two published environments. Credentials do not cross: sandbox clients
|
|
6
|
-
* work only against the sandbox hosts and production clients only against
|
|
7
|
-
* production, so the environment is part of the client, not a preference.
|
|
8
|
-
*/
|
|
9
|
-
export declare const CCB_ENVIRONMENTS: {
|
|
10
|
-
readonly production: {
|
|
11
|
-
readonly apiOrigin: "https://api.ccbchurch.com";
|
|
12
|
-
readonly authorizationEndpoint: "https://oauth.ccbchurch.com/oauth/authorize";
|
|
13
|
-
};
|
|
14
|
-
readonly sandbox: {
|
|
15
|
-
readonly apiOrigin: "https://api-beta.ccbchurch.com";
|
|
16
|
-
readonly authorizationEndpoint: "https://beta-oauth.ccbchurch.com/oauth/authorize";
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* The read scopes the named tools and the search hatch need, from the
|
|
21
|
-
* `security` requirements of the operations they call (spec revision recorded
|
|
22
|
-
* in the drift manifest). `read:campuses` and `read:church` are there so the
|
|
23
|
-
* ids every projection carries — campus, membership type — can be resolved
|
|
24
|
-
* through `ccb_api_get`. Request these on Pushpay's API access form.
|
|
25
|
-
*/
|
|
26
|
-
export declare const CCB_READ_SCOPES: readonly string[];
|
|
27
|
-
/**
|
|
28
|
-
* What `access: "read-write"` adds: the writes of the everyday staff jobs —
|
|
29
|
-
* profiles, groups, events and attendance, form responses, notes, process
|
|
30
|
-
* queues, scheduling. No `delete:` scope, nothing financial, no background
|
|
31
|
-
* checks, and no privacy settings: those are deliberate, so they are only
|
|
32
|
-
* ever requested through an explicit `scopes` list.
|
|
33
|
-
*/
|
|
34
|
-
export declare const CCB_WRITE_SCOPES: readonly string[];
|
|
3
|
+
export { CCB_ENVIRONMENTS, CCB_MEDIA_TYPE, CCB_READ_SCOPES, CCB_WRITE_SCOPES, CCB_SENSITIVE_FIELDS, } from "./lib/constants.js";
|
|
35
4
|
/** How the grant acts: for the whole church, or as one signed-in person. */
|
|
36
5
|
export type CcbMode = "system" | "identity";
|
|
37
6
|
/** Which Pushpay environment the client was issued for. */
|
|
@@ -73,15 +42,15 @@ export interface CcbOptions {
|
|
|
73
42
|
clientSecret: string;
|
|
74
43
|
/**
|
|
75
44
|
* `"read"` (default) requests {@link CCB_READ_SCOPES} and ships no write
|
|
76
|
-
* tool; `"read-write"` adds {@link CCB_WRITE_SCOPES} and
|
|
77
|
-
*
|
|
45
|
+
* tool; `"read-write"` adds {@link CCB_WRITE_SCOPES} and `ccb_api_write`.
|
|
46
|
+
* The host may ask the user to confirm a write. Exclusive with `scopes`.
|
|
78
47
|
*/
|
|
79
48
|
access?: "read" | "read-write";
|
|
80
49
|
/**
|
|
81
50
|
* The exact scope list to request, replacing `access`: narrower (drop
|
|
82
51
|
* `read:individual_notes`) or wider (add `read:background_checks` for the
|
|
83
52
|
* hatch). Must be a subset of what Pushpay granted the client. Any `write:`
|
|
84
|
-
* or `delete:` scope ships `
|
|
53
|
+
* or `delete:` scope ships `ccb_api_write`.
|
|
85
54
|
*/
|
|
86
55
|
scopes?: readonly string[];
|
|
87
56
|
/**
|
|
@@ -100,6 +69,15 @@ export interface CcbOptions {
|
|
|
100
69
|
maxResultBytes?: number;
|
|
101
70
|
/** Serve only read-classified tools on every endpoint. Defaults to false. */
|
|
102
71
|
readOnly?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Whether results may carry {@link CCB_SENSITIVE_FIELDS}: allergies,
|
|
74
|
+
* giving numbers and dates, envelope ids, statement dates, attendance
|
|
75
|
+
* prayer requests and meeting notes, and form payment details. `"drop"`
|
|
76
|
+
* (default) removes them wherever they appear in every result: the named
|
|
77
|
+
* tools, the named reads, `search_records`, `ccb_api_read`, and `ccb_api_write`
|
|
78
|
+
* replies. `"return"` lets the hatches return those allowed fields; named projections stay narrow. Credentials and private file destinations are always withheld. Only deployment code decides; no tool argument can.
|
|
79
|
+
*/
|
|
80
|
+
sensitiveFields?: "drop" | "return";
|
|
103
81
|
}
|
|
104
|
-
/** A maintained Church Community Builder (Pushpay ChMS) v2 connection. */
|
|
82
|
+
/** A maintained Church Community Builder (Pushpay ChMS) v2 connection on the provider kit. */
|
|
105
83
|
export declare const ccb: import("../../provider.js").ProviderFactory<CcbOptions>;
|