@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/README.md CHANGED
@@ -1,14 +1,23 @@
1
1
  # @frt/report-core
2
2
 
3
- Core engine for building, validating, and migrating **report templates**.
3
+ Core engine for building, validating, and migrating **report templates** – plus validating **report responses**.
4
4
 
5
- This package is framework-agnostic, React-free, and storage-agnostic. It gives you:
5
+ This package is:
6
6
 
7
- - A **typed schema** for report templates (templates → sections → fields)
8
- - **Zod-based validation** and parsing from unknown / JSON input
9
- - **Legacy schema migration** (flat `fields` → `sections`, old type names, etc.)
10
- - **Normalization** helpers to ensure safe IDs & consistent structure
11
- - **Field defaults** and **ID utilities** you can use to build your own UI
7
+ * **Framework-agnostic** (no React)
8
+ * **Storage-agnostic** (no DB assumptions)
9
+ * **Runtime-safe** (Zod-based validation)
10
+
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 (`buildResponseSchema`, `validateReportResponse`)
19
+ * **Template diffing** (`diffTemplates`) to compare template versions
20
+ * **Type-level response inference** (`InferResponse`) for fully typed responses
12
21
 
13
22
  It’s the core that powers a flexible incident/report builder, but it’s generic enough to be reused in any app.
14
23
 
@@ -25,7 +34,7 @@ yarn add @frt/report-core zod
25
34
 
26
35
  # pnpm
27
36
  pnpm add @frt/report-core zod
28
- ````
37
+ ```
29
38
 
30
39
  `zod` is a peer dependency and is used for schema validation & parsing.
31
40
 
@@ -81,7 +90,7 @@ const templateJson: ReportTemplateSchema = {
81
90
  type: "longText",
82
91
  label: "What happened?",
83
92
  required: true,
84
- minLength: 10
93
+ minLength: 10,
85
94
  },
86
95
  {
87
96
  id: "status",
@@ -89,22 +98,22 @@ const templateJson: ReportTemplateSchema = {
89
98
  label: "Incident status",
90
99
  required: true,
91
100
  options: ["Pending review", "Resolved", "Escalated"],
92
- defaultValue: "Resolved"
93
- }
94
- ]
95
- }
96
- ]
101
+ defaultValue: "Resolved",
102
+ },
103
+ ],
104
+ },
105
+ ],
97
106
  };
98
107
  ```
99
108
 
100
- ### 2. Validate and normalize it
109
+ ### 2. Validate and normalize the template
101
110
 
102
111
  Use the helpers to safely parse and normalize unknown input:
103
112
 
104
113
  ```ts
105
114
  import {
106
115
  parseReportTemplateSchema,
107
- normalizeReportTemplateSchema
116
+ normalizeReportTemplateSchema,
108
117
  } from "@frt/report-core";
109
118
 
110
119
  const raw = /* from DB, file, API, etc. */ templateJson;
@@ -147,6 +156,119 @@ try {
147
156
 
148
157
  ---
149
158
 
159
+ ## Validating report responses
160
+
161
+ Once you have a template, you can validate **user-submitted responses** against it.
162
+
163
+ ### Runtime schema for responses
164
+
165
+ ```ts
166
+ import {
167
+ buildResponseSchema,
168
+ validateReportResponse,
169
+ } from "@frt/report-core";
170
+
171
+ const template = /* a valid ReportTemplateSchema */ normalized;
172
+
173
+ // Build a Zod schema for responses
174
+ const responseSchema = buildResponseSchema(template);
175
+
176
+ // Example response object
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 = responseSchema.parse(responseData);
184
+
185
+ // Or use the convenience helper:
186
+ const safeResponse2 = validateReportResponse(template, responseData);
187
+ ```
188
+
189
+ `buildResponseSchema` respects:
190
+
191
+ * `required` flags
192
+ * `minLength` / `maxLength` (short/long text)
193
+ * `minValue` / `maxValue` (numbers)
194
+ * `minSelections` / `maxSelections` (multiSelect)
195
+ * `options` (singleSelect + multiSelect)
196
+ * `checkbox` semantics (required checkbox must be `true`)
197
+
198
+ ---
199
+
200
+ ## Type-level response inference (`InferResponse`)
201
+
202
+ If you define templates statically (`as const`), you can get a fully-typed response type from the template:
203
+
204
+ ```ts
205
+ import type { InferResponse } from "@frt/report-core";
206
+
207
+ const incidentTemplate = {
208
+ version: 1,
209
+ sections: [
210
+ {
211
+ id: "section-overview",
212
+ fields: [
213
+ {
214
+ id: "summary",
215
+ type: "longText",
216
+ label: "What happened?",
217
+ required: true,
218
+ minLength: 10,
219
+ },
220
+ {
221
+ id: "status",
222
+ type: "singleSelect",
223
+ label: "Status",
224
+ required: true,
225
+ options: ["Pending", "Resolved", "Escalated"] as const,
226
+ },
227
+ {
228
+ id: "follow_up_date",
229
+ type: "date",
230
+ label: "Follow-up date",
231
+ },
232
+ {
233
+ id: "tags",
234
+ type: "multiSelect",
235
+ label: "Tags",
236
+ options: ["Safety", "Medical", "Security"] as const,
237
+ },
238
+ {
239
+ id: "confirmed",
240
+ type: "checkbox",
241
+ label: "I confirm this report is accurate",
242
+ required: true,
243
+ },
244
+ ],
245
+ },
246
+ ],
247
+ } as const;
248
+
249
+ type IncidentResponse = InferResponse<typeof incidentTemplate>;
250
+ ```
251
+
252
+ `IncidentResponse` will be inferred as:
253
+
254
+ ```ts
255
+ type IncidentResponse = {
256
+ summary: string; // required
257
+ status: "Pending" | "Resolved" | "Escalated"; // required
258
+ follow_up_date?: string; // optional
259
+ tags?: ("Safety" | "Medical" | "Security")[]; // optional
260
+ confirmed: boolean; // required checkbox
261
+ };
262
+ ```
263
+
264
+ This is extremely useful for:
265
+
266
+ * API handlers (`(body: IncidentResponse) => { ... }`)
267
+ * DB layers that store `data` blobs
268
+ * Frontend forms with type-safe state
269
+
270
+ ---
271
+
150
272
  ## Using field defaults
151
273
 
152
274
  The package includes core defaults for each field type (no UI, no icons):
@@ -155,7 +277,7 @@ The package includes core defaults for each field type (no UI, no icons):
155
277
  import {
156
278
  CORE_FIELD_DEFAULTS,
157
279
  DEFAULT_FIELD_LABEL,
158
- type ReportTemplateFieldType
280
+ type ReportTemplateFieldType,
159
281
  } from "@frt/report-core";
160
282
 
161
283
  function createDefaultField(type: ReportTemplateFieldType) {
@@ -164,7 +286,7 @@ function createDefaultField(type: ReportTemplateFieldType) {
164
286
  id: "your-id-here",
165
287
  type,
166
288
  label: (defaults.label as string) ?? DEFAULT_FIELD_LABEL,
167
- ...defaults
289
+ ...defaults,
168
290
  };
169
291
  }
170
292
 
@@ -203,7 +325,7 @@ you can pass them through the migration pipeline:
203
325
  ```ts
204
326
  import {
205
327
  migrateLegacySchema,
206
- parseReportTemplateSchema
328
+ parseReportTemplateSchema,
207
329
  } from "@frt/report-core";
208
330
 
209
331
  const legacy = {
@@ -211,8 +333,8 @@ const legacy = {
211
333
  description: "Using flat fields",
212
334
  fields: [
213
335
  { id: "summary", type: "textarea", label: "Summary" },
214
- { id: "status", type: "dropdown", label: "Status", options: ["Open", "Closed"] }
215
- ]
336
+ { id: "status", type: "dropdown", label: "Status", options: ["Open", "Closed"] },
337
+ ],
216
338
  };
217
339
 
218
340
  const migrated = migrateLegacySchema(legacy);
@@ -232,6 +354,47 @@ const parsed = ReportTemplateSchemaValidator.parse(migrated);
232
354
 
233
355
  ---
234
356
 
357
+ ## Diffing templates
358
+
359
+ When you version templates (e.g. editing in a UI), you might want to see what changed between versions. The core exposes a simple diff utility:
360
+
361
+ ```ts
362
+ import { diffTemplates } from "@frt/report-core";
363
+
364
+ const beforeTemplate = /* old template */;
365
+ const afterTemplate = /* updated template */;
366
+
367
+ const diff = diffTemplates(beforeTemplate, afterTemplate);
368
+
369
+ console.log(diff.addedSections);
370
+ console.log(diff.removedSections);
371
+ console.log(diff.fieldChanges);
372
+ ```
373
+
374
+ `diff.fieldChanges` contains entries like:
375
+
376
+ ```ts
377
+ {
378
+ type: "modified",
379
+ sectionId: "section-overview",
380
+ fieldId: "summary",
381
+ before: { label: "What happened?", required: false, ... },
382
+ after: { label: "What happened exactly?", required: true, ... },
383
+ changes: [
384
+ { key: "label", before: "What happened?", after: "What happened exactly?" },
385
+ { key: "required", before: false, after: true },
386
+ ],
387
+ }
388
+ ```
389
+
390
+ Use this for:
391
+
392
+ * “Changes” tabs in your UI
393
+ * Migration & compatibility warnings
394
+ * Audit trails
395
+
396
+ ---
397
+
235
398
  ## API Overview
236
399
 
237
400
  ### Types
@@ -241,7 +404,8 @@ import type {
241
404
  ReportTemplateSchema,
242
405
  ReportTemplateSection,
243
406
  ReportTemplateField,
244
- ReportTemplateFieldType
407
+ ReportTemplateFieldType,
408
+ InferResponse,
245
409
  } from "@frt/report-core";
246
410
  ```
247
411
 
@@ -249,6 +413,7 @@ import type {
249
413
  * `ReportTemplateField` – one question/field in a section
250
414
  * `ReportTemplateSection` – a logical grouping of fields
251
415
  * `ReportTemplateSchema` – full template
416
+ * `InferResponse<TTemplate>` – builds a typed response object from a static template
252
417
 
253
418
  ### Constants
254
419
 
@@ -257,7 +422,7 @@ import {
257
422
  REPORT_TEMPLATE_VERSION,
258
423
  REPORT_TEMPLATE_FIELD_TYPES,
259
424
  DEFAULT_FIELD_LABEL,
260
- CORE_FIELD_DEFAULTS
425
+ CORE_FIELD_DEFAULTS,
261
426
  } from "@frt/report-core";
262
427
  ```
263
428
 
@@ -276,7 +441,7 @@ import {
276
441
  parseReportTemplateSchema,
277
442
  parseReportTemplateSchemaFromString,
278
443
  normalizeReportTemplateSchema,
279
- serializeReportTemplateSchema
444
+ serializeReportTemplateSchema,
280
445
  } from "@frt/report-core";
281
446
  ```
282
447
 
@@ -299,10 +464,20 @@ import { migrateLegacySchema } from "@frt/report-core";
299
464
 
300
465
  Takes `unknown` legacy input and returns a shape better suited for current schema validation.
301
466
 
302
- ### Utilities
467
+ ### Response validation
303
468
 
304
469
  ```ts
305
- import { createUniqueId } from "@frt/report-core";
470
+ import {
471
+ buildResponseSchema,
472
+ validateReportResponse,
473
+ type InferResponse,
474
+ } from "@frt/report-core";
475
+ ```
476
+
477
+ ### Template diffing
478
+
479
+ ```ts
480
+ import { diffTemplates } from "@frt/report-core";
306
481
  ```
307
482
 
308
483
  ---
@@ -321,23 +496,29 @@ This package is intentionally minimal. You can layer it into:
321
496
  * Validating templates on save
322
497
  * Migrating old template shapes
323
498
  * Ensuring consistent data before persisting to a DB
499
+ * Validating report responses before saving them
324
500
 
325
501
  Typical pattern in a full stack app:
326
502
 
327
- 1. **Frontend**:
503
+ 1. **Frontend**
328
504
 
329
505
  * Build a visual editor for templates
330
506
  * Send template JSON to server
331
507
 
332
- 2. **Backend**:
508
+ 2. **Backend**
333
509
 
334
- * `parseReportTemplateSchema` to validate
510
+ * `parseReportTemplateSchema` to validate templates
335
511
  * Store `ReportTemplateSchema` JSON in your DB (e.g. Mongo via Prisma)
512
+ * On response submission:
336
513
 
337
- 3. **Runtime**:
514
+ * Load template from DB
515
+ * `validateReportResponse(template, data)` before insert
516
+
517
+ 3. **Runtime**
338
518
 
339
519
  * Load templates from DB
340
- * Use them to render dynamic forms and validate responses (you can build a separate response schema per field, if you want).
520
+ * Use them to render dynamic forms
521
+ * Use `buildResponseSchema` to validate and normalize responses
341
522
 
342
523
  ---
343
524
 
@@ -349,15 +530,19 @@ Things you might add on top (in a separate package, e.g. `@frt/report-react`):
349
530
 
350
531
  * Template builder UI
351
532
  * Field editors/renderers (short text, selects, etc.)
352
- * Response schemas built from templates
533
+ * Auto-generated `<ReportForm />` that consumes a template
353
534
  * More advanced field types:
354
535
 
355
536
  * File uploads
356
537
  * Signature pads
357
538
  * Matrix / grid fields
539
+ * Conditional logic:
540
+
541
+ * `visibleIf`
542
+ * `requiredIf`
358
543
  * Multi-language labels & descriptions
359
544
 
360
- The core stays simple: schema + validation + migration.
545
+ The core stays simple: **schema + validation + migration + response validation + diffing**.
361
546
 
362
547
  ---
363
548