@frt-platform/report-core 1.2.1 → 1.3.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/README.md +239 -520
- package/dist/index.d.mts +127 -95
- package/dist/index.d.ts +127 -95
- package/dist/index.js +506 -398
- package/dist/index.mjs +508 -398
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
createUniqueId: () => createUniqueId,
|
|
36
36
|
diffTemplates: () => diffTemplates,
|
|
37
37
|
evaluateCondition: () => evaluateCondition,
|
|
38
|
+
explainValidationError: () => explainValidationError,
|
|
38
39
|
exportJSONSchema: () => exportJSONSchema,
|
|
39
40
|
migrateLegacySchema: () => migrateLegacySchema,
|
|
40
41
|
normalizeReportTemplateSchema: () => normalizeReportTemplateSchema,
|
|
@@ -46,7 +47,7 @@ __export(index_exports, {
|
|
|
46
47
|
});
|
|
47
48
|
module.exports = __toCommonJS(index_exports);
|
|
48
49
|
|
|
49
|
-
// src/schema.ts
|
|
50
|
+
// src/template/schema.ts
|
|
50
51
|
var import_zod = require("zod");
|
|
51
52
|
var REPORT_TEMPLATE_VERSION = 1;
|
|
52
53
|
var REPORT_TEMPLATE_FIELD_TYPES = [
|
|
@@ -171,59 +172,7 @@ var ReportTemplateSchemaValidator = import_zod.z.object({
|
|
|
171
172
|
sections: import_zod.z.array(ReportTemplateSectionSchema).max(25, "Templates can include at most 25 sections.").default([])
|
|
172
173
|
});
|
|
173
174
|
|
|
174
|
-
// src/
|
|
175
|
-
var DEFAULT_FIELD_LABEL = "Untitled question";
|
|
176
|
-
var CORE_FIELD_DEFAULTS = {
|
|
177
|
-
shortText: {
|
|
178
|
-
label: DEFAULT_FIELD_LABEL,
|
|
179
|
-
placeholder: "Short answer text"
|
|
180
|
-
},
|
|
181
|
-
longText: {
|
|
182
|
-
label: DEFAULT_FIELD_LABEL,
|
|
183
|
-
placeholder: "Long answer text"
|
|
184
|
-
},
|
|
185
|
-
number: {
|
|
186
|
-
label: DEFAULT_FIELD_LABEL,
|
|
187
|
-
placeholder: "123"
|
|
188
|
-
},
|
|
189
|
-
date: {
|
|
190
|
-
label: DEFAULT_FIELD_LABEL
|
|
191
|
-
},
|
|
192
|
-
checkbox: {
|
|
193
|
-
label: DEFAULT_FIELD_LABEL,
|
|
194
|
-
placeholder: "Check to confirm"
|
|
195
|
-
},
|
|
196
|
-
singleSelect: {
|
|
197
|
-
label: DEFAULT_FIELD_LABEL,
|
|
198
|
-
options: ["Option 1", "Option 2", "Option 3"]
|
|
199
|
-
},
|
|
200
|
-
multiSelect: {
|
|
201
|
-
label: DEFAULT_FIELD_LABEL,
|
|
202
|
-
options: ["Option 1", "Option 2", "Option 3"],
|
|
203
|
-
allowOther: false
|
|
204
|
-
},
|
|
205
|
-
repeatGroup: {
|
|
206
|
-
// Minimal safe defaults – your builder UI is expected
|
|
207
|
-
// to configure nested fields + min/max as needed.
|
|
208
|
-
label: DEFAULT_FIELD_LABEL,
|
|
209
|
-
fields: []
|
|
210
|
-
// nested fields go here in the UI layer
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// src/ids.ts
|
|
215
|
-
function createUniqueId(prefix, existing) {
|
|
216
|
-
const used = new Set(existing);
|
|
217
|
-
let attempt = used.size + 1;
|
|
218
|
-
let candidate = `${prefix}-${attempt}`;
|
|
219
|
-
while (used.has(candidate)) {
|
|
220
|
-
attempt++;
|
|
221
|
-
candidate = `${prefix}-${attempt}`;
|
|
222
|
-
}
|
|
223
|
-
return candidate;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// src/migrate.ts
|
|
175
|
+
// src/template/migrate.ts
|
|
227
176
|
var LEGACY_FIELD_TYPE_MAP = {
|
|
228
177
|
shorttext: "shortText",
|
|
229
178
|
text: "shortText",
|
|
@@ -250,12 +199,14 @@ function migrateLegacySchema(raw) {
|
|
|
250
199
|
if (obj.fields) {
|
|
251
200
|
obj.sections = [
|
|
252
201
|
{
|
|
253
|
-
id: "
|
|
202
|
+
id: "section_1",
|
|
203
|
+
// ⬅ underscore to match normalizer/tests
|
|
254
204
|
title: obj.title,
|
|
255
205
|
description: obj.description,
|
|
256
206
|
fields: obj.fields.map((f, i) => ({
|
|
257
207
|
...f,
|
|
258
|
-
id: f.id || `
|
|
208
|
+
id: f.id || `field_${i + 1}`,
|
|
209
|
+
// ⬅ underscore here too
|
|
259
210
|
type: LEGACY_FIELD_TYPE_MAP[f.type?.toLowerCase()] ?? f.type
|
|
260
211
|
}))
|
|
261
212
|
}
|
|
@@ -266,10 +217,12 @@ function migrateLegacySchema(raw) {
|
|
|
266
217
|
if (Array.isArray(obj.sections)) {
|
|
267
218
|
obj.sections = obj.sections.map((sec, i) => ({
|
|
268
219
|
...sec,
|
|
269
|
-
id: sec.id || `
|
|
220
|
+
id: sec.id || `section_${i + 1}`,
|
|
221
|
+
// ⬅ underscore
|
|
270
222
|
fields: (sec.fields || []).map((f, idx) => ({
|
|
271
223
|
...f,
|
|
272
|
-
id: f.id || `
|
|
224
|
+
id: f.id || `field_${idx + 1}`,
|
|
225
|
+
// ⬅ underscore
|
|
273
226
|
type: LEGACY_FIELD_TYPE_MAP[f.type?.toLowerCase()] ?? f.type
|
|
274
227
|
}))
|
|
275
228
|
}));
|
|
@@ -277,7 +230,7 @@ function migrateLegacySchema(raw) {
|
|
|
277
230
|
return obj;
|
|
278
231
|
}
|
|
279
232
|
|
|
280
|
-
// src/normalize.ts
|
|
233
|
+
// src/template/normalize.ts
|
|
281
234
|
function normalizeId(raw, fallback) {
|
|
282
235
|
if (!raw || typeof raw !== "string") return fallback;
|
|
283
236
|
const cleaned = raw.trim().toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_-]/g, "");
|
|
@@ -350,349 +303,96 @@ function parseReportTemplateSchema(raw) {
|
|
|
350
303
|
function parseReportTemplateSchemaFromString(raw) {
|
|
351
304
|
return parseReportTemplateSchema(JSON.parse(raw));
|
|
352
305
|
}
|
|
353
|
-
function serializeReportTemplateSchema(schema) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
get(type) {
|
|
371
|
-
return this.registry.get(type);
|
|
372
|
-
}
|
|
373
|
-
/** Check if field type is registered. */
|
|
374
|
-
has(type) {
|
|
375
|
-
return this.registry.has(type);
|
|
376
|
-
}
|
|
377
|
-
/** Return all field types currently registered. */
|
|
378
|
-
list() {
|
|
379
|
-
return [...this.registry.keys()];
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
var FieldRegistry = new FieldRegistryClass();
|
|
383
|
-
var CORE_TYPES = [
|
|
384
|
-
"shortText",
|
|
385
|
-
"longText",
|
|
386
|
-
"number",
|
|
387
|
-
"date",
|
|
388
|
-
"checkbox",
|
|
389
|
-
"singleSelect",
|
|
390
|
-
"multiSelect"
|
|
391
|
-
];
|
|
392
|
-
for (const type of CORE_TYPES) {
|
|
393
|
-
FieldRegistry.register(type, {
|
|
394
|
-
defaults: CORE_FIELD_DEFAULTS[type]
|
|
395
|
-
// Core types rely on existing validation logic.
|
|
396
|
-
// buildResponseSchema is optional — fallback handles it.
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// src/conditions.ts
|
|
401
|
-
function evaluateCondition(condition, response) {
|
|
402
|
-
if ("equals" in condition) {
|
|
403
|
-
return Object.entries(condition.equals).every(([key, val]) => {
|
|
404
|
-
return response[key] === val;
|
|
306
|
+
function serializeReportTemplateSchema(schema, options = {}) {
|
|
307
|
+
const {
|
|
308
|
+
pretty = true,
|
|
309
|
+
sortSectionsById = false,
|
|
310
|
+
sortFieldsById = false
|
|
311
|
+
} = options;
|
|
312
|
+
let toSerialize = schema;
|
|
313
|
+
if (sortSectionsById || sortFieldsById) {
|
|
314
|
+
const sortedSections = [...schema.sections].map((section) => {
|
|
315
|
+
const clonedSection = { ...section };
|
|
316
|
+
if (sortFieldsById) {
|
|
317
|
+
const fields = section.fields ?? [];
|
|
318
|
+
clonedSection.fields = [...fields].sort(
|
|
319
|
+
(a, b) => a.id.localeCompare(b.id)
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
return clonedSection;
|
|
405
323
|
});
|
|
324
|
+
if (sortSectionsById) {
|
|
325
|
+
toSerialize = {
|
|
326
|
+
...schema,
|
|
327
|
+
sections: sortedSections.sort(
|
|
328
|
+
(a, b) => a.id.localeCompare(b.id)
|
|
329
|
+
)
|
|
330
|
+
};
|
|
331
|
+
} else {
|
|
332
|
+
toSerialize = {
|
|
333
|
+
...schema,
|
|
334
|
+
sections: sortedSections
|
|
335
|
+
};
|
|
336
|
+
}
|
|
406
337
|
}
|
|
407
|
-
|
|
408
|
-
return condition.any.some(
|
|
409
|
-
(sub) => evaluateCondition(sub, response)
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
if ("all" in condition) {
|
|
413
|
-
return condition.all.every(
|
|
414
|
-
(sub) => evaluateCondition(sub, response)
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
if ("not" in condition) {
|
|
418
|
-
const sub = condition.not;
|
|
419
|
-
return !evaluateCondition(sub, response);
|
|
420
|
-
}
|
|
421
|
-
return false;
|
|
338
|
+
return JSON.stringify(toSerialize, null, pretty ? 2 : 0);
|
|
422
339
|
}
|
|
423
340
|
|
|
424
|
-
// src/
|
|
425
|
-
function
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
return isRequired ? schema : schema.optional();
|
|
442
|
-
}
|
|
443
|
-
case "number": {
|
|
444
|
-
let schema = import_zod2.z.number();
|
|
445
|
-
if (typeof field.minValue === "number") {
|
|
446
|
-
schema = schema.min(field.minValue);
|
|
447
|
-
}
|
|
448
|
-
if (typeof field.maxValue === "number") {
|
|
449
|
-
schema = schema.max(field.maxValue);
|
|
450
|
-
}
|
|
451
|
-
return isRequired ? schema : schema.optional();
|
|
452
|
-
}
|
|
453
|
-
case "date": {
|
|
454
|
-
let schema = import_zod2.z.string();
|
|
455
|
-
return isRequired ? schema : schema.optional();
|
|
456
|
-
}
|
|
457
|
-
case "checkbox": {
|
|
458
|
-
if (isRequired) return import_zod2.z.literal(true);
|
|
459
|
-
return import_zod2.z.boolean().optional();
|
|
460
|
-
}
|
|
461
|
-
case "singleSelect": {
|
|
462
|
-
let schema = import_zod2.z.string();
|
|
463
|
-
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
464
|
-
const allowed = new Set(field.options);
|
|
465
|
-
schema = schema.refine(
|
|
466
|
-
(value) => allowed.has(value),
|
|
467
|
-
"Value must be one of the defined options."
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
return isRequired ? schema : schema.optional();
|
|
471
|
-
}
|
|
472
|
-
case "multiSelect": {
|
|
473
|
-
let schema = import_zod2.z.array(import_zod2.z.string());
|
|
474
|
-
if (Array.isArray(field.options) && field.options.length > 0 && !field.allowOther) {
|
|
475
|
-
const allowed = new Set(field.options);
|
|
476
|
-
schema = schema.refine(
|
|
477
|
-
(values) => values.every((value) => allowed.has(value)),
|
|
478
|
-
"All values must be among the defined options."
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
if (typeof field.minSelections === "number") {
|
|
482
|
-
schema = schema.min(
|
|
483
|
-
field.minSelections,
|
|
484
|
-
`Select at least ${field.minSelections} option(s).`
|
|
485
|
-
);
|
|
486
|
-
} else if (isRequired) {
|
|
487
|
-
schema = schema.min(
|
|
488
|
-
1,
|
|
489
|
-
"Select at least one option."
|
|
490
|
-
);
|
|
491
|
-
}
|
|
492
|
-
if (typeof field.maxSelections === "number") {
|
|
493
|
-
schema = schema.max(
|
|
494
|
-
field.maxSelections,
|
|
495
|
-
`Select at most ${field.maxSelections} option(s).`
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
return isRequired ? schema : schema.optional();
|
|
499
|
-
}
|
|
500
|
-
default: {
|
|
501
|
-
return import_zod2.z.any();
|
|
341
|
+
// src/template/diff.ts
|
|
342
|
+
function indexById(items) {
|
|
343
|
+
const map = {};
|
|
344
|
+
items.forEach((item, i) => map[item.id] = i);
|
|
345
|
+
return map;
|
|
346
|
+
}
|
|
347
|
+
function diffObjectProperties(before, after, ignoreKeys = []) {
|
|
348
|
+
const changes = [];
|
|
349
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
350
|
+
for (const key of keys) {
|
|
351
|
+
if (ignoreKeys.includes(key)) continue;
|
|
352
|
+
const b = before[key];
|
|
353
|
+
const a = after[key];
|
|
354
|
+
const changed = Array.isArray(b) && Array.isArray(a) ? JSON.stringify(b) !== JSON.stringify(a) : b !== a;
|
|
355
|
+
if (changed) {
|
|
356
|
+
changes.push({ key, before: b, after: a });
|
|
502
357
|
}
|
|
503
358
|
}
|
|
359
|
+
return changes;
|
|
504
360
|
}
|
|
505
|
-
function
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
361
|
+
function diffRepeatGroupFields(beforeFields, afterFields, sectionId, groupId) {
|
|
362
|
+
const empty = {
|
|
363
|
+
addedSections: [],
|
|
364
|
+
removedSections: [],
|
|
365
|
+
reorderedSections: [],
|
|
366
|
+
modifiedSections: [],
|
|
367
|
+
addedFields: [],
|
|
368
|
+
removedFields: [],
|
|
369
|
+
reorderedFields: [],
|
|
370
|
+
modifiedFields: [],
|
|
371
|
+
nestedFieldDiffs: []
|
|
372
|
+
};
|
|
373
|
+
const beforeIndex = indexById(beforeFields);
|
|
374
|
+
const afterIndex = indexById(afterFields);
|
|
375
|
+
for (const f of afterFields) {
|
|
376
|
+
if (!(f.id in beforeIndex)) {
|
|
377
|
+
empty.addedFields.push({ sectionId, fieldId: f.id, index: afterIndex[f.id] });
|
|
510
378
|
}
|
|
511
379
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
return import_zod2.z.object(shape);
|
|
531
|
-
}
|
|
532
|
-
function validateReportResponse(template, data) {
|
|
533
|
-
if (typeof data !== "object" || data === null) {
|
|
534
|
-
throw new Error("Response must be an object");
|
|
535
|
-
}
|
|
536
|
-
const response = data;
|
|
537
|
-
const schema = buildResponseSchemaWithConditions(template, response);
|
|
538
|
-
const parsed = schema.parse(response);
|
|
539
|
-
for (const section of template.sections) {
|
|
540
|
-
for (const field of section.fields) {
|
|
541
|
-
if (field.visibleIf) {
|
|
542
|
-
const visible = evaluateCondition(field.visibleIf, response);
|
|
543
|
-
if (!visible) {
|
|
544
|
-
delete parsed[field.id];
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
return parsed;
|
|
550
|
-
}
|
|
551
|
-
function mapZodIssueToFieldErrorCode(issue) {
|
|
552
|
-
switch (issue.code) {
|
|
553
|
-
case import_zod2.z.ZodIssueCode.invalid_type:
|
|
554
|
-
return "field.invalid_type";
|
|
555
|
-
case import_zod2.z.ZodIssueCode.too_small:
|
|
556
|
-
return "field.too_small";
|
|
557
|
-
case import_zod2.z.ZodIssueCode.too_big:
|
|
558
|
-
return "field.too_big";
|
|
559
|
-
case import_zod2.z.ZodIssueCode.invalid_enum_value:
|
|
560
|
-
case import_zod2.z.ZodIssueCode.invalid_literal:
|
|
561
|
-
return "field.invalid_option";
|
|
562
|
-
case import_zod2.z.ZodIssueCode.custom:
|
|
563
|
-
return "field.custom";
|
|
564
|
-
default:
|
|
565
|
-
return "field.custom";
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
function buildFieldMetadataLookup(template) {
|
|
569
|
-
const map = /* @__PURE__ */ new Map();
|
|
570
|
-
for (const section of template.sections) {
|
|
571
|
-
for (const field of section.fields) {
|
|
572
|
-
map.set(field.id, {
|
|
573
|
-
sectionId: section.id,
|
|
574
|
-
sectionTitle: section.title,
|
|
575
|
-
label: field.label
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
return map;
|
|
580
|
-
}
|
|
581
|
-
function validateReportResponseDetailed(template, data) {
|
|
582
|
-
if (typeof data !== "object" || data === null) {
|
|
583
|
-
return {
|
|
584
|
-
success: false,
|
|
585
|
-
errors: [
|
|
586
|
-
{
|
|
587
|
-
fieldId: "",
|
|
588
|
-
code: "response.invalid_root",
|
|
589
|
-
message: "Response must be an object."
|
|
590
|
-
}
|
|
591
|
-
]
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
const response = data;
|
|
595
|
-
const schema = buildResponseSchemaWithConditions(template, response);
|
|
596
|
-
const fieldMeta = buildFieldMetadataLookup(template);
|
|
597
|
-
const result = schema.safeParse(response);
|
|
598
|
-
if (result.success) {
|
|
599
|
-
const parsed = { ...result.data };
|
|
600
|
-
for (const section of template.sections) {
|
|
601
|
-
for (const field of section.fields) {
|
|
602
|
-
if (field.visibleIf) {
|
|
603
|
-
const visible = evaluateCondition(field.visibleIf, response);
|
|
604
|
-
if (!visible) {
|
|
605
|
-
delete parsed[field.id];
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
return { success: true, value: parsed };
|
|
611
|
-
}
|
|
612
|
-
const errors = [];
|
|
613
|
-
for (const issue of result.error.issues) {
|
|
614
|
-
const path = issue.path ?? [];
|
|
615
|
-
const fieldId = typeof path[0] === "string" ? path[0] : "";
|
|
616
|
-
const meta = fieldId ? fieldMeta.get(fieldId) : void 0;
|
|
617
|
-
const code = mapZodIssueToFieldErrorCode(issue);
|
|
618
|
-
const sectionLabel = meta?.sectionTitle ?? meta?.sectionId;
|
|
619
|
-
const fieldLabel = meta?.label ?? fieldId;
|
|
620
|
-
let message;
|
|
621
|
-
if (meta && sectionLabel && fieldLabel) {
|
|
622
|
-
message = `Section "${sectionLabel}" \u2192 Field "${fieldLabel}": ${issue.message}`;
|
|
623
|
-
} else if (fieldLabel) {
|
|
624
|
-
message = `Field "${fieldLabel}": ${issue.message}`;
|
|
625
|
-
} else {
|
|
626
|
-
message = issue.message;
|
|
627
|
-
}
|
|
628
|
-
errors.push({
|
|
629
|
-
fieldId,
|
|
630
|
-
sectionId: meta?.sectionId,
|
|
631
|
-
sectionTitle: meta?.sectionTitle,
|
|
632
|
-
label: meta?.label,
|
|
633
|
-
code,
|
|
634
|
-
message,
|
|
635
|
-
rawIssue: issue
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
return { success: false, errors };
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// src/diff.ts
|
|
642
|
-
function indexById(items) {
|
|
643
|
-
const map = {};
|
|
644
|
-
items.forEach((item, i) => map[item.id] = i);
|
|
645
|
-
return map;
|
|
646
|
-
}
|
|
647
|
-
function diffObjectProperties(before, after, ignoreKeys = []) {
|
|
648
|
-
const changes = [];
|
|
649
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
650
|
-
for (const key of keys) {
|
|
651
|
-
if (ignoreKeys.includes(key)) continue;
|
|
652
|
-
const b = before[key];
|
|
653
|
-
const a = after[key];
|
|
654
|
-
const changed = Array.isArray(b) && Array.isArray(a) ? JSON.stringify(b) !== JSON.stringify(a) : b !== a;
|
|
655
|
-
if (changed) {
|
|
656
|
-
changes.push({ key, before: b, after: a });
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return changes;
|
|
660
|
-
}
|
|
661
|
-
function diffRepeatGroupFields(beforeFields, afterFields, sectionId, groupId) {
|
|
662
|
-
const empty = {
|
|
663
|
-
addedSections: [],
|
|
664
|
-
removedSections: [],
|
|
665
|
-
reorderedSections: [],
|
|
666
|
-
modifiedSections: [],
|
|
667
|
-
addedFields: [],
|
|
668
|
-
removedFields: [],
|
|
669
|
-
reorderedFields: [],
|
|
670
|
-
modifiedFields: [],
|
|
671
|
-
nestedFieldDiffs: []
|
|
672
|
-
};
|
|
673
|
-
const beforeIndex = indexById(beforeFields);
|
|
674
|
-
const afterIndex = indexById(afterFields);
|
|
675
|
-
for (const f of afterFields) {
|
|
676
|
-
if (!(f.id in beforeIndex)) {
|
|
677
|
-
empty.addedFields.push({ sectionId, fieldId: f.id, index: afterIndex[f.id] });
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
for (const f of beforeFields) {
|
|
681
|
-
if (!(f.id in afterIndex)) {
|
|
682
|
-
empty.removedFields.push({ sectionId, fieldId: f.id, index: beforeIndex[f.id] });
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
for (const f of afterFields) {
|
|
686
|
-
if (f.id in beforeIndex) {
|
|
687
|
-
const from = beforeIndex[f.id];
|
|
688
|
-
const to = afterIndex[f.id];
|
|
689
|
-
if (from !== to) {
|
|
690
|
-
empty.reorderedFields.push({
|
|
691
|
-
sectionId,
|
|
692
|
-
fieldId: f.id,
|
|
693
|
-
from,
|
|
694
|
-
to
|
|
695
|
-
});
|
|
380
|
+
for (const f of beforeFields) {
|
|
381
|
+
if (!(f.id in afterIndex)) {
|
|
382
|
+
empty.removedFields.push({ sectionId, fieldId: f.id, index: beforeIndex[f.id] });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
for (const f of afterFields) {
|
|
386
|
+
if (f.id in beforeIndex) {
|
|
387
|
+
const from = beforeIndex[f.id];
|
|
388
|
+
const to = afterIndex[f.id];
|
|
389
|
+
if (from !== to) {
|
|
390
|
+
empty.reorderedFields.push({
|
|
391
|
+
sectionId,
|
|
392
|
+
fieldId: f.id,
|
|
393
|
+
from,
|
|
394
|
+
to
|
|
395
|
+
});
|
|
696
396
|
}
|
|
697
397
|
}
|
|
698
398
|
}
|
|
@@ -805,7 +505,7 @@ function diffTemplates(before, after) {
|
|
|
805
505
|
return diff;
|
|
806
506
|
}
|
|
807
507
|
|
|
808
|
-
// src/jsonSchema.ts
|
|
508
|
+
// src/template/jsonSchema.ts
|
|
809
509
|
function mapFieldTypeToJSONSchemaCore(field) {
|
|
810
510
|
const common = {
|
|
811
511
|
title: field.label,
|
|
@@ -857,9 +557,6 @@ function mapFieldTypeToJSONSchemaCore(field) {
|
|
|
857
557
|
...common,
|
|
858
558
|
type: "boolean"
|
|
859
559
|
};
|
|
860
|
-
if (field.required && !field.requiredIf) {
|
|
861
|
-
schema.const = true;
|
|
862
|
-
}
|
|
863
560
|
return schema;
|
|
864
561
|
}
|
|
865
562
|
case "singleSelect": {
|
|
@@ -867,7 +564,8 @@ function mapFieldTypeToJSONSchemaCore(field) {
|
|
|
867
564
|
...common,
|
|
868
565
|
type: "string"
|
|
869
566
|
};
|
|
870
|
-
|
|
567
|
+
const hasOptions = Array.isArray(field.options) && field.options.length > 0;
|
|
568
|
+
if (hasOptions && !field.allowOther) {
|
|
871
569
|
schema.enum = field.options;
|
|
872
570
|
}
|
|
873
571
|
return schema;
|
|
@@ -895,6 +593,38 @@ function mapFieldTypeToJSONSchemaCore(field) {
|
|
|
895
593
|
}
|
|
896
594
|
return schema;
|
|
897
595
|
}
|
|
596
|
+
case "repeatGroup": {
|
|
597
|
+
const nestedFields = Array.isArray(field.fields) ? field.fields : [];
|
|
598
|
+
const rowProperties = {};
|
|
599
|
+
const rowRequired = [];
|
|
600
|
+
for (const nested of nestedFields) {
|
|
601
|
+
rowProperties[nested.id] = mapFieldTypeToJSONSchemaCore(
|
|
602
|
+
nested
|
|
603
|
+
);
|
|
604
|
+
if (nested.required === true && !nested.requiredIf && !nested.visibleIf) {
|
|
605
|
+
rowRequired.push(nested.id);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const rowSchema = {
|
|
609
|
+
type: "object",
|
|
610
|
+
properties: rowProperties,
|
|
611
|
+
additionalProperties: false
|
|
612
|
+
};
|
|
613
|
+
if (rowRequired.length > 0) {
|
|
614
|
+
rowSchema.required = rowRequired;
|
|
615
|
+
}
|
|
616
|
+
const schema = {
|
|
617
|
+
...common,
|
|
618
|
+
type: "array",
|
|
619
|
+
items: rowSchema,
|
|
620
|
+
// expose min/max + conditional minIf/maxIf as extensions
|
|
621
|
+
"x-frt-min": field.min,
|
|
622
|
+
"x-frt-max": field.max,
|
|
623
|
+
"x-frt-minIf": field.minIf,
|
|
624
|
+
"x-frt-maxIf": field.maxIf
|
|
625
|
+
};
|
|
626
|
+
return schema;
|
|
627
|
+
}
|
|
898
628
|
default: {
|
|
899
629
|
return {
|
|
900
630
|
...common
|
|
@@ -927,6 +657,383 @@ function exportJSONSchema(template) {
|
|
|
927
657
|
}
|
|
928
658
|
return schema;
|
|
929
659
|
}
|
|
660
|
+
|
|
661
|
+
// src/fields/defaults.ts
|
|
662
|
+
var DEFAULT_FIELD_LABEL = "Untitled question";
|
|
663
|
+
var CORE_FIELD_DEFAULTS = {
|
|
664
|
+
shortText: {
|
|
665
|
+
label: DEFAULT_FIELD_LABEL,
|
|
666
|
+
placeholder: "Short answer text"
|
|
667
|
+
},
|
|
668
|
+
longText: {
|
|
669
|
+
label: DEFAULT_FIELD_LABEL,
|
|
670
|
+
placeholder: "Long answer text"
|
|
671
|
+
},
|
|
672
|
+
number: {
|
|
673
|
+
label: DEFAULT_FIELD_LABEL,
|
|
674
|
+
placeholder: "123"
|
|
675
|
+
},
|
|
676
|
+
date: {
|
|
677
|
+
label: DEFAULT_FIELD_LABEL
|
|
678
|
+
},
|
|
679
|
+
checkbox: {
|
|
680
|
+
label: DEFAULT_FIELD_LABEL,
|
|
681
|
+
placeholder: "Check to confirm"
|
|
682
|
+
},
|
|
683
|
+
singleSelect: {
|
|
684
|
+
label: DEFAULT_FIELD_LABEL,
|
|
685
|
+
options: ["Option 1", "Option 2", "Option 3"]
|
|
686
|
+
},
|
|
687
|
+
multiSelect: {
|
|
688
|
+
label: DEFAULT_FIELD_LABEL,
|
|
689
|
+
options: ["Option 1", "Option 2", "Option 3"],
|
|
690
|
+
allowOther: false
|
|
691
|
+
},
|
|
692
|
+
repeatGroup: {
|
|
693
|
+
// Minimal safe defaults – your builder UI is expected
|
|
694
|
+
// to configure nested fields + min/max as needed.
|
|
695
|
+
label: DEFAULT_FIELD_LABEL,
|
|
696
|
+
fields: []
|
|
697
|
+
// nested fields go here in the UI layer
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
// src/fields/ids.ts
|
|
702
|
+
function createUniqueId(prefix, existing) {
|
|
703
|
+
const used = new Set(existing);
|
|
704
|
+
let attempt = used.size + 1;
|
|
705
|
+
let candidate = `${prefix}-${attempt}`;
|
|
706
|
+
while (used.has(candidate)) {
|
|
707
|
+
attempt++;
|
|
708
|
+
candidate = `${prefix}-${attempt}`;
|
|
709
|
+
}
|
|
710
|
+
return candidate;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/fields/registry.ts
|
|
714
|
+
var FieldRegistryClass = class {
|
|
715
|
+
constructor() {
|
|
716
|
+
this.registry = /* @__PURE__ */ new Map();
|
|
717
|
+
}
|
|
718
|
+
/** Register or override a field type. */
|
|
719
|
+
register(type, entry) {
|
|
720
|
+
this.registry.set(type, entry);
|
|
721
|
+
}
|
|
722
|
+
/** Get registry entry for a field type. */
|
|
723
|
+
get(type) {
|
|
724
|
+
return this.registry.get(type);
|
|
725
|
+
}
|
|
726
|
+
/** Check if field type is registered. */
|
|
727
|
+
has(type) {
|
|
728
|
+
return this.registry.has(type);
|
|
729
|
+
}
|
|
730
|
+
/** Return all field types currently registered. */
|
|
731
|
+
list() {
|
|
732
|
+
return [...this.registry.keys()];
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
var FieldRegistry = new FieldRegistryClass();
|
|
736
|
+
var CORE_TYPES = [
|
|
737
|
+
"shortText",
|
|
738
|
+
"longText",
|
|
739
|
+
"number",
|
|
740
|
+
"date",
|
|
741
|
+
"checkbox",
|
|
742
|
+
"singleSelect",
|
|
743
|
+
"multiSelect",
|
|
744
|
+
"repeatGroup"
|
|
745
|
+
];
|
|
746
|
+
for (const type of CORE_TYPES) {
|
|
747
|
+
FieldRegistry.register(type, {
|
|
748
|
+
defaults: CORE_FIELD_DEFAULTS[type]
|
|
749
|
+
// Core types rely on existing validation logic.
|
|
750
|
+
// buildResponseSchema is optional — fallback handles it.
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/validation/conditions.ts
|
|
755
|
+
function evaluateCondition(condition, response) {
|
|
756
|
+
if ("equals" in condition) {
|
|
757
|
+
return Object.entries(condition.equals).every(([key, val]) => {
|
|
758
|
+
return response[key] === val;
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
if ("any" in condition) {
|
|
762
|
+
return condition.any.some(
|
|
763
|
+
(sub) => evaluateCondition(sub, response)
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
if ("all" in condition) {
|
|
767
|
+
return condition.all.every(
|
|
768
|
+
(sub) => evaluateCondition(sub, response)
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
if ("not" in condition) {
|
|
772
|
+
const sub = condition.not;
|
|
773
|
+
return !evaluateCondition(sub, response);
|
|
774
|
+
}
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/validation/responses.ts
|
|
779
|
+
var import_zod2 = require("zod");
|
|
780
|
+
function buildBaseFieldSchema(field) {
|
|
781
|
+
const isRequired = Boolean(field.required);
|
|
782
|
+
const registry = FieldRegistry.get(field.type);
|
|
783
|
+
if (registry?.buildResponseSchema) {
|
|
784
|
+
return registry.buildResponseSchema(field);
|
|
785
|
+
}
|
|
786
|
+
switch (field.type) {
|
|
787
|
+
case "shortText":
|
|
788
|
+
case "longText": {
|
|
789
|
+
let schema = import_zod2.z.string();
|
|
790
|
+
if (typeof field.minLength === "number") {
|
|
791
|
+
schema = schema.min(field.minLength);
|
|
792
|
+
}
|
|
793
|
+
if (typeof field.maxLength === "number") {
|
|
794
|
+
schema = schema.max(field.maxLength);
|
|
795
|
+
}
|
|
796
|
+
return isRequired ? schema : schema.optional();
|
|
797
|
+
}
|
|
798
|
+
case "number": {
|
|
799
|
+
let schema = import_zod2.z.number();
|
|
800
|
+
if (typeof field.minValue === "number") {
|
|
801
|
+
schema = schema.min(field.minValue);
|
|
802
|
+
}
|
|
803
|
+
if (typeof field.maxValue === "number") {
|
|
804
|
+
schema = schema.max(field.maxValue);
|
|
805
|
+
}
|
|
806
|
+
return isRequired ? schema : schema.optional();
|
|
807
|
+
}
|
|
808
|
+
case "date": {
|
|
809
|
+
let schema = import_zod2.z.string();
|
|
810
|
+
return isRequired ? schema : schema.optional();
|
|
811
|
+
}
|
|
812
|
+
case "checkbox": {
|
|
813
|
+
if (isRequired) return import_zod2.z.literal(true);
|
|
814
|
+
return import_zod2.z.boolean().optional();
|
|
815
|
+
}
|
|
816
|
+
case "singleSelect": {
|
|
817
|
+
let schema = import_zod2.z.string();
|
|
818
|
+
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
819
|
+
const allowed = new Set(field.options);
|
|
820
|
+
schema = schema.refine(
|
|
821
|
+
(value) => allowed.has(value),
|
|
822
|
+
"Value must be one of the defined options."
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
return isRequired ? schema : schema.optional();
|
|
826
|
+
}
|
|
827
|
+
case "multiSelect": {
|
|
828
|
+
let schema = import_zod2.z.array(import_zod2.z.string());
|
|
829
|
+
if (Array.isArray(field.options) && field.options.length > 0 && !field.allowOther) {
|
|
830
|
+
const allowed = new Set(field.options);
|
|
831
|
+
schema = schema.refine(
|
|
832
|
+
(values) => values.every((value) => allowed.has(value)),
|
|
833
|
+
"All values must be among the defined options."
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
if (typeof field.minSelections === "number") {
|
|
837
|
+
schema = schema.min(
|
|
838
|
+
field.minSelections,
|
|
839
|
+
`Select at least ${field.minSelections} option(s).`
|
|
840
|
+
);
|
|
841
|
+
} else if (isRequired) {
|
|
842
|
+
schema = schema.min(
|
|
843
|
+
1,
|
|
844
|
+
"Select at least one option."
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
if (typeof field.maxSelections === "number") {
|
|
848
|
+
schema = schema.max(
|
|
849
|
+
field.maxSelections,
|
|
850
|
+
`Select at most ${field.maxSelections} option(s).`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
return isRequired ? schema : schema.optional();
|
|
854
|
+
}
|
|
855
|
+
case "repeatGroup": {
|
|
856
|
+
const nestedFields = Array.isArray(field.fields) ? field.fields : [];
|
|
857
|
+
const rowShape = {};
|
|
858
|
+
for (const nested of nestedFields) {
|
|
859
|
+
rowShape[nested.id] = buildBaseFieldSchema(
|
|
860
|
+
nested
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
let schema = import_zod2.z.array(import_zod2.z.object(rowShape));
|
|
864
|
+
if (typeof field.min === "number") {
|
|
865
|
+
schema = schema.min(
|
|
866
|
+
field.min,
|
|
867
|
+
`Add at least ${field.min} item(s).`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
if (typeof field.max === "number") {
|
|
871
|
+
schema = schema.max(
|
|
872
|
+
field.max,
|
|
873
|
+
`Add at most ${field.max} item(s).`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
return isRequired ? schema : schema.optional();
|
|
877
|
+
}
|
|
878
|
+
default: {
|
|
879
|
+
return import_zod2.z.any();
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function buildConditionalFieldSchema(field, response) {
|
|
884
|
+
if (field.visibleIf) {
|
|
885
|
+
const visible = evaluateCondition(field.visibleIf, response);
|
|
886
|
+
if (!visible) {
|
|
887
|
+
return import_zod2.z.any().optional();
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
let schema = buildBaseFieldSchema(field);
|
|
891
|
+
if (field.requiredIf) {
|
|
892
|
+
const shouldBeRequired = evaluateCondition(field.requiredIf, response);
|
|
893
|
+
if (shouldBeRequired) {
|
|
894
|
+
if (schema instanceof import_zod2.z.ZodOptional) {
|
|
895
|
+
schema = schema.unwrap();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return schema;
|
|
900
|
+
}
|
|
901
|
+
function buildResponseSchemaWithConditions(template, response) {
|
|
902
|
+
const shape = {};
|
|
903
|
+
for (const section of template.sections) {
|
|
904
|
+
for (const field of section.fields) {
|
|
905
|
+
shape[field.id] = buildConditionalFieldSchema(field, response);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return import_zod2.z.object(shape);
|
|
909
|
+
}
|
|
910
|
+
function validateReportResponse(template, data) {
|
|
911
|
+
if (typeof data !== "object" || data === null) {
|
|
912
|
+
throw new Error("Response must be an object");
|
|
913
|
+
}
|
|
914
|
+
const response = data;
|
|
915
|
+
const schema = buildResponseSchemaWithConditions(template, response);
|
|
916
|
+
const parsed = schema.parse(response);
|
|
917
|
+
for (const section of template.sections) {
|
|
918
|
+
for (const field of section.fields) {
|
|
919
|
+
if (field.visibleIf) {
|
|
920
|
+
const visible = evaluateCondition(field.visibleIf, response);
|
|
921
|
+
if (!visible) {
|
|
922
|
+
delete parsed[field.id];
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return parsed;
|
|
928
|
+
}
|
|
929
|
+
function mapZodIssueToFieldErrorCode(issue) {
|
|
930
|
+
switch (issue.code) {
|
|
931
|
+
case import_zod2.z.ZodIssueCode.invalid_type:
|
|
932
|
+
return "field.invalid_type";
|
|
933
|
+
case import_zod2.z.ZodIssueCode.too_small:
|
|
934
|
+
return "field.too_small";
|
|
935
|
+
case import_zod2.z.ZodIssueCode.too_big:
|
|
936
|
+
return "field.too_big";
|
|
937
|
+
case import_zod2.z.ZodIssueCode.invalid_enum_value:
|
|
938
|
+
case import_zod2.z.ZodIssueCode.invalid_literal:
|
|
939
|
+
return "field.invalid_option";
|
|
940
|
+
case import_zod2.z.ZodIssueCode.custom:
|
|
941
|
+
return "field.custom";
|
|
942
|
+
default:
|
|
943
|
+
return "field.custom";
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
function buildFieldMetadataLookup(template) {
|
|
947
|
+
const map = /* @__PURE__ */ new Map();
|
|
948
|
+
for (const section of template.sections) {
|
|
949
|
+
for (const field of section.fields) {
|
|
950
|
+
map.set(field.id, {
|
|
951
|
+
sectionId: section.id,
|
|
952
|
+
sectionTitle: section.title,
|
|
953
|
+
label: field.label
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return map;
|
|
958
|
+
}
|
|
959
|
+
function mapZodIssuesToResponseErrors(template, issues) {
|
|
960
|
+
const fieldMeta = buildFieldMetadataLookup(template);
|
|
961
|
+
const errors = [];
|
|
962
|
+
for (const issue of issues) {
|
|
963
|
+
const path = issue.path ?? [];
|
|
964
|
+
const topLevelFieldId = path.find(
|
|
965
|
+
(p) => typeof p === "string"
|
|
966
|
+
);
|
|
967
|
+
const rowIndex = path.find(
|
|
968
|
+
(p) => typeof p === "number"
|
|
969
|
+
);
|
|
970
|
+
const nestedFieldId = path.length >= 3 && typeof path[path.length - 1] === "string" ? path[path.length - 1] : void 0;
|
|
971
|
+
const fieldId = topLevelFieldId ?? "";
|
|
972
|
+
const meta = fieldId ? fieldMeta.get(fieldId) : void 0;
|
|
973
|
+
const code = mapZodIssueToFieldErrorCode(issue);
|
|
974
|
+
const sectionLabel = meta?.sectionTitle ?? meta?.sectionId;
|
|
975
|
+
const fieldLabel = meta?.label ?? fieldId;
|
|
976
|
+
let message;
|
|
977
|
+
if (rowIndex != null && nestedFieldId && meta && sectionLabel) {
|
|
978
|
+
message = `Section "${sectionLabel}" \u2192 "${meta.label}" (row ${rowIndex + 1}, field "${nestedFieldId}"): ${issue.message}`;
|
|
979
|
+
} else if (meta && sectionLabel && fieldLabel) {
|
|
980
|
+
message = `Section "${sectionLabel}" \u2192 Field "${fieldLabel}": ${issue.message}`;
|
|
981
|
+
} else if (fieldLabel) {
|
|
982
|
+
message = `Field "${fieldLabel}": ${issue.message}`;
|
|
983
|
+
} else {
|
|
984
|
+
message = issue.message;
|
|
985
|
+
}
|
|
986
|
+
errors.push({
|
|
987
|
+
fieldId,
|
|
988
|
+
sectionId: meta?.sectionId,
|
|
989
|
+
sectionTitle: meta?.sectionTitle,
|
|
990
|
+
label: meta?.label,
|
|
991
|
+
code,
|
|
992
|
+
message,
|
|
993
|
+
rawIssue: issue
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
return errors;
|
|
997
|
+
}
|
|
998
|
+
function validateReportResponseDetailed(template, data) {
|
|
999
|
+
if (typeof data !== "object" || data === null) {
|
|
1000
|
+
return {
|
|
1001
|
+
success: false,
|
|
1002
|
+
errors: [
|
|
1003
|
+
{
|
|
1004
|
+
fieldId: "",
|
|
1005
|
+
code: "response.invalid_root",
|
|
1006
|
+
message: "Response must be an object."
|
|
1007
|
+
}
|
|
1008
|
+
]
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
const response = data;
|
|
1012
|
+
const schema = buildResponseSchemaWithConditions(template, response);
|
|
1013
|
+
const result = schema.safeParse(response);
|
|
1014
|
+
if (result.success) {
|
|
1015
|
+
const parsed = { ...result.data };
|
|
1016
|
+
for (const section of template.sections) {
|
|
1017
|
+
for (const field of section.fields) {
|
|
1018
|
+
if (field.visibleIf) {
|
|
1019
|
+
const visible = evaluateCondition(field.visibleIf, response);
|
|
1020
|
+
if (!visible) {
|
|
1021
|
+
delete parsed[field.id];
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return { success: true, value: parsed };
|
|
1027
|
+
}
|
|
1028
|
+
const errors = mapZodIssuesToResponseErrors(template, result.error.issues);
|
|
1029
|
+
return { success: false, errors };
|
|
1030
|
+
}
|
|
1031
|
+
function explainValidationError(template, error) {
|
|
1032
|
+
if (error instanceof import_zod2.ZodError) {
|
|
1033
|
+
return mapZodIssuesToResponseErrors(template, error.issues);
|
|
1034
|
+
}
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
930
1037
|
// Annotate the CommonJS export names for ESM import in node:
|
|
931
1038
|
0 && (module.exports = {
|
|
932
1039
|
CORE_FIELD_DEFAULTS,
|
|
@@ -944,6 +1051,7 @@ function exportJSONSchema(template) {
|
|
|
944
1051
|
createUniqueId,
|
|
945
1052
|
diffTemplates,
|
|
946
1053
|
evaluateCondition,
|
|
1054
|
+
explainValidationError,
|
|
947
1055
|
exportJSONSchema,
|
|
948
1056
|
migrateLegacySchema,
|
|
949
1057
|
normalizeReportTemplateSchema,
|