@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,39 +1,9 @@
|
|
|
1
1
|
import { skill } from "./skill.generated.js";
|
|
2
|
-
/**
|
|
3
|
-
* Overflow (overflow.co) — church and nonprofit giving: cash, stock, crypto,
|
|
4
|
-
* and donor-advised-fund gifts, the deposits that settle them, the donors who
|
|
5
|
-
* gave them, and the recurring gifts that will.
|
|
6
|
-
*
|
|
7
|
-
* No SDK on purpose, and Overflow publishes none for this API anyway: v3 is
|
|
8
|
-
* header-authenticated JSON over thirty-eight operations, so `fetch` through
|
|
9
|
-
* the shared guarded transport keeps this subpath Workers-clean and adds
|
|
10
|
-
* nothing to install. Every tool below is hand-written. Overflow's OpenAPI
|
|
11
|
-
* document is drift evidence only — `src/providers/overflow/drift.json`
|
|
12
|
-
* records the operations the named tools touch, and
|
|
13
|
-
* `pnpm run providers:check --provider overflow` compares them with the
|
|
14
|
-
* published document without a credential. That document is served only by
|
|
15
|
-
* staging (`server.stage.overflow.co/api/docs/openapi.json`; production's
|
|
16
|
-
* equivalent answers 404), so the check reads staging's contract and assumes
|
|
17
|
-
* production runs the same v3 surface — Overflow documents one API at two
|
|
18
|
-
* base URLs, not two APIs.
|
|
19
|
-
*
|
|
20
|
-
* The surface is small enough to cover whole: named reads for the jobs a
|
|
21
|
-
* finance or ministry team actually asks about — what was given, what settled
|
|
22
|
-
* into the bank, who gave, what recurs, which campaign, and why a webhook
|
|
23
|
-
* missed — and two guarded hatches so every other operation stays reachable.
|
|
24
|
-
* Every write Overflow offers either moves money (charges, payment
|
|
25
|
-
* authorization, refunds, recurring-gift changes) or edits a donor's record,
|
|
26
|
-
* so none gets a named tool: each crosses `overflow_api_mutate`, which is
|
|
27
|
-
* destructive by declaration and lands on the host's approval prompt with the
|
|
28
|
-
* exact method, path, and body a human is being asked to allow. Overflow has
|
|
29
|
-
* no upload endpoint, so there is no upload hatch.
|
|
30
|
-
*/
|
|
31
|
-
import { apiConnector as api, defined } from "../../connectors/api-connector.js";
|
|
32
|
-
import { guardedFetch, retryAfterMs, } from "../../connectors/guarded-fetch.js";
|
|
33
|
-
import { ConnectorCallError } from "../../errors.js";
|
|
34
2
|
import { keys, optionsOf } from "../../config-schema.js";
|
|
35
3
|
import { PROVIDER_COMMON } from "../../connectors/option-shapes.js";
|
|
36
|
-
import {
|
|
4
|
+
import { defineApiProvider } from "../../http/provider.js";
|
|
5
|
+
import { authenticate, API_KEY_CREDENTIAL, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, OVERFLOW_MAX_RESPONSE_BYTES, OverflowApi, errors, safety, result, probe, admit, OVERFLOW_INDEX, OVERFLOW_VERDICTS, OVERFLOW_NAMED, OVERFLOW_PAGES, } from "./api.js";
|
|
6
|
+
import { overflowTools } from "./tools.js";
|
|
37
7
|
/**
|
|
38
8
|
* Overflow's two published environments. They are separate deployments with
|
|
39
9
|
* separate credentials: a staging key works only against staging and a
|
|
@@ -43,22 +13,6 @@ export const OVERFLOW_API_BASE_URLS = {
|
|
|
43
13
|
production: "https://server.overflow.co",
|
|
44
14
|
staging: "https://server.stage.overflow.co",
|
|
45
15
|
};
|
|
46
|
-
/** Every v3 operation lives below this path on either origin. */
|
|
47
|
-
const API_PATH = "/api/v3";
|
|
48
|
-
/** Overflow's cap on `limit` for every page-numbered collection. */
|
|
49
|
-
const MAX_PAGE_SIZE = 100;
|
|
50
|
-
/** Overflow's own default; already a quarter of the cap, so a first read is cheap. */
|
|
51
|
-
const DEFAULT_PAGE_SIZE = 25;
|
|
52
|
-
/** Webhook event logs page by cursor and cap `limit` lower, at 50. */
|
|
53
|
-
const MAX_EVENT_LOG_PAGE_SIZE = 50;
|
|
54
|
-
const DEFAULT_EVENT_LOG_PAGE_SIZE = 25;
|
|
55
|
-
/**
|
|
56
|
-
* A ceiling on absurdity, not a budget. A full page of 100 contributions with
|
|
57
|
-
* their embedded donor blocks is a few hundred kilobytes; anything near four
|
|
58
|
-
* mebibytes is a proxy misbehaving.
|
|
59
|
-
*/
|
|
60
|
-
const OVERFLOW_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
61
|
-
/** Overflow's documented fixed window: 120 requests per 60 seconds per client. */
|
|
62
16
|
const RATE_LIMIT_WINDOW_MS = 60_000;
|
|
63
17
|
/**
|
|
64
18
|
* Overflow documents 120 requests per minute per API client, metered in a
|
|
@@ -84,1452 +38,70 @@ const OVERFLOW_ADMISSION = {
|
|
|
84
38
|
},
|
|
85
39
|
],
|
|
86
40
|
};
|
|
87
|
-
function asRecord(value) {
|
|
88
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
89
|
-
}
|
|
90
|
-
function asArray(value) {
|
|
91
|
-
return Array.isArray(value) ? value : [];
|
|
92
|
-
}
|
|
93
|
-
function compact(value) {
|
|
94
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
95
|
-
}
|
|
96
|
-
/** A `{ id, name }` reference, or undefined when Overflow sent none. */
|
|
97
|
-
function reference(value) {
|
|
98
|
-
const record = asRecord(value);
|
|
99
|
-
return Object.keys(record).length === 0 ? undefined : compact({ id: record["id"], name: record["name"] });
|
|
100
|
-
}
|
|
101
|
-
// --- Credentials and transport ----------------------------------------------
|
|
102
|
-
const API_KEY_CREDENTIAL = {
|
|
103
|
-
label: "Overflow API credentials",
|
|
104
|
-
description: "The client id and API key Overflow issued for this environment (Overflow dashboard, or Overflow's developer team). Staging and production credentials are separate and not interchangeable. Server-side only: connecta sends them to Overflow's API and nowhere else.",
|
|
105
|
-
fields: [
|
|
106
|
-
{
|
|
107
|
-
name: "clientId",
|
|
108
|
-
label: "Client ID",
|
|
109
|
-
description: "Overflow's public client identifier, sent as x-client-id.",
|
|
110
|
-
placeholder: "c77c8d57-7e1c-4a7e-ad46-e756e5f8dabd",
|
|
111
|
-
inputType: "text",
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
name: "apiKey",
|
|
115
|
-
label: "API key",
|
|
116
|
-
description: "The secret key paired with that client id, sent as x-api-key.",
|
|
117
|
-
placeholder: "Paste Overflow API key",
|
|
118
|
-
inputType: "password",
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
};
|
|
122
|
-
/**
|
|
123
|
-
* One credential source today: the operator's client id and API key. This is
|
|
124
|
-
* the only place that knows what proves identity to Overflow, so an OAuth
|
|
125
|
-
* bearer source — if Overflow ever offers one and `api()` grows the slot — is
|
|
126
|
-
* a second function selected at construction, not a change to any tool.
|
|
127
|
-
*/
|
|
128
|
-
async function apiKeyHeaders(ctx) {
|
|
129
|
-
const values = await ctx.credential?.getAll();
|
|
130
|
-
const clientId = values?.["clientId"]?.trim();
|
|
131
|
-
const apiKey = values?.["apiKey"]?.trim();
|
|
132
|
-
if (!clientId || !apiKey) {
|
|
133
|
-
throw new ConnectorCallError("auth_required", "No Overflow client id and API key are configured for this connector. Call authorize_connector for recovery options; an operator adds both fields in this connection in the operator UI.");
|
|
134
|
-
}
|
|
135
|
-
return { "x-client-id": clientId, "x-api-key": apiKey };
|
|
136
|
-
}
|
|
137
|
-
function overflowTransport(origin) {
|
|
138
|
-
return guardedFetch({
|
|
139
|
-
provider: "Overflow",
|
|
140
|
-
baseUrl: `${origin.replace(/\/+$/, "")}${API_PATH}`,
|
|
141
|
-
headers: { Accept: "application/json" },
|
|
142
|
-
maxResponseBytes: OVERFLOW_MAX_RESPONSE_BYTES,
|
|
143
|
-
authenticate: apiKeyHeaders,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
// --- Failures ----------------------------------------------------------------
|
|
147
|
-
/** NestJS framing: `{ statusCode, message: string | string[], error }`, or `{ error }`. */
|
|
148
|
-
function detailFor(payload, status) {
|
|
149
|
-
const root = asRecord(payload);
|
|
150
|
-
const message = root["message"];
|
|
151
|
-
if (typeof message === "string" && message.trim())
|
|
152
|
-
return message.trim();
|
|
153
|
-
if (Array.isArray(message)) {
|
|
154
|
-
const lines = message.filter((line) => typeof line === "string" && line.trim() !== "");
|
|
155
|
-
if (lines.length > 0)
|
|
156
|
-
return lines.join("; ");
|
|
157
|
-
}
|
|
158
|
-
if (typeof root["error"] === "string" && root["error"].trim()) {
|
|
159
|
-
return root["error"].trim();
|
|
160
|
-
}
|
|
161
|
-
if (typeof payload === "string" && payload.trim()) {
|
|
162
|
-
return payload.trim().slice(0, 300);
|
|
163
|
-
}
|
|
164
|
-
return `Overflow returned HTTP ${status}.`;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Overflow's own wait: `Retry-After` if a proxy supplies one, otherwise
|
|
168
|
-
* `x-ratelimit-reset`, which Overflow documents as the seconds left in the
|
|
169
|
-
* current window (a delta, not an epoch).
|
|
170
|
-
*/
|
|
171
|
-
function windowResetMs(headers) {
|
|
172
|
-
const retryAfter = retryAfterMs(headers);
|
|
173
|
-
if (retryAfter !== undefined)
|
|
174
|
-
return retryAfter;
|
|
175
|
-
const raw = headers.get("x-ratelimit-reset");
|
|
176
|
-
if (!raw)
|
|
177
|
-
return undefined;
|
|
178
|
-
const seconds = Number(raw.trim());
|
|
179
|
-
return Number.isFinite(seconds) && seconds >= 0 ? Math.trunc(seconds * 1_000) : undefined;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Map an Overflow failure by what the caller does next (H11).
|
|
183
|
-
*
|
|
184
|
-
* Three readings here are Overflow-specific. A bad client id or key answers
|
|
185
|
-
* 403 "Forbidden resource" (observed against staging; the docs table says
|
|
186
|
-
* 401), and so does an attempt to edit a donor profile that a donor account
|
|
187
|
-
* owns — the status cannot tell those apart, so a write's message says so.
|
|
188
|
-
* A 404 is an absence: credentials are per nonprofit and Overflow scopes every
|
|
189
|
-
* read to it, so a missing id is missing from this nonprofit's view and the
|
|
190
|
-
* next move is to re-resolve it. And a 5xx on a write is never retryable from
|
|
191
|
-
* here: Overflow's own contract returns 500 from `POST /contributions` after
|
|
192
|
-
* work has started, so the charge may exist, and a blind retry could charge a
|
|
193
|
-
* donor twice.
|
|
194
|
-
*/
|
|
195
|
-
function overflowFailure(status, headers, payload, method) {
|
|
196
|
-
const detail = detailFor(payload, status);
|
|
197
|
-
if (status === 429) {
|
|
198
|
-
return new ConnectorCallError("rate_limited", `Overflow rate limit reached (HTTP 429): ${detail} Overflow allows 120 requests per minute per API client in a fixed 60-second window, shared by every integration using this client id.`, { retryAfterMs: windowResetMs(headers) ?? RATE_LIMIT_WINDOW_MS });
|
|
199
|
-
}
|
|
200
|
-
if (status === 401 || status === 403) {
|
|
201
|
-
const ambiguity = method !== "GET" && status === 403
|
|
202
|
-
? " Overflow also answers 403 when a donor profile owned by a donor account cannot be edited through the API; if reads succeed with this credential, that refusal is the cause and no credential change will fix it."
|
|
203
|
-
: "";
|
|
204
|
-
return new ConnectorCallError(status === 403 ? "provider_permission_denied" : "auth_required", `Overflow rejected the request (HTTP ${status}): ${detail} The client id and API key may be wrong, revoked, or issued for the other environment (staging and production credentials are not interchangeable); an operator must check them and ask the account administrator to grant resource access if they are valid.${ambiguity}`);
|
|
205
|
-
}
|
|
206
|
-
if (status === 404) {
|
|
207
|
-
return new ConnectorCallError("not_found", `Overflow found no such resource for this nonprofit (HTTP 404): ${detail} Re-resolve the id with its list tool; for a raw call, confirm the path below /api/v3.`);
|
|
208
|
-
}
|
|
209
|
-
if (status === 400) {
|
|
210
|
-
return new ConnectorCallError("invalid_args", `Overflow rejected the request (HTTP 400): ${detail}`);
|
|
211
|
-
}
|
|
212
|
-
if (status === 409 || status === 422) {
|
|
213
|
-
return new ConnectorCallError("connector_call_failed", `Overflow refused the operation (HTTP ${status}): ${detail} Repeating the same call will not change the outcome.`, { retryable: false });
|
|
214
|
-
}
|
|
215
|
-
if (status >= 500) {
|
|
216
|
-
if (method !== "GET") {
|
|
217
|
-
return new ConnectorCallError("connector_call_failed", `Overflow failed while processing a write (HTTP ${status}): ${detail} The write may or may not have taken effect. Read the affected contribution, donor, or recurring gift before retrying; repeating a charge, refund, or subscription change can apply it twice.`, { retryable: false });
|
|
218
|
-
}
|
|
219
|
-
const wait = windowResetMs(headers);
|
|
220
|
-
return new ConnectorCallError("unavailable", `Overflow is failing upstream (HTTP ${status}): ${detail}`, wait === undefined ? {} : { retryAfterMs: wait });
|
|
221
|
-
}
|
|
222
|
-
return new ConnectorCallError("connector_call_failed", `Overflow request failed (HTTP ${status}): ${detail}`, {
|
|
223
|
-
retryable: false,
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
function parseBody(text) {
|
|
227
|
-
if (!text.trim())
|
|
228
|
-
return undefined;
|
|
229
|
-
try {
|
|
230
|
-
return JSON.parse(text);
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
return text;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Send one request. Named tools read strictly — a successful response that is
|
|
238
|
-
* not JSON is a malformed answer — while the hatches return Overflow's body
|
|
239
|
-
* as it arrived, text included (`/health` answers `ok`).
|
|
240
|
-
*/
|
|
241
|
-
async function callOverflow(send, request, ctx, mode = "strict") {
|
|
242
|
-
return await send(request, ctx, async (response) => {
|
|
243
|
-
const text = await response.text();
|
|
244
|
-
if (!response.ok) {
|
|
245
|
-
throw overflowFailure(response.status, response.headers, parseBody(text), request.method);
|
|
246
|
-
}
|
|
247
|
-
const body = parseBody(text);
|
|
248
|
-
if (mode === "strict" && typeof body === "string") {
|
|
249
|
-
throw new ConnectorCallError("connector_call_failed", "Overflow returned a non-JSON body for a successful read.", { retryable: false });
|
|
250
|
-
}
|
|
251
|
-
return body;
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
function segment(value) {
|
|
255
|
-
return encodeURIComponent(String(value));
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Overflow pages by number and reports `totalCount`, so "is there another
|
|
259
|
-
* page?" is arithmetic over what was asked for. A response with no usable
|
|
260
|
-
* count reports `hasMore` from whether the page came back full — honest in
|
|
261
|
-
* the common case, and never a confident `false` on a page that was full.
|
|
262
|
-
*/
|
|
263
|
-
function pageInfo(payload, page, limit, rows) {
|
|
264
|
-
const total = asRecord(payload)["totalCount"];
|
|
265
|
-
if (typeof total === "number" && Number.isFinite(total)) {
|
|
266
|
-
return { page, limit, totalCount: total, hasMore: page * limit < total };
|
|
267
|
-
}
|
|
268
|
-
return { page, limit, totalCount: (page - 1) * limit + rows, hasMore: rows >= limit };
|
|
269
|
-
}
|
|
270
|
-
// --- Projections -------------------------------------------------------------
|
|
271
|
-
/**
|
|
272
|
-
* The donor as it rides inside a contribution: identity only. Overflow embeds
|
|
273
|
-
* the whole profile — email, phone, mailing address, giving aggregates — in
|
|
274
|
-
* every contribution row; `get_donor` is where that belongs.
|
|
275
|
-
*/
|
|
276
|
-
function projectDonorReference(value) {
|
|
277
|
-
const donor = asRecord(value);
|
|
278
|
-
if (Object.keys(donor).length === 0)
|
|
279
|
-
return undefined;
|
|
280
|
-
return compact({
|
|
281
|
-
id: donor["id"],
|
|
282
|
-
firstName: donor["firstName"],
|
|
283
|
-
lastName: donor["lastName"],
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
function projectContribution(value) {
|
|
287
|
-
const contribution = asRecord(value);
|
|
288
|
-
const paymentMethod = asRecord(contribution["paymentMethod"]);
|
|
289
|
-
const stocks = asRecord(contribution["stocks"]);
|
|
290
|
-
const crypto = asRecord(contribution["crypto"]);
|
|
291
|
-
return compact({
|
|
292
|
-
id: contribution["id"],
|
|
293
|
-
type: contribution["type"],
|
|
294
|
-
status: contribution["status"],
|
|
295
|
-
amount: contribution["amount"],
|
|
296
|
-
frequency: contribution["frequency"] ?? undefined,
|
|
297
|
-
contributionDate: contribution["contributionDate"],
|
|
298
|
-
donor: projectDonorReference(contribution["donor"]),
|
|
299
|
-
anonymous: contribution["anonymous"],
|
|
300
|
-
donorCoveredFees: contribution["donorCoveredFees"],
|
|
301
|
-
campaign: reference(contribution["campaign"]),
|
|
302
|
-
subcampaign: reference(contribution["subcampaign"]),
|
|
303
|
-
locationId: contribution["locationId"],
|
|
304
|
-
depositId: contribution["depositId"] ?? undefined,
|
|
305
|
-
subscriptionId: contribution["subscriptionId"],
|
|
306
|
-
givingLinkId: contribution["givingLinkId"] ?? undefined,
|
|
307
|
-
pledgeId: contribution["pledgeId"] ?? undefined,
|
|
308
|
-
paymentMethod: Object.keys(paymentMethod).length === 0
|
|
309
|
-
? undefined
|
|
310
|
-
: compact({ type: paymentMethod["type"], last4: paymentMethod["last4"] }),
|
|
311
|
-
stocks: Object.keys(stocks).length === 0
|
|
312
|
-
? undefined
|
|
313
|
-
: compact({ quantity: stocks["quantity"], tickers: stocks["tickers"] }),
|
|
314
|
-
crypto: Object.keys(crypto).length === 0 ? undefined : compact({ quantity: crypto["quantity"], token: crypto["token"] }),
|
|
315
|
-
dedication: contribution["dedication"] ?? undefined,
|
|
316
|
-
donorNotes: contribution["donorNotes"] ?? undefined,
|
|
317
|
-
metadata: contribution["metadata"] ?? undefined,
|
|
318
|
-
createdAt: contribution["createdAt"],
|
|
319
|
-
updatedAt: contribution["updatedAt"],
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
function projectDonor(value, full) {
|
|
323
|
-
const donor = asRecord(value);
|
|
324
|
-
return compact({
|
|
325
|
-
id: donor["id"],
|
|
326
|
-
firstName: donor["firstName"],
|
|
327
|
-
lastName: donor["lastName"],
|
|
328
|
-
email: donor["email"],
|
|
329
|
-
phone: full ? donor["phone"] : undefined,
|
|
330
|
-
address: full ? (donor["address"] ?? undefined) : undefined,
|
|
331
|
-
locationIds: donor["locationIds"],
|
|
332
|
-
totalContributionsCount: donor["totalContributionsCount"],
|
|
333
|
-
activeRecurringCount: donor["activeRecurringCount"],
|
|
334
|
-
latestContributionDate: donor["latestContributionDate"],
|
|
335
|
-
createdAt: donor["createdAt"],
|
|
336
|
-
updatedAt: donor["updatedAt"],
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
function projectDeposit(value, withLineItems) {
|
|
340
|
-
const deposit = asRecord(value);
|
|
341
|
-
return compact({
|
|
342
|
-
id: deposit["id"],
|
|
343
|
-
status: deposit["status"],
|
|
344
|
-
type: deposit["type"],
|
|
345
|
-
amountInCents: deposit["amountInCents"],
|
|
346
|
-
arrivalAt: deposit["arrivalAt"],
|
|
347
|
-
name: deposit["name"] ?? undefined,
|
|
348
|
-
bankName: deposit["bankName"] ?? undefined,
|
|
349
|
-
bankLast4: deposit["bankLast4"] ?? undefined,
|
|
350
|
-
statementDescriptor: deposit["statementDescriptor"] ?? undefined,
|
|
351
|
-
paymentMethodTypes: deposit["paymentMethodType"],
|
|
352
|
-
startingBalanceInCents: deposit["startingBalanceInCents"] ?? undefined,
|
|
353
|
-
endingBalanceInCents: deposit["endingBalanceInCents"] ?? undefined,
|
|
354
|
-
reconciledAt: deposit["reconciledAt"] ?? null,
|
|
355
|
-
createdAt: deposit["createdAt"],
|
|
356
|
-
updatedAt: deposit["updatedAt"],
|
|
357
|
-
lineItems: withLineItems
|
|
358
|
-
? asArray(deposit["lineItems"]).map((row) => {
|
|
359
|
-
const item = asRecord(row);
|
|
360
|
-
return compact({
|
|
361
|
-
type: item["type"],
|
|
362
|
-
grossValueInCents: item["grossValueInCents"],
|
|
363
|
-
grossFeeValueInCents: item["grossFeeValueInCents"],
|
|
364
|
-
referenceId: item["referenceId"],
|
|
365
|
-
description: item["description"],
|
|
366
|
-
});
|
|
367
|
-
})
|
|
368
|
-
: undefined,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
function projectRefund(value) {
|
|
372
|
-
const refund = asRecord(value);
|
|
373
|
-
return compact({
|
|
374
|
-
id: refund["id"],
|
|
375
|
-
contributionId: refund["contributionId"],
|
|
376
|
-
status: refund["status"],
|
|
377
|
-
reason: refund["reason"] ?? undefined,
|
|
378
|
-
amountInCents: refund["amountInCents"],
|
|
379
|
-
netValueInCents: refund["netValueInCents"],
|
|
380
|
-
feeValueInCents: refund["feeValueInCents"] ?? undefined,
|
|
381
|
-
depositId: refund["depositId"] ?? undefined,
|
|
382
|
-
createdAt: refund["createdAt"],
|
|
383
|
-
updatedAt: refund["updatedAt"],
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
function projectChargeback(value) {
|
|
387
|
-
const chargeback = asRecord(value);
|
|
388
|
-
return compact({
|
|
389
|
-
id: chargeback["id"],
|
|
390
|
-
contributionId: chargeback["contributionId"],
|
|
391
|
-
status: chargeback["status"],
|
|
392
|
-
type: chargeback["type"],
|
|
393
|
-
reason: chargeback["reason"] ?? undefined,
|
|
394
|
-
amountInCents: chargeback["amountInCents"],
|
|
395
|
-
netValueInCents: chargeback["netValueInCents"] ?? undefined,
|
|
396
|
-
feeValueInCents: chargeback["feeValueInCents"] ?? undefined,
|
|
397
|
-
depositId: chargeback["depositId"] ?? undefined,
|
|
398
|
-
createdAt: chargeback["createdAt"],
|
|
399
|
-
updatedAt: chargeback["updatedAt"],
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
function projectSubscription(value) {
|
|
403
|
-
const subscription = asRecord(value);
|
|
404
|
-
const paymentMethod = asRecord(subscription["paymentMethod"]);
|
|
405
|
-
return compact({
|
|
406
|
-
id: subscription["id"],
|
|
407
|
-
donorId: subscription["donorId"],
|
|
408
|
-
status: subscription["status"],
|
|
409
|
-
amount: subscription["amount"],
|
|
410
|
-
frequency: subscription["frequency"],
|
|
411
|
-
startDate: subscription["startDate"],
|
|
412
|
-
nextPaymentDate: subscription["nextPaymentDate"] ?? null,
|
|
413
|
-
anonymous: subscription["anonymous"],
|
|
414
|
-
donorCoveredFees: subscription["donorCoveredFees"],
|
|
415
|
-
campaign: reference(subscription["campaign"]),
|
|
416
|
-
subcampaign: reference(subscription["subcampaign"]),
|
|
417
|
-
locationId: subscription["locationId"] ?? undefined,
|
|
418
|
-
paymentMethod: Object.keys(paymentMethod).length === 0
|
|
419
|
-
? undefined
|
|
420
|
-
: compact({
|
|
421
|
-
id: paymentMethod["id"],
|
|
422
|
-
type: paymentMethod["type"],
|
|
423
|
-
last4: paymentMethod["last4"],
|
|
424
|
-
expiration: paymentMethod["expiration"],
|
|
425
|
-
}),
|
|
426
|
-
metadata: subscription["metadata"] ?? undefined,
|
|
427
|
-
createdAt: subscription["createdAt"],
|
|
428
|
-
updatedAt: subscription["updatedAt"],
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
function projectPaymentMethod(value) {
|
|
432
|
-
const method = asRecord(value);
|
|
433
|
-
return compact({
|
|
434
|
-
id: method["id"],
|
|
435
|
-
type: method["type"],
|
|
436
|
-
last4: method["last4"],
|
|
437
|
-
expirationDate: method["expirationDate"],
|
|
438
|
-
isExpired: method["isExpired"],
|
|
439
|
-
updatedAt: method["updatedAt"],
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
function projectCampaign(value) {
|
|
443
|
-
const campaign = asRecord(value);
|
|
444
|
-
const refs = (list) => {
|
|
445
|
-
const rows = asArray(list)
|
|
446
|
-
.map(reference)
|
|
447
|
-
.filter((row) => row !== undefined);
|
|
448
|
-
return rows.length === 0 ? undefined : rows;
|
|
449
|
-
};
|
|
450
|
-
return compact({
|
|
451
|
-
id: campaign["id"],
|
|
452
|
-
name: campaign["name"],
|
|
453
|
-
status: campaign["status"],
|
|
454
|
-
isSubcampaign: campaign["isSubcampaign"],
|
|
455
|
-
isActive: campaign["isActive"],
|
|
456
|
-
startDate: campaign["startDate"],
|
|
457
|
-
endDate: campaign["endDate"] ?? null,
|
|
458
|
-
hideFromDonors: campaign["hideFromDonors"],
|
|
459
|
-
archivedAt: campaign["archivedAt"] ?? undefined,
|
|
460
|
-
locationIds: campaign["locationIds"],
|
|
461
|
-
parentCampaigns: refs(campaign["parentCampaigns"]),
|
|
462
|
-
subcampaigns: refs(campaign["subcampaigns"]),
|
|
463
|
-
totalContributionCount: campaign["totalContributionCount"],
|
|
464
|
-
totalContributionValue: campaign["totalContributionValue"],
|
|
465
|
-
uniqueDonorCount: campaign["uniqueDonorCount"],
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
function projectLocation(value) {
|
|
469
|
-
const location = asRecord(value);
|
|
470
|
-
return compact({
|
|
471
|
-
id: location["id"],
|
|
472
|
-
name: location["name"],
|
|
473
|
-
isDefaultLocation: location["isDefaultLocation"],
|
|
474
|
-
hideFromDonors: location["hideFromDonors"],
|
|
475
|
-
archivedAt: location["archivedAt"] ?? null,
|
|
476
|
-
createdAt: location["createdAt"],
|
|
477
|
-
updatedAt: location["updatedAt"],
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
function projectTapEvent(value) {
|
|
481
|
-
const event = asRecord(value);
|
|
482
|
-
const device = asRecord(event["device"]);
|
|
483
|
-
const group = asRecord(event["group"]);
|
|
484
|
-
const destination = asRecord(event["destination"]);
|
|
485
|
-
return compact({
|
|
486
|
-
id: event["id"],
|
|
487
|
-
createdAt: event["createdAt"],
|
|
488
|
-
deviceId: event["deviceId"],
|
|
489
|
-
deviceSerialNumber: device["serialNumber"],
|
|
490
|
-
groupId: event["groupId"],
|
|
491
|
-
groupName: group["name"],
|
|
492
|
-
destinationId: event["destinationId"],
|
|
493
|
-
destinationName: destination["name"],
|
|
494
|
-
destinationType: destination["type"] ?? undefined,
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
function projectWebhook(value) {
|
|
498
|
-
const webhook = asRecord(value);
|
|
499
|
-
return compact({
|
|
500
|
-
id: webhook["id"],
|
|
501
|
-
name: webhook["name"],
|
|
502
|
-
description: webhook["description"] ?? undefined,
|
|
503
|
-
destinationUrl: webhook["destinationUrl"],
|
|
504
|
-
enabledEvents: webhook["enabledEvents"],
|
|
505
|
-
status: webhook["status"],
|
|
506
|
-
createdAt: webhook["createdAt"],
|
|
507
|
-
updatedAt: webhook["updatedAt"],
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* Deliberately drops `request.body` and `response.body`. The request body is
|
|
512
|
-
* the event payload Overflow delivered — donor names, emails, and amounts —
|
|
513
|
-
* and the response body is whatever the receiving server echoed. Diagnosing a
|
|
514
|
-
* missed delivery needs the status, the code, and the error, not the payload.
|
|
515
|
-
*/
|
|
516
|
-
function projectEventLog(value) {
|
|
517
|
-
const log = asRecord(value);
|
|
518
|
-
const request = asRecord(log["request"]);
|
|
519
|
-
const response = asRecord(log["response"]);
|
|
520
|
-
return compact({
|
|
521
|
-
id: log["id"],
|
|
522
|
-
eventId: log["eventId"],
|
|
523
|
-
webhookEventName: log["webhookEventName"],
|
|
524
|
-
status: log["status"],
|
|
525
|
-
attemptNumber: log["attemptNumber"],
|
|
526
|
-
deliverySource: log["deliverySource"],
|
|
527
|
-
originatedAt: log["originatedAt"],
|
|
528
|
-
attemptedAt: log["attemptedAt"],
|
|
529
|
-
url: request["url"],
|
|
530
|
-
responseCode: response["code"],
|
|
531
|
-
responseDurationMs: response["durationMs"],
|
|
532
|
-
errorMessage: response["errorMessage"],
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
// --- Schema fragments --------------------------------------------------------
|
|
536
|
-
function namedInput(properties, required = []) {
|
|
537
|
-
return { type: "object", properties, required, additionalProperties: false };
|
|
538
|
-
}
|
|
539
|
-
const RAW_PROPERTY = {
|
|
540
|
-
type: "boolean",
|
|
541
|
-
description: "Return Overflow's untouched rows instead of the lean projection.",
|
|
542
|
-
};
|
|
543
|
-
const OBJECT_ID = "^[0-9a-fA-F]{24}$";
|
|
544
|
-
function idProperty(source) {
|
|
545
|
-
return {
|
|
546
|
-
type: "string",
|
|
547
|
-
pattern: OBJECT_ID,
|
|
548
|
-
description: `24-hex id from ${source}.`,
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
const DATE_PROPERTY = (meaning) => ({
|
|
552
|
-
type: "string",
|
|
553
|
-
pattern: "^\\d{4}-\\d{2}-\\d{2}",
|
|
554
|
-
description: `${meaning}; ISO date or date-time.`,
|
|
555
|
-
});
|
|
556
|
-
const SORT_DIRECTION = {
|
|
557
|
-
type: "string",
|
|
558
|
-
enum: ["ASC", "DESC"],
|
|
559
|
-
description: "Sort direction.",
|
|
560
|
-
};
|
|
561
|
-
const ID_LIST = (source) => ({
|
|
562
|
-
type: "array",
|
|
563
|
-
minItems: 1,
|
|
564
|
-
items: { type: "string", pattern: OBJECT_ID, description: `Id from ${source}.` },
|
|
565
|
-
description: `Ids from ${source}; matches any.`,
|
|
566
|
-
});
|
|
567
|
-
const PAGE_PROPERTY = {
|
|
568
|
-
type: "integer",
|
|
569
|
-
minimum: 1,
|
|
570
|
-
description: "1-based page number. Defaults to 1.",
|
|
571
|
-
};
|
|
572
|
-
function limitProperty(defaultPageSize) {
|
|
573
|
-
return {
|
|
574
|
-
type: "integer",
|
|
575
|
-
minimum: 1,
|
|
576
|
-
maximum: MAX_PAGE_SIZE,
|
|
577
|
-
description: `Rows per page, up to Overflow's cap of ${MAX_PAGE_SIZE}. Defaults to ${defaultPageSize}.`,
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
const PAGE_OUTPUT = {
|
|
581
|
-
type: "object",
|
|
582
|
-
properties: {
|
|
583
|
-
page: { type: "integer" },
|
|
584
|
-
limit: { type: "integer" },
|
|
585
|
-
totalCount: { type: "integer" },
|
|
586
|
-
hasMore: { type: "boolean", description: "True when page + 1 exists." },
|
|
587
|
-
},
|
|
588
|
-
required: ["page", "limit", "totalCount", "hasMore"],
|
|
589
|
-
};
|
|
590
|
-
function listOutput(key, item) {
|
|
591
|
-
return {
|
|
592
|
-
type: "object",
|
|
593
|
-
properties: { [key]: { type: "array", items: item }, page: PAGE_OUTPUT },
|
|
594
|
-
required: [key, "page"],
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
const REFERENCE = {
|
|
598
|
-
type: "object",
|
|
599
|
-
properties: { id: { type: "string" }, name: { type: "string" } },
|
|
600
|
-
};
|
|
601
|
-
const CONTRIBUTION_SCHEMA = {
|
|
602
|
-
type: "object",
|
|
603
|
-
properties: {
|
|
604
|
-
id: { type: "string" },
|
|
605
|
-
type: { type: "string" },
|
|
606
|
-
status: { type: "string" },
|
|
607
|
-
amount: { type: "number" },
|
|
608
|
-
frequency: { type: "string" },
|
|
609
|
-
contributionDate: { type: "string" },
|
|
610
|
-
donor: { type: "object" },
|
|
611
|
-
campaign: REFERENCE,
|
|
612
|
-
subcampaign: REFERENCE,
|
|
613
|
-
locationId: { type: "string" },
|
|
614
|
-
depositId: { type: "string" },
|
|
615
|
-
subscriptionId: { type: "string" },
|
|
616
|
-
paymentMethod: { type: "object" },
|
|
617
|
-
stocks: { type: "object" },
|
|
618
|
-
crypto: { type: "object" },
|
|
619
|
-
metadata: { type: "object" },
|
|
620
|
-
createdAt: { type: "string" },
|
|
621
|
-
updatedAt: { type: "string" },
|
|
622
|
-
},
|
|
623
|
-
required: ["id", "type", "status", "amount"],
|
|
624
|
-
};
|
|
625
|
-
const DONOR_SCHEMA = {
|
|
626
|
-
type: "object",
|
|
627
|
-
properties: {
|
|
628
|
-
id: { type: "string" },
|
|
629
|
-
firstName: { type: "string" },
|
|
630
|
-
lastName: { type: "string" },
|
|
631
|
-
email: { type: "string" },
|
|
632
|
-
phone: { type: "string" },
|
|
633
|
-
address: { type: "object" },
|
|
634
|
-
locationIds: { type: "array", items: { type: "string" } },
|
|
635
|
-
totalContributionsCount: { type: "number" },
|
|
636
|
-
activeRecurringCount: { type: "number" },
|
|
637
|
-
latestContributionDate: { type: "string" },
|
|
638
|
-
createdAt: { type: "string" },
|
|
639
|
-
updatedAt: { type: "string" },
|
|
640
|
-
},
|
|
641
|
-
required: ["id"],
|
|
642
|
-
};
|
|
643
|
-
const DEPOSIT_SCHEMA = {
|
|
644
|
-
type: "object",
|
|
645
|
-
properties: {
|
|
646
|
-
id: { type: "string" },
|
|
647
|
-
status: { type: "string" },
|
|
648
|
-
type: { type: "string" },
|
|
649
|
-
amountInCents: { type: "number" },
|
|
650
|
-
arrivalAt: { type: "string" },
|
|
651
|
-
name: { type: "string" },
|
|
652
|
-
bankName: { type: "string" },
|
|
653
|
-
bankLast4: { type: "string" },
|
|
654
|
-
paymentMethodTypes: { type: "array", items: { type: "string" } },
|
|
655
|
-
reconciledAt: { type: ["string", "null"] },
|
|
656
|
-
lineItems: { type: "array", items: { type: "object" } },
|
|
657
|
-
createdAt: { type: "string" },
|
|
658
|
-
updatedAt: { type: "string" },
|
|
659
|
-
},
|
|
660
|
-
required: ["id", "status", "amountInCents"],
|
|
661
|
-
};
|
|
662
|
-
const MONEY_ROLLUP = {
|
|
663
|
-
type: "object",
|
|
664
|
-
properties: {
|
|
665
|
-
count: { type: "number" },
|
|
666
|
-
grossInCents: { type: "number" },
|
|
667
|
-
feesInCents: { type: "number" },
|
|
668
|
-
totalInCents: { type: "number" },
|
|
669
|
-
},
|
|
670
|
-
};
|
|
671
|
-
const REFUND_SCHEMA = {
|
|
672
|
-
type: "object",
|
|
673
|
-
properties: {
|
|
674
|
-
id: { type: "string" },
|
|
675
|
-
contributionId: { type: "string" },
|
|
676
|
-
status: { type: "string" },
|
|
677
|
-
reason: { type: "string" },
|
|
678
|
-
amountInCents: { type: "number" },
|
|
679
|
-
netValueInCents: { type: "number" },
|
|
680
|
-
feeValueInCents: { type: "number" },
|
|
681
|
-
depositId: { type: "string" },
|
|
682
|
-
createdAt: { type: "string" },
|
|
683
|
-
updatedAt: { type: "string" },
|
|
684
|
-
},
|
|
685
|
-
required: ["id", "contributionId", "status", "amountInCents"],
|
|
686
|
-
};
|
|
687
|
-
const CHARGEBACK_SCHEMA = {
|
|
688
|
-
type: "object",
|
|
689
|
-
properties: {
|
|
690
|
-
id: { type: "string" },
|
|
691
|
-
contributionId: { type: "string" },
|
|
692
|
-
status: { type: "string" },
|
|
693
|
-
type: { type: "string" },
|
|
694
|
-
reason: { type: "string" },
|
|
695
|
-
amountInCents: { type: "number" },
|
|
696
|
-
netValueInCents: { type: "number" },
|
|
697
|
-
depositId: { type: "string" },
|
|
698
|
-
createdAt: { type: "string" },
|
|
699
|
-
updatedAt: { type: "string" },
|
|
700
|
-
},
|
|
701
|
-
required: ["id", "contributionId", "status", "amountInCents"],
|
|
702
|
-
};
|
|
703
|
-
const SUBSCRIPTION_SCHEMA = {
|
|
704
|
-
type: "object",
|
|
705
|
-
properties: {
|
|
706
|
-
id: { type: "string" },
|
|
707
|
-
donorId: { type: "string" },
|
|
708
|
-
status: { type: "string" },
|
|
709
|
-
amount: { type: "number" },
|
|
710
|
-
frequency: { type: "string" },
|
|
711
|
-
startDate: { type: "string" },
|
|
712
|
-
nextPaymentDate: { type: ["string", "null"] },
|
|
713
|
-
campaign: REFERENCE,
|
|
714
|
-
subcampaign: REFERENCE,
|
|
715
|
-
locationId: { type: "string" },
|
|
716
|
-
paymentMethod: { type: "object" },
|
|
717
|
-
createdAt: { type: "string" },
|
|
718
|
-
updatedAt: { type: "string" },
|
|
719
|
-
},
|
|
720
|
-
required: ["id", "donorId", "status", "amount", "frequency"],
|
|
721
|
-
};
|
|
722
|
-
const PAYMENT_METHOD_SCHEMA = {
|
|
723
|
-
type: "object",
|
|
724
|
-
properties: {
|
|
725
|
-
id: { type: "string" },
|
|
726
|
-
type: { type: "string" },
|
|
727
|
-
last4: { type: "string" },
|
|
728
|
-
expirationDate: { type: "string" },
|
|
729
|
-
isExpired: { type: "boolean" },
|
|
730
|
-
updatedAt: { type: "string" },
|
|
731
|
-
},
|
|
732
|
-
required: ["id", "type", "last4"],
|
|
733
|
-
};
|
|
734
|
-
const CAMPAIGN_SCHEMA = {
|
|
735
|
-
type: "object",
|
|
736
|
-
properties: {
|
|
737
|
-
id: { type: "string" },
|
|
738
|
-
name: { type: "string" },
|
|
739
|
-
status: { type: "string" },
|
|
740
|
-
isSubcampaign: { type: "boolean" },
|
|
741
|
-
isActive: { type: "boolean" },
|
|
742
|
-
startDate: { type: "string" },
|
|
743
|
-
endDate: { type: ["string", "null"] },
|
|
744
|
-
locationIds: { type: "array", items: { type: "string" } },
|
|
745
|
-
parentCampaigns: { type: "array", items: REFERENCE },
|
|
746
|
-
subcampaigns: { type: "array", items: REFERENCE },
|
|
747
|
-
totalContributionCount: { type: "number" },
|
|
748
|
-
totalContributionValue: { type: "number" },
|
|
749
|
-
uniqueDonorCount: { type: "number" },
|
|
750
|
-
},
|
|
751
|
-
required: ["id", "name", "status"],
|
|
752
|
-
};
|
|
753
|
-
const LOCATION_SCHEMA = {
|
|
754
|
-
type: "object",
|
|
755
|
-
properties: {
|
|
756
|
-
id: { type: "string" },
|
|
757
|
-
name: { type: "string" },
|
|
758
|
-
isDefaultLocation: { type: "boolean" },
|
|
759
|
-
hideFromDonors: { type: "boolean" },
|
|
760
|
-
archivedAt: { type: ["string", "null"] },
|
|
761
|
-
createdAt: { type: "string" },
|
|
762
|
-
},
|
|
763
|
-
required: ["id", "name"],
|
|
764
|
-
};
|
|
765
|
-
const TAP_EVENT_SCHEMA = {
|
|
766
|
-
type: "object",
|
|
767
|
-
properties: {
|
|
768
|
-
id: { type: "string" },
|
|
769
|
-
createdAt: { type: "string" },
|
|
770
|
-
deviceId: { type: "string" },
|
|
771
|
-
deviceSerialNumber: { type: "number" },
|
|
772
|
-
groupId: { type: "string" },
|
|
773
|
-
groupName: { type: "string" },
|
|
774
|
-
destinationId: { type: "string" },
|
|
775
|
-
destinationName: { type: "string" },
|
|
776
|
-
destinationType: { type: "string" },
|
|
777
|
-
},
|
|
778
|
-
required: ["id", "createdAt"],
|
|
779
|
-
};
|
|
780
|
-
const WEBHOOK_SCHEMA = {
|
|
781
|
-
type: "object",
|
|
782
|
-
properties: {
|
|
783
|
-
id: { type: "string" },
|
|
784
|
-
name: { type: "string" },
|
|
785
|
-
destinationUrl: { type: "string" },
|
|
786
|
-
enabledEvents: { type: "array", items: { type: "string" } },
|
|
787
|
-
status: { type: "string" },
|
|
788
|
-
createdAt: { type: "string" },
|
|
789
|
-
},
|
|
790
|
-
required: ["id", "name", "status"],
|
|
791
|
-
};
|
|
792
|
-
const EVENT_LOG_SCHEMA = {
|
|
793
|
-
type: "object",
|
|
794
|
-
properties: {
|
|
795
|
-
id: { type: "string" },
|
|
796
|
-
eventId: { type: "string" },
|
|
797
|
-
webhookEventName: { type: "string" },
|
|
798
|
-
status: { type: "string" },
|
|
799
|
-
attemptNumber: { type: "number" },
|
|
800
|
-
attemptedAt: { type: "string" },
|
|
801
|
-
url: { type: "string" },
|
|
802
|
-
responseCode: { type: "number" },
|
|
803
|
-
errorMessage: { type: "string" },
|
|
804
|
-
},
|
|
805
|
-
required: ["id", "status"],
|
|
806
|
-
};
|
|
807
|
-
// --- Raw hatches ---------------------------------------------------------------
|
|
808
|
-
const RAW_PATH_PROPERTY = {
|
|
809
|
-
type: "string",
|
|
810
|
-
minLength: 1,
|
|
811
|
-
description: "Path below /api/v3 beginning with '/', e.g. /refunds/<id>. No query string.",
|
|
812
|
-
};
|
|
813
|
-
const RAW_QUERY_PROPERTY = {
|
|
814
|
-
type: "array",
|
|
815
|
-
maxItems: 50,
|
|
816
|
-
description: "Query parameters as name/value pairs; repeat a name to send several values.",
|
|
817
|
-
items: {
|
|
818
|
-
type: "object",
|
|
819
|
-
properties: {
|
|
820
|
-
name: { type: "string", minLength: 1, description: "Parameter name, e.g. statusBucket[]." },
|
|
821
|
-
value: {
|
|
822
|
-
type: ["string", "number", "boolean"],
|
|
823
|
-
description: "Parameter value.",
|
|
824
|
-
},
|
|
825
|
-
},
|
|
826
|
-
required: ["name", "value"],
|
|
827
|
-
additionalProperties: false,
|
|
828
|
-
},
|
|
829
|
-
};
|
|
830
|
-
/** Name/value pairs to a query, repeated names becoming repeated keys. */
|
|
831
|
-
function rawQuery(value) {
|
|
832
|
-
const query = {};
|
|
833
|
-
for (const row of asArray(value)) {
|
|
834
|
-
const pair = asRecord(row);
|
|
835
|
-
const name = pair["name"];
|
|
836
|
-
const item = pair["value"];
|
|
837
|
-
if (typeof name !== "string")
|
|
838
|
-
continue;
|
|
839
|
-
if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") {
|
|
840
|
-
(query[name] ??= []).push(item);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
return Object.fromEntries(Object.entries(query).map(([name, values]) => [name, values.length === 1 ? values[0] : values]));
|
|
844
|
-
}
|
|
845
|
-
function tools(send, defaultPageSize) {
|
|
846
|
-
const readOnly = { readOnlyHint: true };
|
|
847
|
-
const LIMIT = limitProperty(defaultPageSize);
|
|
848
|
-
/** One page-numbered collection read: `{ data, totalCount }` in, `{ <key>, page }` out. */
|
|
849
|
-
async function list(spec, ctx) {
|
|
850
|
-
const page = spec.args["page"] ?? 1;
|
|
851
|
-
const limit = spec.args["limit"] ?? defaultPageSize;
|
|
852
|
-
const payload = await callOverflow(send, { method: "GET", path: spec.path, query: { ...spec.query, page, limit } }, ctx);
|
|
853
|
-
const rows = asArray(asRecord(payload)["data"]);
|
|
854
|
-
return {
|
|
855
|
-
[spec.key]: spec.args["raw"] === true ? rows : rows.map(spec.project),
|
|
856
|
-
page: pageInfo(payload, page, limit, rows.length),
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
/** One single-resource read: Overflow wraps it as `{ data }`. */
|
|
860
|
-
async function one(path, ctx) {
|
|
861
|
-
const payload = await callOverflow(send, { method: "GET", path }, ctx);
|
|
862
|
-
const record = asRecord(payload)["data"];
|
|
863
|
-
if (record === undefined || record === null) {
|
|
864
|
-
throw new ConnectorCallError("connector_call_failed", "Overflow answered without the requested record.", {
|
|
865
|
-
retryable: false,
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
return asRecord(record);
|
|
869
|
-
}
|
|
870
|
-
return [
|
|
871
|
-
{
|
|
872
|
-
name: "overflow_api_get",
|
|
873
|
-
description: "Call any Overflow v3 GET endpoint and return its untouched body. Prefer a named read; this covers single refunds, chargebacks, campaigns, and Tap devices.",
|
|
874
|
-
annotations: readOnly,
|
|
875
|
-
inputSchema: namedInput({ path: RAW_PATH_PROPERTY, query: RAW_QUERY_PROPERTY }, ["path"]),
|
|
876
|
-
outputSchema: {
|
|
877
|
-
type: "object",
|
|
878
|
-
properties: { result: { description: "Overflow's untouched response body." } },
|
|
879
|
-
required: ["result"],
|
|
880
|
-
},
|
|
881
|
-
handler: async (args, ctx) => ({
|
|
882
|
-
result: (await callOverflow(send, { method: "GET", path: String(args["path"]), query: rawQuery(args["query"]) }, ctx, "raw")) ?? null,
|
|
883
|
-
}),
|
|
884
|
-
},
|
|
885
|
-
{
|
|
886
|
-
name: "overflow_api_mutate",
|
|
887
|
-
description: "Send a JSON POST, PATCH, or DELETE to Overflow v3. Every Overflow write — charges, refunds, recurring gifts, donor edits — goes here, behind approval.",
|
|
888
|
-
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
889
|
-
inputSchema: namedInput({
|
|
890
|
-
method: {
|
|
891
|
-
type: "string",
|
|
892
|
-
enum: ["POST", "PATCH", "DELETE"],
|
|
893
|
-
description: "Mutation method the Overflow endpoint requires.",
|
|
894
|
-
},
|
|
895
|
-
path: RAW_PATH_PROPERTY,
|
|
896
|
-
query: RAW_QUERY_PROPERTY,
|
|
897
|
-
body: {
|
|
898
|
-
type: ["object", "array"],
|
|
899
|
-
description: "JSON body exactly as Overflow documents it. Units differ: subscription `amount` is dollars, " +
|
|
900
|
-
"/payments/authorize takes `amountInCents`, /contributions `amount` states none. Omit when the endpoint takes none.",
|
|
901
|
-
},
|
|
902
|
-
}, ["method", "path"]),
|
|
903
|
-
outputSchema: {
|
|
904
|
-
type: "object",
|
|
905
|
-
properties: {
|
|
906
|
-
result: { description: "Overflow's untouched response body, or null when it sent none." },
|
|
907
|
-
},
|
|
908
|
-
required: ["result"],
|
|
909
|
-
},
|
|
910
|
-
handler: async (args, ctx) => ({
|
|
911
|
-
result: (await callOverflow(send, {
|
|
912
|
-
method: args["method"],
|
|
913
|
-
path: String(args["path"]),
|
|
914
|
-
query: rawQuery(args["query"]),
|
|
915
|
-
...(args["body"] !== undefined ? { body: args["body"] } : {}),
|
|
916
|
-
}, ctx, "raw")) ?? null,
|
|
917
|
-
}),
|
|
918
|
-
},
|
|
919
|
-
{
|
|
920
|
-
name: "list_contributions",
|
|
921
|
-
description: "List gifts (cash, stock, crypto, DAF, manual) with status, amount, campaign, deposit, and donor name. Donor contact details stay in get_donor.",
|
|
922
|
-
annotations: readOnly,
|
|
923
|
-
inputSchema: namedInput({
|
|
924
|
-
minimumUpdatedDate: DATE_PROPERTY("Updated on or after"),
|
|
925
|
-
maximumUpdatedDate: DATE_PROPERTY("Updated on or before"),
|
|
926
|
-
minimumInitiatedDate: DATE_PROPERTY("Initiated on or after"),
|
|
927
|
-
maximumInitiatedDate: DATE_PROPERTY("Initiated on or before"),
|
|
928
|
-
statusBucket: {
|
|
929
|
-
type: "array",
|
|
930
|
-
minItems: 1,
|
|
931
|
-
uniqueItems: true,
|
|
932
|
-
items: {
|
|
933
|
-
type: "string",
|
|
934
|
-
enum: ["PENDING", "CONFIRMED", "FAILED", "CANCELED"],
|
|
935
|
-
description: "Status bucket.",
|
|
936
|
-
},
|
|
937
|
-
description: "Overflow's coarse status buckets; matches any.",
|
|
938
|
-
},
|
|
939
|
-
campaignId: idProperty("list_campaigns"),
|
|
940
|
-
subcampaignId: idProperty("list_campaigns with isSubcampaign"),
|
|
941
|
-
locationIds: ID_LIST("list_locations"),
|
|
942
|
-
givingLinkId: { type: "string", minLength: 1, description: "Giving link id." },
|
|
943
|
-
pledgeId: { type: "string", minLength: 1, description: "Pledge id." },
|
|
944
|
-
page: PAGE_PROPERTY,
|
|
945
|
-
limit: LIMIT,
|
|
946
|
-
raw: RAW_PROPERTY,
|
|
947
|
-
}),
|
|
948
|
-
outputSchema: listOutput("contributions", CONTRIBUTION_SCHEMA),
|
|
949
|
-
handler: (args, ctx) => list({
|
|
950
|
-
args,
|
|
951
|
-
path: "/contributions",
|
|
952
|
-
key: "contributions",
|
|
953
|
-
project: projectContribution,
|
|
954
|
-
query: {
|
|
955
|
-
minimumUpdatedDate: args["minimumUpdatedDate"],
|
|
956
|
-
maximumUpdatedDate: args["maximumUpdatedDate"],
|
|
957
|
-
minimumInitiatedDate: args["minimumInitiatedDate"],
|
|
958
|
-
maximumInitiatedDate: args["maximumInitiatedDate"],
|
|
959
|
-
"statusBucket[]": args["statusBucket"],
|
|
960
|
-
campaignId: args["campaignId"],
|
|
961
|
-
subcampaignId: args["subcampaignId"],
|
|
962
|
-
"locationIds[]": args["locationIds"],
|
|
963
|
-
givingLinkId: args["givingLinkId"],
|
|
964
|
-
pledgeId: args["pledgeId"],
|
|
965
|
-
},
|
|
966
|
-
}, ctx),
|
|
967
|
-
},
|
|
968
|
-
{
|
|
969
|
-
name: "get_contribution",
|
|
970
|
-
description: "Get one contribution by id with its status, amount, campaign, deposit, and donor name. Refunds and chargebacks are separate lists.",
|
|
971
|
-
annotations: readOnly,
|
|
972
|
-
inputSchema: namedInput({ contributionId: idProperty("list_contributions"), raw: RAW_PROPERTY }, [
|
|
973
|
-
"contributionId",
|
|
974
|
-
]),
|
|
975
|
-
outputSchema: CONTRIBUTION_SCHEMA,
|
|
976
|
-
handler: async (args, ctx) => {
|
|
977
|
-
const record = await one(`/contributions/${segment(args["contributionId"])}`, ctx);
|
|
978
|
-
return args["raw"] === true ? record : projectContribution(record);
|
|
979
|
-
},
|
|
980
|
-
},
|
|
981
|
-
{
|
|
982
|
-
name: "list_deposits",
|
|
983
|
-
description: "List deposits (payouts from Overflow to the nonprofit's bank) with status, net amount in cents, arrival date, and reconciliation state.",
|
|
984
|
-
annotations: readOnly,
|
|
985
|
-
inputSchema: namedInput({
|
|
986
|
-
startDate: DATE_PROPERTY("Arrived on or after"),
|
|
987
|
-
endDate: DATE_PROPERTY("Arrived on or before"),
|
|
988
|
-
types: {
|
|
989
|
-
type: "array",
|
|
990
|
-
minItems: 1,
|
|
991
|
-
uniqueItems: true,
|
|
992
|
-
items: { type: "string", enum: ["AUTOMATED", "MANUAL", "IMPORT"], description: "Deposit type." },
|
|
993
|
-
description: "Deposit types; matches any.",
|
|
994
|
-
},
|
|
995
|
-
reconciled: { type: "boolean", description: "Filter by reconciliation state." },
|
|
996
|
-
minimumAmountInCents: { type: "integer", minimum: 0, description: "Net amount at least this, in cents." },
|
|
997
|
-
maximumAmountInCents: { type: "integer", minimum: 0, description: "Net amount at most this, in cents." },
|
|
998
|
-
search: { type: "string", minLength: 1, description: "Overflow's free-text search across deposit fields." },
|
|
999
|
-
sortDirection: { ...SORT_DIRECTION, description: "Arrival-date order. Defaults to DESC." },
|
|
1000
|
-
page: PAGE_PROPERTY,
|
|
1001
|
-
limit: LIMIT,
|
|
1002
|
-
raw: RAW_PROPERTY,
|
|
1003
|
-
}),
|
|
1004
|
-
outputSchema: listOutput("deposits", DEPOSIT_SCHEMA),
|
|
1005
|
-
handler: (args, ctx) => list({
|
|
1006
|
-
args,
|
|
1007
|
-
path: "/deposits",
|
|
1008
|
-
key: "deposits",
|
|
1009
|
-
project: (row) => projectDeposit(row, false),
|
|
1010
|
-
query: {
|
|
1011
|
-
startDate: args["startDate"],
|
|
1012
|
-
endDate: args["endDate"],
|
|
1013
|
-
"types[]": args["types"],
|
|
1014
|
-
reconciled: args["reconciled"],
|
|
1015
|
-
minimumEstimatedValueInCents: args["minimumAmountInCents"],
|
|
1016
|
-
maximumEstimatedValueInCents: args["maximumAmountInCents"],
|
|
1017
|
-
search: args["search"],
|
|
1018
|
-
sortDirection: args["sortDirection"],
|
|
1019
|
-
},
|
|
1020
|
-
}, ctx),
|
|
1021
|
-
},
|
|
1022
|
-
{
|
|
1023
|
-
name: "get_deposit",
|
|
1024
|
-
description: "Get one deposit with its line items: each payment, refund, chargeback, adjustment, or transfer it settled, in cents. Totals are get_deposit_summary.",
|
|
1025
|
-
annotations: readOnly,
|
|
1026
|
-
inputSchema: namedInput({ depositId: idProperty("list_deposits"), raw: RAW_PROPERTY }, ["depositId"]),
|
|
1027
|
-
outputSchema: DEPOSIT_SCHEMA,
|
|
1028
|
-
handler: async (args, ctx) => {
|
|
1029
|
-
const record = await one(`/deposits/${segment(args["depositId"])}`, ctx);
|
|
1030
|
-
return args["raw"] === true ? record : projectDeposit(record, true);
|
|
1031
|
-
},
|
|
1032
|
-
},
|
|
1033
|
-
{
|
|
1034
|
-
name: "get_deposit_summary",
|
|
1035
|
-
description: "Get one deposit's totals by kind — contributions, refunds, chargebacks, adjustments — as count, gross, fees, and net in cents.",
|
|
1036
|
-
annotations: readOnly,
|
|
1037
|
-
inputSchema: namedInput({ depositId: idProperty("list_deposits") }, ["depositId"]),
|
|
1038
|
-
outputSchema: {
|
|
1039
|
-
type: "object",
|
|
1040
|
-
properties: {
|
|
1041
|
-
depositId: { type: "string" },
|
|
1042
|
-
contributions: MONEY_ROLLUP,
|
|
1043
|
-
refunds: MONEY_ROLLUP,
|
|
1044
|
-
chargebacks: MONEY_ROLLUP,
|
|
1045
|
-
adjustments: MONEY_ROLLUP,
|
|
1046
|
-
},
|
|
1047
|
-
required: ["depositId", "contributions", "refunds", "chargebacks", "adjustments"],
|
|
1048
|
-
},
|
|
1049
|
-
handler: async (args, ctx) => {
|
|
1050
|
-
const summary = await one(`/deposits/${segment(args["depositId"])}/summary`, ctx);
|
|
1051
|
-
const rollup = (value) => {
|
|
1052
|
-
const row = asRecord(value);
|
|
1053
|
-
return compact({
|
|
1054
|
-
count: row["count"] ?? 0,
|
|
1055
|
-
grossInCents: row["grossInCents"] ?? 0,
|
|
1056
|
-
feesInCents: row["feesInCents"] ?? 0,
|
|
1057
|
-
totalInCents: row["totalInCents"] ?? 0,
|
|
1058
|
-
});
|
|
1059
|
-
};
|
|
1060
|
-
return {
|
|
1061
|
-
depositId: args["depositId"],
|
|
1062
|
-
contributions: rollup(summary["contributions"]),
|
|
1063
|
-
refunds: rollup(summary["refunds"]),
|
|
1064
|
-
chargebacks: rollup(summary["chargebacks"]),
|
|
1065
|
-
adjustments: rollup(summary["adjustments"]),
|
|
1066
|
-
};
|
|
1067
|
-
},
|
|
1068
|
-
},
|
|
1069
|
-
{
|
|
1070
|
-
name: "list_refunds",
|
|
1071
|
-
description: "List refunds with status, reason, amount, and net in cents, optionally for one contribution. Reads only; issuing a refund is a mutate call.",
|
|
1072
|
-
annotations: readOnly,
|
|
1073
|
-
inputSchema: namedInput({
|
|
1074
|
-
contributionId: idProperty("list_contributions"),
|
|
1075
|
-
minimumUpdatedDate: DATE_PROPERTY("Updated on or after"),
|
|
1076
|
-
maximumUpdatedDate: DATE_PROPERTY("Updated on or before"),
|
|
1077
|
-
sortDirection: { ...SORT_DIRECTION, description: "Created-date order. Defaults to DESC." },
|
|
1078
|
-
page: PAGE_PROPERTY,
|
|
1079
|
-
limit: LIMIT,
|
|
1080
|
-
raw: RAW_PROPERTY,
|
|
1081
|
-
}),
|
|
1082
|
-
outputSchema: listOutput("refunds", REFUND_SCHEMA),
|
|
1083
|
-
handler: (args, ctx) => list({
|
|
1084
|
-
args,
|
|
1085
|
-
path: "/refunds",
|
|
1086
|
-
key: "refunds",
|
|
1087
|
-
project: projectRefund,
|
|
1088
|
-
query: {
|
|
1089
|
-
contributionId: args["contributionId"],
|
|
1090
|
-
minimumUpdatedDate: args["minimumUpdatedDate"],
|
|
1091
|
-
maximumUpdatedDate: args["maximumUpdatedDate"],
|
|
1092
|
-
sortDirection: args["sortDirection"],
|
|
1093
|
-
},
|
|
1094
|
-
}, ctx),
|
|
1095
|
-
},
|
|
1096
|
-
{
|
|
1097
|
-
name: "list_chargebacks",
|
|
1098
|
-
description: "List chargebacks (card disputes and ACH returns) with status — pending, won, lost — and amounts in cents, optionally for one contribution.",
|
|
1099
|
-
annotations: readOnly,
|
|
1100
|
-
inputSchema: namedInput({
|
|
1101
|
-
contributionId: idProperty("list_contributions"),
|
|
1102
|
-
minimumUpdatedDate: DATE_PROPERTY("Updated on or after"),
|
|
1103
|
-
maximumUpdatedDate: DATE_PROPERTY("Updated on or before"),
|
|
1104
|
-
sortDirection: { ...SORT_DIRECTION, description: "Created-date order. Defaults to DESC." },
|
|
1105
|
-
page: PAGE_PROPERTY,
|
|
1106
|
-
limit: LIMIT,
|
|
1107
|
-
raw: RAW_PROPERTY,
|
|
1108
|
-
}),
|
|
1109
|
-
outputSchema: listOutput("chargebacks", CHARGEBACK_SCHEMA),
|
|
1110
|
-
handler: (args, ctx) => list({
|
|
1111
|
-
args,
|
|
1112
|
-
path: "/chargebacks",
|
|
1113
|
-
key: "chargebacks",
|
|
1114
|
-
project: projectChargeback,
|
|
1115
|
-
query: {
|
|
1116
|
-
contributionId: args["contributionId"],
|
|
1117
|
-
minimumUpdatedDate: args["minimumUpdatedDate"],
|
|
1118
|
-
maximumUpdatedDate: args["maximumUpdatedDate"],
|
|
1119
|
-
sortDirection: args["sortDirection"],
|
|
1120
|
-
},
|
|
1121
|
-
}, ctx),
|
|
1122
|
-
},
|
|
1123
|
-
{
|
|
1124
|
-
name: "list_donors",
|
|
1125
|
-
description: "List donors with name, email, gift count, and recurring count. Cannot search by name or email; omits phone and address, which get_donor returns.",
|
|
1126
|
-
annotations: readOnly,
|
|
1127
|
-
inputSchema: namedInput({
|
|
1128
|
-
locationIds: ID_LIST("list_locations"),
|
|
1129
|
-
minimumUpdatedDate: DATE_PROPERTY("Updated on or after"),
|
|
1130
|
-
maximumUpdatedDate: DATE_PROPERTY("Updated on or before"),
|
|
1131
|
-
sortBy: {
|
|
1132
|
-
type: "string",
|
|
1133
|
-
enum: ["updatedAt", "createdAt", "lastName", "firstName", "email", "totalContributionsCount"],
|
|
1134
|
-
description: "Sort field. Defaults to updatedAt.",
|
|
1135
|
-
},
|
|
1136
|
-
sortDirection: { ...SORT_DIRECTION, description: "Defaults to DESC." },
|
|
1137
|
-
page: PAGE_PROPERTY,
|
|
1138
|
-
limit: LIMIT,
|
|
1139
|
-
raw: RAW_PROPERTY,
|
|
1140
|
-
}),
|
|
1141
|
-
outputSchema: listOutput("donors", DONOR_SCHEMA),
|
|
1142
|
-
handler: (args, ctx) => list({
|
|
1143
|
-
args,
|
|
1144
|
-
path: "/donors",
|
|
1145
|
-
key: "donors",
|
|
1146
|
-
project: (row) => projectDonor(row, false),
|
|
1147
|
-
query: {
|
|
1148
|
-
"locationIds[]": args["locationIds"],
|
|
1149
|
-
minimumUpdatedDate: args["minimumUpdatedDate"],
|
|
1150
|
-
maximumUpdatedDate: args["maximumUpdatedDate"],
|
|
1151
|
-
sortBy: args["sortBy"],
|
|
1152
|
-
sortDirection: args["sortDirection"],
|
|
1153
|
-
},
|
|
1154
|
-
}, ctx),
|
|
1155
|
-
},
|
|
1156
|
-
{
|
|
1157
|
-
name: "get_donor",
|
|
1158
|
-
description: "Get one donor's full profile: contact details, mailing address, locations, and giving counts. Their gifts are list_contributions rows.",
|
|
1159
|
-
annotations: readOnly,
|
|
1160
|
-
inputSchema: namedInput({ donorId: idProperty("list_donors or a contribution's donor.id") }, ["donorId"]),
|
|
1161
|
-
outputSchema: DONOR_SCHEMA,
|
|
1162
|
-
handler: async (args, ctx) => projectDonor(await one(`/donors/${segment(args["donorId"])}`, ctx), true),
|
|
1163
|
-
},
|
|
1164
|
-
{
|
|
1165
|
-
name: "list_subscriptions",
|
|
1166
|
-
description: "List recurring gifts with status, amount, frequency, next payment date, and campaign — for the nonprofit or, given donorId, one donor.",
|
|
1167
|
-
annotations: readOnly,
|
|
1168
|
-
inputSchema: namedInput({
|
|
1169
|
-
donorId: idProperty("list_donors; omit for every donor"),
|
|
1170
|
-
status: {
|
|
1171
|
-
type: "array",
|
|
1172
|
-
minItems: 1,
|
|
1173
|
-
uniqueItems: true,
|
|
1174
|
-
items: {
|
|
1175
|
-
type: "string",
|
|
1176
|
-
enum: ["active", "paused", "pending", "failed", "inactive", "recreated"],
|
|
1177
|
-
description: "Recurring-gift status.",
|
|
1178
|
-
},
|
|
1179
|
-
description: "Statuses; matches any.",
|
|
1180
|
-
},
|
|
1181
|
-
locationIds: ID_LIST("list_locations"),
|
|
1182
|
-
minimumUpdatedDate: DATE_PROPERTY("Updated on or after"),
|
|
1183
|
-
maximumUpdatedDate: DATE_PROPERTY("Updated on or before"),
|
|
1184
|
-
sortBy: {
|
|
1185
|
-
type: "string",
|
|
1186
|
-
enum: ["createdAt", "amount", "frequency", "nextContributionAt"],
|
|
1187
|
-
description: "Sort field. Defaults to createdAt.",
|
|
1188
|
-
},
|
|
1189
|
-
sortDirection: { ...SORT_DIRECTION, description: "Defaults to DESC." },
|
|
1190
|
-
page: PAGE_PROPERTY,
|
|
1191
|
-
limit: LIMIT,
|
|
1192
|
-
raw: RAW_PROPERTY,
|
|
1193
|
-
}),
|
|
1194
|
-
outputSchema: listOutput("subscriptions", SUBSCRIPTION_SCHEMA),
|
|
1195
|
-
handler: (args, ctx) => list({
|
|
1196
|
-
args,
|
|
1197
|
-
path: args["donorId"] === undefined ? "/subscriptions" : `/subscriptions/${segment(args["donorId"])}`,
|
|
1198
|
-
key: "subscriptions",
|
|
1199
|
-
project: projectSubscription,
|
|
1200
|
-
query: {
|
|
1201
|
-
"status[]": args["status"],
|
|
1202
|
-
"locationIds[]": args["locationIds"],
|
|
1203
|
-
minimumUpdatedDate: args["minimumUpdatedDate"],
|
|
1204
|
-
maximumUpdatedDate: args["maximumUpdatedDate"],
|
|
1205
|
-
sortBy: args["sortBy"],
|
|
1206
|
-
sortDirection: args["sortDirection"],
|
|
1207
|
-
},
|
|
1208
|
-
}, ctx),
|
|
1209
|
-
},
|
|
1210
|
-
{
|
|
1211
|
-
name: "list_payment_methods",
|
|
1212
|
-
description: "List one donor's saved payment methods (type, last 4, expiry) and their ids. Unpaginated. Never returns account or card numbers.",
|
|
1213
|
-
annotations: readOnly,
|
|
1214
|
-
inputSchema: namedInput({
|
|
1215
|
-
donorId: idProperty("list_donors"),
|
|
1216
|
-
showExpired: { type: "boolean", description: "Include expired methods. Defaults to false." },
|
|
1217
|
-
raw: RAW_PROPERTY,
|
|
1218
|
-
}, ["donorId"]),
|
|
1219
|
-
outputSchema: {
|
|
1220
|
-
type: "object",
|
|
1221
|
-
properties: { paymentMethods: { type: "array", items: PAYMENT_METHOD_SCHEMA } },
|
|
1222
|
-
required: ["paymentMethods"],
|
|
1223
|
-
},
|
|
1224
|
-
handler: async (args, ctx) => {
|
|
1225
|
-
const payload = await callOverflow(send, {
|
|
1226
|
-
method: "GET",
|
|
1227
|
-
path: `/payment-methods/${segment(args["donorId"])}`,
|
|
1228
|
-
query: { showExpired: args["showExpired"] },
|
|
1229
|
-
}, ctx);
|
|
1230
|
-
const rows = asArray(asRecord(payload)["data"]);
|
|
1231
|
-
return { paymentMethods: args["raw"] === true ? rows : rows.map(projectPaymentMethod) };
|
|
1232
|
-
},
|
|
1233
|
-
},
|
|
1234
|
-
{
|
|
1235
|
-
name: "list_campaigns",
|
|
1236
|
-
description: "List campaigns or subcampaigns with status, dates, and giving totals. Ids feed the campaignId filters and gift bodies.",
|
|
1237
|
-
annotations: readOnly,
|
|
1238
|
-
inputSchema: namedInput({
|
|
1239
|
-
isSubcampaign: { type: "boolean", description: "True lists subcampaigns instead. Defaults to false." },
|
|
1240
|
-
parentCampaignId: idProperty("list_campaigns; lists its subcampaigns"),
|
|
1241
|
-
search: { type: "string", minLength: 1, description: "Campaign-name search." },
|
|
1242
|
-
includeArchived: { type: "boolean", description: "Include archived campaigns." },
|
|
1243
|
-
excludeInactive: { type: "boolean", description: "Exclude campaigns that have ended." },
|
|
1244
|
-
sortBy: {
|
|
1245
|
-
type: "string",
|
|
1246
|
-
enum: [
|
|
1247
|
-
"startDate",
|
|
1248
|
-
"endDate",
|
|
1249
|
-
"name",
|
|
1250
|
-
"displayOrder",
|
|
1251
|
-
"totalContributionCount",
|
|
1252
|
-
"totalContributionValue",
|
|
1253
|
-
"uniqueDonorCount",
|
|
1254
|
-
],
|
|
1255
|
-
description: "Sort field. Defaults to startDate.",
|
|
1256
|
-
},
|
|
1257
|
-
sortDirection: { ...SORT_DIRECTION, description: "Defaults to DESC." },
|
|
1258
|
-
page: PAGE_PROPERTY,
|
|
1259
|
-
limit: LIMIT,
|
|
1260
|
-
raw: RAW_PROPERTY,
|
|
1261
|
-
}),
|
|
1262
|
-
outputSchema: listOutput("campaigns", CAMPAIGN_SCHEMA),
|
|
1263
|
-
handler: (args, ctx) => list({
|
|
1264
|
-
args,
|
|
1265
|
-
path: "/campaigns",
|
|
1266
|
-
key: "campaigns",
|
|
1267
|
-
project: projectCampaign,
|
|
1268
|
-
query: {
|
|
1269
|
-
isSubcampaign: args["isSubcampaign"] === undefined
|
|
1270
|
-
? args["parentCampaignId"] === undefined
|
|
1271
|
-
? undefined
|
|
1272
|
-
: "true"
|
|
1273
|
-
: String(args["isSubcampaign"]),
|
|
1274
|
-
parentCampaignId: args["parentCampaignId"],
|
|
1275
|
-
search: args["search"],
|
|
1276
|
-
includeArchived: args["includeArchived"],
|
|
1277
|
-
excludeInactive: args["excludeInactive"],
|
|
1278
|
-
sortBy: args["sortBy"],
|
|
1279
|
-
sortDirection: args["sortDirection"],
|
|
1280
|
-
},
|
|
1281
|
-
}, ctx),
|
|
1282
|
-
},
|
|
1283
|
-
{
|
|
1284
|
-
name: "list_locations",
|
|
1285
|
-
description: "List the nonprofit's locations (campuses) with names and ids for the locationIds filters. The default location is flagged.",
|
|
1286
|
-
annotations: readOnly,
|
|
1287
|
-
inputSchema: namedInput({
|
|
1288
|
-
includeArchived: { type: "boolean", description: "Include archived locations." },
|
|
1289
|
-
sortBy: {
|
|
1290
|
-
type: "string",
|
|
1291
|
-
enum: ["displayOrder", "name", "createdAt"],
|
|
1292
|
-
description: "Sort field. Defaults to displayOrder.",
|
|
1293
|
-
},
|
|
1294
|
-
sortDirection: { ...SORT_DIRECTION, description: "Defaults to ASC." },
|
|
1295
|
-
page: PAGE_PROPERTY,
|
|
1296
|
-
limit: LIMIT,
|
|
1297
|
-
}),
|
|
1298
|
-
outputSchema: listOutput("locations", LOCATION_SCHEMA),
|
|
1299
|
-
handler: (args, ctx) => list({
|
|
1300
|
-
args,
|
|
1301
|
-
path: "/locations",
|
|
1302
|
-
key: "locations",
|
|
1303
|
-
project: projectLocation,
|
|
1304
|
-
query: {
|
|
1305
|
-
includeArchived: args["includeArchived"],
|
|
1306
|
-
sortBy: args["sortBy"],
|
|
1307
|
-
sortDirection: args["sortDirection"],
|
|
1308
|
-
},
|
|
1309
|
-
}, ctx),
|
|
1310
|
-
},
|
|
1311
|
-
{
|
|
1312
|
-
name: "list_tap_events",
|
|
1313
|
-
description: "List Tap-to-give taps with the device, group, and destination each hit. Taps are engagement, not gifts; giving is list_contributions.",
|
|
1314
|
-
annotations: readOnly,
|
|
1315
|
-
inputSchema: namedInput({
|
|
1316
|
-
startDate: DATE_PROPERTY("On or after"),
|
|
1317
|
-
endDate: DATE_PROPERTY("On or before"),
|
|
1318
|
-
groupIds: ID_LIST("a tap event's groupId"),
|
|
1319
|
-
destinationIds: ID_LIST("a tap event's destinationId"),
|
|
1320
|
-
deviceIds: ID_LIST("a tap event's deviceId"),
|
|
1321
|
-
sortDirection: { ...SORT_DIRECTION, description: "Time order. Defaults to DESC." },
|
|
1322
|
-
page: PAGE_PROPERTY,
|
|
1323
|
-
limit: LIMIT,
|
|
1324
|
-
raw: RAW_PROPERTY,
|
|
1325
|
-
}),
|
|
1326
|
-
outputSchema: listOutput("events", TAP_EVENT_SCHEMA),
|
|
1327
|
-
handler: (args, ctx) => list({
|
|
1328
|
-
args,
|
|
1329
|
-
path: "/tap/events",
|
|
1330
|
-
key: "events",
|
|
1331
|
-
project: projectTapEvent,
|
|
1332
|
-
query: {
|
|
1333
|
-
startDate: args["startDate"],
|
|
1334
|
-
endDate: args["endDate"],
|
|
1335
|
-
"groupIds[]": args["groupIds"],
|
|
1336
|
-
"destinationIds[]": args["destinationIds"],
|
|
1337
|
-
"deviceIds[]": args["deviceIds"],
|
|
1338
|
-
sortDirection: args["sortDirection"],
|
|
1339
|
-
},
|
|
1340
|
-
}, ctx),
|
|
1341
|
-
},
|
|
1342
|
-
{
|
|
1343
|
-
name: "list_webhooks",
|
|
1344
|
-
description: "List the nonprofit's webhook subscriptions with destination URL, enabled events, and status. Delivery history is list_webhook_event_logs.",
|
|
1345
|
-
annotations: readOnly,
|
|
1346
|
-
inputSchema: namedInput({
|
|
1347
|
-
status: { type: "string", enum: ["enabled", "disabled"], description: "Subscription status." },
|
|
1348
|
-
sortDirection: { ...SORT_DIRECTION, description: "Created-date order. Defaults to DESC." },
|
|
1349
|
-
page: PAGE_PROPERTY,
|
|
1350
|
-
limit: LIMIT,
|
|
1351
|
-
}),
|
|
1352
|
-
outputSchema: listOutput("webhooks", WEBHOOK_SCHEMA),
|
|
1353
|
-
handler: (args, ctx) => list({
|
|
1354
|
-
args,
|
|
1355
|
-
path: "/webhooks",
|
|
1356
|
-
key: "webhooks",
|
|
1357
|
-
project: projectWebhook,
|
|
1358
|
-
query: { status: args["status"], sortDirection: args["sortDirection"] },
|
|
1359
|
-
}, ctx),
|
|
1360
|
-
},
|
|
1361
|
-
{
|
|
1362
|
-
name: "list_webhook_event_logs",
|
|
1363
|
-
description: "List delivery attempts for one webhook with status, response code, and error. Omits event payloads unless raw. Pages by cursor, not page.",
|
|
1364
|
-
annotations: readOnly,
|
|
1365
|
-
inputSchema: namedInput({
|
|
1366
|
-
webhookId: idProperty("list_webhooks"),
|
|
1367
|
-
status: { type: "string", enum: ["delivered", "failed", "pending"], description: "Delivery status." },
|
|
1368
|
-
webhookEventName: {
|
|
1369
|
-
type: "string",
|
|
1370
|
-
pattern: "^(\\*|[a-z_]+\\.[a-z_]+)$",
|
|
1371
|
-
description: "Event name, e.g. contribution.approved or donor.updated.",
|
|
1372
|
-
},
|
|
1373
|
-
sortDirection: { ...SORT_DIRECTION, description: "Attempt order. Defaults to DESC." },
|
|
1374
|
-
cursor: {
|
|
1375
|
-
type: "string",
|
|
1376
|
-
minLength: 1,
|
|
1377
|
-
description: "This tool pages by cursor: pass nextCursor back unchanged.",
|
|
1378
|
-
},
|
|
1379
|
-
limit: {
|
|
1380
|
-
type: "integer",
|
|
1381
|
-
minimum: 1,
|
|
1382
|
-
maximum: MAX_EVENT_LOG_PAGE_SIZE,
|
|
1383
|
-
description: `Attempts per page, up to Overflow's cap of ${MAX_EVENT_LOG_PAGE_SIZE} for this endpoint. Defaults to ${DEFAULT_EVENT_LOG_PAGE_SIZE}.`,
|
|
1384
|
-
},
|
|
1385
|
-
raw: RAW_PROPERTY,
|
|
1386
|
-
}, ["webhookId"]),
|
|
1387
|
-
outputSchema: {
|
|
1388
|
-
type: "object",
|
|
1389
|
-
properties: {
|
|
1390
|
-
attempts: { type: "array", items: EVENT_LOG_SCHEMA },
|
|
1391
|
-
nextCursor: {
|
|
1392
|
-
type: "string",
|
|
1393
|
-
description: "Present only when more attempts exist; no page object.",
|
|
1394
|
-
},
|
|
1395
|
-
},
|
|
1396
|
-
required: ["attempts"],
|
|
1397
|
-
},
|
|
1398
|
-
handler: async (args, ctx) => {
|
|
1399
|
-
const payload = asRecord(await callOverflow(send, {
|
|
1400
|
-
method: "GET",
|
|
1401
|
-
path: `/webhooks/${segment(args["webhookId"])}/event-logs`,
|
|
1402
|
-
query: {
|
|
1403
|
-
status: args["status"],
|
|
1404
|
-
webhookEventName: args["webhookEventName"],
|
|
1405
|
-
sortDirection: args["sortDirection"],
|
|
1406
|
-
cursor: args["cursor"],
|
|
1407
|
-
limit: args["limit"] ?? DEFAULT_EVENT_LOG_PAGE_SIZE,
|
|
1408
|
-
},
|
|
1409
|
-
}, ctx));
|
|
1410
|
-
const rows = asArray(payload["data"]);
|
|
1411
|
-
const info = asRecord(payload["pageInfo"]);
|
|
1412
|
-
const next = info["nextCursor"];
|
|
1413
|
-
return compact({
|
|
1414
|
-
attempts: args["raw"] === true ? rows : rows.map(projectEventLog),
|
|
1415
|
-
nextCursor: info["hasNextPage"] === true && typeof next === "string" && next !== "" ? next : undefined,
|
|
1416
|
-
});
|
|
1417
|
-
},
|
|
1418
|
-
},
|
|
1419
|
-
];
|
|
1420
|
-
}
|
|
1421
|
-
// --- Guide ---------------------------------------------------------------------
|
|
1422
|
-
function usageGuide(purpose, environment, instructions) {
|
|
1423
|
-
const accountInstructions = instructions?.trim();
|
|
1424
|
-
const environmentLine = environment === "production"
|
|
1425
|
-
? "Overflow **production**: live nonprofit giving data, real donors, and real money."
|
|
1426
|
-
: "Overflow **staging**: Overflow's sandbox, isolated from production donors and money.";
|
|
1427
|
-
return `# Overflow usage
|
|
1428
|
-
|
|
1429
|
-
${environmentLine}
|
|
1430
|
-
|
|
1431
|
-
Nonprofit purpose: ${purpose}${skill.fragments.guide_0}${accountInstructions ? `\n## ${skill.instructionsHeading}\n\n${accountInstructions}\n` : ""}`;
|
|
1432
|
-
}
|
|
1433
|
-
// --- Construction --------------------------------------------------------------
|
|
1434
|
-
/** The closed options overflow() accepts; see `assertKnownOptions`. */
|
|
1435
41
|
const OVERFLOW_OPTIONS = optionsOf()({
|
|
1436
42
|
...PROVIDER_COMMON,
|
|
1437
43
|
...keys("environment", "defaultPageSize", "baseUrl"),
|
|
1438
44
|
});
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
* that is exactly why a default buys nothing. Defaulting to staging, the
|
|
1447
|
-
* "safe" side, sends every real deployment to a sandbox where its production
|
|
1448
|
-
* key is refused with a generic "Forbidden resource" and no hint that the base
|
|
1449
|
-
* URL is the problem. Defaulting to production makes a test deployment that
|
|
1450
|
-
* forgot the option one pasted key away from charging real donors through the
|
|
1451
|
-
* mutate hatch. The environment is a fact the operator knows and connecta
|
|
1452
|
-
* cannot infer — Overflow's keys carry no recognizable environment marker — so
|
|
1453
|
-
* the operator states it, and the title and the guide's first line repeat it.
|
|
1454
|
-
*/
|
|
1455
|
-
export const overflow = asProviderFactory({
|
|
45
|
+
function environmentOf(options) {
|
|
46
|
+
const environment = options.environment;
|
|
47
|
+
if (environment !== "production" && environment !== "staging")
|
|
48
|
+
throw new Error('overflow() requires environment: "production" or "staging". Overflow issues separate credentials for each, and connecta will not guess which one a deployment means.');
|
|
49
|
+
return environment;
|
|
50
|
+
}
|
|
51
|
+
export const overflow = defineApiProvider({
|
|
1456
52
|
name: "overflow",
|
|
1457
53
|
title: "Overflow",
|
|
1458
|
-
kind: "api",
|
|
1459
54
|
readme: "Overflow",
|
|
1460
|
-
bundle: {
|
|
1461
|
-
"baselineGzip": 20410,
|
|
1462
|
-
"maxGzip": 80410,
|
|
1463
|
-
"note": "./providers/overflow starts at 20,410 B gzip: a hand-written api() surface over the guarded transport with no hosted-MCP interface, so it pulls in neither remoteMcp nor the Effect core the other providers carry. The cap uses the existing baseline + 60,000 B policy.",
|
|
1464
|
-
},
|
|
1465
55
|
skill,
|
|
1466
56
|
options: OVERFLOW_OPTIONS,
|
|
1467
|
-
|
|
57
|
+
bundle: {
|
|
58
|
+
baselineGzip: 352543,
|
|
59
|
+
note: "./providers/overflow measures 352,543 B gzip standalone at 88c918ed (0011 step 2), versus 341,849 B on main 8e92eed9 (+10,694 B), Node 26.12.0 with zlib-ng 1.3.1. This includes the shared Effect kit, paid once per deployment; provider entries are reported, not capped, and their sizes are not additive (#899).",
|
|
60
|
+
},
|
|
61
|
+
displayTitle: (options) => `Overflow (${environmentOf(options)})`,
|
|
62
|
+
description: (options) => `Overflow nonprofit giving, ${environmentOf(options)}: ${options.purpose}`,
|
|
63
|
+
vendor: OverflowApi,
|
|
64
|
+
baseUrl: (options) => `${(options.baseUrl?.trim() || OVERFLOW_API_BASE_URLS[environmentOf(options)]).replace(/\/+$/, "")}/api/v3`,
|
|
65
|
+
maxResponseBytes: OVERFLOW_MAX_RESPONSE_BYTES,
|
|
66
|
+
headers: { Accept: "application/json" },
|
|
67
|
+
pageSize: { default: DEFAULT_PAGE_SIZE, max: MAX_PAGE_SIZE },
|
|
68
|
+
credential: API_KEY_CREDENTIAL,
|
|
69
|
+
authenticate,
|
|
70
|
+
admit,
|
|
71
|
+
errors,
|
|
72
|
+
idempotencyKey: false,
|
|
73
|
+
safety: () => safety,
|
|
74
|
+
result,
|
|
75
|
+
probe,
|
|
76
|
+
guide: (options) => ({
|
|
77
|
+
heading: "Overflow usage",
|
|
78
|
+
context: [
|
|
79
|
+
environmentOf(options) === "production"
|
|
80
|
+
? "Overflow production: live nonprofit giving, donors and money."
|
|
81
|
+
: "Overflow staging: a sandbox with separate credentials and data.",
|
|
82
|
+
`Nonprofit purpose: ${options.purpose}`,
|
|
83
|
+
"Named reads minimize contact data; get_donor is the full-contact lookup. Webhook signing secrets are always removed.",
|
|
84
|
+
...(options.readOnly ? ["This connection is read-only: every write is refused."] : []),
|
|
85
|
+
...(options.defaultPageSize !== undefined
|
|
86
|
+
? [`Paged lists return ${options.defaultPageSize} items by default on this connection.`]
|
|
87
|
+
: []),
|
|
88
|
+
],
|
|
89
|
+
summary: "Giving records: asset-specific status, per-field money units, donor joins and deposit reconciliation.",
|
|
90
|
+
required: true,
|
|
91
|
+
}),
|
|
92
|
+
hatch: {
|
|
93
|
+
name: "overflow",
|
|
94
|
+
title: "Overflow",
|
|
95
|
+
index: OVERFLOW_INDEX,
|
|
96
|
+
verdicts: OVERFLOW_VERDICTS,
|
|
97
|
+
named: OVERFLOW_NAMED,
|
|
98
|
+
pages: OVERFLOW_PAGES,
|
|
99
|
+
pathExample: "/donors/6710f34fd5061afeec3eab50",
|
|
100
|
+
notes: () => ({
|
|
101
|
+
read: "Paths are relative to /api/v3. Array query filters use bracketed names and repeated values; GET /health returns text.",
|
|
102
|
+
write: "Refund acknowledgements can be JSON strings or plain text; empty write acknowledgements are valid. Resolve donor and payment-method ids first; money units differ by field.",
|
|
103
|
+
}),
|
|
104
|
+
},
|
|
105
|
+
defaultCallAdmission: OVERFLOW_ADMISSION,
|
|
106
|
+
tools: overflowTools,
|
|
1468
107
|
});
|
|
1469
|
-
function overflowConnector(id, options) {
|
|
1470
|
-
const purpose = options.purpose?.trim();
|
|
1471
|
-
if (!purpose) {
|
|
1472
|
-
throw new Error("overflow() requires a non-empty nonprofit purpose.");
|
|
1473
|
-
}
|
|
1474
|
-
const environment = options.environment;
|
|
1475
|
-
if (environment !== "production" && environment !== "staging") {
|
|
1476
|
-
throw new Error('overflow() requires environment: "production" or "staging". Overflow issues separate credentials for each, and connecta will not guess which one a deployment means.');
|
|
1477
|
-
}
|
|
1478
|
-
const defaultPageSize = options.defaultPageSize ?? DEFAULT_PAGE_SIZE;
|
|
1479
|
-
if (!Number.isInteger(defaultPageSize) || defaultPageSize < 1 || defaultPageSize > MAX_PAGE_SIZE) {
|
|
1480
|
-
throw new Error(`overflow() defaultPageSize must be a whole number between 1 and ${MAX_PAGE_SIZE}.`);
|
|
1481
|
-
}
|
|
1482
|
-
const send = overflowTransport(options.baseUrl?.trim() || OVERFLOW_API_BASE_URLS[environment]);
|
|
1483
|
-
const label = environment === "production" ? "production" : "staging";
|
|
1484
|
-
return api(id, {
|
|
1485
|
-
...defined({ authScope: options.authScope, maxResultBytes: options.maxResultBytes }),
|
|
1486
|
-
title: options.title ?? `Overflow (${label})`,
|
|
1487
|
-
description: `Overflow nonprofit giving, ${label}: ${purpose}`,
|
|
1488
|
-
credential: API_KEY_CREDENTIAL,
|
|
1489
|
-
async testCredentials(values, ctx) {
|
|
1490
|
-
// Overflow has no identity endpoint, and /health answers without a
|
|
1491
|
-
// credential, so it proves nothing. One location is the cheapest
|
|
1492
|
-
// authenticated read: every nonprofit has at least its default one, and
|
|
1493
|
-
// its name is the closest thing to "which organization is this?" the
|
|
1494
|
-
// API will say.
|
|
1495
|
-
try {
|
|
1496
|
-
const payload = asRecord(await callOverflow(send, { method: "GET", path: "/locations", query: { limit: 1, sortBy: "displayOrder" } }, {
|
|
1497
|
-
...ctx,
|
|
1498
|
-
credential: {
|
|
1499
|
-
get: async (field) => (field ? (values[field] ?? null) : null),
|
|
1500
|
-
getAll: async () => values,
|
|
1501
|
-
},
|
|
1502
|
-
}));
|
|
1503
|
-
const first = asRecord(asArray(payload["data"])[0]);
|
|
1504
|
-
const total = payload["totalCount"];
|
|
1505
|
-
const where = typeof first["name"] === "string"
|
|
1506
|
-
? ` The nonprofit's first location is "${first["name"]}"${typeof total === "number" ? ` of ${total}` : ""}.`
|
|
1507
|
-
: "";
|
|
1508
|
-
return {
|
|
1509
|
-
ok: true,
|
|
1510
|
-
message: `Overflow ${label} accepted the client id and API key.${where}`,
|
|
1511
|
-
};
|
|
1512
|
-
}
|
|
1513
|
-
catch (error) {
|
|
1514
|
-
return {
|
|
1515
|
-
ok: false,
|
|
1516
|
-
message: error instanceof ConnectorCallError
|
|
1517
|
-
? error.message
|
|
1518
|
-
: `Overflow ${label} rejected the client id and API key.`,
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
|
-
},
|
|
1522
|
-
callAdmission: options.callAdmission === undefined ? OVERFLOW_ADMISSION : options.callAdmission,
|
|
1523
|
-
usageGuide: {
|
|
1524
|
-
content: usageGuide(purpose, environment, options.instructions),
|
|
1525
|
-
summary: `Overflow ${label}: money units per field, final statuses, deposits, donor-PII care, and writes via mutate.`,
|
|
1526
|
-
// Required because correct use depends on facts no schema carries:
|
|
1527
|
-
// which statuses are final per asset type, which money unit each field
|
|
1528
|
-
// and write body uses (cents, dollars, or unstated), and
|
|
1529
|
-
// that every write is a mutate-hatch call whose body comes from
|
|
1530
|
-
// Overflow's reference.
|
|
1531
|
-
required: true,
|
|
1532
|
-
},
|
|
1533
|
-
tools: tools(send, defaultPageSize),
|
|
1534
|
-
});
|
|
1535
|
-
}
|