@hostwebhook/node-types 1.73.0 → 1.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/shopify-operations.d.ts +112 -11
- package/dist/esm/shopify-operations.js +413 -9
- package/dist/esm/types.d.ts +51 -1
- package/dist/esm/ui.js +20 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -3
- package/dist/shopify-operations.d.ts +112 -11
- package/dist/shopify-operations.js +415 -10
- package/dist/types.d.ts +51 -1
- package/dist/ui.js +20 -2
- package/package.json +1 -1
package/dist/esm/types.d.ts
CHANGED
|
@@ -38,7 +38,20 @@ export interface NodeUIConfig {
|
|
|
38
38
|
hasBranches?: boolean;
|
|
39
39
|
isTerminal?: boolean;
|
|
40
40
|
hasLoopBack?: boolean;
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Aquí había también un `'rule'`, sólo para el router, y con él vivía en
|
|
43
|
+
* el lienzo un camino de conexión a medida: su propio modal, su propio
|
|
44
|
+
* `handleRuleSelect` y su propia puerta en `onConnect`. Dos mecanismos
|
|
45
|
+
* para lo mismo, y así es como uno se arregla y el otro no: el modal del
|
|
46
|
+
* router salía al ARRASTRAR y nunca al pulsar el `+`, porque ese segundo
|
|
47
|
+
* camino pasa por `outputGroups` — que el router no declaraba.
|
|
48
|
+
*
|
|
49
|
+
* El router ramifica igual que el conditional, el split y el clasificador,
|
|
50
|
+
* y su destino vive en el mismo sitio que el de ellos (`port: rule:<id>`),
|
|
51
|
+
* así que usa la misma maquinaria. Se quita del tipo para que no pueda
|
|
52
|
+
* volver a declararse sin que el compilador lo diga.
|
|
53
|
+
*/
|
|
54
|
+
connectModal?: 'branch' | 'output-group';
|
|
42
55
|
routerTargetLabel?: string;
|
|
43
56
|
/**
|
|
44
57
|
* Generic named output groups — used by connectModal: 'output-group'.
|
|
@@ -54,6 +67,20 @@ export interface NodeUIConfig {
|
|
|
54
67
|
nameField?: string;
|
|
55
68
|
/** Field within each group for summary/description (e.g. 'field', 'filterMode') */
|
|
56
69
|
summaryField?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Varios campos del grupo, unidos por espacios, cuando el resumen que
|
|
72
|
+
* de verdad identifica al grupo no cabe en uno solo.
|
|
73
|
+
*
|
|
74
|
+
* Existe por las reglas del router: su condición son TRES campos
|
|
75
|
+
* (`field`, `operator`, `value`) y el modal a medida que se retiró las
|
|
76
|
+
* pintaba juntas — «status eq active». Con un único `summaryField` una
|
|
77
|
+
* regla sin `label` quedaba en «rule 1» y «status», que no dice a qué
|
|
78
|
+
* se está conectando uno.
|
|
79
|
+
*
|
|
80
|
+
* Los campos vacíos se saltan, para que una regla con operador
|
|
81
|
+
* `exists` no salga con un espacio colgando al final.
|
|
82
|
+
*/
|
|
83
|
+
summaryFields?: string[];
|
|
57
84
|
/** Label shown in the modal (e.g. 'output', 'branch') */
|
|
58
85
|
label: string;
|
|
59
86
|
/** Entity field for the fallback/else path (e.g. 'elseOutputNodes') */
|
|
@@ -80,6 +107,29 @@ export interface NodeUIConfig {
|
|
|
80
107
|
field: string;
|
|
81
108
|
value: unknown;
|
|
82
109
|
};
|
|
110
|
+
/**
|
|
111
|
+
* Qué es el puerto `main` de este nodo MIENTRAS su contenedor está
|
|
112
|
+
* vacío. Sin esto, conectar a un nodo que ramifica y todavía no tiene
|
|
113
|
+
* ninguna salida escribía una arista en `main` **en silencio**, y el
|
|
114
|
+
* usuario no tenía forma de saber qué había pasado con ella.
|
|
115
|
+
*
|
|
116
|
+
* No hay una respuesta única, y por eso es un dato y no una regla.
|
|
117
|
+
* Medido en `api/src/common/pipeline-run.service.ts`:
|
|
118
|
+
*
|
|
119
|
+
* 'passthrough' — router sin reglas: `rules.length === 0
|
|
120
|
+
* ? refsForPort(entity, MAIN_PORT) : []`. La
|
|
121
|
+
* arista SÍ dispara... hasta que se cree la
|
|
122
|
+
* primera regla, y entonces deja de hacerlo
|
|
123
|
+
* sin avisar.
|
|
124
|
+
* 'never-dispatched' — split: no tiene salida `main`, y el propio
|
|
125
|
+
* despachador registra un warning por cada
|
|
126
|
+
* arista que se quedó en ese puerto.
|
|
127
|
+
*
|
|
128
|
+
* Se declara sólo donde se ha medido. Sin declarar, el lienzo se
|
|
129
|
+
* comporta como siempre: conecta por el camino universal y no dice
|
|
130
|
+
* nada, que es lo que hacía antes de existir este campo.
|
|
131
|
+
*/
|
|
132
|
+
emptyContainerMain?: 'passthrough' | 'never-dispatched';
|
|
83
133
|
};
|
|
84
134
|
/**
|
|
85
135
|
* Maps output fields to alternate payload fields on the source entity.
|
package/dist/esm/ui.js
CHANGED
|
@@ -50,12 +50,30 @@ export const NODE_UI = {
|
|
|
50
50
|
fileTransform: { fromNodes: true, toNodes: true },
|
|
51
51
|
limit: { fromNodes: true, toNodes: true, special: { loopBackAllowed: true } },
|
|
52
52
|
// ── Flow control nodes ──
|
|
53
|
-
|
|
53
|
+
// `emptyContainerMain: 'never-dispatched'` dice lo que el despachador ya
|
|
54
|
+
// registra como warning: un split NO tiene salida `main`, así que una arista
|
|
55
|
+
// que se quede ahí —la que se guardaba al conectar un split sin salidas— no
|
|
56
|
+
// se despacha nunca. Ver `pipeline-run.service.ts`, el bloque `nodeType ===
|
|
57
|
+
// 'split'`. El lienzo lo usa para avisar en vez de callarse.
|
|
58
|
+
split: { fromNodes: true, toNodes: true, outputHandles: ['right'], special: { connectModal: 'output-group', outputGroups: { field: 'outputs', nameField: 'name', summaryField: 'field', label: 'output', emptyContainerMain: 'never-dispatched' } } },
|
|
54
59
|
merge: { fromNodes: true, toNodes: true, inputHandles: ['left', 'top'] },
|
|
55
60
|
approval: { fromNodes: true, toNodes: true, outputHandles: ['right'], special: { connectModal: 'output-group', outputGroups: { fixedGroups: [{ name: 'Approve', field: 'outputNodes' }, { name: 'Reject', field: 'rejectionOutputNodes' }], label: 'action' } } },
|
|
56
61
|
loop: { fromNodes: true, toNodes: true, inputHandles: ['left'], outputHandles: ['right'], dotHandles: { input: ['left-in'], output: ['right-out-loop', 'right-in-loopback', 'right-out-done'] }, special: { hasLoopBack: true, alternatePayloads: { doneOutputNodes: 'lastDonePayload' } } },
|
|
57
62
|
// ── Routing nodes ──
|
|
58
|
-
|
|
63
|
+
// El router ramifica igual que el conditional, el split y el clasificador:
|
|
64
|
+
// sus salidas son un contenedor (`rules[]`) y el destino de cada una vive en
|
|
65
|
+
// la arista, en `port: rule:<id>`. Lo único que le faltaba era DECIRLO aquí.
|
|
66
|
+
//
|
|
67
|
+
// Mientras no lo decía, tenía en el lienzo un camino propio —`connectModal:
|
|
68
|
+
// 'rule'`, con su modal, su `handleRuleSelect` y su puerta en `onConnect`— y
|
|
69
|
+
// eso dejaba el `+` fuera: ese camino no pasa por `onConnect`, pregunta por
|
|
70
|
+
// `outputGroups`, y el router no tenía. Arrastrando salía el modal; pulsando
|
|
71
|
+
// el `+` la arista se guardaba en `main` sin preguntar nada.
|
|
72
|
+
//
|
|
73
|
+
// `nameField: 'label'` porque el `label` de una regla es opcional; cuando
|
|
74
|
+
// falta, el modal numera («rule 1») y el `summaryFields` de al lado pone la
|
|
75
|
+
// condición, que es lo que de verdad la identifica.
|
|
76
|
+
router: { fromNodes: true, toNodes: true, outputHandles: ['right'], dotHandles: { output: ['right-out'] }, special: { connectModal: 'output-group', outputGroups: { field: 'rules', nameField: 'label', summaryFields: ['field', 'operator', 'value'], label: 'rule', emptyContainerMain: 'passthrough' } } },
|
|
59
77
|
// ── Action nodes ──
|
|
60
78
|
emailAction: { fromNodes: true, toNodes: true, inputHandles: ['left', 'top'], outputHandles: ['right'], dotHandles: { input: ['left-in', 'top-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'email' } },
|
|
61
79
|
gmailAction: { fromNodes: true, toNodes: true, inputHandles: ['left', 'top'], outputHandles: ['right'], dotHandles: { input: ['left-in', 'top-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'gmail' } },
|
package/dist/index.d.ts
CHANGED
|
@@ -30,8 +30,8 @@ export { DISCORD_TOOLKIT_SPECS, DISCORD_TOOLKIT_BY_TOOL_NAME, herramientasDeDisc
|
|
|
30
30
|
export type { DiscordToolkitSpec, DiscordToolkitParameter, } from './discord-toolkit.js';
|
|
31
31
|
export { MAILCHIMP_OPERATIONS, MAILCHIMP_OPERATION_SPECS, MAILCHIMP_CONTACT_STATUSES, isMailchimpOperation, } from './mailchimp-operations.js';
|
|
32
32
|
export type { MailchimpOperation, MailchimpContactStatus, MailchimpParamSpec, MailchimpOperationSpec, } from './mailchimp-operations.js';
|
|
33
|
-
export { SHOPIFY_OPERATIONS, SHOPIFY_OPERATION_SPECS, SHOPIFY_TAGGABLE_RESOURCES, SHOPIFY_SEARCHABLE_RESOURCES, isShopifyOperation, } from './shopify-operations.js';
|
|
34
|
-
export type { ShopifyOperation, ShopifyTaggableResource, ShopifySearchableResource, ShopifyParamSpec, ShopifyOperationSpec, } from './shopify-operations.js';
|
|
33
|
+
export { SHOPIFY_OPERATIONS, SHOPIFY_OPERATION_SPECS, SHOPIFY_OPERATION_SCOPES, SHOPIFY_TAGGABLE_RESOURCES, SHOPIFY_SEARCHABLE_RESOURCES, SHOPIFY_CANCEL_REASONS, SHOPIFY_PRODUCT_STATUSES, isShopifyOperation, scopesQueFaltanEnShopify, } from './shopify-operations.js';
|
|
34
|
+
export type { ShopifyOperation, ShopifyTaggableResource, ShopifySearchableResource, ShopifyCancelReason, ShopifyProductStatus, ShopifyParamSpec, ShopifyOperationSpec, } from './shopify-operations.js';
|
|
35
35
|
export { GITHUB_OPERATIONS, GITHUB_OPERATION_SPECS, GITHUB_DROPDOWN_OPERATIONS, GITHUB_ITERABLE_OPERATIONS, isGithubOperation, } from './github-operations.js';
|
|
36
36
|
export type { GithubOperation, GithubParamType, GithubParamSpec, GithubOperationSpec, } from './github-operations.js';
|
|
37
37
|
export { JIRA_OPERATIONS, JIRA_OPERATION_SPECS, JIRA_DROPDOWN_OPERATIONS, JIRA_ITERABLE_OPERATIONS, isJiraOperation, } from './jira-operations.js';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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.
|
|
5
|
-
exports.
|
|
6
|
-
exports.isCredentialType = exports.getCredentialType = exports.credentialTypeValues = exports.CREDENTIAL_TYPE_VALUES = void 0;
|
|
4
|
+
exports.isBucketOperation = exports.BUCKET_ITERABLE_OPERATIONS = exports.BUCKET_OPERATION_SPECS = exports.BUCKET_OPERATIONS = exports.isJiraOperation = exports.JIRA_ITERABLE_OPERATIONS = exports.JIRA_DROPDOWN_OPERATIONS = exports.JIRA_OPERATION_SPECS = exports.JIRA_OPERATIONS = exports.isGithubOperation = exports.GITHUB_ITERABLE_OPERATIONS = exports.GITHUB_DROPDOWN_OPERATIONS = exports.GITHUB_OPERATION_SPECS = exports.GITHUB_OPERATIONS = exports.scopesQueFaltanEnShopify = exports.isShopifyOperation = exports.SHOPIFY_PRODUCT_STATUSES = exports.SHOPIFY_CANCEL_REASONS = exports.SHOPIFY_SEARCHABLE_RESOURCES = exports.SHOPIFY_TAGGABLE_RESOURCES = exports.SHOPIFY_OPERATION_SCOPES = exports.SHOPIFY_OPERATION_SPECS = exports.SHOPIFY_OPERATIONS = exports.isMailchimpOperation = exports.MAILCHIMP_CONTACT_STATUSES = exports.MAILCHIMP_OPERATION_SPECS = exports.MAILCHIMP_OPERATIONS = exports.herramientasDeDiscordPara = exports.DISCORD_TOOLKIT_BY_TOOL_NAME = exports.DISCORD_TOOLKIT_SPECS = exports.isDiscordOperation = exports.camposDeDiscordNoDisponibles = exports.discordPuedeEjecutar = exports.operacionesDeDiscordPara = exports.DISCORD_CAPACIDADES_POR_CREDENCIAL = exports.DISCORD_OPERATION_SPECS = exports.DISCORD_OPERATIONS = exports.camposNoDisponiblesPara = exports.puedeEjecutar = exports.operacionesPara = 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.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 = 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.herramientasDeSlackPara = exports.SLACK_TOOLKIT_BY_TOOL_NAME = exports.SLACK_TOOLKIT_SPECS = exports.isSlackOperation = exports.camposDeSlackNoDisponibles = exports.slackPuedeEjecutar = exports.operacionesDeSlackPara = exports.SLACK_CAPACIDADES_POR_CREDENCIAL = exports.SLACK_OPERATION_SPECS = exports.SLACK_OPERATIONS = void 0;
|
|
6
|
+
exports.isCredentialType = exports.getCredentialType = exports.credentialTypeValues = exports.CREDENTIAL_TYPE_VALUES = exports.CREDENTIAL_TYPES = exports.ventanaDeContextoDeOpenRouter = exports.opcionesDeModelosDeOpenRouter = exports.URL_DE_MODELOS_DE_OPENROUTER = void 0;
|
|
7
7
|
var types_js_1 = require("./types.js");
|
|
8
8
|
Object.defineProperty(exports, "singleMeta", { enumerable: true, get: function () { return types_js_1.singleMeta; } });
|
|
9
9
|
Object.defineProperty(exports, "iterableMeta", { enumerable: true, get: function () { return types_js_1.iterableMeta; } });
|
|
@@ -115,9 +115,13 @@ Object.defineProperty(exports, "isMailchimpOperation", { enumerable: true, get:
|
|
|
115
115
|
var shopify_operations_js_1 = require("./shopify-operations.js");
|
|
116
116
|
Object.defineProperty(exports, "SHOPIFY_OPERATIONS", { enumerable: true, get: function () { return shopify_operations_js_1.SHOPIFY_OPERATIONS; } });
|
|
117
117
|
Object.defineProperty(exports, "SHOPIFY_OPERATION_SPECS", { enumerable: true, get: function () { return shopify_operations_js_1.SHOPIFY_OPERATION_SPECS; } });
|
|
118
|
+
Object.defineProperty(exports, "SHOPIFY_OPERATION_SCOPES", { enumerable: true, get: function () { return shopify_operations_js_1.SHOPIFY_OPERATION_SCOPES; } });
|
|
118
119
|
Object.defineProperty(exports, "SHOPIFY_TAGGABLE_RESOURCES", { enumerable: true, get: function () { return shopify_operations_js_1.SHOPIFY_TAGGABLE_RESOURCES; } });
|
|
119
120
|
Object.defineProperty(exports, "SHOPIFY_SEARCHABLE_RESOURCES", { enumerable: true, get: function () { return shopify_operations_js_1.SHOPIFY_SEARCHABLE_RESOURCES; } });
|
|
121
|
+
Object.defineProperty(exports, "SHOPIFY_CANCEL_REASONS", { enumerable: true, get: function () { return shopify_operations_js_1.SHOPIFY_CANCEL_REASONS; } });
|
|
122
|
+
Object.defineProperty(exports, "SHOPIFY_PRODUCT_STATUSES", { enumerable: true, get: function () { return shopify_operations_js_1.SHOPIFY_PRODUCT_STATUSES; } });
|
|
120
123
|
Object.defineProperty(exports, "isShopifyOperation", { enumerable: true, get: function () { return shopify_operations_js_1.isShopifyOperation; } });
|
|
124
|
+
Object.defineProperty(exports, "scopesQueFaltanEnShopify", { enumerable: true, get: function () { return shopify_operations_js_1.scopesQueFaltanEnShopify; } });
|
|
121
125
|
var github_operations_js_1 = require("./github-operations.js");
|
|
122
126
|
Object.defineProperty(exports, "GITHUB_OPERATIONS", { enumerable: true, get: function () { return github_operations_js_1.GITHUB_OPERATIONS; } });
|
|
123
127
|
Object.defineProperty(exports, "GITHUB_OPERATION_SPECS", { enumerable: true, get: function () { return github_operations_js_1.GITHUB_OPERATION_SPECS; } });
|
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
* Shopify Admin API operations — single source of truth across the api, the
|
|
3
3
|
* dashboard and downstream consumers (MCP server).
|
|
4
4
|
*
|
|
5
|
-
* ##
|
|
5
|
+
* ## Ten, out of several hundred
|
|
6
6
|
*
|
|
7
|
-
* The Admin API has hundreds of mutations. This node exposes
|
|
7
|
+
* The Admin API has hundreds of mutations. This node exposes ten, and the
|
|
8
8
|
* choice is argued in `api/docs/ADR-0007-shopify.md`: a menu of everything is
|
|
9
|
-
* how a node becomes unusable
|
|
10
|
-
* automate — put a customer in the store, tag something, sync stock, and read
|
|
11
|
-
* back what the store already knows.
|
|
9
|
+
* how a node becomes unusable.
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* The first four cover what people automate on day one — put a customer in the
|
|
12
|
+
* store, tag something, sync stock, and read back what the store already
|
|
13
|
+
* knows. The six added on 2026-09-08 cover what they ask for on day two: the
|
|
14
|
+
* order lifecycle (draft → complete → fulfil → cancel), custom data
|
|
15
|
+
* (metafields) and putting a product in the catalogue.
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ **Every entry below carries the OAuth scope it needs**, in
|
|
18
|
+
* `SHOPIFY_OPERATION_SCOPES`. That is not documentation: a scope missing from
|
|
19
|
+
* the credential answers HTTP 403 on a call that looks perfectly formed, and
|
|
20
|
+
* on Shopify a scope is granted at INSTALL time — adding one to the app means
|
|
21
|
+
* every existing credential has to be reconnected before it can use the new
|
|
22
|
+
* operation.
|
|
17
23
|
*
|
|
18
24
|
* ## GraphQL only, and the version is not ours to drift on
|
|
19
25
|
*
|
|
@@ -54,8 +60,35 @@
|
|
|
54
60
|
* `inventoryAdjustQuantities` must be sent with the `@idempotent` directive
|
|
55
61
|
* and an idempotency key. A stock adjustment is the one operation here where a
|
|
56
62
|
* silent retry is a real inventory error, so this is not boilerplate.
|
|
63
|
+
*
|
|
64
|
+
* ## What was verified for the six added on 2026-09-08
|
|
65
|
+
*
|
|
66
|
+
* Read off the Admin GraphQL reference for the current version, not recalled.
|
|
67
|
+
* The pages, and the one fact from each that a from-memory version gets wrong:
|
|
68
|
+
*
|
|
69
|
+
* - `draftOrderCreate(input: DraftOrderInput!)` — `lineItems` is the only
|
|
70
|
+
* required field of the input, and each item is `{ variantId, quantity }`
|
|
71
|
+
* OR `{ title, originalUnitPrice, quantity }` for a custom line.
|
|
72
|
+
* - `draftOrderComplete(id: ID!, …)` — the id is a **sibling argument**, not
|
|
73
|
+
* inside an input. `paymentPending` is deprecated and is not offered here.
|
|
74
|
+
* - `fulfillmentCreate(fulfillment: FulfillmentInput!)` — takes
|
|
75
|
+
* `lineItemsByFulfillmentOrder`, NOT an order id. There is no mutation that
|
|
76
|
+
* fulfils "an order": fulfilment hangs off FulfillmentOrder, so the
|
|
77
|
+
* executor has to look them up first. That indirection is the whole reason
|
|
78
|
+
* ADR-0007 left this out of v1.
|
|
79
|
+
* - `orderCancel(orderId:, reason:, restock:, …)` — `reason` and `restock` are
|
|
80
|
+
* **required arguments**, and the payload's errors arrive in
|
|
81
|
+
* `orderCancelUserErrors`; plain `userErrors` is deprecated there.
|
|
82
|
+
* - `metafieldsSet(metafields: [MetafieldsSetInput!]!)` — takes a LIST, and
|
|
83
|
+
* each entry needs all of `ownerId`, `namespace`, `key`, `type`, `value`.
|
|
84
|
+
* `type` is not guessable from the value: `single_line_text_field` and
|
|
85
|
+
* `number_integer` are different metafields.
|
|
86
|
+
* - `productCreate(product: ProductCreateInput!)` — the argument is `product`,
|
|
87
|
+
* not `input`; `input: ProductInput` is the deprecated spelling. It creates
|
|
88
|
+
* ONE default variant; more variants are `productVariantsBulkCreate`, which
|
|
89
|
+
* is a surface of its own and is not offered here.
|
|
57
90
|
*/
|
|
58
|
-
export declare const SHOPIFY_OPERATIONS: readonly ["upsertCustomer", "setTags", "adjustInventory", "findRecords"];
|
|
91
|
+
export declare const SHOPIFY_OPERATIONS: readonly ["upsertCustomer", "setTags", "adjustInventory", "findRecords", "createDraftOrder", "completeDraftOrder", "fulfillOrder", "cancelOrder", "setMetafield", "createProduct"];
|
|
59
92
|
export type ShopifyOperation = (typeof SHOPIFY_OPERATIONS)[number];
|
|
60
93
|
/** Type guard — for DTOs and AI tool calls, where the input is untrusted. */
|
|
61
94
|
export declare function isShopifyOperation(value: unknown): value is ShopifyOperation;
|
|
@@ -72,6 +105,22 @@ export type ShopifyTaggableResource = (typeof SHOPIFY_TAGGABLE_RESOURCES)[number
|
|
|
72
105
|
/** What `findRecords` can read back. */
|
|
73
106
|
export declare const SHOPIFY_SEARCHABLE_RESOURCES: readonly ["orders", "customers", "products"];
|
|
74
107
|
export type ShopifySearchableResource = (typeof SHOPIFY_SEARCHABLE_RESOURCES)[number];
|
|
108
|
+
/**
|
|
109
|
+
* `OrderCancelReason`, the whole enum, in Shopify's own spelling.
|
|
110
|
+
*
|
|
111
|
+
* A dropdown and not free text — and that is the opposite of the call made for
|
|
112
|
+
* `adjustInventory`'s `reason`, on purpose. The difference is not taste: the
|
|
113
|
+
* inventory vocabulary was NOT verifiable from the reference (see the note
|
|
114
|
+
* above), so a dropdown there would have been a list of invented values. This
|
|
115
|
+
* one IS the enumeration, read off `enums/OrderCancelReason`, and an argument
|
|
116
|
+
* of type `OrderCancelReason!` rejects anything outside it — so free text here
|
|
117
|
+
* would only mean the user finds out by failing.
|
|
118
|
+
*/
|
|
119
|
+
export declare const SHOPIFY_CANCEL_REASONS: readonly ["CUSTOMER", "DECLINED", "FRAUD", "INVENTORY", "OTHER", "STAFF"];
|
|
120
|
+
export type ShopifyCancelReason = (typeof SHOPIFY_CANCEL_REASONS)[number];
|
|
121
|
+
/** `ProductStatus`, the whole enum. Same reasoning as the cancel reasons. */
|
|
122
|
+
export declare const SHOPIFY_PRODUCT_STATUSES: readonly ["ACTIVE", "DRAFT", "ARCHIVED"];
|
|
123
|
+
export type ShopifyProductStatus = (typeof SHOPIFY_PRODUCT_STATUSES)[number];
|
|
75
124
|
export interface ShopifyParamSpec {
|
|
76
125
|
/** Field key — also the property name on operationConfig. */
|
|
77
126
|
name: string;
|
|
@@ -80,7 +129,7 @@ export interface ShopifyParamSpec {
|
|
|
80
129
|
* picker backed by a live lookup of the store's locations; `gid` is a
|
|
81
130
|
* Shopify global id, which is almost always templated from the payload
|
|
82
131
|
* rather than typed. */
|
|
83
|
-
type: 'gid' | 'location' | 'email' | 'string' | 'number' | 'tags' | 'taggableResource' | 'searchableResource' | 'json' | 'boolean';
|
|
132
|
+
type: 'gid' | 'location' | 'email' | 'string' | 'number' | 'tags' | 'taggableResource' | 'searchableResource' | 'cancelReason' | 'productStatus' | 'json' | 'boolean';
|
|
84
133
|
required?: boolean;
|
|
85
134
|
description: string;
|
|
86
135
|
placeholder?: string;
|
|
@@ -93,3 +142,55 @@ export interface ShopifyOperationSpec {
|
|
|
93
142
|
params: ShopifyParamSpec[];
|
|
94
143
|
}
|
|
95
144
|
export declare const SHOPIFY_OPERATION_SPECS: Record<ShopifyOperation, ShopifyOperationSpec>;
|
|
145
|
+
/**
|
|
146
|
+
* The OAuth scope each operation needs, so a missing one is named BEFORE the
|
|
147
|
+
* call instead of arriving as an HTTP 403 on a request that looks fine.
|
|
148
|
+
*
|
|
149
|
+
* ## The list is "any one of", not "all of"
|
|
150
|
+
*
|
|
151
|
+
* That is `fulfillOrder`'s doing and it is not a generalisation for its own
|
|
152
|
+
* sake: which fulfilment-order scope applies depends on where the order is
|
|
153
|
+
* fulfilled from — the merchant's own locations, a third-party service, or the
|
|
154
|
+
* app itself acting as one. Shopify accepts the mutation if the token carries
|
|
155
|
+
* ANY of the three. Requiring all three would refuse a store that is correctly
|
|
156
|
+
* set up.
|
|
157
|
+
*
|
|
158
|
+
* ## An empty list means "not checkable from the operation alone"
|
|
159
|
+
*
|
|
160
|
+
* `setTags` tags an order, a customer or a product through the same mutation,
|
|
161
|
+
* and `setMetafield` writes onto whichever resource the owner id points at —
|
|
162
|
+
* so the scope depends on a FIELD, not on the operation. Guessing would be
|
|
163
|
+
* worse than not checking: a wrong guess blocks a call Shopify would have
|
|
164
|
+
* accepted, and a pre-flight check that produces false refusals gets deleted.
|
|
165
|
+
* Those fall through to Shopify's own 403, which `describeShopifyError`
|
|
166
|
+
* already explains.
|
|
167
|
+
*
|
|
168
|
+
* ⚠️ Read against `shopify.dev/docs/api/usage/access-scopes` and each
|
|
169
|
+
* mutation's own "Access requirements". Two things follow from that page and
|
|
170
|
+
* are relied on by `scopesQueFaltanEnShopify`:
|
|
171
|
+
*
|
|
172
|
+
* - **A write scope includes read.** `write_orders` grants `read_orders`, so
|
|
173
|
+
* a required `read_x` is satisfied by a granted `write_x`.
|
|
174
|
+
* - **Scopes are granted at install.** Adding one to the app does not give
|
|
175
|
+
* it to credentials that already exist; those have to be reconnected.
|
|
176
|
+
*/
|
|
177
|
+
export declare const SHOPIFY_OPERATION_SCOPES: Record<ShopifyOperation, readonly string[]>;
|
|
178
|
+
/**
|
|
179
|
+
* Which of an operation's scopes the credential does NOT have.
|
|
180
|
+
*
|
|
181
|
+
* Empty means "nothing to say" — and it says that in three different
|
|
182
|
+
* situations, all of which have to fail OPEN:
|
|
183
|
+
*
|
|
184
|
+
* 1. The operation declares no scope (the resource-dependent ones).
|
|
185
|
+
* 2. The credential has a scope string and it covers one of the alternatives.
|
|
186
|
+
* 3. **The credential has no scope string at all.** Shopify returns the
|
|
187
|
+
* granted scopes on the token exchange, but a credential stored before
|
|
188
|
+
* that was read, or one whose metadata was pruned, has an empty string —
|
|
189
|
+
* and refusing to run a node because we cannot see its permissions would
|
|
190
|
+
* break working flows to prevent a maybe. Shopify is the authority here;
|
|
191
|
+
* this check only saves the round trip when it can prove the answer.
|
|
192
|
+
*
|
|
193
|
+
* @param concedidos what Shopify granted, as it sends it: a comma-separated
|
|
194
|
+
* string, or the already-split list.
|
|
195
|
+
*/
|
|
196
|
+
export declare function scopesQueFaltanEnShopify(concedidos: string | readonly string[] | null | undefined, requeridos: readonly string[]): string[];
|