@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
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
// Breeze's curated surface: projections and profile encoders earn named tools; the standard hatch supplies coverage.
|
|
2
|
+
import { Effect, Schema } from "effect";
|
|
3
|
+
import { ConnectorCallError } from "../../errors.js";
|
|
4
|
+
import { Refusal } from "../../http/errors.js";
|
|
5
|
+
import { Integer, isMinChars, isMaxChars } from "../../http/json-schema.js";
|
|
6
|
+
import { RequestSecrets, Classification } from "../../http/services.js";
|
|
7
|
+
import { fieldsInput, fieldsProblem, selectFields } from "../../http/fields.js";
|
|
8
|
+
import { read, list, write as writeTool } from "../../http/tools.js";
|
|
9
|
+
import { asArray, asRecord, READ_ENDPOINTS } from "./api.js";
|
|
10
|
+
const DEFAULT_EVENT_LIMIT = 100;
|
|
11
|
+
const ObjectValue = Schema.Record(Schema.String, Schema.Unknown);
|
|
12
|
+
function compact(value) {
|
|
13
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
14
|
+
}
|
|
15
|
+
function timestamp(value) {
|
|
16
|
+
if (value === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (typeof value !== "string" || value === "" || value.startsWith("0000-00-00")) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function flag(value) {
|
|
24
|
+
if (value === undefined || value === null)
|
|
25
|
+
return undefined;
|
|
26
|
+
return value === true || value === 1 || value === "1";
|
|
27
|
+
}
|
|
28
|
+
function idString(value) {
|
|
29
|
+
return value === undefined || value === null || value === "" ? undefined : String(value);
|
|
30
|
+
}
|
|
31
|
+
function projectPersonSummary(value) {
|
|
32
|
+
const person = asRecord(value);
|
|
33
|
+
return compact({
|
|
34
|
+
id: idString(person["id"]),
|
|
35
|
+
firstName: person["first_name"],
|
|
36
|
+
forceFirstName: person["force_first_name"],
|
|
37
|
+
lastName: person["last_name"],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function projectFamilyMember(value) {
|
|
41
|
+
const member = asRecord(value);
|
|
42
|
+
const details = asRecord(member["details"]);
|
|
43
|
+
return compact({
|
|
44
|
+
personId: idString(member["person_id"] ?? details["id"]),
|
|
45
|
+
familyId: idString(member["family_id"]),
|
|
46
|
+
role: member["role_name"],
|
|
47
|
+
firstName: details["first_name"],
|
|
48
|
+
lastName: details["last_name"],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function projectPerson(value) {
|
|
52
|
+
const person = asRecord(value);
|
|
53
|
+
const family = asArray(person["family"]);
|
|
54
|
+
const details = person["details"];
|
|
55
|
+
return compact({
|
|
56
|
+
...projectPersonSummary(person),
|
|
57
|
+
nickName: person["nick_name"] || undefined,
|
|
58
|
+
middleName: person["middle_name"] || undefined,
|
|
59
|
+
maidenName: person["maiden_name"] || undefined,
|
|
60
|
+
details: details && typeof details === "object" && !Array.isArray(details) ? details : undefined,
|
|
61
|
+
family: family.length ? family.map(projectFamilyMember) : undefined,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function projectProfileFields(value) {
|
|
65
|
+
const fields = [];
|
|
66
|
+
for (const sectionValue of asArray(value)) {
|
|
67
|
+
const section = asRecord(sectionValue);
|
|
68
|
+
for (const fieldValue of asArray(section["fields"])) {
|
|
69
|
+
const field = asRecord(fieldValue);
|
|
70
|
+
const options = asArray(field["options"]).map((optionValue) => {
|
|
71
|
+
const option = asRecord(optionValue);
|
|
72
|
+
return compact({
|
|
73
|
+
optionId: idString(option["option_id"] ?? option["id"]),
|
|
74
|
+
name: option["name"],
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
fields.push(compact({
|
|
78
|
+
fieldId: idString(field["field_id"]),
|
|
79
|
+
name: field["name"],
|
|
80
|
+
type: field["field_type"],
|
|
81
|
+
section: section["name"],
|
|
82
|
+
options: options.length ? options : undefined,
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return fields;
|
|
87
|
+
}
|
|
88
|
+
function projectEvent(value) {
|
|
89
|
+
const event = asRecord(value);
|
|
90
|
+
return compact({
|
|
91
|
+
id: idString(event["id"]),
|
|
92
|
+
eventId: idString(event["event_id"]),
|
|
93
|
+
name: event["name"],
|
|
94
|
+
calendarId: idString(event["category_id"]),
|
|
95
|
+
startsAt: timestamp(event["start_datetime"]),
|
|
96
|
+
endsAt: timestamp(event["end_datetime"]),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function projectContribution(redact, value) {
|
|
100
|
+
const payment = asRecord(value);
|
|
101
|
+
const paidOn = payment["paid_on"];
|
|
102
|
+
const safePaidOn = typeof paidOn === "string" ? redact(paidOn) : undefined;
|
|
103
|
+
return compact({
|
|
104
|
+
id: idString(payment["id"]),
|
|
105
|
+
// Extract only from unchanged public text; a redacted source yields no extracted fragment.
|
|
106
|
+
paidOn: safePaidOn === undefined
|
|
107
|
+
? undefined
|
|
108
|
+
: safePaidOn !== paidOn || safePaidOn.includes("[redacted]")
|
|
109
|
+
? "[redacted]"
|
|
110
|
+
: (/^\d{4}-\d{2}-\d{2}/.exec(safePaidOn)?.[0] ?? safePaidOn),
|
|
111
|
+
amount: idString(payment["amount"]),
|
|
112
|
+
method: payment["method"],
|
|
113
|
+
methodId: idString(payment["method_id"]),
|
|
114
|
+
personId: idString(payment["person_id"]),
|
|
115
|
+
firstName: payment["first_name"],
|
|
116
|
+
lastName: payment["last_name"],
|
|
117
|
+
envelopeNumber: idString(payment["envelope_number"]),
|
|
118
|
+
batchNumber: idString(payment["num"] ?? payment["batch_num"]),
|
|
119
|
+
note: payment["note"] || undefined,
|
|
120
|
+
funds: asArray(payment["funds"]).map((fundValue) => {
|
|
121
|
+
const fund = asRecord(fundValue);
|
|
122
|
+
return compact({
|
|
123
|
+
fundId: idString(fund["fund_id"]),
|
|
124
|
+
name: fund["fund_name"],
|
|
125
|
+
amount: idString(fund["amount"]),
|
|
126
|
+
taxDeductible: flag(fund["tax_deductible"]),
|
|
127
|
+
});
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const DETAIL_FIELD_TYPES = new Set(["email", "phone", "address", "family_role"]);
|
|
132
|
+
function fieldsJson(value) {
|
|
133
|
+
const fields = asArray(value).map((row) => {
|
|
134
|
+
const field = asRecord(row);
|
|
135
|
+
const type = String(field["type"]);
|
|
136
|
+
const needsDetails = DETAIL_FIELD_TYPES.has(type);
|
|
137
|
+
if (needsDetails && !field["details"]) {
|
|
138
|
+
throw new ConnectorCallError("invalid_args", `A ${type} field carries its value in details; field ${String(field["fieldId"])} has none.`);
|
|
139
|
+
}
|
|
140
|
+
if (!needsDetails && field["response"] === undefined) {
|
|
141
|
+
throw new ConnectorCallError("invalid_args", `A ${type} field needs a response; field ${String(field["fieldId"])} has none.`);
|
|
142
|
+
}
|
|
143
|
+
return compact({
|
|
144
|
+
field_id: field["fieldId"],
|
|
145
|
+
field_type: type,
|
|
146
|
+
response: field["response"] ?? (needsDetails ? true : undefined),
|
|
147
|
+
details: field["details"],
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
return JSON.stringify(fields);
|
|
151
|
+
}
|
|
152
|
+
function requireTrue(payload, action) {
|
|
153
|
+
if (payload === false) {
|
|
154
|
+
throw new ConnectorCallError("connector_call_failed", `Breeze answered false to ${action}. Breeze gives no reason; check the target and confirm the ids with their list tools before any fresh call.`, { retryable: false });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const matches = (pattern) => Schema.makeFilter((value) => new RegExp(pattern).test(value), { toJsonSchema: () => ({ pattern }) });
|
|
158
|
+
const Result_get_account_summary = Schema.Struct({
|
|
159
|
+
"id": Schema.optionalKey(Schema.String),
|
|
160
|
+
"name": Schema.String,
|
|
161
|
+
"subdomain": Schema.optionalKey(Schema.String),
|
|
162
|
+
"timezone": Schema.optionalKey(Schema.String),
|
|
163
|
+
"country": Schema.optionalKey(Schema.String),
|
|
164
|
+
"currency": Schema.optionalKey(Schema.String),
|
|
165
|
+
});
|
|
166
|
+
const Result_list_people = Schema.Struct({
|
|
167
|
+
"people": Schema.Array(Schema.Struct({
|
|
168
|
+
"id": Schema.String,
|
|
169
|
+
"firstName": Schema.optionalKey(Schema.String),
|
|
170
|
+
"forceFirstName": Schema.optionalKey(Schema.String.annotate({ description: "Breeze's display first name." })),
|
|
171
|
+
"lastName": Schema.optionalKey(Schema.String),
|
|
172
|
+
"nickName": Schema.optionalKey(Schema.String),
|
|
173
|
+
"middleName": Schema.optionalKey(Schema.String),
|
|
174
|
+
"maidenName": Schema.optionalKey(Schema.String),
|
|
175
|
+
"details": Schema.optionalKey(ObjectValue.annotate({ description: "Values keyed by profile fieldId." })),
|
|
176
|
+
"family": Schema.optionalKey(Schema.Array(ObjectValue)),
|
|
177
|
+
})),
|
|
178
|
+
"page": Schema.Struct({
|
|
179
|
+
"hasMore": Schema.Boolean.annotate({ description: "True when the page came back full." }),
|
|
180
|
+
"nextCursor": Schema.Union([Schema.String, Schema.Null]),
|
|
181
|
+
}),
|
|
182
|
+
});
|
|
183
|
+
const Row_list_people = Result_list_people.fields.people.value;
|
|
184
|
+
const Result_get_person = Schema.Struct({
|
|
185
|
+
"id": Schema.String,
|
|
186
|
+
"firstName": Schema.optionalKey(Schema.String),
|
|
187
|
+
"forceFirstName": Schema.optionalKey(Schema.String.annotate({ description: "Breeze's display first name." })),
|
|
188
|
+
"lastName": Schema.optionalKey(Schema.String),
|
|
189
|
+
"nickName": Schema.optionalKey(Schema.String),
|
|
190
|
+
"middleName": Schema.optionalKey(Schema.String),
|
|
191
|
+
"maidenName": Schema.optionalKey(Schema.String),
|
|
192
|
+
"details": Schema.optionalKey(ObjectValue.annotate({ description: "Values keyed by profile fieldId." })),
|
|
193
|
+
"family": Schema.optionalKey(Schema.Array(ObjectValue)),
|
|
194
|
+
});
|
|
195
|
+
const Result_list_profile_fields = Schema.Struct({
|
|
196
|
+
"fields": Schema.Array(Schema.Struct({
|
|
197
|
+
"fieldId": Schema.String,
|
|
198
|
+
"name": Schema.String,
|
|
199
|
+
"type": Schema.String,
|
|
200
|
+
"section": Schema.optionalKey(Schema.String),
|
|
201
|
+
"options": Schema.optionalKey(Schema.Array(ObjectValue)),
|
|
202
|
+
})),
|
|
203
|
+
});
|
|
204
|
+
const Row_list_profile_fields = Result_list_profile_fields.fields.fields.value;
|
|
205
|
+
const Result_list_tags = Schema.Struct({
|
|
206
|
+
"tags": Schema.Array(Schema.Struct({ "id": Schema.String, "name": Schema.String, "folderId": Schema.optionalKey(Schema.String) })),
|
|
207
|
+
});
|
|
208
|
+
const Row_list_tags = Result_list_tags.fields.tags.value;
|
|
209
|
+
const Result_list_events = Schema.Struct({
|
|
210
|
+
"events": Schema.Array(Schema.Struct({
|
|
211
|
+
"id": Schema.String.annotate({ description: "Instance id." }),
|
|
212
|
+
"eventId": Schema.optionalKey(Schema.String.annotate({ description: "Series id." })),
|
|
213
|
+
"name": Schema.String,
|
|
214
|
+
"calendarId": Schema.optionalKey(Schema.String),
|
|
215
|
+
"startsAt": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])),
|
|
216
|
+
"endsAt": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])),
|
|
217
|
+
})),
|
|
218
|
+
"truncated": Schema.Boolean.annotate({ description: "True when the result filled limit; narrow the range." }),
|
|
219
|
+
});
|
|
220
|
+
const Row_list_events = Result_list_events.fields.events.value;
|
|
221
|
+
const SelectedEvent = Schema.Struct(Object.fromEntries(Object.entries(Row_list_events.fields).map(([name, schema]) => [name, Schema.optionalKey(schema)])));
|
|
222
|
+
const Result_list_attendance = Schema.Struct({
|
|
223
|
+
"attendance": Schema.Array(Schema.Struct({
|
|
224
|
+
"personId": Schema.String,
|
|
225
|
+
"firstName": Schema.optionalKey(Schema.String),
|
|
226
|
+
"lastName": Schema.optionalKey(Schema.String),
|
|
227
|
+
"checkedInAt": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])),
|
|
228
|
+
"checkedOutAt": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])),
|
|
229
|
+
})),
|
|
230
|
+
});
|
|
231
|
+
const Row_list_attendance = Result_list_attendance.fields.attendance.value;
|
|
232
|
+
const Result_list_contributions = Schema.Struct({
|
|
233
|
+
"contributions": Schema.Array(Schema.Struct({
|
|
234
|
+
"id": Schema.String,
|
|
235
|
+
"paidOn": Schema.optionalKey(Schema.String),
|
|
236
|
+
"amount": Schema.String,
|
|
237
|
+
"method": Schema.optionalKey(Schema.String),
|
|
238
|
+
"personId": Schema.optionalKey(Schema.String),
|
|
239
|
+
"firstName": Schema.optionalKey(Schema.String),
|
|
240
|
+
"lastName": Schema.optionalKey(Schema.String),
|
|
241
|
+
"batchNumber": Schema.optionalKey(Schema.String),
|
|
242
|
+
"funds": Schema.optionalKey(Schema.Array(ObjectValue)),
|
|
243
|
+
})),
|
|
244
|
+
});
|
|
245
|
+
const Row_list_contributions = Result_list_contributions.fields.contributions.value;
|
|
246
|
+
const Result_list_funds = Schema.Struct({
|
|
247
|
+
"funds": Schema.Array(Schema.Struct({
|
|
248
|
+
"id": Schema.String,
|
|
249
|
+
"name": Schema.String,
|
|
250
|
+
"taxDeductible": Schema.optionalKey(Schema.Boolean),
|
|
251
|
+
"isDefault": Schema.optionalKey(Schema.Boolean),
|
|
252
|
+
})),
|
|
253
|
+
});
|
|
254
|
+
const Row_list_funds = Result_list_funds.fields.funds.value;
|
|
255
|
+
const Result_add_person = Schema.Union([
|
|
256
|
+
Schema.Struct({
|
|
257
|
+
"id": Schema.String,
|
|
258
|
+
"firstName": Schema.optionalKey(Schema.String),
|
|
259
|
+
"forceFirstName": Schema.optionalKey(Schema.String.annotate({ description: "Breeze's display first name." })),
|
|
260
|
+
"lastName": Schema.optionalKey(Schema.String),
|
|
261
|
+
"nickName": Schema.optionalKey(Schema.String),
|
|
262
|
+
"middleName": Schema.optionalKey(Schema.String),
|
|
263
|
+
"maidenName": Schema.optionalKey(Schema.String),
|
|
264
|
+
"details": Schema.optionalKey(ObjectValue.annotate({ description: "Values keyed by profile fieldId." })),
|
|
265
|
+
"family": Schema.optionalKey(Schema.Array(ObjectValue)),
|
|
266
|
+
}),
|
|
267
|
+
Schema.Struct({ ok: Schema.Literal(true) }),
|
|
268
|
+
]);
|
|
269
|
+
const Result_update_person = Schema.Union([
|
|
270
|
+
Schema.Struct({
|
|
271
|
+
"id": Schema.String,
|
|
272
|
+
"firstName": Schema.optionalKey(Schema.String),
|
|
273
|
+
"forceFirstName": Schema.optionalKey(Schema.String.annotate({ description: "Breeze's display first name." })),
|
|
274
|
+
"lastName": Schema.optionalKey(Schema.String),
|
|
275
|
+
"nickName": Schema.optionalKey(Schema.String),
|
|
276
|
+
"middleName": Schema.optionalKey(Schema.String),
|
|
277
|
+
"maidenName": Schema.optionalKey(Schema.String),
|
|
278
|
+
"details": Schema.optionalKey(ObjectValue.annotate({ description: "Values keyed by profile fieldId." })),
|
|
279
|
+
"family": Schema.optionalKey(Schema.Array(ObjectValue)),
|
|
280
|
+
}),
|
|
281
|
+
Schema.Struct({ ok: Schema.Literal(true) }),
|
|
282
|
+
]);
|
|
283
|
+
function projected(output, run) {
|
|
284
|
+
void output;
|
|
285
|
+
return Effect.suspend(run).pipe(Effect.map((value) => value), Effect.catchDefect((error) => error instanceof ConnectorCallError ? Effect.fail(new Refusal({ error })) : Effect.die(error)));
|
|
286
|
+
}
|
|
287
|
+
function request(connection, path, query, verdict) {
|
|
288
|
+
return Effect.gen(function* () {
|
|
289
|
+
if (verdict !== "read" && verdict !== "write")
|
|
290
|
+
return yield* Effect.die(new Error("A Breeze GET requires an explicit verdict."));
|
|
291
|
+
if (verdict === "write" && (yield* Classification) !== "write")
|
|
292
|
+
return yield* Effect.fail(new Refusal({ error: new ConnectorCallError("invalid_args", "A Breeze write cannot run as a read.") }));
|
|
293
|
+
if (verdict === "read") {
|
|
294
|
+
const template = /^\/people\/\d+$/.test(path) ? "/people/{id}" : path;
|
|
295
|
+
const allowed = READ_ENDPOINTS.get(template);
|
|
296
|
+
if (!allowed || Object.keys(query).some((name) => !allowed.includes(name)))
|
|
297
|
+
return yield* Effect.die(new Error("A Breeze read has no reviewed endpoint/parameter verdict."));
|
|
298
|
+
}
|
|
299
|
+
const answer = yield* connection.sendPath({
|
|
300
|
+
method: "GET",
|
|
301
|
+
path,
|
|
302
|
+
template: path,
|
|
303
|
+
query: Object.entries(query).flatMap(([name, value]) => value === undefined ? [] : [[name, String(value)]]),
|
|
304
|
+
});
|
|
305
|
+
return answer.data;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function windowRows(rows, cursor, limit) {
|
|
309
|
+
const start = cursor === undefined ? 0 : Number(cursor);
|
|
310
|
+
if (!Number.isSafeInteger(start) || start < 0 || start > rows.length)
|
|
311
|
+
throw new ConnectorCallError("invalid_args", "This cursor no longer matches this list.");
|
|
312
|
+
const end = start + limit;
|
|
313
|
+
return {
|
|
314
|
+
rows: rows.slice(start, end),
|
|
315
|
+
page: { hasMore: end < rows.length, nextCursor: end < rows.length ? String(end) : null },
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
export function breezeTools({ connection, pageSize: defaultPageSize, }) {
|
|
319
|
+
const get = (path, query) => request(connection, path, query, "read");
|
|
320
|
+
const write = (path, query) => request(connection, path, query, "write");
|
|
321
|
+
const limit = { default: defaultPageSize, max: 1000, description: "Items per page; pass nextCursor back unchanged." };
|
|
322
|
+
const load_list_people = (args) => projected(Result_list_people, () => Effect.gen(function* () {
|
|
323
|
+
const filter = { ...asRecord(args["filter"]) };
|
|
324
|
+
if (args["tagId"] !== undefined) {
|
|
325
|
+
if (filter["tag_contains"] !== undefined) {
|
|
326
|
+
throw new ConnectorCallError("invalid_args", "Pass tagId or filter.tag_contains, not both.");
|
|
327
|
+
}
|
|
328
|
+
filter["tag_contains"] = `y_${args["tagId"]}`;
|
|
329
|
+
}
|
|
330
|
+
const limit = args["limit"] ?? defaultPageSize;
|
|
331
|
+
const offset = args["cursor"] === undefined ? 0 : Number(args["cursor"]);
|
|
332
|
+
const rows = asArray(yield* get("/people", {
|
|
333
|
+
details: args["includeDetails"] === true ? 1 : 0,
|
|
334
|
+
filter_json: Object.keys(filter).length ? JSON.stringify(filter) : undefined,
|
|
335
|
+
limit,
|
|
336
|
+
offset,
|
|
337
|
+
}));
|
|
338
|
+
// Breeze returns no total, so a full page is the only "maybe more"
|
|
339
|
+
// signal it gives; the page after an exact multiple comes back empty.
|
|
340
|
+
const hasMore = rows.length >= limit;
|
|
341
|
+
return {
|
|
342
|
+
people: rows.map(projectPerson),
|
|
343
|
+
page: { hasMore, nextCursor: hasMore ? String(offset + rows.length) : null },
|
|
344
|
+
};
|
|
345
|
+
}));
|
|
346
|
+
const load_list_profile_fields = (_args) => projected(Result_list_profile_fields, () => Effect.gen(function* () {
|
|
347
|
+
const payload = yield* get("/profile", {});
|
|
348
|
+
return { fields: projectProfileFields(payload) };
|
|
349
|
+
}));
|
|
350
|
+
const load_list_tags = (args) => projected(Result_list_tags, () => Effect.gen(function* () {
|
|
351
|
+
return {
|
|
352
|
+
tags: asArray(yield* get("/tags/list_tags", { folder_id: args["folderId"] })).map((value) => {
|
|
353
|
+
const tag = asRecord(value);
|
|
354
|
+
return compact({ id: idString(tag["id"]), name: tag["name"], folderId: idString(tag["folder_id"]) });
|
|
355
|
+
}),
|
|
356
|
+
};
|
|
357
|
+
}));
|
|
358
|
+
const load_list_events = (args) => projected(Result_list_events, () => Effect.gen(function* () {
|
|
359
|
+
const limit = args["limit"] ?? DEFAULT_EVENT_LIMIT;
|
|
360
|
+
const rows = asArray(yield* get("/events", {
|
|
361
|
+
start: args["start"],
|
|
362
|
+
end: args["end"],
|
|
363
|
+
category_id: args["calendarId"],
|
|
364
|
+
limit,
|
|
365
|
+
}));
|
|
366
|
+
return {
|
|
367
|
+
events: rows.map(projectEvent),
|
|
368
|
+
truncated: rows.length >= limit,
|
|
369
|
+
};
|
|
370
|
+
}));
|
|
371
|
+
const load_list_attendance = (args) => projected(Result_list_attendance, () => Effect.gen(function* () {
|
|
372
|
+
const wantDetails = args["includeNames"] === true;
|
|
373
|
+
const rows = asArray(yield* get("/events/attendance/list", {
|
|
374
|
+
instance_id: args["instanceId"],
|
|
375
|
+
type: "person",
|
|
376
|
+
details: wantDetails ? "true" : "false",
|
|
377
|
+
}));
|
|
378
|
+
return {
|
|
379
|
+
attendance: rows.map((value) => {
|
|
380
|
+
const record = asRecord(value);
|
|
381
|
+
const person = asRecord(record["details"]);
|
|
382
|
+
return compact({
|
|
383
|
+
personId: idString(record["person_id"]),
|
|
384
|
+
firstName: person["first_name"],
|
|
385
|
+
lastName: person["last_name"],
|
|
386
|
+
checkedInAt: timestamp(record["created_on"]),
|
|
387
|
+
checkedOutAt: timestamp(record["check_out"]),
|
|
388
|
+
});
|
|
389
|
+
}),
|
|
390
|
+
};
|
|
391
|
+
}));
|
|
392
|
+
const load_list_contributions = (args) => projected(Result_list_contributions, () => Effect.gen(function* () {
|
|
393
|
+
const secrets = yield* RequestSecrets;
|
|
394
|
+
const redact = (value) => secrets.redact(value);
|
|
395
|
+
if (args["includeFamily"] === true && args["personId"] === undefined) {
|
|
396
|
+
throw new ConnectorCallError("invalid_args", "includeFamily needs a personId.");
|
|
397
|
+
}
|
|
398
|
+
const joined = (value) => (Array.isArray(value) ? value.join("-") : undefined);
|
|
399
|
+
// Breeze's 2023 table says DD-MM-YYYY while its own example request
|
|
400
|
+
// sends 2022-1-15; the server parses both, so the unambiguous ISO day
|
|
401
|
+
// is what the schema accepts and what goes on the wire.
|
|
402
|
+
const rows = asArray(yield* get("/giving/list", {
|
|
403
|
+
start: args["start"],
|
|
404
|
+
end: args["end"],
|
|
405
|
+
person_id: args["personId"],
|
|
406
|
+
include_family: args["includeFamily"] === true ? 1 : undefined,
|
|
407
|
+
amount_min: args["amountMin"],
|
|
408
|
+
amount_max: args["amountMax"],
|
|
409
|
+
fund_ids: joined(args["fundIds"]),
|
|
410
|
+
method_ids: joined(args["methodIds"]),
|
|
411
|
+
batches: joined(args["batchNumbers"]),
|
|
412
|
+
envelope_number: args["envelopeNumber"],
|
|
413
|
+
}));
|
|
414
|
+
return {
|
|
415
|
+
contributions: redact(rows.map((row) => projectContribution(redact, row))),
|
|
416
|
+
};
|
|
417
|
+
}));
|
|
418
|
+
const load_list_funds = (_args) => projected(Result_list_funds, () => Effect.gen(function* () {
|
|
419
|
+
return {
|
|
420
|
+
funds: asArray(yield* get("/funds/list", {})).map((value) => {
|
|
421
|
+
const fund = asRecord(value);
|
|
422
|
+
return compact({
|
|
423
|
+
id: idString(fund["id"]),
|
|
424
|
+
name: fund["name"],
|
|
425
|
+
taxDeductible: flag(fund["tax_deductible"]),
|
|
426
|
+
isDefault: flag(fund["is_default"]),
|
|
427
|
+
});
|
|
428
|
+
}),
|
|
429
|
+
};
|
|
430
|
+
}));
|
|
431
|
+
return [
|
|
432
|
+
read(connection, {
|
|
433
|
+
name: "get_account_summary",
|
|
434
|
+
output: Result_get_account_summary,
|
|
435
|
+
description: "Get the Breeze account's church name, subdomain, timezone, country, and currency. Use it to read giving amounts in the right currency.",
|
|
436
|
+
input: Schema.Struct({}),
|
|
437
|
+
fields: true,
|
|
438
|
+
run: (_args) => projected(Result_get_account_summary, () => Effect.gen(function* () {
|
|
439
|
+
const account = asRecord(yield* get("/account/summary", {}));
|
|
440
|
+
const details = asRecord(account["details"]);
|
|
441
|
+
const country = asRecord(details["country"]);
|
|
442
|
+
return compact({
|
|
443
|
+
id: idString(account["id"]),
|
|
444
|
+
name: account["name"],
|
|
445
|
+
subdomain: account["subdomain"],
|
|
446
|
+
timezone: details["timezone"],
|
|
447
|
+
country: country["name"],
|
|
448
|
+
currency: country["currency"],
|
|
449
|
+
});
|
|
450
|
+
})),
|
|
451
|
+
}),
|
|
452
|
+
list(connection, {
|
|
453
|
+
name: "list_people",
|
|
454
|
+
description: "List people in Breeze, optionally filtered by tag or profile-field values, with id and names or full profile details. No name search; filter names in code.",
|
|
455
|
+
filters: {
|
|
456
|
+
"tagId": Schema.optionalKey(Schema.String.check(matches("^[0-9]+$"), isMaxChars(20)).annotate({
|
|
457
|
+
description: "Only people carrying this tag id from list_tags.",
|
|
458
|
+
})),
|
|
459
|
+
"filter": Schema.optionalKey(ObjectValue.annotate({
|
|
460
|
+
description: 'Breeze filter_json: profile fieldId to option ids joined by \'-\', e.g. {"2000138015":"226-227"}.',
|
|
461
|
+
})),
|
|
462
|
+
"includeDetails": Schema.optionalKey(Schema.Boolean.annotate({ description: "Include every profile field (slower, larger). Defaults to false." })),
|
|
463
|
+
},
|
|
464
|
+
row: Row_list_people,
|
|
465
|
+
limit,
|
|
466
|
+
cursor: "bound",
|
|
467
|
+
page: (args, cursor) => load_list_people({ ...args, cursor }).pipe(Effect.map((result) => ({ rows: result.people, page: result.page }))),
|
|
468
|
+
}),
|
|
469
|
+
read(connection, {
|
|
470
|
+
name: "get_person",
|
|
471
|
+
output: Result_get_person,
|
|
472
|
+
description: "Get one person by Breeze id with profile details keyed by fieldId and family members. Decode fieldIds with list_profile_fields.",
|
|
473
|
+
input: Schema.Struct({
|
|
474
|
+
"personId": Schema.String.check(matches("^[0-9]+$"), isMaxChars(20)).annotate({
|
|
475
|
+
description: "Breeze person id from list_people.",
|
|
476
|
+
}),
|
|
477
|
+
"includeDetails": Schema.optionalKey(Schema.Boolean.annotate({ description: "Include profile details. Defaults to true." })),
|
|
478
|
+
}),
|
|
479
|
+
fields: true,
|
|
480
|
+
run: (args) => projected(Result_get_person, () => Effect.gen(function* () {
|
|
481
|
+
const payload = yield* get(`/people/${args["personId"]}`, {
|
|
482
|
+
details: args["includeDetails"] === false ? 0 : 1,
|
|
483
|
+
});
|
|
484
|
+
// An account-wide key cannot be refused a person, so an empty answer
|
|
485
|
+
// is an absence rather than a permission gap.
|
|
486
|
+
if (!asRecord(payload)["id"]) {
|
|
487
|
+
throw new ConnectorCallError("not_found", `Breeze has no person ${args["personId"]}. Re-read the id from list_people.`);
|
|
488
|
+
}
|
|
489
|
+
return projectPerson(payload);
|
|
490
|
+
})),
|
|
491
|
+
}),
|
|
492
|
+
list(connection, {
|
|
493
|
+
name: "list_profile_fields",
|
|
494
|
+
description: "List profile fields with fieldId, type, section, and option ids. Decodes person details and builds list_people filters and update_person values.",
|
|
495
|
+
filters: {},
|
|
496
|
+
row: Row_list_profile_fields,
|
|
497
|
+
limit,
|
|
498
|
+
cursor: "bound",
|
|
499
|
+
page: (args, cursor) => load_list_profile_fields(args).pipe(Effect.map((result) => windowRows(result.fields, cursor, args.limit))),
|
|
500
|
+
}),
|
|
501
|
+
list(connection, {
|
|
502
|
+
name: "list_tags",
|
|
503
|
+
description: "List Breeze tags with id, name, and folder. Tags model groups and lists; pass a tag id to list_people or breeze_api_write /tags/assign.",
|
|
504
|
+
filters: {
|
|
505
|
+
"folderId": Schema.optionalKey(Schema.String.check(matches("^[0-9]+$"), isMaxChars(20)).annotate({
|
|
506
|
+
description: "Only tags in this folder. Folders come from breeze_api_read /tags/list_folders.",
|
|
507
|
+
})),
|
|
508
|
+
},
|
|
509
|
+
row: Row_list_tags,
|
|
510
|
+
limit,
|
|
511
|
+
cursor: "bound",
|
|
512
|
+
page: (args, cursor) => load_list_tags(args).pipe(Effect.map((result) => windowRows(result.tags, cursor, args.limit))),
|
|
513
|
+
}),
|
|
514
|
+
read(connection, {
|
|
515
|
+
name: "list_events",
|
|
516
|
+
description: "List event instances between two dates with instance id, series id, calendar, and times. Breeze caches this list for up to 15 minutes.",
|
|
517
|
+
input: Schema.Struct({
|
|
518
|
+
"start": Schema.optionalKey(Schema.String.check(matches("^[0-9]{4}-[0-9]{2}-[0-9]{2}$")).annotate({
|
|
519
|
+
description: "First day, YYYY-MM-DD. Defaults to the first of this month.",
|
|
520
|
+
})),
|
|
521
|
+
"end": Schema.optionalKey(Schema.String.check(matches("^[0-9]{4}-[0-9]{2}-[0-9]{2}$")).annotate({
|
|
522
|
+
description: "Last day, YYYY-MM-DD. Defaults to the end of this month.",
|
|
523
|
+
})),
|
|
524
|
+
"calendarId": Schema.optionalKey(Schema.String.check(matches("^[0-9]+$"), isMaxChars(20)).annotate({
|
|
525
|
+
description: "Only events on this calendar from breeze_api_read /events/calendars/list.",
|
|
526
|
+
})),
|
|
527
|
+
"limit": Schema.optionalKey(Integer.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(1000)).annotate({
|
|
528
|
+
description: "Instances returned, 1 to Breeze's 1000. Defaults to 100.",
|
|
529
|
+
default: 100,
|
|
530
|
+
})),
|
|
531
|
+
fields: Schema.optionalKey(fieldsInput("Dot paths to keep in each event item.")),
|
|
532
|
+
}),
|
|
533
|
+
output: Schema.Struct({
|
|
534
|
+
items: Schema.Array(SelectedEvent),
|
|
535
|
+
page: Schema.Struct({ hasMore: Schema.Literal(false) }),
|
|
536
|
+
truncated: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
537
|
+
}),
|
|
538
|
+
run: (args) => Effect.gen(function* () {
|
|
539
|
+
const error = fieldsProblem(args.fields, new Set(Object.keys(Row_list_events.fields)), "an event item");
|
|
540
|
+
if (error)
|
|
541
|
+
return yield* Effect.fail(new Refusal({ error }));
|
|
542
|
+
const result = yield* load_list_events(args);
|
|
543
|
+
return {
|
|
544
|
+
items: selectFields(result.events, args.fields),
|
|
545
|
+
page: { hasMore: false },
|
|
546
|
+
...(result.truncated ? { truncated: ["items[]"] } : {}),
|
|
547
|
+
};
|
|
548
|
+
}),
|
|
549
|
+
}),
|
|
550
|
+
list(connection, {
|
|
551
|
+
name: "list_attendance",
|
|
552
|
+
description: "List people checked in to one event instance, with check-in and check-out times. Not anonymous head counts; those come from breeze_api_read.",
|
|
553
|
+
filters: {
|
|
554
|
+
"instanceId": Schema.String.check(matches("^[0-9]+$"), isMaxChars(20)).annotate({
|
|
555
|
+
description: "Event instance id (list_events id, not eventId).",
|
|
556
|
+
}),
|
|
557
|
+
"includeNames": Schema.optionalKey(Schema.Boolean.annotate({
|
|
558
|
+
description: "Add each attendee's name. Other vendor fields come from breeze_api_read.",
|
|
559
|
+
})),
|
|
560
|
+
},
|
|
561
|
+
row: Row_list_attendance,
|
|
562
|
+
limit,
|
|
563
|
+
cursor: "bound",
|
|
564
|
+
page: (args, cursor) => load_list_attendance(args).pipe(Effect.map((result) => windowRows(result.attendance, cursor, args.limit))),
|
|
565
|
+
}),
|
|
566
|
+
list(connection, {
|
|
567
|
+
name: "list_contributions",
|
|
568
|
+
description: "List giving transactions between two dates with donor, amount, method, batch, and fund split, for exact client-side totals. Undocumented by Breeze since 2023.",
|
|
569
|
+
filters: {
|
|
570
|
+
"start": Schema.String.check(matches("^[0-9]{4}-[0-9]{2}-[0-9]{2}$")).annotate({
|
|
571
|
+
description: "First gift date, YYYY-MM-DD.",
|
|
572
|
+
}),
|
|
573
|
+
"end": Schema.String.check(matches("^[0-9]{4}-[0-9]{2}-[0-9]{2}$")).annotate({
|
|
574
|
+
description: "Last gift date, YYYY-MM-DD.",
|
|
575
|
+
}),
|
|
576
|
+
"personId": Schema.optionalKey(Schema.String.check(matches("^[0-9]+$"), isMaxChars(20)).annotate({
|
|
577
|
+
description: "Only this donor's gifts.",
|
|
578
|
+
})),
|
|
579
|
+
"includeFamily": Schema.optionalKey(Schema.Boolean.annotate({ description: "With personId, include the donor's family." })),
|
|
580
|
+
"amountMin": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)).annotate({ description: "Gift amount at least this." })),
|
|
581
|
+
"amountMax": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)).annotate({ description: "Gift amount at most this." })),
|
|
582
|
+
"fundIds": Schema.optionalKey(Schema.Array(Schema.String.check(matches("^[0-9]+$")).annotate({ description: "Numeric id." }))
|
|
583
|
+
.check(Schema.isMinLength(1), Schema.isMaxLength(50))
|
|
584
|
+
.annotate({ description: "Only gifts to these funds from list_funds." })),
|
|
585
|
+
"methodIds": Schema.optionalKey(Schema.Array(Schema.String.check(matches("^[0-9]+$")).annotate({ description: "Numeric id." }))
|
|
586
|
+
.check(Schema.isMinLength(1), Schema.isMaxLength(50))
|
|
587
|
+
.annotate({ description: "Only these payment method ids." })),
|
|
588
|
+
"batchNumbers": Schema.optionalKey(Schema.Array(Schema.String.check(matches("^[0-9]+$")).annotate({ description: "Numeric id." }))
|
|
589
|
+
.check(Schema.isMinLength(1), Schema.isMaxLength(50))
|
|
590
|
+
.annotate({ description: "Only these batch numbers." })),
|
|
591
|
+
"envelopeNumber": Schema.optionalKey(Schema.String.check(isMaxChars(20)).annotate({ description: "Only this envelope number." })),
|
|
592
|
+
},
|
|
593
|
+
row: Row_list_contributions,
|
|
594
|
+
limit,
|
|
595
|
+
cursor: "bound",
|
|
596
|
+
page: (args, cursor) => load_list_contributions(args).pipe(Effect.map((result) => windowRows(result.contributions, cursor, args.limit))),
|
|
597
|
+
}),
|
|
598
|
+
list(connection, {
|
|
599
|
+
name: "list_funds",
|
|
600
|
+
description: "List giving funds with id, name, tax-deductible flag, and default flag. Supplies fundIds for list_contributions. Undocumented by Breeze since 2023.",
|
|
601
|
+
filters: {},
|
|
602
|
+
row: Row_list_funds,
|
|
603
|
+
limit,
|
|
604
|
+
cursor: "bound",
|
|
605
|
+
page: (args, cursor) => load_list_funds(args).pipe(Effect.map((result) => windowRows(result.funds, cursor, args.limit))),
|
|
606
|
+
}),
|
|
607
|
+
writeTool(connection, {
|
|
608
|
+
name: "add_person",
|
|
609
|
+
output: Result_add_person,
|
|
610
|
+
destructive: false,
|
|
611
|
+
writeSteps: { write: "once" },
|
|
612
|
+
description: "Create a new person profile with a first and last name and optional profile values. Does not check for duplicates; search list_people first.",
|
|
613
|
+
input: Schema.Struct({
|
|
614
|
+
"firstName": Schema.String.check(isMinChars(1), isMaxChars(100)).annotate({ description: "First name." }),
|
|
615
|
+
"lastName": Schema.String.check(isMinChars(1), isMaxChars(100)).annotate({ description: "Last name." }),
|
|
616
|
+
"values": Schema.optionalKey(Schema.Array(Schema.Struct({
|
|
617
|
+
"fieldId": Schema.String.check(matches("^[0-9]+$")).annotate({ description: "Profile fieldId." }),
|
|
618
|
+
"type": Schema.Literals([
|
|
619
|
+
"text",
|
|
620
|
+
"textarea",
|
|
621
|
+
"radio",
|
|
622
|
+
"checkbox",
|
|
623
|
+
"date",
|
|
624
|
+
"birthdate",
|
|
625
|
+
"email",
|
|
626
|
+
"phone",
|
|
627
|
+
"address",
|
|
628
|
+
"family_role",
|
|
629
|
+
]).annotate({ description: "radio covers multiple choice and dropdown." }),
|
|
630
|
+
"response": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean]).annotate({
|
|
631
|
+
description: "Text, M/D/YYYY date, or option id; true for detail types.",
|
|
632
|
+
})),
|
|
633
|
+
"details": Schema.optionalKey(ObjectValue.annotate({
|
|
634
|
+
description: "email {address}; phone {phone_mobile|phone_home|phone_work}; address {street_address,city,state,zip}; family_role {person_id,role_id 1-5}.",
|
|
635
|
+
})),
|
|
636
|
+
}))
|
|
637
|
+
.check(Schema.isMinLength(1), Schema.isMaxLength(50))
|
|
638
|
+
.annotate({ description: "Profile values to set. fieldId and option ids come from list_profile_fields." })),
|
|
639
|
+
}),
|
|
640
|
+
run: (args) => projected(Result_add_person, () => Effect.gen(function* () {
|
|
641
|
+
const payload = yield* write("/people/add", {
|
|
642
|
+
first: args["firstName"],
|
|
643
|
+
last: args["lastName"],
|
|
644
|
+
fields_json: args["values"] === undefined ? undefined : fieldsJson(args["values"]),
|
|
645
|
+
});
|
|
646
|
+
const person = Array.isArray(payload) ? payload[0] : payload;
|
|
647
|
+
requireTrue(payload, "the person creation");
|
|
648
|
+
if (!asRecord(person)["id"])
|
|
649
|
+
return { ok: true };
|
|
650
|
+
return projectPerson(person);
|
|
651
|
+
})),
|
|
652
|
+
}),
|
|
653
|
+
writeTool(connection, {
|
|
654
|
+
name: "update_person",
|
|
655
|
+
output: Result_update_person,
|
|
656
|
+
destructive: true,
|
|
657
|
+
writeSteps: { write: "once" },
|
|
658
|
+
description: "Set profile values on one person: contact details, multiple choice, dates, text, or family role. Replaces each named field's value.",
|
|
659
|
+
input: Schema.Struct({
|
|
660
|
+
"personId": Schema.String.check(matches("^[0-9]+$"), isMaxChars(20)).annotate({
|
|
661
|
+
description: "Breeze person id from list_people.",
|
|
662
|
+
}),
|
|
663
|
+
"values": Schema.Array(Schema.Struct({
|
|
664
|
+
"fieldId": Schema.String.check(matches("^[0-9]+$")).annotate({ description: "Profile fieldId." }),
|
|
665
|
+
"type": Schema.Literals([
|
|
666
|
+
"text",
|
|
667
|
+
"textarea",
|
|
668
|
+
"radio",
|
|
669
|
+
"checkbox",
|
|
670
|
+
"date",
|
|
671
|
+
"birthdate",
|
|
672
|
+
"email",
|
|
673
|
+
"phone",
|
|
674
|
+
"address",
|
|
675
|
+
"family_role",
|
|
676
|
+
]).annotate({ description: "radio covers multiple choice and dropdown." }),
|
|
677
|
+
"response": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean]).annotate({
|
|
678
|
+
description: "Text, M/D/YYYY date, or option id; true for detail types.",
|
|
679
|
+
})),
|
|
680
|
+
"details": Schema.optionalKey(ObjectValue.annotate({
|
|
681
|
+
description: "email {address}; phone {phone_mobile|phone_home|phone_work}; address {street_address,city,state,zip}; family_role {person_id,role_id 1-5}.",
|
|
682
|
+
})),
|
|
683
|
+
}))
|
|
684
|
+
.check(Schema.isMinLength(1), Schema.isMaxLength(50))
|
|
685
|
+
.annotate({ description: "Profile values to set. fieldId and option ids come from list_profile_fields." }),
|
|
686
|
+
}),
|
|
687
|
+
run: (args) => projected(Result_update_person, () => Effect.gen(function* () {
|
|
688
|
+
const fields = fieldsJson(args["values"]);
|
|
689
|
+
const payload = yield* write("/people/update", { person_id: args["personId"], fields_json: fields });
|
|
690
|
+
requireTrue(payload, "the person update");
|
|
691
|
+
const person = Array.isArray(payload) ? payload[0] : payload;
|
|
692
|
+
return asRecord(person)["id"] ? projectPerson(person) : { ok: true };
|
|
693
|
+
})),
|
|
694
|
+
}),
|
|
695
|
+
];
|
|
696
|
+
}
|