@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,467 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { ConnectorCallError } from "../../errors.js";
|
|
3
|
+
import { Refusal } from "../../http/errors.js";
|
|
4
|
+
import { Credentials } from "../../http/services.js";
|
|
5
|
+
import { get } from "../../http/vendor.js";
|
|
6
|
+
import { indexedVendor } from "../_shared/rest/kit.js";
|
|
7
|
+
import { valueSafety } from "../_shared/rest/value-safety.js";
|
|
8
|
+
import { restSafety } from "../../http/safety.js";
|
|
9
|
+
import { openapi } from "./openapi.generated.js";
|
|
10
|
+
import { PlanningCenterIndex } from "./lib/discovery.js";
|
|
11
|
+
import { PCO_REFUSALS } from "./lib/verdicts.js";
|
|
12
|
+
import { TABLE } from "./safety.js";
|
|
13
|
+
import { excludedApps, series } from "./lib/guide.js";
|
|
14
|
+
import { asRecord, asArray, resources, attribute, pageOf } from "./lib/json-api.js";
|
|
15
|
+
import { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS, DEFAULT_USER_AGENT, } from "./lib/constants.js";
|
|
16
|
+
export { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, PLANNING_CENTER_MAX_RESPONSE_BYTES, PLANNING_CENTER_ADMISSION, DEFAULT_USER_AGENT, } from "./lib/constants.js";
|
|
17
|
+
export const CREDENTIAL = {
|
|
18
|
+
label: "Planning Center personal access token",
|
|
19
|
+
description: "Create one at api.planningcenteronline.com/oauth/applications → Personal Access Tokens. It acts with the permissions of the user who creates it, in every product that user can open, so create it as a user whose access matches this connection's purpose.",
|
|
20
|
+
fields: [
|
|
21
|
+
{
|
|
22
|
+
name: "applicationId",
|
|
23
|
+
label: "Application ID",
|
|
24
|
+
description: "The personal access token's Application ID.",
|
|
25
|
+
placeholder: "Paste Application ID",
|
|
26
|
+
inputType: "text",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "secret",
|
|
30
|
+
label: "Secret",
|
|
31
|
+
description: "The personal access token's Secret, shown once when the token is created.",
|
|
32
|
+
placeholder: "Paste Secret",
|
|
33
|
+
inputType: "password",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
const fail = (code, message, options) => Effect.fail(new Refusal({ error: new ConnectorCallError(code, message, options) }));
|
|
38
|
+
export const authenticate = Effect.gen(function* () {
|
|
39
|
+
const values = yield* (yield* Credentials).values.pipe(Effect.mapError((error) => new Refusal({ error })));
|
|
40
|
+
const applicationId = values?.["applicationId"]?.trim();
|
|
41
|
+
const secret = values?.["secret"]?.trim();
|
|
42
|
+
if (!applicationId || !secret) {
|
|
43
|
+
return yield* fail("auth_required", "No Planning Center personal access token is configured for this connector; it needs both the Application ID and the Secret. Call authorize_connector for recovery options. When available, an operator can add the token in this connection in the operator UI.");
|
|
44
|
+
}
|
|
45
|
+
// HTTP Basic is `id:secret` in Latin-1 base64. A colon in the id, or any
|
|
46
|
+
// whitespace or non-ASCII character, is a pasting mistake no real token
|
|
47
|
+
// contains — and btoa would either throw or frame it ambiguously.
|
|
48
|
+
if (applicationId.includes(":") || /[^\x21-\x7e]/.test(applicationId + secret)) {
|
|
49
|
+
return yield* fail("auth_required", "The configured Planning Center Application ID or Secret contains characters a token never has. An operator must re-paste both fields.");
|
|
50
|
+
}
|
|
51
|
+
return { Authorization: `Basic ${btoa(`${applicationId}:${secret}`)}` };
|
|
52
|
+
});
|
|
53
|
+
const APP_PATH = /^\/([a-z-]+)\/v2(?:\/|$)/;
|
|
54
|
+
export function isApp(value) {
|
|
55
|
+
return PLANNING_CENTER_APPS.includes(value);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The product a path addresses, read after URL normalization — the same
|
|
59
|
+
* normalization the guarded transport applies — so `..` cannot carry a request
|
|
60
|
+
* from `/people/v2` to `/oauth` or anywhere else outside a product.
|
|
61
|
+
*/
|
|
62
|
+
export function appForPath(path, apps) {
|
|
63
|
+
let normalized;
|
|
64
|
+
if (path.startsWith("/") && !path.startsWith("//")) {
|
|
65
|
+
try {
|
|
66
|
+
normalized = new URL(`https://planning-center.invalid${path}`).pathname;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
normalized = undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const app = normalized === undefined ? undefined : APP_PATH.exec(normalized)?.[1];
|
|
73
|
+
if (!app || !isApp(app)) {
|
|
74
|
+
throw new ConnectorCallError("invalid_args", `A Planning Center path begins with /<app>/v2, where app is one of ${apps.join(", ")} — for example /people/v2/people. No host, query string, or traversal.`);
|
|
75
|
+
}
|
|
76
|
+
return app;
|
|
77
|
+
}
|
|
78
|
+
/** Display names, in guide order. The guide never lists `api` as a product. */
|
|
79
|
+
export const APP_TITLES = {
|
|
80
|
+
people: "People",
|
|
81
|
+
services: "Services",
|
|
82
|
+
groups: "Groups",
|
|
83
|
+
"check-ins": "Check-Ins",
|
|
84
|
+
calendar: "Calendar",
|
|
85
|
+
registrations: "Registrations",
|
|
86
|
+
giving: "Giving",
|
|
87
|
+
publishing: "Publishing",
|
|
88
|
+
webhooks: "Webhooks",
|
|
89
|
+
api: "API",
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* A path's segments, each percent-decoded once, or a refusal when the path is
|
|
93
|
+
* not literal. While a product is excluded no path may lean on normalization:
|
|
94
|
+
* a dot segment, an empty segment, a backslash, whitespace, or an encoded
|
|
95
|
+
* `/`, `\`, `?`, `#`, or `%` could each mean one thing here and another to a
|
|
96
|
+
* server that decodes or resolves it, so none is admitted at all rather than
|
|
97
|
+
* reasoned about.
|
|
98
|
+
*/
|
|
99
|
+
function literalSegments(path) {
|
|
100
|
+
const ambiguous = () => new ConnectorCallError("invalid_args", "While this deployment excludes a Planning Center product, a path must be literal: no . or .. segments, empty segments, backslashes, whitespace, or percent-encoded /, \\, ?, #, or %.");
|
|
101
|
+
if (!path.startsWith("/"))
|
|
102
|
+
throw ambiguous();
|
|
103
|
+
const raw = path.slice(1).split("/");
|
|
104
|
+
return raw.map((segment, index) => {
|
|
105
|
+
if (segment === "" && index !== raw.length - 1)
|
|
106
|
+
throw ambiguous();
|
|
107
|
+
let decoded;
|
|
108
|
+
try {
|
|
109
|
+
decoded = decodeURIComponent(segment);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
throw ambiguous();
|
|
113
|
+
}
|
|
114
|
+
const control = [...decoded].some((char) => char <= " " || char === "\x7f");
|
|
115
|
+
if (decoded === "." || decoded === ".." || control || /[/\\?#%]/.test(decoded))
|
|
116
|
+
throw ambiguous();
|
|
117
|
+
return decoded;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* People resources a list is built from or answers with. A list's conditions
|
|
122
|
+
* can draw on any product — a Giving condition makes a donor list — and one
|
|
123
|
+
* list can be a condition of another, so while any product is excluded none
|
|
124
|
+
* of them is read, by path or by `include`.
|
|
125
|
+
*/
|
|
126
|
+
const LIST_RESOURCES = new Set(["lists", "list_results", "rules", "conditions"]);
|
|
127
|
+
/** The webhook subscription collections, current and 2018-08-01. */
|
|
128
|
+
const SUBSCRIPTION_COLLECTIONS = new Set(["webhook_subscriptions", "subscriptions"]);
|
|
129
|
+
/**
|
|
130
|
+
* Refuse, before anything is sent, a request that would reach an excluded
|
|
131
|
+
* product. It reads literal segments, so it runs twice: on the path as given,
|
|
132
|
+
* and on the exact URL the transport is about to fetch (`admit`), which is the
|
|
133
|
+
* one that counts. Five doors lead to an excluded product:
|
|
134
|
+
*
|
|
135
|
+
* - its own paths, in any case or percent-encoding of a literal segment;
|
|
136
|
+
* - People lists, rules, conditions, and results, whose criteria can come from
|
|
137
|
+
* the excluded product, by path or by `include`;
|
|
138
|
+
* - a new webhook subscription to its events, which delivers its records to
|
|
139
|
+
* any URL — so a new subscription must name a reachable product's event;
|
|
140
|
+
* - an existing subscription, changed by id: a path names a subscription by
|
|
141
|
+
* id, not by product, so no subscription is enabled, disabled, deleted, or
|
|
142
|
+
* re-keyed while any product is excluded; and
|
|
143
|
+
* - webhook event and delivery payloads, which carry the record that fired
|
|
144
|
+
* them, refused for every subscription for the same reason.
|
|
145
|
+
*
|
|
146
|
+
* People documents no other `include` or attribute that reaches another
|
|
147
|
+
* product's records.
|
|
148
|
+
*/
|
|
149
|
+
function refuseExcluded(segments, query, request, excluded) {
|
|
150
|
+
const disabled = (app, detail) => new ConnectorCallError("provider_permission_denied", `Planning Center ${APP_TITLES[app]} (/${app}/v2) is disabled in this deployment, so this connection ${detail}. Retrying will not help; only an operator can remove "${app}" from excludeApps.`, { retryable: false });
|
|
151
|
+
const because = (detail) => new ConnectorCallError("provider_permission_denied", `This deployment disables Planning Center ${series([...excluded].map((name) => APP_TITLES[name]))}, and ${detail} Retrying will not help.`, { retryable: false });
|
|
152
|
+
const lower = segments.map((segment) => segment.toLowerCase());
|
|
153
|
+
const app = lower[0] ?? "";
|
|
154
|
+
if (isApp(app) && excluded.has(app))
|
|
155
|
+
throw disabled(app, "does not reach its endpoints");
|
|
156
|
+
// Lists are refused and workflows are not. A list is a live query: reading
|
|
157
|
+
// it evaluates its conditions, Giving ones included, at read time, so it
|
|
158
|
+
// is the excluded product's API by another path. A workflow card is a
|
|
159
|
+
// stored People record. A Giving automation can create one, but so can
|
|
160
|
+
// staff, and once created it is People data, like a note or custom field
|
|
161
|
+
// value typed from a donor's details. Exclusion guards products' APIs,
|
|
162
|
+
// not provenance; refusing workflows would cost every exclusion a core
|
|
163
|
+
// People feature and argue equally for notes, fields, forms, and groups.
|
|
164
|
+
// The hard boundary is the token user's own per-product permissions.
|
|
165
|
+
const lists = "a People list's conditions can draw on a disabled product, so this connection reads no list, list result, rule, or condition.";
|
|
166
|
+
if (app === "people" && lower.some((segment) => LIST_RESOURCES.has(segment)))
|
|
167
|
+
throw because(lists);
|
|
168
|
+
for (const [key, value] of query) {
|
|
169
|
+
if (/include/i.test(key) &&
|
|
170
|
+
value
|
|
171
|
+
.toLowerCase()
|
|
172
|
+
.split(/[\s,.]+/)
|
|
173
|
+
.some((part) => LIST_RESOURCES.has(part))) {
|
|
174
|
+
throw because(lists);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (app !== "webhooks")
|
|
178
|
+
return;
|
|
179
|
+
if (lower.some((segment) => /^(?:events|deliveries)\b/.test(segment))) {
|
|
180
|
+
throw because("a webhook event or delivery payload may carry any product's records, so this connection does not read them. Subscriptions themselves stay readable.");
|
|
181
|
+
}
|
|
182
|
+
if (request.method === "GET" || request.method === "HEAD")
|
|
183
|
+
return;
|
|
184
|
+
const resource = lower.slice(2).filter((segment) => segment !== "");
|
|
185
|
+
if (request.method !== "POST" || resource.length !== 1 || !SUBSCRIPTION_COLLECTIONS.has(resource[0])) {
|
|
186
|
+
throw because("a webhook subscription's id does not name its product, so this connection does not enable, disable, delete, or re-key an existing subscription.");
|
|
187
|
+
}
|
|
188
|
+
const data = asRecord(request.body)["data"];
|
|
189
|
+
const name = Array.isArray(data) ? undefined : asRecord(asRecord(data)["attributes"])["name"];
|
|
190
|
+
const event = typeof name === "string" ? /^([a-z-]+)\.v2\.events\./.exec(name.trim().toLowerCase())?.[1] : undefined;
|
|
191
|
+
if (event === undefined || !isApp(event)) {
|
|
192
|
+
throw because("a new webhook subscription must name one product's event, such as people.v2.events.person.created, in data.attributes.name.");
|
|
193
|
+
}
|
|
194
|
+
if (excluded.has(event))
|
|
195
|
+
throw disabled(event, "cannot subscribe to its webhook events");
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The exclusion, enforced on the exact URL about to be fetched: after the
|
|
199
|
+
* transport has resolved and encoded it, so what is checked is what Planning
|
|
200
|
+
* Center routes. The origin and base path are asserted again here, not taken
|
|
201
|
+
* on trust from the confinement before it.
|
|
202
|
+
*/
|
|
203
|
+
function admit(baseUrl, url, request, excluded) {
|
|
204
|
+
const base = new URL(baseUrl);
|
|
205
|
+
const basePath = base.pathname.replace(/\/+$/, "");
|
|
206
|
+
if (url.origin !== base.origin || !url.pathname.startsWith(`${basePath}/`) || url.hash) {
|
|
207
|
+
throw new ConnectorCallError("invalid_args", "A Planning Center request left the Planning Center API origin.");
|
|
208
|
+
}
|
|
209
|
+
refuseExcluded(literalSegments(url.pathname.slice(basePath.length)), url.searchParams, request, excluded);
|
|
210
|
+
}
|
|
211
|
+
function errorDetail(payload, status) {
|
|
212
|
+
const parts = [];
|
|
213
|
+
for (const entry of asArray(asRecord(payload)["errors"])) {
|
|
214
|
+
const error = asRecord(entry);
|
|
215
|
+
const title = typeof error["title"] === "string" ? error["title"].trim() : "";
|
|
216
|
+
const detail = typeof error["detail"] === "string" ? error["detail"].trim() : "";
|
|
217
|
+
const source = asRecord(error["source"]);
|
|
218
|
+
const at = typeof source["pointer"] === "string"
|
|
219
|
+
? ` (${source["pointer"]})`
|
|
220
|
+
: typeof source["parameter"] === "string"
|
|
221
|
+
? ` (${source["parameter"]})`
|
|
222
|
+
: "";
|
|
223
|
+
const text = [title, detail].filter(Boolean).join(": ");
|
|
224
|
+
if (text)
|
|
225
|
+
parts.push(`${text}${at}`);
|
|
226
|
+
}
|
|
227
|
+
return parts.length > 0
|
|
228
|
+
? `Planning Center: ${parts.join("; ").replace(/[.!?]+$/, "")}.`
|
|
229
|
+
: `Planning Center returned HTTP ${status}.`;
|
|
230
|
+
}
|
|
231
|
+
export const errors = {
|
|
232
|
+
meanings: { unknownId: "not_found", provesAbsence: true, conflicts: ["HTTP 409 for duplicate records in People"] },
|
|
233
|
+
classify: [
|
|
234
|
+
({ status, operation }) => status === 409 && operation.template.startsWith("/people/v2/") ? { code: "conflict" } : undefined,
|
|
235
|
+
],
|
|
236
|
+
numericFacts: {
|
|
237
|
+
retryAfterMs: {
|
|
238
|
+
header: "retry-after",
|
|
239
|
+
format: "retry-after",
|
|
240
|
+
review: "HTTP Retry-After delay for failure recovery.",
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
provider: "Planning Center",
|
|
244
|
+
facts: () => ({}),
|
|
245
|
+
detail: ({ body, status }) => errorDetail(body, status),
|
|
246
|
+
unreadable: "Planning Center returned malformed JSON.",
|
|
247
|
+
bodyFailures: {
|
|
248
|
+
review: "The pinned Planning Center product APIs return JSON. An incomplete bounded body read does not establish malformed JSON; preserve the read phase without exposing the exception or changing status/ceiling classifications.",
|
|
249
|
+
statusOnly: [],
|
|
250
|
+
readFailed: "Planning Center's response body could not be read completely.",
|
|
251
|
+
},
|
|
252
|
+
statuses: {
|
|
253
|
+
401: () => ({
|
|
254
|
+
code: "auth_required",
|
|
255
|
+
words: (detail) => `${detail} The personal access token was rejected: revoked, mistyped, or an Application ID and Secret from different tokens. An operator must replace it.`,
|
|
256
|
+
}),
|
|
257
|
+
403: () => ({
|
|
258
|
+
code: "provider_permission_denied",
|
|
259
|
+
retryable: false,
|
|
260
|
+
words: (detail) => `${detail} The token's Planning Center user lacks permission for this product or record. Permissions belong to that user; an operator must raise them or use another user's token. Retrying will not help.`,
|
|
261
|
+
}),
|
|
262
|
+
404: () => ({
|
|
263
|
+
code: "not_found",
|
|
264
|
+
words: (detail) => `${detail} Confirm the id with its list tool. A person id that used to resolve was probably merged: look it up in /people/v2/person_mergers with where[person_to_remove_id].`,
|
|
265
|
+
}),
|
|
266
|
+
409: () => ({ code: "invalid_args" }),
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
/** All ten product pins retain their own public URL and version. */
|
|
270
|
+
export const PCO_PREFIX = "pco";
|
|
271
|
+
export const PCO_INDEX = new PlanningCenterIndex(openapi);
|
|
272
|
+
export const getPeopleMe = get("getPeopleMe", "/people/v2/me", {
|
|
273
|
+
success: Schema.Unknown,
|
|
274
|
+
query: { include: Schema.String },
|
|
275
|
+
headers: { "X-PCO-API-Version": Schema.String },
|
|
276
|
+
});
|
|
277
|
+
export const PlanningCenterApi = indexedVendor("Planning Center", PCO_INDEX, [getPeopleMe]);
|
|
278
|
+
const reviewedSafety = valueSafety(TABLE, () => PCO_INDEX);
|
|
279
|
+
export const safety = {
|
|
280
|
+
reviewed: restSafety(reviewedSafety, PCO_INDEX),
|
|
281
|
+
// These are permanently omitted, independent of any tool or deployment argument.
|
|
282
|
+
sensitiveFields: { names: ["authenticity_secret", "secret", "ical_code"], choice: "drop" },
|
|
283
|
+
};
|
|
284
|
+
export { PCO_VERDICTS } from "./lib/verdicts.js";
|
|
285
|
+
export const PCO_NAMED = {
|
|
286
|
+
"GET /people/v2/me": "get_me",
|
|
287
|
+
"GET /people/v2/people": "search_people",
|
|
288
|
+
"GET /people/v2/people/{person_id}": "get_person",
|
|
289
|
+
"POST /people/v2/people": "create_person",
|
|
290
|
+
"PATCH /people/v2/people/{person_id}": "update_person",
|
|
291
|
+
"GET /people/v2/workflows": "list_workflows",
|
|
292
|
+
"GET /people/v2/workflows/{workflow_id}/cards": "list_workflow_cards",
|
|
293
|
+
"POST /people/v2/workflows/{workflow_id}/cards": "add_workflow_card",
|
|
294
|
+
"GET /people/v2/people/{person_id}/notes": "list_person_notes",
|
|
295
|
+
"POST /people/v2/people/{person_id}/notes": "add_person_note",
|
|
296
|
+
"GET /people/v2/people/{person_id}/field_data": "list_person_field_data",
|
|
297
|
+
"GET /services/v2/service_types": "list_service_types",
|
|
298
|
+
"GET /services/v2/service_types/{service_type_id}/plans": "list_plans",
|
|
299
|
+
"GET /services/v2/service_types/{service_type_id}/plans/{plan_id}": "get_plan",
|
|
300
|
+
"GET /services/v2/service_types/{service_type_id}/plans/{plan_id}/items": "get_plan",
|
|
301
|
+
"GET /services/v2/service_types/{service_type_id}/plans/{plan_id}/team_members": "get_plan",
|
|
302
|
+
"GET /services/v2/service_types/{service_type_id}/teams": "list_teams",
|
|
303
|
+
"GET /services/v2/people/{person_id}/schedules": "list_person_schedules",
|
|
304
|
+
"POST /services/v2/service_types/{service_type_id}/plans/{plan_id}/team_members": "schedule_plan_person",
|
|
305
|
+
"GET /groups/v2/groups": "list_groups",
|
|
306
|
+
"GET /groups/v2/groups/{group_id}/memberships": "list_group_memberships",
|
|
307
|
+
"POST /groups/v2/groups/{group_id}/memberships": "add_group_member",
|
|
308
|
+
"GET /giving/v2/donations": "list_donations",
|
|
309
|
+
"GET /giving/v2/people/{person_id}/donations": "list_donations",
|
|
310
|
+
"GET /giving/v2/funds": "list_funds",
|
|
311
|
+
"POST /people/v2/people/{person_id}/workflow_cards/{workflow_card_id}/promote": "apply_workflow_card_action",
|
|
312
|
+
"POST /people/v2/people/{person_id}/workflow_cards/{workflow_card_id}/go_back": "apply_workflow_card_action",
|
|
313
|
+
"POST /people/v2/people/{person_id}/workflow_cards/{workflow_card_id}/skip_step": "apply_workflow_card_action",
|
|
314
|
+
"POST /people/v2/people/{person_id}/workflow_cards/{workflow_card_id}/snooze": "apply_workflow_card_action",
|
|
315
|
+
"POST /people/v2/people/{person_id}/workflow_cards/{workflow_card_id}/unsnooze": "apply_workflow_card_action",
|
|
316
|
+
"POST /people/v2/people/{person_id}/workflow_cards/{workflow_card_id}/remove": "apply_workflow_card_action",
|
|
317
|
+
"POST /people/v2/people/{person_id}/workflow_cards/{workflow_card_id}/restore": "apply_workflow_card_action",
|
|
318
|
+
};
|
|
319
|
+
/** Native collection offset metadata, read only after response safety. */
|
|
320
|
+
export const pcoPage = {
|
|
321
|
+
param: "offset",
|
|
322
|
+
in: "query",
|
|
323
|
+
total: (data) => pageOf(asRecord(data)).totalCount ?? undefined,
|
|
324
|
+
decode: (value) => {
|
|
325
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
326
|
+
throw new ConnectorCallError("invalid_args", "This Planning Center cursor contains no valid offset.");
|
|
327
|
+
return value;
|
|
328
|
+
},
|
|
329
|
+
next: (data, call) => {
|
|
330
|
+
const document = asRecord(data);
|
|
331
|
+
if (document["meta"] === undefined)
|
|
332
|
+
return undefined;
|
|
333
|
+
return pageOf(document, Number(call.query["offset"] ?? 0)).nextOffset;
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
export const PCO_PAGES = Object.fromEntries(PCO_INDEX.ops
|
|
337
|
+
.filter((op) => op.method === "GET" && PCO_INDEX.contract(op).parameters.some((p) => p.in === "query" && p.name === "offset"))
|
|
338
|
+
.map((op) => [`GET ${op.path}`, pcoPage]));
|
|
339
|
+
export function transport(options) {
|
|
340
|
+
const userAgent = options.userAgent?.trim() ?? DEFAULT_USER_AGENT;
|
|
341
|
+
if (!userAgent || /[\r\n]/.test(userAgent))
|
|
342
|
+
throw new Error("planningCenter() userAgent must be a non-empty single line.");
|
|
343
|
+
const excluded = excludedApps(options.excludeApps);
|
|
344
|
+
const baseUrl = options.baseUrl?.trim() || PLANNING_CENTER_API_BASE_URL;
|
|
345
|
+
return {
|
|
346
|
+
headers: { Accept: "application/json", "User-Agent": userAgent },
|
|
347
|
+
...(excluded.size > 0
|
|
348
|
+
? {
|
|
349
|
+
admit: (url, request) => Effect.try({
|
|
350
|
+
try: () => admit(baseUrl, url, {
|
|
351
|
+
method: request.method,
|
|
352
|
+
path: url.pathname,
|
|
353
|
+
...(request.body === undefined ? {} : { body: JSON.parse(request.body) }),
|
|
354
|
+
}, excluded),
|
|
355
|
+
catch: (error) => {
|
|
356
|
+
if (error instanceof ConnectorCallError)
|
|
357
|
+
return new Refusal({ error });
|
|
358
|
+
throw error;
|
|
359
|
+
},
|
|
360
|
+
}),
|
|
361
|
+
}
|
|
362
|
+
: {}),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
export function planningCenterCall(connection, options) {
|
|
366
|
+
const excluded = excludedApps(options.excludeApps);
|
|
367
|
+
const apps = PLANNING_CENTER_APPS.filter((app) => !excluded.has(app));
|
|
368
|
+
return (request, step) => Effect.gen(function* () {
|
|
369
|
+
const app = yield* Effect.try({
|
|
370
|
+
try: () => {
|
|
371
|
+
if (excluded.size > 0)
|
|
372
|
+
refuseExcluded(literalSegments(request.path), [], request, excluded);
|
|
373
|
+
return appForPath(request.path, apps);
|
|
374
|
+
},
|
|
375
|
+
catch: (error) => {
|
|
376
|
+
if (error instanceof ConnectorCallError)
|
|
377
|
+
return new Refusal({ error });
|
|
378
|
+
throw error;
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
const answer = yield* connection.sendPath({
|
|
382
|
+
method: request.method,
|
|
383
|
+
path: request.path,
|
|
384
|
+
template: request.path,
|
|
385
|
+
...(request.query
|
|
386
|
+
? {
|
|
387
|
+
query: Object.fromEntries(Object.entries(request.query)
|
|
388
|
+
.filter(([, v]) => v !== undefined)
|
|
389
|
+
.map(([k, v]) => [k, String(v)])),
|
|
390
|
+
}
|
|
391
|
+
: {}),
|
|
392
|
+
...(request.body === undefined ? {} : { json: JSON.stringify(request.body) }),
|
|
393
|
+
headers: { "X-PCO-API-Version": PLANNING_CENTER_API_VERSIONS[app] },
|
|
394
|
+
}, step === undefined ? undefined : { step });
|
|
395
|
+
return answer.data;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
export const probe = {
|
|
399
|
+
operation: getPeopleMe,
|
|
400
|
+
request: {
|
|
401
|
+
query: { include: "organization" },
|
|
402
|
+
headers: { "X-PCO-API-Version": PLANNING_CENTER_API_VERSIONS.people },
|
|
403
|
+
},
|
|
404
|
+
describe: (reply) => {
|
|
405
|
+
const document = asRecord(reply);
|
|
406
|
+
const me = resources(document["data"])[0];
|
|
407
|
+
if (!me)
|
|
408
|
+
throw new ConnectorCallError("connector_call_failed", "Planning Center answered without the person record it should return.", { retryable: false });
|
|
409
|
+
const organization = resources(document["included"]).find((row) => row.type === "Organization");
|
|
410
|
+
const name = attribute(me, "name") ?? `person ${me.id}`;
|
|
411
|
+
const where = attribute(organization, "name");
|
|
412
|
+
return `Authenticated as ${String(name)} (person ${me.id})${where ? ` in ${String(where)}` : ""}.`;
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
/** Product-version framing over the shared hatch's admission and projection seam. */
|
|
416
|
+
export function pcoBinding({ connection, options, named = PCO_NAMED, }) {
|
|
417
|
+
const excluded = excludedApps(options.excludeApps);
|
|
418
|
+
const apps = PLANNING_CENTER_APPS.filter((app) => !excluded.has(app));
|
|
419
|
+
return {
|
|
420
|
+
named,
|
|
421
|
+
review: "The ten reviewed JSON:API product pins each require their own X-PCO-API-Version header. Preserve scalar query values/repeated pairs, exact-URL exclusions and Connection.sendPath safety on every dispatch.",
|
|
422
|
+
refusal: (op) => PCO_REFUSALS[`${op.method} ${op.path}`],
|
|
423
|
+
refuse: (call) => {
|
|
424
|
+
const refusal = PCO_REFUSALS[`${call.op.method} ${call.op.path}`];
|
|
425
|
+
if (refusal)
|
|
426
|
+
return refusal;
|
|
427
|
+
// The shared hatch keeps its other-server guard. This binding is JSON
|
|
428
|
+
// only, stricter than the hatch's default JSON-or-form body admission.
|
|
429
|
+
const type = PCO_INDEX.bodyType(call.op)?.split(";", 1)[0].trim().toLowerCase();
|
|
430
|
+
return type && !/^application\/(?:[^/]+\+)?json$/.test(type)
|
|
431
|
+
? "This Planning Center operation takes a non-JSON body, which this connection does not send."
|
|
432
|
+
: undefined;
|
|
433
|
+
},
|
|
434
|
+
send: (call, sendOptions) => Effect.gen(function* () {
|
|
435
|
+
const request = yield* Effect.try({
|
|
436
|
+
try: () => {
|
|
437
|
+
const query = Object.entries(call.query).flatMap(([name, value]) => value === undefined
|
|
438
|
+
? []
|
|
439
|
+
: (Array.isArray(value) ? value : [value]).map((item) => {
|
|
440
|
+
if (typeof item !== "string" &&
|
|
441
|
+
typeof item !== "boolean" &&
|
|
442
|
+
!(typeof item === "number" && Number.isFinite(item)))
|
|
443
|
+
throw new ConnectorCallError("invalid_args", `Planning Center parameter ${name} takes scalar values.`);
|
|
444
|
+
return [name, String(item)];
|
|
445
|
+
}));
|
|
446
|
+
if (excluded.size > 0)
|
|
447
|
+
refuseExcluded(literalSegments(call.path), query, call, excluded);
|
|
448
|
+
const app = appForPath(call.path, apps);
|
|
449
|
+
return {
|
|
450
|
+
method: call.method,
|
|
451
|
+
path: call.path,
|
|
452
|
+
template: call.op.path,
|
|
453
|
+
query,
|
|
454
|
+
headers: { "X-PCO-API-Version": PLANNING_CENTER_API_VERSIONS[app] },
|
|
455
|
+
...(call.body === undefined ? {} : { json: JSON.stringify(call.body) }),
|
|
456
|
+
};
|
|
457
|
+
},
|
|
458
|
+
catch: (error) => {
|
|
459
|
+
if (error instanceof ConnectorCallError)
|
|
460
|
+
return new Refusal({ error });
|
|
461
|
+
throw error;
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
return yield* connection.sendPath(request, sendOptions);
|
|
465
|
+
}),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
@@ -1,38 +1,7 @@
|
|
|
1
1
|
import type { ConnectorCallAdmissionPolicy } from "../../types.js";
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
export declare const PLANNING_CENTER_APPS: readonly ["people", "services", "groups", "check-ins", "calendar", "registrations", "giving", "publishing", "webhooks", "api"];
|
|
6
|
-
export type PlanningCenterApp = (typeof PLANNING_CENTER_APPS)[number];
|
|
7
|
-
/**
|
|
8
|
-
* A product a deployment may remove from this connection. People is the one
|
|
9
|
-
* it may not: every product joins through its person ids, and the credential
|
|
10
|
-
* test reads `/people/v2/me`.
|
|
11
|
-
*/
|
|
12
|
-
export type PlanningCenterExcludableApp = Exclude<PlanningCenterApp, "people">;
|
|
13
|
-
/**
|
|
14
|
-
* One reviewed `X-PCO-API-Version` per product, sent on every request.
|
|
15
|
-
*
|
|
16
|
-
* The header is optional, and omitting it is the trap: Planning Center then
|
|
17
|
-
* applies a per-application default chosen in a web dashboard, so behavior
|
|
18
|
-
* would follow whoever last clicked there rather than this file. Each date is
|
|
19
|
-
* the newest non-beta version in the product's documentation graph on
|
|
20
|
-
* 2026-10-05, and every projection below is written against it. A breaking
|
|
21
|
-
* change arrives as a new dated version, never inside this one, so moving a
|
|
22
|
-
* pin is a reviewed change to this table, its drift manifest, and whatever
|
|
23
|
-
* projection the version's change list touches. Notes worth keeping:
|
|
24
|
-
*
|
|
25
|
-
* - groups `2023-07-10` removed every person attribute from `Membership`, so a
|
|
26
|
-
* membership is nameless without `include=person`.
|
|
27
|
-
* - calendar `2026-06-22` may return `BookedEvent*` stubs in place of events on
|
|
28
|
-
* booking and conflict traversals for users without access to the event.
|
|
29
|
-
* - webhooks `2022-10-20` renamed `Subscription` to `WebhookSubscription`.
|
|
30
|
-
* - services `2018-11-01` and giving `2019-10-18` are simply the newest there is.
|
|
31
|
-
*
|
|
32
|
-
* The hatches send the same pin for the app their path names and accept a
|
|
33
|
-
* per-call `version` override for an endpoint only a newer version serves.
|
|
34
|
-
*/
|
|
35
|
-
export declare const PLANNING_CENTER_API_VERSIONS: Readonly<Record<PlanningCenterApp, string>>;
|
|
2
|
+
import type { PlanningCenterExcludableApp } from "./lib/constants.js";
|
|
3
|
+
export { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS } from "./lib/constants.js";
|
|
4
|
+
export type { PlanningCenterApp, PlanningCenterExcludableApp } from "./lib/constants.js";
|
|
36
5
|
export interface PlanningCenterOptions {
|
|
37
6
|
/** Which organization this is and what it should be used for. Required. */
|
|
38
7
|
purpose: string;
|
|
@@ -84,5 +53,5 @@ export interface PlanningCenterOptions {
|
|
|
84
53
|
/** API origin override for a proxy or test double. */
|
|
85
54
|
baseUrl?: string;
|
|
86
55
|
}
|
|
87
|
-
/**
|
|
56
|
+
/** Planning Center over the reviewed product pins and standard agent surface. */
|
|
88
57
|
export declare const planningCenter: import("../../provider.js").ProviderFactory<PlanningCenterOptions>;
|