@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 CHANGED
@@ -1,496 +1,289 @@
1
1
  # `@frt-platform/report-core`
2
2
 
3
- Core engine for building, validating, and migrating **report templates** – plus validating **report responses** and exporting **JSON Schema**.
3
+ Core engine for defining, validating, migrating, diffing, and serializing **dynamic report templates**.
4
4
 
5
5
  This package is:
6
6
 
7
- * **Framework-agnostic** (no React)
8
- * **Storage-agnostic** (no DB assumptions)
9
- * **Runtime-safe** (Zod-based validation)
7
+ * 🧠 **Framework-agnostic**
8
+ * 🧩 **UI-agnostic**
9
+ * πŸ”’ **Safe by design (Zod-based)**
10
+ * 🧱 **The foundation of the FRT incident & reporting platform**
10
11
 
11
- It gives you:
12
-
13
- * A **typed schema** for report templates (templates β†’ sections β†’ fields)
14
- * **Zod-based validation** and parsing from unknown / JSON input
15
- * **Legacy schema migration** (flat `fields` β†’ `sections`, old type names, etc.)
16
- * **Normalization** helpers to ensure safe IDs & consistent structure
17
- * **Field defaults** and **ID utilities** you can use to build your own UI
18
- * **Response validation** based on templates (`validateReportResponse`)
19
- * **Conditional logic** for fields (`visibleIf` / `requiredIf`)
20
- * **Template diffing** (`diffTemplates`) to compare template versions (including ordering)
21
- * **Type-level response inference** (`InferResponse`) for fully typed responses
22
- * **Field registry** (`FieldRegistry`) for custom / extensible field types
23
- * **JSON Schema export** (`exportJSONSchema`) for integration with OpenAPI / external validators
24
-
25
- It’s the core that powers a flexible incident/report builder, but it’s generic enough to be reused in any app.
12
+ It contains **no React**, **no database code**, and **no styling**.
13
+ You can use it in **Node**, **Next.js**, **backend services**, or custom form engines.
26
14
 
27
15
  ---
28
16
 
29
- ## Installation
17
+ # ✨ Features
30
18
 
31
- ```bash
32
- # npm
33
- npm install @frt-platform/report-core zod
19
+ ### πŸ“„ Template Schema
34
20
 
35
- # yarn
36
- yarn add @frt-platform/report-core zod
21
+ * Sections β†’ fields
22
+ * Field IDs, labels, descriptions, placeholders
23
+ * Built-in field types:
37
24
 
38
- # pnpm
39
- pnpm add @frt-platform/report-core zod
40
- ```
25
+ * `shortText`, `longText`
26
+ * `number`
27
+ * `date`
28
+ * `checkbox`
29
+ * `singleSelect`, `multiSelect`
30
+ * `repeatGroup` (nested fieldsets)
41
31
 
42
- `zod` is a peer dependency and is used for schema validation & parsing.
32
+ ### πŸŽ› Field Constraints
43
33
 
44
- ---
34
+ * min/max length (`shortText`, `longText`)
35
+ * min/max value (`number`)
36
+ * min/max selections (`multiSelect`)
37
+ * allowed options
38
+ * checkbox required semantics
39
+ * default values
45
40
 
46
- ## Concepts
41
+ ### πŸ”€ Conditional Logic
47
42
 
48
- The core model looks like this:
43
+ * `visibleIf`
44
+ * `requiredIf`
45
+ * Supports:
46
+ `equals`, `any`, `all`, `not`
47
+ * Fully integrated into validation.
49
48
 
50
- * A **template** contains multiple **sections**
51
- * A **section** contains multiple **fields**
52
- * A **field** has a `type` (short text, long text, number, select, etc.) and optional constraints
53
- * Fields can have **conditional logic** (`visibleIf`, `requiredIf`)
49
+ ### πŸ”₯ Validation Engine
54
50
 
55
- Supported field types:
51
+ * Build response schema dynamically with conditions
52
+ * Throwing API: `validateReportResponse()`
53
+ * Non-throwing API: `validateReportResponseDetailed()`
54
+ * Rich `ResponseFieldError` objects with:
56
55
 
57
- ```ts
58
- import { REPORT_TEMPLATE_FIELD_TYPES } from "@frt-platform/report-core";
59
-
60
- console.log(REPORT_TEMPLATE_FIELD_TYPES);
61
- // [
62
- // "shortText",
63
- // "longText",
64
- // "number",
65
- // "date",
66
- // "checkbox",
67
- // "singleSelect",
68
- // "multiSelect",
69
- // "repeatGroup", // reserved for repeating groups / fieldsets
70
- // ]
71
- ```
56
+ * section title
57
+ * field label
58
+ * error code
59
+ * full message
60
+ * nested repeatGroup row context
72
61
 
73
- > `repeatGroup` is reserved for repeating fieldsets (e.g. multiple injured people); the core schema knows about it and provides defaults, and the full runtime semantics can be layered on top in your app/React package.
62
+ ### πŸ”„ Template Migration & Normalization
74
63
 
75
- ---
64
+ * Legacy format migration (`fields β†’ sections`)
65
+ * Automatic ID normalization & uniqueness enforcement
66
+ * Safe parsing from JSON or raw objects
76
67
 
77
- ## Quick Start
68
+ ### πŸ” Schema Diffing
78
69
 
79
- ### 1. Define a template in JSON
70
+ Detect:
80
71
 
81
- You can define templates statically or load them from DB / API:
72
+ * Added / removed / reordered sections
73
+ * Added / removed / reordered fields
74
+ * Modified fields
75
+ * Nested diffs inside repeat groups
82
76
 
83
- ```ts
84
- import type { ReportTemplateSchema } from "@frt-platform/report-core";
77
+ ### πŸ“¦ JSON Schema Export
85
78
 
86
- const templateJson: ReportTemplateSchema = {
87
- version: 1,
88
- title: "Incident follow-up",
89
- description: "Collect a quick summary after an incident has been resolved.",
90
- sections: [
91
- {
92
- id: "section-overview",
93
- title: "Incident overview",
94
- fields: [
95
- {
96
- id: "summary",
97
- type: "longText",
98
- label: "What happened?",
99
- required: true,
100
- minLength: 10,
101
- },
102
- {
103
- id: "status",
104
- type: "singleSelect",
105
- label: "Incident status",
106
- required: true,
107
- options: ["Pending review", "Resolved", "Escalated"],
108
- defaultValue: "Resolved",
109
- },
110
- ],
111
- },
112
- ],
113
- };
114
- ```
115
-
116
- ### 2. Validate and normalize the template
117
-
118
- Use the helpers to safely parse and normalize unknown input:
119
-
120
- ```ts
121
- import {
122
- parseReportTemplateSchema,
123
- normalizeReportTemplateSchema,
124
- } from "@frt-platform/report-core";
125
-
126
- const raw = /* from DB, file, API, etc. */ templateJson;
127
-
128
- // 1) parse + validate (throws on invalid input)
129
- const parsed = parseReportTemplateSchema(raw);
130
-
131
- // 2) optional extra normalization step (IDs, trimming, de-duplication, etc.)
132
- const normalized = normalizeReportTemplateSchema(parsed);
133
-
134
- console.log(normalized.sections[0].fields[0].id);
135
- // always a non-empty, safe identifier
136
- ```
79
+ * Export a template as a valid JSON Schema (2020-12 draft)
80
+ * Includes vendor extensions:
137
81
 
138
- ### 3. Serialize for storage
82
+ * `x-frt-visibleIf`
83
+ * `x-frt-requiredIf`
84
+ * repeatGroup min/max
85
+ * placeholders
86
+ * Useful for OpenAPI, Postman, or other backend runtimes.
139
87
 
140
- ```ts
141
- import { serializeReportTemplateSchema } from "@frt-platform/report-core";
88
+ ### πŸ— Field Registry
142
89
 
143
- const jsonString = serializeReportTemplateSchema(normalized);
144
- // ready to store in DB, file, etc.
145
- ```
90
+ Extend the system at runtime:
146
91
 
147
- ---
92
+ * Add custom types (`richText`, `fileUpload`, etc.)
93
+ * Override validation logic
94
+ * Provide metadata for UI packages
148
95
 
149
- ## Parsing from a JSON string
96
+ ### 🧬 Type Inference
150
97
 
151
- If you have a JSON string (e.g. from a form textarea):
98
+ Get a fully typed response type from a template:
152
99
 
153
100
  ```ts
154
- import { parseReportTemplateSchemaFromString } from "@frt-platform/report-core";
155
-
156
- try {
157
- const template = parseReportTemplateSchemaFromString(userInputString);
158
- // valid template here
159
- } catch (err) {
160
- // invalid JSON or schema – show error to user
161
- }
101
+ type MyResponse = InferResponse<typeof template>;
162
102
  ```
163
103
 
164
- ---
165
-
166
- ## Validating report responses
104
+ ### 🧾 Serialization Helpers
167
105
 
168
- Once you have a template, you can validate **user-submitted responses** against it.
169
-
170
- ### Runtime validation
106
+ Deterministic JSON output with sorting options:
171
107
 
172
108
  ```ts
173
- import { validateReportResponse } from "@frt-platform/report-core";
174
-
175
- const template = /* a valid ReportTemplateSchema */ normalized;
176
-
177
- const responseData = {
178
- summary: "Student slipped in the hallway, no serious injuries.",
179
- status: "Resolved",
180
- };
181
-
182
- // Validate (throws ZodError on failure)
183
- const safeResponse = validateReportResponse(template, responseData);
109
+ serializeReportTemplateSchema(template, {
110
+ pretty: true,
111
+ sortSectionsById: true,
112
+ sortFieldsById: true,
113
+ });
184
114
  ```
185
115
 
186
- `validateReportResponse` respects:
187
-
188
- * `required` flags
189
- * `minLength` / `maxLength` (short/long text)
190
- * `minValue` / `maxValue` (numbers)
191
- * `minSelections` / `maxSelections` (multiSelect)
192
- * `options` (singleSelect + multiSelect)
193
- * `checkbox` semantics (required checkbox must be `true`)
194
- * **Conditional logic**:
195
-
196
- * `visibleIf`: if false, the field is treated as invisible and removed from the parsed output
197
- * `requiredIf`: if true, the field is treated as required for this specific response
198
-
199
- You can still build a raw schema yourself using the conditional builder:
200
-
201
- ```ts
202
- import { buildResponseSchemaWithConditions } from "@frt-platform/report-core";
203
-
204
- const responseSchema = buildResponseSchemaWithConditions(template, responseData);
205
- const safeResponse = responseSchema.parse(responseData);
206
- ```
116
+ Perfect for Git diffs and storage.
207
117
 
208
118
  ---
209
119
 
210
- ## Conditional logic (`visibleIf` / `requiredIf`)
211
-
212
- Fields can declare conditions that refer to other field values.
213
-
214
- Example:
120
+ # πŸ“¦ Installation
215
121
 
216
- ```json
217
- {
218
- "id": "injury_details",
219
- "type": "longText",
220
- "label": "Describe the injury",
221
- "visibleIf": { "equals": { "injured": true } },
222
- "requiredIf": { "equals": { "severity": "High" } }
223
- }
122
+ ```bash
123
+ npm install @frt-platform/report-core zod
224
124
  ```
225
125
 
226
- Supported condition shapes:
227
-
228
- * `{"equals": { "fieldId": value } }`
229
- * `{"any": [cond1, cond2, ...] }`
230
- * `{"all": [cond1, cond2, ...] }`
231
- * `{"not": cond }`
232
-
233
- At runtime:
234
-
235
- * If `visibleIf` is **false** β†’ field is treated as **invisible**, becomes optional, and is stripped from the validated response.
236
- * If `requiredIf` is **true** β†’ field is treated as **required** for that specific response.
126
+ `zod` is a peer dependency.
237
127
 
238
128
  ---
239
129
 
240
- ## Type-level response inference (`InferResponse`)
130
+ # πŸš€ Quickstart
241
131
 
242
- If you define templates statically (`as const`), you can get a fully-typed response type from the template:
132
+ ## 1. Define a template
243
133
 
244
134
  ```ts
245
- import type { InferResponse } from "@frt-platform/report-core";
135
+ import type { ReportTemplateSchema } from "@frt-platform/report-core";
246
136
 
247
- const incidentTemplate = {
137
+ const template: ReportTemplateSchema = {
248
138
  version: 1,
249
139
  sections: [
250
140
  {
251
- id: "section-overview",
141
+ id: "general",
142
+ title: "General Info",
252
143
  fields: [
253
144
  {
254
- id: "summary",
255
- type: "longText",
256
- label: "What happened?",
145
+ id: "title",
146
+ type: "shortText",
147
+ label: "Incident title",
257
148
  required: true,
258
- minLength: 10,
259
149
  },
260
150
  {
261
- id: "status",
151
+ id: "severity",
262
152
  type: "singleSelect",
263
- label: "Status",
153
+ label: "Severity",
264
154
  required: true,
265
- options: ["Pending", "Resolved", "Escalated"] as const,
266
- },
267
- {
268
- id: "follow_up_date",
269
- type: "date",
270
- label: "Follow-up date",
155
+ options: ["Low", "Medium", "High"],
271
156
  },
272
157
  {
273
- id: "tags",
274
- type: "multiSelect",
275
- label: "Tags",
276
- options: ["Safety", "Medical", "Security"] as const,
277
- },
278
- {
279
- id: "confirmed",
280
- type: "checkbox",
281
- label: "I confirm this report is accurate",
282
- required: true,
158
+ id: "details",
159
+ type: "longText",
160
+ label: "Details",
161
+ minLength: 10,
283
162
  },
284
163
  ],
285
164
  },
286
165
  ],
287
- } as const;
288
-
289
- type IncidentResponse = InferResponse<typeof incidentTemplate>;
290
- ```
291
-
292
- `IncidentResponse` will be inferred as:
293
-
294
- ```ts
295
- type IncidentResponse = {
296
- summary: string; // required
297
- status: "Pending" | "Resolved" | "Escalated"; // required
298
- follow_up_date?: string; // optional
299
- tags?: ("Safety" | "Medical" | "Security")[]; // optional
300
- confirmed: boolean; // required checkbox
301
166
  };
302
167
  ```
303
168
 
304
- This is extremely useful for:
305
-
306
- * API handlers (`(body: IncidentResponse) => { ... }`)
307
- * DB layers that store `data` blobs
308
- * Frontend forms with type-safe state
309
-
310
169
  ---
311
170
 
312
- ## Using field defaults
313
-
314
- The package includes core defaults for each field type (no UI, no icons):
171
+ ## 2. Validate a response (throwing API)
315
172
 
316
173
  ```ts
317
- import {
318
- CORE_FIELD_DEFAULTS,
319
- DEFAULT_FIELD_LABEL,
320
- type ReportTemplateFieldType,
321
- } from "@frt-platform/report-core";
322
-
323
- function createDefaultField(type: ReportTemplateFieldType) {
324
- const defaults = CORE_FIELD_DEFAULTS[type] ?? {};
325
- return {
326
- id: "your-id-here",
327
- type,
328
- label: (defaults.label as string) ?? DEFAULT_FIELD_LABEL,
329
- ...defaults,
330
- };
331
- }
174
+ import { validateReportResponse } from "@frt-platform/report-core";
332
175
 
333
- const field = createDefaultField("shortText");
176
+ const parsed = validateReportResponse(template, {
177
+ title: "Broken fire alarm",
178
+ severity: "High",
179
+ details: "Triggered after smoke test",
180
+ });
334
181
  ```
335
182
 
336
- `CORE_FIELD_DEFAULTS` includes basic defaults for:
337
-
338
- * `shortText`, `longText`, `number`, `date`, `checkbox`, `singleSelect`, `multiSelect`
339
- * `repeatGroup` (with an empty `fields` array; your builder populates nested fields)
340
-
341
- This is especially useful in a form builder UI when the user adds a new field of a given type.
183
+ If invalid β†’ throws a ZodError.
342
184
 
343
185
  ---
344
186
 
345
- ## Field Registry (`FieldRegistry`)
346
-
347
- You can register **custom field types** or override core types at runtime:
187
+ ## 3. Validate without throwing (UI-friendly)
348
188
 
349
189
  ```ts
350
- import { FieldRegistry } from "@frt-platform/report-core";
351
- import { z } from "zod";
190
+ import { validateReportResponseDetailed } from "@frt-platform/report-core";
352
191
 
353
- // Simple example: a "richText" field that behaves like a string
354
- FieldRegistry.register("richText", {
355
- defaults: {
356
- label: "Details",
357
- placeholder: "Write here...",
358
- },
359
- buildResponseSchema: (field) => {
360
- let schema = z.string();
361
-
362
- if (typeof field.minLength === "number") {
363
- schema = schema.min(field.minLength);
364
- }
365
- if (typeof field.maxLength === "number") {
366
- schema = schema.max(field.maxLength);
367
- }
368
-
369
- return field.required ? schema : schema.optional();
370
- },
192
+ const result = validateReportResponseDetailed(template, {
193
+ title: "",
194
+ severity: "High",
371
195
  });
372
- ```
373
-
374
- At validation time, `validateReportResponse` and `buildBaseFieldSchema` will:
375
196
 
376
- 1. Check the registry for a custom handler.
377
- 2. Use the built-in fallback if none is registered.
197
+ if (!result.success) {
198
+ console.log(result.errors);
199
+ }
200
+ ```
378
201
 
379
- Use this for:
202
+ Produces:
380
203
 
381
- * App-specific field types (e.g. `fileUpload`, `signature`, `mapLocation`)
382
- * Tenant-specific extensions in a multi-tenant platform
383
- * Gradually introducing new field behaviors without changing the core package
204
+ ```ts
205
+ [
206
+ {
207
+ fieldId: "title",
208
+ sectionId: "general",
209
+ sectionTitle: "General Info",
210
+ label: "Incident title",
211
+ code: "field.too_small",
212
+ message: 'Section "General Info" β†’ Field "Incident title": String must contain at least 1 character(s).'
213
+ }
214
+ ]
215
+ ```
384
216
 
385
217
  ---
386
218
 
387
- ## Generating unique IDs
388
-
389
- The core package exposes a generic `createUniqueId` helper:
219
+ # πŸ”€ Conditional Logic Example
390
220
 
391
221
  ```ts
392
- import { createUniqueId } from "@frt-platform/report-core";
393
-
394
- const existingSectionIds = ["section-1", "section-2"];
395
- const newId = createUniqueId("section", existingSectionIds);
396
- // "section-3" (or the next available)
222
+ {
223
+ id: "follow_up_notes",
224
+ type: "longText",
225
+ label: "Follow-up notes",
226
+ visibleIf: { equals: { follow_up_required: true } },
227
+ requiredIf: { equals: { follow_up_required: true } },
228
+ }
397
229
  ```
398
230
 
399
- You can use this for both section and field IDs when building templates dynamically.
400
-
401
- ---
402
-
403
- ## Legacy schema migration
231
+ Behavior:
404
232
 
405
- If you had older templates that:
233
+ * If `follow_up_required = false` β†’ field is **hidden** and **ignored**
234
+ * If `true` β†’ field becomes **required**
406
235
 
407
- * Used a flat `fields` array instead of `sections`, or
408
- * Used older field type names (`text`, `textarea`, `dropdown`, `multiselect`, …)
236
+ ---
409
237
 
410
- you can pass them through the migration pipeline:
238
+ # πŸ” Repeat Group Example
411
239
 
412
240
  ```ts
413
- import {
414
- migrateLegacySchema,
415
- parseReportTemplateSchema,
416
- } from "@frt-platform/report-core";
417
-
418
- const legacy = {
419
- title: "Old template",
420
- description: "Using flat fields",
241
+ {
242
+ id: "injured",
243
+ type: "repeatGroup",
244
+ label: "Injured people",
245
+ min: 1,
246
+ max: 5,
421
247
  fields: [
422
- { id: "summary", type: "textarea", label: "Summary" },
423
- { id: "status", type: "dropdown", label: "Status", options: ["Open", "Closed"] },
424
- ],
425
- };
426
-
427
- const migrated = migrateLegacySchema(legacy);
428
- const parsed = parseReportTemplateSchema(migrated);
429
-
430
- console.log(parsed.sections.length); // 1
431
- console.log(parsed.sections[0].fields[0].type); // "longText"
248
+ { id: "name", type: "shortText", label: "Name", required: true },
249
+ { id: "injury", type: "longText", label: "Injury description" }
250
+ ]
251
+ }
432
252
  ```
433
253
 
434
- You don’t normally need to call `migrateLegacySchema` yourself – `parseReportTemplateSchema` already does:
254
+ Response shape:
435
255
 
436
256
  ```ts
437
- // internally:
438
- const migrated = migrateLegacySchema(raw);
439
- const parsed = ReportTemplateSchemaValidator.parse(migrated);
257
+ injured: Array<{ name: string; injury?: string }>;
440
258
  ```
441
259
 
442
260
  ---
443
261
 
444
- ## Diffing templates
445
-
446
- When you version templates (e.g. editing in a UI), you might want to see what changed between versions. The core exposes a diff utility:
262
+ # 🧩 Field Registry (Custom Types)
447
263
 
448
264
  ```ts
449
- import { diffTemplates } from "@frt-platform/report-core";
450
-
451
- const beforeTemplate = /* old template */;
452
- const afterTemplate = /* updated template */;
453
-
454
- const diff = diffTemplates(beforeTemplate, afterTemplate);
455
-
456
- console.log(diff.addedSections);
457
- console.log(diff.removedSections);
458
- console.log(diff.reorderedSections);
459
- console.log(diff.modifiedSections);
265
+ import { FieldRegistry } from "@frt-platform/report-core";
266
+ import { z } from "zod";
460
267
 
461
- console.log(diff.addedFields);
462
- console.log(diff.removedFields);
463
- console.log(diff.reorderedFields);
464
- console.log(diff.modifiedFields);
268
+ FieldRegistry.register("richText", {
269
+ defaults: { label: "Details" },
270
+ buildResponseSchema(field) {
271
+ let schema = z.string();
272
+ if (field.minLength) schema = schema.min(field.minLength);
273
+ return field.required ? schema : schema.optional();
274
+ },
275
+ });
465
276
  ```
466
277
 
467
- `diff.modifiedFields` contains entries like:
278
+ Now templates may include fields like:
468
279
 
469
280
  ```ts
470
- {
471
- sectionId: "section-overview",
472
- fieldId: "summary",
473
- before: { label: "What happened?", required: false, ... },
474
- after: { label: "What happened exactly?", required: true, ... },
475
- changes: [
476
- { key: "label", before: "What happened?", after: "What happened exactly?" },
477
- { key: "required", before: false, after: true },
478
- ],
479
- }
281
+ { id: "body", type: "richText", label: "Report body" }
480
282
  ```
481
283
 
482
- Use this for:
483
-
484
- * β€œChanges” tabs in your UI
485
- * Migration & compatibility warnings
486
- * Audit trails
487
- * Showing ordered changes (section/field reorder is explicitly tracked)
488
-
489
284
  ---
490
285
 
491
- ## JSON Schema export
492
-
493
- You can export a template as a **JSON Schema** object for use with OpenAPI, Swagger, Postman, or other runtimes that don’t speak TypeScript/Zod.
286
+ # 🧾 JSON Schema Export
494
287
 
495
288
  ```ts
496
289
  import { exportJSONSchema } from "@frt-platform/report-core";
@@ -498,198 +291,124 @@ import { exportJSONSchema } from "@frt-platform/report-core";
498
291
  const jsonSchema = exportJSONSchema(template);
499
292
  ```
500
293
 
501
- Shape (simplified):
294
+ Produces JSON Schema with:
502
295
 
503
- ```json
504
- {
505
- "$schema": "https://json-schema.org/draft/2020-12/schema",
506
- "type": "object",
507
- "properties": {
508
- "summary": { "type": "string", "minLength": 10 },
509
- "status": { "type": "string", "enum": ["Pending", "Resolved", "Escalated"] }
510
- },
511
- "required": ["summary", "status"],
512
- "additionalProperties": false,
513
- "x-frt-templateTitle": "Incident follow-up",
514
- "x-frt-version": 1
515
- }
516
- ```
517
-
518
- Key points:
519
-
520
- * String/number/boolean/array mapping matches the runtime validation semantics.
521
- * `required` only includes **unconditionally required** fields (no `visibleIf` / `requiredIf`), but we expose conditional logic as vendor extensions.
522
- * Extra metadata is available under `x-frt-*` keys, e.g. `x-frt-placeholder`, `x-frt-dataClassification`, `x-frt-visibleIf`, `x-frt-requiredIf`.
296
+ * field types
297
+ * enums
298
+ * min/max constraints
299
+ * default values
300
+ * conditional logic preserved as custom `x-frt-*` properties
523
301
 
524
302
  ---
525
303
 
526
- ## API Overview
527
-
528
- ### Types
529
-
530
- ```ts
531
- import type {
532
- ReportTemplateSchema,
533
- ReportTemplateSection,
534
- ReportTemplateField,
535
- ReportTemplateFieldType,
536
- InferResponse,
537
- } from "@frt-platform/report-core";
538
- ```
539
-
540
- * `ReportTemplateFieldType` – union of all field type strings
541
- * `ReportTemplateField` – one question/field in a section
542
- * `ReportTemplateSection` – a logical grouping of fields
543
- * `ReportTemplateSchema` – full template
544
- * `InferResponse<TTemplate>` – builds a typed response object from a static template
545
-
546
- ### Constants
304
+ # πŸ” Diff Templates
547
305
 
548
306
  ```ts
549
- import {
550
- REPORT_TEMPLATE_VERSION,
551
- REPORT_TEMPLATE_FIELD_TYPES,
552
- DEFAULT_FIELD_LABEL,
553
- CORE_FIELD_DEFAULTS,
554
- } from "@frt-platform/report-core";
555
- ```
556
-
557
- * `REPORT_TEMPLATE_VERSION: number`
558
- * `REPORT_TEMPLATE_FIELD_TYPES: readonly ReportTemplateFieldType[]`
559
- * `DEFAULT_FIELD_LABEL: string`
560
- * `CORE_FIELD_DEFAULTS: Record<ReportTemplateFieldType, Record<string, unknown>>`
561
-
562
- ### Validation & parsing
307
+ import { diffTemplates } from "@frt-platform/report-core";
563
308
 
564
- ```ts
565
- import {
566
- ReportTemplateFieldSchema,
567
- ReportTemplateSectionSchema,
568
- ReportTemplateSchemaValidator,
569
- parseReportTemplateSchema,
570
- parseReportTemplateSchemaFromString,
571
- normalizeReportTemplateSchema,
572
- serializeReportTemplateSchema,
573
- } from "@frt-platform/report-core";
309
+ const diff = diffTemplates(oldTemplate, newTemplate);
574
310
  ```
575
311
 
576
- Use the Zod schemas directly if you want:
577
-
578
- ```ts
579
- import { ReportTemplateSchemaValidator } from "@frt-platform/report-core";
580
-
581
- const result = ReportTemplateSchemaValidator.safeParse(raw);
582
- if (!result.success) {
583
- // handle validation errors
584
- }
585
- ```
312
+ Detects:
586
313
 
587
- ### Migration
314
+ * added/removed/reordered sections
315
+ * added/removed/reordered fields
316
+ * modified fields
317
+ * nested diffs for repeat groups
588
318
 
589
- ```ts
590
- import { migrateLegacySchema } from "@frt-platform/report-core";
591
- ```
319
+ Perfect for:
592
320
 
593
- Takes `unknown` legacy input and returns a shape better suited for current schema validation.
321
+ * Version history
322
+ * Audit logs
323
+ * Template editing UI
594
324
 
595
- ### Response validation
325
+ ---
596
326
 
597
- ```ts
598
- import {
599
- validateReportResponse,
600
- buildResponseSchemaWithConditions,
601
- type InferResponse,
602
- } from "@frt-platform/report-core";
603
- ```
327
+ # 🧬 Type Inference
604
328
 
605
- ### Template diffing
329
+ Given a template:
606
330
 
607
331
  ```ts
608
- import { diffTemplates } from "@frt-platform/report-core";
332
+ export const myTemplate = {
333
+ version: 1,
334
+ sections: [
335
+ {
336
+ id: "s",
337
+ fields: [
338
+ { id: "title", type: "shortText", required: true },
339
+ { id: "tags", type: "multiSelect", options: ["A", "B"] },
340
+ ]
341
+ }
342
+ ]
343
+ } as const;
609
344
  ```
610
345
 
611
- ### JSON Schema export
346
+ Infer response type:
612
347
 
613
348
  ```ts
614
- import { exportJSONSchema } from "@frt-platform/report-core";
349
+ type MyResponse = InferResponse<typeof myTemplate>;
615
350
  ```
616
351
 
617
- ### Field registry
352
+ Produces:
618
353
 
619
354
  ```ts
620
- import { FieldRegistry } from "@frt-platform/report-core";
355
+ type MyResponse = {
356
+ title: string;
357
+ tags?: ("A" | "B")[];
358
+ };
621
359
  ```
622
360
 
623
361
  ---
624
362
 
625
- ## Integration Ideas
626
-
627
- This package is intentionally minimal. You can layer it into:
363
+ # 🧾 Serialization
628
364
 
629
- * **Next.js / React** apps for:
630
-
631
- * Template builders
632
- * Dynamic report forms
633
- * Admin panels for managing report templates
634
- * **Node/Edge backends** for:
635
-
636
- * Validating templates on save
637
- * Migrating old template shapes
638
- * Ensuring consistent data before persisting to a DB
639
- * Validating report responses before saving them
640
- * Serving JSON Schema to other services
641
-
642
- Typical pattern in a full stack app:
643
-
644
- 1. **Frontend**
645
-
646
- * Build a visual editor for templates
647
- * Send template JSON to server
648
-
649
- 2. **Backend**
365
+ ```ts
366
+ import { serializeReportTemplateSchema } from "@frt-platform/report-core";
650
367
 
651
- * `parseReportTemplateSchema` to validate templates
652
- * Store `ReportTemplateSchema` JSON in your DB (e.g. Mongo via Prisma)
653
- * On response submission:
368
+ const json = serializeReportTemplateSchema(template, {
369
+ pretty: true,
370
+ sortSectionsById: true,
371
+ sortFieldsById: true,
372
+ });
373
+ ```
654
374
 
655
- * Load template from DB
656
- * `validateReportResponse(template, data)` before insert
375
+ Useful for deterministic output in Git.
657
376
 
658
- 3. **Runtime**
377
+ ---
659
378
 
660
- * Load templates from DB
661
- * Use them to render dynamic forms
662
- * Use `validateReportResponse` / `buildResponseSchemaWithConditions` to validate and normalize responses
663
- * Use `exportJSONSchema` for interoperability with other stacks
379
+ # 🧱 Roadmap
664
380
 
665
- ---
381
+ ### Phase 1 β€” Core Maturation (βœ”οΈ COMPLETE)
666
382
 
667
- ## Roadmap / Extensions (ideas)
383
+ * Validation
384
+ * Conditional logic
385
+ * Diffing
386
+ * Field Registry
387
+ * Error helpers
388
+ * Serialization features
668
389
 
669
- Things you might add on top (in a separate package, e.g. `@frt-platform/report-react`):
390
+ ### Phase 2 β€” Advanced Field System (IN PROGRESS)
670
391
 
671
- * React components for:
392
+ * Repeat group conditional rules (`minIf` / `maxIf`)
393
+ * Conditional logic inside group rows
394
+ * Computed fields (design)
395
+ * RichText / FileUpload via registry
672
396
 
673
- * Template builder UI
674
- * Field editors/renderers (short text, selects, etc.)
675
- * Auto-generated `<ReportForm />` that consumes a template
676
- * More advanced field types:
397
+ ### Phase 3 β€” Reactions & Analytics (Planned)
677
398
 
678
- * File uploads
679
- * Signature pads
680
- * Matrix / grid fields
681
- * Fully-featured `repeatGroup` UI & runtime
682
- * Richer conditional logic:
399
+ * Scoring rules
400
+ * Auto-tagging
401
+ * Suggested outcomes
683
402
 
684
- * `visibleIf`
685
- * `requiredIf`
686
- * multi-step branching
687
- * Multi-language labels & descriptions
403
+ ### Phase 4 β€” React UI Package (Planned)
688
404
 
689
- The core stays simple: **schema + validation + conditional logic + migration + diffing + registry + JSON Schema export**.
405
+ * Form renderer
406
+ * Template builder
407
+ * Field palette
408
+ * Full ShadCN integration
690
409
 
691
410
  ---
692
411
 
693
- ## License
412
+ # πŸ“„ License
694
413
 
695
- MIT – use it in your projects, commercial or not. Contributions welcome.
414
+ MIT β€” feel free to use, extend, or contribute.