@zackbart/connecta 0.34.1 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +363 -0
- package/README.md +62 -106
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2051 -1160
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +663 -271
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +12 -7
- package/dist/connectors/api-connector.js +142 -56
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/remote-mcp.d.ts +10 -30
- package/dist/connectors/remote-mcp.js +955 -563
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +2 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +228 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +383 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +218 -85
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +531 -406
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/agents.d.ts +5 -4
- package/dist/operator-ui/agents.js +17 -7
- package/dist/operator-ui/config-summary.js +5 -4
- package/dist/operator-ui/connection-map.js +7 -5
- package/dist/operator-ui/contract.d.ts +7 -3
- package/dist/operator-ui/endpoints.js +4 -13
- package/dist/operator-ui/fix-prompts.d.ts +1 -1
- package/dist/operator-ui/fix-prompts.js +0 -8
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/operator-ui/overview.js +5 -2
- package/dist/operator-ui/setup-commands.js +0 -4
- package/dist/operator-ui/view.d.ts +2 -25
- package/dist/operator-ui/view.js +2 -76
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +65 -31
- package/dist/provider.js +116 -39
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +537 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +6 -210
- package/dist/providers/_shared/google/workspace.js +6 -425
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/dispatch.js +3 -23
- package/dist/providers/_shared/rest/kit.js +546 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/tools.js +101 -78
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +1 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +15 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +5 -22
- package/dist/providers/cloudflare/index.js +79 -197
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +4 -3
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +802 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1024 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +1 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +4 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +734 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +5 -27
- package/dist/providers/stripe/index.js +74 -210
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -11
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +17 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +9 -25
- package/dist/providers/vercel/index.js +66 -260
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +10 -5
- package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +21 -15
- package/dist/registry.js +409 -376
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +3 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +21 -20
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +556 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +322 -222
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +7 -109
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +27 -7
- package/documentation/architecture.md +2547 -226
- package/documentation/auth.md +821 -98
- package/documentation/code-mode.md +49 -16
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +129 -144
- package/documentation/meta-tools.md +409 -32
- package/documentation/operating.md +27 -3
- package/documentation/operator-tests.md +3 -0
- package/documentation/operator-ui.md +68 -45
- package/documentation/provider-auth-migration.md +16 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +965 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +27 -46
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -138
- package/dist/catalog-drift.js +0 -578
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -45
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1097
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/intercom/index.d.ts +0 -24
- package/dist/providers/intercom/index.js +0 -121
- package/dist/providers/intercom/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -537
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -59
- package/dist/providers/mixpanel/index.js +0 -472
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -67
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -51
- package/dist/providers/revenuecat/index.js +0 -697
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
// Tithe.ly's giving API (v1) as a kit vendor (decision 0010): the operations,
|
|
2
|
+
// what its statuses mean, its credential, its in-band refusal, the probe, and
|
|
3
|
+
// the value-safety review. Facts from the per-operation reference at
|
|
4
|
+
// https://docs.tithe.ly/reference/introduction, each marked where it is
|
|
5
|
+
// observation instead:
|
|
6
|
+
//
|
|
7
|
+
// - **HTTP Basic auth**, public key as user and private key as password.
|
|
8
|
+
// - **Envelopes vary by operation.** Lists answer `{status, type, data: []}`;
|
|
9
|
+
// single reads and writes answer `{status, <kind>_id, type, object}`; the
|
|
10
|
+
// reference's charge examples show a bare object.
|
|
11
|
+
// - **Refusals arrive as HTTP 200** `{status: "fail", reason}` with no code.
|
|
12
|
+
// The reference documents no error shape at all; this one comes from the
|
|
13
|
+
// recorded fixtures of the third-party Omnipay driver
|
|
14
|
+
// (github.com/elvanto/omnipay-tithely), which also confirms the charge
|
|
15
|
+
// envelope and form-encoded request bodies. Treat it as well-supported
|
|
16
|
+
// observation, not contract.
|
|
17
|
+
// - **Writes are form-encoded.** Each operation snippet declares an
|
|
18
|
+
// `application/json` body (ReadMe's default), but every curl sample sends
|
|
19
|
+
// `-d field=value`, and so does the Omnipay driver.
|
|
20
|
+
// - **Cursor pagination** by id: `starting_after` / `ending_before` with
|
|
21
|
+
// `limit` (default 10; the charges page documents a maximum of 100) and
|
|
22
|
+
// `order_by` DESC|ASC. No `has_more` is returned.
|
|
23
|
+
// - **Not targeted:** an undocumented v2 (`https://tithe.ly/api/v2`) exists
|
|
24
|
+
// but is not public. The guarded client confines every path below
|
|
25
|
+
// `/api/v1`, so the hatch cannot reach it either.
|
|
26
|
+
//
|
|
27
|
+
// Drift: there is no combined specification, but each reference page served
|
|
28
|
+
// as `.md` embeds a one-operation OpenAPI 3.1 snippet; drift.json names the
|
|
29
|
+
// page of every operation declared here.
|
|
30
|
+
import { Effect, Schema } from "effect";
|
|
31
|
+
import { ConnectorCallError, resendAdvice } from "../../errors.js";
|
|
32
|
+
import { Refusal } from "../../http/errors.js";
|
|
33
|
+
import { Integer } from "../../http/json-schema.js";
|
|
34
|
+
import { Credentials } from "../../http/services.js";
|
|
35
|
+
import { del, form, get, post, Summary, vendor } from "../../http/vendor.js";
|
|
36
|
+
import { vendorIndex } from "../../http/vendor-index.js";
|
|
37
|
+
// --- Operations ------------------------------------------------------------------
|
|
38
|
+
// Request contracts follow each operation's reference page; the hatch's index
|
|
39
|
+
// is derived from these declarations, so they are the one operation list.
|
|
40
|
+
// Success schemas describe the documented records leniently, for the manifest
|
|
41
|
+
// and hatch search only: every key is optional and nothing decodes with them.
|
|
42
|
+
const loose = (fields) => Schema.Struct(Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, Schema.optionalKey(value)])));
|
|
43
|
+
const Address = loose({
|
|
44
|
+
street_address: Schema.String,
|
|
45
|
+
city: Schema.String,
|
|
46
|
+
state: Schema.String,
|
|
47
|
+
postal: Schema.String,
|
|
48
|
+
country: Schema.String,
|
|
49
|
+
});
|
|
50
|
+
const OrganizationRecord = loose({
|
|
51
|
+
organization_id: Schema.String,
|
|
52
|
+
widget_id: Schema.String,
|
|
53
|
+
account_id: Schema.String,
|
|
54
|
+
created_date: Schema.String,
|
|
55
|
+
name: Schema.String,
|
|
56
|
+
phone_number: Schema.String,
|
|
57
|
+
website: Schema.String,
|
|
58
|
+
address: Address,
|
|
59
|
+
giving_types: Schema.Array(Schema.String),
|
|
60
|
+
giving_types_full: Schema.Array(loose({ id: Schema.String, name: Schema.String, status: Schema.String })),
|
|
61
|
+
bank: loose({
|
|
62
|
+
account_number_last4: Schema.String,
|
|
63
|
+
name: Schema.String,
|
|
64
|
+
country: Schema.String,
|
|
65
|
+
currency: Schema.String,
|
|
66
|
+
}),
|
|
67
|
+
legal: loose({
|
|
68
|
+
entity_type: Schema.String,
|
|
69
|
+
organization_name: Schema.String,
|
|
70
|
+
first_name: Schema.String,
|
|
71
|
+
last_name: Schema.String,
|
|
72
|
+
date_of_birth: Schema.String,
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
const AccountRecord = loose({
|
|
76
|
+
account_id: Schema.String,
|
|
77
|
+
created_date: Schema.String,
|
|
78
|
+
email: Schema.String,
|
|
79
|
+
first_name: Schema.String,
|
|
80
|
+
last_name: Schema.String,
|
|
81
|
+
address: Address,
|
|
82
|
+
phone_number: Schema.String,
|
|
83
|
+
});
|
|
84
|
+
const PaymentMethodRecord = loose({
|
|
85
|
+
pm_id: Schema.String,
|
|
86
|
+
last_4_digits: Schema.String,
|
|
87
|
+
brand: Schema.String,
|
|
88
|
+
pm_type: Schema.String,
|
|
89
|
+
account_id: Schema.String,
|
|
90
|
+
});
|
|
91
|
+
const ChargeRecord = loose({
|
|
92
|
+
charge_id: Schema.String,
|
|
93
|
+
charge_status: Schema.String,
|
|
94
|
+
amount: Schema.String,
|
|
95
|
+
net_amount: Schema.String,
|
|
96
|
+
fees: Schema.String,
|
|
97
|
+
currency: Schema.String,
|
|
98
|
+
giving_type: Schema.String,
|
|
99
|
+
charge_date: Schema.String,
|
|
100
|
+
deposit_date: Schema.String,
|
|
101
|
+
recurring_transaction: Schema.Boolean,
|
|
102
|
+
fees_covered: Schema.Boolean,
|
|
103
|
+
memo: Schema.String,
|
|
104
|
+
organization: OrganizationRecord,
|
|
105
|
+
donor_account: AccountRecord,
|
|
106
|
+
payment_method: PaymentMethodRecord,
|
|
107
|
+
});
|
|
108
|
+
const RecurringRecord = loose({
|
|
109
|
+
recurring_id: Schema.String,
|
|
110
|
+
amount: Schema.String,
|
|
111
|
+
currency: Schema.String,
|
|
112
|
+
giving_type: Schema.String,
|
|
113
|
+
start_date: Schema.String,
|
|
114
|
+
term: Schema.String,
|
|
115
|
+
organization: OrganizationRecord,
|
|
116
|
+
donor_account: AccountRecord,
|
|
117
|
+
payment_method: Schema.String,
|
|
118
|
+
});
|
|
119
|
+
const Envelope = Schema.Struct({ status: Schema.optionalKey(Schema.String), type: Schema.optionalKey(Schema.String) });
|
|
120
|
+
const Single = (id, record) => Schema.Struct({ ...Envelope.fields, [id]: Schema.optionalKey(Schema.String), object: Schema.optionalKey(record) });
|
|
121
|
+
const Rows = (record) => Schema.Struct({ ...Envelope.fields, data: Schema.optionalKey(Schema.Array(record)) });
|
|
122
|
+
const Note = Schema.Struct({ ...Envelope.fields, note: Schema.optionalKey(Schema.String) });
|
|
123
|
+
// A charge answer arrives either way: the reference documents the bare record for retrieve,
|
|
124
|
+
// create, quick charge and refund, and Tithe.ly has also answered in the single-record envelope.
|
|
125
|
+
// The hatch admits `fields` roots from both.
|
|
126
|
+
const ChargeAnswer = Schema.Union([ChargeRecord, Single("charge_id", ChargeRecord)]);
|
|
127
|
+
// Key annotations survive the form and query encodings the manifest renders.
|
|
128
|
+
const described = (schema, description) => schema.pipe(Schema.annotateKey({ description }));
|
|
129
|
+
const field = (description) => described(Schema.String, description);
|
|
130
|
+
const optionalField = (description) => described(Schema.optionalKey(Schema.String), description);
|
|
131
|
+
const PageQuery = {
|
|
132
|
+
limit: described(Schema.optionalKey(Integer), "Rows to return: default 10, at most 100."),
|
|
133
|
+
order_by: described(Schema.optionalKey(Schema.Literals(["DESC", "ASC"])), "DESC (default) or ASC by creation."),
|
|
134
|
+
starting_after: optionalField("Return rows after this id."),
|
|
135
|
+
ending_before: optionalField("Return rows before this id."),
|
|
136
|
+
};
|
|
137
|
+
const ScopeQuery = {
|
|
138
|
+
organization_id: optionalField("Organization (org_…); this or account_id is required."),
|
|
139
|
+
account_id: optionalField("Donor account (user_…); this or organization_id is required."),
|
|
140
|
+
};
|
|
141
|
+
const id = (name) => ({ [name]: Schema.String });
|
|
142
|
+
const Cents = described(Integer, "Amount in integer cents, e.g. 2555 for $25.55.");
|
|
143
|
+
const Fund = field("Giving type (fund) name, e.g. General Fund.");
|
|
144
|
+
const Contact = {
|
|
145
|
+
street_address: optionalField("Street address."),
|
|
146
|
+
city: optionalField("City."),
|
|
147
|
+
state: optionalField("State or province, abbreviated (2 to 3 characters)."),
|
|
148
|
+
postal: optionalField("Postal or zip code."),
|
|
149
|
+
country: optionalField("Two-letter country code."),
|
|
150
|
+
phone_number: optionalField("Phone number, with country code outside the US."),
|
|
151
|
+
};
|
|
152
|
+
const AccountLogin = {
|
|
153
|
+
email: field("Email address."),
|
|
154
|
+
password: field("At least 6 characters with letters and numbers."),
|
|
155
|
+
pin: field("Four-digit numeric PIN."),
|
|
156
|
+
first_name: field("First name."),
|
|
157
|
+
last_name: field("Last name."),
|
|
158
|
+
};
|
|
159
|
+
// The named reads. Path parameters keep the reference's names, so the manifest
|
|
160
|
+
// spells each operation as its page does.
|
|
161
|
+
export const listOrganizations = get("listOrganizations", "/organizations-list", {
|
|
162
|
+
query: PageQuery,
|
|
163
|
+
success: Rows(OrganizationRecord),
|
|
164
|
+
}).annotate(Summary, "List all organizations these keys manage");
|
|
165
|
+
export const getOrganization = get("getOrganization", "/organizations/:organization_id", {
|
|
166
|
+
params: id("organization_id"),
|
|
167
|
+
success: Single("organization_id", OrganizationRecord),
|
|
168
|
+
}).annotate(Summary, "Retrieve an organization");
|
|
169
|
+
export const listAccounts = get("listAccounts", "/accounts-list", {
|
|
170
|
+
query: PageQuery,
|
|
171
|
+
success: Rows(AccountRecord),
|
|
172
|
+
}).annotate(Summary, "List donor accounts created via the API");
|
|
173
|
+
export const listAllAccounts = get("listAllAccounts", "/accounts-list-all", {
|
|
174
|
+
query: PageQuery,
|
|
175
|
+
success: Rows(AccountRecord),
|
|
176
|
+
}).annotate(Summary, "List all donor accounts");
|
|
177
|
+
export const getAccount = get("getAccount", "/accounts/:account_id", {
|
|
178
|
+
params: id("account_id"),
|
|
179
|
+
success: Single("account_id", AccountRecord),
|
|
180
|
+
}).annotate(Summary, "Retrieve a donor account");
|
|
181
|
+
export const listCharges = get("listCharges", "/charges-list", {
|
|
182
|
+
// Field order is wire order: the filters, then the paging fields.
|
|
183
|
+
query: {
|
|
184
|
+
...ScopeQuery,
|
|
185
|
+
created_after: described(Schema.optionalKey(Integer), "Only charges created after this Unix time."),
|
|
186
|
+
created_before: described(Schema.optionalKey(Integer), "Only charges created before this Unix time."),
|
|
187
|
+
...PageQuery,
|
|
188
|
+
},
|
|
189
|
+
success: Rows(ChargeRecord),
|
|
190
|
+
}).annotate(Summary, "List charges (gifts) for an organization or donor");
|
|
191
|
+
export const getCharge = get("getCharge", "/charges/:charge_id", {
|
|
192
|
+
params: id("charge_id"),
|
|
193
|
+
success: ChargeAnswer,
|
|
194
|
+
}).annotate(Summary, "Retrieve a charge");
|
|
195
|
+
export const listRecurring = get("listRecurring", "/recurring-list", {
|
|
196
|
+
query: { ...ScopeQuery, ...PageQuery },
|
|
197
|
+
success: Rows(RecurringRecord),
|
|
198
|
+
}).annotate(Summary, "List recurring charges (gifts) for an organization or donor");
|
|
199
|
+
export const getRecurring = get("getRecurring", "/recurring/:recurring_id", {
|
|
200
|
+
params: id("recurring_id"),
|
|
201
|
+
success: Single("recurring_id", RecurringRecord),
|
|
202
|
+
}).annotate(Summary, "Retrieve a recurring charge");
|
|
203
|
+
export const listPaymentMethods = get("listPaymentMethods", "/payment-methods-list", {
|
|
204
|
+
query: { account_id: field("Donor account (user_…) that owns the payment methods.") },
|
|
205
|
+
success: Rows(PaymentMethodRecord),
|
|
206
|
+
}).annotate(Summary, "List a donor's payment methods");
|
|
207
|
+
/**
|
|
208
|
+
* The vendor: the named reads, and the documented operations only the hatch
|
|
209
|
+
* reaches by path, so drift checks and the hatch's index cover every
|
|
210
|
+
* operation an agent can send.
|
|
211
|
+
*/
|
|
212
|
+
export const TithelyApi = vendor("Tithe.ly", listOrganizations, getOrganization, listAccounts, listAllAccounts, getAccount, post("createAccount", "/accounts", {
|
|
213
|
+
payload: form(Schema.Struct({ ...AccountLogin, ...Contact })),
|
|
214
|
+
success: Single("account_id", AccountRecord),
|
|
215
|
+
}).annotate(Summary, "Create a donor account"), post("updateAccount", "/accounts/:account_id", {
|
|
216
|
+
params: id("account_id"),
|
|
217
|
+
payload: form(Schema.Struct({
|
|
218
|
+
email: Schema.optionalKey(AccountLogin.email),
|
|
219
|
+
password: Schema.optionalKey(AccountLogin.password),
|
|
220
|
+
pin: Schema.optionalKey(AccountLogin.pin),
|
|
221
|
+
first_name: Schema.optionalKey(AccountLogin.first_name),
|
|
222
|
+
last_name: Schema.optionalKey(AccountLogin.last_name),
|
|
223
|
+
...Contact,
|
|
224
|
+
})),
|
|
225
|
+
success: Single("account_id", AccountRecord),
|
|
226
|
+
}).annotate(Summary, "Update a donor account"), listCharges, getCharge, post("createCharge", "/charges", {
|
|
227
|
+
payload: form(Schema.Struct({
|
|
228
|
+
account_id: field("Donor account (user_…)."),
|
|
229
|
+
pm_id: field("Payment method (pm_…) to charge."),
|
|
230
|
+
organization_id: field("Organization (org_…) to give to."),
|
|
231
|
+
amount: Cents,
|
|
232
|
+
giving_type: Fund,
|
|
233
|
+
memo: optionalField("A memo or note for the gift."),
|
|
234
|
+
})),
|
|
235
|
+
success: ChargeAnswer,
|
|
236
|
+
}).annotate(Summary, "Create a charge (gift) on a donor's stored payment method"), post("chargeOnce", "/charge-once", {
|
|
237
|
+
payload: form(Schema.Struct({
|
|
238
|
+
email: field("Donor email."),
|
|
239
|
+
first_name: field("Donor first name."),
|
|
240
|
+
last_name: field("Donor last name."),
|
|
241
|
+
token: field("Card or bank token from Tithely.js in the donor's browser."),
|
|
242
|
+
organization_id: field("Organization (org_…) to give to."),
|
|
243
|
+
amount: Cents,
|
|
244
|
+
giving_type: Fund,
|
|
245
|
+
...Contact,
|
|
246
|
+
memo: optionalField("A memo or note for the gift."),
|
|
247
|
+
})),
|
|
248
|
+
success: ChargeAnswer,
|
|
249
|
+
}).annotate(Summary, "Quick charge a donor once from a Tithely.js token, without an account"), post("refundCharge", "/refunds/:charge_id", {
|
|
250
|
+
params: id("charge_id"),
|
|
251
|
+
success: ChargeAnswer,
|
|
252
|
+
}).annotate(Summary, "Refund a charge"), listRecurring, getRecurring, post("createRecurring", "/recurring", {
|
|
253
|
+
payload: form(Schema.Struct({
|
|
254
|
+
account_id: field("Donor account (user_…)."),
|
|
255
|
+
pm_id: field("Payment method (pm_…) to charge each term."),
|
|
256
|
+
organization_id: field("Organization (org_…) to give to."),
|
|
257
|
+
amount: Cents,
|
|
258
|
+
giving_type: Fund,
|
|
259
|
+
term: described(Schema.Literals(["weekly", "monthly", "bimonthly", "fortnightly"]), "How often to charge."),
|
|
260
|
+
start_date: described(Integer, "Unix time of the first charge."),
|
|
261
|
+
})),
|
|
262
|
+
success: Single("recurring_id", RecurringRecord),
|
|
263
|
+
}).annotate(Summary, "Create a recurring charge (gift)"), del("cancelRecurring", "/recurring/:recurring_id", { params: id("recurring_id"), success: Note }).annotate(Summary, "Cancel a recurring charge's future charges; refunds nothing"), listPaymentMethods, get("getPaymentMethod", "/payment-methods/:pm_id", {
|
|
264
|
+
params: id("pm_id"),
|
|
265
|
+
query: { account_id: field("Donor account (user_…) that owns the payment method.") },
|
|
266
|
+
success: Single("pm_id", PaymentMethodRecord),
|
|
267
|
+
}).annotate(Summary, "Retrieve a payment method"), post("createPaymentMethod", "/payment-methods", {
|
|
268
|
+
payload: form(Schema.Struct({
|
|
269
|
+
account_id: field("Donor account (user_…)."),
|
|
270
|
+
token: field("Card or bank token from Tithely.js in the donor's browser."),
|
|
271
|
+
})),
|
|
272
|
+
success: Single("pm_id", PaymentMethodRecord),
|
|
273
|
+
}).annotate(Summary, "Create a payment method from a Tithely.js token"), post("updatePaymentMethod", "/payment-methods/:pm_id", {
|
|
274
|
+
params: id("pm_id"),
|
|
275
|
+
payload: form(Schema.Struct({
|
|
276
|
+
account_id: field("Donor account (user_…) that owns the payment method."),
|
|
277
|
+
exp_month: optionalField("Card expiration month."),
|
|
278
|
+
exp_year: optionalField("Card expiration year."),
|
|
279
|
+
postal_code: optionalField("Card billing postal code."),
|
|
280
|
+
account_holder_name: optionalField("Bank account holder name."),
|
|
281
|
+
account_holder_type: optionalField("Bank account holder type: individual or company."),
|
|
282
|
+
})),
|
|
283
|
+
success: Single("pm_id", PaymentMethodRecord),
|
|
284
|
+
}).annotate(Summary, "Update a payment method"), del("deletePaymentMethod", "/payment-methods/:pm_id", {
|
|
285
|
+
params: id("pm_id"),
|
|
286
|
+
query: { account_id: field("Donor account (user_…) that owns the payment method.") },
|
|
287
|
+
success: Note,
|
|
288
|
+
}).annotate(Summary, "Delete a payment method"));
|
|
289
|
+
/** The hatch's operation index, derived from the vendor above. */
|
|
290
|
+
export const TITHELY_INDEX = vendorIndex(TithelyApi, { prefix: "tithely", title: "Tithe.ly", revision: "v1" });
|
|
291
|
+
/**
|
|
292
|
+
* Every operation's hatch verdict. Reads are reads. Money movement, recurring
|
|
293
|
+
* gifts and deleting a payment method are destructive; creating or updating
|
|
294
|
+
* a donor account or payment method is a write. Every write goes through
|
|
295
|
+
* `tithely_api_write`, which is always approval-gated.
|
|
296
|
+
*/
|
|
297
|
+
export const TITHELY_VERDICTS = {
|
|
298
|
+
"GET /organizations-list": "read",
|
|
299
|
+
"GET /organizations/{organization_id}": "read",
|
|
300
|
+
"GET /accounts-list": "read",
|
|
301
|
+
"GET /accounts-list-all": "read",
|
|
302
|
+
"GET /accounts/{account_id}": "read",
|
|
303
|
+
"POST /accounts": "write",
|
|
304
|
+
"POST /accounts/{account_id}": "write",
|
|
305
|
+
"GET /charges-list": "read",
|
|
306
|
+
"GET /charges/{charge_id}": "read",
|
|
307
|
+
"POST /charges": "destructive",
|
|
308
|
+
"POST /charge-once": "destructive",
|
|
309
|
+
"POST /refunds/{charge_id}": "destructive",
|
|
310
|
+
"GET /recurring-list": "read",
|
|
311
|
+
"GET /recurring/{recurring_id}": "read",
|
|
312
|
+
"POST /recurring": "destructive",
|
|
313
|
+
"DELETE /recurring/{recurring_id}": "destructive",
|
|
314
|
+
"GET /payment-methods-list": "read",
|
|
315
|
+
"GET /payment-methods/{pm_id}": "read",
|
|
316
|
+
"POST /payment-methods": "write",
|
|
317
|
+
"POST /payment-methods/{pm_id}": "write",
|
|
318
|
+
"DELETE /payment-methods/{pm_id}": "destructive",
|
|
319
|
+
};
|
|
320
|
+
/** The named read serving each operation; hatch search points at it. */
|
|
321
|
+
export const TITHELY_NAMED = {
|
|
322
|
+
"GET /organizations-list": "list_organizations",
|
|
323
|
+
"GET /accounts-list": "list_accounts",
|
|
324
|
+
"GET /accounts-list-all": "list_accounts",
|
|
325
|
+
"GET /charges-list": "list_charges",
|
|
326
|
+
"GET /charges/{charge_id}": "get_charge",
|
|
327
|
+
"GET /recurring-list": "list_recurring_charges",
|
|
328
|
+
"GET /recurring/{recurring_id}": "get_recurring_charge",
|
|
329
|
+
"GET /payment-methods-list": "list_payment_methods",
|
|
330
|
+
};
|
|
331
|
+
/** A full list page of fully embedded organizations stays far below this. */
|
|
332
|
+
export const TITHELY_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
333
|
+
export const DEFAULT_PAGE_SIZE = 25;
|
|
334
|
+
/**
|
|
335
|
+
* One below Tithe.ly's documented maximum of 100, because each list asks for
|
|
336
|
+
* one extra row to learn whether another page exists: Tithe.ly returns no
|
|
337
|
+
* `has_more`, and a full page is not proof of a next one.
|
|
338
|
+
*/
|
|
339
|
+
export const MAX_PAGE_SIZE = 99;
|
|
340
|
+
// --- Credential --------------------------------------------------------------------
|
|
341
|
+
export const KEY_PAIR_CREDENTIAL = {
|
|
342
|
+
label: "Tithe.ly API keys",
|
|
343
|
+
description: "The public and private key pair for this connection's environment. API access is by request: email support@tithe.ly to have keys issued. Test keys work only against tithelydev.com and live keys only against tithe.ly.",
|
|
344
|
+
fields: [
|
|
345
|
+
{
|
|
346
|
+
name: "publicKey",
|
|
347
|
+
label: "Public key",
|
|
348
|
+
description: "Starts with pub_. Sent as the HTTP Basic user name.",
|
|
349
|
+
placeholder: "pub_…",
|
|
350
|
+
inputType: "text",
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: "privateKey",
|
|
354
|
+
label: "Private key",
|
|
355
|
+
description: "Starts with pri_. Sent as the HTTP Basic password; never expose it in a browser.",
|
|
356
|
+
placeholder: "pri_…",
|
|
357
|
+
inputType: "password",
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* Refuse a pair that cannot be right before it costs a request: a missing
|
|
363
|
+
* half, keys swapped between the fields, or a header-unsafe character. Only
|
|
364
|
+
* the obvious mismatches: a key without either prefix is passed through,
|
|
365
|
+
* because Tithe.ly has never promised the prefix and a stricter check would
|
|
366
|
+
* lock out a key format it later introduces.
|
|
367
|
+
*/
|
|
368
|
+
function keyPairProblem(values) {
|
|
369
|
+
const publicKey = values?.["publicKey"]?.trim() ?? "";
|
|
370
|
+
const privateKey = values?.["privateKey"]?.trim() ?? "";
|
|
371
|
+
if (!publicKey || !privateKey) {
|
|
372
|
+
return "Both the Tithe.ly public key and private key are required.";
|
|
373
|
+
}
|
|
374
|
+
if (publicKey.startsWith("pri_") && privateKey.startsWith("pub_")) {
|
|
375
|
+
return "The Tithe.ly keys are swapped: the pub_ key belongs in the public-key field and the pri_ key in the private-key field.";
|
|
376
|
+
}
|
|
377
|
+
if (publicKey.startsWith("pri_")) {
|
|
378
|
+
return "The public-key field holds a private (pri_) key. Put the pub_ key there.";
|
|
379
|
+
}
|
|
380
|
+
if (privateKey.startsWith("pub_")) {
|
|
381
|
+
return "The private-key field holds a public (pub_) key. Put the pri_ key there.";
|
|
382
|
+
}
|
|
383
|
+
if (!/^[\x21-\x7e]+$/.test(publicKey) || !/^[\x21-\x7e]+$/.test(privateKey)) {
|
|
384
|
+
return "A Tithe.ly key contains whitespace or a non-ASCII character; paste the key exactly as issued.";
|
|
385
|
+
}
|
|
386
|
+
return { publicKey, privateKey };
|
|
387
|
+
}
|
|
388
|
+
/** Operator-held Basic keys, resolved per request; a mismatched pair is refused before anything leaves. */
|
|
389
|
+
export const authenticate = Effect.gen(function* () {
|
|
390
|
+
const values = yield* (yield* Credentials).values.pipe(Effect.mapError((error) => new Refusal({ error })));
|
|
391
|
+
const pair = keyPairProblem(values);
|
|
392
|
+
if (typeof pair === "string") {
|
|
393
|
+
return yield* failed(new ConnectorCallError("auth_required", `${pair} Call authorize_connector for recovery options; an operator adds the keys on this connection in the operator UI.`));
|
|
394
|
+
}
|
|
395
|
+
return { Authorization: `Basic ${btoa(`${pair.publicKey}:${pair.privateKey}`)}` };
|
|
396
|
+
});
|
|
397
|
+
/** The credential probe: one organization through the candidate keys; a mismatched pair is refused before it. */
|
|
398
|
+
export const probe = {
|
|
399
|
+
operation: listOrganizations,
|
|
400
|
+
request: { query: { limit: 1 } },
|
|
401
|
+
check: (values) => {
|
|
402
|
+
const pair = keyPairProblem(values);
|
|
403
|
+
return typeof pair === "string" ? pair : undefined;
|
|
404
|
+
},
|
|
405
|
+
describe: (reply, options) => {
|
|
406
|
+
const first = asRecord(asArray(asRecord(reply)["data"])[0]);
|
|
407
|
+
const name = text(first["name"]);
|
|
408
|
+
const organization = text(first["organization_id"]);
|
|
409
|
+
return name
|
|
410
|
+
? `Keys accepted by Tithe.ly (${options.environment}); first organization reached: ${name}${organization ? ` (${organization})` : ""}.`
|
|
411
|
+
: `Keys accepted by Tithe.ly (${options.environment}); no organization is visible to them.`;
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
// --- Failures ----------------------------------------------------------------------
|
|
415
|
+
function detailFor(payload, status) {
|
|
416
|
+
const root = asRecord(payload);
|
|
417
|
+
const error = root["error"];
|
|
418
|
+
const message = text(root["reason"]) ?? text(root["message"]) ?? text(error) ?? text(asRecord(error)["message"]);
|
|
419
|
+
return message ? `Tithe.ly: ${message.trim()}` : `Tithe.ly returned HTTP ${status}.`;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* What Tithe.ly's 401, 403 and 404 mean (decision 0011 S4). It documents no
|
|
423
|
+
* status, so each rule says what it cannot know; its error bodies carry no
|
|
424
|
+
* machine-readable code, and nothing documents that it checks keys before
|
|
425
|
+
* acting, so a write answered 401 has an unknown outcome. Every other status
|
|
426
|
+
* takes the kit's fallbacks: 400 and 422 are invalid_args, 429 rate_limited,
|
|
427
|
+
* and a 5xx on a read unavailable.
|
|
428
|
+
*/
|
|
429
|
+
export const errors = {
|
|
430
|
+
numericFacts: {
|
|
431
|
+
retryAfterMs: {
|
|
432
|
+
header: "retry-after",
|
|
433
|
+
format: "retry-after",
|
|
434
|
+
review: "HTTP Retry-After delay for failure recovery.",
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
provider: "Tithe.ly",
|
|
438
|
+
facts: () => ({}),
|
|
439
|
+
detail: ({ body, status }) => detailFor(body, status),
|
|
440
|
+
unreadable: "Tithe.ly returned a successful response that is not JSON.",
|
|
441
|
+
// Reviewed meanings for the generated Errors convention: an unknown id answers 404 or an in-band
|
|
442
|
+
// fail, neither of which tells absence from reach, and Tithe.ly documents no conflict.
|
|
443
|
+
meanings: { unknownId: "connector_call_failed", provesAbsence: false, conflicts: [] },
|
|
444
|
+
statuses: {
|
|
445
|
+
401: () => ({
|
|
446
|
+
code: "auth_required",
|
|
447
|
+
words: (detail) => `${detail} Tithe.ly rejected the key pair. It may be revoked, mistyped, or issued for the other environment — test keys work only on tithelydev.com, live keys only on tithe.ly. An operator must fix the keys on this connection.`,
|
|
448
|
+
}),
|
|
449
|
+
403: () => ({
|
|
450
|
+
code: "provider_permission_denied",
|
|
451
|
+
retryable: false,
|
|
452
|
+
words: (detail) => `${detail} Tithe.ly refused the request: the key pair may not reach this resource, or API access may not be enabled for this environment. Tithe.ly does not say which. Ask the account administrator to grant API access to this resource and environment.`,
|
|
453
|
+
}),
|
|
454
|
+
// Not `not_found`: nothing documents whether an id outside this key's
|
|
455
|
+
// reach answers 404 too, so absence cannot be told from a permission gap.
|
|
456
|
+
404: () => ({
|
|
457
|
+
code: "connector_call_failed",
|
|
458
|
+
retryable: false,
|
|
459
|
+
words: (detail) => `${detail} The path does not exist, or the id is unknown or outside this key pair's reach; Tithe.ly does not distinguish them. Re-read the id from its list tool.`,
|
|
460
|
+
}),
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
/**
|
|
464
|
+
* Tithe.ly's in-band refusal: HTTP 200 with a `status` other than `success`.
|
|
465
|
+
* Its v1 API reports a declined payment, an unknown id, and a rejected
|
|
466
|
+
* argument this same way, with prose and no code, so the classification stays
|
|
467
|
+
* generic and the message says so. Tithe.ly withholds nothing, so its own
|
|
468
|
+
* reason may be quoted; it is for the reader, never for this code to parse.
|
|
469
|
+
*/
|
|
470
|
+
export const result = (body) => {
|
|
471
|
+
const status = asRecord(body)["status"];
|
|
472
|
+
if (status === undefined || status === "success")
|
|
473
|
+
return Effect.succeed(body);
|
|
474
|
+
return failed(resendAdvice(new ConnectorCallError("connector_call_failed", `${detailFor(body, 200)} (status "${String(status)}"). Tithe.ly reports declined payments, unknown ids, and rejected arguments all this way, without a code.`, { retryable: false }), "; read the reason before retrying"));
|
|
475
|
+
};
|
|
476
|
+
// --- Value safety --------------------------------------------------------------------
|
|
477
|
+
/** A request flag: this lean organization read keeps the payout bank's currency (see `tithelySafety`). */
|
|
478
|
+
export const PAYOUT_CURRENCY = { payoutCurrency: true };
|
|
479
|
+
/**
|
|
480
|
+
* Tithe.ly returns no credential or stored secret. Its sensitive fields are
|
|
481
|
+
* personal data, which the deployment's choice drops at any depth. Lean
|
|
482
|
+
* organization results have always carried the payout bank's currency, so a
|
|
483
|
+
* read flagged `PAYOUT_CURRENCY` copies that one reviewed scalar out of `bank`
|
|
484
|
+
* before the drop; the hatch never carries the copy.
|
|
485
|
+
*/
|
|
486
|
+
export function tithelySafety(sensitiveFields) {
|
|
487
|
+
const withCurrency = (value) => {
|
|
488
|
+
const record = asRecord(value);
|
|
489
|
+
return { ...record, payoutCurrency: text(asRecord(record["bank"])["currency"]) };
|
|
490
|
+
};
|
|
491
|
+
return {
|
|
492
|
+
review: "Tithe.ly's API returns no credential or stored secret; its sensitive fields are personal data.",
|
|
493
|
+
reviewed: {
|
|
494
|
+
// The hatch's index names each path's canonical operation; only typed sends carry projection flags.
|
|
495
|
+
resolve: (method, path) => ({ method, template: TITHELY_INDEX.resolve(method, path).op.path }),
|
|
496
|
+
withholdsErrors: () => false,
|
|
497
|
+
redact: (body, operation) => {
|
|
498
|
+
if (operation.flags?.["payoutCurrency"] !== true)
|
|
499
|
+
return body;
|
|
500
|
+
if (operation.template === listOrganizations.path) {
|
|
501
|
+
const root = asRecord(body);
|
|
502
|
+
return Array.isArray(root["data"]) ? { ...root, data: root["data"].map(withCurrency) } : body;
|
|
503
|
+
}
|
|
504
|
+
return { object: withCurrency(entity(body, "organization_id")) };
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
sensitiveFields,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
// --- Shared readers ------------------------------------------------------------------
|
|
511
|
+
const failed = (error) => Effect.fail(new Refusal({ error }));
|
|
512
|
+
export function asRecord(value) {
|
|
513
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
514
|
+
}
|
|
515
|
+
export function asArray(value) {
|
|
516
|
+
return Array.isArray(value) ? value : [];
|
|
517
|
+
}
|
|
518
|
+
export function text(value) {
|
|
519
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* The entity inside an envelope, with the envelope's own id folded in. Single
|
|
523
|
+
* reads put the id beside `object` rather than inside it.
|
|
524
|
+
*/
|
|
525
|
+
export function entity(payload, idKey) {
|
|
526
|
+
const root = asRecord(payload);
|
|
527
|
+
const inner = asRecord(root["object"] ?? root["data"]);
|
|
528
|
+
const record = Object.keys(inner).length > 0 ? inner : root;
|
|
529
|
+
const id = record[idKey] ?? root[idKey];
|
|
530
|
+
return id === undefined ? record : { ...record, [idKey]: id };
|
|
531
|
+
}
|
|
@@ -46,6 +46,23 @@ export interface TithelyOptions {
|
|
|
46
46
|
defaultPageSize?: number;
|
|
47
47
|
/** API base override for a proxy or test double; replaces the environment's endpoint. */
|
|
48
48
|
baseUrl?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Whether results may carry {@link TITHELY_SENSITIVE_FIELDS}: an
|
|
51
|
+
* organization's payout `bank` (bank name and account last four) and
|
|
52
|
+
* `legal` contact (name and date of birth). `"drop"` (default) removes them
|
|
53
|
+
* wherever they appear, in every named read and `tithely_api_read` and
|
|
54
|
+
* `tithely_api_write` replies. `"return"` lets the hatch return Tithe.ly's
|
|
55
|
+
* records with them; the named projections never include them. Only
|
|
56
|
+
* deployment code decides; no tool argument can.
|
|
57
|
+
*/
|
|
58
|
+
sensitiveFields?: "drop" | "return";
|
|
49
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* The organization fields every result drops by default, by name and at any
|
|
62
|
+
* depth: `bank` (payout bank name and account last four) and `legal` (the
|
|
63
|
+
* legal contact's name and date of birth). Neither answers a giving question,
|
|
64
|
+
* and both are exactly what should not leak into a transcript.
|
|
65
|
+
*/
|
|
66
|
+
export declare const TITHELY_SENSITIVE_FIELDS: readonly string[];
|
|
50
67
|
/** A maintained Tithe.ly giving connection over the v1 REST API. */
|
|
51
68
|
export declare const tithely: import("../../provider.js").ProviderFactory<TithelyOptions>;
|