@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.
- package/CHANGELOG.md +97 -0
- package/README.md +8 -4
- package/bin/version.mjs +1 -1
- package/dist/auth/downstream-oauth.js +71 -79
- package/dist/auth/static-oauth.js +104 -26
- package/dist/connectors/api-connector.d.ts +17 -0
- package/dist/connectors/api-connector.js +3 -1
- package/dist/http/client.js +1 -1
- package/dist/http/hatch-paging.js +2 -2
- package/dist/http/paging.js +3 -89
- package/dist/http/provider.js +1 -2
- package/dist/http/services.js +1 -1
- package/dist/http/tools.js +1 -27
- package/dist/operator-ui/contract.d.ts +1 -0
- package/dist/operator-ui/generated.js +4 -4
- package/dist/program-writes.js +0 -1
- package/dist/providers/_shared/google/kit.js +29 -139
- package/dist/providers/_shared/rest/kit.js +4 -404
- package/dist/providers/_shared/sensitive-fields.js +0 -16
- package/dist/providers/breeze/api.js +2 -2
- package/dist/providers/ccb/api.js +1 -1
- package/dist/providers/cloudflare/api.js +8 -8
- package/dist/providers/cloudflare/lib/config.d.ts +2 -1
- package/dist/providers/docs/api.js +1 -1
- package/dist/providers/docs/tools.js +1 -2
- package/dist/providers/drive/api.js +2 -2
- package/dist/providers/drive/lib/hatch.js +1 -1
- package/dist/providers/drive/tools.js +1 -1
- package/dist/providers/forms/api.js +1 -1
- package/dist/providers/forms/lib/hatch.js +1 -1
- package/dist/providers/forms/tools.js +2 -3
- package/dist/providers/github/api.js +1 -1
- package/dist/providers/github/lib/discovery.js +2 -2
- package/dist/providers/gmail/api.js +1 -1
- package/dist/providers/gmail/tools.js +2 -2
- package/dist/providers/granola/api.js +155 -0
- package/dist/providers/granola/index.d.ts +10 -0
- package/dist/providers/granola/index.js +68 -0
- package/dist/providers/granola/openapi.generated.js +30 -0
- package/dist/providers/granola/safety.js +37 -0
- package/dist/providers/granola/skill.generated.js +5 -0
- package/dist/providers/granola/tools.js +2 -0
- package/dist/providers/overflow/api.js +26 -26
- package/dist/providers/planning-center/api.js +4 -4
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +2 -2
- 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 +1 -1
- package/dist/providers/sheets/lib/hatch.js +2 -2
- package/dist/providers/sheets/lib/requests.js +1 -1
- package/dist/providers/sheets/tools.js +1 -2
- package/dist/providers/slides/api.js +1 -1
- package/dist/providers/slides/tools.js +2 -2
- package/dist/providers/stripe/api.js +7 -7
- package/dist/providers/tithely/api.js +2 -2
- package/dist/providers/vercel/api.js +12 -12
- package/dist/providers/vercel/index.js +1 -2
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/routes/mcp.js +1 -1
- package/dist/routes/oauth.js +3 -0
- package/dist/runtime/admission.js +0 -12
- package/dist/runtime/call.js +0 -4
- package/dist/skills.js +0 -1
- package/dist/storage/keys.js +2 -3
- package/dist/types.d.ts +2 -0
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +0 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/README.md +1 -0
- package/documentation/architecture.md +249 -366
- package/documentation/auth.md +56 -19
- package/documentation/code-mode.md +6 -6
- package/documentation/integrating.md +1 -1
- package/documentation/meta-tools.md +6 -3
- package/documentation/operator-tests.md +11 -2
- package/documentation/operator-ui.md +17 -5
- package/documentation/providers.md +492 -0
- package/package.json +12 -9
- package/templates/node/package.json +1 -1
- package/dist/operator-ui/agents.d.ts +0 -75
- package/dist/operator-ui/agents.js +0 -114
- package/dist/operator-ui/config-summary.js +0 -566
- package/dist/operator-ui/connection-map.js +0 -378
- package/dist/operator-ui/endpoints.js +0 -212
- package/dist/operator-ui/fix-prompts.d.ts +0 -18
- package/dist/operator-ui/fix-prompts.js +0 -79
- package/dist/operator-ui/health.js +0 -52
- package/dist/operator-ui/member-page.js +0 -97
- package/dist/operator-ui/overview.js +0 -126
- package/dist/operator-ui/setup-commands.js +0 -47
- package/dist/operator-ui/view.d.ts +0 -378
- package/dist/operator-ui/view.js +0 -631
- package/dist/providers/_shared/rest/dispatch.js +0 -21
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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>;
|