@frt-platform/report-core 1.1.0 → 1.2.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.js CHANGED
@@ -21,21 +21,28 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  CORE_FIELD_DEFAULTS: () => CORE_FIELD_DEFAULTS,
24
+ Condition: () => Condition,
24
25
  DEFAULT_FIELD_LABEL: () => DEFAULT_FIELD_LABEL,
26
+ FieldRegistry: () => FieldRegistry,
25
27
  REPORT_TEMPLATE_FIELD_TYPES: () => REPORT_TEMPLATE_FIELD_TYPES,
26
28
  REPORT_TEMPLATE_VERSION: () => REPORT_TEMPLATE_VERSION,
29
+ RepeatGroupFieldSchema: () => RepeatGroupFieldSchema,
27
30
  ReportTemplateFieldSchema: () => ReportTemplateFieldSchema,
28
31
  ReportTemplateSchemaValidator: () => ReportTemplateSchemaValidator,
29
32
  ReportTemplateSectionSchema: () => ReportTemplateSectionSchema,
30
- buildFieldResponseSchema: () => buildFieldResponseSchema,
31
- buildResponseSchema: () => buildResponseSchema,
33
+ buildBaseFieldSchema: () => buildBaseFieldSchema,
34
+ buildResponseSchemaWithConditions: () => buildResponseSchemaWithConditions,
32
35
  createUniqueId: () => createUniqueId,
36
+ diffTemplates: () => diffTemplates,
37
+ evaluateCondition: () => evaluateCondition,
38
+ exportJSONSchema: () => exportJSONSchema,
33
39
  migrateLegacySchema: () => migrateLegacySchema,
34
40
  normalizeReportTemplateSchema: () => normalizeReportTemplateSchema,
35
41
  parseReportTemplateSchema: () => parseReportTemplateSchema,
36
42
  parseReportTemplateSchemaFromString: () => parseReportTemplateSchemaFromString,
37
43
  serializeReportTemplateSchema: () => serializeReportTemplateSchema,
38
- validateReportResponse: () => validateReportResponse
44
+ validateReportResponse: () => validateReportResponse,
45
+ validateReportResponseDetailed: () => validateReportResponseDetailed
39
46
  });
40
47
  module.exports = __toCommonJS(index_exports);
41
48
 
@@ -49,14 +56,44 @@ var REPORT_TEMPLATE_FIELD_TYPES = [
49
56
  "date",
50
57
  "checkbox",
51
58
  "singleSelect",
52
- "multiSelect"
59
+ "multiSelect",
60
+ "repeatGroup"
61
+ // NEW FIELD TYPE
53
62
  ];
54
- var ReportTemplateFieldSchema = import_zod.z.object({
63
+ var Condition = import_zod.z.lazy(
64
+ () => import_zod.z.union([
65
+ // equals: { equals: { fieldId: value } }
66
+ import_zod.z.object({
67
+ equals: import_zod.z.record(import_zod.z.any())
68
+ }),
69
+ // any: { any: [cond, cond] }
70
+ import_zod.z.object({
71
+ any: import_zod.z.array(Condition)
72
+ }),
73
+ // all: { all: [cond, cond] }
74
+ import_zod.z.object({
75
+ all: import_zod.z.array(Condition)
76
+ }),
77
+ // not: { not: cond }
78
+ import_zod.z.object({
79
+ not: Condition
80
+ })
81
+ ])
82
+ );
83
+ var BaseFieldSchema = import_zod.z.object({
55
84
  id: import_zod.z.string().min(1, "Field identifiers cannot be empty.").max(60, "Field identifiers must be 60 characters or fewer.").regex(
56
85
  /^[a-z0-9_-]+$/,
57
86
  "Use lowercase letters, numbers, underscores, or dashes for field identifiers."
58
87
  ),
59
- type: import_zod.z.enum(REPORT_TEMPLATE_FIELD_TYPES),
88
+ type: import_zod.z.enum([
89
+ "shortText",
90
+ "longText",
91
+ "number",
92
+ "date",
93
+ "checkbox",
94
+ "singleSelect",
95
+ "multiSelect"
96
+ ]),
60
97
  label: import_zod.z.string().min(1, "Field labels cannot be empty.").max(200, "Field labels must be 200 characters or fewer."),
61
98
  required: import_zod.z.boolean().optional(),
62
99
  description: import_zod.z.string().max(400, "Descriptions must be 400 characters or fewer.").optional(),
@@ -65,16 +102,59 @@ var ReportTemplateFieldSchema = import_zod.z.object({
65
102
  import_zod.z.string().min(1, "Options cannot be empty.").max(120, "Options must be 120 characters or fewer.")
66
103
  ).optional(),
67
104
  allowOther: import_zod.z.boolean().optional(),
68
- defaultValue: import_zod.z.union([import_zod.z.string(), import_zod.z.number(), import_zod.z.boolean(), import_zod.z.array(import_zod.z.string()), import_zod.z.null()]).optional(),
105
+ defaultValue: import_zod.z.union([
106
+ import_zod.z.string(),
107
+ import_zod.z.number(),
108
+ import_zod.z.boolean(),
109
+ import_zod.z.array(import_zod.z.string()),
110
+ import_zod.z.null()
111
+ ]).optional(),
112
+ // Conditional logic
113
+ visibleIf: Condition.optional(),
114
+ requiredIf: Condition.optional(),
115
+ // Text constraints
69
116
  minLength: import_zod.z.number().int().min(0).optional(),
70
117
  maxLength: import_zod.z.number().int().min(0).optional(),
118
+ // Number constraints
71
119
  minValue: import_zod.z.number().optional(),
72
120
  maxValue: import_zod.z.number().optional(),
73
121
  step: import_zod.z.number().nonnegative().optional(),
122
+ // Multi-select constraints
74
123
  minSelections: import_zod.z.number().int().min(0).optional(),
75
124
  maxSelections: import_zod.z.number().int().min(0).optional(),
125
+ // Privacy
76
126
  dataClassification: import_zod.z.enum(["none", "personal", "special"]).optional()
77
127
  });
128
+ var RepeatGroupFieldSchema = import_zod.z.lazy(
129
+ () => import_zod.z.object({
130
+ id: import_zod.z.string().min(1).max(60).regex(
131
+ /^[a-z0-9_-]+$/,
132
+ "Use lowercase letters, numbers, underscores, or dashes for field identifiers."
133
+ ),
134
+ type: import_zod.z.literal("repeatGroup"),
135
+ label: import_zod.z.string().min(1).max(200),
136
+ // Conditional logic for the whole group
137
+ visibleIf: Condition.optional(),
138
+ requiredIf: Condition.optional(),
139
+ // NEW: Dynamic and static min/max
140
+ min: import_zod.z.number().int().min(0).optional(),
141
+ max: import_zod.z.number().int().min(1).optional(),
142
+ minIf: Condition.optional(),
143
+ maxIf: Condition.optional(),
144
+ // Contents: nested fields (recursive)
145
+ fields: import_zod.z.array(
146
+ import_zod.z.lazy(() => ReportTemplateFieldSchema)
147
+ ).min(1, "Repeat groups must contain at least one field."),
148
+ // Pre-populated rows allowed
149
+ defaultValue: import_zod.z.array(import_zod.z.record(import_zod.z.any())).optional()
150
+ })
151
+ );
152
+ var ReportTemplateFieldSchema = import_zod.z.lazy(
153
+ () => import_zod.z.union([
154
+ BaseFieldSchema,
155
+ RepeatGroupFieldSchema
156
+ ])
157
+ );
78
158
  var ReportTemplateSectionSchema = import_zod.z.object({
79
159
  id: import_zod.z.string().min(1, "Section identifiers cannot be empty.").max(60, "Section identifiers must be 60 characters or fewer.").regex(
80
160
  /^[a-z0-9_-]+$/,
@@ -121,6 +201,13 @@ var CORE_FIELD_DEFAULTS = {
121
201
  label: DEFAULT_FIELD_LABEL,
122
202
  options: ["Option 1", "Option 2", "Option 3"],
123
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
124
211
  }
125
212
  };
126
213
 
@@ -191,46 +278,77 @@ function migrateLegacySchema(raw) {
191
278
  }
192
279
 
193
280
  // src/normalize.ts
194
- function parseReportTemplateSchema(raw) {
195
- const migrated = migrateLegacySchema(raw);
196
- const parsed = ReportTemplateSchemaValidator.parse(migrated);
197
- return normalizeReportTemplateSchema(parsed);
281
+ function normalizeId(raw, fallback) {
282
+ if (!raw || typeof raw !== "string") return fallback;
283
+ const cleaned = raw.trim().toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_-]/g, "");
284
+ return cleaned.length > 0 ? cleaned : fallback;
285
+ }
286
+ function ensureUniqueId(id, used) {
287
+ let final = id;
288
+ let counter = 1;
289
+ while (used.has(final)) {
290
+ final = `${id}-${counter++}`;
291
+ }
292
+ used.add(final);
293
+ return final;
294
+ }
295
+ function normalizeField(field, usedFieldIds, index) {
296
+ const fallbackId = `field_${index + 1}`;
297
+ const normalizedId = normalizeId(field.id, fallbackId);
298
+ const uniqueId = ensureUniqueId(normalizedId, usedFieldIds);
299
+ const base = {
300
+ ...field,
301
+ id: uniqueId
302
+ };
303
+ if (field.type === "repeatGroup") {
304
+ const nestedUsed = /* @__PURE__ */ new Set();
305
+ const nestedFields = Array.isArray(field.fields) ? field.fields : [];
306
+ base.fields = nestedFields.map(
307
+ (inner, idx) => normalizeField(inner, nestedUsed, idx)
308
+ );
309
+ }
310
+ return base;
311
+ }
312
+ function normalizeSection(section, sectionIndex) {
313
+ const fallbackId = `section_${sectionIndex + 1}`;
314
+ const normalizedId = normalizeId(section.id, fallbackId);
315
+ const base = {
316
+ ...section,
317
+ id: normalizedId
318
+ };
319
+ const usedFieldIds = /* @__PURE__ */ new Set();
320
+ base.fields = (section.fields ?? []).map(
321
+ (field, idx) => normalizeField(field, usedFieldIds, idx)
322
+ );
323
+ return base;
198
324
  }
199
325
  function normalizeReportTemplateSchema(schema) {
200
326
  const sections = schema.sections.length > 0 ? schema.sections : [
201
327
  {
202
- id: "section-1",
328
+ id: "section_1",
203
329
  title: "",
204
330
  description: "",
205
331
  fields: []
206
332
  }
207
333
  ];
208
- const normalizedSections = sections.map((sec, i) => {
209
- const seen = /* @__PURE__ */ new Set();
210
- const safeSectionId = sec.id.trim() || `section-${i + 1}`;
211
- const fields = sec.fields.map((field, idx) => {
212
- const trimmed = field.id.trim() || `field-${idx + 1}`;
213
- const safeId = seen.has(trimmed) ? `field-${idx + 1}` : trimmed;
214
- seen.add(safeId);
215
- return {
216
- ...field,
217
- id: safeId
218
- };
219
- });
220
- return {
221
- ...sec,
222
- id: safeSectionId,
223
- fields
224
- };
334
+ const usedSectionIds = /* @__PURE__ */ new Set();
335
+ const normalizedSections = sections.map((section, idx) => {
336
+ const normalized = normalizeSection(section, idx);
337
+ normalized.id = ensureUniqueId(normalized.id, usedSectionIds);
338
+ return normalized;
225
339
  });
226
340
  return {
227
341
  ...schema,
228
342
  sections: normalizedSections
229
343
  };
230
344
  }
345
+ function parseReportTemplateSchema(raw) {
346
+ const migrated = migrateLegacySchema(raw);
347
+ const parsed = ReportTemplateSchemaValidator.parse(migrated);
348
+ return normalizeReportTemplateSchema(parsed);
349
+ }
231
350
  function parseReportTemplateSchemaFromString(raw) {
232
- const obj = JSON.parse(raw);
233
- return parseReportTemplateSchema(obj);
351
+ return parseReportTemplateSchema(JSON.parse(raw));
234
352
  }
235
353
  function serializeReportTemplateSchema(schema) {
236
354
  return JSON.stringify(schema, null, 2);
@@ -238,8 +356,78 @@ function serializeReportTemplateSchema(schema) {
238
356
 
239
357
  // src/responses.ts
240
358
  var import_zod2 = require("zod");
241
- function buildFieldResponseSchema(field) {
359
+
360
+ // src/fieldRegistry.ts
361
+ var FieldRegistryClass = class {
362
+ constructor() {
363
+ this.registry = /* @__PURE__ */ new Map();
364
+ }
365
+ /** Register or override a field type. */
366
+ register(type, entry) {
367
+ this.registry.set(type, entry);
368
+ }
369
+ /** Get registry entry for a field type. */
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;
405
+ });
406
+ }
407
+ if ("any" in condition) {
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;
422
+ }
423
+
424
+ // src/responses.ts
425
+ function buildBaseFieldSchema(field) {
242
426
  const isRequired = Boolean(field.required);
427
+ const registry = FieldRegistry.get(field.type);
428
+ if (registry?.buildResponseSchema) {
429
+ return registry.buildResponseSchema(field);
430
+ }
243
431
  switch (field.type) {
244
432
  case "shortText":
245
433
  case "longText": {
@@ -267,9 +455,7 @@ function buildFieldResponseSchema(field) {
267
455
  return isRequired ? schema : schema.optional();
268
456
  }
269
457
  case "checkbox": {
270
- if (isRequired) {
271
- return import_zod2.z.literal(true);
272
- }
458
+ if (isRequired) return import_zod2.z.literal(true);
273
459
  return import_zod2.z.boolean().optional();
274
460
  }
275
461
  case "singleSelect": {
@@ -312,40 +498,458 @@ function buildFieldResponseSchema(field) {
312
498
  return isRequired ? schema : schema.optional();
313
499
  }
314
500
  default: {
315
- const _exhaustive = field.type;
316
501
  return import_zod2.z.any();
317
502
  }
318
503
  }
319
504
  }
320
- function buildResponseSchema(template) {
505
+ function buildConditionalFieldSchema(field, response) {
506
+ if (field.visibleIf) {
507
+ const visible = evaluateCondition(field.visibleIf, response);
508
+ if (!visible) {
509
+ return import_zod2.z.any().optional();
510
+ }
511
+ }
512
+ let schema = buildBaseFieldSchema(field);
513
+ if (field.requiredIf) {
514
+ const shouldBeRequired = evaluateCondition(field.requiredIf, response);
515
+ if (shouldBeRequired) {
516
+ if (schema instanceof import_zod2.z.ZodOptional) {
517
+ schema = schema.unwrap();
518
+ }
519
+ }
520
+ }
521
+ return schema;
522
+ }
523
+ function buildResponseSchemaWithConditions(template, response) {
321
524
  const shape = {};
322
525
  for (const section of template.sections) {
323
526
  for (const field of section.fields) {
324
- shape[field.id] = buildFieldResponseSchema(field);
527
+ shape[field.id] = buildConditionalFieldSchema(field, response);
325
528
  }
326
529
  }
327
530
  return import_zod2.z.object(shape);
328
531
  }
329
532
  function validateReportResponse(template, data) {
330
- const schema = buildResponseSchema(template);
331
- return schema.parse(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
+ });
696
+ }
697
+ }
698
+ }
699
+ for (const f of afterFields) {
700
+ const idx = beforeIndex[f.id];
701
+ if (idx == null) continue;
702
+ const before = beforeFields[idx];
703
+ const changes = diffObjectProperties(before, f, ["id", "fields"]);
704
+ if (changes.length > 0) {
705
+ empty.modifiedFields.push({
706
+ sectionId,
707
+ fieldId: f.id,
708
+ before,
709
+ after: f,
710
+ changes
711
+ });
712
+ }
713
+ if (f.type === "repeatGroup" && Array.isArray(f.fields)) {
714
+ const innerBefore = before.fields ?? [];
715
+ const innerAfter = f.fields;
716
+ const nested = diffRepeatGroupFields(
717
+ innerBefore,
718
+ innerAfter,
719
+ sectionId,
720
+ f.id
721
+ );
722
+ if (nested.addedFields.length > 0 || nested.removedFields.length > 0 || nested.reorderedFields.length > 0 || nested.modifiedFields.length > 0) {
723
+ empty.nestedFieldDiffs.push({
724
+ sectionId,
725
+ groupId: f.id,
726
+ diffs: nested
727
+ });
728
+ }
729
+ }
730
+ }
731
+ return empty;
732
+ }
733
+ function diffTemplates(before, after) {
734
+ const diff = {
735
+ addedSections: [],
736
+ removedSections: [],
737
+ reorderedSections: [],
738
+ modifiedSections: [],
739
+ addedFields: [],
740
+ removedFields: [],
741
+ reorderedFields: [],
742
+ modifiedFields: [],
743
+ nestedFieldDiffs: []
744
+ };
745
+ const beforeSections = before.sections;
746
+ const afterSections = after.sections;
747
+ const beforeSecIndex = indexById(beforeSections);
748
+ const afterSecIndex = indexById(afterSections);
749
+ for (const sec of afterSections) {
750
+ if (!(sec.id in beforeSecIndex)) {
751
+ diff.addedSections.push({
752
+ sectionId: sec.id,
753
+ index: afterSecIndex[sec.id]
754
+ });
755
+ }
756
+ }
757
+ for (const sec of beforeSections) {
758
+ if (!(sec.id in afterSecIndex)) {
759
+ diff.removedSections.push({
760
+ sectionId: sec.id,
761
+ index: beforeSecIndex[sec.id]
762
+ });
763
+ }
764
+ }
765
+ for (const sec of afterSections) {
766
+ if (sec.id in beforeSecIndex) {
767
+ const from = beforeSecIndex[sec.id];
768
+ const to = afterSecIndex[sec.id];
769
+ if (from !== to) {
770
+ diff.reorderedSections.push({ sectionId: sec.id, from, to });
771
+ }
772
+ }
773
+ }
774
+ for (const sec of afterSections) {
775
+ const idx = beforeSecIndex[sec.id];
776
+ if (idx == null) continue;
777
+ const beforeSec = beforeSections[idx];
778
+ const changes = diffObjectProperties(beforeSec, sec, ["id", "fields"]);
779
+ if (changes.length > 0) {
780
+ diff.modifiedSections.push({
781
+ sectionId: sec.id,
782
+ changes
783
+ });
784
+ }
785
+ }
786
+ for (const afterSec of afterSections) {
787
+ const secId = afterSec.id;
788
+ const beforeIdx = beforeSecIndex[secId];
789
+ if (beforeIdx == null) continue;
790
+ const beforeSec = beforeSections[beforeIdx];
791
+ const beforeFields = beforeSec.fields ?? [];
792
+ const afterFields = afterSec.fields ?? [];
793
+ const nestedDiff = diffRepeatGroupFields(
794
+ beforeFields,
795
+ afterFields,
796
+ secId,
797
+ ""
798
+ );
799
+ diff.addedFields.push(...nestedDiff.addedFields);
800
+ diff.removedFields.push(...nestedDiff.removedFields);
801
+ diff.reorderedFields.push(...nestedDiff.reorderedFields);
802
+ diff.modifiedFields.push(...nestedDiff.modifiedFields);
803
+ diff.nestedFieldDiffs.push(...nestedDiff.nestedFieldDiffs);
804
+ }
805
+ return diff;
806
+ }
807
+
808
+ // src/jsonSchema.ts
809
+ function mapFieldTypeToJSONSchemaCore(field) {
810
+ const common = {
811
+ title: field.label,
812
+ description: field.description,
813
+ default: field.defaultValue ?? void 0,
814
+ "x-frt-placeholder": field.placeholder,
815
+ "x-frt-dataClassification": field.dataClassification,
816
+ "x-frt-visibleIf": field.visibleIf,
817
+ "x-frt-requiredIf": field.requiredIf
818
+ };
819
+ switch (field.type) {
820
+ case "shortText":
821
+ case "longText": {
822
+ const schema = {
823
+ ...common,
824
+ type: "string"
825
+ };
826
+ if (typeof field.minLength === "number") {
827
+ schema.minLength = field.minLength;
828
+ }
829
+ if (typeof field.maxLength === "number") {
830
+ schema.maxLength = field.maxLength;
831
+ }
832
+ return schema;
833
+ }
834
+ case "number": {
835
+ const schema = {
836
+ ...common,
837
+ type: "number"
838
+ };
839
+ if (typeof field.minValue === "number") {
840
+ schema.minimum = field.minValue;
841
+ }
842
+ if (typeof field.maxValue === "number") {
843
+ schema.maximum = field.maxValue;
844
+ }
845
+ return schema;
846
+ }
847
+ case "date": {
848
+ const schema = {
849
+ ...common,
850
+ type: "string",
851
+ format: "date-time"
852
+ };
853
+ return schema;
854
+ }
855
+ case "checkbox": {
856
+ const schema = {
857
+ ...common,
858
+ type: "boolean"
859
+ };
860
+ if (field.required && !field.requiredIf) {
861
+ schema.const = true;
862
+ }
863
+ return schema;
864
+ }
865
+ case "singleSelect": {
866
+ const schema = {
867
+ ...common,
868
+ type: "string"
869
+ };
870
+ if (Array.isArray(field.options) && field.options.length > 0) {
871
+ schema.enum = field.options;
872
+ }
873
+ return schema;
874
+ }
875
+ case "multiSelect": {
876
+ const schema = {
877
+ ...common,
878
+ type: "array",
879
+ items: {
880
+ type: "string"
881
+ }
882
+ };
883
+ const hasOptions = Array.isArray(field.options) && field.options.length > 0;
884
+ if (hasOptions && !field.allowOther) {
885
+ schema.items = {
886
+ type: "string",
887
+ enum: field.options
888
+ };
889
+ }
890
+ if (typeof field.minSelections === "number") {
891
+ schema.minItems = field.minSelections;
892
+ }
893
+ if (typeof field.maxSelections === "number") {
894
+ schema.maxItems = field.maxSelections;
895
+ }
896
+ return schema;
897
+ }
898
+ default: {
899
+ return {
900
+ ...common
901
+ };
902
+ }
903
+ }
904
+ }
905
+ function exportJSONSchema(template) {
906
+ const properties = {};
907
+ const required = [];
908
+ for (const section of template.sections) {
909
+ for (const field of section.fields) {
910
+ properties[field.id] = mapFieldTypeToJSONSchemaCore(field);
911
+ if (field.required === true && !field.requiredIf && !field.visibleIf) {
912
+ required.push(field.id);
913
+ }
914
+ }
915
+ }
916
+ const schema = {
917
+ $schema: "https://json-schema.org/draft/2020-12/schema",
918
+ type: "object",
919
+ properties,
920
+ additionalProperties: false,
921
+ "x-frt-templateTitle": template.title,
922
+ "x-frt-templateDescription": template.description,
923
+ "x-frt-version": template.version
924
+ };
925
+ if (required.length > 0) {
926
+ schema.required = required;
927
+ }
928
+ return schema;
332
929
  }
333
930
  // Annotate the CommonJS export names for ESM import in node:
334
931
  0 && (module.exports = {
335
932
  CORE_FIELD_DEFAULTS,
933
+ Condition,
336
934
  DEFAULT_FIELD_LABEL,
935
+ FieldRegistry,
337
936
  REPORT_TEMPLATE_FIELD_TYPES,
338
937
  REPORT_TEMPLATE_VERSION,
938
+ RepeatGroupFieldSchema,
339
939
  ReportTemplateFieldSchema,
340
940
  ReportTemplateSchemaValidator,
341
941
  ReportTemplateSectionSchema,
342
- buildFieldResponseSchema,
343
- buildResponseSchema,
942
+ buildBaseFieldSchema,
943
+ buildResponseSchemaWithConditions,
344
944
  createUniqueId,
945
+ diffTemplates,
946
+ evaluateCondition,
947
+ exportJSONSchema,
345
948
  migrateLegacySchema,
346
949
  normalizeReportTemplateSchema,
347
950
  parseReportTemplateSchema,
348
951
  parseReportTemplateSchemaFromString,
349
952
  serializeReportTemplateSchema,
350
- validateReportResponse
953
+ validateReportResponse,
954
+ validateReportResponseDetailed
351
955
  });