@hostwebhook/node-types 1.62.0 → 1.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,7 +18,7 @@ exports.isNodeType = isNodeType;
18
18
  const PROCESSING_OUTPUTS = [
19
19
  'webhook', 'router', 'filter', 'transform', 'cache', 'code', 'rateLimiter',
20
20
  'aggregator', 'conditional', 'delay', 'schemaValidator', 'split', 'markdown', 'fileTransform', 'limit',
21
- 'emailAction', 'gmailAction', 'httpAction', 'mongoAction', 'postgresAction', 'notificationAction', 'sheetsAction', 'calendarAction', 'docsAction', 'driveAction', 'firecrawlAction', 'telegramAction', 'whatsappAction', 'discordAction', 'slackAction', 'googleContactsAction', 'googleAnalyticsAction', 'notionAction', 'vectorStore', 'rssAction', 'socialMediaAction', 'mailchimpAction', 'loop',
21
+ 'emailAction', 'gmailAction', 'httpAction', 'mongoAction', 'postgresAction', 'notificationAction', 'sheetsAction', 'calendarAction', 'docsAction', 'driveAction', 'firecrawlAction', 'telegramAction', 'whatsappAction', 'discordAction', 'slackAction', 'googleContactsAction', 'googleAnalyticsAction', 'notionAction', 'vectorStore', 'rssAction', 'socialMediaAction', 'mailchimpAction', 'shopifyAction', 'loop',
22
22
  ];
23
23
  /** Standard processing input sources */
24
24
  const STANDARD_INPUTS = [
@@ -26,7 +26,7 @@ const STANDARD_INPUTS = [
26
26
  'cache', 'code', 'rateLimiter', 'aggregator', 'conditional', 'delay', 'schemaValidator', 'split', 'loop', 'markdown', 'fileTransform', 'limit',
27
27
  ];
28
28
  /** Action nodes that can chain (non-terminal) */
29
- const CHAINABLE_ACTIONS = ['httpAction', 'mongoAction', 'postgresAction', 'sheetsAction', 'calendarAction', 'docsAction', 'driveAction', 'firecrawlAction', 'telegramAction', 'whatsappAction', 'discordAction', 'slackAction', 'googleContactsAction', 'googleAnalyticsAction', 'notionAction', 'vectorStore', 'rssAction', 'socialMediaAction', 'mailchimpAction'];
29
+ const CHAINABLE_ACTIONS = ['httpAction', 'mongoAction', 'postgresAction', 'sheetsAction', 'calendarAction', 'docsAction', 'driveAction', 'firecrawlAction', 'telegramAction', 'whatsappAction', 'discordAction', 'slackAction', 'googleContactsAction', 'googleAnalyticsAction', 'notionAction', 'vectorStore', 'rssAction', 'socialMediaAction', 'mailchimpAction', 'shopifyAction'];
30
30
  /** Full input list for action nodes (standard + chainable actions) */
31
31
  const ACTION_INPUTS = [...STANDARD_INPUTS, ...CHAINABLE_ACTIONS];
32
32
  exports.NODE_CONNECTIONS = {
@@ -78,6 +78,7 @@ exports.NODE_CONNECTIONS = {
78
78
  discordAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
79
79
  slackAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
80
80
  mailchimpAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
81
+ shopifyAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
81
82
  googleContactsAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
82
83
  googleAnalyticsAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
83
84
  notionAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
@@ -96,6 +96,23 @@ exports.CREDENTIAL_TYPES = [
96
96
  // Renombrarlo ahora rompería las credenciales que hayan llegado a existir:
97
97
  // el `type` es la clave por la que los nodos las referencian.
98
98
  { type: 'mailchimp_oauth2' },
99
+ // El OAuth de HostWebhook contra Shopify.
100
+ //
101
+ // Se anota aquí ANTES de escribir el servicio, precisamente por lo que
102
+ // cuenta el bloque de Mailchimp de arriba: la api puede tener el flujo
103
+ // entero y bien, y aun así el `create` de la credencial muere con un enum
104
+ // de Mongoose que TypeScript no ve. Es el mismo agujero, y ya sabemos que
105
+ // no avisa al compilar.
106
+ //
107
+ // Lleva `_oauth2` por coherencia con `mailchimp_oauth2`, y porque el
108
+ // `scopeFilter` del dashboard filtra por esta cadena exacta.
109
+ //
110
+ // Una nota sobre lo que este tipo GUARDA, que aquí no se ve: el dominio de
111
+ // la tienda (`{shop}.myshopify.com`) viaja DENTRO del blob cifrado, igual
112
+ // que el `dc` de Mailchimp. Es parte del host de toda llamada y de la URL
113
+ // de autorización, así que una credencial de Shopify sin su dominio es
114
+ // inservible — no un detalle de presentación.
115
+ { type: 'shopify_oauth2' },
99
116
  ];
100
117
  /**
101
118
  * Plain string array — what the Mongoose `enum` field and the
package/dist/dispatch.js CHANGED
@@ -53,6 +53,7 @@ exports.NODE_DISPATCH = {
53
53
  whatsappAction: { service: 'whatsappActionsService', collection: 'whatsappactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
54
54
  discordAction: { service: 'discordActionsService', collection: 'discordactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
55
55
  mailchimpAction: { service: 'mailchimpActionsService', collection: 'mailchimpactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
56
+ shopifyAction: { service: 'shopifyActionsService', collection: 'shopifyactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
56
57
  slackAction: { service: 'slackActionsService', collection: 'slackactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
57
58
  googleContactsAction: { service: 'googleContactsActionsService', collection: 'googlecontactsactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
58
59
  googleAnalyticsAction: { service: 'googleAnalyticsActionsService', collection: 'googleanalyticsactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
package/dist/index.d.ts CHANGED
@@ -26,6 +26,8 @@ export { DISCORD_OPERATIONS, DISCORD_OPERATION_SPECS, isDiscordOperation, } from
26
26
  export type { DiscordOperation, DiscordParamSpec, DiscordOperationSpec, } from './discord-operations';
27
27
  export { MAILCHIMP_OPERATIONS, MAILCHIMP_OPERATION_SPECS, MAILCHIMP_CONTACT_STATUSES, isMailchimpOperation, } from './mailchimp-operations';
28
28
  export type { MailchimpOperation, MailchimpContactStatus, MailchimpParamSpec, MailchimpOperationSpec, } from './mailchimp-operations';
29
+ export { SHOPIFY_OPERATIONS, SHOPIFY_OPERATION_SPECS, SHOPIFY_TAGGABLE_RESOURCES, SHOPIFY_SEARCHABLE_RESOURCES, isShopifyOperation, } from './shopify-operations';
30
+ export type { ShopifyOperation, ShopifyTaggableResource, ShopifySearchableResource, ShopifyParamSpec, ShopifyOperationSpec, } from './shopify-operations';
29
31
  export { SLACK_OPERATIONS, SLACK_OPERATION_SPECS, isSlackOperation, } from './slack-operations';
30
32
  export type { SlackOperation, SlackParamSpec, SlackOperationSpec, } from './slack-operations';
31
33
  export { SHEETS_OPERATIONS, SHEETS_OPERATION_SPECS, isSheetsOperation, } from './sheets-operations';
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DRIVE_OPERATION_SPECS = exports.DRIVE_OPERATIONS = exports.GOOGLE_CALENDAR_TOOLKIT_BY_TOOL_NAME = exports.GOOGLE_CALENDAR_TOOLKIT_SPECS = exports.isGoogleCalendarOperation = exports.GOOGLE_CALENDAR_OPERATION_SPECS = exports.GOOGLE_CALENDAR_OPERATIONS = exports.isGmailOperation = exports.resolveGmailSendFields = exports.GMAIL_SEND_LEGACY_FIELDS = exports.NATIVE_EMAIL_TOOLKIT_BY_TOOL_NAME = exports.GMAIL_TOOLKIT_BY_TOOL_NAME = exports.NATIVE_EMAIL_TOOLKIT_SPECS = exports.GMAIL_SEND_AND_WAIT_TOOL_SPEC = exports.GMAIL_ALL_TOOLKIT_SPECS = exports.GMAIL_TOOLKIT_SPECS = exports.GMAIL_TOOLKIT_OPERATIONS = exports.GMAIL_DROPDOWN_OPERATIONS = exports.GMAIL_OPERATION_GROUPS = exports.GMAIL_OPERATION_SPECS = exports.GMAIL_OPERATIONS = exports.versionCatalogErrors = exports.fieldsLost = exports.fieldsLostBetween = exports.currentVersion = exports.versionSpec = exports.versionsOf = exports.isVersioned = exports.NODE_TYPE_TO_PREFIX = exports.PREFIX_TO_NODE_TYPE = exports.NODE_STATE_KEYS = exports.NODE_COLORS = exports.NODE_DETAIL_PATHS = exports.getNodeRegistryEntry = exports.NODE_REGISTRY = exports.getNodeDispatchConfig = exports.getAllNodeCollections = exports.NODE_DISPATCH = exports.resolveNodeId = exports.PREFIX_TO_TYPE = exports.NODE_UI = exports.ALL_NODE_TYPES = exports.isNodeType = exports.isTerminal = exports.canSendToNodes = exports.canReceiveFromNodes = exports.canReceiveFrom = exports.NODE_CONNECTIONS = exports.iterableMeta = exports.singleMeta = void 0;
4
- exports.DOCS_OPERATION_SPECS = exports.DOCS_OPERATIONS = exports.isMongoOperation = exports.MONGO_OPERATION_SPECS = exports.MONGO_OPERATIONS = exports.isPostgresOperation = exports.POSTGRES_OPERATION_SPECS = exports.POSTGRES_MODES = exports.POSTGRES_OPERATIONS = exports.isNotionOperation = exports.NOTION_DROPDOWN_OPERATIONS = exports.NOTION_OPERATION_SPECS = exports.NOTION_OPERATIONS = exports.isGoogleAnalyticsOperation = exports.GOOGLE_ANALYTICS_DROPDOWN_OPERATIONS = exports.GOOGLE_ANALYTICS_OPERATION_SPECS = exports.GOOGLE_ANALYTICS_OPERATIONS = exports.isGoogleContactsOperation = exports.GOOGLE_CONTACTS_DEFAULT_PERSON_FIELDS = exports.GOOGLE_CONTACTS_OPERATION_GROUPS = exports.GOOGLE_CONTACTS_OPERATION_SPECS = exports.GOOGLE_CONTACTS_OPERATIONS_V2 = exports.GOOGLE_CONTACTS_OPERATIONS_V1 = exports.GOOGLE_CONTACTS_OPERATIONS = exports.SHEETS_TOOLKIT_DEFAULTABLE = exports.SHEETS_TOOLKIT_BY_TOOL_NAME = exports.SHEETS_TOOLKIT_SPECS = exports.isSheetsOperation = exports.SHEETS_OPERATION_SPECS = exports.SHEETS_OPERATIONS = exports.isSlackOperation = exports.SLACK_OPERATION_SPECS = exports.SLACK_OPERATIONS = exports.isMailchimpOperation = exports.MAILCHIMP_CONTACT_STATUSES = exports.MAILCHIMP_OPERATION_SPECS = exports.MAILCHIMP_OPERATIONS = exports.isDiscordOperation = exports.DISCORD_OPERATION_SPECS = exports.DISCORD_OPERATIONS = exports.isWhatsAppOperation = exports.WHATSAPP_OPERATIONS = exports.TELEGRAM_TOOLKIT_BY_TOOL_NAME = exports.TELEGRAM_TOOLKIT_SPECS = exports.isTelegramOperation = exports.TELEGRAM_OPERATION_SPECS = exports.TELEGRAM_OPERATIONS = exports.DRIVE_TOOLKIT_BY_TOOL_NAME = exports.DRIVE_TOOLKIT_SPECS = exports.isDriveOperation = void 0;
5
- exports.isCredentialType = exports.getCredentialType = exports.credentialTypeValues = exports.CREDENTIAL_TYPE_VALUES = exports.CREDENTIAL_TYPES = exports.getModelLabel = exports.getDefaultModel = exports.getModelsFor = exports.MODEL_CONTEXT_WINDOWS = exports.LLM_MODELS = exports.LLM_PROVIDERS = exports.DOCS_TOOLKIT_DEFAULTABLE = exports.DOCS_TOOLKIT_BY_TOOL_NAME = exports.DOCS_TOOLKIT_SPECS = exports.isDocsOperation = void 0;
4
+ exports.isPostgresOperation = exports.POSTGRES_OPERATION_SPECS = exports.POSTGRES_MODES = exports.POSTGRES_OPERATIONS = exports.isNotionOperation = exports.NOTION_DROPDOWN_OPERATIONS = exports.NOTION_OPERATION_SPECS = exports.NOTION_OPERATIONS = exports.isGoogleAnalyticsOperation = exports.GOOGLE_ANALYTICS_DROPDOWN_OPERATIONS = exports.GOOGLE_ANALYTICS_OPERATION_SPECS = exports.GOOGLE_ANALYTICS_OPERATIONS = exports.isGoogleContactsOperation = exports.GOOGLE_CONTACTS_DEFAULT_PERSON_FIELDS = exports.GOOGLE_CONTACTS_OPERATION_GROUPS = exports.GOOGLE_CONTACTS_OPERATION_SPECS = exports.GOOGLE_CONTACTS_OPERATIONS_V2 = exports.GOOGLE_CONTACTS_OPERATIONS_V1 = exports.GOOGLE_CONTACTS_OPERATIONS = exports.SHEETS_TOOLKIT_DEFAULTABLE = exports.SHEETS_TOOLKIT_BY_TOOL_NAME = exports.SHEETS_TOOLKIT_SPECS = exports.isSheetsOperation = exports.SHEETS_OPERATION_SPECS = exports.SHEETS_OPERATIONS = exports.isSlackOperation = exports.SLACK_OPERATION_SPECS = exports.SLACK_OPERATIONS = exports.isShopifyOperation = exports.SHOPIFY_SEARCHABLE_RESOURCES = exports.SHOPIFY_TAGGABLE_RESOURCES = exports.SHOPIFY_OPERATION_SPECS = exports.SHOPIFY_OPERATIONS = exports.isMailchimpOperation = exports.MAILCHIMP_CONTACT_STATUSES = exports.MAILCHIMP_OPERATION_SPECS = exports.MAILCHIMP_OPERATIONS = exports.isDiscordOperation = exports.DISCORD_OPERATION_SPECS = exports.DISCORD_OPERATIONS = exports.isWhatsAppOperation = exports.WHATSAPP_OPERATIONS = exports.TELEGRAM_TOOLKIT_BY_TOOL_NAME = exports.TELEGRAM_TOOLKIT_SPECS = exports.isTelegramOperation = exports.TELEGRAM_OPERATION_SPECS = exports.TELEGRAM_OPERATIONS = exports.DRIVE_TOOLKIT_BY_TOOL_NAME = exports.DRIVE_TOOLKIT_SPECS = exports.isDriveOperation = void 0;
5
+ exports.isCredentialType = exports.getCredentialType = exports.credentialTypeValues = exports.CREDENTIAL_TYPE_VALUES = exports.CREDENTIAL_TYPES = exports.getModelLabel = exports.getDefaultModel = exports.getModelsFor = exports.MODEL_CONTEXT_WINDOWS = exports.LLM_MODELS = exports.LLM_PROVIDERS = exports.DOCS_TOOLKIT_DEFAULTABLE = exports.DOCS_TOOLKIT_BY_TOOL_NAME = exports.DOCS_TOOLKIT_SPECS = exports.isDocsOperation = exports.DOCS_OPERATION_SPECS = exports.DOCS_OPERATIONS = exports.isMongoOperation = exports.MONGO_OPERATION_SPECS = exports.MONGO_OPERATIONS = void 0;
6
6
  var types_1 = require("./types");
7
7
  Object.defineProperty(exports, "singleMeta", { enumerable: true, get: function () { return types_1.singleMeta; } });
8
8
  Object.defineProperty(exports, "iterableMeta", { enumerable: true, get: function () { return types_1.iterableMeta; } });
@@ -92,6 +92,12 @@ Object.defineProperty(exports, "MAILCHIMP_OPERATIONS", { enumerable: true, get:
92
92
  Object.defineProperty(exports, "MAILCHIMP_OPERATION_SPECS", { enumerable: true, get: function () { return mailchimp_operations_1.MAILCHIMP_OPERATION_SPECS; } });
93
93
  Object.defineProperty(exports, "MAILCHIMP_CONTACT_STATUSES", { enumerable: true, get: function () { return mailchimp_operations_1.MAILCHIMP_CONTACT_STATUSES; } });
94
94
  Object.defineProperty(exports, "isMailchimpOperation", { enumerable: true, get: function () { return mailchimp_operations_1.isMailchimpOperation; } });
95
+ var shopify_operations_1 = require("./shopify-operations");
96
+ Object.defineProperty(exports, "SHOPIFY_OPERATIONS", { enumerable: true, get: function () { return shopify_operations_1.SHOPIFY_OPERATIONS; } });
97
+ Object.defineProperty(exports, "SHOPIFY_OPERATION_SPECS", { enumerable: true, get: function () { return shopify_operations_1.SHOPIFY_OPERATION_SPECS; } });
98
+ Object.defineProperty(exports, "SHOPIFY_TAGGABLE_RESOURCES", { enumerable: true, get: function () { return shopify_operations_1.SHOPIFY_TAGGABLE_RESOURCES; } });
99
+ Object.defineProperty(exports, "SHOPIFY_SEARCHABLE_RESOURCES", { enumerable: true, get: function () { return shopify_operations_1.SHOPIFY_SEARCHABLE_RESOURCES; } });
100
+ Object.defineProperty(exports, "isShopifyOperation", { enumerable: true, get: function () { return shopify_operations_1.isShopifyOperation; } });
95
101
  var slack_operations_1 = require("./slack-operations");
96
102
  Object.defineProperty(exports, "SLACK_OPERATIONS", { enumerable: true, get: function () { return slack_operations_1.SLACK_OPERATIONS; } });
97
103
  Object.defineProperty(exports, "SLACK_OPERATION_SPECS", { enumerable: true, get: function () { return slack_operations_1.SLACK_OPERATION_SPECS; } });
package/dist/registry.js CHANGED
@@ -273,6 +273,16 @@ exports.NODE_REGISTRY = {
273
273
  // light canvas. The brand value (#ffe01b) reads as a highlighter there.
274
274
  color: '#c9a227', testable: true,
275
275
  },
276
+ shopifyAction: {
277
+ type: 'shopifyAction', prefix: 'shp', label: 'Shopify',
278
+ group: 'Actions', detailPath: '/dashboard/shopify-actions', apiPath: '/shopify-actions',
279
+ stateKey: 'shopifyActions', allStateKey: 'allShopifyActions',
280
+ // Shopify's green, abierto un punto. El de marca (#008060) es más oscuro
281
+ // que cualquier otro color de este registro y se hunde contra el lienzo
282
+ // en modo oscuro; éste sigue leyéndose como Shopify y aguanta en los dos
283
+ // temas. Mismo criterio que el amarillo de Mailchimp, al revés.
284
+ color: '#0a9e70', testable: true,
285
+ },
276
286
  telegramAction: {
277
287
  type: 'telegramAction', prefix: 'tg', label: 'Telegram Action',
278
288
  group: 'Actions', detailPath: '/dashboard/telegram-actions', apiPath: '/telegram-actions',
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Shopify Admin API operations — single source of truth across the api, the
3
+ * dashboard and downstream consumers (MCP server).
4
+ *
5
+ * ## Four, out of several hundred
6
+ *
7
+ * The Admin API has hundreds of mutations. This node exposes four, and the
8
+ * choice is argued in `api/docs/ADR-0007-shopify.md`: a menu of everything is
9
+ * how a node becomes unusable, and these four cover what people actually
10
+ * automate — put a customer in the store, tag something, sync stock, and read
11
+ * back what the store already knows.
12
+ *
13
+ * Product creation and order fulfilment were considered and left out of v1.
14
+ * A product means variants, media and prices; a fulfilment means a location, a
15
+ * fulfilment service and a state machine. Each is a configuration surface of
16
+ * its own, not a field.
17
+ *
18
+ * ## GraphQL only, and the version is not ours to drift on
19
+ *
20
+ * REST went legacy in October 2024 and public apps have been GraphQL-only
21
+ * since April 2025, so there is no REST path here to fall back to. Every
22
+ * `apiRoute` below is a GraphQL mutation or query name, checkable against
23
+ * `shopify.dev/docs/api/admin-graphql`. The API version lives in ONE constant
24
+ * on the api side (`SHOPIFY_API_VERSION`) — never interpolated per call site.
25
+ *
26
+ * ## What was verified, and what was not (2026-08-25)
27
+ *
28
+ * Checked against the Admin GraphQL reference, not from memory:
29
+ *
30
+ * - `tagsAdd(id: ID!, tags: [String!]!)` and `tagsRemove` with the same
31
+ * shape. Both accept Order, DraftOrder, Customer, Product, Article and
32
+ * DiscountNode.
33
+ * - `customerCreate(input: CustomerInput!)` and `customerUpdate(input:
34
+ * CustomerInput!)` — for the update, **the id goes inside the input**, not
35
+ * as a sibling argument.
36
+ * - `inventoryAdjustQuantities(input: InventoryAdjustQuantitiesInput!)`, whose
37
+ * input requires `reason`, `name` and a `changes[]` of
38
+ * `{ delta, inventoryItemId, locationId }`.
39
+ *
40
+ * ⚠️ **NOT verified, and deliberately left loose below** — do not turn either
41
+ * into a hard-coded dropdown until someone reads the enumeration:
42
+ *
43
+ * - The full vocabulary of `reason`. Only `"correction"` is documented by
44
+ * example.
45
+ * - The full vocabulary of `name`. Only `"available"` is documented by
46
+ * example; the docs mention `on_hand` moving as a consequence, which is not
47
+ * the same as it being a valid input.
48
+ * - The exact search syntax for finding a customer by email
49
+ * (`customers(query: "email:…")`). It is the obvious shape and almost
50
+ * certainly right, which is exactly why it is worth confirming rather than
51
+ * assuming.
52
+ *
53
+ * ⚠️ **`@idempotent` is mandatory.** As of API version 2026-04,
54
+ * `inventoryAdjustQuantities` must be sent with the `@idempotent` directive
55
+ * and an idempotency key. A stock adjustment is the one operation here where a
56
+ * silent retry is a real inventory error, so this is not boilerplate.
57
+ */
58
+ export declare const SHOPIFY_OPERATIONS: readonly ["upsertCustomer", "setTags", "adjustInventory", "findRecords"];
59
+ export type ShopifyOperation = (typeof SHOPIFY_OPERATIONS)[number];
60
+ /** Type guard — for DTOs and AI tool calls, where the input is untrusted. */
61
+ export declare function isShopifyOperation(value: unknown): value is ShopifyOperation;
62
+ /**
63
+ * The resources `tagsAdd` / `tagsRemove` accept. Taken from the mutation
64
+ * reference — this list is the API's, not a subset we chose.
65
+ *
66
+ * `article` and `discountNode` are left out of the node's own picker on
67
+ * purpose: neither is something a HostWebhook flow tags in practice, and every
68
+ * extra option is a field the user has to read past.
69
+ */
70
+ export declare const SHOPIFY_TAGGABLE_RESOURCES: readonly ["order", "customer", "product", "draftOrder"];
71
+ export type ShopifyTaggableResource = (typeof SHOPIFY_TAGGABLE_RESOURCES)[number];
72
+ /** What `findRecords` can read back. */
73
+ export declare const SHOPIFY_SEARCHABLE_RESOURCES: readonly ["orders", "customers", "products"];
74
+ export type ShopifySearchableResource = (typeof SHOPIFY_SEARCHABLE_RESOURCES)[number];
75
+ export interface ShopifyParamSpec {
76
+ /** Field key — also the property name on operationConfig. */
77
+ name: string;
78
+ label: string;
79
+ /** Param type, so the dashboard renders the right control. `location` is a
80
+ * picker backed by a live lookup of the store's locations; `gid` is a
81
+ * Shopify global id, which is almost always templated from the payload
82
+ * rather than typed. */
83
+ type: 'gid' | 'location' | 'email' | 'string' | 'number' | 'tags' | 'taggableResource' | 'searchableResource' | 'json' | 'boolean';
84
+ required?: boolean;
85
+ description: string;
86
+ placeholder?: string;
87
+ }
88
+ export interface ShopifyOperationSpec {
89
+ label: string;
90
+ description: string;
91
+ /** The GraphQL operation this maps to, so the mapping is checkable. */
92
+ apiRoute: string;
93
+ params: ShopifyParamSpec[];
94
+ }
95
+ export declare const SHOPIFY_OPERATION_SPECS: Record<ShopifyOperation, ShopifyOperationSpec>;
@@ -0,0 +1,263 @@
1
+ "use strict";
2
+ /**
3
+ * Shopify Admin API operations — single source of truth across the api, the
4
+ * dashboard and downstream consumers (MCP server).
5
+ *
6
+ * ## Four, out of several hundred
7
+ *
8
+ * The Admin API has hundreds of mutations. This node exposes four, and the
9
+ * choice is argued in `api/docs/ADR-0007-shopify.md`: a menu of everything is
10
+ * how a node becomes unusable, and these four cover what people actually
11
+ * automate — put a customer in the store, tag something, sync stock, and read
12
+ * back what the store already knows.
13
+ *
14
+ * Product creation and order fulfilment were considered and left out of v1.
15
+ * A product means variants, media and prices; a fulfilment means a location, a
16
+ * fulfilment service and a state machine. Each is a configuration surface of
17
+ * its own, not a field.
18
+ *
19
+ * ## GraphQL only, and the version is not ours to drift on
20
+ *
21
+ * REST went legacy in October 2024 and public apps have been GraphQL-only
22
+ * since April 2025, so there is no REST path here to fall back to. Every
23
+ * `apiRoute` below is a GraphQL mutation or query name, checkable against
24
+ * `shopify.dev/docs/api/admin-graphql`. The API version lives in ONE constant
25
+ * on the api side (`SHOPIFY_API_VERSION`) — never interpolated per call site.
26
+ *
27
+ * ## What was verified, and what was not (2026-08-25)
28
+ *
29
+ * Checked against the Admin GraphQL reference, not from memory:
30
+ *
31
+ * - `tagsAdd(id: ID!, tags: [String!]!)` and `tagsRemove` with the same
32
+ * shape. Both accept Order, DraftOrder, Customer, Product, Article and
33
+ * DiscountNode.
34
+ * - `customerCreate(input: CustomerInput!)` and `customerUpdate(input:
35
+ * CustomerInput!)` — for the update, **the id goes inside the input**, not
36
+ * as a sibling argument.
37
+ * - `inventoryAdjustQuantities(input: InventoryAdjustQuantitiesInput!)`, whose
38
+ * input requires `reason`, `name` and a `changes[]` of
39
+ * `{ delta, inventoryItemId, locationId }`.
40
+ *
41
+ * ⚠️ **NOT verified, and deliberately left loose below** — do not turn either
42
+ * into a hard-coded dropdown until someone reads the enumeration:
43
+ *
44
+ * - The full vocabulary of `reason`. Only `"correction"` is documented by
45
+ * example.
46
+ * - The full vocabulary of `name`. Only `"available"` is documented by
47
+ * example; the docs mention `on_hand` moving as a consequence, which is not
48
+ * the same as it being a valid input.
49
+ * - The exact search syntax for finding a customer by email
50
+ * (`customers(query: "email:…")`). It is the obvious shape and almost
51
+ * certainly right, which is exactly why it is worth confirming rather than
52
+ * assuming.
53
+ *
54
+ * ⚠️ **`@idempotent` is mandatory.** As of API version 2026-04,
55
+ * `inventoryAdjustQuantities` must be sent with the `@idempotent` directive
56
+ * and an idempotency key. A stock adjustment is the one operation here where a
57
+ * silent retry is a real inventory error, so this is not boilerplate.
58
+ */
59
+ Object.defineProperty(exports, "__esModule", { value: true });
60
+ exports.SHOPIFY_OPERATION_SPECS = exports.SHOPIFY_SEARCHABLE_RESOURCES = exports.SHOPIFY_TAGGABLE_RESOURCES = exports.SHOPIFY_OPERATIONS = void 0;
61
+ exports.isShopifyOperation = isShopifyOperation;
62
+ exports.SHOPIFY_OPERATIONS = [
63
+ 'upsertCustomer',
64
+ 'setTags',
65
+ 'adjustInventory',
66
+ 'findRecords',
67
+ ];
68
+ /** Type guard — for DTOs and AI tool calls, where the input is untrusted. */
69
+ function isShopifyOperation(value) {
70
+ return (typeof value === 'string' &&
71
+ exports.SHOPIFY_OPERATIONS.includes(value));
72
+ }
73
+ /**
74
+ * The resources `tagsAdd` / `tagsRemove` accept. Taken from the mutation
75
+ * reference — this list is the API's, not a subset we chose.
76
+ *
77
+ * `article` and `discountNode` are left out of the node's own picker on
78
+ * purpose: neither is something a HostWebhook flow tags in practice, and every
79
+ * extra option is a field the user has to read past.
80
+ */
81
+ exports.SHOPIFY_TAGGABLE_RESOURCES = [
82
+ 'order',
83
+ 'customer',
84
+ 'product',
85
+ 'draftOrder',
86
+ ];
87
+ /** What `findRecords` can read back. */
88
+ exports.SHOPIFY_SEARCHABLE_RESOURCES = [
89
+ 'orders',
90
+ 'customers',
91
+ 'products',
92
+ ];
93
+ exports.SHOPIFY_OPERATION_SPECS = {
94
+ upsertCustomer: {
95
+ label: 'Add or update customer',
96
+ description: 'Look the customer up by email, then create them or update the fields you set.',
97
+ apiRoute: 'mutation customerCreate / customerUpdate (lookup by email first)',
98
+ params: [
99
+ {
100
+ name: 'email',
101
+ label: 'Email address',
102
+ type: 'email',
103
+ required: true,
104
+ description: 'Identifies the customer. This is what decides between creating and updating — there is no separate "id" to keep.',
105
+ placeholder: '{{payload.email}}',
106
+ },
107
+ {
108
+ name: 'firstName',
109
+ label: 'First name',
110
+ type: 'string',
111
+ description: 'Left untouched on an existing customer if you leave it empty.',
112
+ placeholder: '{{payload.firstName}}',
113
+ },
114
+ {
115
+ name: 'lastName',
116
+ label: 'Last name',
117
+ type: 'string',
118
+ description: 'Left untouched on an existing customer if you leave it empty.',
119
+ placeholder: '{{payload.lastName}}',
120
+ },
121
+ {
122
+ name: 'phone',
123
+ label: 'Phone',
124
+ type: 'string',
125
+ description: 'Shopify wants it in E.164 (+52…). A malformed number fails the whole mutation, not just the field.',
126
+ },
127
+ {
128
+ name: 'tags',
129
+ label: 'Tags',
130
+ type: 'tags',
131
+ description: 'Tags to set on the customer. Note these REPLACE the customer tags — to add without losing the existing ones, use the tag operation instead.',
132
+ },
133
+ {
134
+ name: 'note',
135
+ label: 'Note',
136
+ type: 'string',
137
+ description: 'Internal note, visible to staff in the Shopify admin.',
138
+ },
139
+ {
140
+ name: 'emailMarketingConsent',
141
+ label: 'Email marketing consent',
142
+ type: 'json',
143
+ description: 'Leave this EMPTY unless the customer actually consented. Omitting it leaves their consent exactly as it was; writing a subscribed state for someone who never opted in is a legal problem, not a technical one. Shape: {"marketingState":"SUBSCRIBED","marketingOptInLevel":"SINGLE_OPT_IN"}.',
144
+ },
145
+ ],
146
+ },
147
+ setTags: {
148
+ label: 'Add or remove tags',
149
+ description: 'Tag an order, a customer or a product. Adding and removing are separate calls, so one node does one of the two.',
150
+ apiRoute: 'mutation tagsAdd / tagsRemove',
151
+ params: [
152
+ {
153
+ name: 'resource',
154
+ label: 'What to tag',
155
+ type: 'taggableResource',
156
+ required: true,
157
+ description: 'Shopify tags many resource types through the same mutation; this only picks which id you are passing.',
158
+ },
159
+ {
160
+ name: 'resourceId',
161
+ label: 'Resource id',
162
+ type: 'gid',
163
+ required: true,
164
+ description: 'The Shopify global id, e.g. gid://shopify/Order/1234. A Shopify trigger gives you this directly in the payload.',
165
+ placeholder: '{{payload.admin_graphql_api_id}}',
166
+ },
167
+ {
168
+ name: 'tags',
169
+ label: 'Tags',
170
+ type: 'tags',
171
+ required: true,
172
+ description: 'One or more tags.',
173
+ },
174
+ {
175
+ name: 'remove',
176
+ label: 'Remove instead of add',
177
+ type: 'boolean',
178
+ description: 'Off adds the tags (tagsAdd); on removes them (tagsRemove). Unlike Mailchimp, Shopify cannot do both in one call.',
179
+ },
180
+ ],
181
+ },
182
+ adjustInventory: {
183
+ label: 'Adjust inventory',
184
+ description: 'Move stock by a delta at one location. For the case where the real stock lives in another system.',
185
+ apiRoute: 'mutation inventoryAdjustQuantities',
186
+ params: [
187
+ {
188
+ name: 'inventoryItemId',
189
+ label: 'Inventory item id',
190
+ type: 'gid',
191
+ required: true,
192
+ description: 'gid://shopify/InventoryItem/1234. This is the inventory item, NOT the product or the variant — a variant points at one.',
193
+ placeholder: '{{payload.inventory_item_id}}',
194
+ },
195
+ {
196
+ name: 'locationId',
197
+ label: 'Location',
198
+ type: 'location',
199
+ required: true,
200
+ description: 'Which location the stock moves at. A store with one location still has to name it.',
201
+ },
202
+ {
203
+ name: 'delta',
204
+ label: 'Change by',
205
+ type: 'number',
206
+ required: true,
207
+ description: 'A DELTA, not a total. 5 adds five units and -5 removes five. Sending the absolute stock level here is the mistake that silently multiplies inventory.',
208
+ placeholder: '{{payload.delta}}',
209
+ },
210
+ {
211
+ name: 'name',
212
+ label: 'Quantity type',
213
+ type: 'string',
214
+ required: true,
215
+ description: 'Which quantity is being changed. "available" is the documented value; check the reference before using another.',
216
+ placeholder: 'available',
217
+ },
218
+ {
219
+ name: 'reason',
220
+ label: 'Reason',
221
+ type: 'string',
222
+ required: true,
223
+ description: 'Why the stock moved — it lands in the store\'s inventory history. "correction" is the documented value; check the reference before using another.',
224
+ placeholder: 'correction',
225
+ },
226
+ {
227
+ name: 'referenceDocumentUri',
228
+ label: 'Reference',
229
+ type: 'string',
230
+ description: 'Optional audit-trail link back to whatever caused the change, so someone reading Shopify\'s inventory history can find it.',
231
+ },
232
+ ],
233
+ },
234
+ findRecords: {
235
+ label: 'Find orders, customers or products',
236
+ description: 'Read back what the store knows, to enrich a payload that came from somewhere else.',
237
+ apiRoute: 'query orders / customers / products (with search syntax)',
238
+ params: [
239
+ {
240
+ name: 'resource',
241
+ label: 'What to look for',
242
+ type: 'searchableResource',
243
+ required: true,
244
+ description: 'Which collection to search.',
245
+ },
246
+ {
247
+ name: 'query',
248
+ label: 'Search',
249
+ type: 'string',
250
+ required: true,
251
+ description: 'Shopify search syntax, e.g. email:someone@example.com or financial_status:paid. Empty would return the newest records, which is rarely what anyone means.',
252
+ placeholder: 'email:{{payload.email}}',
253
+ },
254
+ {
255
+ name: 'first',
256
+ label: 'How many',
257
+ type: 'number',
258
+ description: 'Defaults to 1, which is the enrichment case. Asking for many costs more against the query-cost budget, not more calls.',
259
+ placeholder: '1',
260
+ },
261
+ ],
262
+ },
263
+ };
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** All valid node type identifiers */
2
- export type NodeType = 'webhook' | 'scheduledWorkflow' | 'chatTrigger' | 'trigger' | 'voiceAgent' | 'filter' | 'transform' | 'schemaValidator' | 'conditional' | 'delay' | 'rateLimiter' | 'aggregator' | 'cache' | 'code' | 'ai' | 'merge' | 'approval' | 'split' | 'loop' | 'markdown' | 'fileTransform' | 'limit' | 'router' | 'emailAction' | 'gmailAction' | 'httpAction' | 'mongoAction' | 'postgresAction' | 'notificationAction' | 'sheetsAction' | 'calendarAction' | 'docsAction' | 'driveAction' | 'firecrawlAction' | 'telegramAction' | 'whatsappAction' | 'discordAction' | 'slackAction' | 'googleContactsAction' | 'googleAnalyticsAction' | 'notionAction' | 'vectorStore' | 'rssAction' | 'socialMediaAction' | 'mailchimpAction' | 'stickyNote';
2
+ export type NodeType = 'webhook' | 'scheduledWorkflow' | 'chatTrigger' | 'trigger' | 'voiceAgent' | 'filter' | 'transform' | 'schemaValidator' | 'conditional' | 'delay' | 'rateLimiter' | 'aggregator' | 'cache' | 'code' | 'ai' | 'merge' | 'approval' | 'split' | 'loop' | 'markdown' | 'fileTransform' | 'limit' | 'router' | 'emailAction' | 'gmailAction' | 'httpAction' | 'mongoAction' | 'postgresAction' | 'notificationAction' | 'sheetsAction' | 'calendarAction' | 'docsAction' | 'driveAction' | 'firecrawlAction' | 'telegramAction' | 'whatsappAction' | 'discordAction' | 'slackAction' | 'googleContactsAction' | 'googleAnalyticsAction' | 'notionAction' | 'vectorStore' | 'rssAction' | 'socialMediaAction' | 'mailchimpAction' | 'shopifyAction' | 'stickyNote';
3
3
  /** Node role in the pipeline */
4
4
  export type NodeRole = 'source' | 'processing' | 'flowControl' | 'routing' | 'action' | 'monitoring';
5
5
  /** Handle positions on the canvas node */
package/dist/ui.js CHANGED
@@ -76,6 +76,7 @@ exports.NODE_UI = {
76
76
  whatsappAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'whatsapp' } },
77
77
  discordAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'discord' } },
78
78
  mailchimpAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'mailchimp' } },
79
+ shopifyAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'shopify' } },
79
80
  slackAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'slack' } },
80
81
  googleContactsAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
81
82
  googleAnalyticsAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
@@ -100,6 +101,7 @@ exports.PREFIX_TO_TYPE = [
100
101
  { prefix: 'dc-', type: 'discordAction', canvasType: 'discordAction' },
101
102
  { prefix: 'sl-', type: 'slackAction', canvasType: 'slackAction' },
102
103
  { prefix: 'mc-', type: 'mailchimpAction', canvasType: 'mailchimpAction' },
104
+ { prefix: 'shp-', type: 'shopifyAction', canvasType: 'shopifyAction' },
103
105
  { prefix: 'gc-', type: 'googleContactsAction', canvasType: 'googleContactsAction' },
104
106
  { prefix: 'gaa-', type: 'googleAnalyticsAction', canvasType: 'googleAnalyticsAction' },
105
107
  { prefix: 'ntn-', type: 'notionAction', canvasType: 'notionAction' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostwebhook/node-types",
3
- "version": "1.62.0",
3
+ "version": "1.63.0",
4
4
  "description": "Shared node type definitions, connection rules, and dispatch config for HostWebhook",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",