@jskit-ai/connectors-catalog 0.2.2 → 0.2.4
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/README.md +4 -4
- package/docs/ai.md +3 -3
- package/docs/airtable.md +1 -1
- package/docs/algolia.md +2 -2
- package/docs/amazon-redshift.md +2 -2
- package/docs/apollo-io.md +2 -2
- package/docs/asana.md +2 -2
- package/docs/ashby.md +1 -1
- package/docs/attention.md +2 -2
- package/docs/aws-athena.md +1 -1
- package/docs/aws-credentials.md +3 -3
- package/docs/aws-s3.md +1 -1
- package/docs/bigquery.md +2 -2
- package/docs/brevo.md +2 -2
- package/docs/calendly.md +3 -3
- package/docs/canva.md +2 -2
- package/docs/chargebee.md +2 -2
- package/docs/clay.md +2 -2
- package/docs/clickhouse.md +1 -1
- package/docs/confidence-flags.md +1 -1
- package/docs/contentful.md +1 -1
- package/docs/dbt-semantic-layer.md +2 -2
- package/docs/elevenlabs.md +2 -2
- package/docs/firebase-cloud-messaging.md +3 -3
- package/docs/firecrawl.md +4 -4
- package/docs/fireflies.md +1 -1
- package/docs/gatewayapi.md +1 -1
- package/docs/gemini-enterprise.md +1 -1
- package/docs/github-api.md +1 -1
- package/docs/gitlab-api.md +1 -1
- package/docs/gmail.md +1 -1
- package/docs/gong.md +1 -1
- package/docs/google-ads.md +3 -3
- package/docs/google-analytics.md +2 -2
- package/docs/google-docs.md +1 -1
- package/docs/google-drive.md +1 -1
- package/docs/google-maps-platform.md +2 -2
- package/docs/google-search-console.md +1 -1
- package/docs/google-sheets.md +1 -1
- package/docs/google-slides.md +2 -2
- package/docs/granola.md +2 -2
- package/docs/hex.md +2 -2
- package/docs/heygen.md +2 -2
- package/docs/inngest.md +1 -1
- package/docs/klipy.md +1 -1
- package/docs/lightspeed.md +2 -2
- package/docs/linear.md +1 -1
- package/docs/linkedin.md +2 -2
- package/docs/mailgun.md +1 -1
- package/docs/microsoft-excel.md +2 -2
- package/docs/microsoft-fabric.md +1 -1
- package/docs/microsoft-outlook.md +1 -1
- package/docs/microsoft-powerpoint.md +1 -1
- package/docs/microsoft-teams.md +1 -1
- package/docs/microsoft-word.md +1 -1
- package/docs/notion.md +1 -1
- package/docs/oura.md +3 -3
- package/docs/paddle.md +3 -3
- package/docs/pipedrive.md +2 -2
- package/docs/prestashop.md +1 -1
- package/docs/replicate.md +1 -1
- package/docs/resend.md +1 -1
- package/docs/salesforce.md +1 -1
- package/docs/sanity.md +3 -3
- package/docs/semrush.md +2 -2
- package/docs/sentry.md +1 -1
- package/docs/slack.md +1 -1
- package/docs/snowflake.md +5 -5
- package/docs/storyblok.md +1 -1
- package/docs/stripe.md +2 -2
- package/docs/tally.md +1 -1
- package/docs/telegram.md +2 -2
- package/docs/tiktok.md +3 -3
- package/docs/twilio.md +3 -3
- package/docs/twitch.md +2 -2
- package/docs/wave.md +3 -3
- package/docs/wix.md +13 -13
- package/docs/wordpress-com.md +1 -1
- package/docs/wordpress-self-hosted.md +1 -1
- package/docs/workday.md +2 -2
- package/docs/x-twitter.md +3 -3
- package/docs/xero.md +3 -3
- package/docs/zoho-books.md +2 -2
- package/docs/zoho-crm.md +2 -2
- package/package.json +4 -3
- package/patterns/ai-connections/PATTERN.md +16 -1
- package/patterns/ai-connections/example/ai-model.js +12 -3
- package/patterns/ai-connections/example/perplexity-answer.js +15 -4
- package/patterns/api-key-connection/PATTERN.md +9 -1
- package/patterns/api-key-connection/example/connections.js +5 -2
- package/patterns/aws-storage-queries/PATTERN.md +13 -3
- package/patterns/aws-storage-queries/example/s3-transfer.js +14 -3
- package/patterns/event-delivery/PATTERN.md +1 -1
- package/patterns/firebase-messaging/PATTERN.md +10 -0
- package/patterns/firebase-messaging/example/server/notifications.js +8 -0
- package/patterns/google-ads-search/PATTERN.md +18 -7
- package/patterns/google-ads-search/example/ads-setup.js +13 -5
- package/patterns/oauth-connection/PATTERN.md +1 -1
- package/patterns/paddle-catalogue/PATTERN.md +11 -2
- package/patterns/paddle-catalogue/example/create-products.js +5 -2
- package/patterns/public-image/PATTERN.md +10 -1
- package/patterns/public-image/example/logo-url.js +4 -1
- package/patterns/redshift-queries/PATTERN.md +1 -1
- package/patterns/source-scanning/PATTERN.md +13 -2
- package/patterns/source-scanning/example/source-scanner.js +9 -0
- package/src/server/ai.js +19 -5
- package/src/server/apify.js +2 -2
- package/src/server/asana.js +6 -6
- package/src/server/ashby.js +2 -1
- package/src/server/attention.js +12 -11
- package/src/server/awsTransport.js +6 -0
- package/src/server/bigquery.js +5 -4
- package/src/server/brevo.js +2 -1
- package/src/server/clay.js +2 -1
- package/src/server/clickhouse.js +2 -1
- package/src/server/confidence.js +6 -1
- package/src/server/connectorValues.js +33 -0
- package/src/server/databricks.js +7 -7
- package/src/server/dbt-semantic-layer.js +5 -5
- package/src/server/elevenlabs.js +2 -2
- package/src/server/firebase-cloud-messaging.js +2 -2
- package/src/server/gemini-enterprise.js +5 -5
- package/src/server/google-ads-search.js +68 -48
- package/src/server/google-ads.js +15 -17
- package/src/server/google-drive.js +7 -7
- package/src/server/google-maps-platform.js +4 -2
- package/src/server/google-search-console.js +4 -3
- package/src/server/google.js +8 -4
- package/src/server/googleAdsApi.js +14 -0
- package/src/server/granola.js +2 -2
- package/src/server/hubspot.js +2 -1
- package/src/server/incident-io.js +3 -2
- package/src/server/lexware.js +5 -4
- package/src/server/lightspeed.js +7 -5
- package/src/server/linkedin.js +2 -2
- package/src/server/mcpTransport.js +8 -4
- package/src/server/microsoft-fabric.js +3 -3
- package/src/server/microsoft-onedrive.js +2 -2
- package/src/server/microsoft-onenote.js +4 -3
- package/src/server/microsoft-outlook.js +6 -6
- package/src/server/microsoft-sharepoint.js +4 -2
- package/src/server/microsoft-teams.js +2 -1
- package/src/server/microsoft.js +20 -13
- package/src/server/notion.js +2 -1
- package/src/server/pipedrive.js +2 -1
- package/src/server/salesforce.js +12 -12
- package/src/server/semrush.js +9 -8
- package/src/server/snowflake.js +20 -20
- package/src/server/storyblok.js +2 -1
- package/src/server/tally.js +2 -1
- package/src/server/telegram.js +2 -6
- package/src/server/tiktok.js +3 -3
- package/src/server/twilio.js +3 -2
- package/src/server/twitch.js +5 -3
- package/src/server/types.js +13 -0
- package/src/server/wix.js +20 -878
- package/src/server/wixBookings.js +449 -0
- package/src/server/wixCms.js +132 -0
- package/src/server/wixStores.js +296 -0
- package/src/server/wixValues.js +35 -0
- package/src/server/wiz.js +18 -4
- package/src/server/woocommerce.js +1 -1
- package/src/server/wordpress-com.js +2 -1
- package/src/server/workday.js +9 -9
- package/src/server/x-twitter.js +2 -1
- package/src/server/xero.js +40 -40
- package/src/server/zoho-books.js +6 -6
- package/src/server/zoho-crm.js +5 -5
- package/src/shared/confidence.js +6 -1
- package/src/shared/dbt-semantic-layer.js +1 -1
- package/src/shared/figma.js +1 -1
- package/src/shared/google.js +8 -4
- package/src/shared/googleScope.js +4 -0
- package/src/shared/microsoft-fabric.js +4 -5
- package/src/shared/microsoft.js +8 -3
- package/src/shared/salesforce.js +1 -1
- package/src/shared/tokens.js +7 -3
- package/src/shared/uuid.js +13 -0
- package/src/shared/wix.js +4 -3
- package/src/shared/zoho-books.js +3 -4
- package/src/shared/zoho-crm.js +1 -1
- package/src/shared/{zoho-regions.js → zoho.js} +4 -1
- package/test/ai.test.js +1 -1
- package/test/brevo.test.js +1 -1
- package/test/chargebee.test.js +1 -1
- package/test/google-ads.test.js +8 -4
- package/test/linkedin.test.js +1 -1
- package/test/resend.test.js +2 -2
- package/test/salesforce.test.js +1 -1
- package/test/snowflake.test.js +7 -7
- package/test/tiktok.test.js +1 -1
- package/test/wiz.test.js +1 -1
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
2
|
+
import { createSchema } from "json-rest-schema";
|
|
3
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
4
|
+
import { requestPath, sameRecord, cursor, contactIdField, nativeJsonObjectField } from "./wixValues.js";
|
|
5
|
+
import { isUuid } from "../shared/uuid.js";
|
|
6
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
7
|
+
|
|
8
|
+
// Wix eCommerce and Stores operations: carts, products, catalog version, orders, fulfillments and business locations.
|
|
9
|
+
|
|
10
|
+
function productReadOperation(version, action) {
|
|
11
|
+
const schema = createSchema(action === "get" ? {
|
|
12
|
+
productId: { type: "string", required: true, validator: value => isUuid(value) || "Use the Wix product ID." }
|
|
13
|
+
} : {
|
|
14
|
+
limit: { type: "integer", min: 1, max: 100, defaultTo: 20 },
|
|
15
|
+
...(version === 1 ? { offset: { type: "integer", min: 0, max: 100000, defaultTo: 0 }, includeVariants: { type: "boolean", defaultTo: false } } :
|
|
16
|
+
{ cursor: { type: "string", validator: value => cursor(value) || "Use the returned product cursor." } })
|
|
17
|
+
});
|
|
18
|
+
const validProduct = value => isUuid(value?.id) && typeof value.name === "string" && value.name.length > 0;
|
|
19
|
+
return {
|
|
20
|
+
scopes: [],
|
|
21
|
+
request(input) {
|
|
22
|
+
const value = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
23
|
+
const base = version === 1 ? "https://www.wixapis.com/stores-reader/v1/products" : "https://www.wixapis.com/stores/v3/products";
|
|
24
|
+
if (action === "get") return { method: "GET", url: `${base}/${value.productId}` };
|
|
25
|
+
const { includeVariants, ...paging } = value;
|
|
26
|
+
return { method: "POST", url: `${base}/query`,
|
|
27
|
+
body: version === 1 ? { query: { paging }, includeVariants } : { query: { cursorPaging: paging } } };
|
|
28
|
+
},
|
|
29
|
+
validateResult: (value, request) => action === "get" ? validProduct(value?.product) && sameRecord(value.product.id === requestPath(request, -1), "Wix returned a different product.", { messageCode: "connectors.wix.returnedDifferentProduct" }) : Array.isArray(value?.products) && value.products.length <= 100 && value.products.every(validProduct) &&
|
|
30
|
+
(version === 1 ? (value.totalResults === undefined || Number.isSafeInteger(value.totalResults) && value.totalResults >= 0) &&
|
|
31
|
+
(value.metadata === undefined || isRecord(value.metadata) &&
|
|
32
|
+
["items", "offset"].every(key => value.metadata[key] === undefined || Number.isSafeInteger(value.metadata[key]) && value.metadata[key] >= 0)) :
|
|
33
|
+
(value.pagingMetadata === undefined || isRecord(value.pagingMetadata) &&
|
|
34
|
+
(value.pagingMetadata.cursors === undefined || isRecord(value.pagingMetadata.cursors) &&
|
|
35
|
+
["next", "prev"].every(key => value.pagingMetadata.cursors[key] === undefined || cursor(value.pagingMetadata.cursors[key])))))
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function locationWriteOperation(action) {
|
|
39
|
+
const schema = createSchema({
|
|
40
|
+
...(action === "update" ? {
|
|
41
|
+
locationId: contactIdField,
|
|
42
|
+
revision: { type: "none", required: true, validator: value => typeof value === "string" && /^\d{1,19}$/u.test(value) && BigInt(value) <= 9223372036854775807n || "Use the current location revision string." }
|
|
43
|
+
} : {}),
|
|
44
|
+
name: { type: "string", required: true, minLength: 1, maxLength: 150 },
|
|
45
|
+
timeZone: { type: "string", required: true, maxLength: 150, validator: value => {
|
|
46
|
+
try { new Intl.DateTimeFormat("en", { timeZone: value }); return true; } catch { return "Use an IANA time zone."; }
|
|
47
|
+
} },
|
|
48
|
+
description: { type: "string", maxLength: 500 }, email: { type: "string", maxLength: 320 },
|
|
49
|
+
phone: { type: "string", maxLength: 100 }, fax: { type: "string", maxLength: 100 },
|
|
50
|
+
status: { type: "string", enum: ["ACTIVE"] },
|
|
51
|
+
locationTypes: { type: "array", validator: values => values.length <= 10 || "Use at most 10 location types.", items: { type: "string", enum: ["UNKNOWN", "BRANCH", "OFFICES", "RECEPTION", "HEADQUARTERS", "INVENTORY"] } },
|
|
52
|
+
businessSchedule: nativeJsonObjectField, extendedFields: nativeJsonObjectField,
|
|
53
|
+
address: { type: "object", required: true, schema: createSchema({
|
|
54
|
+
country: { type: "string", validator: value => /^[A-Z]{2}$/u.test(value) || "Use a two-letter country code." },
|
|
55
|
+
subdivision: { type: "string", maxLength: 100 }, city: { type: "string", maxLength: 1000 }, postalCode: { type: "string", maxLength: 20 },
|
|
56
|
+
formattedAddress: { type: "string", maxLength: 4000 }, hint: { type: "string", maxLength: 500 },
|
|
57
|
+
streetAddress: { type: "object", schema: createSchema({ number: { type: "string", maxLength: 100 }, name: { type: "string", maxLength: 1000 }, apt: { type: "string", maxLength: 100 } }) },
|
|
58
|
+
geocode: { type: "object", schema: createSchema({ latitude: { type: "number", required: true, min: -90, max: 90 }, longitude: { type: "number", required: true, min: -180, max: 180 } }) }
|
|
59
|
+
}) }
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
scopes: [],
|
|
63
|
+
request(input) {
|
|
64
|
+
const { locationId, ...location } = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
65
|
+
if (!Object.keys(location.address).length) throw new ConnectorError("connector_input_invalid", "Provide the location address.", { statusCode: 422, messageCode: "connectors.wix.provideLocationAddress" });
|
|
66
|
+
if (location.businessSchedule && (["periods", "specialHourPeriod"].some(key => location.businessSchedule[key] !== undefined &&
|
|
67
|
+
(!Array.isArray(location.businessSchedule[key]) || location.businessSchedule[key].length > 100)) ||
|
|
68
|
+
Object.keys(location.businessSchedule).some(key => !["periods", "specialHourPeriod"].includes(key)))) {
|
|
69
|
+
throw new ConnectorError("connector_input_invalid", "Use native businessSchedule periods and specialHourPeriod arrays with at most 100 entries each.", { statusCode: 422, messageCode: "connectors.wix.businessScheduleLimits" });
|
|
70
|
+
}
|
|
71
|
+
return { method: action === "create" ? "POST" : "PUT", url: `https://www.wixapis.com/locations/v1/locations${locationId ? `/${locationId}` : ""}`,
|
|
72
|
+
body: { location: { ...(locationId ? { id: locationId } : {}), ...location } } };
|
|
73
|
+
},
|
|
74
|
+
validateResult: (value, request) => validLocation(value?.location) && typeof value.location.revision === "string" && /^\d{1,19}$/u.test(value.location.revision) &&
|
|
75
|
+
(action === "create" || sameRecord(value.location.id === requestPath(request, -1), "Wix returned a different business location.", { messageCode: "connectors.wix.returnedDifferentBusinessLocation" }))
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function locationActionOperation(action) {
|
|
79
|
+
const schema = createSchema({ locationId: contactIdField });
|
|
80
|
+
return {
|
|
81
|
+
scopes: [],
|
|
82
|
+
request(input) {
|
|
83
|
+
const { locationId } = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
84
|
+
return { method: "POST", url: `https://www.wixapis.com/locations/v1/locations/${locationId}/${action}`,
|
|
85
|
+
body: {} };
|
|
86
|
+
},
|
|
87
|
+
validateResult: (value, request) => validLocation(value?.location) && sameRecord(value.location.id === requestPath(request, -2), "Wix returned a different business location.", { messageCode: "connectors.wix.returnedDifferentBusinessLocation" }) &&
|
|
88
|
+
(action === "archive" ? value.location.archived === true : value.location.default === true)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const locationQuerySchema = createSchema({
|
|
92
|
+
limit: { type: "integer", min: 1, max: 100, defaultTo: 20 }, offset: { type: "integer", min: 0, max: 100000, defaultTo: 0 },
|
|
93
|
+
archived: { type: "boolean", defaultTo: false }
|
|
94
|
+
});
|
|
95
|
+
const locationGetSchema = createSchema({ locationId: { type: "string", required: true, validator: value => isUuid(value) || "Use the returned location ID." } });
|
|
96
|
+
const validLocation = value => isUuid(value?.id) && typeof value.name === "string" &&
|
|
97
|
+
(value.timeZone === undefined || typeof value.timeZone === "string") && (value.archived === undefined || typeof value.archived === "boolean");
|
|
98
|
+
const productV1UpdateSchema = createSchema({
|
|
99
|
+
productId: { type: "string", required: true, validator: value => isUuid(value) || "Use the Wix product ID." },
|
|
100
|
+
name: { type: "string", minLength: 1, maxLength: 80 }, visible: { type: "boolean" },
|
|
101
|
+
price: { type: "none", validator: value => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 999999999.99 || "Use a numeric Wix base price from 0 to 999999999.99." }
|
|
102
|
+
});
|
|
103
|
+
const productV3UpdateSchema = createSchema({
|
|
104
|
+
productId: { type: "string", required: true, validator: value => isUuid(value) || "Use the Wix product ID." },
|
|
105
|
+
revision: { type: "none", required: true, validator: value => typeof value === "string" && /^\d{1,19}$/u.test(value) && BigInt(value) <= 9223372036854775807n || "Use the current product revision string." },
|
|
106
|
+
name: { type: "string", minLength: 1, maxLength: 80 },
|
|
107
|
+
visible: { type: "boolean" }
|
|
108
|
+
});
|
|
109
|
+
const catalogVersionSchema = createSchema({});
|
|
110
|
+
const orderSearchSchema = createSchema({
|
|
111
|
+
limit: { type: "integer", min: 1, max: 100, defaultTo: 20 },
|
|
112
|
+
cursor: { type: "string", validator: value => cursor(value) || "Use the returned order cursor." },
|
|
113
|
+
status: { type: "string", minLength: 1, maxLength: 100 },
|
|
114
|
+
contactId: { type: "string", validator: value => isUuid(value) || "Use the buyer's contact ID." }
|
|
115
|
+
});
|
|
116
|
+
const orderIdSchema = createSchema({ orderId: { type: "string", required: true, minLength: 1, maxLength: 100, validator: value => !/[\p{Cc}]/u.test(value) && ![".", ".."].includes(value) || "Use the returned order ID." } });
|
|
117
|
+
const cartCreateSchema = createSchema({
|
|
118
|
+
note: { type: "string", maxLength: 1000 },
|
|
119
|
+
catalogItems: { type: "array", required: true, validator: values => values.length > 0 && values.length <= 300 || "Select 1–300 catalogue items.",
|
|
120
|
+
items: { type: "object", schema: createSchema({
|
|
121
|
+
quantity: { type: "integer", required: true, min: 1, max: 100000 },
|
|
122
|
+
catalogReference: { type: "object", required: true, schema: createSchema({
|
|
123
|
+
appId: { type: "string", required: true, validator: value => isUuid(value) || "Use the catalogue app ID." },
|
|
124
|
+
catalogItemId: { type: "string", required: true, minLength: 1, maxLength: 36, validator: value => !/[\p{Cc}]/u.test(value) || "Use the selected catalogue item ID." },
|
|
125
|
+
options: nativeJsonObjectField
|
|
126
|
+
}) }
|
|
127
|
+
}) } }
|
|
128
|
+
});
|
|
129
|
+
const cartIdSchema = createSchema({ cartId: { type: "string", required: true, validator: value => isUuid(value) || "Use the cart ID returned by Wix." } });
|
|
130
|
+
const validOrder = value => typeof value?.id === "string" && value.id.length > 0 && value.id.length <= 100 &&
|
|
131
|
+
typeof value.status === "string" && value.status.length > 0 && (value.currency === undefined || typeof value.currency === "string") &&
|
|
132
|
+
(value.lineItems === undefined || Array.isArray(value.lineItems));
|
|
133
|
+
function fulfillmentWriteOperation(update) {
|
|
134
|
+
const trackingSchema = createSchema({
|
|
135
|
+
trackingNumber: { type: "string", minLength: 1, maxLength: 100 },
|
|
136
|
+
shippingProvider: { type: "string", minLength: 1, maxLength: 100 },
|
|
137
|
+
trackingLink: { type: "string", minLength: 1, maxLength: 2048, validator: value => {
|
|
138
|
+
try { const url = new URL(value); return ["https:", "http:"].includes(url.protocol) && !url.username && !url.password || "Use a public tracking URL."; }
|
|
139
|
+
catch { return "Use a public tracking URL."; }
|
|
140
|
+
} }
|
|
141
|
+
});
|
|
142
|
+
const schema = createSchema({
|
|
143
|
+
orderId: { type: "string", required: true, minLength: 1, maxLength: 100, validator: value => !/[\p{Cc}]/u.test(value) && ![".", ".."].includes(value) || "Use the returned order ID." },
|
|
144
|
+
...(update ? { fulfillmentId: contactIdField } : {}),
|
|
145
|
+
lineItems: { type: "array", required: !update, validator: values => values.length > 0 && values.length <= 300 && new Set(values.map(item => item?.id)).size === values.length || "Select 1–300 distinct order line items.",
|
|
146
|
+
items: { type: "object", schema: createSchema({ id: contactIdField, quantity: { type: "integer", required: true, min: 1, max: 100000 } }) } },
|
|
147
|
+
trackingInfo: { type: "object", schema: trackingSchema },
|
|
148
|
+
status: { type: "string", enum: ["Pending", "Accepted", "Ready", "In_Delivery", "Fulfilled"] },
|
|
149
|
+
completed: { type: "boolean" }
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
scopes: [],
|
|
153
|
+
request(input) {
|
|
154
|
+
const { orderId, fulfillmentId, ...fulfillment } = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
155
|
+
if (!Object.keys(fulfillment).length || fulfillment.trackingInfo && !Object.keys(fulfillment.trackingInfo).length) {
|
|
156
|
+
throw new ConnectorError("connector_input_invalid", "Supply the fulfillment properties to write.", { statusCode: 422, messageCode: "connectors.wix.supplyFulfillmentPropertiesWrite" });
|
|
157
|
+
}
|
|
158
|
+
const tracking = fulfillment.trackingInfo;
|
|
159
|
+
if (!update && tracking && (!tracking.trackingNumber || !tracking.shippingProvider ||
|
|
160
|
+
!["fedex", "ups", "usps", "dhl", "canadaPost"].includes(tracking.shippingProvider) && !tracking.trackingLink)) {
|
|
161
|
+
throw new ConnectorError("connector_input_invalid", "Supply tracking number and provider; custom carriers also require a tracking URL.", { statusCode: 422, messageCode: "connectors.wix.supplyTrackingNumberProviderCustom" });
|
|
162
|
+
}
|
|
163
|
+
return { method: update ? "PATCH" : "POST", url: update
|
|
164
|
+
? `https://www.wixapis.com/ecom/v1/fulfillments/${fulfillmentId}/orders/${encodeURIComponent(orderId)}`
|
|
165
|
+
: `https://www.wixapis.com/ecom/v1/fulfillments/orders/${encodeURIComponent(orderId)}/create-fulfillment`,
|
|
166
|
+
body: { fulfillment } };
|
|
167
|
+
},
|
|
168
|
+
validateResult: (value, request) => typeof value?.orderWithFulfillments?.orderId === "string" &&
|
|
169
|
+
sameRecord(value.orderWithFulfillments.orderId === requestPath(request, update ? -1 : -2), "Wix returned fulfillments for a different order.", { messageCode: "connectors.wix.returnedFulfillmentsDifferentOrder" }) &&
|
|
170
|
+
Array.isArray(value.orderWithFulfillments.fulfillments) &&
|
|
171
|
+
value.orderWithFulfillments.fulfillments.every(item => isUuid(item?.id) && Array.isArray(item.lineItems) && item.lineItems.length <= 300 &&
|
|
172
|
+
item.lineItems.every(line => isUuid(line?.id) && Number.isInteger(line.quantity) && line.quantity >= 1 && line.quantity <= 100000)) &&
|
|
173
|
+
(update ? sameRecord(value.orderWithFulfillments.fulfillments.some(item => item.id === requestPath(request, -3)),
|
|
174
|
+
"Wix did not return the updated fulfillment. Inspect the order before retrying.", { messageCode: "connectors.wix.didNotReturnUpdatedFulfillment" }) :
|
|
175
|
+
isUuid(value.fulfillmentId) && value.orderWithFulfillments.fulfillments.some(item => item.id === value.fulfillmentId))
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const wixStoreOperations = {
|
|
179
|
+
"carts.create": {
|
|
180
|
+
scopes: [],
|
|
181
|
+
request(input) {
|
|
182
|
+
const { note, catalogItems } = validateSchemaPayload({ schema: cartCreateSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
183
|
+
return { method: "POST", url: "https://www.wixapis.com/ecom/v2/carts",
|
|
184
|
+
body: { cart: { source: { channelType: "WEB" }, ...(note === undefined ? {} : { note }) }, catalogItems } };
|
|
185
|
+
},
|
|
186
|
+
validateResult: value => isUuid(value?.cart?.id) && Array.isArray(value.cart.lineItems) && value.cart.lineItems.length <= 300 &&
|
|
187
|
+
value.cart.lineItems.every(item => isUuid(item?.id))
|
|
188
|
+
},
|
|
189
|
+
"carts.getCheckoutUrl": {
|
|
190
|
+
scopes: [],
|
|
191
|
+
request(input) {
|
|
192
|
+
const { cartId } = validateSchemaPayload({ schema: cartIdSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
193
|
+
return { method: "POST", url: `https://www.wixapis.com/ecom/v2/carts/${cartId}/get-checkout-url`, body: {} };
|
|
194
|
+
},
|
|
195
|
+
validateResult(value) {
|
|
196
|
+
if (typeof value?.checkoutUrl !== "string" || value.checkoutUrl.length > 8000 || /[\p{Cc}]/u.test(value.checkoutUrl)) return false;
|
|
197
|
+
try { const url = new URL(value.checkoutUrl); return url.protocol === "https:" && !url.username && !url.password; } catch { return false; }
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
"locations.archive": locationActionOperation("archive"),
|
|
201
|
+
"locations.setDefault": locationActionOperation("set-default"),
|
|
202
|
+
"locations.create": locationWriteOperation("create"),
|
|
203
|
+
"locations.update": locationWriteOperation("update"),
|
|
204
|
+
"locations.list": {
|
|
205
|
+
scopes: [],
|
|
206
|
+
request(input) {
|
|
207
|
+
const { archived, ...paging } = validateSchemaPayload({ schema: locationQuerySchema, mode: "replace" }, input, { statusCode: 422 });
|
|
208
|
+
return { method: "POST", url: "https://www.wixapis.com/locations/v1/locations/query",
|
|
209
|
+
body: { query: { filter: { archived }, paging }, filterAuthorizedLocationEntities: true } };
|
|
210
|
+
},
|
|
211
|
+
validateResult: value => Array.isArray(value?.locations) && value.locations.length <= 100 && value.locations.every(validLocation) &&
|
|
212
|
+
(value.pagingMetadata === undefined || isRecord(value.pagingMetadata) &&
|
|
213
|
+
["count", "offset", "total"].every(key => value.pagingMetadata[key] === undefined || Number.isSafeInteger(value.pagingMetadata[key]) && value.pagingMetadata[key] >= 0))
|
|
214
|
+
},
|
|
215
|
+
"locations.get": {
|
|
216
|
+
scopes: [],
|
|
217
|
+
request(input) {
|
|
218
|
+
const { locationId } = validateSchemaPayload({ schema: locationGetSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
219
|
+
return { method: "GET", url: `https://www.wixapis.com/locations/v1/locations/${locationId}` };
|
|
220
|
+
},
|
|
221
|
+
validateResult: (value, request) => validLocation(value?.location) && sameRecord(value.location.id === requestPath(request, -1), "Wix returned a different business location.", { messageCode: "connectors.wix.returnedDifferentBusinessLocation" })
|
|
222
|
+
},
|
|
223
|
+
"productsV1.list": productReadOperation(1, "list"),
|
|
224
|
+
"productsV1.get": productReadOperation(1, "get"),
|
|
225
|
+
"productsV1.update": {
|
|
226
|
+
scopes: [],
|
|
227
|
+
request(input) {
|
|
228
|
+
const { productId, price, ...changes } = validateSchemaPayload({ schema: productV1UpdateSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
229
|
+
if (!Object.keys(changes).length && price === undefined) throw new ConnectorError("connector_input_invalid", "Provide a product name, visibility or base-price change.", { statusCode: 422, messageCode: "connectors.wix.provideProductNameVisibilityBase" });
|
|
230
|
+
return { method: "PATCH", url: `https://www.wixapis.com/stores/v1/products/${productId}`,
|
|
231
|
+
body: { product: { ...changes, ...(price === undefined ? {} : { priceData: { price } }) } } };
|
|
232
|
+
},
|
|
233
|
+
validateResult: (value, request) => isUuid(value?.product?.id) && typeof value.product.name === "string" && value.product.name.length > 0 &&
|
|
234
|
+
sameRecord(value.product.id === requestPath(request, -1), "Wix returned a different product.", { messageCode: "connectors.wix.returnedDifferentProduct" })
|
|
235
|
+
},
|
|
236
|
+
"productsV3.list": productReadOperation(3, "list"),
|
|
237
|
+
"productsV3.get": productReadOperation(3, "get"),
|
|
238
|
+
"productsV3.update": {
|
|
239
|
+
scopes: [],
|
|
240
|
+
request(input) {
|
|
241
|
+
const { productId, revision, ...changes } = validateSchemaPayload({ schema: productV3UpdateSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
242
|
+
if (!Object.keys(changes).length) throw new ConnectorError("connector_input_invalid", "Provide a product name or visibility change.", { statusCode: 422, messageCode: "connectors.wix.provideProductNameVisibilityChange" });
|
|
243
|
+
return { method: "PATCH", url: `https://www.wixapis.com/stores/v3/products/${productId}`,
|
|
244
|
+
body: { product: { id: productId, revision, ...changes } } };
|
|
245
|
+
},
|
|
246
|
+
validateResult: (value, request) => isUuid(value?.product?.id) && typeof value.product.name === "string" && value.product.name.length > 0 &&
|
|
247
|
+
sameRecord(value.product.id === requestPath(request, -1), "Wix returned a different product.", { messageCode: "connectors.wix.returnedDifferentProduct" }) &&
|
|
248
|
+
typeof value.product.revision === "string" && /^\d{1,19}$/u.test(value.product.revision) && BigInt(value.product.revision) <= 9223372036854775807n
|
|
249
|
+
},
|
|
250
|
+
"catalog.version": {
|
|
251
|
+
scopes: [],
|
|
252
|
+
request(input) {
|
|
253
|
+
validateSchemaPayload({ schema: catalogVersionSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
254
|
+
return { method: "GET", url: "https://www.wixapis.com/stores/v3/provision/version" };
|
|
255
|
+
}, validateResult: value => ["V1_CATALOG", "V3_CATALOG", "STORES_NOT_INSTALLED"].includes(value?.catalogVersion)
|
|
256
|
+
},
|
|
257
|
+
"orders.list": {
|
|
258
|
+
scopes: [],
|
|
259
|
+
request(input) {
|
|
260
|
+
const { status, contactId, ...cursorPaging } = validateSchemaPayload({ schema: orderSearchSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
261
|
+
if (cursorPaging.cursor && (status || contactId)) throw new ConnectorError("connector_input_invalid", "Use only cursor and limit for the next order page.", { statusCode: 422, messageCode: "connectors.wix.useCursorLimitNextOrder" });
|
|
262
|
+
const filter = { ...(status ? { status } : {}), ...(contactId ? { "buyerInfo.contactId": contactId } : {}) };
|
|
263
|
+
return { method: "POST", url: "https://www.wixapis.com/ecom/v1/orders/search",
|
|
264
|
+
body: { search: { cursorPaging, ...(Object.keys(filter).length ? { filter } : {}) } } };
|
|
265
|
+
},
|
|
266
|
+
validateResult: value => Array.isArray(value?.orders) && value.orders.length <= 100 && value.orders.every(validOrder) &&
|
|
267
|
+
(value.metadata === undefined || isRecord(value.metadata) &&
|
|
268
|
+
(value.metadata.count === undefined || value.metadata.count === value.orders.length) &&
|
|
269
|
+
(value.metadata.hasNext === undefined || typeof value.metadata.hasNext === "boolean") &&
|
|
270
|
+
(value.metadata.cursors === undefined || isRecord(value.metadata.cursors) &&
|
|
271
|
+
["next", "prev"].every(key => value.metadata.cursors[key] === undefined || cursor(value.metadata.cursors[key]))))
|
|
272
|
+
},
|
|
273
|
+
"orders.get": {
|
|
274
|
+
scopes: [],
|
|
275
|
+
request(input) {
|
|
276
|
+
const { orderId } = validateSchemaPayload({ schema: orderIdSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
277
|
+
return { method: "GET", url: `https://www.wixapis.com/ecom/v1/orders/${encodeURIComponent(orderId)}` };
|
|
278
|
+
}, validateResult: (value, request) => validOrder(value?.order) && sameRecord(value.order.id === requestPath(request, -1), "Wix returned a different order.", { messageCode: "connectors.wix.returnedDifferentOrder" })
|
|
279
|
+
},
|
|
280
|
+
"orderFulfillments.create": fulfillmentWriteOperation(false),
|
|
281
|
+
"orderFulfillments.update": fulfillmentWriteOperation(true),
|
|
282
|
+
"orderFulfillments.list": {
|
|
283
|
+
scopes: [],
|
|
284
|
+
request(input) {
|
|
285
|
+
const { orderId } = validateSchemaPayload({ schema: orderIdSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
286
|
+
return { method: "GET", url: `https://www.wixapis.com/ecom/v1/fulfillments/orders/${encodeURIComponent(orderId)}` };
|
|
287
|
+
},
|
|
288
|
+
validateResult: (value, request) => isUuid(value?.orderWithFulfillments?.orderId) &&
|
|
289
|
+
sameRecord(value.orderWithFulfillments.orderId === requestPath(request, -1), "Wix returned fulfillments for a different order.", { messageCode: "connectors.wix.returnedFulfillmentsDifferentOrder" }) &&
|
|
290
|
+
Array.isArray(value.orderWithFulfillments.fulfillments) &&
|
|
291
|
+
value.orderWithFulfillments.fulfillments.every(item => isUuid(item?.id) && Array.isArray(item.lineItems) && item.lineItems.length <= 300 &&
|
|
292
|
+
item.lineItems.every(line => isUuid(line?.id) && Number.isInteger(line.quantity) && line.quantity >= 1 && line.quantity <= 100000))
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export { wixStoreOperations };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
2
|
+
import { createSchema } from "json-rest-schema";
|
|
3
|
+
import { isUuid } from "../shared/uuid.js";
|
|
4
|
+
|
|
5
|
+
// The Wix value checks, fields and record checks the operation groups share.
|
|
6
|
+
|
|
7
|
+
// Preserve native Wix JSON without silently dropping unsupported values.
|
|
8
|
+
function serializeNativeJson(value) {
|
|
9
|
+
return JSON.stringify(value, (key, item) => {
|
|
10
|
+
if (["undefined", "function", "symbol", "bigint"].includes(typeof item) ||
|
|
11
|
+
typeof item === "number" && !Number.isFinite(item)) throw new Error("JSON required");
|
|
12
|
+
return item;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
// Results are checked against the operation's own request; a different record is reported with what Wix got wrong.
|
|
16
|
+
const requestPath = (request, index) => decodeURIComponent(new URL(request.url).pathname.split("/").at(index));
|
|
17
|
+
function sameRecord(matches, message, codes) {
|
|
18
|
+
if (!matches) throw new ConnectorError("connector_response_invalid", message, { statusCode: 502, ...codes });
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const cursor = (value) => typeof value === "string" && value.length > 0 && value.length <= 16000 && !/[\p{Cc}]/u.test(value);
|
|
22
|
+
const querySchema = createSchema({
|
|
23
|
+
limit: { type: "integer", min: 1, max: 100, defaultTo: 20 },
|
|
24
|
+
cursor: { type: "string", minLength: 1, maxLength: 16000, noTrim: true, validator: (value) => cursor(value) || "Use the returned Wix page cursor." }
|
|
25
|
+
});
|
|
26
|
+
const contactIdField = { type: "string", required: true, validator: value => isUuid(value) || "Use the contact ID returned by Wix." };
|
|
27
|
+
const nativeJsonObjectField = { type: "none", validator: value => {
|
|
28
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return "Use a native Wix JSON object.";
|
|
29
|
+
try {
|
|
30
|
+
const json = serializeNativeJson(value);
|
|
31
|
+
return Buffer.byteLength(json) <= 100000 || "Keep native Wix object data within 100KB.";
|
|
32
|
+
} catch { return "Use serializable native Wix JSON data."; }
|
|
33
|
+
} };
|
|
34
|
+
|
|
35
|
+
export { serializeNativeJson, requestPath, sameRecord, cursor, querySchema, contactIdField, nativeJsonObjectField };
|
package/src/server/wiz.js
CHANGED
|
@@ -6,8 +6,8 @@ import path from "node:path";
|
|
|
6
6
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
7
7
|
import { validateIntegrationConfiguration } from "@jskit-ai/connectors-core/shared/configuration";
|
|
8
8
|
import { wizDefinition } from "../shared/wiz.js";
|
|
9
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
9
10
|
|
|
10
|
-
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11
11
|
const failure = (code, message, statusCode = 502, codes = {}) => new ConnectorError(code, message, { statusCode, ...codes });
|
|
12
12
|
|
|
13
13
|
// This process belongs to the scanner, not the application's chat/agent runtime.
|
|
@@ -40,11 +40,25 @@ async function runCli(executable, args, options, signal, timeoutMs) {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @param {object} options
|
|
45
|
+
* @param {Record<string, any>} options.configuration The integration configuration.
|
|
46
|
+
* @param {import("@jskit-ai/connectors-core/server/types").ConnectorAuthorize} options.authorize
|
|
47
|
+
* @param {import("@jskit-ai/connectors-core/server/types").ResolveReference} options.resolveReference Resolves the registration's client secret.
|
|
48
|
+
* @param {import("./types.js").WizScanTargetResolver} options.resolveScanTarget
|
|
49
|
+
* @param {string} options.executable The installed Wiz CLI's absolute path.
|
|
50
|
+
* @param {Record<string, string>} [options.environment] The scan worker's PATH, proxy and CA variables.
|
|
51
|
+
* @param {number} [options.timeoutMs]
|
|
52
|
+
* @param {number} [options.maxReportBytes]
|
|
53
|
+
*/
|
|
43
54
|
function createWizScanner({ configuration, authorize, resolveReference, resolveScanTarget, executable,
|
|
44
55
|
environment = { PATH: process.env.PATH || "/usr/bin:/bin" }, timeoutMs = 600_000, maxReportBytes = 16 * 1024 * 1024 }) {
|
|
45
56
|
if (!["linux", "darwin"].includes(process.platform)) throw new TypeError("Run Wiz in a Linux or macOS scan worker.");
|
|
46
57
|
if (typeof executable !== "string" || !path.isAbsolute(executable) || executable.includes("\0")) throw new TypeError("Supply the installed Wiz CLI's absolute path.");
|
|
47
|
-
|
|
58
|
+
// readable-check: allow method-probe - input validation: the application passes its access policy, reference resolver and scan target resolver
|
|
59
|
+
if (typeof authorize !== "function" || typeof resolveReference !== "function" || typeof resolveScanTarget !== "function") {
|
|
60
|
+
throw new TypeError("Wiz requires host authorization, secret resolution and source resolution.");
|
|
61
|
+
}
|
|
48
62
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 3_600_000) throw new TypeError("Use a scan timeout between 1 and 3600000 milliseconds.");
|
|
49
63
|
if (!Number.isSafeInteger(maxReportBytes) || maxReportBytes < 1 || maxReportBytes > 64 * 1024 * 1024) throw new TypeError("Use a positive report limit of at most 64 MiB.");
|
|
50
64
|
const env = {};
|
|
@@ -69,7 +83,7 @@ function createWizScanner({ configuration, authorize, resolveReference, resolveS
|
|
|
69
83
|
// The browser supplies no filesystem path. The trusted host selects an immutable
|
|
70
84
|
// source snapshot and revision after checking the requesting principal's access.
|
|
71
85
|
const target = await resolveScanTarget({ ...owner }, { integrationId, signal });
|
|
72
|
-
if (!
|
|
86
|
+
if (!isRecord(target) || typeof target.directory !== "string" || !path.isAbsolute(target.directory) ||
|
|
73
87
|
target.directory.includes("\0") || typeof target.revision !== "string" || !target.revision.trim() || target.revision.length > 512) {
|
|
74
88
|
throw failure("connector_source_invalid", "The host must supply an absolute source directory and revision.", 422, { messageCode: "connectors.wiz.hostMustSupplyAbsoluteSource" });
|
|
75
89
|
}
|
|
@@ -103,7 +117,7 @@ function createWizScanner({ configuration, authorize, resolveReference, resolveS
|
|
|
103
117
|
} finally { await file.close(); }
|
|
104
118
|
} catch { throw failure("connector_response_invalid", "Wiz did not produce a readable report within the configured size limit.", 502, { messageCode: "connectors.wiz.didNotProduceReadableReport" }); }
|
|
105
119
|
const verdict = report?.status?.verdict;
|
|
106
|
-
if (!
|
|
120
|
+
if (!isRecord(report) || report.status?.state !== "SUCCESS" || !isRecord(report.result) || !["PASSED_BY_POLICY", "WARN_BY_POLICY", "FAILED_BY_POLICY"].includes(verdict) ||
|
|
107
121
|
(exitCode === 4 && verdict !== "FAILED_BY_POLICY")) {
|
|
108
122
|
throw failure("connector_response_invalid", "Wiz returned an unsupported or inconsistent scan report.", 502, { messageCode: "connectors.wiz.returnedUnsupportedInconsistentScanReport" });
|
|
109
123
|
}
|
|
@@ -242,7 +242,7 @@ function refundOperation(action) {
|
|
|
242
242
|
scopes: [],
|
|
243
243
|
request(input, settings) {
|
|
244
244
|
if (action === "create" && (typeof input?.amount !== "string" || typeof input.api_refund !== "boolean" || typeof input.api_restock !== "boolean" ||
|
|
245
|
-
input.line_items
|
|
245
|
+
Array.isArray(input.line_items) && input.line_items.some(line => typeof line?.refund_total !== "string"))) {
|
|
246
246
|
throw new ConnectorError("connector_input_invalid", "Choose explicit refund/restock flags and decimal-string amounts.", { statusCode: 422, messageCode: "connectors.woocommerce.chooseExplicitRefundRestockFlags" });
|
|
247
247
|
}
|
|
248
248
|
const { order_id, id, ...fields } = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
@@ -3,6 +3,7 @@ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
|
3
3
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
4
|
import { wordpressComDefinition } from "../shared/wordpress-com.js";
|
|
5
5
|
import { jsonOperation } from "./jsonOperation.js";
|
|
6
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
7
|
|
|
7
8
|
const postsSchema = createSchema({
|
|
8
9
|
siteId: { type: "integer", required: true, min: 1, max: Number.MAX_SAFE_INTEGER },
|
|
@@ -131,7 +132,7 @@ const wordpressComProvider = Object.freeze({
|
|
|
131
132
|
}
|
|
132
133
|
return { method: "GET", url: url.href };
|
|
133
134
|
},
|
|
134
|
-
validateResult: value =>
|
|
135
|
+
validateResult: value => isRecord(value) &&
|
|
135
136
|
Object.keys(value).length > 0 && Object.values(value).every(entry => entry !== null && typeof entry === "object")
|
|
136
137
|
},
|
|
137
138
|
"stats.read": siteOperation("", {}, {
|
package/src/server/workday.js
CHANGED
|
@@ -3,21 +3,21 @@ import { jsonOperation } from "./jsonOperation.js";
|
|
|
3
3
|
import { createSchema } from "json-rest-schema";
|
|
4
4
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
5
5
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
6
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
7
|
|
|
7
8
|
function workersUrl(settings) {
|
|
8
9
|
const { host, prefix = "", tenant } = workdayRestEndpoint(settings.restApiEndpoint);
|
|
9
10
|
return `https://${host}${prefix}/api/staffing/v7/${tenant}/workers`;
|
|
10
11
|
}
|
|
11
|
-
const
|
|
12
|
-
const worker = (value) => object(value) && /^[a-f0-9]{32}$/u.test(value.id) && typeof value.descriptor === "string" &&
|
|
12
|
+
const worker = (value) => isRecord(value) && /^[a-f0-9]{32}$/u.test(value.id) && typeof value.descriptor === "string" &&
|
|
13
13
|
(value.workerId === undefined || typeof value.workerId === "string");
|
|
14
14
|
const pageFields = {
|
|
15
15
|
limit: { type: "integer", min: 1, max: 100, defaultTo: 20 },
|
|
16
16
|
offset: { type: "integer", min: 0, max: Number.MAX_SAFE_INTEGER, defaultTo: 0 }
|
|
17
17
|
};
|
|
18
|
-
const organizationRecord = (value) =>
|
|
18
|
+
const organizationRecord = (value) => isRecord(value) && typeof value.id === "string" && value.id.length > 0 &&
|
|
19
19
|
typeof value.descriptor === "string";
|
|
20
|
-
const organizationPage = (value) =>
|
|
20
|
+
const organizationPage = (value) => isRecord(value) && !value.error && !value.errors && Number.isSafeInteger(value.total) && value.total >= 0 &&
|
|
21
21
|
Array.isArray(value.data) && value.data.length <= 100 && value.data.every(organizationRecord);
|
|
22
22
|
function organizationRead(part) {
|
|
23
23
|
const schema = createSchema({
|
|
@@ -68,10 +68,10 @@ function absenceRead(kind) {
|
|
|
68
68
|
return { method: "GET", url: url.href };
|
|
69
69
|
},
|
|
70
70
|
validateResult(value) {
|
|
71
|
-
return
|
|
71
|
+
return isRecord(value) && !value.error && !value.errors && Number.isSafeInteger(value.total) && value.total >= 0 &&
|
|
72
72
|
Array.isArray(value.data) && value.data.length <= 100 && value.data.every(row =>
|
|
73
|
-
kind === "statuses" ? organizationRecord(row) :
|
|
74
|
-
(kind === "balances" ?
|
|
73
|
+
kind === "statuses" ? organizationRecord(row) : isRecord(row) && Number.isFinite(row.quantity) && isRecord(row.unit) &&
|
|
74
|
+
(kind === "balances" ? isRecord(row.absencePlan) : typeof row.date === "string" && isRecord(row.status)));
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
77
|
}
|
|
@@ -112,7 +112,7 @@ const workdayProvider = Object.freeze({
|
|
|
112
112
|
if (url.href.length > 16384) throw new ConnectorError("connector_input_invalid", "Use a smaller report prompt request.", { statusCode: 422, messageCode: "connectors.workday.useSmallerReportPromptRequest" });
|
|
113
113
|
return { method: "GET", url: url.href };
|
|
114
114
|
},
|
|
115
|
-
validateResult: result =>
|
|
115
|
+
validateResult: result => isRecord(result) && !result.error && !result.errors && Array.isArray(result.Report_Entry) && result.Report_Entry.every(isRecord)
|
|
116
116
|
},
|
|
117
117
|
"timeOff.balances": absenceRead("balances"),
|
|
118
118
|
"timeOff.details": absenceRead("details"),
|
|
@@ -129,7 +129,7 @@ const workdayProvider = Object.freeze({
|
|
|
129
129
|
offset: { type: "integer", min: 0, max: Number.MAX_SAFE_INTEGER, defaultTo: 0 },
|
|
130
130
|
search: { type: "string", minLength: 1, maxLength: 512, noTrim: true },
|
|
131
131
|
includeTerminatedWorkers: { type: "boolean" }, filterByOrgVisibility: { type: "boolean" }
|
|
132
|
-
}, (result) =>
|
|
132
|
+
}, (result) => isRecord(result) && !result.error && !result.errors && Number.isSafeInteger(result.total) && result.total >= 0 &&
|
|
133
133
|
Array.isArray(result.data) && result.data.length <= 100 && result.data.every(worker))
|
|
134
134
|
}
|
|
135
135
|
});
|
package/src/server/x-twitter.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createSchema } from "json-rest-schema";
|
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
3
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
4
|
import { xTwitterDefinition } from "../shared/x-twitter.js";
|
|
5
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
6
|
|
|
6
7
|
const validId = (value) => typeof value === "string" && /^[0-9]{1,19}$/u.test(value);
|
|
7
8
|
const validUsername = (value) => typeof value === "string" && /^[A-Za-z0-9_]{1,15}$/u.test(value);
|
|
@@ -21,7 +22,7 @@ const searchSchema = createSchema({
|
|
|
21
22
|
nextToken: { type: "string", noTrim: true, validator: (value) => validCursor(value) || "Use the returned next token." }
|
|
22
23
|
});
|
|
23
24
|
|
|
24
|
-
const validEnvelope = (value) =>
|
|
25
|
+
const validEnvelope = (value) => isRecord(value) && (value.errors === undefined || Array.isArray(value.errors));
|
|
25
26
|
// The profile must be the username the request asked for.
|
|
26
27
|
const validUser = (value, request) => validEnvelope(value) && validId(value.data?.id) && validUsername(value.data?.username) &&
|
|
27
28
|
typeof value.data.name === "string" && value.data.username.toLowerCase() === new URL(request.url).pathname.split("/").at(-1).toLowerCase();
|