@zerodot/node-sdk 0.0.0-stage → 1.0.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/index.mjs ADDED
@@ -0,0 +1,694 @@
1
+ import { z } from "zod";
2
+ import { readFileSync } from "node:fs";
3
+ import { extname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ // ../engine/src/expression.ts
6
+ /**
7
+ * Small safe expression evaluator. No eval, no new Function, no function calls.
8
+ * Syntax: literals, paths (a.b, a["b"], a[0]), unary ! and -, arithmetic,
9
+ * comparison, && || ??, the ternary operator, and `.includes(value)` on lists and text.
10
+ */
11
+ var ExpressionError = class extends Error {
12
+ detail;
13
+ path;
14
+ constructor(detail, path) {
15
+ super(path ? `${detail} (at ${path})` : detail);
16
+ this.detail = detail;
17
+ this.path = path;
18
+ this.name = "ExpressionError";
19
+ }
20
+ };
21
+ const OPERATORS = [
22
+ "===",
23
+ "!==",
24
+ "==",
25
+ "!=",
26
+ "<=",
27
+ ">=",
28
+ "&&",
29
+ "||",
30
+ "??",
31
+ "+",
32
+ "-",
33
+ "*",
34
+ "/",
35
+ "%",
36
+ "<",
37
+ ">",
38
+ "!",
39
+ "?",
40
+ ":",
41
+ ".",
42
+ "[",
43
+ "]",
44
+ "(",
45
+ ")"
46
+ ];
47
+ function tokenize(source) {
48
+ const tokens = [];
49
+ let i = 0;
50
+ while (i < source.length) {
51
+ const ch = source[i];
52
+ if (/\s/.test(ch)) i++;
53
+ else if (/[0-9]/.test(ch)) {
54
+ const text = /^[0-9]+(\.[0-9]+)?/.exec(source.slice(i))?.[0] ?? ch;
55
+ tokens.push({
56
+ kind: "num",
57
+ value: Number(text)
58
+ });
59
+ i += text.length;
60
+ } else if (ch === "\"" || ch === "'") {
61
+ let out = "";
62
+ i++;
63
+ while (i < source.length && source[i] !== ch) {
64
+ if (source[i] === "\\" && i + 1 < source.length) i++;
65
+ out += source[i];
66
+ i++;
67
+ }
68
+ if (source[i] !== ch) throw new ExpressionError("Unterminated string");
69
+ i++;
70
+ tokens.push({
71
+ kind: "str",
72
+ value: out
73
+ });
74
+ } else if (/[A-Za-z_$]/.test(ch)) {
75
+ const text = /^[A-Za-z0-9_$]+/.exec(source.slice(i))?.[0] ?? ch;
76
+ tokens.push({
77
+ kind: "id",
78
+ value: text
79
+ });
80
+ i += text.length;
81
+ } else {
82
+ const op = OPERATORS.find((candidate) => source.startsWith(candidate, i));
83
+ if (!op) throw new ExpressionError(`Unexpected character "${ch}"`);
84
+ tokens.push({
85
+ kind: "op",
86
+ value: op
87
+ });
88
+ i += op.length;
89
+ }
90
+ }
91
+ tokens.push({ kind: "end" });
92
+ return tokens;
93
+ }
94
+ const BINARY_PRECEDENCE = {
95
+ "??": 1,
96
+ "||": 2,
97
+ "&&": 3,
98
+ "==": 4,
99
+ "!=": 4,
100
+ "===": 4,
101
+ "!==": 4,
102
+ "<": 5,
103
+ "<=": 5,
104
+ ">": 5,
105
+ ">=": 5,
106
+ "+": 6,
107
+ "-": 6,
108
+ "*": 7,
109
+ "/": 7,
110
+ "%": 7
111
+ };
112
+ const LITERALS = {
113
+ true: true,
114
+ false: false,
115
+ null: null,
116
+ undefined: void 0
117
+ };
118
+ var Parser = class {
119
+ tokens;
120
+ pos = 0;
121
+ constructor(tokens) {
122
+ this.tokens = tokens;
123
+ }
124
+ peek() {
125
+ return this.tokens[this.pos];
126
+ }
127
+ isOp(value) {
128
+ const token = this.peek();
129
+ return token.kind === "op" && token.value === value;
130
+ }
131
+ expectOp(value) {
132
+ if (!this.isOp(value)) throw new ExpressionError(`Expected "${value}"`);
133
+ this.pos++;
134
+ }
135
+ parse() {
136
+ const node = this.ternary();
137
+ if (this.peek().kind !== "end") throw new ExpressionError("Unexpected token after expression");
138
+ return node;
139
+ }
140
+ ternary() {
141
+ const test = this.binary(1);
142
+ if (!this.isOp("?")) return test;
143
+ this.pos++;
144
+ const yes = this.ternary();
145
+ this.expectOp(":");
146
+ return {
147
+ type: "cond",
148
+ test,
149
+ yes,
150
+ no: this.ternary()
151
+ };
152
+ }
153
+ binary(minPrecedence) {
154
+ let left = this.unary();
155
+ for (;;) {
156
+ const token = this.peek();
157
+ if (token.kind !== "op") break;
158
+ const precedence = BINARY_PRECEDENCE[token.value];
159
+ if (precedence === void 0 || precedence < minPrecedence) break;
160
+ this.pos++;
161
+ const right = this.binary(precedence + 1);
162
+ left = {
163
+ type: "binary",
164
+ op: token.value,
165
+ left,
166
+ right
167
+ };
168
+ }
169
+ return left;
170
+ }
171
+ unary() {
172
+ if (this.isOp("!") || this.isOp("-")) {
173
+ const op = this.peek().value;
174
+ this.pos++;
175
+ return {
176
+ type: "unary",
177
+ op,
178
+ arg: this.unary()
179
+ };
180
+ }
181
+ return this.postfix();
182
+ }
183
+ postfix() {
184
+ let node = this.primary();
185
+ for (;;) if (this.isOp(".")) {
186
+ this.pos++;
187
+ const token = this.peek();
188
+ if (token.kind !== "id") throw new ExpressionError("Expected a name after \".\"");
189
+ this.pos++;
190
+ node = {
191
+ type: "member",
192
+ object: node,
193
+ key: {
194
+ type: "lit",
195
+ value: token.value
196
+ },
197
+ label: token.value
198
+ };
199
+ } else if (this.isOp("[")) {
200
+ this.pos++;
201
+ const key = this.ternary();
202
+ this.expectOp("]");
203
+ node = {
204
+ type: "member",
205
+ object: node,
206
+ key,
207
+ label: key.type === "lit" ? String(key.value) : "[…]"
208
+ };
209
+ } else if (this.isOp("(")) {
210
+ if (node.type !== "member" || node.label !== "includes" || node.key.type !== "lit") throw new ExpressionError("Function calls are not allowed. Use a code node.");
211
+ this.pos++;
212
+ const arg = this.ternary();
213
+ this.expectOp(")");
214
+ node = {
215
+ type: "includes",
216
+ object: node.object,
217
+ arg
218
+ };
219
+ } else return node;
220
+ }
221
+ primary() {
222
+ const token = this.peek();
223
+ this.pos++;
224
+ if (token.kind === "num" || token.kind === "str") return {
225
+ type: "lit",
226
+ value: token.value
227
+ };
228
+ if (token.kind === "id") {
229
+ if (Object.hasOwn(LITERALS, token.value)) return {
230
+ type: "lit",
231
+ value: LITERALS[token.value]
232
+ };
233
+ return {
234
+ type: "id",
235
+ name: token.value
236
+ };
237
+ }
238
+ if (token.kind === "op" && token.value === "(") {
239
+ const inner = this.ternary();
240
+ this.expectOp(")");
241
+ return inner;
242
+ }
243
+ throw new ExpressionError("Unexpected end or token in expression");
244
+ }
245
+ };
246
+ /**
247
+ * An object where a missing key is an error, not `undefined`. Used for `$vars`,
248
+ * so a mistyped variable name fails the node and names the keys that are close.
249
+ */
250
+ var StrictRecord = class {
251
+ label;
252
+ values;
253
+ constructor(label, values) {
254
+ this.label = label;
255
+ this.values = values;
256
+ }
257
+ };
258
+ function distance(a, b) {
259
+ const row = Array.from({ length: b.length + 1 }, (_, i) => i);
260
+ for (let i = 1; i <= a.length; i++) {
261
+ let previous = row[0] ?? 0;
262
+ row[0] = i;
263
+ for (let j = 1; j <= b.length; j++) {
264
+ const current = row[j] ?? 0;
265
+ row[j] = Math.min((row[j] ?? 0) + 1, (row[j - 1] ?? 0) + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
266
+ previous = current;
267
+ }
268
+ }
269
+ return row[b.length] ?? 0;
270
+ }
271
+ function similarKeys(key, keys) {
272
+ const lower = key.toLowerCase();
273
+ return keys.map((k) => ({
274
+ k,
275
+ d: distance(lower, k.toLowerCase())
276
+ })).filter(({ k, d }) => d <= 2 || k.toLowerCase().includes(lower) || lower.includes(k.toLowerCase())).sort((x, y) => x.d - y.d).slice(0, 3).map(({ k }) => k);
277
+ }
278
+ const FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
279
+ "__proto__",
280
+ "constructor",
281
+ "prototype"
282
+ ]);
283
+ function readMember(object, key, path) {
284
+ if (object === null || object === void 0) throw new ExpressionError(`Cannot read "${String(key)}" of ${String(object)}`, path);
285
+ if (typeof key !== "string" && typeof key !== "number") throw new ExpressionError("A key must be a string or a number", path);
286
+ if (typeof key === "string" && FORBIDDEN_KEYS.has(key)) throw new ExpressionError(`Key "${key}" is not allowed`, path);
287
+ if (object instanceof StrictRecord) {
288
+ const name = String(key);
289
+ if (Object.hasOwn(object.values, name)) return object.values[name];
290
+ const similar = similarKeys(name, Object.keys(object.values));
291
+ const hint = similar.length > 0 ? ` Did you mean: ${similar.join(", ")}?` : "";
292
+ throw new ExpressionError(`${object.label} "${name}" does not exist.${hint}`, path);
293
+ }
294
+ if (Array.isArray(object)) {
295
+ if (key === "length") return object.length;
296
+ return typeof key === "number" ? object[key] : void 0;
297
+ }
298
+ if (typeof object === "string") {
299
+ if (key === "length") return object.length;
300
+ return;
301
+ }
302
+ if (typeof object === "object") return Object.hasOwn(object, key) ? object[key] : void 0;
303
+ }
304
+ function numeric(value, op) {
305
+ if (typeof value !== "number") throw new ExpressionError(`Operator "${op}" needs numbers`);
306
+ return value;
307
+ }
308
+ function evaluate(node, scope, path) {
309
+ switch (node.type) {
310
+ case "lit": return node.value;
311
+ case "id": return Object.hasOwn(scope, node.name) ? scope[node.name] : void 0;
312
+ case "member": return readMember(evaluate(node.object, scope, path), evaluate(node.key, scope, path), `${path}.${node.label}`.replace(/^\./, ""));
313
+ case "includes": {
314
+ const object = evaluate(node.object, scope, path);
315
+ const arg = evaluate(node.arg, scope, path);
316
+ if (Array.isArray(object)) return object.includes(arg);
317
+ if (typeof object === "string") return typeof arg === "string" && object.includes(arg);
318
+ throw new ExpressionError("\"includes\" needs a list or text", path);
319
+ }
320
+ case "unary": {
321
+ const value = evaluate(node.arg, scope, path);
322
+ return node.op === "!" ? !value : -numeric(value, "-");
323
+ }
324
+ case "cond": return evaluate(node.test, scope, path) ? evaluate(node.yes, scope, path) : evaluate(node.no, scope, path);
325
+ case "binary": {
326
+ if (node.op === "&&") {
327
+ const left = evaluate(node.left, scope, path);
328
+ return left ? evaluate(node.right, scope, path) : left;
329
+ }
330
+ if (node.op === "||") {
331
+ const left = evaluate(node.left, scope, path);
332
+ return left ? left : evaluate(node.right, scope, path);
333
+ }
334
+ if (node.op === "??") return evaluate(node.left, scope, path) ?? evaluate(node.right, scope, path);
335
+ const left = evaluate(node.left, scope, path);
336
+ const right = evaluate(node.right, scope, path);
337
+ switch (node.op) {
338
+ case "==":
339
+ case "===": return left === right;
340
+ case "!=":
341
+ case "!==": return left !== right;
342
+ case "<": return left < right;
343
+ case "<=": return left <= right;
344
+ case ">": return left > right;
345
+ case ">=": return left >= right;
346
+ case "+":
347
+ if (typeof left === "string" || typeof right === "string") return `${stringify(left)}${stringify(right)}`;
348
+ return numeric(left, "+") + numeric(right, "+");
349
+ case "-": return numeric(left, "-") - numeric(right, "-");
350
+ case "*": return numeric(left, "*") * numeric(right, "*");
351
+ case "/": return numeric(left, "/") / numeric(right, "/");
352
+ case "%": return numeric(left, "%") % numeric(right, "%");
353
+ default: throw new ExpressionError(`Unknown operator "${node.op}"`);
354
+ }
355
+ }
356
+ }
357
+ }
358
+ function stringify(value) {
359
+ if (typeof value === "string") return value;
360
+ if (value === void 0) return "";
361
+ if (typeof value === "object") return JSON.stringify(value);
362
+ return String(value);
363
+ }
364
+ function evaluateExpression(source, scope) {
365
+ return evaluate(new Parser(tokenize(source)).parse(), scope, "");
366
+ }
367
+ function splitTemplate(input) {
368
+ const segments = [];
369
+ let rest = input;
370
+ while (rest.length > 0) {
371
+ const start = rest.indexOf("{{");
372
+ if (start === -1) {
373
+ segments.push({ text: rest });
374
+ break;
375
+ }
376
+ const end = rest.indexOf("}}", start + 2);
377
+ if (end === -1) throw new ExpressionError("Missing \"}}\"");
378
+ if (start > 0) segments.push({ text: rest.slice(0, start) });
379
+ segments.push({ expression: rest.slice(start + 2, end).trim() });
380
+ rest = rest.slice(end + 2);
381
+ }
382
+ return segments;
383
+ }
384
+ /** A string with one `` and nothing else keeps the value type. Otherwise the result is text. */
385
+ function resolveTemplate(input, scope) {
386
+ if (!input.includes("{{")) return input;
387
+ const segments = splitTemplate(input);
388
+ const only = segments.length === 1 ? segments[0] : void 0;
389
+ if (only && "expression" in only) return evaluateExpression(only.expression, scope);
390
+ return segments.map((s) => "text" in s ? s.text : stringify(evaluateExpression(s.expression, scope))).join("");
391
+ }
392
+ // ../engine/src/graph.ts
393
+ const retrySchema = z.object({
394
+ max: z.number().int().min(1).max(10),
395
+ backoffMs: z.number().int().min(0).max(6e4).default(1e3)
396
+ });
397
+ const graphNodeSchema = z.object({
398
+ id: z.string().min(1),
399
+ type: z.string().min(1),
400
+ name: z.string().min(1),
401
+ position: z.object({
402
+ x: z.number(),
403
+ y: z.number()
404
+ }),
405
+ config: z.record(z.string(), z.unknown()).default({}),
406
+ credentialId: z.string().nullable().default(null),
407
+ /**
408
+ * An expression that gives the id or the name of the credential at run time. When set, it is
409
+ * used instead of `credentialId`.
410
+ */
411
+ credentialExpression: z.string().min(1).optional(),
412
+ /** Sample output. Test runs use it and skip the node. */
413
+ pinned: z.object({
414
+ port: z.string(),
415
+ data: z.unknown()
416
+ }).nullable().default(null),
417
+ /** A deactivated node passes its input through and does not run. */
418
+ disabled: z.boolean().optional(),
419
+ retry: retrySchema.optional(),
420
+ onError: z.enum(["fail", "continue"]).default("fail"),
421
+ /** If the node returns nothing or an empty list, it outputs an empty object instead. */
422
+ alwaysOutputData: z.boolean().optional(),
423
+ /** If the input is a list, the node runs once with the first item only. */
424
+ executeOnce: z.boolean().optional(),
425
+ /** A note for people. Not used at run time. */
426
+ notes: z.string().optional(),
427
+ /** Show the note under the node on the canvas. */
428
+ notesInFlow: z.boolean().optional()
429
+ });
430
+ const graphEdgeSchema = z.object({
431
+ id: z.string().min(1),
432
+ from: z.string().min(1),
433
+ fromPort: z.string().min(1),
434
+ to: z.string().min(1),
435
+ toPort: z.string().min(1)
436
+ });
437
+ const graphSchema = z.object({
438
+ schema: z.literal(1),
439
+ nodes: z.array(graphNodeSchema),
440
+ edges: z.array(graphEdgeSchema)
441
+ });
442
+ // ../engine/src/node.ts
443
+ const FORM_FIELD_TYPES = [
444
+ "text",
445
+ "number",
446
+ "email",
447
+ "textarea",
448
+ "dropdown",
449
+ "checkbox",
450
+ "radio",
451
+ "password",
452
+ "date"
453
+ ];
454
+ /** The most items one poll may return. A poll that returns more fails and the cursor stays. */
455
+ const POLL_MAX_ITEMS = 1e3;
456
+ function defineNode(definition) {
457
+ return definition;
458
+ }
459
+ graphSchema.extend({
460
+ name: z.string().trim().min(1).max(120).optional(),
461
+ variables: z.array(z.string()).optional()
462
+ });
463
+ z.object({
464
+ code: z.string().min(1).max(2e5),
465
+ /** Names the code can read, for example `$input` and `$vars`. Plain JSON values only. */
466
+ scope: z.record(z.string(), z.unknown()),
467
+ timeoutMs: z.number().int().min(1).max(6e4),
468
+ maxOutputBytes: z.number().int().min(1).optional()
469
+ });
470
+ // ../engine/src/tables.ts
471
+ const COLUMN_TYPES = [
472
+ "string",
473
+ "number",
474
+ "boolean",
475
+ "date",
476
+ "json"
477
+ ];
478
+ const MAX_QUERY_ROWS = 1e3;
479
+ const COLUMN_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
480
+ /** Every row has these. A column cannot use these keys. */
481
+ const SYSTEM_FIELDS = [
482
+ "id",
483
+ "createdAt",
484
+ "updatedAt"
485
+ ];
486
+ const columnKeySchema = z.string().max(64).regex(COLUMN_KEY_PATTERN, "Use letters, digits, and _. Do not start with a digit.").refine((key) => !SYSTEM_FIELDS.includes(key), { message: "id, createdAt, and updatedAt are reserved" });
487
+ const columnSchema = z.object({
488
+ key: columnKeySchema,
489
+ type: z.enum(COLUMN_TYPES),
490
+ required: z.boolean().default(false),
491
+ unique: z.boolean().default(false),
492
+ default: z.unknown().optional()
493
+ }).superRefine((column, ctx) => {
494
+ if (column.unique && column.type === "json") ctx.addIssue({
495
+ code: "custom",
496
+ message: "A json column cannot be unique",
497
+ path: ["unique"]
498
+ });
499
+ if (column.default !== void 0 && column.default !== null) {
500
+ const checked = checkValue(column.type, column.default);
501
+ if (!checked.ok) ctx.addIssue({
502
+ code: "custom",
503
+ message: checked.message,
504
+ path: ["default"]
505
+ });
506
+ }
507
+ if (column.unique && column.default !== void 0 && column.default !== null) ctx.addIssue({
508
+ code: "custom",
509
+ message: "A unique column cannot have a default",
510
+ path: ["default"]
511
+ });
512
+ });
513
+ z.array(columnSchema).max(20, `A table can have 20 columns`).superRefine((columns, ctx) => {
514
+ const seen = /* @__PURE__ */ new Set();
515
+ for (const [index, column] of columns.entries()) {
516
+ if (seen.has(column.key)) ctx.addIssue({
517
+ code: "custom",
518
+ message: `Column "${column.key}" is listed twice`,
519
+ path: [index, "key"]
520
+ });
521
+ seen.add(column.key);
522
+ }
523
+ });
524
+ z.string().trim().min(1).max(80);
525
+ function isJson(value, depth = 0) {
526
+ if (depth > 64) return false;
527
+ if (value === null) return true;
528
+ switch (typeof value) {
529
+ case "string":
530
+ case "boolean": return true;
531
+ case "number": return Number.isFinite(value);
532
+ case "object": return Array.isArray(value) ? value.every((item) => isJson(item, depth + 1)) : Object.values(value).every((item) => isJson(item, depth + 1));
533
+ default: return false;
534
+ }
535
+ }
536
+ /** Checks one non-null value against a column type. A date is stored as an ISO text in UTC. */
537
+ function checkValue(type, value) {
538
+ switch (type) {
539
+ case "string": return typeof value === "string" ? {
540
+ ok: true,
541
+ value
542
+ } : {
543
+ ok: false,
544
+ message: "must be a string"
545
+ };
546
+ case "number": return typeof value === "number" && Number.isFinite(value) ? {
547
+ ok: true,
548
+ value
549
+ } : {
550
+ ok: false,
551
+ message: "must be a number"
552
+ };
553
+ case "boolean": return typeof value === "boolean" ? {
554
+ ok: true,
555
+ value
556
+ } : {
557
+ ok: false,
558
+ message: "must be true or false"
559
+ };
560
+ case "date": {
561
+ if (typeof value !== "string" && !(value instanceof Date) && typeof value !== "number") return {
562
+ ok: false,
563
+ message: "must be a date"
564
+ };
565
+ const time = new Date(value).getTime();
566
+ if (Number.isNaN(time)) return {
567
+ ok: false,
568
+ message: "must be a date"
569
+ };
570
+ return {
571
+ ok: true,
572
+ value: new Date(time).toISOString()
573
+ };
574
+ }
575
+ case "json": return isJson(value) ? {
576
+ ok: true,
577
+ value
578
+ } : {
579
+ ok: false,
580
+ message: "must be JSON"
581
+ };
582
+ }
583
+ }
584
+ const conditionSchema = z.object({
585
+ field: z.string().min(1).max(64),
586
+ op: z.enum([
587
+ "eq",
588
+ "neq",
589
+ "gt",
590
+ "gte",
591
+ "lt",
592
+ "lte",
593
+ "in",
594
+ "contains",
595
+ "isNull"
596
+ ]),
597
+ value: z.unknown().optional()
598
+ });
599
+ /** A condition, or `{ and: [...] }`, or `{ or: [...] }`. No raw SQL. */
600
+ const filterSchema = z.lazy(() => z.union([
601
+ z.object({ and: z.array(filterSchema).min(1).max(20) }),
602
+ z.object({ or: z.array(filterSchema).min(1).max(20) }),
603
+ conditionSchema
604
+ ]));
605
+ const sortSchema = z.array(z.object({
606
+ field: z.string().min(1).max(64),
607
+ direction: z.enum(["asc", "desc"]).default("asc")
608
+ })).max(3);
609
+ z.object({
610
+ filter: filterSchema.optional(),
611
+ sort: sortSchema.optional(),
612
+ limit: z.number().int().min(1).max(MAX_QUERY_ROWS).default(100),
613
+ cursor: z.string().max(512).nullish()
614
+ });
615
+ // ../engine/src/variables.ts
616
+ const VARIABLE_TYPES = [
617
+ "string",
618
+ "number",
619
+ "boolean",
620
+ "json"
621
+ ];
622
+ const VARIABLE_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
623
+ /** Returns an error message, or null when the value fits the type and the size limit. */
624
+ function checkVariableValue(type, value) {
625
+ if (!(type === "string" && typeof value === "string" || type === "number" && typeof value === "number" && Number.isFinite(value) || type === "boolean" && typeof value === "boolean" || type === "json" && value !== void 0)) return `The value must be of type ${type}`;
626
+ return new TextEncoder().encode(JSON.stringify(value)).length > 65536 ? "The value is larger than 64 KB" : null;
627
+ }
628
+ z.object({
629
+ key: z.string().max(64).regex(VARIABLE_KEY_PATTERN, "Use letters, digits, and _. Do not start with a digit."),
630
+ type: z.enum(VARIABLE_TYPES),
631
+ value: z.unknown(),
632
+ description: z.string().max(500).default("")
633
+ }).superRefine((input, ctx) => {
634
+ const problem = checkVariableValue(input.type, input.value);
635
+ if (problem) ctx.addIssue({
636
+ code: "custom",
637
+ message: problem,
638
+ path: ["value"]
639
+ });
640
+ });
641
+ // src/icon.ts
642
+ const TYPES = {
643
+ ".svg": "image/svg+xml",
644
+ ".png": "image/png",
645
+ ".webp": "image/webp"
646
+ };
647
+ /**
648
+ * Reads an icon file of a package and returns it as a data URL, for `iconUrl` of a node or a
649
+ * credential type. `from` is `import.meta.url` of the file that calls it, and `path` is relative
650
+ * to that file, for example `fileIcon(import.meta.url, "./redis.svg")`. The icon file must be in
651
+ * the `files` of the package.
652
+ */
653
+ function fileIcon(from, path) {
654
+ const type = TYPES[extname(path).toLowerCase()];
655
+ if (!type) throw new Error(`Icon "${path}" must be .svg, .png, or .webp`);
656
+ const file = fileURLToPath(new URL(path, from));
657
+ return `data:${type};base64,${readFileSync(file).toString("base64")}`;
658
+ }
659
+ // src/version.ts
660
+ /**
661
+ * Version of the node contract (the shape of `NodeDefinition` and what a node can use).
662
+ * A minor version only adds. A major version can break a node package.
663
+ */
664
+ const SDK_VERSION = "1.0.0";
665
+ function parse(text) {
666
+ const match = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(text.trim());
667
+ if (!match) return null;
668
+ return [
669
+ Number(match[1]),
670
+ Number(match[2] ?? 0),
671
+ Number(match[3] ?? 0)
672
+ ];
673
+ }
674
+ const compare = (a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
675
+ /**
676
+ * True when `version` fits `range`. A range is a caret range (`^1.4`, `^1.4.2`) or one exact
677
+ * version (`1.4.2`). A caret range allows the same major version, from the given version up.
678
+ * For major 0 it allows the same minor version only, as npm does.
679
+ */
680
+ function satisfiesSdk(range, version = SDK_VERSION) {
681
+ const actual = parse(version);
682
+ if (!actual) return false;
683
+ const text = range.trim();
684
+ if (text.startsWith("^")) {
685
+ const floor = parse(text.slice(1));
686
+ if (!floor) return false;
687
+ if (compare(actual, floor) < 0) return false;
688
+ if (actual[0] !== floor[0]) return false;
689
+ return floor[0] !== 0 || actual[1] === floor[1];
690
+ }
691
+ const exact = parse(text);
692
+ return exact !== null && compare(actual, exact) === 0;
693
+ }
694
+ export { FORM_FIELD_TYPES, POLL_MAX_ITEMS, SDK_VERSION, defineNode, fileIcon, resolveTemplate, satisfiesSdk };