@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 +218 -33
- package/dist/index.d.mts +211 -298
- package/dist/index.d.ts +211 -298
- package/dist/index.js +647 -43
- package/dist/index.mjs +637 -40
- package/package.json +1 -1
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
|
|
5
|
+
This package is:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
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
|
-
###
|
|
467
|
+
### Response validation
|
|
303
468
|
|
|
304
469
|
```ts
|
|
305
|
-
import {
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|