@hostwebhook/node-types 1.69.0 → 1.70.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.
Files changed (92) hide show
  1. package/dist/calendar-toolkit.d.ts +1 -1
  2. package/dist/connections.d.ts +1 -1
  3. package/dist/discord-operations.d.ts +1 -1
  4. package/dist/discord-operations.js +4 -4
  5. package/dist/discord-toolkit.d.ts +1 -1
  6. package/dist/discord-toolkit.js +3 -3
  7. package/dist/dispatch.d.ts +1 -1
  8. package/dist/docs-toolkit.d.ts +1 -1
  9. package/dist/drive-toolkit.d.ts +1 -1
  10. package/dist/esm/calendar-operations.d.ts +67 -0
  11. package/dist/esm/calendar-operations.js +176 -0
  12. package/dist/esm/calendar-toolkit.d.ts +50 -0
  13. package/dist/esm/calendar-toolkit.js +152 -0
  14. package/dist/esm/capacidad-de-credencial.d.ts +75 -0
  15. package/dist/esm/capacidad-de-credencial.js +68 -0
  16. package/dist/esm/connections.d.ts +17 -0
  17. package/dist/esm/connections.js +106 -0
  18. package/dist/esm/credentials.d.ts +144 -0
  19. package/dist/esm/credentials.js +199 -0
  20. package/dist/esm/discord-operations.d.ts +104 -0
  21. package/dist/esm/discord-operations.js +407 -0
  22. package/dist/esm/discord-toolkit.d.ts +64 -0
  23. package/dist/esm/discord-toolkit.js +260 -0
  24. package/dist/esm/dispatch.d.ts +16 -0
  25. package/dist/esm/dispatch.js +76 -0
  26. package/dist/esm/docs-operations.d.ts +99 -0
  27. package/dist/esm/docs-operations.js +127 -0
  28. package/dist/esm/docs-toolkit.d.ts +42 -0
  29. package/dist/esm/docs-toolkit.js +97 -0
  30. package/dist/esm/drive-operations.d.ts +103 -0
  31. package/dist/esm/drive-operations.js +319 -0
  32. package/dist/esm/drive-toolkit.d.ts +54 -0
  33. package/dist/esm/drive-toolkit.js +177 -0
  34. package/dist/esm/github-operations.d.ts +147 -0
  35. package/dist/esm/github-operations.js +551 -0
  36. package/dist/esm/gmail-operations.d.ts +190 -0
  37. package/dist/esm/gmail-operations.js +560 -0
  38. package/dist/esm/google-analytics-operations.d.ts +99 -0
  39. package/dist/esm/google-analytics-operations.js +222 -0
  40. package/dist/esm/google-contacts-operations.d.ts +93 -0
  41. package/dist/esm/google-contacts-operations.js +363 -0
  42. package/dist/esm/index.d.ts +66 -0
  43. package/dist/esm/index.js +50 -0
  44. package/dist/esm/jira-operations.d.ts +164 -0
  45. package/dist/esm/jira-operations.js +470 -0
  46. package/dist/esm/llm-models.d.ts +101 -0
  47. package/dist/esm/llm-models.js +230 -0
  48. package/dist/esm/mailchimp-operations.d.ts +56 -0
  49. package/dist/esm/mailchimp-operations.js +155 -0
  50. package/dist/esm/mongo-operations.d.ts +61 -0
  51. package/dist/esm/mongo-operations.js +120 -0
  52. package/dist/esm/notion-operations.d.ts +125 -0
  53. package/dist/esm/notion-operations.js +511 -0
  54. package/dist/esm/openrouter.d.ts +142 -0
  55. package/dist/esm/openrouter.js +99 -0
  56. package/dist/esm/package.json +4 -0
  57. package/dist/esm/postgres-operations.d.ts +60 -0
  58. package/dist/esm/postgres-operations.js +75 -0
  59. package/dist/esm/registry.d.ts +160 -0
  60. package/dist/esm/registry.js +555 -0
  61. package/dist/esm/sheets-operations.d.ts +78 -0
  62. package/dist/esm/sheets-operations.js +150 -0
  63. package/dist/esm/sheets-toolkit.d.ts +49 -0
  64. package/dist/esm/sheets-toolkit.js +127 -0
  65. package/dist/esm/shopify-operations.d.ts +95 -0
  66. package/dist/esm/shopify-operations.js +259 -0
  67. package/dist/esm/slack-operations.d.ts +81 -0
  68. package/dist/esm/slack-operations.js +244 -0
  69. package/dist/esm/slack-toolkit.d.ts +60 -0
  70. package/dist/esm/slack-toolkit.js +235 -0
  71. package/dist/esm/telegram-operations.d.ts +104 -0
  72. package/dist/esm/telegram-operations.js +351 -0
  73. package/dist/esm/telegram-toolkit.d.ts +52 -0
  74. package/dist/esm/telegram-toolkit.js +183 -0
  75. package/dist/esm/types.d.ts +149 -0
  76. package/dist/esm/types.js +8 -0
  77. package/dist/esm/ui.d.ts +13 -0
  78. package/dist/esm/ui.js +150 -0
  79. package/dist/esm/whatsapp-operations.d.ts +23 -0
  80. package/dist/esm/whatsapp-operations.js +36 -0
  81. package/dist/index.d.ts +66 -66
  82. package/dist/index.js +185 -185
  83. package/dist/openrouter.d.ts +1 -1
  84. package/dist/registry.d.ts +1 -1
  85. package/dist/sheets-toolkit.d.ts +1 -1
  86. package/dist/slack-operations.d.ts +1 -1
  87. package/dist/slack-operations.js +4 -4
  88. package/dist/slack-toolkit.d.ts +1 -1
  89. package/dist/slack-toolkit.js +3 -3
  90. package/dist/telegram-toolkit.d.ts +1 -1
  91. package/dist/ui.d.ts +1 -1
  92. package/package.json +21 -3
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Google Sheets operation enum + form schema — single source of truth across
3
+ * the API, Dashboard, and Message Broker. Used by:
4
+ * - sheetsAction entity / DTO (api): operation field + validation
5
+ * - dashboard SheetsAction detail page: the whole operation form
6
+ *
7
+ * The api already accepts these six; the dashboard's own `SheetsAction` type
8
+ * only listed four (it never learned about appendOrUpdateRow and
9
+ * createSpreadsheet), which is exactly the drift this enum removes.
10
+ */
11
+ export const SHEETS_OPERATIONS = [
12
+ 'appendRow',
13
+ 'updateRow',
14
+ 'appendOrUpdateRow',
15
+ 'readRange',
16
+ 'getRows',
17
+ 'createSpreadsheet',
18
+ ];
19
+ /** Type guard — useful when validating untrusted input (DTOs, AI tool calls). */
20
+ export function isSheetsOperation(value) {
21
+ return (typeof value === 'string' &&
22
+ SHEETS_OPERATIONS.includes(value));
23
+ }
24
+ /* Fields most operations share. The spreadsheet and its tab are the same two
25
+ controls everywhere they appear — createSpreadsheet is the only operation
26
+ that shows neither, because there is no document yet to point at. */
27
+ const spreadsheet = () => ({
28
+ name: 'spreadsheetId',
29
+ label: 'Select Spreadsheet',
30
+ type: 'spreadsheet',
31
+ required: true,
32
+ });
33
+ const sheetTab = () => ({
34
+ name: 'sheetName',
35
+ label: 'Sheet Name',
36
+ type: 'sheetTab',
37
+ required: true,
38
+ });
39
+ /** The mapping editor, whose label, section and empty line change per op. */
40
+ const columnMapping = (over = {}) => ({
41
+ name: 'columnMapping',
42
+ label: 'Columns to Update',
43
+ type: 'columnMapping',
44
+ emptyText: 'No columns selected. Add columns to update.',
45
+ ...over,
46
+ });
47
+ /** Which row to touch: the column to search in, and the value to look for. */
48
+ const matchFields = (section) => [
49
+ {
50
+ name: 'matchColumn',
51
+ label: 'Match Column',
52
+ type: 'headerColumn',
53
+ required: true,
54
+ description: 'Select the column to find the row to update',
55
+ section,
56
+ },
57
+ {
58
+ /* No static placeholder or description: both are built from the chosen
59
+ column at render time ({{payload.<column>}} and 'Template to match
60
+ against "<column>"'), so the schema cannot carry them. */
61
+ name: 'matchValueTemplate',
62
+ label: 'Match Value',
63
+ type: 'string',
64
+ required: true,
65
+ section,
66
+ },
67
+ ];
68
+ export const SHEETS_OPERATION_SPECS = {
69
+ appendRow: {
70
+ label: 'Append Row',
71
+ description: 'Add a new row at the bottom',
72
+ apiMethod: 'spreadsheets.values.batchUpdate',
73
+ params: [
74
+ spreadsheet(),
75
+ sheetTab(),
76
+ columnMapping({
77
+ /* The append editor has never carried a label of its own — the section
78
+ title above it already says "Columns to Append". */
79
+ label: '',
80
+ section: 'Columns to Append',
81
+ emptyText: 'No columns selected. Add columns to populate in the new row.',
82
+ }),
83
+ ],
84
+ },
85
+ updateRow: {
86
+ label: 'Update Row',
87
+ description: 'Update cells in a specific range',
88
+ apiMethod: 'spreadsheets.values.batchUpdate',
89
+ params: [
90
+ spreadsheet(),
91
+ sheetTab(),
92
+ ...matchFields('Update Row'),
93
+ columnMapping({ section: 'Update Row' }),
94
+ ],
95
+ },
96
+ appendOrUpdateRow: {
97
+ label: 'Append or Update',
98
+ description: 'Update if row exists, append if not',
99
+ apiMethod: 'spreadsheets.values.batchUpdate',
100
+ /* Same fields as updateRow — only the section title differs. The label
101
+ inside still reads "Columns to Update", which is what the page has always
102
+ shown. */
103
+ params: [
104
+ spreadsheet(),
105
+ sheetTab(),
106
+ ...matchFields('Match & Upsert'),
107
+ columnMapping({ section: 'Match & Upsert' }),
108
+ ],
109
+ },
110
+ readRange: {
111
+ label: 'Read Range',
112
+ description: 'Read cells from a specific range',
113
+ apiMethod: 'spreadsheets.values.get',
114
+ params: [
115
+ spreadsheet(),
116
+ sheetTab(),
117
+ {
118
+ name: 'rangeTemplate',
119
+ label: 'Range Template',
120
+ type: 'string',
121
+ required: true,
122
+ placeholder: 'e.g. A2:D100',
123
+ },
124
+ ],
125
+ },
126
+ getRows: {
127
+ label: 'Get Rows',
128
+ description: 'Read all rows with headers as keys',
129
+ apiMethod: 'spreadsheets.values.get',
130
+ params: [spreadsheet(), sheetTab()],
131
+ },
132
+ createSpreadsheet: {
133
+ label: 'Create Spreadsheet',
134
+ description: 'Create a new Google Sheets spreadsheet',
135
+ apiMethod: 'spreadsheets.create',
136
+ /* `sheetName` means the **new document's title** here, not a tab — the one
137
+ field in this node that changes meaning *and* control between
138
+ operations. The api uses it for both the file name and its first tab. */
139
+ params: [
140
+ {
141
+ name: 'sheetName',
142
+ label: 'Spreadsheet Title',
143
+ type: 'string',
144
+ required: true,
145
+ placeholder: 'My New Spreadsheet',
146
+ description: 'Name of the new spreadsheet. Supports {{payload.field}} templates.',
147
+ },
148
+ ],
149
+ },
150
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Google Sheets AI Toolkit — las herramientas que un sheetsAction expone
3
+ * cuando `aiEnabled` está encendido.
4
+ *
5
+ * **Se escribe una sola vez.**
6
+ *
7
+ * Cuando esto se escribió, las especificaciones de los demás nodos vivían dos
8
+ * veces —una copia en el dashboard y otra en
9
+ * `api/src/mcp-servers/toolkit-specs.ts`, las dos a mano y sin nada que las
10
+ * ate— y de ahí salió que Contacts anunciara 15 herramientas y expusiera una.
11
+ *
12
+ * Ya no es así, y este comentario decía lo contrario hasta 2026-08-12: hoy en
13
+ * el paquete viven **siete** toolkits —Gmail, native email, Calendar, Drive,
14
+ * Telegram, Sheets y Docs— y los que siguen duplicados a mano son WhatsApp,
15
+ * Discord, Slack y Contacts. El paquete es el sitio por defecto para uno
16
+ * nuevo; los cuatro rezagados están pendientes de migrar.
17
+ *
18
+ * La forma es la que ya consume `toolkitSpecToMcpTool` en la api, para que
19
+ * pueda usarse sin adaptador. Los textos van en inglés porque los lee el modelo
20
+ * y quien mire la lista de herramientas del servidor MCP.
21
+ */
22
+ import type { SheetsOperation } from './sheets-operations.js';
23
+ export interface SheetsToolkitParameter {
24
+ name: string;
25
+ /** El esquema MCP sólo admite estos tres; lo estructurado viaja como JSON
26
+ * en un `string`, igual que el `contacts` del toolkit de Contacts. */
27
+ type: 'string' | 'number' | 'boolean';
28
+ description: string;
29
+ required: boolean;
30
+ }
31
+ export interface SheetsToolkitSpec {
32
+ operation: SheetsOperation;
33
+ label: string;
34
+ toolName: string;
35
+ description: string;
36
+ parameters: SheetsToolkitParameter[];
37
+ /** Escribe en la hoja del usuario. Lo usa la confirmación previa del AI Node. */
38
+ destructive?: boolean;
39
+ }
40
+ export declare const SHEETS_TOOLKIT_SPECS: SheetsToolkitSpec[];
41
+ /** Las herramientas por nombre, para despachar una llamada del modelo. */
42
+ export declare const SHEETS_TOOLKIT_BY_TOOL_NAME: Record<string, SheetsToolkitSpec>;
43
+ /**
44
+ * Los dos parámetros que el nodo puede rellenar por defecto. Se exporta para
45
+ * que la api y el dashboard no vuelvan a escribir la lista a mano —
46
+ * `create_spreadsheet` no declara ninguno de los dos y por eso se queda fuera
47
+ * de la inyección sin necesidad de excepciones.
48
+ */
49
+ export declare const SHEETS_TOOLKIT_DEFAULTABLE: readonly ["spreadsheetId", "sheetName"];
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Google Sheets AI Toolkit — las herramientas que un sheetsAction expone
3
+ * cuando `aiEnabled` está encendido.
4
+ *
5
+ * **Se escribe una sola vez.**
6
+ *
7
+ * Cuando esto se escribió, las especificaciones de los demás nodos vivían dos
8
+ * veces —una copia en el dashboard y otra en
9
+ * `api/src/mcp-servers/toolkit-specs.ts`, las dos a mano y sin nada que las
10
+ * ate— y de ahí salió que Contacts anunciara 15 herramientas y expusiera una.
11
+ *
12
+ * Ya no es así, y este comentario decía lo contrario hasta 2026-08-12: hoy en
13
+ * el paquete viven **siete** toolkits —Gmail, native email, Calendar, Drive,
14
+ * Telegram, Sheets y Docs— y los que siguen duplicados a mano son WhatsApp,
15
+ * Discord, Slack y Contacts. El paquete es el sitio por defecto para uno
16
+ * nuevo; los cuatro rezagados están pendientes de migrar.
17
+ *
18
+ * La forma es la que ya consume `toolkitSpecToMcpTool` en la api, para que
19
+ * pueda usarse sin adaptador. Los textos van en inglés porque los lee el modelo
20
+ * y quien mire la lista de herramientas del servidor MCP.
21
+ */
22
+ const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
23
+ /*
24
+ * La hoja y la pestaña del nodo van como **defecto**, y el modelo puede
25
+ * cambiarlas — la misma decisión que el canal por defecto de Slack, y la misma
26
+ * maquinaria: `toolkitSpecToMcpTool(spec, nombre, { spreadsheetId, sheetName })`
27
+ * saca esos dos de `required` **y le cuenta el valor al modelo** en la
28
+ * descripción. Las dos cosas o ninguna: mientras el esquema diga `required`, el
29
+ * LLM siempre los rellena, nunca los omite, y el valor configurado no se usaría
30
+ * jamás.
31
+ *
32
+ * Por eso aquí se declaran `required: true`: lo son para la API de Google, y es
33
+ * el defecto del nodo quien los vuelve opcionales, nodo por nodo.
34
+ *
35
+ * `create_spreadsheet` es el único que NO los declara —no hay documento todavía
36
+ * al que apuntar— y su título se llama `title`, no `sheetName`. Si se llamara
37
+ * `sheetName`, la inyección de defectos vería que la operación declara ese
38
+ * parámetro y le pasaría como título el nombre de la pestaña del nodo: el
39
+ * documento nuevo nacería llamándose «Sheet1».
40
+ */
41
+ const hoja = () => p('spreadsheetId', "Id of the Google Sheets document — the long chunk of its URL, between /d/ and /edit.");
42
+ const pestana = () => p('sheetName', 'Name of the tab inside the document, exactly as it reads on its tab strip.');
43
+ const fila = (que) => p('row', `JSON object holding ${que}. Keys are the sheet's **header names**, spelled exactly as they appear in its title row: {"Name": "Ariel", "Email": "a@b.c"}. A key that is not a header makes the call fail, so when you do not know the headers, call get_rows first.`);
44
+ export const SHEETS_TOOLKIT_SPECS = [
45
+ {
46
+ operation: 'appendRow',
47
+ label: 'Append row',
48
+ toolName: 'append_row',
49
+ description: 'Add a new row at the bottom of the tab. It does not check whether the row already exists — when it might, use append_or_update_row instead so you do not duplicate it.',
50
+ parameters: [fila('the values of the new row'), hoja(), pestana()],
51
+ destructive: true,
52
+ },
53
+ {
54
+ operation: 'updateRow',
55
+ label: 'Update row',
56
+ toolName: 'update_row',
57
+ description: 'Find a row by the value of one column and overwrite the cells you pass. When no row matches it writes nothing — it does not create one.',
58
+ parameters: [
59
+ p('matchColumn', 'Header of the column to search in, e.g. "Email".'),
60
+ p('matchValue', 'Value to look for inside that column. The whole cell is compared, not a fragment.'),
61
+ fila('the cells to overwrite; the ones you leave out keep their value'),
62
+ hoja(),
63
+ pestana(),
64
+ ],
65
+ destructive: true,
66
+ },
67
+ {
68
+ operation: 'appendOrUpdateRow',
69
+ label: 'Append or update row',
70
+ toolName: 'append_or_update_row',
71
+ description: 'Find a row by the value of one column: update it when it exists, append it at the bottom when it does not. This is the one to use to keep a sheet in sync without duplicating rows.',
72
+ parameters: [
73
+ p('matchColumn', 'Header of the column that identifies the row, e.g. "Email".'),
74
+ p('matchValue', 'Value to look for in that column. When it is not there, the row is appended.'),
75
+ fila('the values of the row, used both to update and to append'),
76
+ hoja(),
77
+ pestana(),
78
+ ],
79
+ destructive: true,
80
+ },
81
+ {
82
+ operation: 'readRange',
83
+ label: 'Read range',
84
+ toolName: 'read_range',
85
+ description: 'Read the cells of an A1-notation range and return them as rows of text, without reading headers. To read data with its column names, get_rows is more useful.',
86
+ parameters: [
87
+ p('range', 'A1-notation range relative to the tab, e.g. "A2:D100". Do not prefix it with the tab name.'),
88
+ hoja(),
89
+ pestana(),
90
+ ],
91
+ },
92
+ {
93
+ operation: 'getRows',
94
+ label: 'Get rows',
95
+ toolName: 'get_rows',
96
+ description: "Read every row of the tab and return them as objects keyed by the sheet's headers. This is how you find out which columns a sheet has before writing to it.",
97
+ parameters: [hoja(), pestana()],
98
+ },
99
+ {
100
+ operation: 'createSpreadsheet',
101
+ label: 'Create spreadsheet',
102
+ toolName: 'create_spreadsheet',
103
+ description: "Create a new, empty Google Sheets document and return its id and URL. It writes no data: to fill it, call append_row afterwards with the id it returns. It lands wherever the node's Save Location says — you do not choose that.",
104
+ /*
105
+ * Sin `folderId` a propósito, y esta es la excepción a «defecto del nodo que
106
+ * el modelo puede cambiar».
107
+ *
108
+ * La hoja sí se le ofrece porque el modelo tiene de dónde sacar un id: se lo
109
+ * dice quien escribe, o lo devuelve un get_rows. Un id de carpeta de Drive
110
+ * no: no hay ninguna herramienta aquí que las liste, así que sólo podría
111
+ * inventárselo, y un id inventado falla mientras uno real acertado por
112
+ * casualidad escribe en la carpeta de otro. La carpeta la elige la persona
113
+ * en el nodo, y la api la aplica.
114
+ */
115
+ parameters: [p('title', 'Name of the new document.')],
116
+ destructive: true,
117
+ },
118
+ ];
119
+ /** Las herramientas por nombre, para despachar una llamada del modelo. */
120
+ export const SHEETS_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(SHEETS_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
121
+ /**
122
+ * Los dos parámetros que el nodo puede rellenar por defecto. Se exporta para
123
+ * que la api y el dashboard no vuelvan a escribir la lista a mano —
124
+ * `create_spreadsheet` no declara ninguno de los dos y por eso se queda fuera
125
+ * de la inyección sin necesidad de excepciones.
126
+ */
127
+ export const SHEETS_TOOLKIT_DEFAULTABLE = ['spreadsheetId', 'sheetName'];
@@ -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,259 @@
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 const SHOPIFY_OPERATIONS = [
59
+ 'upsertCustomer',
60
+ 'setTags',
61
+ 'adjustInventory',
62
+ 'findRecords',
63
+ ];
64
+ /** Type guard — for DTOs and AI tool calls, where the input is untrusted. */
65
+ export function isShopifyOperation(value) {
66
+ return (typeof value === 'string' &&
67
+ SHOPIFY_OPERATIONS.includes(value));
68
+ }
69
+ /**
70
+ * The resources `tagsAdd` / `tagsRemove` accept. Taken from the mutation
71
+ * reference — this list is the API's, not a subset we chose.
72
+ *
73
+ * `article` and `discountNode` are left out of the node's own picker on
74
+ * purpose: neither is something a HostWebhook flow tags in practice, and every
75
+ * extra option is a field the user has to read past.
76
+ */
77
+ export const SHOPIFY_TAGGABLE_RESOURCES = [
78
+ 'order',
79
+ 'customer',
80
+ 'product',
81
+ 'draftOrder',
82
+ ];
83
+ /** What `findRecords` can read back. */
84
+ export const SHOPIFY_SEARCHABLE_RESOURCES = [
85
+ 'orders',
86
+ 'customers',
87
+ 'products',
88
+ ];
89
+ export const SHOPIFY_OPERATION_SPECS = {
90
+ upsertCustomer: {
91
+ label: 'Add or update customer',
92
+ description: 'Look the customer up by email, then create them or update the fields you set.',
93
+ apiRoute: 'mutation customerCreate / customerUpdate (lookup by email first)',
94
+ params: [
95
+ {
96
+ name: 'email',
97
+ label: 'Email address',
98
+ type: 'email',
99
+ required: true,
100
+ description: 'Identifies the customer. This is what decides between creating and updating — there is no separate "id" to keep.',
101
+ placeholder: '{{payload.email}}',
102
+ },
103
+ {
104
+ name: 'firstName',
105
+ label: 'First name',
106
+ type: 'string',
107
+ description: 'Left untouched on an existing customer if you leave it empty.',
108
+ placeholder: '{{payload.firstName}}',
109
+ },
110
+ {
111
+ name: 'lastName',
112
+ label: 'Last name',
113
+ type: 'string',
114
+ description: 'Left untouched on an existing customer if you leave it empty.',
115
+ placeholder: '{{payload.lastName}}',
116
+ },
117
+ {
118
+ name: 'phone',
119
+ label: 'Phone',
120
+ type: 'string',
121
+ description: 'Shopify wants it in E.164 (+52…). A malformed number fails the whole mutation, not just the field.',
122
+ },
123
+ {
124
+ name: 'tags',
125
+ label: 'Tags',
126
+ type: 'tags',
127
+ 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.',
128
+ },
129
+ {
130
+ name: 'note',
131
+ label: 'Note',
132
+ type: 'string',
133
+ description: 'Internal note, visible to staff in the Shopify admin.',
134
+ },
135
+ {
136
+ name: 'emailMarketingConsent',
137
+ label: 'Email marketing consent',
138
+ type: 'json',
139
+ 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"}.',
140
+ },
141
+ ],
142
+ },
143
+ setTags: {
144
+ label: 'Add or remove tags',
145
+ description: 'Tag an order, a customer or a product. Adding and removing are separate calls, so one node does one of the two.',
146
+ apiRoute: 'mutation tagsAdd / tagsRemove',
147
+ params: [
148
+ {
149
+ name: 'resource',
150
+ label: 'What to tag',
151
+ type: 'taggableResource',
152
+ required: true,
153
+ description: 'Shopify tags many resource types through the same mutation; this only picks which id you are passing.',
154
+ },
155
+ {
156
+ name: 'resourceId',
157
+ label: 'Resource id',
158
+ type: 'gid',
159
+ required: true,
160
+ description: 'The Shopify global id, e.g. gid://shopify/Order/1234. A Shopify trigger gives you this directly in the payload.',
161
+ placeholder: '{{payload.admin_graphql_api_id}}',
162
+ },
163
+ {
164
+ name: 'tags',
165
+ label: 'Tags',
166
+ type: 'tags',
167
+ required: true,
168
+ description: 'One or more tags.',
169
+ },
170
+ {
171
+ name: 'remove',
172
+ label: 'Remove instead of add',
173
+ type: 'boolean',
174
+ description: 'Off adds the tags (tagsAdd); on removes them (tagsRemove). Unlike Mailchimp, Shopify cannot do both in one call.',
175
+ },
176
+ ],
177
+ },
178
+ adjustInventory: {
179
+ label: 'Adjust inventory',
180
+ description: 'Move stock by a delta at one location. For the case where the real stock lives in another system.',
181
+ apiRoute: 'mutation inventoryAdjustQuantities',
182
+ params: [
183
+ {
184
+ name: 'inventoryItemId',
185
+ label: 'Inventory item id',
186
+ type: 'gid',
187
+ required: true,
188
+ description: 'gid://shopify/InventoryItem/1234. This is the inventory item, NOT the product or the variant — a variant points at one.',
189
+ placeholder: '{{payload.inventory_item_id}}',
190
+ },
191
+ {
192
+ name: 'locationId',
193
+ label: 'Location',
194
+ type: 'location',
195
+ required: true,
196
+ description: 'Which location the stock moves at. A store with one location still has to name it.',
197
+ },
198
+ {
199
+ name: 'delta',
200
+ label: 'Change by',
201
+ type: 'number',
202
+ required: true,
203
+ 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.',
204
+ placeholder: '{{payload.delta}}',
205
+ },
206
+ {
207
+ name: 'name',
208
+ label: 'Quantity type',
209
+ type: 'string',
210
+ required: true,
211
+ description: 'Which quantity is being changed. "available" is the documented value; check the reference before using another.',
212
+ placeholder: 'available',
213
+ },
214
+ {
215
+ name: 'reason',
216
+ label: 'Reason',
217
+ type: 'string',
218
+ required: true,
219
+ description: 'Why the stock moved — it lands in the store\'s inventory history. "correction" is the documented value; check the reference before using another.',
220
+ placeholder: 'correction',
221
+ },
222
+ {
223
+ name: 'referenceDocumentUri',
224
+ label: 'Reference',
225
+ type: 'string',
226
+ description: 'Optional audit-trail link back to whatever caused the change, so someone reading Shopify\'s inventory history can find it.',
227
+ },
228
+ ],
229
+ },
230
+ findRecords: {
231
+ label: 'Find orders, customers or products',
232
+ description: 'Read back what the store knows, to enrich a payload that came from somewhere else.',
233
+ apiRoute: 'query orders / customers / products (with search syntax)',
234
+ params: [
235
+ {
236
+ name: 'resource',
237
+ label: 'What to look for',
238
+ type: 'searchableResource',
239
+ required: true,
240
+ description: 'Which collection to search.',
241
+ },
242
+ {
243
+ name: 'query',
244
+ label: 'Search',
245
+ type: 'string',
246
+ required: true,
247
+ 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.',
248
+ placeholder: 'email:{{payload.email}}',
249
+ },
250
+ {
251
+ name: 'first',
252
+ label: 'How many',
253
+ type: 'number',
254
+ description: 'Defaults to 1, which is the enrichment case. Asking for many costs more against the query-cost budget, not more calls.',
255
+ placeholder: '1',
256
+ },
257
+ ],
258
+ },
259
+ };