@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.
- package/dist/calendar-toolkit.d.ts +1 -1
- package/dist/connections.d.ts +1 -1
- package/dist/discord-operations.d.ts +1 -1
- package/dist/discord-operations.js +4 -4
- package/dist/discord-toolkit.d.ts +1 -1
- package/dist/discord-toolkit.js +3 -3
- package/dist/dispatch.d.ts +1 -1
- package/dist/docs-toolkit.d.ts +1 -1
- package/dist/drive-toolkit.d.ts +1 -1
- package/dist/esm/calendar-operations.d.ts +67 -0
- package/dist/esm/calendar-operations.js +176 -0
- package/dist/esm/calendar-toolkit.d.ts +50 -0
- package/dist/esm/calendar-toolkit.js +152 -0
- package/dist/esm/capacidad-de-credencial.d.ts +75 -0
- package/dist/esm/capacidad-de-credencial.js +68 -0
- package/dist/esm/connections.d.ts +17 -0
- package/dist/esm/connections.js +106 -0
- package/dist/esm/credentials.d.ts +144 -0
- package/dist/esm/credentials.js +199 -0
- package/dist/esm/discord-operations.d.ts +104 -0
- package/dist/esm/discord-operations.js +407 -0
- package/dist/esm/discord-toolkit.d.ts +64 -0
- package/dist/esm/discord-toolkit.js +260 -0
- package/dist/esm/dispatch.d.ts +16 -0
- package/dist/esm/dispatch.js +76 -0
- package/dist/esm/docs-operations.d.ts +99 -0
- package/dist/esm/docs-operations.js +127 -0
- package/dist/esm/docs-toolkit.d.ts +42 -0
- package/dist/esm/docs-toolkit.js +97 -0
- package/dist/esm/drive-operations.d.ts +103 -0
- package/dist/esm/drive-operations.js +319 -0
- package/dist/esm/drive-toolkit.d.ts +54 -0
- package/dist/esm/drive-toolkit.js +177 -0
- package/dist/esm/github-operations.d.ts +147 -0
- package/dist/esm/github-operations.js +551 -0
- package/dist/esm/gmail-operations.d.ts +190 -0
- package/dist/esm/gmail-operations.js +560 -0
- package/dist/esm/google-analytics-operations.d.ts +99 -0
- package/dist/esm/google-analytics-operations.js +222 -0
- package/dist/esm/google-contacts-operations.d.ts +93 -0
- package/dist/esm/google-contacts-operations.js +363 -0
- package/dist/esm/index.d.ts +66 -0
- package/dist/esm/index.js +50 -0
- package/dist/esm/jira-operations.d.ts +164 -0
- package/dist/esm/jira-operations.js +470 -0
- package/dist/esm/llm-models.d.ts +101 -0
- package/dist/esm/llm-models.js +230 -0
- package/dist/esm/mailchimp-operations.d.ts +56 -0
- package/dist/esm/mailchimp-operations.js +155 -0
- package/dist/esm/mongo-operations.d.ts +61 -0
- package/dist/esm/mongo-operations.js +120 -0
- package/dist/esm/notion-operations.d.ts +125 -0
- package/dist/esm/notion-operations.js +511 -0
- package/dist/esm/openrouter.d.ts +142 -0
- package/dist/esm/openrouter.js +99 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/postgres-operations.d.ts +60 -0
- package/dist/esm/postgres-operations.js +75 -0
- package/dist/esm/registry.d.ts +160 -0
- package/dist/esm/registry.js +555 -0
- package/dist/esm/sheets-operations.d.ts +78 -0
- package/dist/esm/sheets-operations.js +150 -0
- package/dist/esm/sheets-toolkit.d.ts +49 -0
- package/dist/esm/sheets-toolkit.js +127 -0
- package/dist/esm/shopify-operations.d.ts +95 -0
- package/dist/esm/shopify-operations.js +259 -0
- package/dist/esm/slack-operations.d.ts +81 -0
- package/dist/esm/slack-operations.js +244 -0
- package/dist/esm/slack-toolkit.d.ts +60 -0
- package/dist/esm/slack-toolkit.js +235 -0
- package/dist/esm/telegram-operations.d.ts +104 -0
- package/dist/esm/telegram-operations.js +351 -0
- package/dist/esm/telegram-toolkit.d.ts +52 -0
- package/dist/esm/telegram-toolkit.js +183 -0
- package/dist/esm/types.d.ts +149 -0
- package/dist/esm/types.js +8 -0
- package/dist/esm/ui.d.ts +13 -0
- package/dist/esm/ui.js +150 -0
- package/dist/esm/whatsapp-operations.d.ts +23 -0
- package/dist/esm/whatsapp-operations.js +36 -0
- package/dist/index.d.ts +66 -66
- package/dist/index.js +185 -185
- package/dist/openrouter.d.ts +1 -1
- package/dist/registry.d.ts +1 -1
- package/dist/sheets-toolkit.d.ts +1 -1
- package/dist/slack-operations.d.ts +1 -1
- package/dist/slack-operations.js +4 -4
- package/dist/slack-toolkit.d.ts +1 -1
- package/dist/slack-toolkit.js +3 -3
- package/dist/telegram-toolkit.d.ts +1 -1
- package/dist/ui.d.ts +1 -1
- package/package.json +21 -3
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized NODE_REGISTRY — single source of truth for all node metadata.
|
|
3
|
+
*
|
|
4
|
+
* When adding a new node type, add ONE entry here. The dashboard derives
|
|
5
|
+
* all per-node maps (colors, URLs, Redux keys, palette, delete, etc.)
|
|
6
|
+
* from this registry. No more 18+ touch points.
|
|
7
|
+
*/
|
|
8
|
+
export const NODE_REGISTRY = {
|
|
9
|
+
// ── Sources ──
|
|
10
|
+
webhook: {
|
|
11
|
+
type: 'webhook', prefix: 'wh', label: 'Webhook',
|
|
12
|
+
group: 'Sources', detailPath: '/dashboard/webhooks', apiPath: '/webhooks',
|
|
13
|
+
stateKey: 'webhooks', allStateKey: 'allWebhooks',
|
|
14
|
+
color: '#a78bfa', testable: false,
|
|
15
|
+
},
|
|
16
|
+
scheduledWorkflow: {
|
|
17
|
+
type: 'scheduledWorkflow', prefix: 'swf', label: 'Scheduled Workflow',
|
|
18
|
+
group: 'Sources', detailPath: '/dashboard/scheduled-workflows', apiPath: '/scheduled-workflows',
|
|
19
|
+
stateKey: 'scheduledWorkflows', allStateKey: 'allScheduledWorkflows',
|
|
20
|
+
color: '#2dd4bf', testable: true,
|
|
21
|
+
},
|
|
22
|
+
chatTrigger: {
|
|
23
|
+
type: 'chatTrigger', prefix: 'chat', label: 'Chat Trigger',
|
|
24
|
+
group: 'Sources', detailPath: '/dashboard/chat-triggers', apiPath: '/chat-triggers',
|
|
25
|
+
stateKey: 'chatTriggers', allStateKey: 'allChatTriggers',
|
|
26
|
+
color: '#06b6d4', testable: false,
|
|
27
|
+
},
|
|
28
|
+
trigger: {
|
|
29
|
+
type: 'trigger', prefix: 'trg', label: 'Trigger',
|
|
30
|
+
group: 'Sources', detailPath: '/dashboard/triggers', apiPath: '/triggers',
|
|
31
|
+
stateKey: 'triggers', allStateKey: 'allTriggers',
|
|
32
|
+
color: '#ef4444', testable: false,
|
|
33
|
+
},
|
|
34
|
+
voiceAgent: {
|
|
35
|
+
type: 'voiceAgent', prefix: 'va', label: 'Voice Agent',
|
|
36
|
+
group: 'Sources', detailPath: '/dashboard/voice-agents', apiPath: '/voice-agents',
|
|
37
|
+
stateKey: 'voiceAgents', allStateKey: 'allVoiceAgents',
|
|
38
|
+
color: '#a855f7', testable: false,
|
|
39
|
+
},
|
|
40
|
+
// ── Flow Control ──
|
|
41
|
+
filter: {
|
|
42
|
+
type: 'filter', prefix: 'fi', label: 'Filter',
|
|
43
|
+
group: 'Flow Control', detailPath: '/dashboard/filter-nodes', apiPath: '/filter-nodes',
|
|
44
|
+
stateKey: 'filterNodes', allStateKey: 'allFilterNodes',
|
|
45
|
+
color: '#facc15', testable: true,
|
|
46
|
+
},
|
|
47
|
+
transform: {
|
|
48
|
+
type: 'transform', prefix: 'tr', label: 'Transform',
|
|
49
|
+
group: 'Flow Control', detailPath: '/dashboard/transform-nodes', apiPath: '/transform-nodes',
|
|
50
|
+
stateKey: 'transformNodes', allStateKey: 'allTransformNodes',
|
|
51
|
+
color: '#fbbf24', testable: true,
|
|
52
|
+
},
|
|
53
|
+
schemaValidator: {
|
|
54
|
+
type: 'schemaValidator', prefix: 'sv', label: 'Schema Validator',
|
|
55
|
+
group: 'Flow Control', detailPath: '/dashboard/schema-validators', apiPath: '/schema-validators',
|
|
56
|
+
stateKey: 'schemaValidators', allStateKey: 'allSchemaValidators',
|
|
57
|
+
color: '#818cf8', testable: true,
|
|
58
|
+
},
|
|
59
|
+
conditional: {
|
|
60
|
+
type: 'conditional', prefix: 'cond', label: 'Conditional',
|
|
61
|
+
group: 'Flow Control', detailPath: '/dashboard/conditional-nodes', apiPath: '/conditional-nodes',
|
|
62
|
+
stateKey: 'conditionalNodes', allStateKey: 'allConditionalNodes',
|
|
63
|
+
color: '#c084fc', testable: true,
|
|
64
|
+
},
|
|
65
|
+
delay: {
|
|
66
|
+
type: 'delay', prefix: 'delay', label: 'Delay',
|
|
67
|
+
group: 'Flow Control', detailPath: '/dashboard/delay-nodes', apiPath: '/delay-nodes',
|
|
68
|
+
stateKey: 'delayNodes', allStateKey: 'allDelayNodes',
|
|
69
|
+
color: '#94a3b8', testable: true,
|
|
70
|
+
},
|
|
71
|
+
rateLimiter: {
|
|
72
|
+
type: 'rateLimiter', prefix: 'rl', label: 'Rate Limiter',
|
|
73
|
+
group: 'Flow Control', detailPath: '/dashboard/rate-limiter-nodes', apiPath: '/rate-limiter-nodes',
|
|
74
|
+
stateKey: 'rateLimiterNodes', allStateKey: 'allRateLimiterNodes',
|
|
75
|
+
color: '#f97316', testable: false,
|
|
76
|
+
},
|
|
77
|
+
aggregator: {
|
|
78
|
+
type: 'aggregator', prefix: 'agg', label: 'Aggregator',
|
|
79
|
+
group: 'Flow Control', detailPath: '/dashboard/aggregator-nodes', apiPath: '/aggregator-nodes',
|
|
80
|
+
stateKey: 'aggregatorNodes', allStateKey: 'allAggregatorNodes',
|
|
81
|
+
color: '#84cc16', testable: true,
|
|
82
|
+
},
|
|
83
|
+
cache: {
|
|
84
|
+
type: 'cache', prefix: 'cache', label: 'Cache',
|
|
85
|
+
group: 'Flow Control', detailPath: '/dashboard/cache-nodes', apiPath: '/cache-nodes',
|
|
86
|
+
stateKey: 'cacheNodes', allStateKey: 'allCacheNodes',
|
|
87
|
+
color: '#d946ef', testable: true,
|
|
88
|
+
},
|
|
89
|
+
code: {
|
|
90
|
+
type: 'code', prefix: 'code', label: 'Code',
|
|
91
|
+
group: 'Flow Control', detailPath: '/dashboard/code-nodes', apiPath: '/code-nodes',
|
|
92
|
+
stateKey: 'codeNodes', allStateKey: 'allCodeNodes',
|
|
93
|
+
color: '#a855f7', testable: true,
|
|
94
|
+
},
|
|
95
|
+
ai: {
|
|
96
|
+
type: 'ai', prefix: 'ai', label: 'AI',
|
|
97
|
+
/* 'Actions' y no 'Flow Control': un nodo de IA no encamina ni retiene un
|
|
98
|
+
evento —eso es control de flujo— sino que HACE algo con él y devuelve un
|
|
99
|
+
resultado, igual que enviar un correo o escribir en una hoja. Además es
|
|
100
|
+
lo que decide qué se ofrece como acción en el flujo guiado del primer
|
|
101
|
+
flujo (`FIRST_FLOW_ACTIONS` en el dashboard), y ahí tiene que estar. */
|
|
102
|
+
group: 'Actions', detailPath: '/dashboard/ai-nodes', apiPath: '/ai-nodes',
|
|
103
|
+
stateKey: 'aiNodes', allStateKey: 'allAiNodes',
|
|
104
|
+
color: '#d946ef', testable: true,
|
|
105
|
+
},
|
|
106
|
+
split: {
|
|
107
|
+
type: 'split', prefix: 'split', label: 'Split',
|
|
108
|
+
group: 'Flow Control', detailPath: '/dashboard/split-nodes', apiPath: '/split-nodes',
|
|
109
|
+
stateKey: 'splitNodes', allStateKey: 'allSplitNodes',
|
|
110
|
+
color: '#38bdf8', testable: true,
|
|
111
|
+
},
|
|
112
|
+
merge: {
|
|
113
|
+
type: 'merge', prefix: 'merge', label: 'Merge',
|
|
114
|
+
group: 'Flow Control', detailPath: '/dashboard/merge-nodes', apiPath: '/merge-nodes',
|
|
115
|
+
stateKey: 'mergeNodes', allStateKey: 'allMergeNodes',
|
|
116
|
+
color: '#94a3b8', testable: false,
|
|
117
|
+
},
|
|
118
|
+
approval: {
|
|
119
|
+
type: 'approval', prefix: 'appr', label: 'Approval',
|
|
120
|
+
group: 'Flow Control', detailPath: '/dashboard/approval-nodes', apiPath: '/approval-nodes',
|
|
121
|
+
stateKey: 'approvalNodes', allStateKey: 'allApprovalNodes',
|
|
122
|
+
color: '#94a3b8', testable: true,
|
|
123
|
+
},
|
|
124
|
+
loop: {
|
|
125
|
+
type: 'loop', prefix: 'loop', label: 'Loop',
|
|
126
|
+
group: 'Flow Control', detailPath: '/dashboard/loop-nodes', apiPath: '/loop-nodes',
|
|
127
|
+
stateKey: 'loopNodes', allStateKey: 'allLoopNodes',
|
|
128
|
+
color: '#f59e0b', testable: true,
|
|
129
|
+
},
|
|
130
|
+
markdown: {
|
|
131
|
+
type: 'markdown', prefix: 'md', label: 'Markdown',
|
|
132
|
+
group: 'Flow Control', detailPath: '/dashboard/markdown-nodes', apiPath: '/markdown-nodes',
|
|
133
|
+
stateKey: 'markdownNodes', allStateKey: 'allMarkdownNodes',
|
|
134
|
+
color: '#6366f1', testable: true,
|
|
135
|
+
},
|
|
136
|
+
fileTransform: {
|
|
137
|
+
type: 'fileTransform', prefix: 'ft', label: 'File Transform',
|
|
138
|
+
group: 'Flow Control', detailPath: '/dashboard/file-transform-nodes', apiPath: '/file-transform-nodes',
|
|
139
|
+
stateKey: 'fileTransformNodes', allStateKey: 'allFileTransformNodes',
|
|
140
|
+
color: '#f97316', testable: true,
|
|
141
|
+
},
|
|
142
|
+
limit: {
|
|
143
|
+
type: 'limit', prefix: 'lim', label: 'Limit',
|
|
144
|
+
group: 'Flow Control', detailPath: '/dashboard/limit-nodes', apiPath: '/limit-nodes',
|
|
145
|
+
stateKey: 'limitNodes', allStateKey: 'allLimitNodes',
|
|
146
|
+
// Amber matches filter / aggregator family — visually groups
|
|
147
|
+
// "array-shape transformations" together on the canvas.
|
|
148
|
+
color: '#fbbf24', testable: true,
|
|
149
|
+
},
|
|
150
|
+
// ── Routing ──
|
|
151
|
+
router: {
|
|
152
|
+
type: 'router', prefix: 'ro', label: 'Router',
|
|
153
|
+
group: 'Flow Control', detailPath: '/dashboard/routers', apiPath: '/routers',
|
|
154
|
+
stateKey: 'routers', allStateKey: 'allRouters',
|
|
155
|
+
color: '#38bdf8', testable: true,
|
|
156
|
+
},
|
|
157
|
+
// ── Actions ──
|
|
158
|
+
/**
|
|
159
|
+
* Manda correo por Resend, y sólo por Resend.
|
|
160
|
+
*
|
|
161
|
+
* Tuvo un campo `provider` con Gmail y un Outlook que nunca llegó. Gmail se
|
|
162
|
+
* fue a su propio nodo —ver `gmailAction`— y con él su OAuth, sus dieciséis
|
|
163
|
+
* operaciones y el toolkit de IA, que era suyo: aquí sólo quedan enviar y
|
|
164
|
+
* el modo de IA no tiene dieciséis herramientas que ofrecer.
|
|
165
|
+
*/
|
|
166
|
+
emailAction: {
|
|
167
|
+
type: 'emailAction', prefix: 'ea', label: 'Email Action',
|
|
168
|
+
group: 'Actions', detailPath: '/dashboard/email-actions', apiPath: '/email-actions',
|
|
169
|
+
stateKey: 'emailActions', allStateKey: 'allEmailActions',
|
|
170
|
+
color: '#f472b6', testable: true,
|
|
171
|
+
/**
|
|
172
|
+
* Sigue siendo herramienta: hay una ruta native + aiEnabled donde el nodo
|
|
173
|
+
* AI genera el correo y este lo manda por Resend —en produccion existe un
|
|
174
|
+
* «using native email as tool»—. Se la quite al partir Gmail dando por
|
|
175
|
+
* hecho que el toolkit era solo suyo, y sin esto el nodo recupera sus
|
|
176
|
+
* handles en modo herramienta: justo el mal enrutado que este flag evita.
|
|
177
|
+
*/
|
|
178
|
+
isToolOnly: (e) => e.aiEnabled === true,
|
|
179
|
+
},
|
|
180
|
+
/**
|
|
181
|
+
* Gmail salio del nodo Email, que ahora manda SOLO por Resend.
|
|
182
|
+
*
|
|
183
|
+
* Estaban juntos bajo un campo `provider`, y eso obligaba a que un nodo de
|
|
184
|
+
* dos operaciones —enviar por Resend— cargara con las dieciseis de Gmail, su
|
|
185
|
+
* OAuth, su toolkit de IA y su send-and-wait. Partidos, cada uno se explica
|
|
186
|
+
* solo y el de Email vuelve a caber en una pantalla.
|
|
187
|
+
*/
|
|
188
|
+
gmailAction: {
|
|
189
|
+
type: 'gmailAction', prefix: 'gm', label: 'Gmail',
|
|
190
|
+
group: 'Actions', detailPath: '/dashboard/gmail-actions', apiPath: '/gmail-actions',
|
|
191
|
+
stateKey: 'gmailActions', allStateKey: 'allGmailActions',
|
|
192
|
+
color: '#ea4335', testable: true,
|
|
193
|
+
// El toolkit de IA se vino con Gmail: son sus dieciseis operaciones las
|
|
194
|
+
// que el LLM llama, no las dos de Resend. Ver el comentario de arriba.
|
|
195
|
+
isToolOnly: (e) => e.aiEnabled === true,
|
|
196
|
+
},
|
|
197
|
+
httpAction: {
|
|
198
|
+
type: 'httpAction', prefix: 'ha', label: 'HTTP Action',
|
|
199
|
+
group: 'Actions', detailPath: '/dashboard/http-actions', apiPath: '/http-actions',
|
|
200
|
+
stateKey: 'httpActions', allStateKey: 'allHttpActions',
|
|
201
|
+
color: '#3b82f6', testable: true,
|
|
202
|
+
},
|
|
203
|
+
mongoAction: {
|
|
204
|
+
type: 'mongoAction', prefix: 'ma', label: 'MongoDB Action',
|
|
205
|
+
group: 'Actions', detailPath: '/dashboard/mongo-actions', apiPath: '/mongo-actions',
|
|
206
|
+
stateKey: 'mongoActions', allStateKey: 'allMongoActions',
|
|
207
|
+
color: '#34d399', testable: true,
|
|
208
|
+
},
|
|
209
|
+
postgresAction: {
|
|
210
|
+
type: 'postgresAction', prefix: 'pg', label: 'Postgres Action',
|
|
211
|
+
group: 'Actions', detailPath: '/dashboard/postgres-actions', apiPath: '/postgres-actions',
|
|
212
|
+
stateKey: 'postgresActions', allStateKey: 'allPostgresActions',
|
|
213
|
+
color: '#336791', testable: true,
|
|
214
|
+
},
|
|
215
|
+
notificationAction: {
|
|
216
|
+
type: 'notificationAction', prefix: 'na', label: 'Notification',
|
|
217
|
+
group: 'Actions', detailPath: '/dashboard/notification-actions', apiPath: '/notification-actions',
|
|
218
|
+
stateKey: 'notificationActions', allStateKey: 'allNotificationActions',
|
|
219
|
+
color: '#f472b6', testable: true,
|
|
220
|
+
},
|
|
221
|
+
sheetsAction: {
|
|
222
|
+
type: 'sheetsAction', prefix: 'sheet', label: 'Google Sheets Action',
|
|
223
|
+
group: 'Actions', detailPath: '/dashboard/sheets-actions', apiPath: '/sheets-actions',
|
|
224
|
+
stateKey: 'sheetsActions', allStateKey: 'allSheetsActions',
|
|
225
|
+
color: '#34d399', testable: true,
|
|
226
|
+
},
|
|
227
|
+
calendarAction: {
|
|
228
|
+
type: 'calendarAction', prefix: 'cal', label: 'Google Calendar Action',
|
|
229
|
+
group: 'Actions', detailPath: '/dashboard/calendar-actions', apiPath: '/calendar-actions',
|
|
230
|
+
stateKey: 'calendarActions', allStateKey: 'allCalendarActions',
|
|
231
|
+
color: '#60a5fa', testable: true,
|
|
232
|
+
// AI Toolkit mode wires the node through the upstream AI Node's
|
|
233
|
+
// tools[] list, not via canvas edges — same pattern as emailAction.
|
|
234
|
+
isToolOnly: (e) => e.aiEnabled === true,
|
|
235
|
+
},
|
|
236
|
+
docsAction: {
|
|
237
|
+
type: 'docsAction', prefix: 'doc', label: 'Google Docs',
|
|
238
|
+
group: 'Actions', detailPath: '/dashboard/docs-actions', apiPath: '/docs-actions',
|
|
239
|
+
stateKey: 'docsActions', allStateKey: 'allDocsActions',
|
|
240
|
+
color: '#4285F4', testable: true,
|
|
241
|
+
},
|
|
242
|
+
driveAction: {
|
|
243
|
+
type: 'driveAction', prefix: 'drv', label: 'Google Drive',
|
|
244
|
+
group: 'Actions', detailPath: '/dashboard/drive-actions', apiPath: '/drive-actions',
|
|
245
|
+
stateKey: 'driveActions', allStateKey: 'allDriveActions',
|
|
246
|
+
color: '#22c55e', testable: true,
|
|
247
|
+
// AI Toolkit mode wires the node through the upstream AI Node's
|
|
248
|
+
// tools[] list, not via canvas edges — same pattern as emailAction.
|
|
249
|
+
isToolOnly: (e) => e.aiEnabled === true,
|
|
250
|
+
},
|
|
251
|
+
firecrawlAction: {
|
|
252
|
+
type: 'firecrawlAction', prefix: 'fc', label: 'Firecrawl',
|
|
253
|
+
group: 'Actions', detailPath: '/dashboard/firecrawl-actions', apiPath: '/firecrawl-actions',
|
|
254
|
+
stateKey: 'firecrawlActions', allStateKey: 'allFirecrawlActions',
|
|
255
|
+
color: '#f97316', testable: true,
|
|
256
|
+
},
|
|
257
|
+
mailchimpAction: {
|
|
258
|
+
type: 'mailchimpAction', prefix: 'mc', label: 'Mailchimp',
|
|
259
|
+
group: 'Actions', detailPath: '/dashboard/mailchimp-actions', apiPath: '/mailchimp-actions',
|
|
260
|
+
stateKey: 'mailchimpActions', allStateKey: 'allMailchimpActions',
|
|
261
|
+
// Mailchimp's Cavendish yellow, darkened until it holds contrast on a
|
|
262
|
+
// light canvas. The brand value (#ffe01b) reads as a highlighter there.
|
|
263
|
+
color: '#c9a227', testable: true,
|
|
264
|
+
},
|
|
265
|
+
shopifyAction: {
|
|
266
|
+
type: 'shopifyAction', prefix: 'shp', label: 'Shopify',
|
|
267
|
+
group: 'Actions', detailPath: '/dashboard/shopify-actions', apiPath: '/shopify-actions',
|
|
268
|
+
stateKey: 'shopifyActions', allStateKey: 'allShopifyActions',
|
|
269
|
+
// Shopify's green, abierto un punto. El de marca (#008060) es más oscuro
|
|
270
|
+
// que cualquier otro color de este registro y se hunde contra el lienzo
|
|
271
|
+
// en modo oscuro; éste sigue leyéndose como Shopify y aguanta en los dos
|
|
272
|
+
// temas. Mismo criterio que el amarillo de Mailchimp, al revés.
|
|
273
|
+
color: '#0a9e70', testable: true,
|
|
274
|
+
},
|
|
275
|
+
githubAction: {
|
|
276
|
+
type: 'githubAction', prefix: 'gh', label: 'GitHub',
|
|
277
|
+
group: 'Actions', detailPath: '/dashboard/github-actions', apiPath: '/github-actions',
|
|
278
|
+
stateKey: 'githubActions', allStateKey: 'allGithubActions',
|
|
279
|
+
// El morado de "merged" de GitHub. Su marca real es acromática, y ahí no
|
|
280
|
+
// hay elección posible: en el minimapa lo único que distingue a un nodo es
|
|
281
|
+
// el color, el gris cálido ya lo ocupa Notion y el frío lo llevan tres.
|
|
282
|
+
//
|
|
283
|
+
// ⚠️ Es la banda MÁS disputada de este registro — ocho nodos en morado,
|
|
284
|
+
// con #7c3aed y #a855f7 como vecinos inmediatos. Se eligió por ser el
|
|
285
|
+
// color que de verdad dice GitHub, pero **la decisión se cierra mirando el
|
|
286
|
+
// minimapa**, no leyendo este comentario. Si choca, el plan B es un
|
|
287
|
+
// grafito claro, sabiendo que compite con Notion.
|
|
288
|
+
color: '#8957e5', testable: true,
|
|
289
|
+
/* Sin isToolOnly: la v1 no trae modo toolkit de IA, y aquí pesa más que en
|
|
290
|
+
ningún otro nodo — el MCP oficial de GitHub ya cubre ese caso dentro del
|
|
291
|
+
AI Node. Los rieles son este nodo; la improvisación es el MCP. */
|
|
292
|
+
},
|
|
293
|
+
jiraAction: {
|
|
294
|
+
type: 'jiraAction', prefix: 'jr', label: 'Jira',
|
|
295
|
+
group: 'Actions', detailPath: '/dashboard/jira-actions', apiPath: '/jira-actions',
|
|
296
|
+
stateKey: 'jiraActions', allStateKey: 'allJiraActions',
|
|
297
|
+
// El azul de marca de Jira, y estaba libre. La banda azul es la segunda
|
|
298
|
+
// mas poblada de este registro —nueve nodos— pero ninguno tan profundo:
|
|
299
|
+
// #3b82f6 y #60a5fa son claramente mas claros, y el #336791 de Postgres es
|
|
300
|
+
// mas apagado. En el minimapa es el azul oscuro, y eso lo distingue.
|
|
301
|
+
//
|
|
302
|
+
// Sigue valiendo lo que escribio GitHub aqui al lado: **la decision se
|
|
303
|
+
// cierra mirando el minimapa**, no leyendo este comentario.
|
|
304
|
+
color: '#0052CC', testable: true,
|
|
305
|
+
/* Sin isToolOnly: la v1 no trae modo toolkit de IA. Cada toolkit vive dos
|
|
306
|
+
veces —specs del dashboard para el AI Node y specs de la api para MCP— y
|
|
307
|
+
esa duplicacion no se paga antes de ver el nodo en pantalla. Ademas
|
|
308
|
+
Atlassian tiene servidor MCP propio, ya registrado en
|
|
309
|
+
`MCP_OAUTH_PRESETS.atlassian`, que cubre la cola larga dentro del AI
|
|
310
|
+
Node. Los rieles son este nodo. */
|
|
311
|
+
},
|
|
312
|
+
telegramAction: {
|
|
313
|
+
type: 'telegramAction', prefix: 'tg', label: 'Telegram Action',
|
|
314
|
+
group: 'Actions', detailPath: '/dashboard/telegram-actions', apiPath: '/telegram-actions',
|
|
315
|
+
stateKey: 'telegramActions', allStateKey: 'allTelegramActions',
|
|
316
|
+
color: '#0ea5e9', testable: true,
|
|
317
|
+
// AI Toolkit mode — same pattern as emailAction. One aiEnabled
|
|
318
|
+
// telegramAction expands into N MCP tools (one per operation) in
|
|
319
|
+
// the upstream AI Node's tool list, so we hide canvas handles to
|
|
320
|
+
// prevent accidental misrouting that would silently fire with
|
|
321
|
+
// empty fields.
|
|
322
|
+
isToolOnly: (e) => e.aiEnabled === true,
|
|
323
|
+
},
|
|
324
|
+
whatsappAction: {
|
|
325
|
+
type: 'whatsappAction', prefix: 'wa', label: 'WhatsApp Action',
|
|
326
|
+
group: 'Actions', detailPath: '/dashboard/whatsapp-actions', apiPath: '/whatsapp-actions',
|
|
327
|
+
stateKey: 'whatsappActions', allStateKey: 'allWhatsappActions',
|
|
328
|
+
// WhatsApp brand green (#25d366) — instantly recognisable on the canvas.
|
|
329
|
+
color: '#25d366', testable: true,
|
|
330
|
+
// Same toolkit pattern as telegramAction — when aiEnabled the
|
|
331
|
+
// entity stops being a manually-wired single-op action and becomes
|
|
332
|
+
// a multi-op tool surface for an upstream AI Node. Hide canvas
|
|
333
|
+
// handles to prevent accidental wiring that would fire with empty
|
|
334
|
+
// fields the LLM is supposed to populate.
|
|
335
|
+
isToolOnly: (e) => e.aiEnabled === true,
|
|
336
|
+
},
|
|
337
|
+
discordAction: {
|
|
338
|
+
type: 'discordAction', prefix: 'dc', label: 'Discord Action',
|
|
339
|
+
group: 'Actions', detailPath: '/dashboard/discord-actions', apiPath: '/discord-actions',
|
|
340
|
+
stateKey: 'discordActions', allStateKey: 'allDiscordActions',
|
|
341
|
+
// Discord brand "Blurple" (#5865F2).
|
|
342
|
+
color: '#5865F2', testable: true,
|
|
343
|
+
// Same toolkit pattern as telegramAction / whatsappAction. Hide
|
|
344
|
+
// canvas handles when aiEnabled so the user can't manually wire a
|
|
345
|
+
// multi-op tool surface — the LLM picks the operation per call.
|
|
346
|
+
isToolOnly: (e) => e.aiEnabled === true,
|
|
347
|
+
},
|
|
348
|
+
slackAction: {
|
|
349
|
+
type: 'slackAction', prefix: 'sl', label: 'Slack Action',
|
|
350
|
+
group: 'Actions', detailPath: '/dashboard/slack-actions', apiPath: '/slack-actions',
|
|
351
|
+
stateKey: 'slackActions', allStateKey: 'allSlackActions',
|
|
352
|
+
// Slack brand purple ("aubergine"-derived family). Matches the
|
|
353
|
+
// accent color used by SlackConfigSection / ServiceTriggerNode for
|
|
354
|
+
// visual continuity across Slack-related nodes.
|
|
355
|
+
color: '#7c3aed', testable: true,
|
|
356
|
+
// Same AI-tool gating as Telegram/Discord/WhatsApp/Email/Calendar/
|
|
357
|
+
// Contacts. When aiEnabled the node fans out into 16 MCP tools
|
|
358
|
+
// (one per SLACK_OPERATIONS entry); hide canvas handles to prevent
|
|
359
|
+
// accidental wiring with empty fields the LLM is supposed to fill.
|
|
360
|
+
isToolOnly: (e) => e.aiEnabled === true,
|
|
361
|
+
},
|
|
362
|
+
googleContactsAction: {
|
|
363
|
+
type: 'googleContactsAction', prefix: 'gc', label: 'Google Contacts',
|
|
364
|
+
group: 'Actions', detailPath: '/dashboard/google-contacts-actions', apiPath: '/google-contacts-actions',
|
|
365
|
+
stateKey: 'googleContactsActions', allStateKey: 'allGoogleContactsActions',
|
|
366
|
+
// Google "blue" used by Google Drive / Docs (#4285F4) — same family.
|
|
367
|
+
color: '#4285F4', testable: true,
|
|
368
|
+
// AI toolkit mode mirrors emailAction / calendarAction — when
|
|
369
|
+
// aiEnabled the node expands into N MCP tools (one per People API
|
|
370
|
+
// operation) in the upstream AI Node's tool list. Hide canvas
|
|
371
|
+
// handles to keep manual + tool modes from being mixed.
|
|
372
|
+
isToolOnly: (e) => e.aiEnabled === true,
|
|
373
|
+
},
|
|
374
|
+
googleAnalyticsAction: {
|
|
375
|
+
type: 'googleAnalyticsAction', prefix: 'gaa', label: 'Google Analytics',
|
|
376
|
+
group: 'Actions', detailPath: '/dashboard/google-analytics-actions', apiPath: '/google-analytics-actions',
|
|
377
|
+
stateKey: 'googleAnalyticsActions', allStateKey: 'allGoogleAnalyticsActions',
|
|
378
|
+
// Naranja de marca de Google Analytics (#E37400). No reutiliza el
|
|
379
|
+
// #ff9933 de RSS ni el orange-500 de Tailwind que ya llevan tres nodos:
|
|
380
|
+
// en el minimapa lo unico que distingue a un nodo es el color.
|
|
381
|
+
color: '#E37400', testable: true,
|
|
382
|
+
/* Sin isToolOnly: la v1 no trae modo toolkit de IA. Cada toolkit vive
|
|
383
|
+
dos veces --specs del dashboard para el AI Node y specs de la api para
|
|
384
|
+
MCP, sin nada que las ate-- y esa duplicacion no se paga antes de ver
|
|
385
|
+
el nodo funcionando en pantalla. */
|
|
386
|
+
},
|
|
387
|
+
notionAction: {
|
|
388
|
+
type: 'notionAction', prefix: 'ntn', label: 'Notion',
|
|
389
|
+
group: 'Actions', detailPath: '/dashboard/notion-actions', apiPath: '/notion-actions',
|
|
390
|
+
stateKey: 'notionActions', allStateKey: 'allNotionActions',
|
|
391
|
+
// Notion es monocromo de marca (su negro es #37352F), y un punto negro
|
|
392
|
+
// en el minimapa desaparece en modo oscuro. Este stone-500 es el gris
|
|
393
|
+
// CÁLIDO más cercano a esa marca, y no choca con el #94a3b8 (slate, gris
|
|
394
|
+
// frío) que ya llevan tres nodos: en el minimapa lo único que distingue
|
|
395
|
+
// a un nodo es el color.
|
|
396
|
+
color: '#78716c', testable: true,
|
|
397
|
+
/* Sin isToolOnly: la v1 no trae modo toolkit de IA. Cada toolkit vive dos
|
|
398
|
+
veces --specs del dashboard para el AI Node y specs de la api para
|
|
399
|
+
MCP-- y esa duplicación no se paga antes de ver el nodo en pantalla. */
|
|
400
|
+
},
|
|
401
|
+
vectorStore: {
|
|
402
|
+
type: 'vectorStore', prefix: 'vs', label: 'Vector Store',
|
|
403
|
+
group: 'Actions', detailPath: '/dashboard/vector-store-nodes', apiPath: '/vector-store-nodes',
|
|
404
|
+
stateKey: 'vectorStoreNodes', allStateKey: 'allVectorStoreNodes',
|
|
405
|
+
color: '#06b6d4', testable: true,
|
|
406
|
+
},
|
|
407
|
+
rssAction: {
|
|
408
|
+
type: 'rssAction', prefix: 'rssa', label: 'RSS Fetch',
|
|
409
|
+
group: 'Actions', detailPath: '/dashboard/rss-actions', apiPath: '/rss-actions',
|
|
410
|
+
stateKey: 'rssActions', allStateKey: 'allRssActions',
|
|
411
|
+
// Classic RSS feed orange (#ff9933) — distinguishes from rateLimiter /
|
|
412
|
+
// fileTransform / firecrawl which sit on Tailwind orange-500.
|
|
413
|
+
color: '#ff9933', testable: true,
|
|
414
|
+
},
|
|
415
|
+
socialMediaAction: {
|
|
416
|
+
type: 'socialMediaAction', prefix: 'sm', label: 'Social Media',
|
|
417
|
+
group: 'Actions', detailPath: '/dashboard/social-media-actions', apiPath: '/social-media-actions',
|
|
418
|
+
stateKey: 'socialMediaActions', allStateKey: 'allSocialMediaActions',
|
|
419
|
+
// Pink/magenta (#ec4899) — neutral default; the canvas card overrides
|
|
420
|
+
// with the picked provider's brandColor at render time so each
|
|
421
|
+
// platform reads at a glance (Bluesky #0085ff, Mastodon #6364ff, ...).
|
|
422
|
+
color: '#ec4899', testable: true,
|
|
423
|
+
// AI toolkit mode (Sprint 4) wires through tools[] like the other
|
|
424
|
+
// toolkit-capable actions. Manual + toolkit modes share the same
|
|
425
|
+
// detail page; the segmented control hides ops-config when toolkit.
|
|
426
|
+
isToolOnly: (e) => e.aiEnabled === true,
|
|
427
|
+
},
|
|
428
|
+
// ── Annotations ──
|
|
429
|
+
stickyNote: {
|
|
430
|
+
type: 'stickyNote', prefix: 'note', label: 'Sticky Note',
|
|
431
|
+
group: 'Annotations', detailPath: '', apiPath: '/sticky-notes',
|
|
432
|
+
stateKey: 'stickyNotes', allStateKey: 'allStickyNotes',
|
|
433
|
+
color: '#fbbf24', testable: false,
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
// ── Derived helpers ──
|
|
437
|
+
/** Get registry entry by canonical type */
|
|
438
|
+
export function getNodeRegistryEntry(type) {
|
|
439
|
+
return NODE_REGISTRY[type];
|
|
440
|
+
}
|
|
441
|
+
/** type → detail page path */
|
|
442
|
+
export const NODE_DETAIL_PATHS = Object.fromEntries(Object.values(NODE_REGISTRY).filter(n => n.detailPath).map(n => [n.type, n.detailPath]));
|
|
443
|
+
/** type → minimap color */
|
|
444
|
+
export const NODE_COLORS = Object.fromEntries(Object.values(NODE_REGISTRY).map(n => [n.type, n.color]));
|
|
445
|
+
/** type → [stateKey, allStateKey] */
|
|
446
|
+
export const NODE_STATE_KEYS = Object.fromEntries(Object.values(NODE_REGISTRY).map(n => [n.type, [n.stateKey, n.allStateKey]]));
|
|
447
|
+
/** prefix → type */
|
|
448
|
+
export const PREFIX_TO_NODE_TYPE = Object.fromEntries(Object.values(NODE_REGISTRY).map(n => [n.prefix, n.type]));
|
|
449
|
+
/** type → prefix */
|
|
450
|
+
export const NODE_TYPE_TO_PREFIX = Object.fromEntries(Object.values(NODE_REGISTRY).map(n => [n.type, n.prefix]));
|
|
451
|
+
function specsFor(type, catalog) {
|
|
452
|
+
return catalog[type]?.versions;
|
|
453
|
+
}
|
|
454
|
+
/** True when this type ships more than one version. False for every type today. */
|
|
455
|
+
export function isVersioned(type, catalog = NODE_REGISTRY) {
|
|
456
|
+
return (specsFor(type, catalog)?.length ?? 0) > 0;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Every declared version, newest first — the order a picker wants.
|
|
460
|
+
* Empty for a type that does not version.
|
|
461
|
+
*/
|
|
462
|
+
export function versionsOf(type, catalog = NODE_REGISTRY) {
|
|
463
|
+
const declared = specsFor(type, catalog);
|
|
464
|
+
if (!declared)
|
|
465
|
+
return [];
|
|
466
|
+
return [...declared].sort((a, b) => b.v - a.v);
|
|
467
|
+
}
|
|
468
|
+
/** One version's spec, or undefined when the type or the number is unknown. */
|
|
469
|
+
export function versionSpec(type, v, catalog = NODE_REGISTRY) {
|
|
470
|
+
return specsFor(type, catalog)?.find(s => s.v === v);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* The version a new node of this type is born on.
|
|
474
|
+
*
|
|
475
|
+
* Returns 1 for a type that does not version, which is also how an entity
|
|
476
|
+
* with no `nodeVersion` is read. A node created before its type ever
|
|
477
|
+
* versioned was calling the original endpoints, so calling it version 1
|
|
478
|
+
* is not a convention — it is the truth.
|
|
479
|
+
*/
|
|
480
|
+
export function currentVersion(type, catalog = NODE_REGISTRY) {
|
|
481
|
+
const current = specsFor(type, catalog)?.find(s => s.status === 'current');
|
|
482
|
+
return current?.v ?? 1;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Config fields present in `from` that `to` does not declare — the exact
|
|
486
|
+
* list a confirmation dialog should name, and the exact list the server
|
|
487
|
+
* should unset.
|
|
488
|
+
*
|
|
489
|
+
* Returns `null` when the question cannot be answered: the type does not
|
|
490
|
+
* version, or one of the two versions is not declared. `null` is not the
|
|
491
|
+
* same as "nothing is lost", and callers must not treat it that way — a
|
|
492
|
+
* dialog should refuse to offer the switch, and the api should reject the
|
|
493
|
+
* request. Returning `null` rather than `[]` is what stops "I don't know"
|
|
494
|
+
* from being read as "nothing".
|
|
495
|
+
*
|
|
496
|
+
* This lives here rather than in each consumer because the dialog that
|
|
497
|
+
* ANNOUNCES the loss and the server that PERFORMS it have to give the
|
|
498
|
+
* same answer. Two implementations would agree right until the day they
|
|
499
|
+
* did not, and that day the dialog lies.
|
|
500
|
+
*/
|
|
501
|
+
export function fieldsLostBetween(type, from, to, catalog = NODE_REGISTRY) {
|
|
502
|
+
const a = versionSpec(type, from, catalog);
|
|
503
|
+
const b = versionSpec(type, to, catalog);
|
|
504
|
+
if (!a || !b)
|
|
505
|
+
return null;
|
|
506
|
+
return fieldsLost(a, b);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* The same answer for two specs already in hand — what `fieldsLostBetween`
|
|
510
|
+
* delegates to once it has resolved both, and what a caller that already
|
|
511
|
+
* holds them from `versionsOf()` can use without a second lookup.
|
|
512
|
+
*/
|
|
513
|
+
export function fieldsLost(from, to) {
|
|
514
|
+
if (from.v === to.v)
|
|
515
|
+
return [];
|
|
516
|
+
if (to.cleanBreak)
|
|
517
|
+
return [...from.fields];
|
|
518
|
+
const kept = new Set(to.fields);
|
|
519
|
+
return from.fields.filter(f => !kept.has(f));
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Declaration mistakes in the version catalogs, as readable lines.
|
|
523
|
+
*
|
|
524
|
+
* A guard rather than a line in a document: what this codebase learned
|
|
525
|
+
* from fifteen nodes skipping a checklist point everyone had read is that
|
|
526
|
+
* the written rule prevented nothing and the guard did. Empty means every
|
|
527
|
+
* catalog is well-formed.
|
|
528
|
+
*/
|
|
529
|
+
export function versionCatalogErrors(catalog = NODE_REGISTRY) {
|
|
530
|
+
const errors = [];
|
|
531
|
+
for (const entry of Object.values(catalog)) {
|
|
532
|
+
const versions = entry.versions;
|
|
533
|
+
if (!versions)
|
|
534
|
+
continue;
|
|
535
|
+
if (versions.length === 0) {
|
|
536
|
+
errors.push(`${entry.type}: declares an empty versions array — omit the field instead`);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const numbers = versions.map(s => s.v);
|
|
540
|
+
const repeatedNumbers = [...new Set(numbers.filter((n, i) => numbers.indexOf(n) !== i))];
|
|
541
|
+
if (repeatedNumbers.length)
|
|
542
|
+
errors.push(`${entry.type}: repeated version number ${repeatedNumbers.join(', ')}`);
|
|
543
|
+
for (const spec of versions) {
|
|
544
|
+
if (!Number.isInteger(spec.v) || spec.v < 1)
|
|
545
|
+
errors.push(`${entry.type}: version "${spec.v}" is not a positive integer`);
|
|
546
|
+
const repeatedFields = [...new Set(spec.fields.filter((f, i) => spec.fields.indexOf(f) !== i))];
|
|
547
|
+
if (repeatedFields.length)
|
|
548
|
+
errors.push(`${entry.type} v${spec.v}: repeated field ${repeatedFields.join(', ')}`);
|
|
549
|
+
}
|
|
550
|
+
const current = versions.filter(s => s.status === 'current');
|
|
551
|
+
if (current.length !== 1)
|
|
552
|
+
errors.push(`${entry.type}: needs exactly one version with status "current", found ${current.length}`);
|
|
553
|
+
}
|
|
554
|
+
return errors;
|
|
555
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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 declare const SHEETS_OPERATIONS: readonly ["appendRow", "updateRow", "appendOrUpdateRow", "readRange", "getRows", "createSpreadsheet"];
|
|
12
|
+
export type SheetsOperation = (typeof SHEETS_OPERATIONS)[number];
|
|
13
|
+
/** Type guard — useful when validating untrusted input (DTOs, AI tool calls). */
|
|
14
|
+
export declare function isSheetsOperation(value: unknown): value is SheetsOperation;
|
|
15
|
+
/**
|
|
16
|
+
* Per-operation form schema, same idea as SLACK_OPERATION_SPECS and
|
|
17
|
+
* GOOGLE_CONTACTS_OPERATION_SPECS: the detail page renders it with one loop
|
|
18
|
+
* instead of a branch per operation.
|
|
19
|
+
*
|
|
20
|
+
* Two things make this node's schema different from the others, and both are
|
|
21
|
+
* deliberate:
|
|
22
|
+
*
|
|
23
|
+
* 1. **The values are top-level entity fields, not an `operationConfig`
|
|
24
|
+
* record.** `name` here is the property on the sheetsAction document
|
|
25
|
+
* (`spreadsheetId`, `sheetName`, …), and `columnMapping` is a complex field.
|
|
26
|
+
* A renderer copied from Contacts would write to the wrong place.
|
|
27
|
+
* 2. **Half the fields are discovered at run time.** The column list comes from
|
|
28
|
+
* the chosen sheet's header row, fetched live. So the schema names the
|
|
29
|
+
* *picker* rather than carrying options — the same trick Discord's
|
|
30
|
+
* `channel` / `guild` / `role` types use.
|
|
31
|
+
*/
|
|
32
|
+
export interface SheetsParamSpec {
|
|
33
|
+
/** Field key — the property name on the sheetsAction document. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** UI label. Empty string where the control has never shown one. */
|
|
36
|
+
label: string;
|
|
37
|
+
/**
|
|
38
|
+
* Param type for form rendering.
|
|
39
|
+
*
|
|
40
|
+
* - `string` — one-line input with `{{payload.x}}` templating.
|
|
41
|
+
* - `spreadsheet` — the spreadsheet picker **plus** the "or paste ID / URL"
|
|
42
|
+
* input that sits under it. They are one field with two ways in, not two
|
|
43
|
+
* fields, and the paste box extracts the id out of a full URL.
|
|
44
|
+
* - `sheetTab` — the tab picker for the chosen spreadsheet. Picking a tab
|
|
45
|
+
* clears `columnMapping`, because the headers just changed.
|
|
46
|
+
* - `headerColumn` — a dropdown of the sheet's live header row.
|
|
47
|
+
* - `columnMapping` — the header-to-template editor. It owns the three
|
|
48
|
+
* states of the header fetch (loading, loaded, failed), which is what
|
|
49
|
+
* used to be six copies of the same conditional in the page.
|
|
50
|
+
*/
|
|
51
|
+
type: 'string' | 'spreadsheet' | 'sheetTab' | 'headerColumn' | 'columnMapping';
|
|
52
|
+
/** Required by the operation. Metadata — the form does not gate on it. */
|
|
53
|
+
required?: boolean;
|
|
54
|
+
/** Help text under the control. Absent where the form shows none. */
|
|
55
|
+
description?: string;
|
|
56
|
+
/** Hint shown inside the input. */
|
|
57
|
+
placeholder?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Section this field is rendered inside. Fields without one share the first
|
|
60
|
+
* section with the operation picker; the mapping fields sit in a second
|
|
61
|
+
* section whose title changes per operation ("Columns to Append",
|
|
62
|
+
* "Update Row", "Match & Upsert").
|
|
63
|
+
*/
|
|
64
|
+
section?: string;
|
|
65
|
+
/** `columnMapping` only — the line shown while no column has been added. */
|
|
66
|
+
emptyText?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface SheetsOperationSpec {
|
|
69
|
+
/** UI label for the operation picker. */
|
|
70
|
+
label: string;
|
|
71
|
+
/** Sub-line under the label in the picker. */
|
|
72
|
+
description: string;
|
|
73
|
+
/** googleapis call the operation maps to (sheets v4). */
|
|
74
|
+
apiMethod: string;
|
|
75
|
+
/** Parameter schema. Order matters for form rendering. */
|
|
76
|
+
params: SheetsParamSpec[];
|
|
77
|
+
}
|
|
78
|
+
export declare const SHEETS_OPERATION_SPECS: Record<SheetsOperation, SheetsOperationSpec>;
|