@hostwebhook/node-types 1.70.0 → 1.71.1

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/registry.js CHANGED
@@ -19,151 +19,289 @@ exports.versionCatalogErrors = versionCatalogErrors;
19
19
  exports.NODE_REGISTRY = {
20
20
  // ── Sources ──
21
21
  webhook: {
22
- type: 'webhook', prefix: 'wh', label: 'Webhook',
23
- group: 'Sources', detailPath: '/dashboard/webhooks', apiPath: '/webhooks',
24
- stateKey: 'webhooks', allStateKey: 'allWebhooks',
25
- color: '#a78bfa', testable: false,
22
+ type: "webhook",
23
+ prefix: "wh",
24
+ label: "Webhook",
25
+ group: "Sources",
26
+ detailPath: "/dashboard/webhooks",
27
+ apiPath: "/webhooks",
28
+ stateKey: "webhooks",
29
+ allStateKey: "allWebhooks",
30
+ color: "#a78bfa",
31
+ testable: false,
26
32
  },
27
33
  scheduledWorkflow: {
28
- type: 'scheduledWorkflow', prefix: 'swf', label: 'Scheduled Workflow',
29
- group: 'Sources', detailPath: '/dashboard/scheduled-workflows', apiPath: '/scheduled-workflows',
30
- stateKey: 'scheduledWorkflows', allStateKey: 'allScheduledWorkflows',
31
- color: '#2dd4bf', testable: true,
34
+ type: "scheduledWorkflow",
35
+ prefix: "swf",
36
+ label: "Scheduled Workflow",
37
+ group: "Sources",
38
+ detailPath: "/dashboard/scheduled-workflows",
39
+ apiPath: "/scheduled-workflows",
40
+ stateKey: "scheduledWorkflows",
41
+ allStateKey: "allScheduledWorkflows",
42
+ color: "#2dd4bf",
43
+ testable: true,
32
44
  },
33
45
  chatTrigger: {
34
- type: 'chatTrigger', prefix: 'chat', label: 'Chat Trigger',
35
- group: 'Sources', detailPath: '/dashboard/chat-triggers', apiPath: '/chat-triggers',
36
- stateKey: 'chatTriggers', allStateKey: 'allChatTriggers',
37
- color: '#06b6d4', testable: false,
46
+ type: "chatTrigger",
47
+ prefix: "chat",
48
+ label: "Chat Trigger",
49
+ group: "Sources",
50
+ detailPath: "/dashboard/chat-triggers",
51
+ apiPath: "/chat-triggers",
52
+ stateKey: "chatTriggers",
53
+ allStateKey: "allChatTriggers",
54
+ color: "#06b6d4",
55
+ testable: false,
38
56
  },
39
57
  trigger: {
40
- type: 'trigger', prefix: 'trg', label: 'Trigger',
41
- group: 'Sources', detailPath: '/dashboard/triggers', apiPath: '/triggers',
42
- stateKey: 'triggers', allStateKey: 'allTriggers',
43
- color: '#ef4444', testable: false,
58
+ type: "trigger",
59
+ prefix: "trg",
60
+ label: "Trigger",
61
+ group: "Sources",
62
+ detailPath: "/dashboard/triggers",
63
+ apiPath: "/triggers",
64
+ stateKey: "triggers",
65
+ allStateKey: "allTriggers",
66
+ color: "#ef4444",
67
+ testable: false,
44
68
  },
45
69
  voiceAgent: {
46
- type: 'voiceAgent', prefix: 'va', label: 'Voice Agent',
47
- group: 'Sources', detailPath: '/dashboard/voice-agents', apiPath: '/voice-agents',
48
- stateKey: 'voiceAgents', allStateKey: 'allVoiceAgents',
49
- color: '#a855f7', testable: false,
70
+ type: "voiceAgent",
71
+ prefix: "va",
72
+ label: "Voice Agent",
73
+ group: "Sources",
74
+ detailPath: "/dashboard/voice-agents",
75
+ apiPath: "/voice-agents",
76
+ stateKey: "voiceAgents",
77
+ allStateKey: "allVoiceAgents",
78
+ color: "#a855f7",
79
+ testable: false,
50
80
  },
51
81
  // ── Flow Control ──
52
82
  filter: {
53
- type: 'filter', prefix: 'fi', label: 'Filter',
54
- group: 'Flow Control', detailPath: '/dashboard/filter-nodes', apiPath: '/filter-nodes',
55
- stateKey: 'filterNodes', allStateKey: 'allFilterNodes',
56
- color: '#facc15', testable: true,
83
+ type: "filter",
84
+ prefix: "fi",
85
+ label: "Filter",
86
+ group: "Flow Control",
87
+ detailPath: "/dashboard/filter-nodes",
88
+ apiPath: "/filter-nodes",
89
+ stateKey: "filterNodes",
90
+ allStateKey: "allFilterNodes",
91
+ color: "#facc15",
92
+ testable: true,
57
93
  },
58
94
  transform: {
59
- type: 'transform', prefix: 'tr', label: 'Transform',
60
- group: 'Flow Control', detailPath: '/dashboard/transform-nodes', apiPath: '/transform-nodes',
61
- stateKey: 'transformNodes', allStateKey: 'allTransformNodes',
62
- color: '#fbbf24', testable: true,
95
+ type: "transform",
96
+ prefix: "tr",
97
+ label: "Transform",
98
+ group: "Flow Control",
99
+ detailPath: "/dashboard/transform-nodes",
100
+ apiPath: "/transform-nodes",
101
+ stateKey: "transformNodes",
102
+ allStateKey: "allTransformNodes",
103
+ color: "#fbbf24",
104
+ testable: true,
63
105
  },
64
106
  schemaValidator: {
65
- type: 'schemaValidator', prefix: 'sv', label: 'Schema Validator',
66
- group: 'Flow Control', detailPath: '/dashboard/schema-validators', apiPath: '/schema-validators',
67
- stateKey: 'schemaValidators', allStateKey: 'allSchemaValidators',
68
- color: '#818cf8', testable: true,
107
+ type: "schemaValidator",
108
+ prefix: "sv",
109
+ label: "Schema Validator",
110
+ group: "Flow Control",
111
+ detailPath: "/dashboard/schema-validators",
112
+ apiPath: "/schema-validators",
113
+ stateKey: "schemaValidators",
114
+ allStateKey: "allSchemaValidators",
115
+ color: "#818cf8",
116
+ testable: true,
69
117
  },
70
118
  conditional: {
71
- type: 'conditional', prefix: 'cond', label: 'Conditional',
72
- group: 'Flow Control', detailPath: '/dashboard/conditional-nodes', apiPath: '/conditional-nodes',
73
- stateKey: 'conditionalNodes', allStateKey: 'allConditionalNodes',
74
- color: '#c084fc', testable: true,
119
+ type: "conditional",
120
+ prefix: "cond",
121
+ label: "Conditional",
122
+ group: "Flow Control",
123
+ detailPath: "/dashboard/conditional-nodes",
124
+ apiPath: "/conditional-nodes",
125
+ stateKey: "conditionalNodes",
126
+ allStateKey: "allConditionalNodes",
127
+ color: "#c084fc",
128
+ testable: true,
75
129
  },
76
130
  delay: {
77
- type: 'delay', prefix: 'delay', label: 'Delay',
78
- group: 'Flow Control', detailPath: '/dashboard/delay-nodes', apiPath: '/delay-nodes',
79
- stateKey: 'delayNodes', allStateKey: 'allDelayNodes',
80
- color: '#94a3b8', testable: true,
131
+ type: "delay",
132
+ prefix: "delay",
133
+ label: "Delay",
134
+ group: "Flow Control",
135
+ detailPath: "/dashboard/delay-nodes",
136
+ apiPath: "/delay-nodes",
137
+ stateKey: "delayNodes",
138
+ allStateKey: "allDelayNodes",
139
+ color: "#94a3b8",
140
+ testable: true,
81
141
  },
82
142
  rateLimiter: {
83
- type: 'rateLimiter', prefix: 'rl', label: 'Rate Limiter',
84
- group: 'Flow Control', detailPath: '/dashboard/rate-limiter-nodes', apiPath: '/rate-limiter-nodes',
85
- stateKey: 'rateLimiterNodes', allStateKey: 'allRateLimiterNodes',
86
- color: '#f97316', testable: false,
143
+ type: "rateLimiter",
144
+ prefix: "rl",
145
+ label: "Rate Limiter",
146
+ group: "Flow Control",
147
+ detailPath: "/dashboard/rate-limiter-nodes",
148
+ apiPath: "/rate-limiter-nodes",
149
+ stateKey: "rateLimiterNodes",
150
+ allStateKey: "allRateLimiterNodes",
151
+ color: "#f97316",
152
+ testable: false,
87
153
  },
88
154
  aggregator: {
89
- type: 'aggregator', prefix: 'agg', label: 'Aggregator',
90
- group: 'Flow Control', detailPath: '/dashboard/aggregator-nodes', apiPath: '/aggregator-nodes',
91
- stateKey: 'aggregatorNodes', allStateKey: 'allAggregatorNodes',
92
- color: '#84cc16', testable: true,
155
+ type: "aggregator",
156
+ prefix: "agg",
157
+ label: "Aggregator",
158
+ group: "Flow Control",
159
+ detailPath: "/dashboard/aggregator-nodes",
160
+ apiPath: "/aggregator-nodes",
161
+ stateKey: "aggregatorNodes",
162
+ allStateKey: "allAggregatorNodes",
163
+ color: "#84cc16",
164
+ testable: true,
93
165
  },
94
166
  cache: {
95
- type: 'cache', prefix: 'cache', label: 'Cache',
96
- group: 'Flow Control', detailPath: '/dashboard/cache-nodes', apiPath: '/cache-nodes',
97
- stateKey: 'cacheNodes', allStateKey: 'allCacheNodes',
98
- color: '#d946ef', testable: true,
167
+ type: "cache",
168
+ prefix: "cache",
169
+ label: "Cache",
170
+ group: "Flow Control",
171
+ detailPath: "/dashboard/cache-nodes",
172
+ apiPath: "/cache-nodes",
173
+ stateKey: "cacheNodes",
174
+ allStateKey: "allCacheNodes",
175
+ color: "#d946ef",
176
+ testable: true,
99
177
  },
100
178
  code: {
101
- type: 'code', prefix: 'code', label: 'Code',
102
- group: 'Flow Control', detailPath: '/dashboard/code-nodes', apiPath: '/code-nodes',
103
- stateKey: 'codeNodes', allStateKey: 'allCodeNodes',
104
- color: '#a855f7', testable: true,
179
+ type: "code",
180
+ prefix: "code",
181
+ label: "Code",
182
+ group: "Flow Control",
183
+ detailPath: "/dashboard/code-nodes",
184
+ apiPath: "/code-nodes",
185
+ stateKey: "codeNodes",
186
+ allStateKey: "allCodeNodes",
187
+ color: "#a855f7",
188
+ testable: true,
105
189
  },
106
190
  ai: {
107
- type: 'ai', prefix: 'ai', label: 'AI',
191
+ type: "ai",
192
+ prefix: "ai",
193
+ label: "AI",
108
194
  /* 'Actions' y no 'Flow Control': un nodo de IA no encamina ni retiene un
109
195
  evento —eso es control de flujo— sino que HACE algo con él y devuelve un
110
196
  resultado, igual que enviar un correo o escribir en una hoja. Además es
111
197
  lo que decide qué se ofrece como acción en el flujo guiado del primer
112
198
  flujo (`FIRST_FLOW_ACTIONS` en el dashboard), y ahí tiene que estar. */
113
- group: 'Actions', detailPath: '/dashboard/ai-nodes', apiPath: '/ai-nodes',
114
- stateKey: 'aiNodes', allStateKey: 'allAiNodes',
115
- color: '#d946ef', testable: true,
199
+ group: "Actions",
200
+ detailPath: "/dashboard/ai-nodes",
201
+ apiPath: "/ai-nodes",
202
+ stateKey: "aiNodes",
203
+ allStateKey: "allAiNodes",
204
+ color: "#d946ef",
205
+ testable: true,
116
206
  },
117
207
  split: {
118
- type: 'split', prefix: 'split', label: 'Split',
119
- group: 'Flow Control', detailPath: '/dashboard/split-nodes', apiPath: '/split-nodes',
120
- stateKey: 'splitNodes', allStateKey: 'allSplitNodes',
121
- color: '#38bdf8', testable: true,
208
+ type: "split",
209
+ prefix: "split",
210
+ label: "Split",
211
+ group: "Flow Control",
212
+ detailPath: "/dashboard/split-nodes",
213
+ apiPath: "/split-nodes",
214
+ stateKey: "splitNodes",
215
+ allStateKey: "allSplitNodes",
216
+ color: "#38bdf8",
217
+ testable: true,
122
218
  },
123
219
  merge: {
124
- type: 'merge', prefix: 'merge', label: 'Merge',
125
- group: 'Flow Control', detailPath: '/dashboard/merge-nodes', apiPath: '/merge-nodes',
126
- stateKey: 'mergeNodes', allStateKey: 'allMergeNodes',
127
- color: '#94a3b8', testable: false,
220
+ type: "merge",
221
+ prefix: "merge",
222
+ label: "Merge",
223
+ group: "Flow Control",
224
+ detailPath: "/dashboard/merge-nodes",
225
+ apiPath: "/merge-nodes",
226
+ stateKey: "mergeNodes",
227
+ allStateKey: "allMergeNodes",
228
+ color: "#94a3b8",
229
+ testable: false,
128
230
  },
129
231
  approval: {
130
- type: 'approval', prefix: 'appr', label: 'Approval',
131
- group: 'Flow Control', detailPath: '/dashboard/approval-nodes', apiPath: '/approval-nodes',
132
- stateKey: 'approvalNodes', allStateKey: 'allApprovalNodes',
133
- color: '#94a3b8', testable: true,
232
+ type: "approval",
233
+ prefix: "appr",
234
+ label: "Approval",
235
+ group: "Flow Control",
236
+ detailPath: "/dashboard/approval-nodes",
237
+ apiPath: "/approval-nodes",
238
+ stateKey: "approvalNodes",
239
+ allStateKey: "allApprovalNodes",
240
+ color: "#94a3b8",
241
+ testable: true,
134
242
  },
135
243
  loop: {
136
- type: 'loop', prefix: 'loop', label: 'Loop',
137
- group: 'Flow Control', detailPath: '/dashboard/loop-nodes', apiPath: '/loop-nodes',
138
- stateKey: 'loopNodes', allStateKey: 'allLoopNodes',
139
- color: '#f59e0b', testable: true,
244
+ type: "loop",
245
+ prefix: "loop",
246
+ label: "Loop",
247
+ group: "Flow Control",
248
+ detailPath: "/dashboard/loop-nodes",
249
+ apiPath: "/loop-nodes",
250
+ stateKey: "loopNodes",
251
+ allStateKey: "allLoopNodes",
252
+ color: "#f59e0b",
253
+ testable: true,
140
254
  },
141
255
  markdown: {
142
- type: 'markdown', prefix: 'md', label: 'Markdown',
143
- group: 'Flow Control', detailPath: '/dashboard/markdown-nodes', apiPath: '/markdown-nodes',
144
- stateKey: 'markdownNodes', allStateKey: 'allMarkdownNodes',
145
- color: '#6366f1', testable: true,
256
+ type: "markdown",
257
+ prefix: "md",
258
+ label: "Markdown",
259
+ group: "Flow Control",
260
+ detailPath: "/dashboard/markdown-nodes",
261
+ apiPath: "/markdown-nodes",
262
+ stateKey: "markdownNodes",
263
+ allStateKey: "allMarkdownNodes",
264
+ color: "#6366f1",
265
+ testable: true,
146
266
  },
147
267
  fileTransform: {
148
- type: 'fileTransform', prefix: 'ft', label: 'File Transform',
149
- group: 'Flow Control', detailPath: '/dashboard/file-transform-nodes', apiPath: '/file-transform-nodes',
150
- stateKey: 'fileTransformNodes', allStateKey: 'allFileTransformNodes',
151
- color: '#f97316', testable: true,
268
+ type: "fileTransform",
269
+ prefix: "ft",
270
+ label: "File Transform",
271
+ group: "Flow Control",
272
+ detailPath: "/dashboard/file-transform-nodes",
273
+ apiPath: "/file-transform-nodes",
274
+ stateKey: "fileTransformNodes",
275
+ allStateKey: "allFileTransformNodes",
276
+ color: "#f97316",
277
+ testable: true,
152
278
  },
153
279
  limit: {
154
- type: 'limit', prefix: 'lim', label: 'Limit',
155
- group: 'Flow Control', detailPath: '/dashboard/limit-nodes', apiPath: '/limit-nodes',
156
- stateKey: 'limitNodes', allStateKey: 'allLimitNodes',
280
+ type: "limit",
281
+ prefix: "lim",
282
+ label: "Limit",
283
+ group: "Flow Control",
284
+ detailPath: "/dashboard/limit-nodes",
285
+ apiPath: "/limit-nodes",
286
+ stateKey: "limitNodes",
287
+ allStateKey: "allLimitNodes",
157
288
  // Amber matches filter / aggregator family — visually groups
158
289
  // "array-shape transformations" together on the canvas.
159
- color: '#fbbf24', testable: true,
290
+ color: "#fbbf24",
291
+ testable: true,
160
292
  },
161
293
  // ── Routing ──
162
294
  router: {
163
- type: 'router', prefix: 'ro', label: 'Router',
164
- group: 'Flow Control', detailPath: '/dashboard/routers', apiPath: '/routers',
165
- stateKey: 'routers', allStateKey: 'allRouters',
166
- color: '#38bdf8', testable: true,
295
+ type: "router",
296
+ prefix: "ro",
297
+ label: "Router",
298
+ group: "Flow Control",
299
+ detailPath: "/dashboard/routers",
300
+ apiPath: "/routers",
301
+ stateKey: "routers",
302
+ allStateKey: "allRouters",
303
+ color: "#38bdf8",
304
+ testable: true,
167
305
  },
168
306
  // ── Actions ──
169
307
  /**
@@ -175,10 +313,16 @@ exports.NODE_REGISTRY = {
175
313
  * el modo de IA no tiene dieciséis herramientas que ofrecer.
176
314
  */
177
315
  emailAction: {
178
- type: 'emailAction', prefix: 'ea', label: 'Email Action',
179
- group: 'Actions', detailPath: '/dashboard/email-actions', apiPath: '/email-actions',
180
- stateKey: 'emailActions', allStateKey: 'allEmailActions',
181
- color: '#f472b6', testable: true,
316
+ type: "emailAction",
317
+ prefix: "ea",
318
+ label: "Email Action",
319
+ group: "Actions",
320
+ detailPath: "/dashboard/email-actions",
321
+ apiPath: "/email-actions",
322
+ stateKey: "emailActions",
323
+ allStateKey: "allEmailActions",
324
+ color: "#f472b6",
325
+ testable: true,
182
326
  /**
183
327
  * Sigue siendo herramienta: hay una ruta native + aiEnabled donde el nodo
184
328
  * AI genera el correo y este lo manda por Resend —en produccion existe un
@@ -197,96 +341,173 @@ exports.NODE_REGISTRY = {
197
341
  * solo y el de Email vuelve a caber en una pantalla.
198
342
  */
199
343
  gmailAction: {
200
- type: 'gmailAction', prefix: 'gm', label: 'Gmail',
201
- group: 'Actions', detailPath: '/dashboard/gmail-actions', apiPath: '/gmail-actions',
202
- stateKey: 'gmailActions', allStateKey: 'allGmailActions',
203
- color: '#ea4335', testable: true,
344
+ type: "gmailAction",
345
+ prefix: "gm",
346
+ label: "Gmail",
347
+ group: "Actions",
348
+ detailPath: "/dashboard/gmail-actions",
349
+ apiPath: "/gmail-actions",
350
+ stateKey: "gmailActions",
351
+ allStateKey: "allGmailActions",
352
+ color: "#ea4335",
353
+ testable: true,
204
354
  // El toolkit de IA se vino con Gmail: son sus dieciseis operaciones las
205
355
  // que el LLM llama, no las dos de Resend. Ver el comentario de arriba.
206
356
  isToolOnly: (e) => e.aiEnabled === true,
207
357
  },
208
358
  httpAction: {
209
- type: 'httpAction', prefix: 'ha', label: 'HTTP Action',
210
- group: 'Actions', detailPath: '/dashboard/http-actions', apiPath: '/http-actions',
211
- stateKey: 'httpActions', allStateKey: 'allHttpActions',
212
- color: '#3b82f6', testable: true,
359
+ type: "httpAction",
360
+ prefix: "ha",
361
+ label: "HTTP Action",
362
+ group: "Actions",
363
+ detailPath: "/dashboard/http-actions",
364
+ apiPath: "/http-actions",
365
+ stateKey: "httpActions",
366
+ allStateKey: "allHttpActions",
367
+ color: "#3b82f6",
368
+ testable: true,
213
369
  },
214
370
  mongoAction: {
215
- type: 'mongoAction', prefix: 'ma', label: 'MongoDB Action',
216
- group: 'Actions', detailPath: '/dashboard/mongo-actions', apiPath: '/mongo-actions',
217
- stateKey: 'mongoActions', allStateKey: 'allMongoActions',
218
- color: '#34d399', testable: true,
371
+ type: "mongoAction",
372
+ prefix: "ma",
373
+ label: "MongoDB Action",
374
+ group: "Actions",
375
+ detailPath: "/dashboard/mongo-actions",
376
+ apiPath: "/mongo-actions",
377
+ stateKey: "mongoActions",
378
+ allStateKey: "allMongoActions",
379
+ color: "#34d399",
380
+ testable: true,
219
381
  },
220
382
  postgresAction: {
221
- type: 'postgresAction', prefix: 'pg', label: 'Postgres Action',
222
- group: 'Actions', detailPath: '/dashboard/postgres-actions', apiPath: '/postgres-actions',
223
- stateKey: 'postgresActions', allStateKey: 'allPostgresActions',
224
- color: '#336791', testable: true,
383
+ type: "postgresAction",
384
+ prefix: "pg",
385
+ label: "Postgres Action",
386
+ group: "Actions",
387
+ detailPath: "/dashboard/postgres-actions",
388
+ apiPath: "/postgres-actions",
389
+ stateKey: "postgresActions",
390
+ allStateKey: "allPostgresActions",
391
+ color: "#336791",
392
+ testable: true,
225
393
  },
226
394
  notificationAction: {
227
- type: 'notificationAction', prefix: 'na', label: 'Notification',
228
- group: 'Actions', detailPath: '/dashboard/notification-actions', apiPath: '/notification-actions',
229
- stateKey: 'notificationActions', allStateKey: 'allNotificationActions',
230
- color: '#f472b6', testable: true,
395
+ type: "notificationAction",
396
+ prefix: "na",
397
+ label: "Notification",
398
+ group: "Actions",
399
+ detailPath: "/dashboard/notification-actions",
400
+ apiPath: "/notification-actions",
401
+ stateKey: "notificationActions",
402
+ allStateKey: "allNotificationActions",
403
+ color: "#f472b6",
404
+ testable: true,
231
405
  },
232
406
  sheetsAction: {
233
- type: 'sheetsAction', prefix: 'sheet', label: 'Google Sheets Action',
234
- group: 'Actions', detailPath: '/dashboard/sheets-actions', apiPath: '/sheets-actions',
235
- stateKey: 'sheetsActions', allStateKey: 'allSheetsActions',
236
- color: '#34d399', testable: true,
407
+ type: "sheetsAction",
408
+ prefix: "sheet",
409
+ label: "Google Sheets Action",
410
+ group: "Actions",
411
+ detailPath: "/dashboard/sheets-actions",
412
+ apiPath: "/sheets-actions",
413
+ stateKey: "sheetsActions",
414
+ allStateKey: "allSheetsActions",
415
+ color: "#34d399",
416
+ testable: true,
237
417
  },
238
418
  calendarAction: {
239
- type: 'calendarAction', prefix: 'cal', label: 'Google Calendar Action',
240
- group: 'Actions', detailPath: '/dashboard/calendar-actions', apiPath: '/calendar-actions',
241
- stateKey: 'calendarActions', allStateKey: 'allCalendarActions',
242
- color: '#60a5fa', testable: true,
419
+ type: "calendarAction",
420
+ prefix: "cal",
421
+ label: "Google Calendar Action",
422
+ group: "Actions",
423
+ detailPath: "/dashboard/calendar-actions",
424
+ apiPath: "/calendar-actions",
425
+ stateKey: "calendarActions",
426
+ allStateKey: "allCalendarActions",
427
+ color: "#60a5fa",
428
+ testable: true,
243
429
  // AI Toolkit mode wires the node through the upstream AI Node's
244
430
  // tools[] list, not via canvas edges — same pattern as emailAction.
245
431
  isToolOnly: (e) => e.aiEnabled === true,
246
432
  },
247
433
  docsAction: {
248
- type: 'docsAction', prefix: 'doc', label: 'Google Docs',
249
- group: 'Actions', detailPath: '/dashboard/docs-actions', apiPath: '/docs-actions',
250
- stateKey: 'docsActions', allStateKey: 'allDocsActions',
251
- color: '#4285F4', testable: true,
434
+ type: "docsAction",
435
+ prefix: "doc",
436
+ label: "Google Docs",
437
+ group: "Actions",
438
+ detailPath: "/dashboard/docs-actions",
439
+ apiPath: "/docs-actions",
440
+ stateKey: "docsActions",
441
+ allStateKey: "allDocsActions",
442
+ color: "#4285F4",
443
+ testable: true,
252
444
  },
253
445
  driveAction: {
254
- type: 'driveAction', prefix: 'drv', label: 'Google Drive',
255
- group: 'Actions', detailPath: '/dashboard/drive-actions', apiPath: '/drive-actions',
256
- stateKey: 'driveActions', allStateKey: 'allDriveActions',
257
- color: '#22c55e', testable: true,
446
+ type: "driveAction",
447
+ prefix: "drv",
448
+ label: "Google Drive",
449
+ group: "Actions",
450
+ detailPath: "/dashboard/drive-actions",
451
+ apiPath: "/drive-actions",
452
+ stateKey: "driveActions",
453
+ allStateKey: "allDriveActions",
454
+ color: "#22c55e",
455
+ testable: true,
258
456
  // AI Toolkit mode wires the node through the upstream AI Node's
259
457
  // tools[] list, not via canvas edges — same pattern as emailAction.
260
458
  isToolOnly: (e) => e.aiEnabled === true,
261
459
  },
262
460
  firecrawlAction: {
263
- type: 'firecrawlAction', prefix: 'fc', label: 'Firecrawl',
264
- group: 'Actions', detailPath: '/dashboard/firecrawl-actions', apiPath: '/firecrawl-actions',
265
- stateKey: 'firecrawlActions', allStateKey: 'allFirecrawlActions',
266
- color: '#f97316', testable: true,
461
+ type: "firecrawlAction",
462
+ prefix: "fc",
463
+ label: "Firecrawl",
464
+ group: "Actions",
465
+ detailPath: "/dashboard/firecrawl-actions",
466
+ apiPath: "/firecrawl-actions",
467
+ stateKey: "firecrawlActions",
468
+ allStateKey: "allFirecrawlActions",
469
+ color: "#f97316",
470
+ testable: true,
267
471
  },
268
472
  mailchimpAction: {
269
- type: 'mailchimpAction', prefix: 'mc', label: 'Mailchimp',
270
- group: 'Actions', detailPath: '/dashboard/mailchimp-actions', apiPath: '/mailchimp-actions',
271
- stateKey: 'mailchimpActions', allStateKey: 'allMailchimpActions',
473
+ type: "mailchimpAction",
474
+ prefix: "mc",
475
+ label: "Mailchimp",
476
+ group: "Actions",
477
+ detailPath: "/dashboard/mailchimp-actions",
478
+ apiPath: "/mailchimp-actions",
479
+ stateKey: "mailchimpActions",
480
+ allStateKey: "allMailchimpActions",
272
481
  // Mailchimp's Cavendish yellow, darkened until it holds contrast on a
273
482
  // light canvas. The brand value (#ffe01b) reads as a highlighter there.
274
- color: '#c9a227', testable: true,
483
+ color: "#c9a227",
484
+ testable: true,
275
485
  },
276
486
  shopifyAction: {
277
- type: 'shopifyAction', prefix: 'shp', label: 'Shopify',
278
- group: 'Actions', detailPath: '/dashboard/shopify-actions', apiPath: '/shopify-actions',
279
- stateKey: 'shopifyActions', allStateKey: 'allShopifyActions',
487
+ type: "shopifyAction",
488
+ prefix: "shp",
489
+ label: "Shopify",
490
+ group: "Actions",
491
+ detailPath: "/dashboard/shopify-actions",
492
+ apiPath: "/shopify-actions",
493
+ stateKey: "shopifyActions",
494
+ allStateKey: "allShopifyActions",
280
495
  // Shopify's green, abierto un punto. El de marca (#008060) es más oscuro
281
496
  // que cualquier otro color de este registro y se hunde contra el lienzo
282
497
  // en modo oscuro; éste sigue leyéndose como Shopify y aguanta en los dos
283
498
  // temas. Mismo criterio que el amarillo de Mailchimp, al revés.
284
- color: '#0a9e70', testable: true,
499
+ color: "#0a9e70",
500
+ testable: true,
285
501
  },
286
502
  githubAction: {
287
- type: 'githubAction', prefix: 'gh', label: 'GitHub',
288
- group: 'Actions', detailPath: '/dashboard/github-actions', apiPath: '/github-actions',
289
- stateKey: 'githubActions', allStateKey: 'allGithubActions',
503
+ type: "githubAction",
504
+ prefix: "gh",
505
+ label: "GitHub",
506
+ group: "Actions",
507
+ detailPath: "/dashboard/github-actions",
508
+ apiPath: "/github-actions",
509
+ stateKey: "githubActions",
510
+ allStateKey: "allGithubActions",
290
511
  // El morado de "merged" de GitHub. Su marca real es acromática, y ahí no
291
512
  // hay elección posible: en el minimapa lo único que distingue a un nodo es
292
513
  // el color, el gris cálido ya lo ocupa Notion y el frío lo llevan tres.
@@ -296,15 +517,21 @@ exports.NODE_REGISTRY = {
296
517
  // color que de verdad dice GitHub, pero **la decisión se cierra mirando el
297
518
  // minimapa**, no leyendo este comentario. Si choca, el plan B es un
298
519
  // grafito claro, sabiendo que compite con Notion.
299
- color: '#8957e5', testable: true,
520
+ color: "#8957e5",
521
+ testable: true,
300
522
  /* Sin isToolOnly: la v1 no trae modo toolkit de IA, y aquí pesa más que en
301
523
  ningún otro nodo — el MCP oficial de GitHub ya cubre ese caso dentro del
302
524
  AI Node. Los rieles son este nodo; la improvisación es el MCP. */
303
525
  },
304
526
  jiraAction: {
305
- type: 'jiraAction', prefix: 'jr', label: 'Jira',
306
- group: 'Actions', detailPath: '/dashboard/jira-actions', apiPath: '/jira-actions',
307
- stateKey: 'jiraActions', allStateKey: 'allJiraActions',
527
+ type: "jiraAction",
528
+ prefix: "jr",
529
+ label: "Jira",
530
+ group: "Actions",
531
+ detailPath: "/dashboard/jira-actions",
532
+ apiPath: "/jira-actions",
533
+ stateKey: "jiraActions",
534
+ allStateKey: "allJiraActions",
308
535
  // El azul de marca de Jira, y estaba libre. La banda azul es la segunda
309
536
  // mas poblada de este registro —nueve nodos— pero ninguno tan profundo:
310
537
  // #3b82f6 y #60a5fa son claramente mas claros, y el #336791 de Postgres es
@@ -312,7 +539,8 @@ exports.NODE_REGISTRY = {
312
539
  //
313
540
  // Sigue valiendo lo que escribio GitHub aqui al lado: **la decision se
314
541
  // cierra mirando el minimapa**, no leyendo este comentario.
315
- color: '#0052CC', testable: true,
542
+ color: "#0052CC",
543
+ testable: true,
316
544
  /* Sin isToolOnly: la v1 no trae modo toolkit de IA. Cada toolkit vive dos
317
545
  veces —specs del dashboard para el AI Node y specs de la api para MCP— y
318
546
  esa duplicacion no se paga antes de ver el nodo en pantalla. Ademas
@@ -320,11 +548,44 @@ exports.NODE_REGISTRY = {
320
548
  `MCP_OAUTH_PRESETS.atlassian`, que cubre la cola larga dentro del AI
321
549
  Node. Los rieles son este nodo. */
322
550
  },
551
+ bucketAction: {
552
+ type: "bucketAction",
553
+ prefix: "bkt",
554
+ label: "Bucket",
555
+ group: "Actions",
556
+ detailPath: "/dashboard/bucket-actions",
557
+ apiPath: "/bucket-actions",
558
+ stateKey: "bucketActions",
559
+ allStateKey: "allBucketActions",
560
+ // Teal profundo, y la banda estaba VACIA por abajo. Los cuatro vecinos de
561
+ // esa familia son claramente mas claros: `#2dd4bf` y `#34d399` son pasteles,
562
+ // `#06b6d4` tira a cian brillante y `#0a9e70` es verde. En el minimapa este
563
+ // es el unico oscuro de la banda, y eso lo distingue.
564
+ //
565
+ // Se eligio neutro a proposito y no un color de marca: el nodo no es de
566
+ // Cloudflare. Pintarlo del naranja de R2 seria mentir en cuanto alguien lo
567
+ // apunte a AWS, a MinIO o a Backblaze — que es justo para lo que se llama
568
+ // `bucketAction` y no `r2Action`.
569
+ //
570
+ // Vale lo que escribieron GitHub y Jira aqui al lado: **la decision se
571
+ // cierra mirando el minimapa**, no leyendo este comentario.
572
+ color: "#0f766e",
573
+ testable: true,
574
+ /* Sin `isToolOnly`: la v1 no trae modo toolkit de IA. Cada toolkit vive dos
575
+ veces —specs del dashboard para el AI Node y specs de la api para MCP— y
576
+ esa duplicacion no se paga antes de ver el nodo en pantalla. */
577
+ },
323
578
  telegramAction: {
324
- type: 'telegramAction', prefix: 'tg', label: 'Telegram Action',
325
- group: 'Actions', detailPath: '/dashboard/telegram-actions', apiPath: '/telegram-actions',
326
- stateKey: 'telegramActions', allStateKey: 'allTelegramActions',
327
- color: '#0ea5e9', testable: true,
579
+ type: "telegramAction",
580
+ prefix: "tg",
581
+ label: "Telegram Action",
582
+ group: "Actions",
583
+ detailPath: "/dashboard/telegram-actions",
584
+ apiPath: "/telegram-actions",
585
+ stateKey: "telegramActions",
586
+ allStateKey: "allTelegramActions",
587
+ color: "#0ea5e9",
588
+ testable: true,
328
589
  // AI Toolkit mode — same pattern as emailAction. One aiEnabled
329
590
  // telegramAction expands into N MCP tools (one per operation) in
330
591
  // the upstream AI Node's tool list, so we hide canvas handles to
@@ -333,11 +594,17 @@ exports.NODE_REGISTRY = {
333
594
  isToolOnly: (e) => e.aiEnabled === true,
334
595
  },
335
596
  whatsappAction: {
336
- type: 'whatsappAction', prefix: 'wa', label: 'WhatsApp Action',
337
- group: 'Actions', detailPath: '/dashboard/whatsapp-actions', apiPath: '/whatsapp-actions',
338
- stateKey: 'whatsappActions', allStateKey: 'allWhatsappActions',
597
+ type: "whatsappAction",
598
+ prefix: "wa",
599
+ label: "WhatsApp Action",
600
+ group: "Actions",
601
+ detailPath: "/dashboard/whatsapp-actions",
602
+ apiPath: "/whatsapp-actions",
603
+ stateKey: "whatsappActions",
604
+ allStateKey: "allWhatsappActions",
339
605
  // WhatsApp brand green (#25d366) — instantly recognisable on the canvas.
340
- color: '#25d366', testable: true,
606
+ color: "#25d366",
607
+ testable: true,
341
608
  // Same toolkit pattern as telegramAction — when aiEnabled the
342
609
  // entity stops being a manually-wired single-op action and becomes
343
610
  // a multi-op tool surface for an upstream AI Node. Hide canvas
@@ -346,24 +613,36 @@ exports.NODE_REGISTRY = {
346
613
  isToolOnly: (e) => e.aiEnabled === true,
347
614
  },
348
615
  discordAction: {
349
- type: 'discordAction', prefix: 'dc', label: 'Discord Action',
350
- group: 'Actions', detailPath: '/dashboard/discord-actions', apiPath: '/discord-actions',
351
- stateKey: 'discordActions', allStateKey: 'allDiscordActions',
616
+ type: "discordAction",
617
+ prefix: "dc",
618
+ label: "Discord Action",
619
+ group: "Actions",
620
+ detailPath: "/dashboard/discord-actions",
621
+ apiPath: "/discord-actions",
622
+ stateKey: "discordActions",
623
+ allStateKey: "allDiscordActions",
352
624
  // Discord brand "Blurple" (#5865F2).
353
- color: '#5865F2', testable: true,
625
+ color: "#5865F2",
626
+ testable: true,
354
627
  // Same toolkit pattern as telegramAction / whatsappAction. Hide
355
628
  // canvas handles when aiEnabled so the user can't manually wire a
356
629
  // multi-op tool surface — the LLM picks the operation per call.
357
630
  isToolOnly: (e) => e.aiEnabled === true,
358
631
  },
359
632
  slackAction: {
360
- type: 'slackAction', prefix: 'sl', label: 'Slack Action',
361
- group: 'Actions', detailPath: '/dashboard/slack-actions', apiPath: '/slack-actions',
362
- stateKey: 'slackActions', allStateKey: 'allSlackActions',
633
+ type: "slackAction",
634
+ prefix: "sl",
635
+ label: "Slack Action",
636
+ group: "Actions",
637
+ detailPath: "/dashboard/slack-actions",
638
+ apiPath: "/slack-actions",
639
+ stateKey: "slackActions",
640
+ allStateKey: "allSlackActions",
363
641
  // Slack brand purple ("aubergine"-derived family). Matches the
364
642
  // accent color used by SlackConfigSection / ServiceTriggerNode for
365
643
  // visual continuity across Slack-related nodes.
366
- color: '#7c3aed', testable: true,
644
+ color: "#7c3aed",
645
+ testable: true,
367
646
  // Same AI-tool gating as Telegram/Discord/WhatsApp/Email/Calendar/
368
647
  // Contacts. When aiEnabled the node fans out into 16 MCP tools
369
648
  // (one per SLACK_OPERATIONS entry); hide canvas handles to prevent
@@ -371,11 +650,17 @@ exports.NODE_REGISTRY = {
371
650
  isToolOnly: (e) => e.aiEnabled === true,
372
651
  },
373
652
  googleContactsAction: {
374
- type: 'googleContactsAction', prefix: 'gc', label: 'Google Contacts',
375
- group: 'Actions', detailPath: '/dashboard/google-contacts-actions', apiPath: '/google-contacts-actions',
376
- stateKey: 'googleContactsActions', allStateKey: 'allGoogleContactsActions',
653
+ type: "googleContactsAction",
654
+ prefix: "gc",
655
+ label: "Google Contacts",
656
+ group: "Actions",
657
+ detailPath: "/dashboard/google-contacts-actions",
658
+ apiPath: "/google-contacts-actions",
659
+ stateKey: "googleContactsActions",
660
+ allStateKey: "allGoogleContactsActions",
377
661
  // Google "blue" used by Google Drive / Docs (#4285F4) — same family.
378
- color: '#4285F4', testable: true,
662
+ color: "#4285F4",
663
+ testable: true,
379
664
  // AI toolkit mode mirrors emailAction / calendarAction — when
380
665
  // aiEnabled the node expands into N MCP tools (one per People API
381
666
  // operation) in the upstream AI Node's tool list. Hide canvas
@@ -383,54 +668,84 @@ exports.NODE_REGISTRY = {
383
668
  isToolOnly: (e) => e.aiEnabled === true,
384
669
  },
385
670
  googleAnalyticsAction: {
386
- type: 'googleAnalyticsAction', prefix: 'gaa', label: 'Google Analytics',
387
- group: 'Actions', detailPath: '/dashboard/google-analytics-actions', apiPath: '/google-analytics-actions',
388
- stateKey: 'googleAnalyticsActions', allStateKey: 'allGoogleAnalyticsActions',
671
+ type: "googleAnalyticsAction",
672
+ prefix: "gaa",
673
+ label: "Google Analytics",
674
+ group: "Actions",
675
+ detailPath: "/dashboard/google-analytics-actions",
676
+ apiPath: "/google-analytics-actions",
677
+ stateKey: "googleAnalyticsActions",
678
+ allStateKey: "allGoogleAnalyticsActions",
389
679
  // Naranja de marca de Google Analytics (#E37400). No reutiliza el
390
680
  // #ff9933 de RSS ni el orange-500 de Tailwind que ya llevan tres nodos:
391
681
  // en el minimapa lo unico que distingue a un nodo es el color.
392
- color: '#E37400', testable: true,
682
+ color: "#E37400",
683
+ testable: true,
393
684
  /* Sin isToolOnly: la v1 no trae modo toolkit de IA. Cada toolkit vive
394
685
  dos veces --specs del dashboard para el AI Node y specs de la api para
395
686
  MCP, sin nada que las ate-- y esa duplicacion no se paga antes de ver
396
687
  el nodo funcionando en pantalla. */
397
688
  },
398
689
  notionAction: {
399
- type: 'notionAction', prefix: 'ntn', label: 'Notion',
400
- group: 'Actions', detailPath: '/dashboard/notion-actions', apiPath: '/notion-actions',
401
- stateKey: 'notionActions', allStateKey: 'allNotionActions',
690
+ type: "notionAction",
691
+ prefix: "ntn",
692
+ label: "Notion",
693
+ group: "Actions",
694
+ detailPath: "/dashboard/notion-actions",
695
+ apiPath: "/notion-actions",
696
+ stateKey: "notionActions",
697
+ allStateKey: "allNotionActions",
402
698
  // Notion es monocromo de marca (su negro es #37352F), y un punto negro
403
699
  // en el minimapa desaparece en modo oscuro. Este stone-500 es el gris
404
700
  // CÁLIDO más cercano a esa marca, y no choca con el #94a3b8 (slate, gris
405
701
  // frío) que ya llevan tres nodos: en el minimapa lo único que distingue
406
702
  // a un nodo es el color.
407
- color: '#78716c', testable: true,
703
+ color: "#78716c",
704
+ testable: true,
408
705
  /* Sin isToolOnly: la v1 no trae modo toolkit de IA. Cada toolkit vive dos
409
706
  veces --specs del dashboard para el AI Node y specs de la api para
410
707
  MCP-- y esa duplicación no se paga antes de ver el nodo en pantalla. */
411
708
  },
412
709
  vectorStore: {
413
- type: 'vectorStore', prefix: 'vs', label: 'Vector Store',
414
- group: 'Actions', detailPath: '/dashboard/vector-store-nodes', apiPath: '/vector-store-nodes',
415
- stateKey: 'vectorStoreNodes', allStateKey: 'allVectorStoreNodes',
416
- color: '#06b6d4', testable: true,
710
+ type: "vectorStore",
711
+ prefix: "vs",
712
+ label: "Vector Store",
713
+ group: "Actions",
714
+ detailPath: "/dashboard/vector-store-nodes",
715
+ apiPath: "/vector-store-nodes",
716
+ stateKey: "vectorStoreNodes",
717
+ allStateKey: "allVectorStoreNodes",
718
+ color: "#06b6d4",
719
+ testable: true,
417
720
  },
418
721
  rssAction: {
419
- type: 'rssAction', prefix: 'rssa', label: 'RSS Fetch',
420
- group: 'Actions', detailPath: '/dashboard/rss-actions', apiPath: '/rss-actions',
421
- stateKey: 'rssActions', allStateKey: 'allRssActions',
722
+ type: "rssAction",
723
+ prefix: "rssa",
724
+ label: "RSS Fetch",
725
+ group: "Actions",
726
+ detailPath: "/dashboard/rss-actions",
727
+ apiPath: "/rss-actions",
728
+ stateKey: "rssActions",
729
+ allStateKey: "allRssActions",
422
730
  // Classic RSS feed orange (#ff9933) — distinguishes from rateLimiter /
423
731
  // fileTransform / firecrawl which sit on Tailwind orange-500.
424
- color: '#ff9933', testable: true,
732
+ color: "#ff9933",
733
+ testable: true,
425
734
  },
426
735
  socialMediaAction: {
427
- type: 'socialMediaAction', prefix: 'sm', label: 'Social Media',
428
- group: 'Actions', detailPath: '/dashboard/social-media-actions', apiPath: '/social-media-actions',
429
- stateKey: 'socialMediaActions', allStateKey: 'allSocialMediaActions',
736
+ type: "socialMediaAction",
737
+ prefix: "sm",
738
+ label: "Social Media",
739
+ group: "Actions",
740
+ detailPath: "/dashboard/social-media-actions",
741
+ apiPath: "/social-media-actions",
742
+ stateKey: "socialMediaActions",
743
+ allStateKey: "allSocialMediaActions",
430
744
  // Pink/magenta (#ec4899) — neutral default; the canvas card overrides
431
745
  // with the picked provider's brandColor at render time so each
432
746
  // platform reads at a glance (Bluesky #0085ff, Mastodon #6364ff, ...).
433
- color: '#ec4899', testable: true,
747
+ color: "#ec4899",
748
+ testable: true,
434
749
  // AI toolkit mode (Sprint 4) wires through tools[] like the other
435
750
  // toolkit-capable actions. Manual + toolkit modes share the same
436
751
  // detail page; the segmented control hides ops-config when toolkit.
@@ -438,10 +753,16 @@ exports.NODE_REGISTRY = {
438
753
  },
439
754
  // ── Annotations ──
440
755
  stickyNote: {
441
- type: 'stickyNote', prefix: 'note', label: 'Sticky Note',
442
- group: 'Annotations', detailPath: '', apiPath: '/sticky-notes',
443
- stateKey: 'stickyNotes', allStateKey: 'allStickyNotes',
444
- color: '#fbbf24', testable: false,
756
+ type: "stickyNote",
757
+ prefix: "note",
758
+ label: "Sticky Note",
759
+ group: "Annotations",
760
+ detailPath: "",
761
+ apiPath: "/sticky-notes",
762
+ stateKey: "stickyNotes",
763
+ allStateKey: "allStickyNotes",
764
+ color: "#fbbf24",
765
+ testable: false,
445
766
  },
446
767
  };
447
768
  // ── Derived helpers ──
@@ -450,15 +771,20 @@ function getNodeRegistryEntry(type) {
450
771
  return exports.NODE_REGISTRY[type];
451
772
  }
452
773
  /** type → detail page path */
453
- exports.NODE_DETAIL_PATHS = Object.fromEntries(Object.values(exports.NODE_REGISTRY).filter(n => n.detailPath).map(n => [n.type, n.detailPath]));
774
+ exports.NODE_DETAIL_PATHS = Object.fromEntries(Object.values(exports.NODE_REGISTRY)
775
+ .filter((n) => n.detailPath)
776
+ .map((n) => [n.type, n.detailPath]));
454
777
  /** type → minimap color */
455
- exports.NODE_COLORS = Object.fromEntries(Object.values(exports.NODE_REGISTRY).map(n => [n.type, n.color]));
778
+ exports.NODE_COLORS = Object.fromEntries(Object.values(exports.NODE_REGISTRY).map((n) => [n.type, n.color]));
456
779
  /** type → [stateKey, allStateKey] */
457
- exports.NODE_STATE_KEYS = Object.fromEntries(Object.values(exports.NODE_REGISTRY).map(n => [n.type, [n.stateKey, n.allStateKey]]));
780
+ exports.NODE_STATE_KEYS = Object.fromEntries(Object.values(exports.NODE_REGISTRY).map((n) => [
781
+ n.type,
782
+ [n.stateKey, n.allStateKey],
783
+ ]));
458
784
  /** prefix → type */
459
- exports.PREFIX_TO_NODE_TYPE = Object.fromEntries(Object.values(exports.NODE_REGISTRY).map(n => [n.prefix, n.type]));
785
+ exports.PREFIX_TO_NODE_TYPE = Object.fromEntries(Object.values(exports.NODE_REGISTRY).map((n) => [n.prefix, n.type]));
460
786
  /** type → prefix */
461
- exports.NODE_TYPE_TO_PREFIX = Object.fromEntries(Object.values(exports.NODE_REGISTRY).map(n => [n.type, n.prefix]));
787
+ exports.NODE_TYPE_TO_PREFIX = Object.fromEntries(Object.values(exports.NODE_REGISTRY).map((n) => [n.type, n.prefix]));
462
788
  function specsFor(type, catalog) {
463
789
  return catalog[type]?.versions;
464
790
  }
@@ -478,7 +804,7 @@ function versionsOf(type, catalog = exports.NODE_REGISTRY) {
478
804
  }
479
805
  /** One version's spec, or undefined when the type or the number is unknown. */
480
806
  function versionSpec(type, v, catalog = exports.NODE_REGISTRY) {
481
- return specsFor(type, catalog)?.find(s => s.v === v);
807
+ return specsFor(type, catalog)?.find((s) => s.v === v);
482
808
  }
483
809
  /**
484
810
  * The version a new node of this type is born on.
@@ -489,7 +815,7 @@ function versionSpec(type, v, catalog = exports.NODE_REGISTRY) {
489
815
  * is not a convention — it is the truth.
490
816
  */
491
817
  function currentVersion(type, catalog = exports.NODE_REGISTRY) {
492
- const current = specsFor(type, catalog)?.find(s => s.status === 'current');
818
+ const current = specsFor(type, catalog)?.find((s) => s.status === "current");
493
819
  return current?.v ?? 1;
494
820
  }
495
821
  /**
@@ -527,7 +853,7 @@ function fieldsLost(from, to) {
527
853
  if (to.cleanBreak)
528
854
  return [...from.fields];
529
855
  const kept = new Set(to.fields);
530
- return from.fields.filter(f => !kept.has(f));
856
+ return from.fields.filter((f) => !kept.has(f));
531
857
  }
532
858
  /**
533
859
  * Declaration mistakes in the version catalogs, as readable lines.
@@ -547,18 +873,22 @@ function versionCatalogErrors(catalog = exports.NODE_REGISTRY) {
547
873
  errors.push(`${entry.type}: declares an empty versions array — omit the field instead`);
548
874
  continue;
549
875
  }
550
- const numbers = versions.map(s => s.v);
551
- const repeatedNumbers = [...new Set(numbers.filter((n, i) => numbers.indexOf(n) !== i))];
876
+ const numbers = versions.map((s) => s.v);
877
+ const repeatedNumbers = [
878
+ ...new Set(numbers.filter((n, i) => numbers.indexOf(n) !== i)),
879
+ ];
552
880
  if (repeatedNumbers.length)
553
- errors.push(`${entry.type}: repeated version number ${repeatedNumbers.join(', ')}`);
881
+ errors.push(`${entry.type}: repeated version number ${repeatedNumbers.join(", ")}`);
554
882
  for (const spec of versions) {
555
883
  if (!Number.isInteger(spec.v) || spec.v < 1)
556
884
  errors.push(`${entry.type}: version "${spec.v}" is not a positive integer`);
557
- const repeatedFields = [...new Set(spec.fields.filter((f, i) => spec.fields.indexOf(f) !== i))];
885
+ const repeatedFields = [
886
+ ...new Set(spec.fields.filter((f, i) => spec.fields.indexOf(f) !== i)),
887
+ ];
558
888
  if (repeatedFields.length)
559
- errors.push(`${entry.type} v${spec.v}: repeated field ${repeatedFields.join(', ')}`);
889
+ errors.push(`${entry.type} v${spec.v}: repeated field ${repeatedFields.join(", ")}`);
560
890
  }
561
- const current = versions.filter(s => s.status === 'current');
891
+ const current = versions.filter((s) => s.status === "current");
562
892
  if (current.length !== 1)
563
893
  errors.push(`${entry.type}: needs exactly one version with status "current", found ${current.length}`);
564
894
  }