@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/README.md +382 -53
- 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,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@frt-platform/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** and exporting **JSON Schema**.
|
|
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 (`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
|
|
12
24
|
|
|
13
25
|
It’s the core that powers a flexible incident/report builder, but it’s generic enough to be reused in any app.
|
|
14
26
|
|
|
@@ -18,14 +30,14 @@ It’s the core that powers a flexible incident/report builder, but it’s gener
|
|
|
18
30
|
|
|
19
31
|
```bash
|
|
20
32
|
# npm
|
|
21
|
-
npm install @frt/report-core zod
|
|
33
|
+
npm install @frt-platform/report-core zod
|
|
22
34
|
|
|
23
35
|
# yarn
|
|
24
|
-
yarn add @frt/report-core zod
|
|
36
|
+
yarn add @frt-platform/report-core zod
|
|
25
37
|
|
|
26
38
|
# pnpm
|
|
27
|
-
pnpm add @frt/report-core zod
|
|
28
|
-
|
|
39
|
+
pnpm add @frt-platform/report-core zod
|
|
40
|
+
```
|
|
29
41
|
|
|
30
42
|
`zod` is a peer dependency and is used for schema validation & parsing.
|
|
31
43
|
|
|
@@ -38,11 +50,12 @@ The core model looks like this:
|
|
|
38
50
|
* A **template** contains multiple **sections**
|
|
39
51
|
* A **section** contains multiple **fields**
|
|
40
52
|
* A **field** has a `type` (short text, long text, number, select, etc.) and optional constraints
|
|
53
|
+
* Fields can have **conditional logic** (`visibleIf`, `requiredIf`)
|
|
41
54
|
|
|
42
55
|
Supported field types:
|
|
43
56
|
|
|
44
57
|
```ts
|
|
45
|
-
import { REPORT_TEMPLATE_FIELD_TYPES } from "@frt/report-core";
|
|
58
|
+
import { REPORT_TEMPLATE_FIELD_TYPES } from "@frt-platform/report-core";
|
|
46
59
|
|
|
47
60
|
console.log(REPORT_TEMPLATE_FIELD_TYPES);
|
|
48
61
|
// [
|
|
@@ -53,9 +66,12 @@ console.log(REPORT_TEMPLATE_FIELD_TYPES);
|
|
|
53
66
|
// "checkbox",
|
|
54
67
|
// "singleSelect",
|
|
55
68
|
// "multiSelect",
|
|
69
|
+
// "repeatGroup", // reserved for repeating groups / fieldsets
|
|
56
70
|
// ]
|
|
57
71
|
```
|
|
58
72
|
|
|
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.
|
|
74
|
+
|
|
59
75
|
---
|
|
60
76
|
|
|
61
77
|
## Quick Start
|
|
@@ -65,7 +81,7 @@ console.log(REPORT_TEMPLATE_FIELD_TYPES);
|
|
|
65
81
|
You can define templates statically or load them from DB / API:
|
|
66
82
|
|
|
67
83
|
```ts
|
|
68
|
-
import type { ReportTemplateSchema } from "@frt/report-core";
|
|
84
|
+
import type { ReportTemplateSchema } from "@frt-platform/report-core";
|
|
69
85
|
|
|
70
86
|
const templateJson: ReportTemplateSchema = {
|
|
71
87
|
version: 1,
|
|
@@ -81,7 +97,7 @@ const templateJson: ReportTemplateSchema = {
|
|
|
81
97
|
type: "longText",
|
|
82
98
|
label: "What happened?",
|
|
83
99
|
required: true,
|
|
84
|
-
minLength: 10
|
|
100
|
+
minLength: 10,
|
|
85
101
|
},
|
|
86
102
|
{
|
|
87
103
|
id: "status",
|
|
@@ -89,30 +105,30 @@ const templateJson: ReportTemplateSchema = {
|
|
|
89
105
|
label: "Incident status",
|
|
90
106
|
required: true,
|
|
91
107
|
options: ["Pending review", "Resolved", "Escalated"],
|
|
92
|
-
defaultValue: "Resolved"
|
|
93
|
-
}
|
|
94
|
-
]
|
|
95
|
-
}
|
|
96
|
-
]
|
|
108
|
+
defaultValue: "Resolved",
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
],
|
|
97
113
|
};
|
|
98
114
|
```
|
|
99
115
|
|
|
100
|
-
### 2. Validate and normalize
|
|
116
|
+
### 2. Validate and normalize the template
|
|
101
117
|
|
|
102
118
|
Use the helpers to safely parse and normalize unknown input:
|
|
103
119
|
|
|
104
120
|
```ts
|
|
105
121
|
import {
|
|
106
122
|
parseReportTemplateSchema,
|
|
107
|
-
normalizeReportTemplateSchema
|
|
108
|
-
} from "@frt/report-core";
|
|
123
|
+
normalizeReportTemplateSchema,
|
|
124
|
+
} from "@frt-platform/report-core";
|
|
109
125
|
|
|
110
126
|
const raw = /* from DB, file, API, etc. */ templateJson;
|
|
111
127
|
|
|
112
128
|
// 1) parse + validate (throws on invalid input)
|
|
113
129
|
const parsed = parseReportTemplateSchema(raw);
|
|
114
130
|
|
|
115
|
-
// 2) optional extra normalization step (IDs, trimming, etc.)
|
|
131
|
+
// 2) optional extra normalization step (IDs, trimming, de-duplication, etc.)
|
|
116
132
|
const normalized = normalizeReportTemplateSchema(parsed);
|
|
117
133
|
|
|
118
134
|
console.log(normalized.sections[0].fields[0].id);
|
|
@@ -122,7 +138,7 @@ console.log(normalized.sections[0].fields[0].id);
|
|
|
122
138
|
### 3. Serialize for storage
|
|
123
139
|
|
|
124
140
|
```ts
|
|
125
|
-
import { serializeReportTemplateSchema } from "@frt/report-core";
|
|
141
|
+
import { serializeReportTemplateSchema } from "@frt-platform/report-core";
|
|
126
142
|
|
|
127
143
|
const jsonString = serializeReportTemplateSchema(normalized);
|
|
128
144
|
// ready to store in DB, file, etc.
|
|
@@ -135,7 +151,7 @@ const jsonString = serializeReportTemplateSchema(normalized);
|
|
|
135
151
|
If you have a JSON string (e.g. from a form textarea):
|
|
136
152
|
|
|
137
153
|
```ts
|
|
138
|
-
import { parseReportTemplateSchemaFromString } from "@frt/report-core";
|
|
154
|
+
import { parseReportTemplateSchemaFromString } from "@frt-platform/report-core";
|
|
139
155
|
|
|
140
156
|
try {
|
|
141
157
|
const template = parseReportTemplateSchemaFromString(userInputString);
|
|
@@ -147,6 +163,152 @@ try {
|
|
|
147
163
|
|
|
148
164
|
---
|
|
149
165
|
|
|
166
|
+
## Validating report responses
|
|
167
|
+
|
|
168
|
+
Once you have a template, you can validate **user-submitted responses** against it.
|
|
169
|
+
|
|
170
|
+
### Runtime validation
|
|
171
|
+
|
|
172
|
+
```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);
|
|
184
|
+
```
|
|
185
|
+
|
|
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
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Conditional logic (`visibleIf` / `requiredIf`)
|
|
211
|
+
|
|
212
|
+
Fields can declare conditions that refer to other field values.
|
|
213
|
+
|
|
214
|
+
Example:
|
|
215
|
+
|
|
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
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
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.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Type-level response inference (`InferResponse`)
|
|
241
|
+
|
|
242
|
+
If you define templates statically (`as const`), you can get a fully-typed response type from the template:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import type { InferResponse } from "@frt-platform/report-core";
|
|
246
|
+
|
|
247
|
+
const incidentTemplate = {
|
|
248
|
+
version: 1,
|
|
249
|
+
sections: [
|
|
250
|
+
{
|
|
251
|
+
id: "section-overview",
|
|
252
|
+
fields: [
|
|
253
|
+
{
|
|
254
|
+
id: "summary",
|
|
255
|
+
type: "longText",
|
|
256
|
+
label: "What happened?",
|
|
257
|
+
required: true,
|
|
258
|
+
minLength: 10,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: "status",
|
|
262
|
+
type: "singleSelect",
|
|
263
|
+
label: "Status",
|
|
264
|
+
required: true,
|
|
265
|
+
options: ["Pending", "Resolved", "Escalated"] as const,
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: "follow_up_date",
|
|
269
|
+
type: "date",
|
|
270
|
+
label: "Follow-up date",
|
|
271
|
+
},
|
|
272
|
+
{
|
|
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,
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
],
|
|
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
|
+
};
|
|
302
|
+
```
|
|
303
|
+
|
|
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
|
+
---
|
|
311
|
+
|
|
150
312
|
## Using field defaults
|
|
151
313
|
|
|
152
314
|
The package includes core defaults for each field type (no UI, no icons):
|
|
@@ -155,8 +317,8 @@ The package includes core defaults for each field type (no UI, no icons):
|
|
|
155
317
|
import {
|
|
156
318
|
CORE_FIELD_DEFAULTS,
|
|
157
319
|
DEFAULT_FIELD_LABEL,
|
|
158
|
-
type ReportTemplateFieldType
|
|
159
|
-
} from "@frt/report-core";
|
|
320
|
+
type ReportTemplateFieldType,
|
|
321
|
+
} from "@frt-platform/report-core";
|
|
160
322
|
|
|
161
323
|
function createDefaultField(type: ReportTemplateFieldType) {
|
|
162
324
|
const defaults = CORE_FIELD_DEFAULTS[type] ?? {};
|
|
@@ -164,23 +326,70 @@ function createDefaultField(type: ReportTemplateFieldType) {
|
|
|
164
326
|
id: "your-id-here",
|
|
165
327
|
type,
|
|
166
328
|
label: (defaults.label as string) ?? DEFAULT_FIELD_LABEL,
|
|
167
|
-
...defaults
|
|
329
|
+
...defaults,
|
|
168
330
|
};
|
|
169
331
|
}
|
|
170
332
|
|
|
171
333
|
const field = createDefaultField("shortText");
|
|
172
334
|
```
|
|
173
335
|
|
|
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
|
+
|
|
174
341
|
This is especially useful in a form builder UI when the user adds a new field of a given type.
|
|
175
342
|
|
|
176
343
|
---
|
|
177
344
|
|
|
345
|
+
## Field Registry (`FieldRegistry`)
|
|
346
|
+
|
|
347
|
+
You can register **custom field types** or override core types at runtime:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
import { FieldRegistry } from "@frt-platform/report-core";
|
|
351
|
+
import { z } from "zod";
|
|
352
|
+
|
|
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
|
+
},
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
At validation time, `validateReportResponse` and `buildBaseFieldSchema` will:
|
|
375
|
+
|
|
376
|
+
1. Check the registry for a custom handler.
|
|
377
|
+
2. Use the built-in fallback if none is registered.
|
|
378
|
+
|
|
379
|
+
Use this for:
|
|
380
|
+
|
|
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
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
178
387
|
## Generating unique IDs
|
|
179
388
|
|
|
180
389
|
The core package exposes a generic `createUniqueId` helper:
|
|
181
390
|
|
|
182
391
|
```ts
|
|
183
|
-
import { createUniqueId } from "@frt/report-core";
|
|
392
|
+
import { createUniqueId } from "@frt-platform/report-core";
|
|
184
393
|
|
|
185
394
|
const existingSectionIds = ["section-1", "section-2"];
|
|
186
395
|
const newId = createUniqueId("section", existingSectionIds);
|
|
@@ -203,16 +412,16 @@ you can pass them through the migration pipeline:
|
|
|
203
412
|
```ts
|
|
204
413
|
import {
|
|
205
414
|
migrateLegacySchema,
|
|
206
|
-
parseReportTemplateSchema
|
|
207
|
-
} from "@frt/report-core";
|
|
415
|
+
parseReportTemplateSchema,
|
|
416
|
+
} from "@frt-platform/report-core";
|
|
208
417
|
|
|
209
418
|
const legacy = {
|
|
210
419
|
title: "Old template",
|
|
211
420
|
description: "Using flat fields",
|
|
212
421
|
fields: [
|
|
213
422
|
{ id: "summary", type: "textarea", label: "Summary" },
|
|
214
|
-
{ id: "status", type: "dropdown", label: "Status", options: ["Open", "Closed"] }
|
|
215
|
-
]
|
|
423
|
+
{ id: "status", type: "dropdown", label: "Status", options: ["Open", "Closed"] },
|
|
424
|
+
],
|
|
216
425
|
};
|
|
217
426
|
|
|
218
427
|
const migrated = migrateLegacySchema(legacy);
|
|
@@ -232,6 +441,88 @@ const parsed = ReportTemplateSchemaValidator.parse(migrated);
|
|
|
232
441
|
|
|
233
442
|
---
|
|
234
443
|
|
|
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:
|
|
447
|
+
|
|
448
|
+
```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);
|
|
460
|
+
|
|
461
|
+
console.log(diff.addedFields);
|
|
462
|
+
console.log(diff.removedFields);
|
|
463
|
+
console.log(diff.reorderedFields);
|
|
464
|
+
console.log(diff.modifiedFields);
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
`diff.modifiedFields` contains entries like:
|
|
468
|
+
|
|
469
|
+
```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
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
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
|
+
---
|
|
490
|
+
|
|
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.
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
import { exportJSONSchema } from "@frt-platform/report-core";
|
|
497
|
+
|
|
498
|
+
const jsonSchema = exportJSONSchema(template);
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Shape (simplified):
|
|
502
|
+
|
|
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`.
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
235
526
|
## API Overview
|
|
236
527
|
|
|
237
528
|
### Types
|
|
@@ -241,14 +532,16 @@ import type {
|
|
|
241
532
|
ReportTemplateSchema,
|
|
242
533
|
ReportTemplateSection,
|
|
243
534
|
ReportTemplateField,
|
|
244
|
-
ReportTemplateFieldType
|
|
245
|
-
|
|
535
|
+
ReportTemplateFieldType,
|
|
536
|
+
InferResponse,
|
|
537
|
+
} from "@frt-platform/report-core";
|
|
246
538
|
```
|
|
247
539
|
|
|
248
540
|
* `ReportTemplateFieldType` – union of all field type strings
|
|
249
541
|
* `ReportTemplateField` – one question/field in a section
|
|
250
542
|
* `ReportTemplateSection` – a logical grouping of fields
|
|
251
543
|
* `ReportTemplateSchema` – full template
|
|
544
|
+
* `InferResponse<TTemplate>` – builds a typed response object from a static template
|
|
252
545
|
|
|
253
546
|
### Constants
|
|
254
547
|
|
|
@@ -257,8 +550,8 @@ import {
|
|
|
257
550
|
REPORT_TEMPLATE_VERSION,
|
|
258
551
|
REPORT_TEMPLATE_FIELD_TYPES,
|
|
259
552
|
DEFAULT_FIELD_LABEL,
|
|
260
|
-
CORE_FIELD_DEFAULTS
|
|
261
|
-
} from "@frt/report-core";
|
|
553
|
+
CORE_FIELD_DEFAULTS,
|
|
554
|
+
} from "@frt-platform/report-core";
|
|
262
555
|
```
|
|
263
556
|
|
|
264
557
|
* `REPORT_TEMPLATE_VERSION: number`
|
|
@@ -276,14 +569,14 @@ import {
|
|
|
276
569
|
parseReportTemplateSchema,
|
|
277
570
|
parseReportTemplateSchemaFromString,
|
|
278
571
|
normalizeReportTemplateSchema,
|
|
279
|
-
serializeReportTemplateSchema
|
|
280
|
-
} from "@frt/report-core";
|
|
572
|
+
serializeReportTemplateSchema,
|
|
573
|
+
} from "@frt-platform/report-core";
|
|
281
574
|
```
|
|
282
575
|
|
|
283
576
|
Use the Zod schemas directly if you want:
|
|
284
577
|
|
|
285
578
|
```ts
|
|
286
|
-
import { ReportTemplateSchemaValidator } from "@frt/report-core";
|
|
579
|
+
import { ReportTemplateSchemaValidator } from "@frt-platform/report-core";
|
|
287
580
|
|
|
288
581
|
const result = ReportTemplateSchemaValidator.safeParse(raw);
|
|
289
582
|
if (!result.success) {
|
|
@@ -294,15 +587,37 @@ if (!result.success) {
|
|
|
294
587
|
### Migration
|
|
295
588
|
|
|
296
589
|
```ts
|
|
297
|
-
import { migrateLegacySchema } from "@frt/report-core";
|
|
590
|
+
import { migrateLegacySchema } from "@frt-platform/report-core";
|
|
298
591
|
```
|
|
299
592
|
|
|
300
593
|
Takes `unknown` legacy input and returns a shape better suited for current schema validation.
|
|
301
594
|
|
|
302
|
-
###
|
|
595
|
+
### Response validation
|
|
303
596
|
|
|
304
597
|
```ts
|
|
305
|
-
import {
|
|
598
|
+
import {
|
|
599
|
+
validateReportResponse,
|
|
600
|
+
buildResponseSchemaWithConditions,
|
|
601
|
+
type InferResponse,
|
|
602
|
+
} from "@frt-platform/report-core";
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Template diffing
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
import { diffTemplates } from "@frt-platform/report-core";
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### JSON Schema export
|
|
612
|
+
|
|
613
|
+
```ts
|
|
614
|
+
import { exportJSONSchema } from "@frt-platform/report-core";
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### Field registry
|
|
618
|
+
|
|
619
|
+
```ts
|
|
620
|
+
import { FieldRegistry } from "@frt-platform/report-core";
|
|
306
621
|
```
|
|
307
622
|
|
|
308
623
|
---
|
|
@@ -321,46 +636,60 @@ This package is intentionally minimal. You can layer it into:
|
|
|
321
636
|
* Validating templates on save
|
|
322
637
|
* Migrating old template shapes
|
|
323
638
|
* Ensuring consistent data before persisting to a DB
|
|
639
|
+
* Validating report responses before saving them
|
|
640
|
+
* Serving JSON Schema to other services
|
|
324
641
|
|
|
325
642
|
Typical pattern in a full stack app:
|
|
326
643
|
|
|
327
|
-
1. **Frontend
|
|
644
|
+
1. **Frontend**
|
|
328
645
|
|
|
329
646
|
* Build a visual editor for templates
|
|
330
647
|
* Send template JSON to server
|
|
331
648
|
|
|
332
|
-
2. **Backend
|
|
649
|
+
2. **Backend**
|
|
333
650
|
|
|
334
|
-
* `parseReportTemplateSchema` to validate
|
|
651
|
+
* `parseReportTemplateSchema` to validate templates
|
|
335
652
|
* Store `ReportTemplateSchema` JSON in your DB (e.g. Mongo via Prisma)
|
|
653
|
+
* On response submission:
|
|
336
654
|
|
|
337
|
-
|
|
655
|
+
* Load template from DB
|
|
656
|
+
* `validateReportResponse(template, data)` before insert
|
|
657
|
+
|
|
658
|
+
3. **Runtime**
|
|
338
659
|
|
|
339
660
|
* Load templates from DB
|
|
340
|
-
* Use them to render dynamic forms
|
|
661
|
+
* Use them to render dynamic forms
|
|
662
|
+
* Use `validateReportResponse` / `buildResponseSchemaWithConditions` to validate and normalize responses
|
|
663
|
+
* Use `exportJSONSchema` for interoperability with other stacks
|
|
341
664
|
|
|
342
665
|
---
|
|
343
666
|
|
|
344
667
|
## Roadmap / Extensions (ideas)
|
|
345
668
|
|
|
346
|
-
Things you might add on top (in a separate package, e.g. `@frt/report-react`):
|
|
669
|
+
Things you might add on top (in a separate package, e.g. `@frt-platform/report-react`):
|
|
347
670
|
|
|
348
671
|
* React components for:
|
|
349
672
|
|
|
350
673
|
* Template builder UI
|
|
351
674
|
* Field editors/renderers (short text, selects, etc.)
|
|
352
|
-
*
|
|
675
|
+
* Auto-generated `<ReportForm />` that consumes a template
|
|
353
676
|
* More advanced field types:
|
|
354
677
|
|
|
355
678
|
* File uploads
|
|
356
679
|
* Signature pads
|
|
357
680
|
* Matrix / grid fields
|
|
681
|
+
* Fully-featured `repeatGroup` UI & runtime
|
|
682
|
+
* Richer conditional logic:
|
|
683
|
+
|
|
684
|
+
* `visibleIf`
|
|
685
|
+
* `requiredIf`
|
|
686
|
+
* multi-step branching
|
|
358
687
|
* Multi-language labels & descriptions
|
|
359
688
|
|
|
360
|
-
The core stays simple: schema + validation + migration
|
|
689
|
+
The core stays simple: **schema + validation + conditional logic + migration + diffing + registry + JSON Schema export**.
|
|
361
690
|
|
|
362
691
|
---
|
|
363
692
|
|
|
364
693
|
## License
|
|
365
694
|
|
|
366
|
-
MIT – use it in your projects, commercial or not. Contributions welcome.
|
|
695
|
+
MIT – use it in your projects, commercial or not. Contributions welcome.
|