@doclo/core 0.1.5

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.
@@ -0,0 +1 @@
1
+ export { z as AggregatedMetrics, B as BBox, r as CategorizeNodeConfig, s as ChunkMetadata, u as ChunkNodeConfig, t as ChunkOutput, n as CitationConfig, k as CitationSourceType, v as CombineNodeConfig, T as CompatibilityRule, C as ConsensusConfig, e as ConsensusMetadata, d as ConsensusRunResult, D as DocumentIR, b as DocumentIRExtras, x as EnhancedExtractionSchema, E as ExtractNodeConfig, $ as ExtractedImage, m as FieldCitation, F as FieldVotingDetails, G as FlowContext, a6 as FlowExecutionError, h as FlowInput, i as FlowInputValidation, j as FlowResult, a7 as FlowValidationError, I as IRLine, a as IRPage, W as JSONSchemaNode, c as LLMJsonProvider, L as LLMProvider, Z as LanguageOptions, l as LineCitation, g as MaybeWithConsensusMetadata, M as MultimodalInput, a8 as NODE_COMPATIBILITY_MATRIX, H as NodeCtx, K as NodeDef, aq as NodeObservabilityContext, J as NodeTypeInfo, Q as NodeTypeName, N as NormalizedBBox, O as OCRProvider, a0 as OCRProviderOptions, w as OutputNodeConfig, o as OutputWithCitations, f as OutputWithConsensus, Y as PageRangeOptions, p as ParseNodeConfig, X as ProcessingMode, a2 as ProviderCitation, ah as RESERVED_VARIABLES, R as ReasoningConfig, _ as SegmentationResult, S as SplitDocument, q as SplitNodeConfig, y as StepMetric, ao as SupportedMimeType, ap as TraceContextLite, V as VLMProvider, a1 as VLMProviderOptions, U as ValidationResult, a3 as aggregateMetrics, af as canStartForEachItemFlow, ab as getCompatibleTargets, aa as getNodeTypeInfo, a9 as getNodeTypeName, ac as getSuggestedConnections, ae as getValidForEachStarters, a4 as node, ai as protectReservedVariables, a5 as runPipeline, ag as validateJson, ad as validateNodeConnection } from '../validation-CzOz6fwq.js';
@@ -0,0 +1,650 @@
1
+ // src/internal/validation-utils.ts
2
+ function aggregateMetrics(metrics) {
3
+ const byProvider = {};
4
+ const result = metrics.reduce((acc, m) => {
5
+ acc.totalDurationMs += m.ms;
6
+ acc.totalCostUSD += m.costUSD || 0;
7
+ acc.totalInputTokens += m.inputTokens || 0;
8
+ acc.totalOutputTokens += m.outputTokens || 0;
9
+ acc.totalCacheCreationTokens += m.cacheCreationInputTokens || 0;
10
+ acc.totalCacheReadTokens += m.cacheReadInputTokens || 0;
11
+ if (m.provider) {
12
+ if (!byProvider[m.provider]) {
13
+ byProvider[m.provider] = { costUSD: 0, inputTokens: 0, outputTokens: 0, callCount: 0 };
14
+ }
15
+ byProvider[m.provider].costUSD += m.costUSD || 0;
16
+ byProvider[m.provider].inputTokens += m.inputTokens || 0;
17
+ byProvider[m.provider].outputTokens += m.outputTokens || 0;
18
+ byProvider[m.provider].callCount += 1;
19
+ }
20
+ return acc;
21
+ }, {
22
+ totalDurationMs: 0,
23
+ totalCostUSD: 0,
24
+ totalInputTokens: 0,
25
+ totalOutputTokens: 0,
26
+ totalCacheCreationTokens: 0,
27
+ totalCacheReadTokens: 0,
28
+ stepCount: metrics.length,
29
+ byProvider
30
+ });
31
+ return result;
32
+ }
33
+ var node = (key, run) => ({ key, run });
34
+ async function runPipeline(steps, input, observabilityContext) {
35
+ const artifacts = {};
36
+ const metrics = [];
37
+ const ctx = {
38
+ stepId: observabilityContext?.stepId,
39
+ artifacts,
40
+ emit: (k, v) => {
41
+ artifacts[k] = v;
42
+ },
43
+ metrics: { push: (m) => metrics.push(m) },
44
+ observability: observabilityContext
45
+ };
46
+ let acc = input;
47
+ for (const s of steps) {
48
+ acc = await s.run(acc, ctx);
49
+ ctx.emit(s.key, acc);
50
+ }
51
+ return { output: acc, artifacts, metrics };
52
+ }
53
+ var FlowExecutionError = class _FlowExecutionError extends Error {
54
+ constructor(message, failedStep, failedStepIndex, failedStepType, completedSteps, originalError, partialArtifacts) {
55
+ super(message);
56
+ this.failedStep = failedStep;
57
+ this.failedStepIndex = failedStepIndex;
58
+ this.failedStepType = failedStepType;
59
+ this.completedSteps = completedSteps;
60
+ this.originalError = originalError;
61
+ this.partialArtifacts = partialArtifacts;
62
+ this.name = "FlowExecutionError";
63
+ if (Error.captureStackTrace) {
64
+ Error.captureStackTrace(this, _FlowExecutionError);
65
+ }
66
+ }
67
+ };
68
+ var FlowValidationError = class _FlowValidationError extends Error {
69
+ constructor(message, reason, suggestions, sourceNode, targetNode, sourceOutputType, targetInputTypes) {
70
+ super(message);
71
+ this.reason = reason;
72
+ this.suggestions = suggestions;
73
+ this.sourceNode = sourceNode;
74
+ this.targetNode = targetNode;
75
+ this.sourceOutputType = sourceOutputType;
76
+ this.targetInputTypes = targetInputTypes;
77
+ this.name = "FlowValidationError";
78
+ if (Error.captureStackTrace) {
79
+ Error.captureStackTrace(this, _FlowValidationError);
80
+ }
81
+ }
82
+ };
83
+ var NODE_COMPATIBILITY_MATRIX = {
84
+ parse: {
85
+ parse: {
86
+ valid: false,
87
+ reason: "Cannot chain parse nodes. Parse is typically the starting node."
88
+ },
89
+ split: {
90
+ valid: false,
91
+ reason: "Split requires FlowInput, but parse outputs DocumentIR. Use split directly on input instead.",
92
+ note: "If you need to re-split after parsing, use trigger to invoke a child flow with FlowInput."
93
+ },
94
+ categorize: {
95
+ valid: true,
96
+ note: "categorize accepts DocumentIR and wraps it with {input, category}"
97
+ },
98
+ extract: {
99
+ valid: true,
100
+ note: "extract accepts DocumentIR and produces typed JSON"
101
+ },
102
+ chunk: {
103
+ valid: true,
104
+ note: "chunk accepts DocumentIR and produces ChunkOutput for RAG"
105
+ },
106
+ combine: {
107
+ valid: false,
108
+ reason: "Parse outputs DocumentIR (single document), not an array. Combine requires array input from forEach.",
109
+ note: "Use parse with chunked:true to output DocumentIR[], then use combine."
110
+ },
111
+ trigger: {
112
+ valid: true,
113
+ note: "trigger accepts any input type"
114
+ },
115
+ output: {
116
+ valid: true,
117
+ note: "output node can follow any node to select or transform results"
118
+ }
119
+ },
120
+ split: {
121
+ parse: {
122
+ valid: true,
123
+ requiresForEach: true,
124
+ reason: "Split outputs SplitDocument[] which requires forEach. forEach auto-unwraps SplitDocument.input \u2192 FlowInput for parse.",
125
+ note: "Enable forEach on split node before connecting to parse."
126
+ },
127
+ split: {
128
+ valid: false,
129
+ reason: "Cannot nest split operations. Split nodes cannot appear in forEach itemFlow."
130
+ },
131
+ categorize: {
132
+ valid: true,
133
+ requiresForEach: true,
134
+ reason: "Split outputs SplitDocument[] which requires forEach. forEach auto-unwraps SplitDocument.input for categorize."
135
+ },
136
+ extract: {
137
+ valid: true,
138
+ requiresForEach: true,
139
+ reason: "Split outputs SplitDocument[] which requires forEach. forEach auto-unwraps SplitDocument.input for extract."
140
+ },
141
+ chunk: {
142
+ valid: false,
143
+ reason: "SplitDocument output is incompatible with Chunk input. Chunk expects DocumentIR or DocumentIR[].",
144
+ note: "Use parse in forEach after split to convert SplitDocument \u2192 DocumentIR, then chunk."
145
+ },
146
+ combine: {
147
+ valid: false,
148
+ reason: "Combine should appear AFTER forEach completes, not as a forEach itemFlow step.",
149
+ note: "Place combine after the forEach block to merge results."
150
+ },
151
+ trigger: {
152
+ valid: true,
153
+ requiresForEach: true,
154
+ reason: "Split outputs SplitDocument[] which requires forEach for processing.",
155
+ note: "forEach auto-unwraps SplitDocument.input for child flow."
156
+ },
157
+ output: {
158
+ valid: true,
159
+ note: "output node can follow any node to select or transform results"
160
+ }
161
+ },
162
+ categorize: {
163
+ parse: {
164
+ valid: true,
165
+ note: "categorize outputs {input, category}. Conditional can unwrap this or use directly."
166
+ },
167
+ split: {
168
+ valid: false,
169
+ reason: "Split requires FlowInput, but categorize outputs {input, category}.",
170
+ note: "Use conditional to unwrap and pass input field to split."
171
+ },
172
+ categorize: {
173
+ valid: true,
174
+ note: "Can chain categorize nodes for multi-level classification."
175
+ },
176
+ extract: {
177
+ valid: true,
178
+ note: "extract can process the categorized document."
179
+ },
180
+ chunk: {
181
+ valid: false,
182
+ reason: "Categorize wraps input as {input, category}. Chunk needs unwrapped DocumentIR.",
183
+ note: "Use conditional to unwrap input field before chunk."
184
+ },
185
+ combine: {
186
+ valid: false,
187
+ reason: "Categorize outputs single result {input, category}, not an array. Combine requires array input."
188
+ },
189
+ trigger: {
190
+ valid: true,
191
+ note: "trigger accepts any input type, including {input, category}"
192
+ },
193
+ output: {
194
+ valid: true,
195
+ note: "output node can follow any node to select or transform results"
196
+ }
197
+ },
198
+ extract: {
199
+ parse: {
200
+ valid: false,
201
+ reason: "Extract outputs typed JSON (terminal node). Cannot pipe JSON to parse.",
202
+ note: "Extract should be one of the last steps in a flow. Use combine if extracting in parallel."
203
+ },
204
+ split: {
205
+ valid: false,
206
+ reason: "Extract outputs typed JSON (terminal node). Cannot pipe JSON to split."
207
+ },
208
+ categorize: {
209
+ valid: false,
210
+ reason: "Extract outputs typed JSON (terminal node). Cannot pipe JSON to categorize."
211
+ },
212
+ extract: {
213
+ valid: false,
214
+ reason: "Extract outputs typed JSON (terminal node). Cannot chain extractions on JSON output.",
215
+ note: "If you need multi-step extraction, extract from DocumentIR/ChunkOutput in parallel, then combine."
216
+ },
217
+ chunk: {
218
+ valid: false,
219
+ reason: "Extract outputs typed JSON, not DocumentIR. Chunk expects DocumentIR input."
220
+ },
221
+ combine: {
222
+ valid: true,
223
+ note: "Use combine to merge parallel extraction results from forEach."
224
+ },
225
+ trigger: {
226
+ valid: true,
227
+ note: "trigger accepts any input type, including extracted JSON"
228
+ },
229
+ output: {
230
+ valid: true,
231
+ note: "output node can follow any node to select or transform results"
232
+ }
233
+ },
234
+ chunk: {
235
+ parse: {
236
+ valid: false,
237
+ reason: "Chunk outputs ChunkOutput (specialized type), not FlowInput. Parse expects FlowInput as input."
238
+ },
239
+ split: {
240
+ valid: false,
241
+ reason: "Chunk outputs ChunkOutput, incompatible with Split input (FlowInput)."
242
+ },
243
+ categorize: {
244
+ valid: false,
245
+ reason: "Chunk outputs ChunkOutput, incompatible with Categorize input (DocumentIR|FlowInput).",
246
+ note: "Categorize before chunking, not after."
247
+ },
248
+ extract: {
249
+ valid: true,
250
+ note: "extract has special handling for ChunkOutput - extracts data from chunks."
251
+ },
252
+ chunk: {
253
+ valid: false,
254
+ reason: "Cannot chain chunk operations. Chunk only once per document.",
255
+ note: "Different chunking strategies should be applied to the original DocumentIR, not to chunks."
256
+ },
257
+ combine: {
258
+ valid: false,
259
+ reason: "Chunk outputs ChunkOutput (specialized type), not an array type. Combine expects T[].",
260
+ note: "Use chunk on individual documents in forEach, then extract, then combine extractions."
261
+ },
262
+ trigger: {
263
+ valid: true,
264
+ note: "trigger accepts any input type, including ChunkOutput"
265
+ },
266
+ output: {
267
+ valid: true,
268
+ note: "output node can follow any node to select or transform results"
269
+ }
270
+ },
271
+ combine: {
272
+ parse: {
273
+ valid: true,
274
+ note: "After combining, result can be re-parsed if needed."
275
+ },
276
+ split: {
277
+ valid: false,
278
+ reason: "Combine output depends on strategy. Split requires FlowInput.",
279
+ note: "Most combine strategies output merged objects/arrays, not FlowInput."
280
+ },
281
+ categorize: {
282
+ valid: true,
283
+ note: "Can categorize combined results."
284
+ },
285
+ extract: {
286
+ valid: true,
287
+ note: "Can extract from combined results."
288
+ },
289
+ chunk: {
290
+ valid: true,
291
+ note: "Can chunk combined DocumentIR. Only valid if combine output is DocumentIR or DocumentIR[]."
292
+ },
293
+ combine: {
294
+ valid: false,
295
+ reason: "Cannot chain combine nodes. Combine once per forEach operation."
296
+ },
297
+ trigger: {
298
+ valid: true,
299
+ note: "trigger accepts any input type"
300
+ },
301
+ output: {
302
+ valid: true,
303
+ note: "output node can follow any node to select or transform results"
304
+ }
305
+ },
306
+ trigger: {
307
+ parse: {
308
+ valid: true,
309
+ requiresRuntimeValidation: true,
310
+ note: "Valid only if child flow returns FlowInput. Type safety cannot be guaranteed at build-time."
311
+ },
312
+ split: {
313
+ valid: true,
314
+ requiresRuntimeValidation: true,
315
+ note: "Valid only if child flow returns FlowInput. Type safety cannot be guaranteed at build-time."
316
+ },
317
+ categorize: {
318
+ valid: true,
319
+ requiresRuntimeValidation: true,
320
+ note: "Valid only if child flow returns DocumentIR or FlowInput. Type safety cannot be guaranteed at build-time."
321
+ },
322
+ extract: {
323
+ valid: true,
324
+ requiresRuntimeValidation: true,
325
+ note: "Valid only if child flow returns DocumentIR, FlowInput, or ChunkOutput. Type safety cannot be guaranteed at build-time."
326
+ },
327
+ chunk: {
328
+ valid: true,
329
+ requiresRuntimeValidation: true,
330
+ note: "Valid only if child flow returns DocumentIR or DocumentIR[]. Type safety cannot be guaranteed at build-time."
331
+ },
332
+ combine: {
333
+ valid: true,
334
+ requiresRuntimeValidation: true,
335
+ note: "Valid only if child flow returns an array (T[]). Type safety cannot be guaranteed at build-time."
336
+ },
337
+ trigger: {
338
+ valid: true,
339
+ requiresRuntimeValidation: true,
340
+ note: "Can nest trigger nodes (with circular dependency detection and max depth limits). Output type depends on nested child flow."
341
+ },
342
+ output: {
343
+ valid: true,
344
+ note: "output node can follow any node to select or transform results"
345
+ }
346
+ },
347
+ output: {
348
+ parse: {
349
+ valid: false,
350
+ reason: "Output is a terminal node that selects/transforms results. Cannot chain to other nodes."
351
+ },
352
+ split: {
353
+ valid: false,
354
+ reason: "Output is a terminal node that selects/transforms results. Cannot chain to other nodes."
355
+ },
356
+ categorize: {
357
+ valid: false,
358
+ reason: "Output is a terminal node that selects/transforms results. Cannot chain to other nodes."
359
+ },
360
+ extract: {
361
+ valid: false,
362
+ reason: "Output is a terminal node that selects/transforms results. Cannot chain to other nodes."
363
+ },
364
+ chunk: {
365
+ valid: false,
366
+ reason: "Output is a terminal node that selects/transforms results. Cannot chain to other nodes."
367
+ },
368
+ combine: {
369
+ valid: false,
370
+ reason: "Output is a terminal node that selects/transforms results. Cannot chain to other nodes."
371
+ },
372
+ trigger: {
373
+ valid: false,
374
+ reason: "Output is a terminal node that selects/transforms results. Cannot chain to other nodes."
375
+ },
376
+ output: {
377
+ valid: true,
378
+ note: "Multiple output nodes are allowed to create multiple named outputs from a flow."
379
+ }
380
+ }
381
+ };
382
+ function getNodeTypeName(node2) {
383
+ if (!node2 || !node2.key) return null;
384
+ const key = node2.key;
385
+ const knownTypes = ["parse", "split", "categorize", "extract", "chunk", "combine", "trigger", "output"];
386
+ return knownTypes.includes(key) ? key : null;
387
+ }
388
+ function getNodeTypeInfo(node2) {
389
+ return node2.__meta || null;
390
+ }
391
+ function getCompatibleTargets(sourceType, includeForEach = false) {
392
+ const rules = NODE_COMPATIBILITY_MATRIX[sourceType];
393
+ if (!rules) return [];
394
+ return Object.entries(rules).filter(([_, rule]) => {
395
+ if (!rule.valid) return false;
396
+ if (rule.requiresForEach && !includeForEach) return false;
397
+ return true;
398
+ }).map(([targetType, _]) => targetType);
399
+ }
400
+ function getSuggestedConnections(sourceType) {
401
+ const compatibleTargets = getCompatibleTargets(sourceType, false);
402
+ const forEachTargets = getCompatibleTargets(sourceType, true).filter(
403
+ (t) => !compatibleTargets.includes(t)
404
+ );
405
+ if (compatibleTargets.length === 0 && forEachTargets.length === 0) {
406
+ return [`${sourceType} has no standard outgoing connections (terminal node).`];
407
+ }
408
+ const suggestions = [];
409
+ if (compatibleTargets.length > 0) {
410
+ suggestions.push(`${sourceType} can connect to:`);
411
+ compatibleTargets.forEach((target) => {
412
+ const rule = NODE_COMPATIBILITY_MATRIX[sourceType][target];
413
+ suggestions.push(` \u2022 ${target}${rule.note ? ` - ${rule.note}` : ""}`);
414
+ });
415
+ }
416
+ if (forEachTargets.length > 0) {
417
+ suggestions.push(`${sourceType} can connect to (with forEach enabled):`);
418
+ forEachTargets.forEach((target) => {
419
+ const rule = NODE_COMPATIBILITY_MATRIX[sourceType][target];
420
+ suggestions.push(` \u2022 ${target}${rule.note ? ` - ${rule.note}` : ""}`);
421
+ });
422
+ }
423
+ return suggestions;
424
+ }
425
+ function validateNodeConnection(sourceType, targetType, forEachEnabled = false) {
426
+ const rule = NODE_COMPATIBILITY_MATRIX[sourceType]?.[targetType];
427
+ if (!rule) {
428
+ return {
429
+ valid: false,
430
+ reason: `Unknown node type combination: ${sourceType} \u2192 ${targetType}`,
431
+ suggestions: ["Ensure both nodes are valid node types."]
432
+ };
433
+ }
434
+ if (!rule.valid) {
435
+ return {
436
+ valid: false,
437
+ reason: rule.reason,
438
+ suggestions: getSuggestedConnections(sourceType)
439
+ };
440
+ }
441
+ if (rule.requiresForEach && !forEachEnabled) {
442
+ return {
443
+ valid: false,
444
+ reason: `Cannot connect ${sourceType} to ${targetType} without forEach enabled.`,
445
+ suggestions: [
446
+ `Enable forEach on the ${sourceType} node:`,
447
+ ` 1. Click the ${sourceType} node`,
448
+ ` 2. Enable "forEach Processing" in the configuration`,
449
+ ` 3. Try connecting again`,
450
+ "",
451
+ ...getSuggestedConnections(sourceType)
452
+ ],
453
+ requiresForEach: true
454
+ };
455
+ }
456
+ if (rule.requiresRuntimeValidation) {
457
+ return {
458
+ valid: true,
459
+ warning: `\u26A0\uFE0F ${sourceType} \u2192 ${targetType}: ${rule.note || "Type compatibility depends on runtime values and cannot be validated at build-time."}`
460
+ };
461
+ }
462
+ return {
463
+ valid: true
464
+ };
465
+ }
466
+ function getValidForEachStarters(parentType) {
467
+ const rules = NODE_COMPATIBILITY_MATRIX[parentType];
468
+ if (!rules) return [];
469
+ return Object.entries(rules).filter(([_, rule]) => rule.valid && rule.requiresForEach).map(([targetType, _]) => targetType);
470
+ }
471
+ function canStartForEachItemFlow(parentType, starterType) {
472
+ const rule = NODE_COMPATIBILITY_MATRIX[parentType]?.[starterType];
473
+ if (!rule) {
474
+ return {
475
+ valid: false,
476
+ reason: `Unknown node type combination: ${parentType} \u2192 forEach \u2192 ${starterType}`,
477
+ suggestions: ["Ensure both nodes are valid node types."]
478
+ };
479
+ }
480
+ if (rule.valid && rule.requiresForEach) {
481
+ return {
482
+ valid: true
483
+ };
484
+ }
485
+ if (!rule.valid) {
486
+ const validStarters2 = getValidForEachStarters(parentType);
487
+ return {
488
+ valid: false,
489
+ reason: `${starterType} cannot start forEach itemFlow after ${parentType}. ${rule.reason || "Type incompatible with forEach unwrapped item."}`,
490
+ suggestions: validStarters2.length > 0 ? [`Valid itemFlow starters for ${parentType}: ${validStarters2.join(", ")}`] : [`${parentType} has no valid forEach itemFlow starters.`]
491
+ };
492
+ }
493
+ const validStarters = getValidForEachStarters(parentType);
494
+ return {
495
+ valid: false,
496
+ reason: `${starterType} cannot start forEach itemFlow after ${parentType}. This connection does not require forEach, meaning it expects the full array, not individual items.`,
497
+ suggestions: validStarters.length > 0 ? [`Valid itemFlow starters for ${parentType}: ${validStarters.join(", ")}`] : [`${parentType} has no valid forEach itemFlow starters.`]
498
+ };
499
+ }
500
+ function validateJson(data, schema) {
501
+ const errors = [];
502
+ const MAX_DEPTH = 50;
503
+ function validate(value, schema2, path = "", depth = 0) {
504
+ if (depth > MAX_DEPTH) {
505
+ errors.push(`${path || "root"}: maximum nesting depth (${MAX_DEPTH}) exceeded`);
506
+ return;
507
+ }
508
+ if (schema2.nullable && (value === null || value === void 0)) {
509
+ return;
510
+ }
511
+ if (value === null || value === void 0) {
512
+ if (schema2.nullable !== true) {
513
+ errors.push(`${path || "root"}: value is null or undefined`);
514
+ }
515
+ return;
516
+ }
517
+ const actualType = Array.isArray(value) ? "array" : typeof value;
518
+ const expectedType = schema2.type;
519
+ if (expectedType) {
520
+ if (expectedType === "integer") {
521
+ if (typeof value !== "number" || !Number.isInteger(value)) {
522
+ errors.push(`${path || "root"}: expected integer, got ${actualType}`);
523
+ return;
524
+ }
525
+ } else if (expectedType === "number") {
526
+ if (typeof value !== "number") {
527
+ errors.push(`${path || "root"}: expected number, got ${actualType}`);
528
+ return;
529
+ }
530
+ } else if (expectedType === "string") {
531
+ if (typeof value !== "string") {
532
+ errors.push(`${path || "root"}: expected string, got ${actualType}`);
533
+ return;
534
+ }
535
+ } else if (expectedType === "boolean") {
536
+ if (typeof value !== "boolean") {
537
+ errors.push(`${path || "root"}: expected boolean, got ${actualType}`);
538
+ return;
539
+ }
540
+ } else if (expectedType === "object") {
541
+ if (typeof value !== "object" || Array.isArray(value)) {
542
+ errors.push(`${path || "root"}: expected object, got ${actualType}`);
543
+ return;
544
+ }
545
+ if (schema2.required && Array.isArray(schema2.required)) {
546
+ for (const reqProp of schema2.required) {
547
+ if (!(reqProp in value)) {
548
+ errors.push(`${path}.${reqProp}: required property missing`);
549
+ }
550
+ }
551
+ }
552
+ const dangerousProps = ["__proto__", "constructor", "prototype"];
553
+ if (schema2.additionalProperties === false && schema2.properties) {
554
+ const allowedProps = Object.keys(schema2.properties);
555
+ const requiredProps = schema2.required || [];
556
+ const allAllowedProps = /* @__PURE__ */ new Set([...allowedProps, ...requiredProps]);
557
+ for (const key of [...Object.keys(value), ...Object.getOwnPropertyNames(value)]) {
558
+ if (dangerousProps.includes(key)) {
559
+ errors.push(`${path}.${key}: dangerous property not allowed`);
560
+ continue;
561
+ }
562
+ if (!allAllowedProps.has(key)) {
563
+ errors.push(`${path}.${key}: additional property not allowed`);
564
+ }
565
+ }
566
+ } else {
567
+ for (const key of dangerousProps) {
568
+ if (key in value && Object.prototype.hasOwnProperty.call(value, key)) {
569
+ errors.push(`${path}.${key}: dangerous property not allowed`);
570
+ }
571
+ }
572
+ }
573
+ if (schema2.properties) {
574
+ const valueObj = value;
575
+ for (const [propName, propSchema] of Object.entries(schema2.properties)) {
576
+ if (propName in valueObj) {
577
+ validate(valueObj[propName], propSchema, path ? `${path}.${propName}` : propName, depth + 1);
578
+ }
579
+ }
580
+ }
581
+ } else if (expectedType === "array") {
582
+ if (!Array.isArray(value)) {
583
+ errors.push(`${path || "root"}: expected array, got ${actualType}`);
584
+ return;
585
+ }
586
+ if (schema2.items && !Array.isArray(schema2.items)) {
587
+ const itemSchema = schema2.items;
588
+ value.forEach((item, index) => {
589
+ validate(item, itemSchema, `${path}[${index}]`, depth + 1);
590
+ });
591
+ }
592
+ }
593
+ }
594
+ }
595
+ validate(data, schema);
596
+ if (errors.length > 0) {
597
+ throw new Error(`Schema validation failed:
598
+ ${errors.join("\n")}`);
599
+ }
600
+ return data;
601
+ }
602
+ var RESERVED_VARIABLES = {
603
+ extract: ["schema", "documentText", "schemaTitle", "schemaDescription", "structuredFormat"],
604
+ categorize: ["categories", "documentText"],
605
+ parse: ["format", "schema", "describeFigures", "citationsEnabled"]
606
+ };
607
+ function protectReservedVariables(nodeType, userVariables, autoInjectedVariables) {
608
+ if (!userVariables || Object.keys(userVariables).length === 0) {
609
+ return autoInjectedVariables;
610
+ }
611
+ const reserved = RESERVED_VARIABLES[nodeType];
612
+ const warnings = [];
613
+ for (const key of reserved) {
614
+ if (key in userVariables) {
615
+ warnings.push(key);
616
+ }
617
+ }
618
+ if (warnings.length > 0) {
619
+ console.warn(
620
+ `[doclo] Attempted to override reserved variables in ${nodeType} node: ${warnings.join(", ")}. These variables are auto-injected from config and cannot be overridden. They will be ignored.`
621
+ );
622
+ }
623
+ return {
624
+ ...autoInjectedVariables,
625
+ ...userVariables,
626
+ // Restore reserved variables to ensure they can't be overridden
627
+ ...Object.fromEntries(
628
+ reserved.map((key) => [key, autoInjectedVariables[key]])
629
+ )
630
+ };
631
+ }
632
+ export {
633
+ FlowExecutionError,
634
+ FlowValidationError,
635
+ NODE_COMPATIBILITY_MATRIX,
636
+ RESERVED_VARIABLES,
637
+ aggregateMetrics,
638
+ canStartForEachItemFlow,
639
+ getCompatibleTargets,
640
+ getNodeTypeInfo,
641
+ getNodeTypeName,
642
+ getSuggestedConnections,
643
+ getValidForEachStarters,
644
+ node,
645
+ protectReservedVariables,
646
+ runPipeline,
647
+ validateJson,
648
+ validateNodeConnection
649
+ };
650
+ //# sourceMappingURL=validation-utils.js.map