@zackbart/connecta 0.34.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +393 -0
- package/README.md +62 -105
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2051 -1160
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +663 -271
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +15 -7
- package/dist/connectors/api-connector.js +147 -57
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/option-shapes.js +1 -1
- package/dist/connectors/remote-mcp.d.ts +12 -30
- package/dist/connectors/remote-mcp.js +960 -564
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +5 -1
- package/dist/describe-config.js +3 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +228 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +383 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +221 -87
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +534 -408
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/agents.d.ts +5 -4
- package/dist/operator-ui/agents.js +17 -7
- package/dist/operator-ui/config-summary.js +8 -4
- package/dist/operator-ui/connection-map.js +9 -6
- package/dist/operator-ui/contract.d.ts +7 -3
- package/dist/operator-ui/endpoints.js +5 -14
- package/dist/operator-ui/fix-prompts.d.ts +1 -1
- package/dist/operator-ui/fix-prompts.js +0 -8
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/health.js +52 -0
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/operator-ui/overview.js +11 -19
- package/dist/operator-ui/setup-commands.js +0 -4
- package/dist/operator-ui/view.d.ts +5 -29
- package/dist/operator-ui/view.js +3 -59
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +71 -32
- package/dist/provider.js +134 -40
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +537 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +8 -209
- package/dist/providers/_shared/google/workspace.js +7 -426
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/dispatch.js +3 -23
- package/dist/providers/_shared/rest/kit.js +546 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/tools.js +101 -78
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +3 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +17 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +7 -22
- package/dist/providers/cloudflare/index.js +80 -194
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +3299 -3208
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +538 -18
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +802 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1024 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +3 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +6 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +734 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +7 -27
- package/dist/providers/stripe/index.js +75 -191
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -10
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +19 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +11 -25
- package/dist/providers/vercel/index.js +67 -261
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +398 -376
- package/dist/providers/vercel/{value-safety.js → safety.js} +261 -42
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +30 -28
- package/dist/registry.js +446 -413
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +3 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +29 -22
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +556 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +330 -224
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +11 -99
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +27 -7
- package/documentation/architecture.md +2550 -186
- package/documentation/auth.md +821 -98
- package/documentation/code-mode.md +56 -21
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +144 -105
- package/documentation/meta-tools.md +411 -34
- package/documentation/operating.md +28 -4
- package/documentation/operator-tests.md +11 -2
- package/documentation/operator-ui.md +103 -33
- package/documentation/provider-auth-migration.md +38 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +965 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +27 -42
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -134
- package/dist/catalog-drift.js +0 -493
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -43
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1078
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -352
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -57
- package/dist/providers/mixpanel/index.js +0 -468
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -65
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -49
- package/dist/providers/revenuecat/index.js +0 -557
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/generate-providers.mjs; do not edit.
|
|
2
2
|
export const skill = {
|
|
3
|
-
"content": "
|
|
4
|
-
"instructionsHeading": "Account instructions"
|
|
5
|
-
"fragments": {
|
|
6
|
-
"guide_0": "\n\n## Giving data is sensitive\n\n- Donor names, emails, addresses, and gift amounts are pastoral data. Reduce\n inside `execute_code`: page, filter, and total there, and return the\n aggregate (sums by fund, month, or donor count), not donor rows, unless the\n task needs named people.\n- Organization projections omit the payout bank's last four and the legal\n contact's date of birth. `raw: true` and `tithely_api_get` return them;\n do not reach for either to answer a giving question.\n\n## Ids and vocabulary\n\n- Ids carry their kind: `org_` organization, `user_` donor account,\n `ch_` charge, `rc_` recurring gift, `pm_` payment method. Read them\n from `list_organizations` and `list_accounts`; never guess one.\n- A fund is a Tithe.ly giving type: `giving_type` becomes `fund`.\n- Projections rename snake_case to camelCase, return amounts as integer\n `…Cents`, and Unix-second dates as ISO 8601 UTC `…At`.\n `depositedAt` may be `\"pending\"`. There is no deposits resource: net\n amount and fees live on each charge.\n\n## Paging\n\nList tools return `page.hasMore` and `page.nextCursor`; pass the cursor\nback unchanged with the same `order`. `list_charges` and\n`list_recurring_charges` need `organizationId` or `accountId`.\n`list_payment_methods` is not paged.\n\n## Failures\n\nTithe.ly answers most refusals with HTTP 200, `status: \"fail\"`, and a\nreason but no code. They arrive as `connector_call_failed`; read the reason\nrather than retrying blind.\n\n## Writes go through the mutate hatch\n\nEvery Tithe.ly write moves money or changes a donor's payment state, so none\nhas a named tool. `tithely_api_mutate` sends form fields as name/value pairs\nand always needs approval. Amounts are integer cents; card and bank tokens\ncome only from Tithely.js in a browser, never from an agent.\n\n- `POST /charges`: account_id, pm_id, organization_id, amount,\n giving_type, optional memo. `POST /charge-once` charges a Tithely.js token\n without an account.\n- `POST /refunds/{charge_id}` refunds a charge.\n- `POST /recurring`: account_id, pm_id, organization_id, amount,\n giving_type, term (weekly, monthly, bimonthly, fortnightly), start_date\n (Unix seconds). `DELETE /recurring/{recurring_id}` stops future charges and\n refunds nothing.\n- `POST /payment-methods` (account_id, token), `POST\n /payment-methods/{pm_id}` (account_id plus fields to change), `DELETE\n /payment-methods/{pm_id}` with query account_id.\n- `POST /accounts` and `POST /accounts/{account_id}` create and update\n donor accounts.\n\n## Limits\n\nTithe.ly publishes no rate limit, so this connection declares no call budget.\nAn operator who knows one sets `callAdmission` on the connection. Only v1 is\nreachable; Tithe.ly's undocumented v2 is not.\n"
|
|
7
|
-
}
|
|
3
|
+
"content": "## Giving data is pastoral\n\nDonor names, emails, addresses, and gift amounts are pastoral data. Reduce\ninside `execute_code`: page, filter, and total there, and return the\naggregate (sums by fund, month, or donor count), not donor rows, unless the\ntask needs named people.\n\n## Ids and joins\n\n- Ids carry their kind: `org_` organization, `user_` donor account, `ch_`\n charge, `rc_` recurring gift, `pm_` payment method. Read them from the list\n tools; never guess one.\n- Tithe.ly lists charges and recurring gifts only for one organization or one\n donor, so pass `organizationId` or `accountId`.\n- A charge carries its donor's name and email, its organization's name, and\n its payment method, so most joins need no second read.\n- `list_accounts` covers every account these keys can see; `scope: \"api\"`\n narrows it to accounts created through this API.\n- The named reads project Tithe.ly's records. A field a projection omits, such\n as an account's phone and postal address, comes back from `tithely_api_read`\n on that record's path.\n\n## Money, dates and funds\n\n- A fund is a Tithe.ly giving type: `giving_type` in Tithe.ly's records,\n `fund` in projections.\n- Projections give amounts as integer `…Cents` and dates as ISO 8601 UTC\n `…At`. Tithe.ly's own records, from `tithely_api_read`, keep string cents\n and Unix seconds.\n- `depositedAt` may be `\"pending\"`. There is no deposits resource: net amount\n and fees live on each charge.\n- A charge a recurring gift produced has `recurring: true`; a one-time gift\n has `recurring: false`.\n\n## Writes\n\n- Every write moves money or changes a donor's records. Bodies are form\n fields in integer cents and Unix seconds.\n- `POST /refunds/{charge_id}` refunds a whole charge.\n `DELETE /recurring/{recurring_id}` stops future charges and refunds nothing.\n- Card and bank tokens, which `POST /charge-once` and `POST /payment-methods`\n need, come only from Tithely.js in a donor's browser. An agent cannot\n create one.\n- Tithe.ly reports most refusals as HTTP 200 with `status: \"fail\"` and a\n reason but no code. They arrive as `connector_call_failed`; read the reason\n before deciding what to do next.\n\n## Not available\n\nOnly Tithe.ly's API v1 is reachable; its undocumented v2 is not.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `list_accounts`, `list_charges`, `list_organizations`, `list_payment_methods`, `list_recurring_charges`, `tithely_api_details`, `tithely_api_search`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. Paged `list_*` and `search_*` reads take `limit` (default 25, at most 99). The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `tithely_api_search` finds this API's operations and reads no account data, `tithely_api_details` shows one operation's contract, and `tithely_api_read` calls a reviewed read. `tithely_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. It documents no conflict.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
|
|
4
|
+
"instructionsHeading": "Account instructions"
|
|
8
5
|
};
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// Tithe.ly's curated tools on the provider kit (decision 0010) and their
|
|
2
|
+
// projections. Decision 0011's step 2: standard lists ({ items, page } with
|
|
3
|
+
// limit, cursor and fields), fields on every named read, and no `raw`: what a
|
|
4
|
+
// projection drops stays reachable through the standard hatch, which the kit
|
|
5
|
+
// builds from `api.ts`'s index and verdicts (decision 0014). Each named read
|
|
6
|
+
// earns its place against the hatch with paging Tithe.ly cannot report
|
|
7
|
+
// (lookahead), units (cents, ISO dates), joins (donor, organization and
|
|
8
|
+
// payment method inline) or reconciliation of a known id after a write.
|
|
9
|
+
import { Effect, Schema } from "effect";
|
|
10
|
+
import { defined } from "../../connectors/api-connector.js";
|
|
11
|
+
import { ConnectorCallError } from "../../errors.js";
|
|
12
|
+
import { Refusal } from "../../http/errors.js";
|
|
13
|
+
import { Integer } from "../../http/json-schema.js";
|
|
14
|
+
import { lookahead } from "../../http/paging.js";
|
|
15
|
+
import { get as getTool, list } from "../../http/tools.js";
|
|
16
|
+
import { asArray, asRecord, entity, getCharge, getRecurring, listAccounts, listAllAccounts, listCharges, listOrganizations, listPaymentMethods, listRecurring, MAX_PAGE_SIZE, PAYOUT_CURRENCY, text, } from "./api.js";
|
|
17
|
+
// --- Schemas ---------------------------------------------------------------------
|
|
18
|
+
const Id = Schema.String.check(Schema.isMinLength(1));
|
|
19
|
+
const Text = Schema.optionalKey(Schema.String);
|
|
20
|
+
const Cents = Schema.optionalKey(Integer);
|
|
21
|
+
const Flag = Schema.optionalKey(Schema.Boolean);
|
|
22
|
+
const When = Schema.Union([
|
|
23
|
+
Integer.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
24
|
+
Schema.String.check(Schema.isMinLength(1)),
|
|
25
|
+
]);
|
|
26
|
+
const Address = Schema.Struct({ streetAddress: Text, city: Text, state: Text, postal: Text, country: Text });
|
|
27
|
+
const Organization = Schema.Struct({
|
|
28
|
+
organizationId: Text,
|
|
29
|
+
name: Text,
|
|
30
|
+
widgetId: Text,
|
|
31
|
+
ownerAccountId: Text,
|
|
32
|
+
website: Text,
|
|
33
|
+
phoneNumber: Text,
|
|
34
|
+
address: Schema.optionalKey(Address),
|
|
35
|
+
payoutCurrency: Text,
|
|
36
|
+
createdAt: Text,
|
|
37
|
+
funds: Schema.optionalKey(Schema.Array(Schema.Struct({ id: Text, name: Schema.String, status: Text }))),
|
|
38
|
+
});
|
|
39
|
+
const AccountSummary = Schema.Struct({
|
|
40
|
+
accountId: Text,
|
|
41
|
+
firstName: Text,
|
|
42
|
+
lastName: Text,
|
|
43
|
+
email: Text,
|
|
44
|
+
createdAt: Text,
|
|
45
|
+
});
|
|
46
|
+
const Donor = Schema.Struct({ firstName: Text, lastName: Text, email: Text });
|
|
47
|
+
const Charge = Schema.Struct({
|
|
48
|
+
chargeId: Text,
|
|
49
|
+
status: Text,
|
|
50
|
+
amountCents: Cents,
|
|
51
|
+
netAmountCents: Cents,
|
|
52
|
+
feesCents: Cents,
|
|
53
|
+
currency: Text,
|
|
54
|
+
fund: Text,
|
|
55
|
+
chargedAt: Text,
|
|
56
|
+
depositedAt: Text,
|
|
57
|
+
recurring: Flag,
|
|
58
|
+
feesCovered: Flag,
|
|
59
|
+
memo: Text,
|
|
60
|
+
organizationId: Text,
|
|
61
|
+
organizationName: Text,
|
|
62
|
+
accountId: Text,
|
|
63
|
+
donor: Schema.optionalKey(Donor),
|
|
64
|
+
paymentMethodId: Text,
|
|
65
|
+
paymentMethod: Schema.optionalKey(Schema.Struct({ type: Text, brand: Text, last4: Text })),
|
|
66
|
+
});
|
|
67
|
+
const Recurring = Schema.Struct({
|
|
68
|
+
recurringId: Text,
|
|
69
|
+
amountCents: Cents,
|
|
70
|
+
currency: Text,
|
|
71
|
+
fund: Text,
|
|
72
|
+
term: Text,
|
|
73
|
+
startAt: Text,
|
|
74
|
+
organizationId: Text,
|
|
75
|
+
organizationName: Text,
|
|
76
|
+
accountId: Text,
|
|
77
|
+
donor: Schema.optionalKey(Donor),
|
|
78
|
+
paymentMethodId: Text,
|
|
79
|
+
});
|
|
80
|
+
const PaymentMethod = Schema.Struct({ paymentMethodId: Text, accountId: Text, type: Text, brand: Text, last4: Text });
|
|
81
|
+
const AccountId = Id.annotate({ description: "Donor account id (user_…) from list_accounts." });
|
|
82
|
+
const Order = Schema.optionalKey(Schema.Literals(["desc", "asc"]).annotate({
|
|
83
|
+
description: "Newest first (desc, Tithe.ly's default) or oldest first. Keep it constant while paging.",
|
|
84
|
+
}));
|
|
85
|
+
const Scope = (noun) => ({
|
|
86
|
+
organizationId: Schema.optionalKey(Id.annotate({ description: `Organization (org_…) ${noun}. This or accountId is required.` })),
|
|
87
|
+
accountId: Schema.optionalKey(Id.annotate({ description: `Donor account (user_…) that gave. This or organizationId is required.` })),
|
|
88
|
+
});
|
|
89
|
+
// --- Tools -----------------------------------------------------------------------
|
|
90
|
+
/**
|
|
91
|
+
* Tithe.ly's seven curated reads, in catalog order. Every answer has already
|
|
92
|
+
* passed the connection's value safety and sensitive-field choice.
|
|
93
|
+
*/
|
|
94
|
+
export function tithelyTools({ connection, pageSize, options, }) {
|
|
95
|
+
const { send } = connection;
|
|
96
|
+
const limit = {
|
|
97
|
+
default: pageSize,
|
|
98
|
+
max: MAX_PAGE_SIZE,
|
|
99
|
+
description: `Rows per page. Tithe.ly allows 100; connecta asks for one extra to learn whether another page exists.`,
|
|
100
|
+
};
|
|
101
|
+
const pageQuery = (args, cursor, ask) => ({
|
|
102
|
+
limit: ask,
|
|
103
|
+
order_by: args.order === "asc" ? "ASC" : "DESC",
|
|
104
|
+
...defined({ starting_after: cursor }),
|
|
105
|
+
});
|
|
106
|
+
// Ask for one row more than the page and address the next page by the last kept id.
|
|
107
|
+
const page = (idKey, size, fetch) => lookahead({
|
|
108
|
+
provider: "Tithe.ly",
|
|
109
|
+
idKey,
|
|
110
|
+
limit: size,
|
|
111
|
+
fetch: (ask) => fetch(ask).pipe(Effect.flatMap(({ data }) => listRows(data))),
|
|
112
|
+
});
|
|
113
|
+
// S6: a projection that drops fields points to the hatch, which passes the same privacy boundary.
|
|
114
|
+
const sensitive = options.sensitiveFields === "return"
|
|
115
|
+
? "Fields this omits, payout bank and legal-contact details included, come from tithely_api_read."
|
|
116
|
+
: "Fields this omits come from tithely_api_read; payout bank and legal-contact details stay withheld.";
|
|
117
|
+
return [
|
|
118
|
+
list(connection, {
|
|
119
|
+
name: "list_organizations",
|
|
120
|
+
description: `List the organizations these keys manage, with their funds (giving types) and status. ${sensitive}`,
|
|
121
|
+
filters: { order: Order },
|
|
122
|
+
row: Organization,
|
|
123
|
+
limit,
|
|
124
|
+
cursor: "plain",
|
|
125
|
+
page: (args, cursor) => page("organization_id", args.limit, (ask) => send(listOrganizations, { query: pageQuery(args, cursor, ask) }, { flags: PAYOUT_CURRENCY })).pipe(Effect.map(({ rows, page }) => ({ rows: rows.map(projectOrganization), page }))),
|
|
126
|
+
}),
|
|
127
|
+
list(connection, {
|
|
128
|
+
name: "list_accounts",
|
|
129
|
+
description: "List donor accounts by name, email, and id. Giving history is list_charges by accountId; phone, address, and other fields come from tithely_api_read.",
|
|
130
|
+
filters: {
|
|
131
|
+
scope: Schema.optionalKey(Schema.Literals(["all", "api"]).annotate({
|
|
132
|
+
description: "all (default) lists every account Tithe.ly lets these keys see; api lists only accounts created through this API.",
|
|
133
|
+
})),
|
|
134
|
+
order: Order,
|
|
135
|
+
},
|
|
136
|
+
row: AccountSummary,
|
|
137
|
+
limit,
|
|
138
|
+
cursor: "plain",
|
|
139
|
+
page: (args, cursor) => page("account_id", args.limit, (ask) => send(args.scope === "api" ? listAccounts : listAllAccounts, { query: pageQuery(args, cursor, ask) })).pipe(Effect.map(({ rows, page }) => ({ rows: rows.map(projectAccountSummary), page }))),
|
|
140
|
+
}),
|
|
141
|
+
list(connection, {
|
|
142
|
+
name: "list_charges",
|
|
143
|
+
description: "List gifts (charges) to one organization or from one donor, optionally within a created-date range, with gross, net, and fees in cents.",
|
|
144
|
+
filters: {
|
|
145
|
+
...Scope("the gifts went to"),
|
|
146
|
+
createdAfter: Schema.optionalKey(When.annotate({
|
|
147
|
+
description: "Only charges created after this time: Unix seconds or an ISO 8601 date/date-time (UTC when no offset).",
|
|
148
|
+
})),
|
|
149
|
+
createdBefore: Schema.optionalKey(When.annotate({ description: "Only charges created before this time, in the same forms as createdAfter." })),
|
|
150
|
+
order: Order,
|
|
151
|
+
},
|
|
152
|
+
row: Charge,
|
|
153
|
+
limit,
|
|
154
|
+
cursor: "plain",
|
|
155
|
+
page: (args, cursor) => Effect.gen(function* () {
|
|
156
|
+
const after = yield* seconds(args.createdAfter, "createdAfter");
|
|
157
|
+
const before = yield* seconds(args.createdBefore, "createdBefore");
|
|
158
|
+
if (after !== undefined && before !== undefined && after > before) {
|
|
159
|
+
return yield* failed(new ConnectorCallError("invalid_args", "createdAfter is later than createdBefore, so no charge can match."));
|
|
160
|
+
}
|
|
161
|
+
const scope = yield* scopeFilters(args, "charges");
|
|
162
|
+
const { rows, page: next } = yield* page("charge_id", args.limit, (ask) => send(listCharges, {
|
|
163
|
+
query: {
|
|
164
|
+
...scope,
|
|
165
|
+
...defined({ created_after: after, created_before: before }),
|
|
166
|
+
...pageQuery(args, cursor, ask),
|
|
167
|
+
},
|
|
168
|
+
}));
|
|
169
|
+
return { rows: rows.map(projectCharge), page: next };
|
|
170
|
+
}),
|
|
171
|
+
}),
|
|
172
|
+
getTool(connection, {
|
|
173
|
+
name: "get_charge",
|
|
174
|
+
description: "Get one charge by id: amounts in cents, status, fund, deposit date, donor, and payment method. Use it to check a charge after a write; tithely_api_read returns the full record.",
|
|
175
|
+
input: Schema.Struct({ chargeId: Id.annotate({ description: "Charge id (ch_…) from list_charges." }) }),
|
|
176
|
+
output: Charge,
|
|
177
|
+
fields: true,
|
|
178
|
+
run: (args) => send(getCharge, { params: { charge_id: args.chargeId } }).pipe(Effect.map(({ data }) => projectCharge(entity(data, "charge_id")))),
|
|
179
|
+
}),
|
|
180
|
+
list(connection, {
|
|
181
|
+
name: "list_recurring_charges",
|
|
182
|
+
description: "List recurring gifts for one organization or one donor: amount, fund, term, and start date. Does not list the charges they produced.",
|
|
183
|
+
filters: { ...Scope("receiving the gifts"), order: Order },
|
|
184
|
+
row: Recurring,
|
|
185
|
+
limit,
|
|
186
|
+
cursor: "plain",
|
|
187
|
+
page: (args, cursor) => Effect.gen(function* () {
|
|
188
|
+
const scope = yield* scopeFilters(args, "recurring gifts");
|
|
189
|
+
const { rows, page: next } = yield* page("recurring_id", args.limit, (ask) => send(listRecurring, { query: { ...scope, ...pageQuery(args, cursor, ask) } }));
|
|
190
|
+
return { rows: rows.map(projectRecurring), page: next };
|
|
191
|
+
}),
|
|
192
|
+
}),
|
|
193
|
+
getTool(connection, {
|
|
194
|
+
name: "get_recurring_charge",
|
|
195
|
+
description: "Get one recurring gift by id: amount in cents, fund, term, start date, organization, and donor. Use it to check a gift after changing it.",
|
|
196
|
+
input: Schema.Struct({
|
|
197
|
+
recurringId: Id.annotate({ description: "Recurring gift id (rc_…) from list_recurring_charges." }),
|
|
198
|
+
}),
|
|
199
|
+
output: Recurring,
|
|
200
|
+
fields: true,
|
|
201
|
+
run: (args) => send(getRecurring, { params: { recurring_id: args.recurringId } }).pipe(Effect.map(({ data }) => projectRecurring(entity(data, "recurring_id")))),
|
|
202
|
+
}),
|
|
203
|
+
// Tithe.ly answers every method in one unpaged response; the kit windows it
|
|
204
|
+
// with a bound cursor so the list keeps the standard envelope.
|
|
205
|
+
list(connection, {
|
|
206
|
+
name: "list_payment_methods",
|
|
207
|
+
description: "List one donor's stored payment methods as type, brand, and last four, for the pm_ id a charge needs. Never card numbers or tokens.",
|
|
208
|
+
filters: { accountId: AccountId },
|
|
209
|
+
row: PaymentMethod,
|
|
210
|
+
limit,
|
|
211
|
+
cursor: "bound",
|
|
212
|
+
page: (args, cursor) => Effect.gen(function* () {
|
|
213
|
+
const { data } = yield* send(listPaymentMethods, { query: { account_id: args.accountId } });
|
|
214
|
+
const rows = asArray(asRecord(data)["data"]);
|
|
215
|
+
const start = cursor === undefined ? 0 : Number(cursor);
|
|
216
|
+
if (!Number.isSafeInteger(start) || start < 0 || start > rows.length) {
|
|
217
|
+
return yield* failed(new ConnectorCallError("invalid_args", "This cursor no longer matches the donor's payment methods."));
|
|
218
|
+
}
|
|
219
|
+
const end = start + args.limit;
|
|
220
|
+
return {
|
|
221
|
+
rows: rows.slice(start, end).map(projectPaymentMethod),
|
|
222
|
+
page: end < rows.length ? { hasMore: true, nextCursor: String(end) } : { hasMore: false, nextCursor: null },
|
|
223
|
+
};
|
|
224
|
+
}),
|
|
225
|
+
}),
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
// --- Arguments -----------------------------------------------------------------------
|
|
229
|
+
const failed = (error) => Effect.fail(new Refusal({ error }));
|
|
230
|
+
/**
|
|
231
|
+
* Tithe.ly lists charges and recurring gifts only for a donor or an
|
|
232
|
+
* organization, so a call naming neither can only fail: refuse it locally.
|
|
233
|
+
*/
|
|
234
|
+
function scopeFilters(args, noun) {
|
|
235
|
+
if (args.organizationId === undefined && args.accountId === undefined) {
|
|
236
|
+
return failed(new ConnectorCallError("invalid_args", `Tithe.ly lists ${noun} only for an organization or a donor: pass organizationId (from list_organizations) or accountId (from list_accounts).`));
|
|
237
|
+
}
|
|
238
|
+
return Effect.succeed(defined({ organization_id: args.organizationId, account_id: args.accountId }));
|
|
239
|
+
}
|
|
240
|
+
function listRows(payload) {
|
|
241
|
+
const data = asRecord(payload)["data"];
|
|
242
|
+
if (data !== undefined && !Array.isArray(data)) {
|
|
243
|
+
return failed(new ConnectorCallError("connector_call_failed", "Tithe.ly returned a list response whose data is not an array.", {
|
|
244
|
+
retryable: false,
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
return Effect.succeed(data ?? []);
|
|
248
|
+
}
|
|
249
|
+
/** An ISO 8601 date-time with no `Z` or offset, which `Date.parse` reads as local time. */
|
|
250
|
+
const OFFSETLESS_DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?$/;
|
|
251
|
+
/**
|
|
252
|
+
* Unix seconds from an integer or an ISO 8601 date/date-time. A date-time
|
|
253
|
+
* without an offset is UTC, as the schema promises, not the runtime's zone.
|
|
254
|
+
*/
|
|
255
|
+
function seconds(value, name) {
|
|
256
|
+
if (value === undefined)
|
|
257
|
+
return Effect.succeed(undefined);
|
|
258
|
+
if (typeof value === "number")
|
|
259
|
+
return Effect.succeed(value);
|
|
260
|
+
const raw = String(value).trim();
|
|
261
|
+
if (/^\d+$/.test(raw))
|
|
262
|
+
return Effect.succeed(Number(raw));
|
|
263
|
+
const parsed = Date.parse(OFFSETLESS_DATE_TIME.test(raw) ? `${raw}Z` : raw);
|
|
264
|
+
if (!Number.isFinite(parsed)) {
|
|
265
|
+
return failed(new ConnectorCallError("invalid_args", `${name} must be Unix seconds or an ISO 8601 date or date-time; received "${raw}".`));
|
|
266
|
+
}
|
|
267
|
+
return Effect.succeed(Math.floor(parsed / 1000));
|
|
268
|
+
}
|
|
269
|
+
function compact(value) {
|
|
270
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
271
|
+
}
|
|
272
|
+
/** Integer cents from Tithe.ly's string-or-number amounts. */
|
|
273
|
+
function cents(value) {
|
|
274
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
275
|
+
return Math.trunc(value);
|
|
276
|
+
if (typeof value === "string" && /^-?\d+$/.test(value.trim()))
|
|
277
|
+
return Number(value.trim());
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
/** ISO 8601 UTC from a Unix-seconds string or number. */
|
|
281
|
+
function isoTime(value) {
|
|
282
|
+
const seconds = typeof value === "number"
|
|
283
|
+
? value
|
|
284
|
+
: typeof value === "string" && /^\d+$/.test(value.trim())
|
|
285
|
+
? Number(value.trim())
|
|
286
|
+
: undefined;
|
|
287
|
+
if (seconds === undefined || !Number.isFinite(seconds))
|
|
288
|
+
return undefined;
|
|
289
|
+
const date = new Date(seconds * 1000);
|
|
290
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
291
|
+
}
|
|
292
|
+
function projectAddress(value) {
|
|
293
|
+
const address = asRecord(value);
|
|
294
|
+
if (Object.keys(address).length === 0)
|
|
295
|
+
return undefined;
|
|
296
|
+
return compact({
|
|
297
|
+
streetAddress: text(address["street_address"]),
|
|
298
|
+
city: text(address["city"]),
|
|
299
|
+
state: text(address["state"]),
|
|
300
|
+
postal: text(address["postal"]),
|
|
301
|
+
country: text(address["country"]),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const FUND_STATUS = { "0": "archived", "1": "active", "2": "hidden" };
|
|
305
|
+
function projectFunds(organization) {
|
|
306
|
+
const full = asArray(organization["giving_types_full"]);
|
|
307
|
+
if (full.length > 0) {
|
|
308
|
+
return full.map((value) => {
|
|
309
|
+
const fund = asRecord(value);
|
|
310
|
+
const status = fund["status"] === undefined ? undefined : String(fund["status"]);
|
|
311
|
+
return compact({
|
|
312
|
+
id: fund["id"] === undefined ? undefined : String(fund["id"]),
|
|
313
|
+
name: String(fund["name"] ?? ""),
|
|
314
|
+
status: status === undefined ? undefined : (FUND_STATUS[status] ?? status),
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return asArray(organization["giving_types"])
|
|
319
|
+
.filter((name) => typeof name === "string")
|
|
320
|
+
.map((name) => ({ name }));
|
|
321
|
+
}
|
|
322
|
+
/** Omits the payout bank and legal contact; `payoutCurrency` is the reviewed copy the connection's safety made. */
|
|
323
|
+
function projectOrganization(value) {
|
|
324
|
+
const organization = asRecord(value);
|
|
325
|
+
return compact({
|
|
326
|
+
organizationId: text(organization["organization_id"]),
|
|
327
|
+
name: text(organization["name"]),
|
|
328
|
+
// The giving widget id embeds Tithe.ly's giving form; the step-2 eval showed agents need it (decision 0014).
|
|
329
|
+
widgetId: text(organization["widget_id"]),
|
|
330
|
+
ownerAccountId: text(organization["account_id"]),
|
|
331
|
+
website: text(organization["website"]),
|
|
332
|
+
phoneNumber: text(organization["phone_number"]),
|
|
333
|
+
address: projectAddress(organization["address"]),
|
|
334
|
+
payoutCurrency: text(organization["payoutCurrency"]),
|
|
335
|
+
createdAt: isoTime(organization["created_date"]),
|
|
336
|
+
funds: projectFunds(organization),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function projectAccountSummary(value) {
|
|
340
|
+
const account = asRecord(value);
|
|
341
|
+
return compact({
|
|
342
|
+
accountId: text(account["account_id"]),
|
|
343
|
+
firstName: text(account["first_name"]),
|
|
344
|
+
lastName: text(account["last_name"]),
|
|
345
|
+
email: text(account["email"]),
|
|
346
|
+
createdAt: isoTime(account["created_date"]),
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
/** A nested reference that is sometimes an id string and sometimes an object. */
|
|
350
|
+
function reference(value, idKey) {
|
|
351
|
+
if (typeof value === "string")
|
|
352
|
+
return { id: value, record: {} };
|
|
353
|
+
const record = asRecord(value);
|
|
354
|
+
return { ...defined({ id: text(record[idKey]) }), record };
|
|
355
|
+
}
|
|
356
|
+
/** The donor as a giving record names them; address and phone stay on get_account. */
|
|
357
|
+
function projectDonor(record) {
|
|
358
|
+
const donor = compact({
|
|
359
|
+
firstName: text(record["first_name"]),
|
|
360
|
+
lastName: text(record["last_name"]),
|
|
361
|
+
email: text(record["email"]),
|
|
362
|
+
});
|
|
363
|
+
return Object.keys(donor).length === 0 ? undefined : donor;
|
|
364
|
+
}
|
|
365
|
+
function projectCharge(value) {
|
|
366
|
+
const charge = asRecord(value);
|
|
367
|
+
const organization = reference(charge["organization"], "organization_id");
|
|
368
|
+
const donor = reference(charge["donor_account"], "account_id");
|
|
369
|
+
const method = reference(charge["payment_method"], "pm_id");
|
|
370
|
+
const deposit = charge["deposit_date"];
|
|
371
|
+
return compact({
|
|
372
|
+
chargeId: text(charge["charge_id"]),
|
|
373
|
+
status: text(charge["charge_status"]),
|
|
374
|
+
amountCents: cents(charge["amount"]),
|
|
375
|
+
netAmountCents: cents(charge["net_amount"]),
|
|
376
|
+
feesCents: cents(charge["fees"]),
|
|
377
|
+
currency: text(charge["currency"]),
|
|
378
|
+
fund: text(charge["giving_type"]),
|
|
379
|
+
chargedAt: isoTime(charge["charge_date"]),
|
|
380
|
+
depositedAt: deposit === "pending" ? "pending" : isoTime(deposit),
|
|
381
|
+
recurring: typeof charge["recurring_transaction"] === "boolean" ? charge["recurring_transaction"] : undefined,
|
|
382
|
+
feesCovered: typeof charge["fees_covered"] === "boolean" ? charge["fees_covered"] : undefined,
|
|
383
|
+
memo: text(charge["memo"]),
|
|
384
|
+
organizationId: organization.id,
|
|
385
|
+
organizationName: text(organization.record["name"]),
|
|
386
|
+
accountId: donor.id ?? text(method.record["account_id"]),
|
|
387
|
+
donor: projectDonor(donor.record),
|
|
388
|
+
paymentMethodId: method.id,
|
|
389
|
+
paymentMethod: Object.keys(method.record).length === 0
|
|
390
|
+
? undefined
|
|
391
|
+
: compact({
|
|
392
|
+
type: text(method.record["pm_type"]),
|
|
393
|
+
brand: text(method.record["brand"]),
|
|
394
|
+
last4: text(method.record["last_4_digits"]),
|
|
395
|
+
}),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
function projectRecurring(value) {
|
|
399
|
+
const recurring = asRecord(value);
|
|
400
|
+
const organization = reference(recurring["organization"], "organization_id");
|
|
401
|
+
const donor = reference(recurring["donor_account"], "account_id");
|
|
402
|
+
const method = reference(recurring["payment_method"], "pm_id");
|
|
403
|
+
return compact({
|
|
404
|
+
recurringId: text(recurring["recurring_id"]),
|
|
405
|
+
amountCents: cents(recurring["amount"]),
|
|
406
|
+
currency: text(recurring["currency"]),
|
|
407
|
+
fund: text(recurring["giving_type"]),
|
|
408
|
+
term: text(recurring["term"]),
|
|
409
|
+
startAt: isoTime(recurring["start_date"]),
|
|
410
|
+
organizationId: organization.id,
|
|
411
|
+
organizationName: text(organization.record["name"]),
|
|
412
|
+
accountId: donor.id,
|
|
413
|
+
donor: projectDonor(donor.record),
|
|
414
|
+
paymentMethodId: method.id,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
function projectPaymentMethod(value) {
|
|
418
|
+
const method = asRecord(value);
|
|
419
|
+
return compact({
|
|
420
|
+
paymentMethodId: text(method["pm_id"]),
|
|
421
|
+
accountId: text(method["account_id"]),
|
|
422
|
+
type: text(method["pm_type"]),
|
|
423
|
+
brand: text(method["brand"]),
|
|
424
|
+
last4: text(method["last_4_digits"]),
|
|
425
|
+
});
|
|
426
|
+
}
|