@frt-platform/report-core 1.3.0 โ†’ 1.5.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 CHANGED
@@ -9,7 +9,7 @@ This package is:
9
9
  * ๐Ÿ”’ **Safe by design (Zod-based)**
10
10
  * ๐Ÿงฑ **The foundation of the FRT incident & reporting platform**
11
11
 
12
- It contains **no React**, **no database code**, and **no styling**.
12
+ It contains **no React**, **no database code**, and **no styling**.
13
13
  You can use it in **Node**, **Next.js**, **backend services**, or custom form engines.
14
14
 
15
15
  ---
@@ -49,6 +49,7 @@ You can use it in **Node**, **Next.js**, **backend services**, or custom form en
49
49
  ### ๐Ÿ”ฅ Validation Engine
50
50
 
51
51
  * Build response schema dynamically with conditions
52
+ * DX helper: `buildResponseSchema(template)` (no response needed)
52
53
  * Throwing API: `validateReportResponse()`
53
54
  * Non-throwing API: `validateReportResponseDetailed()`
54
55
  * Rich `ResponseFieldError` objects with:
@@ -63,7 +64,8 @@ You can use it in **Node**, **Next.js**, **backend services**, or custom form en
63
64
 
64
65
  * Legacy format migration (`fields โ†’ sections`)
65
66
  * Automatic ID normalization & uniqueness enforcement
66
- * Safe parsing from JSON or raw objects
67
+ * Stable `"lowercase_with_underscores"`-style IDs
68
+ * Fallback IDs for missing values: `section_1`, `field_1`, etc.
67
69
 
68
70
  ### ๐Ÿ” Schema Diffing
69
71
 
@@ -81,7 +83,7 @@ Detect:
81
83
 
82
84
  * `x-frt-visibleIf`
83
85
  * `x-frt-requiredIf`
84
- * repeatGroup min/max
86
+ * `x-frt-minIf` / `x-frt-maxIf` for repeatGroup
85
87
  * placeholders
86
88
  * Useful for OpenAPI, Postman, or other backend runtimes.
87
89
 
@@ -92,6 +94,7 @@ Extend the system at runtime:
92
94
  * Add custom types (`richText`, `fileUpload`, etc.)
93
95
  * Override validation logic
94
96
  * Provide metadata for UI packages
97
+ * Unknown/unregistered field types safely fall back to `z.any()` so they never break the core engine.
95
98
 
96
99
  ### ๐Ÿงฌ Type Inference
97
100
 
@@ -99,7 +102,7 @@ Get a fully typed response type from a template:
99
102
 
100
103
  ```ts
101
104
  type MyResponse = InferResponse<typeof template>;
102
- ```
105
+ ````
103
106
 
104
107
  ### ๐Ÿงพ Serialization Helpers
105
108
 
@@ -127,6 +130,63 @@ npm install @frt-platform/report-core zod
127
130
 
128
131
  ---
129
132
 
133
+ # ๐Ÿงน Parsing & Normalization
134
+
135
+ The core exposes helpers for safely **parsing**, **migrating**, and **normalizing** templates.
136
+
137
+ ```ts
138
+ import {
139
+ parseReportTemplateSchema,
140
+ parseReportTemplateSchemaFromString,
141
+ } from "@frt-platform/report-core";
142
+
143
+ // From a raw JS object (e.g. loaded from DB, file, etc.)
144
+ const template = parseReportTemplateSchema(rawTemplateObject);
145
+
146
+ // From a JSON string
147
+ const templateFromString = parseReportTemplateSchemaFromString(jsonString);
148
+ ```
149
+
150
+ Under the hood, `parseReportTemplateSchema` does:
151
+
152
+ 1. **Legacy migration**
153
+
154
+ * Accepts both the new `{ sections: [...] }` format and legacy flat:
155
+
156
+ ```ts
157
+ {
158
+ version: 1,
159
+ fields: [ /* ... */ ]
160
+ }
161
+ ```
162
+
163
+ * Legacy `fields` are automatically wrapped into a single `section_1`.
164
+
165
+ 2. **Schema validation**
166
+
167
+ * Uses a Zod validator for `ReportTemplateSchema` to ensure the template is structurally valid.
168
+
169
+ 3. **Normalization**
170
+
171
+ * Section and field IDs are normalized to **lowercase** with spaces โ†’ underscores:
172
+
173
+ * `"My Field!"` โ†’ `"my_field"`
174
+
175
+ * Invalid characters are stripped (only `[a-z0-9_-]` are kept).
176
+
177
+ * IDs are made **unique per namespace** by appending `-1`, `-2`, โ€ฆ as needed:
178
+
179
+ * `my_field`, `my_field-1`, `my_field-2`, โ€ฆ
180
+
181
+ * Missing IDs get deterministic fallbacks:
182
+
183
+ * Sections: `section_1`, `section_2`, โ€ฆ
184
+ * Fields: `field_1`, `field_2`, โ€ฆ
185
+
186
+ This makes templates safe to store, diff, and round-trip in a stable way.
187
+
188
+ ---
189
+
130
190
  # ๐Ÿš€ Quickstart
131
191
 
132
192
  ## 1. Define a template
@@ -209,7 +269,8 @@ Produces:
209
269
  sectionTitle: "General Info",
210
270
  label: "Incident title",
211
271
  code: "field.too_small",
212
- message: 'Section "General Info" โ†’ Field "Incident title": String must contain at least 1 character(s).'
272
+ message:
273
+ 'Section "General Info" โ†’ Field "Incident title": String must contain at least 1 character(s).'
213
274
  }
214
275
  ]
215
276
  ```
@@ -257,6 +318,29 @@ Response shape:
257
318
  injured: Array<{ name: string; injury?: string }>;
258
319
  ```
259
320
 
321
+ ### ๐Ÿ“ repeatGroup behavior & limitations
322
+
323
+ * **Base constraints**
324
+
325
+ * `min` / `max` are always enforced on the row array.
326
+ * Each row is an object keyed by nested field IDs.
327
+
328
+ * **Conditional `minIf` / `maxIf`**
329
+
330
+ * If `minIf` / `maxIf` are present, they are evaluated against the **current response**.
331
+ * When the condition is `true`, the conditional value overrides the static `min` / `max` for that validation pass.
332
+ * When the condition is `false`, the engine falls back to the static `min` / `max` (if any).
333
+
334
+ * **Conditional logic inside rows**
335
+
336
+ * Nested fields in a repeatGroup support the same `visibleIf` / `requiredIf` semantics as top-level fields.
337
+ * Hidden nested fields are treated as **optional** and are **stripped** from the parsed response, just like hidden top-level fields.
338
+ * For now, row-level conditions see the **full response object**, not just the row. This matches top-level behavior and keeps the logic model simple.
339
+
340
+ * **JSON Schema export**
341
+
342
+ * repeatGroup constraints and conditions are exported via `x-frt-*` vendor extensions (e.g. `x-frt-minIf`, `x-frt-maxIf`, `x-frt-visibleIf`, `x-frt-requiredIf`), so you can mirror this behavior in other runtimes.
343
+
260
344
  ---
261
345
 
262
346
  # ๐Ÿงฉ Field Registry (Custom Types)
@@ -281,6 +365,8 @@ Now templates may include fields like:
281
365
  { id: "body", type: "richText", label: "Report body" }
282
366
  ```
283
367
 
368
+ If a template uses a field type that is **not** registered and not one of the built-in core types, the engine safely falls back to `z.any()` so unknown types never crash validation.
369
+
284
370
  ---
285
371
 
286
372
  # ๐Ÿงพ JSON Schema Export
@@ -372,7 +458,7 @@ const json = serializeReportTemplateSchema(template, {
372
458
  });
373
459
  ```
374
460
 
375
- Useful for deterministic output in Git.
461
+ Useful for deterministic output in Git and stable diffs across environments.
376
462
 
377
463
  ---
378
464
 
@@ -386,11 +472,11 @@ Useful for deterministic output in Git.
386
472
  * Field Registry
387
473
  * Error helpers
388
474
  * Serialization features
475
+ * Parsing & normalization helpers
389
476
 
390
477
  ### Phase 2 โ€” Advanced Field System (IN PROGRESS)
391
478
 
392
- * Repeat group conditional rules (`minIf` / `maxIf`)
393
- * Conditional logic inside group rows
479
+ * Richer repeatGroup UX
394
480
  * Computed fields (design)
395
481
  * RichText / FileUpload via registry
396
482
 
@@ -411,4 +497,4 @@ Useful for deterministic output in Git.
411
497
 
412
498
  # ๐Ÿ“„ License
413
499
 
414
- MIT โ€” feel free to use, extend, or contribute.
500
+ MIT โ€” feel free to use, extend, or contribute.
package/dist/index.d.mts CHANGED
@@ -7,7 +7,9 @@ declare const Condition: z.ZodType<any>;
7
7
  type SimpleCondition = z.infer<typeof Condition>;
8
8
  declare const RepeatGroupFieldSchema: z.ZodType<any>;
9
9
  declare const ReportTemplateFieldSchema: z.ZodType<any>;
10
- type ReportTemplateField = z.infer<typeof ReportTemplateFieldSchema>;
10
+ type ReportTemplateField = z.infer<typeof ReportTemplateFieldSchema> & {
11
+ computed?: string;
12
+ };
11
13
  declare const ReportTemplateSectionSchema: z.ZodObject<{
12
14
  id: z.ZodString;
13
15
  title: z.ZodOptional<z.ZodString>;
@@ -68,7 +70,25 @@ declare const ReportTemplateSchemaValidator: z.ZodObject<{
68
70
  }>;
69
71
  type ReportTemplateSchema = z.infer<typeof ReportTemplateSchemaValidator>;
70
72
 
71
- declare function migrateLegacySchema(raw: unknown): unknown;
73
+ /**
74
+ * Helper for generating a unique id in a *local* namespace, typically in
75
+ * UI/builders when adding new sections/fields client-side.
76
+ *
77
+ * Note:
78
+ * - This is deliberately simple and deterministic.
79
+ * - Core template normalization uses its own normalizeId/ensureUniqueId logic
80
+ * so that persisted IDs stay stable and backwards compatible.
81
+ */
82
+ declare function createUniqueId(prefix: string, existing: Iterable<string>): string;
83
+
84
+ /**
85
+ * Migrate a legacy flat { fields: [...] } structure or partially-upgraded
86
+ * schema into the current { sections: [...] } shape.
87
+ *
88
+ * This is intentionally loose: it accepts `unknown` and only normalizes
89
+ * what we care about. The full Zod validation happens later.
90
+ */
91
+ declare function migrateLegacySchema(raw: unknown): ReportTemplateSchema | any;
72
92
 
73
93
  declare function normalizeReportTemplateSchema(schema: ReportTemplateSchema): ReportTemplateSchema;
74
94
  declare function parseReportTemplateSchema(raw: unknown): ReportTemplateSchema;
@@ -99,61 +119,55 @@ interface SerializeOptions {
99
119
  */
100
120
  declare function serializeReportTemplateSchema(schema: ReportTemplateSchema, options?: SerializeOptions): string;
101
121
 
122
+ interface SectionPropertyChange {
123
+ key: string;
124
+ before: unknown;
125
+ after: unknown;
126
+ }
127
+ interface ModifiedSection {
128
+ sectionId: string;
129
+ changes: SectionPropertyChange[];
130
+ }
131
+ interface ModifiedField {
132
+ sectionId: string;
133
+ fieldId: string;
134
+ changes: SectionPropertyChange[];
135
+ }
136
+ interface SectionAddRemove {
137
+ sectionId: string;
138
+ index: number;
139
+ }
140
+ interface SectionReorder {
141
+ sectionId: string;
142
+ from: number;
143
+ to: number;
144
+ }
145
+ interface FieldAddRemove {
146
+ sectionId: string;
147
+ fieldId: string;
148
+ index: number;
149
+ }
150
+ interface FieldReorder {
151
+ sectionId: string;
152
+ fieldId: string;
153
+ from: number;
154
+ to: number;
155
+ }
156
+ interface NestedFieldDiff {
157
+ sectionId: string;
158
+ groupId: string;
159
+ diffs: TemplateDiff;
160
+ }
102
161
  interface TemplateDiff {
103
- addedSections: Array<{
104
- sectionId: string;
105
- index: number;
106
- }>;
107
- removedSections: Array<{
108
- sectionId: string;
109
- index: number;
110
- }>;
111
- reorderedSections: Array<{
112
- sectionId: string;
113
- from: number;
114
- to: number;
115
- }>;
116
- modifiedSections: Array<{
117
- sectionId: string;
118
- changes: Array<{
119
- key: string;
120
- before: any;
121
- after: any;
122
- }>;
123
- }>;
124
- addedFields: Array<{
125
- sectionId: string;
126
- fieldId: string;
127
- index: number;
128
- }>;
129
- removedFields: Array<{
130
- sectionId: string;
131
- fieldId: string;
132
- index: number;
133
- }>;
134
- reorderedFields: Array<{
135
- sectionId: string;
136
- fieldId: string;
137
- from: number;
138
- to: number;
139
- }>;
140
- modifiedFields: Array<{
141
- sectionId: string;
142
- fieldId: string;
143
- before: Record<string, any>;
144
- after: Record<string, any>;
145
- changes: Array<{
146
- key: string;
147
- before: any;
148
- after: any;
149
- }>;
150
- }>;
151
- /** NEW: repeatGroup nested diffs */
152
- nestedFieldDiffs: Array<{
153
- sectionId: string;
154
- groupId: string;
155
- diffs: TemplateDiff;
156
- }>;
162
+ addedSections: SectionAddRemove[];
163
+ removedSections: SectionAddRemove[];
164
+ reorderedSections: SectionReorder[];
165
+ modifiedSections: ModifiedSection[];
166
+ addedFields: FieldAddRemove[];
167
+ removedFields: FieldAddRemove[];
168
+ reorderedFields: FieldReorder[];
169
+ modifiedFields: ModifiedField[];
170
+ nestedFieldDiffs: NestedFieldDiff[];
157
171
  }
158
172
  declare function diffTemplates(before: ReportTemplateSchema, after: ReportTemplateSchema): TemplateDiff;
159
173
 
@@ -192,63 +206,33 @@ interface JSONSchema {
192
206
  */
193
207
  declare function exportJSONSchema(template: ReportTemplateSchema): JSONSchema;
194
208
 
195
- declare const DEFAULT_FIELD_LABEL = "Untitled question";
196
- declare const CORE_FIELD_DEFAULTS: Record<ReportTemplateFieldType, Record<string, unknown>>;
197
-
198
- declare function createUniqueId(prefix: string, existing: Iterable<string>): string;
199
-
200
- /** -------------------------------------------------------------
201
- * FieldRegistryEntry
202
- * --------------------------------------------------------------
203
- * Represents metadata + behavior for a single field type.
204
- * -------------------------------------------------------------- */
205
- interface FieldRegistryEntry {
206
- /** Default field-level properties merged when creating new fields */
207
- defaults?: Record<string, unknown>;
208
- /**
209
- * Build a Zod schema for validating the *response value* of this field.
210
- * If not provided, the core engine falls back to the built-in handler.
211
- */
212
- buildResponseSchema?: (field: ReportTemplateField) => ZodTypeAny;
213
- /**
214
- * Optional UI metadata for @frt/report-react
215
- * (icon, color, description, grouping, etc.)
216
- */
217
- ui?: Record<string, any>;
218
- }
219
- /** -------------------------------------------------------------
220
- * FieldRegistry
221
- * --------------------------------------------------------------
222
- * Central store of all field types (built-in + custom).
223
- * -------------------------------------------------------------- */
224
- declare class FieldRegistryClass {
225
- private registry;
226
- /** Register or override a field type. */
227
- register(type: string, entry: FieldRegistryEntry): void;
228
- /** Get registry entry for a field type. */
229
- get(type: string): FieldRegistryEntry | undefined;
230
- /** Check if field type is registered. */
231
- has(type: string): boolean;
232
- /** Return all field types currently registered. */
233
- list(): string[];
234
- }
235
- /** Singleton instance */
236
- declare const FieldRegistry: FieldRegistryClass;
237
-
238
209
  /**
239
- * Evaluate a conditional rule against a submitted response object.
210
+ * Evaluate a SimpleCondition against a flat response object.
240
211
  *
241
- * Supported forms:
242
- * { equals: { fieldId: value } }
243
- * { any: [cond, cond] }
244
- * { all: [cond, cond] }
245
- * { not: cond }
212
+ * Supported structure:
213
+ * - { equals: { fieldId: value } }
214
+ * - { any: [cond, cond, ...] }
215
+ * - { all: [cond, cond, ...] }
216
+ * - { not: cond }
217
+ *
218
+ * Notes:
219
+ * - We intentionally keep this evaluation shallow and deterministic.
220
+ * - No special array semantics (e.g. "contains") yet โ€” it's strict equality.
246
221
  */
247
- declare function evaluateCondition(condition: SimpleCondition, response: Record<string, any>): boolean;
222
+ declare function evaluateCondition(condition: SimpleCondition | undefined, response: Record<string, any>): boolean;
248
223
 
224
+ /**
225
+ * Build the base Zod schema for a field, WITHOUT:
226
+ * - visibleIf
227
+ * - requiredIf
228
+ * - computed fields
229
+ * - repeatGroup logic
230
+ *
231
+ * This is the lowest-level โ€œfield โ†’ Zodโ€ mapping.
232
+ * All conditional logic is layered on top in conditionalSchema.ts.
233
+ */
249
234
  declare function buildBaseFieldSchema(field: ReportTemplateField): ZodTypeAny;
250
- declare function buildResponseSchemaWithConditions(template: ReportTemplateSchema, response: Record<string, any>): z.ZodObject<Record<string, ZodTypeAny>>;
251
- declare function validateReportResponse(template: ReportTemplateSchema, data: unknown): Record<string, unknown>;
235
+
252
236
  type FieldErrorCode = "response.invalid_root" | "field.required" | "field.invalid_type" | "field.too_small" | "field.too_big" | "field.invalid_option" | "field.custom";
253
237
  interface ResponseFieldError {
254
238
  /** field id (template-level id) */
@@ -266,6 +250,27 @@ interface ResponseFieldError {
266
250
  /** original Zod issue (for debugging / logging) */
267
251
  rawIssue?: ZodIssue;
268
252
  }
253
+ /**
254
+ * Helper to turn a thrown ZodError (e.g. from validateReportResponse)
255
+ * into the same ResponseFieldError[] shape used by
256
+ * validateReportResponseDetailed.
257
+ *
258
+ * Returns null if the error is not a ZodError.
259
+ */
260
+ declare function explainValidationError(template: ReportTemplateSchema, error: unknown): ResponseFieldError[] | null;
261
+
262
+ declare function buildResponseSchemaWithConditions(template: ReportTemplateSchema, response: Record<string, any>): z.ZodObject<Record<string, ZodTypeAny>>;
263
+ /**
264
+ * Build a Zod object schema for a template *without* needing a response object.
265
+ *
266
+ * Notes:
267
+ * - All conditional logic is evaluated against an empty response `{}`.
268
+ * - This is mainly useful for:
269
+ * - Static type inference
270
+ * - Generic tooling / OpenAPI-style usage
271
+ */
272
+ declare function buildResponseSchema(template: ReportTemplateSchema): z.ZodObject<Record<string, ZodTypeAny>>;
273
+ declare function validateReportResponse(template: ReportTemplateSchema, data: unknown): Record<string, unknown>;
269
274
  /**
270
275
  * Validates a response against a template, but instead of throwing,
271
276
  * returns a structured error object with:
@@ -282,28 +287,66 @@ declare function validateReportResponseDetailed(template: ReportTemplateSchema,
282
287
  success: false;
283
288
  errors: ResponseFieldError[];
284
289
  };
290
+
285
291
  /**
286
- * Helper to turn a thrown ZodError (e.g. from validateReportResponse)
287
- * into the same ResponseFieldError[] shape used by
288
- * validateReportResponseDetailed.
292
+ * Extracts the union of all field configs from a template type.
289
293
  *
290
- * Returns null if the error is not a ZodError.
294
+ * Given:
295
+ * {
296
+ * sections: [
297
+ * { fields: [F1, F2] },
298
+ * { fields: [F3] }
299
+ * ]
300
+ * }
301
+ *
302
+ * โ†’ TemplateFieldUnion<TTemplate> = F1 | F2 | F3
291
303
  */
292
- declare function explainValidationError(template: ReportTemplateSchema, error: unknown): ResponseFieldError[] | null;
293
304
  type TemplateFieldUnion<TTemplate> = TTemplate extends {
294
305
  sections: readonly (infer S)[];
295
306
  } ? S extends {
296
307
  fields: readonly (infer F)[];
297
308
  } ? F : never : never;
309
+ /**
310
+ * Base value type for a field, ignoring `required`.
311
+ * This is deliberately simple and only keyed off `type` + `options`.
312
+ */
298
313
  type BaseFieldValue<TField extends {
299
314
  type: ReportTemplateFieldType;
300
315
  required?: boolean;
301
316
  options?: readonly string[] | string[];
302
- }> = TField["type"] extends "shortText" | "longText" ? string : TField["type"] extends "number" ? number : TField["type"] extends "date" ? string : TField["type"] extends "checkbox" ? boolean : TField["type"] extends "singleSelect" ? (TField["options"] extends readonly (infer O)[] ? O : TField["options"] extends (infer O)[] ? O : string) : TField["type"] extends "multiSelect" ? (TField["options"] extends readonly (infer O)[] ? O[] : TField["options"] extends (infer O)[] ? O[] : string[]) : TField["type"] extends "repeatGroup" ? Array<Record<string, unknown>> : unknown;
317
+ }> = TField["type"] extends "shortText" | "longText" ? string : TField["type"] extends "number" ? number : TField["type"] extends "date" ? string : TField["type"] extends "checkbox" ? boolean : TField["type"] extends "singleSelect" ? TField["options"] extends readonly (infer O)[] ? O : TField["options"] extends (infer O)[] ? O : string : TField["type"] extends "multiSelect" ? TField["options"] extends readonly (infer O)[] ? O[] : TField["options"] extends (infer O)[] ? O[] : string[] : TField["type"] extends "repeatGroup" ? Array<Record<string, unknown>> : unknown;
318
+ /**
319
+ * Wrap base value in optionality depending on `required`.
320
+ */
303
321
  type FieldResponseValue<TField extends {
304
322
  type: ReportTemplateFieldType;
305
323
  required?: boolean;
306
324
  }> = TField["required"] extends true ? BaseFieldValue<TField> : BaseFieldValue<TField> | undefined;
325
+ /**
326
+ * Infer the response shape from a *const* template definition.
327
+ *
328
+ * Example:
329
+ *
330
+ * export const myTemplate = {
331
+ * version: 1,
332
+ * sections: [
333
+ * {
334
+ * id: "main",
335
+ * fields: [
336
+ * { id: "title", type: "shortText", required: true },
337
+ * { id: "tags", type: "multiSelect", options: ["A", "B"] },
338
+ * ]
339
+ * }
340
+ * ]
341
+ * } as const;
342
+ *
343
+ * type MyResponse = InferResponse<typeof myTemplate>;
344
+ *
345
+ * // {
346
+ * // title: string;
347
+ * // tags?: ("A" | "B")[];
348
+ * // }
349
+ */
307
350
  type InferResponse<TTemplate extends {
308
351
  sections: readonly {
309
352
  fields: readonly {
@@ -317,4 +360,59 @@ type InferResponse<TTemplate extends {
317
360
  [F in TemplateFieldUnion<TTemplate> as F["id"]]: FieldResponseValue<F>;
318
361
  };
319
362
 
320
- export { CORE_FIELD_DEFAULTS, Condition, DEFAULT_FIELD_LABEL, type FieldErrorCode, FieldRegistry, type FieldRegistryEntry, type InferResponse, type JSONSchema, REPORT_TEMPLATE_FIELD_TYPES, REPORT_TEMPLATE_VERSION, RepeatGroupFieldSchema, type ReportTemplateField, ReportTemplateFieldSchema, type ReportTemplateFieldType, type ReportTemplateSchema, ReportTemplateSchemaValidator, type ReportTemplateSection, ReportTemplateSectionSchema, type ResponseFieldError, type SerializeOptions, type SimpleCondition, type TemplateDiff, buildBaseFieldSchema, buildResponseSchemaWithConditions, createUniqueId, diffTemplates, evaluateCondition, explainValidationError, exportJSONSchema, migrateLegacySchema, normalizeReportTemplateSchema, parseReportTemplateSchema, parseReportTemplateSchemaFromString, serializeReportTemplateSchema, validateReportResponse, validateReportResponseDetailed };
363
+ /** -------------------------------------------------------------
364
+ * FieldRegistryEntry
365
+ * --------------------------------------------------------------
366
+ * Represents metadata + behavior for a single field type.
367
+ * -------------------------------------------------------------- */
368
+ interface FieldRegistryEntry {
369
+ /** Default field-level properties merged when creating new fields */
370
+ defaults?: Record<string, unknown>;
371
+ /**
372
+ * Build a Zod schema for validating the *response value* of this field.
373
+ * If not provided, the core engine falls back to the built-in handler.
374
+ */
375
+ buildResponseSchema?: (field: ReportTemplateField) => ZodTypeAny;
376
+ /**
377
+ * Optional UI metadata for @frt/report-react
378
+ * (icon, color, description, grouping, etc.)
379
+ */
380
+ ui?: Record<string, any>;
381
+ }
382
+ /** -------------------------------------------------------------
383
+ * FieldRegistry
384
+ * --------------------------------------------------------------
385
+ * Central store of all field types (built-in + custom).
386
+ * -------------------------------------------------------------- */
387
+ declare class FieldRegistryClass {
388
+ private registry;
389
+ /** Register or override a field type. */
390
+ register(type: string, entry: FieldRegistryEntry): void;
391
+ /** Get registry entry for a field type. */
392
+ get(type: string): FieldRegistryEntry | undefined;
393
+ /** Check if field type is registered. */
394
+ has(type: string): boolean;
395
+ /** Return all field types currently registered. */
396
+ list(): string[];
397
+ }
398
+ /** Singleton instance */
399
+ declare const FieldRegistry: FieldRegistryClass;
400
+
401
+ /**
402
+ * Shared default label used for all core field types.
403
+ */
404
+ declare const DEFAULT_FIELD_LABEL = "Untitled field";
405
+ /**
406
+ * Shape of the core defaults map.
407
+ * We keep values loose (Record<string, any>) because different field
408
+ * types have different shapes, and these are just "seed" objects.
409
+ */
410
+ type CoreFieldDefaults = Record<ReportTemplateFieldType, Record<string, any>>;
411
+ /**
412
+ * Default configs for every core field type.
413
+ * These are *not* full fields (no id), just starter values that
414
+ * template builders / UIs can merge into new fields.
415
+ */
416
+ declare const CORE_FIELD_DEFAULTS: CoreFieldDefaults;
417
+
418
+ export { CORE_FIELD_DEFAULTS, Condition, type CoreFieldDefaults, DEFAULT_FIELD_LABEL, type FieldAddRemove, type FieldErrorCode, FieldRegistry, type FieldRegistryEntry, type FieldReorder, type InferResponse, type JSONSchema, type ModifiedField, type ModifiedSection, type NestedFieldDiff, REPORT_TEMPLATE_FIELD_TYPES, REPORT_TEMPLATE_VERSION, RepeatGroupFieldSchema, type ReportTemplateField, ReportTemplateFieldSchema, type ReportTemplateFieldType, type ReportTemplateSchema, ReportTemplateSchemaValidator, type ReportTemplateSection, ReportTemplateSectionSchema, type ResponseFieldError, type SectionAddRemove, type SectionPropertyChange, type SectionReorder, type SerializeOptions, type SimpleCondition, type TemplateDiff, buildBaseFieldSchema, buildResponseSchema, buildResponseSchemaWithConditions, createUniqueId, diffTemplates, evaluateCondition, explainValidationError, exportJSONSchema, migrateLegacySchema, normalizeReportTemplateSchema, parseReportTemplateSchema, parseReportTemplateSchemaFromString, serializeReportTemplateSchema, validateReportResponse, validateReportResponseDetailed };