@zackbart/connecta 0.35.0 → 0.36.1

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.
Files changed (102) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +8 -4
  3. package/bin/version.mjs +1 -1
  4. package/dist/auth/downstream-oauth.js +71 -79
  5. package/dist/auth/static-oauth.js +104 -26
  6. package/dist/connectors/api-connector.d.ts +17 -0
  7. package/dist/connectors/api-connector.js +3 -1
  8. package/dist/http/client.js +1 -1
  9. package/dist/http/hatch-paging.js +2 -2
  10. package/dist/http/paging.js +3 -89
  11. package/dist/http/provider.js +1 -2
  12. package/dist/http/services.js +1 -1
  13. package/dist/http/tools.js +1 -27
  14. package/dist/operator-ui/contract.d.ts +1 -0
  15. package/dist/operator-ui/generated.js +4 -4
  16. package/dist/program-writes.js +0 -1
  17. package/dist/providers/_shared/google/kit.js +29 -139
  18. package/dist/providers/_shared/rest/kit.js +4 -404
  19. package/dist/providers/_shared/sensitive-fields.js +0 -16
  20. package/dist/providers/breeze/api.js +2 -2
  21. package/dist/providers/ccb/api.js +1 -1
  22. package/dist/providers/cloudflare/api.js +8 -8
  23. package/dist/providers/cloudflare/lib/config.d.ts +2 -1
  24. package/dist/providers/docs/api.js +1 -1
  25. package/dist/providers/docs/tools.js +1 -2
  26. package/dist/providers/drive/api.js +2 -2
  27. package/dist/providers/drive/lib/hatch.js +1 -1
  28. package/dist/providers/drive/tools.js +1 -1
  29. package/dist/providers/forms/api.js +1 -1
  30. package/dist/providers/forms/lib/hatch.js +1 -1
  31. package/dist/providers/forms/tools.js +2 -3
  32. package/dist/providers/github/api.js +1 -1
  33. package/dist/providers/github/lib/discovery.js +2 -2
  34. package/dist/providers/gmail/api.js +1 -1
  35. package/dist/providers/gmail/tools.js +2 -2
  36. package/dist/providers/granola/api.js +155 -0
  37. package/dist/providers/granola/index.d.ts +10 -0
  38. package/dist/providers/granola/index.js +68 -0
  39. package/dist/providers/granola/openapi.generated.js +30 -0
  40. package/dist/providers/granola/safety.js +37 -0
  41. package/dist/providers/granola/skill.generated.js +5 -0
  42. package/dist/providers/granola/tools.js +2 -0
  43. package/dist/providers/overflow/api.js +26 -26
  44. package/dist/providers/planning-center/api.js +4 -4
  45. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  46. package/dist/providers/planning-center/lib/json-api.js +2 -2
  47. package/dist/providers/quickbooks/api.js +299 -0
  48. package/dist/providers/quickbooks/index.d.ts +13 -0
  49. package/dist/providers/quickbooks/index.js +93 -0
  50. package/dist/providers/quickbooks/lib/query.js +118 -0
  51. package/dist/providers/quickbooks/lib/writes.js +168 -0
  52. package/dist/providers/quickbooks/safety.js +222 -0
  53. package/dist/providers/quickbooks/skill.generated.js +5 -0
  54. package/dist/providers/quickbooks/tools.js +117 -0
  55. package/dist/providers/sheets/api.js +1 -1
  56. package/dist/providers/sheets/lib/hatch.js +2 -2
  57. package/dist/providers/sheets/lib/requests.js +1 -1
  58. package/dist/providers/sheets/tools.js +1 -2
  59. package/dist/providers/slides/api.js +1 -1
  60. package/dist/providers/slides/tools.js +2 -2
  61. package/dist/providers/stripe/api.js +7 -7
  62. package/dist/providers/tithely/api.js +2 -2
  63. package/dist/providers/vercel/api.js +12 -12
  64. package/dist/providers/vercel/index.js +1 -2
  65. package/dist/providers/vercel/lib/pin.js +1 -1
  66. package/dist/routes/mcp.js +1 -1
  67. package/dist/routes/oauth.js +3 -0
  68. package/dist/runtime/admission.js +0 -12
  69. package/dist/runtime/call.js +0 -4
  70. package/dist/skills.js +0 -1
  71. package/dist/storage/keys.js +2 -3
  72. package/dist/types.d.ts +2 -0
  73. package/dist/ui.d.ts +1 -1
  74. package/dist/ui.js +0 -1
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.js +1 -1
  77. package/documentation/README.md +1 -0
  78. package/documentation/architecture.md +249 -366
  79. package/documentation/auth.md +56 -19
  80. package/documentation/code-mode.md +6 -6
  81. package/documentation/integrating.md +1 -1
  82. package/documentation/meta-tools.md +6 -3
  83. package/documentation/operator-tests.md +11 -2
  84. package/documentation/operator-ui.md +17 -5
  85. package/documentation/providers.md +492 -0
  86. package/package.json +12 -9
  87. package/templates/node/package.json +1 -1
  88. package/dist/operator-ui/agents.d.ts +0 -75
  89. package/dist/operator-ui/agents.js +0 -114
  90. package/dist/operator-ui/config-summary.js +0 -566
  91. package/dist/operator-ui/connection-map.js +0 -378
  92. package/dist/operator-ui/endpoints.js +0 -212
  93. package/dist/operator-ui/fix-prompts.d.ts +0 -18
  94. package/dist/operator-ui/fix-prompts.js +0 -79
  95. package/dist/operator-ui/health.js +0 -52
  96. package/dist/operator-ui/member-page.js +0 -97
  97. package/dist/operator-ui/overview.js +0 -126
  98. package/dist/operator-ui/setup-commands.js +0 -47
  99. package/dist/operator-ui/view.d.ts +0 -378
  100. package/dist/operator-ui/view.js +0 -631
  101. package/dist/providers/_shared/rest/dispatch.js +0 -21
  102. package/dist/providers/_shared/rest/tools.js +0 -592
@@ -314,7 +314,7 @@ export const getContributions = get("getContributions", "/contributions", {
314
314
  },
315
315
  success: GetContributionsResponse,
316
316
  }).annotate(Summary, "List contributions (gifts), filtered by campaign, campus, status and dates");
317
- export const chargeCashPaymentMethod = post("chargeCashPaymentMethod", "/contributions", {
317
+ const chargeCashPaymentMethod = post("chargeCashPaymentMethod", "/contributions", {
318
318
  payload: ChargeCashPaymentMethodParams,
319
319
  success: ChargeCashPaymentMethodResponse,
320
320
  }).annotate(Summary, "Charge a donor's saved card, bank or wallet payment method for a cash gift");
@@ -322,7 +322,7 @@ export const getContributionById = get("getContributionById", "/contributions/:c
322
322
  params: { "contributionId": Schema.String.pipe(Schema.annotateKey({ description: "contribution id." })) },
323
323
  success: GetContributionResponse,
324
324
  }).annotate(Summary, "Retrieve one contribution (gift) and its donor, campaign and settlement status");
325
- export const initiateRefund = post("initiateRefund", "/contributions/:contributionId/initiate-refund", {
325
+ const initiateRefund = post("initiateRefund", "/contributions/:contributionId/initiate-refund", {
326
326
  params: {
327
327
  "contributionId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the contribution to refund." })),
328
328
  },
@@ -372,7 +372,7 @@ export const getRefunds = get("getRefunds", "/refunds", {
372
372
  },
373
373
  success: GetRefundsResponse,
374
374
  }).annotate(Summary, "List refunds with contribution and deposit joins and cent amounts");
375
- export const getRefundById = get("getRefundById", "/refunds/:refundId", {
375
+ const getRefundById = get("getRefundById", "/refunds/:refundId", {
376
376
  params: { "refundId": Schema.String.pipe(Schema.annotateKey({ description: "refund id." })) },
377
377
  success: GetRefundResponse,
378
378
  }).annotate(Summary, "Retrieve one refund and its contribution, status and cent amounts");
@@ -387,11 +387,11 @@ export const getChargebacks = get("getChargebacks", "/chargebacks", {
387
387
  },
388
388
  success: GetChargebacksResponse,
389
389
  }).annotate(Summary, "List card disputes and ACH returns (chargebacks)");
390
- export const getChargebackById = get("getChargebackById", "/chargebacks/:chargebackId", {
390
+ const getChargebackById = get("getChargebackById", "/chargebacks/:chargebackId", {
391
391
  params: { "chargebackId": Schema.String.pipe(Schema.annotateKey({ description: "chargeback id." })) },
392
392
  success: GetChargebackResponse,
393
393
  }).annotate(Summary, "Retrieve one chargeback (card dispute or ACH return)");
394
- export const getWebhooks = get("getWebhooks", "/webhooks", {
394
+ const getWebhooks = get("getWebhooks", "/webhooks", {
395
395
  query: {
396
396
  "limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100))).pipe(Schema.annotateKey({ description: "The number of webhook subscriptions to return." })),
397
397
  "page": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1))).pipe(Schema.annotateKey({ description: "The page number of webhook subscriptions to return." })),
@@ -402,11 +402,11 @@ export const getWebhooks = get("getWebhooks", "/webhooks", {
402
402
  },
403
403
  success: GetWebhooksResponse,
404
404
  }).annotate(Summary, "List webhook subscriptions, destinations and enabled event names");
405
- export const getWebhookById = get("getWebhookById", "/webhooks/:webhookId", {
405
+ const getWebhookById = get("getWebhookById", "/webhooks/:webhookId", {
406
406
  params: { "webhookId": Schema.String.pipe(Schema.annotateKey({ description: "webhook id." })) },
407
407
  success: GetWebhookResponse,
408
408
  }).annotate(Summary, "Retrieve one webhook subscription and its delivery destination");
409
- export const getWebhookEventLogs = get("getWebhookEventLogs", "/webhooks/:webhookId/event-logs", {
409
+ const getWebhookEventLogs = get("getWebhookEventLogs", "/webhooks/:webhookId/event-logs", {
410
410
  params: { "webhookId": Schema.String.pipe(Schema.annotateKey({ description: "webhook id." })) },
411
411
  query: {
412
412
  "limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(50))).pipe(Schema.annotateKey({ description: "The number of webhook event logs to return." })),
@@ -467,7 +467,7 @@ export const getDonorProfiles = get("getDonorProfiles", "/donors", {
467
467
  },
468
468
  success: GetDonorsResponse,
469
469
  }).annotate(Summary, "List donor profiles, contact information and giving counts");
470
- export const createDonorProfile = post("createDonorProfile", "/donors", {
470
+ const createDonorProfile = post("createDonorProfile", "/donors", {
471
471
  payload: CreateDonorProfileParams,
472
472
  success: CreateDonorProfileResponse,
473
473
  }).annotate(Summary, "Create a donor profile for a nonprofit");
@@ -475,12 +475,12 @@ export const getDonorProfile = get("getDonorProfile", "/donors/:donorId", {
475
475
  params: { "donorId": Schema.String.pipe(Schema.annotateKey({ description: "The ID of the donor to fetch." })) },
476
476
  success: GetDonorResponse,
477
477
  }).annotate(Summary, "Retrieve a donor profile, phone, address and giving counts");
478
- export const updateDonorProfile = patch("updateDonorProfile", "/donors/:donorId", {
478
+ const updateDonorProfile = patch("updateDonorProfile", "/donors/:donorId", {
479
479
  params: { "donorId": Schema.String.pipe(Schema.annotateKey({ description: "The ID of the donor to update." })) },
480
480
  payload: UpdateDonorProfileParams,
481
481
  success: Schema.Unknown,
482
482
  }).annotate(Summary, "Update (edit) a donor's name, email or phone");
483
- export const createSubscription = post("createSubscription", "/subscriptions/:donorId", {
483
+ const createSubscription = post("createSubscription", "/subscriptions/:donorId", {
484
484
  params: {
485
485
  "donorId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the donor to create the subscription for." })),
486
486
  },
@@ -526,14 +526,14 @@ export const getSubscriptions = get("getSubscriptions", "/subscriptions", {
526
526
  },
527
527
  success: GetSubscriptionsResponse,
528
528
  }).annotate(Summary, "List recurring gifts (subscriptions) across donors");
529
- export const getSubscriptionById = get("getSubscriptionById", "/subscriptions/:donorId/:subscriptionId", {
529
+ const getSubscriptionById = get("getSubscriptionById", "/subscriptions/:donorId/:subscriptionId", {
530
530
  params: {
531
531
  "donorId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the donor to get the subscription for." })),
532
532
  "subscriptionId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the subscription to get." })),
533
533
  },
534
534
  success: GetSubscriptionResponse,
535
535
  }).annotate(Summary, "Retrieve one donor's recurring gift and payment schedule");
536
- export const updateSubscriptionById = patch("updateSubscriptionById", "/subscriptions/:donorId/:subscriptionId", {
536
+ const updateSubscriptionById = patch("updateSubscriptionById", "/subscriptions/:donorId/:subscriptionId", {
537
537
  params: {
538
538
  "donorId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the donor to update the subscription for." })),
539
539
  "subscriptionId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the subscription to update." })),
@@ -541,7 +541,7 @@ export const updateSubscriptionById = patch("updateSubscriptionById", "/subscrip
541
541
  payload: UpdateSubscriptionRequest,
542
542
  success: Schema.Unknown,
543
543
  }).annotate(Summary, "Update a recurring gift, payment method, schedule or dollar amount");
544
- export const cancelSubscription = del("cancelSubscription", "/subscriptions/:donorId/:subscriptionId", {
544
+ const cancelSubscription = del("cancelSubscription", "/subscriptions/:donorId/:subscriptionId", {
545
545
  params: {
546
546
  "donorId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the donor to cancel the subscription for." })),
547
547
  "subscriptionId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the subscription to cancel." })),
@@ -584,7 +584,7 @@ export const getCampaigns = get("getCampaigns", "/campaigns", {
584
584
  },
585
585
  success: GetCampaignsResponse,
586
586
  }).annotate(Summary, "List campaigns and subcampaigns, with parent and giving relationships");
587
- export const getCampaignById = get("getCampaignById", "/campaigns/:campaignId", {
587
+ const getCampaignById = get("getCampaignById", "/campaigns/:campaignId", {
588
588
  params: {
589
589
  "campaignId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the campaign/subcampaign to get." })),
590
590
  },
@@ -600,15 +600,15 @@ export const getLocations = get("getLocations", "/locations", {
600
600
  },
601
601
  success: GetLocationsResponse,
602
602
  }).annotate(Summary, "List nonprofit locations (campuses)");
603
- export const getLocationById = get("getLocationById", "/locations/:locationId", {
603
+ const getLocationById = get("getLocationById", "/locations/:locationId", {
604
604
  params: { "locationId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the location to get." })) },
605
605
  success: GetLocationByIdResponse,
606
606
  }).annotate(Summary, "Retrieve a nonprofit location (campus)");
607
- export const authorizePayment = post("authorizePayment", "/payments/authorize", {
607
+ const authorizePayment = post("authorizePayment", "/payments/authorize", {
608
608
  payload: AuthorizePaymentRequest,
609
609
  success: AuthorizePaymentResponse,
610
610
  }).annotate(Summary, "Authorize a payment, or save a payment method with zero amountInCents");
611
- export const getDestinations = get("getDestinations", "/tap/destinations", {
611
+ const getDestinations = get("getDestinations", "/tap/destinations", {
612
612
  query: {
613
613
  "limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100))).pipe(Schema.annotateKey({ description: "The number of tap destinations to return." })),
614
614
  "page": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1))).pipe(Schema.annotateKey({ description: "The page number of the tap destinations to return." })),
@@ -620,13 +620,13 @@ export const getDestinations = get("getDestinations", "/tap/destinations", {
620
620
  },
621
621
  success: GetTapDestinationsResponse,
622
622
  }).annotate(Summary, "List Tap-to-give destinations and configured URLs or messages");
623
- export const getDestinationById = get("getDestinationById", "/tap/destinations/:destinationId", {
623
+ const getDestinationById = get("getDestinationById", "/tap/destinations/:destinationId", {
624
624
  params: {
625
625
  "destinationId": Schema.String.pipe(Schema.annotateKey({ description: "The ID of the tap destination." })),
626
626
  },
627
627
  success: GetTapDestinationByIdResponse,
628
628
  }).annotate(Summary, "Retrieve a Tap-to-give destination URL or message");
629
- export const getDevices = get("getDevices", "/tap/devices", {
629
+ const getDevices = get("getDevices", "/tap/devices", {
630
630
  query: {
631
631
  "limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100))).pipe(Schema.annotateKey({ description: "The number of tap devices to return." })),
632
632
  "page": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1))).pipe(Schema.annotateKey({ description: "The page number of the tap devices to return." })),
@@ -657,11 +657,11 @@ export const getDevices = get("getDevices", "/tap/devices", {
657
657
  },
658
658
  success: GetTapDevicesResponse,
659
659
  }).annotate(Summary, "List Tap-to-give devices, groups and current destinations");
660
- export const getDeviceById = get("getDeviceById", "/tap/devices/:deviceId", {
660
+ const getDeviceById = get("getDeviceById", "/tap/devices/:deviceId", {
661
661
  params: { "deviceId": Schema.String.pipe(Schema.annotateKey({ description: "The ID of the tap device." })) },
662
662
  success: GetTapDeviceByIdResponse,
663
663
  }).annotate(Summary, "Retrieve a Tap-to-give device and its current destination");
664
- export const getEvents = get("getEvents", "/tap/events", {
664
+ const getEvents = get("getEvents", "/tap/events", {
665
665
  query: {
666
666
  "limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100))).pipe(Schema.annotateKey({ description: "The number of tap events to return." })),
667
667
  "page": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1))).pipe(Schema.annotateKey({ description: "The page number of the tap events to return." })),
@@ -682,7 +682,7 @@ export const getEvents = get("getEvents", "/tap/events", {
682
682
  },
683
683
  success: GetTapEventsResponse,
684
684
  }).annotate(Summary, "List Tap-to-give engagement events (taps, not donations)");
685
- export const getGroups = get("getGroups", "/tap/groups", {
685
+ const getGroups = get("getGroups", "/tap/groups", {
686
686
  query: {
687
687
  "limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100))).pipe(Schema.annotateKey({ description: "The number of tap groups to return." })),
688
688
  "page": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1))).pipe(Schema.annotateKey({ description: "The page number of the tap groups to return." })),
@@ -693,11 +693,11 @@ export const getGroups = get("getGroups", "/tap/groups", {
693
693
  },
694
694
  success: GetTapGroupsResponse,
695
695
  }).annotate(Summary, "List Tap-to-give device groups");
696
- export const getGroupById = get("getGroupById", "/tap/groups/:groupId", {
696
+ const getGroupById = get("getGroupById", "/tap/groups/:groupId", {
697
697
  params: { "groupId": Schema.String.pipe(Schema.annotateKey({ description: "The ID of the tap group." })) },
698
698
  success: GetTapGroupByIdResponse,
699
699
  }).annotate(Summary, "Retrieve a Tap-to-give group and its devices and destination");
700
- export const health = get("health", "/health", {
700
+ const health = get("health", "/health", {
701
701
  success: Schema.String,
702
702
  }).annotate(Summary, "Read the API health text (not an authenticated credential check)");
703
703
  export const OverflowApi = {
@@ -808,7 +808,7 @@ function pageNumber(value) {
808
808
  throw new ConnectorCallError("invalid_args", "This Overflow cursor contains no valid page number.");
809
809
  return page;
810
810
  }
811
- export const overflowPage = {
811
+ const overflowPage = {
812
812
  param: "page",
813
813
  in: "query",
814
814
  decode: pageNumber,
@@ -823,7 +823,7 @@ export const overflowPage = {
823
823
  return more ? current + 1 : null;
824
824
  },
825
825
  };
826
- export const overflowEventPage = {
826
+ const overflowEventPage = {
827
827
  param: "cursor",
828
828
  in: "query",
829
829
  decode(cursor) {
@@ -13,7 +13,7 @@ import { TABLE } from "./safety.js";
13
13
  import { excludedApps, series } from "./lib/guide.js";
14
14
  import { asRecord, asArray, resources, attribute, pageOf } from "./lib/json-api.js";
15
15
  import { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS, DEFAULT_USER_AGENT, } from "./lib/constants.js";
16
- export { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, PLANNING_CENTER_MAX_RESPONSE_BYTES, PLANNING_CENTER_ADMISSION, DEFAULT_USER_AGENT, } from "./lib/constants.js";
16
+ export { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, PLANNING_CENTER_MAX_RESPONSE_BYTES, PLANNING_CENTER_ADMISSION, } from "./lib/constants.js";
17
17
  export const CREDENTIAL = {
18
18
  label: "Planning Center personal access token",
19
19
  description: "Create one at api.planningcenteronline.com/oauth/applications → Personal Access Tokens. It acts with the permissions of the user who creates it, in every product that user can open, so create it as a user whose access matches this connection's purpose.",
@@ -59,7 +59,7 @@ export function isApp(value) {
59
59
  * normalization the guarded transport applies — so `..` cannot carry a request
60
60
  * from `/people/v2` to `/oauth` or anywhere else outside a product.
61
61
  */
62
- export function appForPath(path, apps) {
62
+ function appForPath(path, apps) {
63
63
  let normalized;
64
64
  if (path.startsWith("/") && !path.startsWith("//")) {
65
65
  try {
@@ -269,7 +269,7 @@ export const errors = {
269
269
  /** All ten product pins retain their own public URL and version. */
270
270
  export const PCO_PREFIX = "pco";
271
271
  export const PCO_INDEX = new PlanningCenterIndex(openapi);
272
- export const getPeopleMe = get("getPeopleMe", "/people/v2/me", {
272
+ const getPeopleMe = get("getPeopleMe", "/people/v2/me", {
273
273
  success: Schema.Unknown,
274
274
  query: { include: Schema.String },
275
275
  headers: { "X-PCO-API-Version": Schema.String },
@@ -317,7 +317,7 @@ export const PCO_NAMED = {
317
317
  "POST /people/v2/people/{person_id}/workflow_cards/{workflow_card_id}/restore": "apply_workflow_card_action",
318
318
  };
319
319
  /** Native collection offset metadata, read only after response safety. */
320
- export const pcoPage = {
320
+ const pcoPage = {
321
321
  param: "offset",
322
322
  in: "query",
323
323
  total: (data) => pageOf(asRecord(data)).totalCount ?? undefined,
@@ -55,4 +55,4 @@ export declare const PLANNING_CENTER_MAX_RESPONSE_BYTES: number;
55
55
  */
56
56
  export declare const PLANNING_CENTER_ADMISSION: ConnectorCallAdmissionPolicy;
57
57
  /** Planning Center refuses a request without a descriptive one (HTTP 403). */
58
- export declare const DEFAULT_USER_AGENT = "connecta/0.35.0 (+https://github.com/zackbart/connecta)";
58
+ export declare const DEFAULT_USER_AGENT = "connecta/0.36.1 (+https://github.com/zackbart/connecta)";
@@ -8,7 +8,7 @@ export function asArray(value) {
8
8
  export function compact(value) {
9
9
  return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
10
10
  }
11
- export function isResource(value) {
11
+ function isResource(value) {
12
12
  const record = asRecord(value);
13
13
  return typeof record["id"] === "string" && typeof record["type"] === "string";
14
14
  }
@@ -22,7 +22,7 @@ export function indexIncluded(document) {
22
22
  }
23
23
  return index;
24
24
  }
25
- export function relationshipData(resource, name) {
25
+ function relationshipData(resource, name) {
26
26
  return resource.relationships?.[name]?.data;
27
27
  }
28
28
  /** The related id, `null` when Planning Center says there is none. */
@@ -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>;