@zackbart/connecta 0.34.1 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +418 -0
- package/README.md +65 -105
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2044 -1161
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +751 -281
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +29 -7
- package/dist/connectors/api-connector.js +145 -57
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/remote-mcp.d.ts +10 -30
- package/dist/connectors/remote-mcp.js +955 -563
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +2 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +227 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +379 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +218 -85
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +531 -406
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/contract.d.ts +8 -3
- package/dist/operator-ui/generated.js +4 -4
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +65 -31
- package/dist/provider.js +116 -39
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +427 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +6 -210
- package/dist/providers/_shared/google/workspace.js +6 -425
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/kit.js +146 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +1 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +15 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +5 -22
- package/dist/providers/cloudflare/index.js +79 -197
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +4 -3
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +801 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1023 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +1 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +4 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/quickbooks/api.js +299 -0
- package/dist/providers/quickbooks/index.d.ts +13 -0
- package/dist/providers/quickbooks/index.js +93 -0
- package/dist/providers/quickbooks/lib/query.js +118 -0
- package/dist/providers/quickbooks/lib/writes.js +168 -0
- package/dist/providers/quickbooks/safety.js +222 -0
- package/dist/providers/quickbooks/skill.generated.js +5 -0
- package/dist/providers/quickbooks/tools.js +117 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +733 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +5 -27
- package/dist/providers/stripe/index.js +74 -210
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -11
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +17 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +9 -25
- package/dist/providers/vercel/index.js +65 -260
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +10 -5
- package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +21 -15
- package/dist/registry.js +409 -376
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +6 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +21 -20
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +552 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +322 -223
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +9 -109
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +0 -1
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +28 -7
- package/documentation/architecture.md +2409 -255
- package/documentation/auth.md +867 -114
- package/documentation/code-mode.md +49 -16
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +129 -144
- package/documentation/meta-tools.md +409 -32
- package/documentation/operating.md +27 -3
- package/documentation/operator-tests.md +3 -0
- package/documentation/operator-ui.md +84 -48
- package/documentation/provider-auth-migration.md +16 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +1347 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +30 -50
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -138
- package/dist/catalog-drift.js +0 -578
- package/dist/operator-ui/agents.d.ts +0 -74
- package/dist/operator-ui/agents.js +0 -104
- package/dist/operator-ui/config-summary.js +0 -565
- package/dist/operator-ui/connection-map.js +0 -376
- package/dist/operator-ui/endpoints.js +0 -221
- package/dist/operator-ui/fix-prompts.d.ts +0 -18
- package/dist/operator-ui/fix-prompts.js +0 -87
- package/dist/operator-ui/health.js +0 -52
- package/dist/operator-ui/member-page.js +0 -97
- package/dist/operator-ui/overview.js +0 -123
- package/dist/operator-ui/setup-commands.js +0 -51
- package/dist/operator-ui/view.d.ts +0 -401
- package/dist/operator-ui/view.js +0 -705
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/dispatch.js +0 -41
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/tools.js +0 -569
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -45
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1097
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/intercom/index.d.ts +0 -24
- package/dist/providers/intercom/index.js +0 -121
- package/dist/providers/intercom/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -537
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -59
- package/dist/providers/mixpanel/index.js +0 -472
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -67
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -51
- package/dist/providers/revenuecat/index.js +0 -697
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { StatusFailure } from "../../http/errors.js";
|
|
3
|
+
import { get, post, Summary, vendor } from "../../http/vendor.js";
|
|
4
|
+
import { vendorIndex } from "../../http/vendor-index.js";
|
|
5
|
+
import { RecordShape, CompanyShape, ReportShape, object } from "./safety.js";
|
|
6
|
+
import { matches, reportProblem, reportQuery } from "./lib/query.js";
|
|
7
|
+
export const ENTITIES = {
|
|
8
|
+
customers: "Customer",
|
|
9
|
+
vendors: "Vendor",
|
|
10
|
+
accounts: "Account",
|
|
11
|
+
items: "Item",
|
|
12
|
+
invoices: "Invoice",
|
|
13
|
+
bills: "Bill",
|
|
14
|
+
payments: "Payment",
|
|
15
|
+
bill_payments: "BillPayment",
|
|
16
|
+
};
|
|
17
|
+
export const REPORTS = ["ProfitAndLoss", "BalanceSheet", "CashFlow", "AgedReceivables", "AgedPayables"];
|
|
18
|
+
const version = { minorversion: Schema.optionalKey(Schema.Literal("75")) };
|
|
19
|
+
const id = { id: Schema.String.check(matches("^[1-9]\\d{0,30}$")) };
|
|
20
|
+
export const companyInfo = get("companyInfo", "/companyinfo/:id", {
|
|
21
|
+
params: id,
|
|
22
|
+
query: version,
|
|
23
|
+
success: Schema.Struct({ CompanyInfo: CompanyShape }),
|
|
24
|
+
}).annotate(Summary, "Get the configured company identity");
|
|
25
|
+
export const queryRecords = get("queryRecords", "/query", {
|
|
26
|
+
query: { ...version, query: Schema.String },
|
|
27
|
+
success: Schema.Struct({ QueryResponse: Schema.Unknown }),
|
|
28
|
+
}).annotate(Summary, "Structured accounting searches through search_records only; raw SQL refused");
|
|
29
|
+
export const recordReads = Object.fromEntries(Object.entries(ENTITIES).map(([key, entity]) => [
|
|
30
|
+
key,
|
|
31
|
+
get(`get${entity}`, `/${entity.toLowerCase()}/:id`, {
|
|
32
|
+
params: id,
|
|
33
|
+
query: version,
|
|
34
|
+
success: Schema.Struct({ [entity]: RecordShape }),
|
|
35
|
+
}).annotate(Summary, `Read one ${entity} accounting record`),
|
|
36
|
+
]));
|
|
37
|
+
export const reportReads = Object.fromEntries(REPORTS.map((name) => [
|
|
38
|
+
name,
|
|
39
|
+
get(`report${name}`, `/reports/${name}`, {
|
|
40
|
+
query: { ...version, ...reportQuery(name) },
|
|
41
|
+
success: ReportShape,
|
|
42
|
+
}).annotate(Summary, `Read the ${name} financial report`),
|
|
43
|
+
]));
|
|
44
|
+
// Closed named-write bodies. POSTs are indexed for safety, but refused by the hatch.
|
|
45
|
+
export const PublicId = Schema.String.check(matches("^[1-9]\\d{0,30}$"));
|
|
46
|
+
export const SyncToken = Schema.String.check(matches("^(?:0|[1-9]\\d{0,30})$"));
|
|
47
|
+
const Name = Schema.String.check(matches("^\\S(?:[^\\r\\n]{0,498}\\S)?$"));
|
|
48
|
+
export const CustomerNames = { DisplayName: Name, CompanyName: Schema.optionalKey(Name) };
|
|
49
|
+
export const customerWrite = post("writeCustomer", "/customer", {
|
|
50
|
+
query: { minorversion: Schema.Literal("75"), requestid: Schema.String },
|
|
51
|
+
payload: Schema.Struct({
|
|
52
|
+
...CustomerNames,
|
|
53
|
+
Id: Schema.optionalKey(PublicId),
|
|
54
|
+
SyncToken: Schema.optionalKey(SyncToken),
|
|
55
|
+
sparse: Schema.optionalKey(Schema.Literal(true)),
|
|
56
|
+
}),
|
|
57
|
+
success: Schema.Struct({ Customer: RecordShape }),
|
|
58
|
+
}).annotate(Summary, "Create a customer or sparsely update naming fields through named tools only");
|
|
59
|
+
export const invoiceWrite = post("createInvoice", "/invoice", {
|
|
60
|
+
query: { minorversion: Schema.Literal("75"), requestid: Schema.String },
|
|
61
|
+
payload: Schema.Struct({
|
|
62
|
+
CustomerRef: Schema.Struct({ value: PublicId }),
|
|
63
|
+
CurrencyRef: Schema.Struct({ value: Schema.String }),
|
|
64
|
+
ExchangeRate: Schema.Number,
|
|
65
|
+
TxnDate: Schema.String,
|
|
66
|
+
DueDate: Schema.String,
|
|
67
|
+
DocNumber: Schema.String,
|
|
68
|
+
Line: Schema.Array(Schema.Struct({
|
|
69
|
+
Amount: Schema.Number,
|
|
70
|
+
DetailType: Schema.Literal("SalesItemLineDetail"),
|
|
71
|
+
SalesItemLineDetail: Schema.Struct({
|
|
72
|
+
ItemRef: Schema.Struct({ value: PublicId }),
|
|
73
|
+
Qty: Schema.Literal(1),
|
|
74
|
+
UnitPrice: Schema.Number,
|
|
75
|
+
}),
|
|
76
|
+
})),
|
|
77
|
+
EmailStatus: Schema.Literal("NotSet"),
|
|
78
|
+
AllowIPNPayment: Schema.Literal(false),
|
|
79
|
+
AllowOnlinePayment: Schema.Literal(false),
|
|
80
|
+
AllowOnlineCreditCardPayment: Schema.Literal(false),
|
|
81
|
+
AllowOnlineACHPayment: Schema.Literal(false),
|
|
82
|
+
AllowOnlinePayPalPayment: Schema.Literal(false),
|
|
83
|
+
AllowOnlineAffirmPayment: Schema.Literal(false),
|
|
84
|
+
}),
|
|
85
|
+
success: Schema.Struct({ Invoice: RecordShape }),
|
|
86
|
+
}).annotate(Summary, "Create a real unsent service invoice through create_invoice only");
|
|
87
|
+
export const QuickBooksApi = vendor("QuickBooks Online", companyInfo, queryRecords, customerWrite, invoiceWrite, ...Object.values(recordReads), ...Object.values(reportReads));
|
|
88
|
+
export const QUICKBOOKS_INDEX = vendorIndex(QuickBooksApi, {
|
|
89
|
+
prefix: "quickbooks",
|
|
90
|
+
title: "QuickBooks Online",
|
|
91
|
+
revision: "v3-minor75",
|
|
92
|
+
});
|
|
93
|
+
export const QUICKBOOKS_VERDICTS = {
|
|
94
|
+
"GET /companyinfo/{id}": "read",
|
|
95
|
+
"GET /query": "refuse",
|
|
96
|
+
"POST /customer": "refuse",
|
|
97
|
+
"POST /invoice": "refuse",
|
|
98
|
+
...Object.fromEntries(Object.values(ENTITIES).map((entity) => [`GET /${entity.toLowerCase()}/{id}`, "read"])),
|
|
99
|
+
...Object.fromEntries(REPORTS.map((name) => [`GET /reports/${name}`, "read"])),
|
|
100
|
+
};
|
|
101
|
+
export const QUICKBOOKS_NAMED = {
|
|
102
|
+
"GET /companyinfo/{id}": "get_company_info",
|
|
103
|
+
"GET /query": "search_records",
|
|
104
|
+
...Object.fromEntries(Object.values(ENTITIES).map((entity) => [`GET /${entity.toLowerCase()}/{id}`, "get_record"])),
|
|
105
|
+
...Object.fromEntries(REPORTS.map((name) => [`GET /reports/${name}`, "get_report"])),
|
|
106
|
+
};
|
|
107
|
+
export const apiOrigin = (options) => options.environment === "sandbox" ? "https://sandbox-quickbooks.api.intuit.com" : "https://quickbooks.api.intuit.com";
|
|
108
|
+
export function oauth(options) {
|
|
109
|
+
return {
|
|
110
|
+
authorizationEndpoint: "https://appcenter.intuit.com/connect/oauth2",
|
|
111
|
+
tokenEndpoint: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
|
|
112
|
+
revocationEndpoint: "https://developer.api.intuit.com/v2/oauth2/tokens/revoke",
|
|
113
|
+
revocationRequestFormat: "json",
|
|
114
|
+
clientId: options.clientId,
|
|
115
|
+
clientSecret: options.clientSecret,
|
|
116
|
+
scope: "com.intuit.quickbooks.accounting",
|
|
117
|
+
pkce: false,
|
|
118
|
+
expectedCallbackParameters: { realmId: options.realmId },
|
|
119
|
+
apiOrigins: [apiOrigin(options)],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function quickbooksBinding({ connection, options, }) {
|
|
123
|
+
return {
|
|
124
|
+
review: "Fixed company/environment, indexed GETs only, minorversion 75; query SQL is reserved for the named structured builder.",
|
|
125
|
+
cursorScope: { realmId: options.realmId, environment: options.environment },
|
|
126
|
+
refuse: (call) => {
|
|
127
|
+
// The indexed refusal owns /query, including its discovery/details reason.
|
|
128
|
+
if (call.op.path === "/query")
|
|
129
|
+
return undefined;
|
|
130
|
+
if (call.op.path === "/companyinfo/{id}" && call.path !== `/companyinfo/${options.realmId}`)
|
|
131
|
+
return "Company info must name this connector's configured realmId.";
|
|
132
|
+
if (!/^\/(?:[a-z]+\/[1-9]\d{0,30}|reports\/[A-Za-z]+)$/.test(call.path))
|
|
133
|
+
return "Use a canonical reviewed QuickBooks path.";
|
|
134
|
+
if (call.op.path.startsWith("/reports/"))
|
|
135
|
+
return reportProblem(call.path.split("/").at(-1), call.query);
|
|
136
|
+
if (Object.keys(call.query).some((key) => key !== "minorversion"))
|
|
137
|
+
return "This record read accepts no extra query parameters.";
|
|
138
|
+
return undefined;
|
|
139
|
+
},
|
|
140
|
+
send: (call, settings) => connection.sendPath({
|
|
141
|
+
method: "GET",
|
|
142
|
+
path: call.path,
|
|
143
|
+
template: call.op.path,
|
|
144
|
+
query: Object.entries({ ...call.query, minorversion: "75" }).map(([key, value]) => [
|
|
145
|
+
key,
|
|
146
|
+
String(value),
|
|
147
|
+
]),
|
|
148
|
+
}, settings),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export const errors = {
|
|
152
|
+
provider: "QuickBooks Online",
|
|
153
|
+
meanings: {
|
|
154
|
+
unknownId: "connector_call_failed",
|
|
155
|
+
provesAbsence: false,
|
|
156
|
+
conflicts: ["Intuit fault 5010 for a stale SyncToken", "Intuit fault 6240 for a duplicate DisplayName"],
|
|
157
|
+
},
|
|
158
|
+
facts: (body) => {
|
|
159
|
+
const rows = object(object(body)["Fault"])["Error"];
|
|
160
|
+
const code = Array.isArray(rows) ? object(rows[0])["code"] : undefined;
|
|
161
|
+
return { faultCode: typeof code === "string" && ["610", "5010", "6240"].includes(code) ? code : "" };
|
|
162
|
+
},
|
|
163
|
+
factVocabulary: { faultCode: ["610", "5010", "6240"] },
|
|
164
|
+
detail: ({ status }) => `QuickBooks Online returned HTTP ${status}.`,
|
|
165
|
+
unreadable: "QuickBooks Online returned a successful response that is not JSON.",
|
|
166
|
+
numericFacts: {
|
|
167
|
+
retryAfterMs: { header: "retry-after", format: "retry-after", review: "Standard HTTP Retry-After delay." },
|
|
168
|
+
},
|
|
169
|
+
statuses: {
|
|
170
|
+
200: () => ({
|
|
171
|
+
code: "connector_call_failed",
|
|
172
|
+
retryable: false,
|
|
173
|
+
words: (detail) => `${detail} The accounting response was incomplete or invalid.`,
|
|
174
|
+
}),
|
|
175
|
+
400: ({ facts }) => ({
|
|
176
|
+
code: facts["faultCode"] === "610"
|
|
177
|
+
? "connector_call_failed"
|
|
178
|
+
: ["5010", "6240"].includes(String(facts["faultCode"]))
|
|
179
|
+
? "conflict"
|
|
180
|
+
: "invalid_args",
|
|
181
|
+
words: (detail) => facts["faultCode"] === "5010"
|
|
182
|
+
? `${detail} The observed SyncToken is stale; reread and review the change.`
|
|
183
|
+
: facts["faultCode"] === "6240"
|
|
184
|
+
? `${detail} The DisplayName is already in use by a customer, vendor or employee.`
|
|
185
|
+
: detail,
|
|
186
|
+
retryable: false,
|
|
187
|
+
}),
|
|
188
|
+
401: () => ({ code: "auth_required" }),
|
|
189
|
+
403: () => ({ code: "provider_permission_denied", retryable: false }),
|
|
190
|
+
404: () => ({ code: "connector_call_failed", retryable: false }),
|
|
191
|
+
429: ({ retryAfterMs }) => ({
|
|
192
|
+
code: "rate_limited",
|
|
193
|
+
retryAfterMs: Math.max(60_000, retryAfterMs ?? 0),
|
|
194
|
+
words: (detail) => `${detail} Intuit documents a wait of at least 60 seconds after throttling.`,
|
|
195
|
+
}),
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
/** Missing data is a failure, never a fabricated record/report/search or confirmed write. */
|
|
199
|
+
const container = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
200
|
+
const cells = (value) => Array.isArray(value) && value.every((cell) => container(cell) && typeof cell["value"] === "string");
|
|
201
|
+
function reportRows(value, depth = 0) {
|
|
202
|
+
if (!container(value) || depth > 32)
|
|
203
|
+
return false;
|
|
204
|
+
if (value["Row"] === undefined)
|
|
205
|
+
return Object.keys(value).length === 0;
|
|
206
|
+
return (Array.isArray(value["Row"]) &&
|
|
207
|
+
value["Row"].every((row) => {
|
|
208
|
+
if (!container(row))
|
|
209
|
+
return false;
|
|
210
|
+
if (!["Header", "Summary", "ColData", "Rows"].some((key) => row[key] !== undefined))
|
|
211
|
+
return false;
|
|
212
|
+
for (const key of ["Header", "Summary"]) {
|
|
213
|
+
if (row[key] !== undefined && (!container(row[key]) || !cells(row[key]["ColData"])))
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
return ((row["ColData"] === undefined || cells(row["ColData"])) &&
|
|
217
|
+
(row["Rows"] === undefined || reportRows(row["Rows"], depth + 1)));
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
export const result = (body, answer) => {
|
|
221
|
+
const root = object(body);
|
|
222
|
+
const path = answer.operation.template;
|
|
223
|
+
let valid;
|
|
224
|
+
if (answer.operation.method === "POST") {
|
|
225
|
+
const key = path === "/customer" ? "Customer" : path === "/invoice" ? "Invoice" : undefined;
|
|
226
|
+
const record = key === undefined ? {} : object(root[key]);
|
|
227
|
+
const numericId = (value) => typeof value === "string" && /^[1-9]\d{0,30}$/.test(value);
|
|
228
|
+
const amount = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
229
|
+
valid =
|
|
230
|
+
key !== undefined &&
|
|
231
|
+
numericId(record["Id"]) &&
|
|
232
|
+
typeof record["SyncToken"] === "string" &&
|
|
233
|
+
/^(?:0|[1-9]\d{0,30})$/.test(record["SyncToken"]) &&
|
|
234
|
+
(key === "Customer"
|
|
235
|
+
? typeof record["DisplayName"] === "string" && record["DisplayName"].trim() !== ""
|
|
236
|
+
: (record["EmailStatus"] === undefined || record["EmailStatus"] === "NotSet") &&
|
|
237
|
+
[
|
|
238
|
+
"AllowIPNPayment",
|
|
239
|
+
"AllowOnlinePayment",
|
|
240
|
+
"AllowOnlineCreditCardPayment",
|
|
241
|
+
"AllowOnlineACHPayment",
|
|
242
|
+
"AllowOnlinePayPalPayment",
|
|
243
|
+
"AllowOnlineAffirmPayment",
|
|
244
|
+
].every((key) => record[key] === undefined || record[key] === false) &&
|
|
245
|
+
amount(record["TotalAmt"]) &&
|
|
246
|
+
["Balance", "HomeTotalAmt", "HomeBalance"].every((key) => record[key] === undefined || amount(record[key])) &&
|
|
247
|
+
(record["ExchangeRate"] === undefined ||
|
|
248
|
+
(typeof record["ExchangeRate"] === "number" &&
|
|
249
|
+
Number.isFinite(record["ExchangeRate"]) &&
|
|
250
|
+
record["ExchangeRate"] > 0)) &&
|
|
251
|
+
typeof record["DocNumber"] === "string" &&
|
|
252
|
+
record["DocNumber"].trim() !== "" &&
|
|
253
|
+
numericId(object(record["CustomerRef"])["value"]) &&
|
|
254
|
+
typeof object(record["CurrencyRef"])["value"] === "string" &&
|
|
255
|
+
/^[A-Z]{3}$/.test(String(object(record["CurrencyRef"])["value"])) &&
|
|
256
|
+
Array.isArray(record["Line"]) &&
|
|
257
|
+
record["Line"].length > 0 &&
|
|
258
|
+
record["Line"].every((line) => container(line) && amount(line["Amount"])) &&
|
|
259
|
+
record["Line"].some((line) => object(line)["DetailType"] === "SalesItemLineDetail" &&
|
|
260
|
+
numericId(object(object(object(line)["SalesItemLineDetail"])["ItemRef"])["value"])));
|
|
261
|
+
}
|
|
262
|
+
else if (path === "/query") {
|
|
263
|
+
const rows = root["QueryResponse"];
|
|
264
|
+
valid =
|
|
265
|
+
container(rows) &&
|
|
266
|
+
Object.entries(rows).every(([key, value]) => (["startPosition", "maxResults", "totalCount"].includes(key) &&
|
|
267
|
+
typeof value === "number" &&
|
|
268
|
+
Number.isSafeInteger(value) &&
|
|
269
|
+
value >= (key === "startPosition" ? 1 : 0)) ||
|
|
270
|
+
(answer.operation.flags?.[key] === true &&
|
|
271
|
+
Array.isArray(value) &&
|
|
272
|
+
(rows["totalCount"] === undefined ||
|
|
273
|
+
(typeof rows["totalCount"] === "number" && rows["totalCount"] >= value.length)) &&
|
|
274
|
+
(value.length > 0 ||
|
|
275
|
+
((rows["totalCount"] === undefined || rows["totalCount"] === 0) &&
|
|
276
|
+
(rows["maxResults"] === undefined || rows["maxResults"] === 0))) &&
|
|
277
|
+
value.every((row) => typeof object(row)["Id"] === "string" && object(row)["Id"] !== ""))) &&
|
|
278
|
+
(Object.keys(rows).some((key) => answer.operation.flags?.[key] === true) ||
|
|
279
|
+
((rows["totalCount"] === undefined || rows["totalCount"] === 0) &&
|
|
280
|
+
(rows["maxResults"] === undefined || rows["maxResults"] === 0)));
|
|
281
|
+
}
|
|
282
|
+
else if (path.startsWith("/reports/")) {
|
|
283
|
+
const columns = object(root["Columns"])["Column"];
|
|
284
|
+
valid =
|
|
285
|
+
typeof object(root["Header"])["ReportName"] === "string" &&
|
|
286
|
+
Array.isArray(columns) &&
|
|
287
|
+
columns.every((column) => container(column)) &&
|
|
288
|
+
reportRows(root["Rows"]);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
const key = path.startsWith("/companyinfo/")
|
|
292
|
+
? "CompanyInfo"
|
|
293
|
+
: Object.values(ENTITIES).find((entity) => path.startsWith(`/${entity.toLowerCase()}/`));
|
|
294
|
+
valid = key !== undefined && typeof object(root[key])["Id"] === "string";
|
|
295
|
+
}
|
|
296
|
+
return valid && root["Fault"] === undefined
|
|
297
|
+
? Effect.succeed(body)
|
|
298
|
+
: Effect.fail(new StatusFailure({ status: answer.status, headers: answer.headers, body, operation: answer.operation }));
|
|
299
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProviderOptions } from "../../provider.js";
|
|
2
|
+
/** One fixed QuickBooks Online company, using the deployment's own Intuit app. */
|
|
3
|
+
export interface QuickBooksOptions extends ProviderOptions {
|
|
4
|
+
realmId: string;
|
|
5
|
+
environment: "sandbox" | "production";
|
|
6
|
+
clientId: string;
|
|
7
|
+
clientSecret: string;
|
|
8
|
+
/** Default rows per search: 25, at most 100. */
|
|
9
|
+
defaultPageSize?: number;
|
|
10
|
+
/** Defaults to true. Explicit false enables the bounded named writes. */
|
|
11
|
+
readOnly?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const quickbooks: import("../../provider.js").ProviderFactory<QuickBooksOptions>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { keys, optionsOf } from "../../config-schema.js";
|
|
2
|
+
import { PROVIDER_COMMON } from "../../connectors/option-shapes.js";
|
|
3
|
+
import { defineApiProvider } from "../../http/provider.js";
|
|
4
|
+
import { bearer } from "../../http/oauth.js";
|
|
5
|
+
import { apiOrigin, companyInfo, errors, oauth, QuickBooksApi, QUICKBOOKS_INDEX, QUICKBOOKS_NAMED, QUICKBOOKS_VERDICTS, quickbooksBinding, result, } from "./api.js";
|
|
6
|
+
import { quickbooksSafety } from "./safety.js";
|
|
7
|
+
import { quickbooksTools } from "./tools.js";
|
|
8
|
+
import { skill } from "./skill.generated.js";
|
|
9
|
+
const OPTIONS = optionsOf()({
|
|
10
|
+
...PROVIDER_COMMON,
|
|
11
|
+
...keys("realmId", "environment", "clientId", "clientSecret", "defaultPageSize"),
|
|
12
|
+
});
|
|
13
|
+
function baseUrl(options) {
|
|
14
|
+
if (typeof options.realmId !== "string" ||
|
|
15
|
+
options.realmId.trim() !== options.realmId ||
|
|
16
|
+
!/^[1-9]\d{0,30}$/.test(options.realmId))
|
|
17
|
+
throw new Error("quickbooks() realmId must be the fixed numeric company id.");
|
|
18
|
+
if (options.environment !== "sandbox" && options.environment !== "production")
|
|
19
|
+
throw new Error('quickbooks() requires environment: "sandbox" or "production".');
|
|
20
|
+
for (const key of ["clientId", "clientSecret"]) {
|
|
21
|
+
if (typeof options[key] !== "string" || !options[key].trim() || /[\r\n]/.test(options[key]))
|
|
22
|
+
throw new Error(`quickbooks() requires ${key}.`);
|
|
23
|
+
}
|
|
24
|
+
return `${apiOrigin(options)}/v3/company/${options.realmId}`;
|
|
25
|
+
}
|
|
26
|
+
export const quickbooks = defineApiProvider({
|
|
27
|
+
name: "quickbooks",
|
|
28
|
+
title: "QuickBooks Online",
|
|
29
|
+
readme: "QuickBooks Online",
|
|
30
|
+
bundle: {
|
|
31
|
+
baselineGzip: 357549,
|
|
32
|
+
note: "./providers/quickbooks initial read surface measures 357,549 B standalone gzip-9 before recording these bundle facts (Node 26.12.0, neutral workerd/worker/browser/import conditions, esbuild). Shared Effect is paid once per deployment; this provider baseline is reported, not capped (#1079).",
|
|
33
|
+
},
|
|
34
|
+
skill,
|
|
35
|
+
options: OPTIONS,
|
|
36
|
+
displayTitle: (options) => `QuickBooks Online (${options.environment})`,
|
|
37
|
+
description: (options) => `QuickBooks Online accounting, company ${options.realmId} (${options.environment}): ${options.purpose}`,
|
|
38
|
+
vendor: QuickBooksApi,
|
|
39
|
+
baseUrl,
|
|
40
|
+
maxResponseBytes: 4 * 1024 * 1024,
|
|
41
|
+
headers: { Accept: "application/json" },
|
|
42
|
+
pageSize: { default: 25, max: 100 },
|
|
43
|
+
idempotencyKey: false,
|
|
44
|
+
authenticate: bearer,
|
|
45
|
+
oauth,
|
|
46
|
+
errors,
|
|
47
|
+
result,
|
|
48
|
+
safety: quickbooksSafety,
|
|
49
|
+
probe: {
|
|
50
|
+
operation: companyInfo,
|
|
51
|
+
request: (options) => ({
|
|
52
|
+
params: { id: options.realmId },
|
|
53
|
+
query: { minorversion: "75" },
|
|
54
|
+
}),
|
|
55
|
+
describe: () => "Connected to the configured QuickBooks Online company.",
|
|
56
|
+
},
|
|
57
|
+
guide: (options) => ({
|
|
58
|
+
context: [
|
|
59
|
+
`Company ${options.realmId}, ${options.environment}: ${options.purpose}`,
|
|
60
|
+
options.readOnly === false
|
|
61
|
+
? "Bounded named writes are enabled. The API hatch remains read-only."
|
|
62
|
+
: "This connection is read-only. Set readOnly: false in deployment code to enable bounded named writes.",
|
|
63
|
+
"Contact details, bank/card/tax identifiers, credentials and private links are withheld on every read.",
|
|
64
|
+
],
|
|
65
|
+
summary: "QuickBooks Online accounting reads and explicitly enabled customer/service-invoice writes.",
|
|
66
|
+
required: true,
|
|
67
|
+
}),
|
|
68
|
+
hatch: {
|
|
69
|
+
name: "quickbooks",
|
|
70
|
+
title: "QuickBooks Online",
|
|
71
|
+
index: QUICKBOOKS_INDEX,
|
|
72
|
+
verdicts: QUICKBOOKS_VERDICTS,
|
|
73
|
+
named: QUICKBOOKS_NAMED,
|
|
74
|
+
pathExample: "/invoice/42",
|
|
75
|
+
binding: quickbooksBinding,
|
|
76
|
+
notes: () => ({
|
|
77
|
+
read: "Only reviewed record, company and report reads. Use search_records for bounded structured queries; raw SQL is refused.",
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
// The existing common option defaults writable elsewhere. This provider requires explicit opt-in.
|
|
81
|
+
tagConnector: (connector, options) => {
|
|
82
|
+
Object.defineProperty(connector, "readOnly", {
|
|
83
|
+
value: options.readOnly !== false,
|
|
84
|
+
enumerable: true,
|
|
85
|
+
configurable: true,
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
transformGuide: (guide) => ({
|
|
89
|
+
...guide,
|
|
90
|
+
content: guide.content.replace("Writes take no idempotency key.", "Every named write requires requestId, sent as Intuit's requestid query parameter; no idempotencyKey header argument is accepted."),
|
|
91
|
+
}),
|
|
92
|
+
tools: quickbooksTools,
|
|
93
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { ConnectorCallError } from "../../../errors.js";
|
|
3
|
+
import { isMaxChars } from "../../../http/json-schema.js";
|
|
4
|
+
export const matches = (pattern) => Schema.makeFilter((value) => new RegExp(pattern).test(value), { toJsonSchema: () => ({ pattern }) });
|
|
5
|
+
const DateOnly = Schema.String.check(matches("^\\d{4}-\\d{2}-\\d{2}$"));
|
|
6
|
+
export const REPORT_QUERY = {
|
|
7
|
+
start_date: Schema.optionalKey(DateOnly.annotate({ description: "First date, YYYY-MM-DD; P&L and Cash Flow only." })),
|
|
8
|
+
end_date: Schema.optionalKey(DateOnly.annotate({ description: "Last date or statement date, YYYY-MM-DD." })),
|
|
9
|
+
accounting_method: Schema.optionalKey(Schema.Literals(["Cash", "Accrual"]).annotate({ description: "Accounting basis; P&L and Balance Sheet only." })),
|
|
10
|
+
};
|
|
11
|
+
export function reportQuery(report) {
|
|
12
|
+
if (report === "AgedReceivables" || report === "AgedPayables")
|
|
13
|
+
return {
|
|
14
|
+
report_date: Schema.optionalKey(DateOnly.annotate({ description: "As-of date, YYYY-MM-DD." })),
|
|
15
|
+
aging_method: Schema.optionalKey(Schema.Literal("Report_Date").annotate({ description: "Age relative to report_date." })),
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
...(["ProfitAndLoss", "CashFlow"].includes(report) ? { start_date: REPORT_QUERY.start_date } : {}),
|
|
19
|
+
end_date: REPORT_QUERY.end_date,
|
|
20
|
+
...(["ProfitAndLoss", "BalanceSheet"].includes(report)
|
|
21
|
+
? { accounting_method: REPORT_QUERY.accounting_method }
|
|
22
|
+
: {}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Named reports use one end_date; aging translates it to the vendor's snapshot date. */
|
|
26
|
+
export function reportParameters(report, query) {
|
|
27
|
+
if ((report === "AgedReceivables" || report === "AgedPayables") && query["end_date"] !== undefined) {
|
|
28
|
+
const { end_date, ...rest } = query;
|
|
29
|
+
return { ...rest, report_date: end_date, aging_method: "Report_Date" };
|
|
30
|
+
}
|
|
31
|
+
return query;
|
|
32
|
+
}
|
|
33
|
+
export function reportProblem(report, query) {
|
|
34
|
+
const allowed = new Set(["minorversion", ...Object.keys(reportQuery(report))]);
|
|
35
|
+
if (Object.keys(query).some((key) => !allowed.has(key)))
|
|
36
|
+
return "This report does not support a supplied parameter; inspect get_report or quickbooks_api_details.";
|
|
37
|
+
for (const key of ["start_date", "end_date", "report_date"]) {
|
|
38
|
+
const value = query[key];
|
|
39
|
+
if (value !== undefined &&
|
|
40
|
+
(typeof value !== "string" ||
|
|
41
|
+
!/^\d{4}-\d{2}-\d{2}$/.test(value) ||
|
|
42
|
+
!Number.isFinite(Date.parse(value)) ||
|
|
43
|
+
new Date(value).toISOString().slice(0, 10) !== value))
|
|
44
|
+
return `${key} must be a real YYYY-MM-DD date.`;
|
|
45
|
+
}
|
|
46
|
+
if (query["start_date"] && query["end_date"] && String(query["start_date"]) > String(query["end_date"]))
|
|
47
|
+
return "start_date must not follow end_date.";
|
|
48
|
+
if (query["aging_method"] !== undefined && query["report_date"] === undefined)
|
|
49
|
+
return "aging_method requires report_date.";
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
export const FILTER_FIELDS = [
|
|
53
|
+
"Id",
|
|
54
|
+
"DisplayName",
|
|
55
|
+
"Name",
|
|
56
|
+
"Active",
|
|
57
|
+
"MetaData.LastUpdatedTime",
|
|
58
|
+
"TxnDate",
|
|
59
|
+
"DueDate",
|
|
60
|
+
"DocNumber",
|
|
61
|
+
"CustomerRef",
|
|
62
|
+
"VendorRef",
|
|
63
|
+
"Balance",
|
|
64
|
+
];
|
|
65
|
+
export const Filters = Schema.Array(Schema.Struct({
|
|
66
|
+
field: Schema.Literals(FILTER_FIELDS).annotate({
|
|
67
|
+
description: "Reviewed filter field, subject to the chosen record type.",
|
|
68
|
+
}),
|
|
69
|
+
operator: Schema.Literals(["eq", "gt", "gte", "lt", "lte"]).annotate({
|
|
70
|
+
description: "Equality or an ordered date/amount comparison.",
|
|
71
|
+
}),
|
|
72
|
+
value: Schema.Union([Schema.String.check(isMaxChars(200)), Schema.Boolean, Schema.Number.check(Schema.isFinite())]),
|
|
73
|
+
})).check(Schema.isMaxLength(8));
|
|
74
|
+
const operations = { eq: "=", gt: ">", gte: ">=", lt: "<", lte: "<=" };
|
|
75
|
+
/** One SQL builder. No agent text is ever used as a clause, identifier or paging anchor. */
|
|
76
|
+
export function buildSearch(entity, filters, start, size) {
|
|
77
|
+
const master = ["Customer", "Vendor", "Account", "Item"].includes(entity);
|
|
78
|
+
const fields = new Set([
|
|
79
|
+
"Id",
|
|
80
|
+
"MetaData.LastUpdatedTime",
|
|
81
|
+
...(master
|
|
82
|
+
? ["Active", entity === "Customer" || entity === "Vendor" ? "DisplayName" : "Name"]
|
|
83
|
+
: [
|
|
84
|
+
"TxnDate",
|
|
85
|
+
...(entity === "Payment" ? [] : ["DocNumber"]),
|
|
86
|
+
...(entity === "Invoice" || entity === "Bill" ? ["DueDate", "Balance"] : []),
|
|
87
|
+
...(entity === "Invoice" || entity === "Payment" ? ["CustomerRef"] : ["VendorRef"]),
|
|
88
|
+
]),
|
|
89
|
+
]);
|
|
90
|
+
if (!Number.isSafeInteger(start) || start < 1 || !Number.isSafeInteger(size) || size < 1 || size > 101)
|
|
91
|
+
throw new ConnectorCallError("invalid_args", "Invalid QuickBooks search page.");
|
|
92
|
+
const clauses = filters.map(({ field, operator, value }) => {
|
|
93
|
+
if (!fields.has(field))
|
|
94
|
+
throw new ConnectorCallError("invalid_args", `${field} is not a reviewed filter for ${entity}.`);
|
|
95
|
+
const range = ["TxnDate", "DueDate", "MetaData.LastUpdatedTime", "Balance"].includes(field);
|
|
96
|
+
if (!range && operator !== "eq")
|
|
97
|
+
throw new ConnectorCallError("invalid_args", `${field} supports only eq.`);
|
|
98
|
+
if (field === "Active"
|
|
99
|
+
? typeof value !== "boolean"
|
|
100
|
+
: field === "Balance"
|
|
101
|
+
? typeof value !== "number"
|
|
102
|
+
: typeof value !== "string" ||
|
|
103
|
+
[...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127))
|
|
104
|
+
throw new ConnectorCallError("invalid_args", `Invalid value type for ${field}.`);
|
|
105
|
+
if (["Id", "CustomerRef", "VendorRef"].includes(field) && !/^[1-9]\d{0,30}$/.test(String(value)))
|
|
106
|
+
throw new ConnectorCallError("invalid_args", `${field} requires a numeric record id.`);
|
|
107
|
+
if (["TxnDate", "DueDate"].includes(field) && reportProblem("ProfitAndLoss", { end_date: value }))
|
|
108
|
+
throw new ConnectorCallError("invalid_args", `${field} requires a real YYYY-MM-DD date.`);
|
|
109
|
+
if (field === "MetaData.LastUpdatedTime" &&
|
|
110
|
+
(typeof value !== "string" ||
|
|
111
|
+
!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) ||
|
|
112
|
+
!Number.isFinite(Date.parse(value))))
|
|
113
|
+
throw new ConnectorCallError("invalid_args", "LastUpdatedTime requires an ISO timestamp with timezone.");
|
|
114
|
+
const literal = typeof value === "string" ? `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'` : String(value);
|
|
115
|
+
return `${field} ${operations[operator]} ${literal}`;
|
|
116
|
+
});
|
|
117
|
+
return `SELECT * FROM ${entity}${clauses.length ? ` WHERE ${clauses.join(" AND ")}` : ""} STARTPOSITION ${start} MAXRESULTS ${size}`;
|
|
118
|
+
}
|