@frt-platform/report-core 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -8,14 +8,44 @@ var REPORT_TEMPLATE_FIELD_TYPES = [
8
8
  "date",
9
9
  "checkbox",
10
10
  "singleSelect",
11
- "multiSelect"
11
+ "multiSelect",
12
+ "repeatGroup"
13
+ // NEW FIELD TYPE
12
14
  ];
13
- var ReportTemplateFieldSchema = z.object({
15
+ var Condition = z.lazy(
16
+ () => z.union([
17
+ // equals: { equals: { fieldId: value } }
18
+ z.object({
19
+ equals: z.record(z.any())
20
+ }),
21
+ // any: { any: [cond, cond] }
22
+ z.object({
23
+ any: z.array(Condition)
24
+ }),
25
+ // all: { all: [cond, cond] }
26
+ z.object({
27
+ all: z.array(Condition)
28
+ }),
29
+ // not: { not: cond }
30
+ z.object({
31
+ not: Condition
32
+ })
33
+ ])
34
+ );
35
+ var BaseFieldSchema = z.object({
14
36
  id: z.string().min(1, "Field identifiers cannot be empty.").max(60, "Field identifiers must be 60 characters or fewer.").regex(
15
37
  /^[a-z0-9_-]+$/,
16
38
  "Use lowercase letters, numbers, underscores, or dashes for field identifiers."
17
39
  ),
18
- type: z.enum(REPORT_TEMPLATE_FIELD_TYPES),
40
+ type: z.enum([
41
+ "shortText",
42
+ "longText",
43
+ "number",
44
+ "date",
45
+ "checkbox",
46
+ "singleSelect",
47
+ "multiSelect"
48
+ ]),
19
49
  label: z.string().min(1, "Field labels cannot be empty.").max(200, "Field labels must be 200 characters or fewer."),
20
50
  required: z.boolean().optional(),
21
51
  description: z.string().max(400, "Descriptions must be 400 characters or fewer.").optional(),
@@ -24,16 +54,59 @@ var ReportTemplateFieldSchema = z.object({
24
54
  z.string().min(1, "Options cannot be empty.").max(120, "Options must be 120 characters or fewer.")
25
55
  ).optional(),
26
56
  allowOther: z.boolean().optional(),
27
- defaultValue: z.union([z.string(), z.number(), z.boolean(), z.array(z.string()), z.null()]).optional(),
57
+ defaultValue: z.union([
58
+ z.string(),
59
+ z.number(),
60
+ z.boolean(),
61
+ z.array(z.string()),
62
+ z.null()
63
+ ]).optional(),
64
+ // Conditional logic
65
+ visibleIf: Condition.optional(),
66
+ requiredIf: Condition.optional(),
67
+ // Text constraints
28
68
  minLength: z.number().int().min(0).optional(),
29
69
  maxLength: z.number().int().min(0).optional(),
70
+ // Number constraints
30
71
  minValue: z.number().optional(),
31
72
  maxValue: z.number().optional(),
32
73
  step: z.number().nonnegative().optional(),
74
+ // Multi-select constraints
33
75
  minSelections: z.number().int().min(0).optional(),
34
76
  maxSelections: z.number().int().min(0).optional(),
77
+ // Privacy
35
78
  dataClassification: z.enum(["none", "personal", "special"]).optional()
36
79
  });
80
+ var RepeatGroupFieldSchema = z.lazy(
81
+ () => z.object({
82
+ id: z.string().min(1).max(60).regex(
83
+ /^[a-z0-9_-]+$/,
84
+ "Use lowercase letters, numbers, underscores, or dashes for field identifiers."
85
+ ),
86
+ type: z.literal("repeatGroup"),
87
+ label: z.string().min(1).max(200),
88
+ // Conditional logic for the whole group
89
+ visibleIf: Condition.optional(),
90
+ requiredIf: Condition.optional(),
91
+ // NEW: Dynamic and static min/max
92
+ min: z.number().int().min(0).optional(),
93
+ max: z.number().int().min(1).optional(),
94
+ minIf: Condition.optional(),
95
+ maxIf: Condition.optional(),
96
+ // Contents: nested fields (recursive)
97
+ fields: z.array(
98
+ z.lazy(() => ReportTemplateFieldSchema)
99
+ ).min(1, "Repeat groups must contain at least one field."),
100
+ // Pre-populated rows allowed
101
+ defaultValue: z.array(z.record(z.any())).optional()
102
+ })
103
+ );
104
+ var ReportTemplateFieldSchema = z.lazy(
105
+ () => z.union([
106
+ BaseFieldSchema,
107
+ RepeatGroupFieldSchema
108
+ ])
109
+ );
37
110
  var ReportTemplateSectionSchema = z.object({
38
111
  id: z.string().min(1, "Section identifiers cannot be empty.").max(60, "Section identifiers must be 60 characters or fewer.").regex(
39
112
  /^[a-z0-9_-]+$/,
@@ -80,6 +153,13 @@ var CORE_FIELD_DEFAULTS = {
80
153
  label: DEFAULT_FIELD_LABEL,
81
154
  options: ["Option 1", "Option 2", "Option 3"],
82
155
  allowOther: false
156
+ },
157
+ repeatGroup: {
158
+ // Minimal safe defaults – your builder UI is expected
159
+ // to configure nested fields + min/max as needed.
160
+ label: DEFAULT_FIELD_LABEL,
161
+ fields: []
162
+ // nested fields go here in the UI layer
83
163
  }
84
164
  };
85
165
 
@@ -150,46 +230,77 @@ function migrateLegacySchema(raw) {
150
230
  }
151
231
 
152
232
  // src/normalize.ts
153
- function parseReportTemplateSchema(raw) {
154
- const migrated = migrateLegacySchema(raw);
155
- const parsed = ReportTemplateSchemaValidator.parse(migrated);
156
- return normalizeReportTemplateSchema(parsed);
233
+ function normalizeId(raw, fallback) {
234
+ if (!raw || typeof raw !== "string") return fallback;
235
+ const cleaned = raw.trim().toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_-]/g, "");
236
+ return cleaned.length > 0 ? cleaned : fallback;
237
+ }
238
+ function ensureUniqueId(id, used) {
239
+ let final = id;
240
+ let counter = 1;
241
+ while (used.has(final)) {
242
+ final = `${id}-${counter++}`;
243
+ }
244
+ used.add(final);
245
+ return final;
246
+ }
247
+ function normalizeField(field, usedFieldIds, index) {
248
+ const fallbackId = `field_${index + 1}`;
249
+ const normalizedId = normalizeId(field.id, fallbackId);
250
+ const uniqueId = ensureUniqueId(normalizedId, usedFieldIds);
251
+ const base = {
252
+ ...field,
253
+ id: uniqueId
254
+ };
255
+ if (field.type === "repeatGroup") {
256
+ const nestedUsed = /* @__PURE__ */ new Set();
257
+ const nestedFields = Array.isArray(field.fields) ? field.fields : [];
258
+ base.fields = nestedFields.map(
259
+ (inner, idx) => normalizeField(inner, nestedUsed, idx)
260
+ );
261
+ }
262
+ return base;
263
+ }
264
+ function normalizeSection(section, sectionIndex) {
265
+ const fallbackId = `section_${sectionIndex + 1}`;
266
+ const normalizedId = normalizeId(section.id, fallbackId);
267
+ const base = {
268
+ ...section,
269
+ id: normalizedId
270
+ };
271
+ const usedFieldIds = /* @__PURE__ */ new Set();
272
+ base.fields = (section.fields ?? []).map(
273
+ (field, idx) => normalizeField(field, usedFieldIds, idx)
274
+ );
275
+ return base;
157
276
  }
158
277
  function normalizeReportTemplateSchema(schema) {
159
278
  const sections = schema.sections.length > 0 ? schema.sections : [
160
279
  {
161
- id: "section-1",
280
+ id: "section_1",
162
281
  title: "",
163
282
  description: "",
164
283
  fields: []
165
284
  }
166
285
  ];
167
- const normalizedSections = sections.map((sec, i) => {
168
- const seen = /* @__PURE__ */ new Set();
169
- const safeSectionId = sec.id.trim() || `section-${i + 1}`;
170
- const fields = sec.fields.map((field, idx) => {
171
- const trimmed = field.id.trim() || `field-${idx + 1}`;
172
- const safeId = seen.has(trimmed) ? `field-${idx + 1}` : trimmed;
173
- seen.add(safeId);
174
- return {
175
- ...field,
176
- id: safeId
177
- };
178
- });
179
- return {
180
- ...sec,
181
- id: safeSectionId,
182
- fields
183
- };
286
+ const usedSectionIds = /* @__PURE__ */ new Set();
287
+ const normalizedSections = sections.map((section, idx) => {
288
+ const normalized = normalizeSection(section, idx);
289
+ normalized.id = ensureUniqueId(normalized.id, usedSectionIds);
290
+ return normalized;
184
291
  });
185
292
  return {
186
293
  ...schema,
187
294
  sections: normalizedSections
188
295
  };
189
296
  }
297
+ function parseReportTemplateSchema(raw) {
298
+ const migrated = migrateLegacySchema(raw);
299
+ const parsed = ReportTemplateSchemaValidator.parse(migrated);
300
+ return normalizeReportTemplateSchema(parsed);
301
+ }
190
302
  function parseReportTemplateSchemaFromString(raw) {
191
- const obj = JSON.parse(raw);
192
- return parseReportTemplateSchema(obj);
303
+ return parseReportTemplateSchema(JSON.parse(raw));
193
304
  }
194
305
  function serializeReportTemplateSchema(schema) {
195
306
  return JSON.stringify(schema, null, 2);
@@ -197,8 +308,78 @@ function serializeReportTemplateSchema(schema) {
197
308
 
198
309
  // src/responses.ts
199
310
  import { z as z2 } from "zod";
200
- function buildFieldResponseSchema(field) {
311
+
312
+ // src/fieldRegistry.ts
313
+ var FieldRegistryClass = class {
314
+ constructor() {
315
+ this.registry = /* @__PURE__ */ new Map();
316
+ }
317
+ /** Register or override a field type. */
318
+ register(type, entry) {
319
+ this.registry.set(type, entry);
320
+ }
321
+ /** Get registry entry for a field type. */
322
+ get(type) {
323
+ return this.registry.get(type);
324
+ }
325
+ /** Check if field type is registered. */
326
+ has(type) {
327
+ return this.registry.has(type);
328
+ }
329
+ /** Return all field types currently registered. */
330
+ list() {
331
+ return [...this.registry.keys()];
332
+ }
333
+ };
334
+ var FieldRegistry = new FieldRegistryClass();
335
+ var CORE_TYPES = [
336
+ "shortText",
337
+ "longText",
338
+ "number",
339
+ "date",
340
+ "checkbox",
341
+ "singleSelect",
342
+ "multiSelect"
343
+ ];
344
+ for (const type of CORE_TYPES) {
345
+ FieldRegistry.register(type, {
346
+ defaults: CORE_FIELD_DEFAULTS[type]
347
+ // Core types rely on existing validation logic.
348
+ // buildResponseSchema is optional — fallback handles it.
349
+ });
350
+ }
351
+
352
+ // src/conditions.ts
353
+ function evaluateCondition(condition, response) {
354
+ if ("equals" in condition) {
355
+ return Object.entries(condition.equals).every(([key, val]) => {
356
+ return response[key] === val;
357
+ });
358
+ }
359
+ if ("any" in condition) {
360
+ return condition.any.some(
361
+ (sub) => evaluateCondition(sub, response)
362
+ );
363
+ }
364
+ if ("all" in condition) {
365
+ return condition.all.every(
366
+ (sub) => evaluateCondition(sub, response)
367
+ );
368
+ }
369
+ if ("not" in condition) {
370
+ const sub = condition.not;
371
+ return !evaluateCondition(sub, response);
372
+ }
373
+ return false;
374
+ }
375
+
376
+ // src/responses.ts
377
+ function buildBaseFieldSchema(field) {
201
378
  const isRequired = Boolean(field.required);
379
+ const registry = FieldRegistry.get(field.type);
380
+ if (registry?.buildResponseSchema) {
381
+ return registry.buildResponseSchema(field);
382
+ }
202
383
  switch (field.type) {
203
384
  case "shortText":
204
385
  case "longText": {
@@ -226,9 +407,7 @@ function buildFieldResponseSchema(field) {
226
407
  return isRequired ? schema : schema.optional();
227
408
  }
228
409
  case "checkbox": {
229
- if (isRequired) {
230
- return z2.literal(true);
231
- }
410
+ if (isRequired) return z2.literal(true);
232
411
  return z2.boolean().optional();
233
412
  }
234
413
  case "singleSelect": {
@@ -271,39 +450,457 @@ function buildFieldResponseSchema(field) {
271
450
  return isRequired ? schema : schema.optional();
272
451
  }
273
452
  default: {
274
- const _exhaustive = field.type;
275
453
  return z2.any();
276
454
  }
277
455
  }
278
456
  }
279
- function buildResponseSchema(template) {
457
+ function buildConditionalFieldSchema(field, response) {
458
+ if (field.visibleIf) {
459
+ const visible = evaluateCondition(field.visibleIf, response);
460
+ if (!visible) {
461
+ return z2.any().optional();
462
+ }
463
+ }
464
+ let schema = buildBaseFieldSchema(field);
465
+ if (field.requiredIf) {
466
+ const shouldBeRequired = evaluateCondition(field.requiredIf, response);
467
+ if (shouldBeRequired) {
468
+ if (schema instanceof z2.ZodOptional) {
469
+ schema = schema.unwrap();
470
+ }
471
+ }
472
+ }
473
+ return schema;
474
+ }
475
+ function buildResponseSchemaWithConditions(template, response) {
280
476
  const shape = {};
281
477
  for (const section of template.sections) {
282
478
  for (const field of section.fields) {
283
- shape[field.id] = buildFieldResponseSchema(field);
479
+ shape[field.id] = buildConditionalFieldSchema(field, response);
284
480
  }
285
481
  }
286
482
  return z2.object(shape);
287
483
  }
288
484
  function validateReportResponse(template, data) {
289
- const schema = buildResponseSchema(template);
290
- return schema.parse(data);
485
+ if (typeof data !== "object" || data === null) {
486
+ throw new Error("Response must be an object");
487
+ }
488
+ const response = data;
489
+ const schema = buildResponseSchemaWithConditions(template, response);
490
+ const parsed = schema.parse(response);
491
+ for (const section of template.sections) {
492
+ for (const field of section.fields) {
493
+ if (field.visibleIf) {
494
+ const visible = evaluateCondition(field.visibleIf, response);
495
+ if (!visible) {
496
+ delete parsed[field.id];
497
+ }
498
+ }
499
+ }
500
+ }
501
+ return parsed;
502
+ }
503
+ function mapZodIssueToFieldErrorCode(issue) {
504
+ switch (issue.code) {
505
+ case z2.ZodIssueCode.invalid_type:
506
+ return "field.invalid_type";
507
+ case z2.ZodIssueCode.too_small:
508
+ return "field.too_small";
509
+ case z2.ZodIssueCode.too_big:
510
+ return "field.too_big";
511
+ case z2.ZodIssueCode.invalid_enum_value:
512
+ case z2.ZodIssueCode.invalid_literal:
513
+ return "field.invalid_option";
514
+ case z2.ZodIssueCode.custom:
515
+ return "field.custom";
516
+ default:
517
+ return "field.custom";
518
+ }
519
+ }
520
+ function buildFieldMetadataLookup(template) {
521
+ const map = /* @__PURE__ */ new Map();
522
+ for (const section of template.sections) {
523
+ for (const field of section.fields) {
524
+ map.set(field.id, {
525
+ sectionId: section.id,
526
+ sectionTitle: section.title,
527
+ label: field.label
528
+ });
529
+ }
530
+ }
531
+ return map;
532
+ }
533
+ function validateReportResponseDetailed(template, data) {
534
+ if (typeof data !== "object" || data === null) {
535
+ return {
536
+ success: false,
537
+ errors: [
538
+ {
539
+ fieldId: "",
540
+ code: "response.invalid_root",
541
+ message: "Response must be an object."
542
+ }
543
+ ]
544
+ };
545
+ }
546
+ const response = data;
547
+ const schema = buildResponseSchemaWithConditions(template, response);
548
+ const fieldMeta = buildFieldMetadataLookup(template);
549
+ const result = schema.safeParse(response);
550
+ if (result.success) {
551
+ const parsed = { ...result.data };
552
+ for (const section of template.sections) {
553
+ for (const field of section.fields) {
554
+ if (field.visibleIf) {
555
+ const visible = evaluateCondition(field.visibleIf, response);
556
+ if (!visible) {
557
+ delete parsed[field.id];
558
+ }
559
+ }
560
+ }
561
+ }
562
+ return { success: true, value: parsed };
563
+ }
564
+ const errors = [];
565
+ for (const issue of result.error.issues) {
566
+ const path = issue.path ?? [];
567
+ const fieldId = typeof path[0] === "string" ? path[0] : "";
568
+ const meta = fieldId ? fieldMeta.get(fieldId) : void 0;
569
+ const code = mapZodIssueToFieldErrorCode(issue);
570
+ const sectionLabel = meta?.sectionTitle ?? meta?.sectionId;
571
+ const fieldLabel = meta?.label ?? fieldId;
572
+ let message;
573
+ if (meta && sectionLabel && fieldLabel) {
574
+ message = `Section "${sectionLabel}" \u2192 Field "${fieldLabel}": ${issue.message}`;
575
+ } else if (fieldLabel) {
576
+ message = `Field "${fieldLabel}": ${issue.message}`;
577
+ } else {
578
+ message = issue.message;
579
+ }
580
+ errors.push({
581
+ fieldId,
582
+ sectionId: meta?.sectionId,
583
+ sectionTitle: meta?.sectionTitle,
584
+ label: meta?.label,
585
+ code,
586
+ message,
587
+ rawIssue: issue
588
+ });
589
+ }
590
+ return { success: false, errors };
591
+ }
592
+
593
+ // src/diff.ts
594
+ function indexById(items) {
595
+ const map = {};
596
+ items.forEach((item, i) => map[item.id] = i);
597
+ return map;
598
+ }
599
+ function diffObjectProperties(before, after, ignoreKeys = []) {
600
+ const changes = [];
601
+ const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
602
+ for (const key of keys) {
603
+ if (ignoreKeys.includes(key)) continue;
604
+ const b = before[key];
605
+ const a = after[key];
606
+ const changed = Array.isArray(b) && Array.isArray(a) ? JSON.stringify(b) !== JSON.stringify(a) : b !== a;
607
+ if (changed) {
608
+ changes.push({ key, before: b, after: a });
609
+ }
610
+ }
611
+ return changes;
612
+ }
613
+ function diffRepeatGroupFields(beforeFields, afterFields, sectionId, groupId) {
614
+ const empty = {
615
+ addedSections: [],
616
+ removedSections: [],
617
+ reorderedSections: [],
618
+ modifiedSections: [],
619
+ addedFields: [],
620
+ removedFields: [],
621
+ reorderedFields: [],
622
+ modifiedFields: [],
623
+ nestedFieldDiffs: []
624
+ };
625
+ const beforeIndex = indexById(beforeFields);
626
+ const afterIndex = indexById(afterFields);
627
+ for (const f of afterFields) {
628
+ if (!(f.id in beforeIndex)) {
629
+ empty.addedFields.push({ sectionId, fieldId: f.id, index: afterIndex[f.id] });
630
+ }
631
+ }
632
+ for (const f of beforeFields) {
633
+ if (!(f.id in afterIndex)) {
634
+ empty.removedFields.push({ sectionId, fieldId: f.id, index: beforeIndex[f.id] });
635
+ }
636
+ }
637
+ for (const f of afterFields) {
638
+ if (f.id in beforeIndex) {
639
+ const from = beforeIndex[f.id];
640
+ const to = afterIndex[f.id];
641
+ if (from !== to) {
642
+ empty.reorderedFields.push({
643
+ sectionId,
644
+ fieldId: f.id,
645
+ from,
646
+ to
647
+ });
648
+ }
649
+ }
650
+ }
651
+ for (const f of afterFields) {
652
+ const idx = beforeIndex[f.id];
653
+ if (idx == null) continue;
654
+ const before = beforeFields[idx];
655
+ const changes = diffObjectProperties(before, f, ["id", "fields"]);
656
+ if (changes.length > 0) {
657
+ empty.modifiedFields.push({
658
+ sectionId,
659
+ fieldId: f.id,
660
+ before,
661
+ after: f,
662
+ changes
663
+ });
664
+ }
665
+ if (f.type === "repeatGroup" && Array.isArray(f.fields)) {
666
+ const innerBefore = before.fields ?? [];
667
+ const innerAfter = f.fields;
668
+ const nested = diffRepeatGroupFields(
669
+ innerBefore,
670
+ innerAfter,
671
+ sectionId,
672
+ f.id
673
+ );
674
+ if (nested.addedFields.length > 0 || nested.removedFields.length > 0 || nested.reorderedFields.length > 0 || nested.modifiedFields.length > 0) {
675
+ empty.nestedFieldDiffs.push({
676
+ sectionId,
677
+ groupId: f.id,
678
+ diffs: nested
679
+ });
680
+ }
681
+ }
682
+ }
683
+ return empty;
684
+ }
685
+ function diffTemplates(before, after) {
686
+ const diff = {
687
+ addedSections: [],
688
+ removedSections: [],
689
+ reorderedSections: [],
690
+ modifiedSections: [],
691
+ addedFields: [],
692
+ removedFields: [],
693
+ reorderedFields: [],
694
+ modifiedFields: [],
695
+ nestedFieldDiffs: []
696
+ };
697
+ const beforeSections = before.sections;
698
+ const afterSections = after.sections;
699
+ const beforeSecIndex = indexById(beforeSections);
700
+ const afterSecIndex = indexById(afterSections);
701
+ for (const sec of afterSections) {
702
+ if (!(sec.id in beforeSecIndex)) {
703
+ diff.addedSections.push({
704
+ sectionId: sec.id,
705
+ index: afterSecIndex[sec.id]
706
+ });
707
+ }
708
+ }
709
+ for (const sec of beforeSections) {
710
+ if (!(sec.id in afterSecIndex)) {
711
+ diff.removedSections.push({
712
+ sectionId: sec.id,
713
+ index: beforeSecIndex[sec.id]
714
+ });
715
+ }
716
+ }
717
+ for (const sec of afterSections) {
718
+ if (sec.id in beforeSecIndex) {
719
+ const from = beforeSecIndex[sec.id];
720
+ const to = afterSecIndex[sec.id];
721
+ if (from !== to) {
722
+ diff.reorderedSections.push({ sectionId: sec.id, from, to });
723
+ }
724
+ }
725
+ }
726
+ for (const sec of afterSections) {
727
+ const idx = beforeSecIndex[sec.id];
728
+ if (idx == null) continue;
729
+ const beforeSec = beforeSections[idx];
730
+ const changes = diffObjectProperties(beforeSec, sec, ["id", "fields"]);
731
+ if (changes.length > 0) {
732
+ diff.modifiedSections.push({
733
+ sectionId: sec.id,
734
+ changes
735
+ });
736
+ }
737
+ }
738
+ for (const afterSec of afterSections) {
739
+ const secId = afterSec.id;
740
+ const beforeIdx = beforeSecIndex[secId];
741
+ if (beforeIdx == null) continue;
742
+ const beforeSec = beforeSections[beforeIdx];
743
+ const beforeFields = beforeSec.fields ?? [];
744
+ const afterFields = afterSec.fields ?? [];
745
+ const nestedDiff = diffRepeatGroupFields(
746
+ beforeFields,
747
+ afterFields,
748
+ secId,
749
+ ""
750
+ );
751
+ diff.addedFields.push(...nestedDiff.addedFields);
752
+ diff.removedFields.push(...nestedDiff.removedFields);
753
+ diff.reorderedFields.push(...nestedDiff.reorderedFields);
754
+ diff.modifiedFields.push(...nestedDiff.modifiedFields);
755
+ diff.nestedFieldDiffs.push(...nestedDiff.nestedFieldDiffs);
756
+ }
757
+ return diff;
758
+ }
759
+
760
+ // src/jsonSchema.ts
761
+ function mapFieldTypeToJSONSchemaCore(field) {
762
+ const common = {
763
+ title: field.label,
764
+ description: field.description,
765
+ default: field.defaultValue ?? void 0,
766
+ "x-frt-placeholder": field.placeholder,
767
+ "x-frt-dataClassification": field.dataClassification,
768
+ "x-frt-visibleIf": field.visibleIf,
769
+ "x-frt-requiredIf": field.requiredIf
770
+ };
771
+ switch (field.type) {
772
+ case "shortText":
773
+ case "longText": {
774
+ const schema = {
775
+ ...common,
776
+ type: "string"
777
+ };
778
+ if (typeof field.minLength === "number") {
779
+ schema.minLength = field.minLength;
780
+ }
781
+ if (typeof field.maxLength === "number") {
782
+ schema.maxLength = field.maxLength;
783
+ }
784
+ return schema;
785
+ }
786
+ case "number": {
787
+ const schema = {
788
+ ...common,
789
+ type: "number"
790
+ };
791
+ if (typeof field.minValue === "number") {
792
+ schema.minimum = field.minValue;
793
+ }
794
+ if (typeof field.maxValue === "number") {
795
+ schema.maximum = field.maxValue;
796
+ }
797
+ return schema;
798
+ }
799
+ case "date": {
800
+ const schema = {
801
+ ...common,
802
+ type: "string",
803
+ format: "date-time"
804
+ };
805
+ return schema;
806
+ }
807
+ case "checkbox": {
808
+ const schema = {
809
+ ...common,
810
+ type: "boolean"
811
+ };
812
+ if (field.required && !field.requiredIf) {
813
+ schema.const = true;
814
+ }
815
+ return schema;
816
+ }
817
+ case "singleSelect": {
818
+ const schema = {
819
+ ...common,
820
+ type: "string"
821
+ };
822
+ if (Array.isArray(field.options) && field.options.length > 0) {
823
+ schema.enum = field.options;
824
+ }
825
+ return schema;
826
+ }
827
+ case "multiSelect": {
828
+ const schema = {
829
+ ...common,
830
+ type: "array",
831
+ items: {
832
+ type: "string"
833
+ }
834
+ };
835
+ const hasOptions = Array.isArray(field.options) && field.options.length > 0;
836
+ if (hasOptions && !field.allowOther) {
837
+ schema.items = {
838
+ type: "string",
839
+ enum: field.options
840
+ };
841
+ }
842
+ if (typeof field.minSelections === "number") {
843
+ schema.minItems = field.minSelections;
844
+ }
845
+ if (typeof field.maxSelections === "number") {
846
+ schema.maxItems = field.maxSelections;
847
+ }
848
+ return schema;
849
+ }
850
+ default: {
851
+ return {
852
+ ...common
853
+ };
854
+ }
855
+ }
856
+ }
857
+ function exportJSONSchema(template) {
858
+ const properties = {};
859
+ const required = [];
860
+ for (const section of template.sections) {
861
+ for (const field of section.fields) {
862
+ properties[field.id] = mapFieldTypeToJSONSchemaCore(field);
863
+ if (field.required === true && !field.requiredIf && !field.visibleIf) {
864
+ required.push(field.id);
865
+ }
866
+ }
867
+ }
868
+ const schema = {
869
+ $schema: "https://json-schema.org/draft/2020-12/schema",
870
+ type: "object",
871
+ properties,
872
+ additionalProperties: false,
873
+ "x-frt-templateTitle": template.title,
874
+ "x-frt-templateDescription": template.description,
875
+ "x-frt-version": template.version
876
+ };
877
+ if (required.length > 0) {
878
+ schema.required = required;
879
+ }
880
+ return schema;
291
881
  }
292
882
  export {
293
883
  CORE_FIELD_DEFAULTS,
884
+ Condition,
294
885
  DEFAULT_FIELD_LABEL,
886
+ FieldRegistry,
295
887
  REPORT_TEMPLATE_FIELD_TYPES,
296
888
  REPORT_TEMPLATE_VERSION,
889
+ RepeatGroupFieldSchema,
297
890
  ReportTemplateFieldSchema,
298
891
  ReportTemplateSchemaValidator,
299
892
  ReportTemplateSectionSchema,
300
- buildFieldResponseSchema,
301
- buildResponseSchema,
893
+ buildBaseFieldSchema,
894
+ buildResponseSchemaWithConditions,
302
895
  createUniqueId,
896
+ diffTemplates,
897
+ evaluateCondition,
898
+ exportJSONSchema,
303
899
  migrateLegacySchema,
304
900
  normalizeReportTemplateSchema,
305
901
  parseReportTemplateSchema,
306
902
  parseReportTemplateSchemaFromString,
307
903
  serializeReportTemplateSchema,
308
- validateReportResponse
904
+ validateReportResponse,
905
+ validateReportResponseDetailed
309
906
  };