@frt-platform/report-core 1.2.0 β 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 +255 -392
- package/dist/index.d.mts +127 -95
- package/dist/index.d.ts +127 -95
- package/dist/index.js +506 -398
- package/dist/index.mjs +508 -398
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,551 +1,414 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@frt-platform/report-core`
|
|
2
2
|
|
|
3
|
-
Core engine for
|
|
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**
|
|
8
|
-
* **
|
|
9
|
-
* **
|
|
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
|
|
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
|
|
21
|
-
|
|
22
|
-
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.
|
|
23
14
|
|
|
24
15
|
---
|
|
25
16
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
# npm
|
|
30
|
-
npm install @frt/report-core zod
|
|
17
|
+
# β¨ Features
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
yarn add @frt/report-core zod
|
|
19
|
+
### π Template Schema
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
21
|
+
* Sections β fields
|
|
22
|
+
* Field IDs, labels, descriptions, placeholders
|
|
23
|
+
* Built-in field types:
|
|
38
24
|
|
|
39
|
-
`
|
|
25
|
+
* `shortText`, `longText`
|
|
26
|
+
* `number`
|
|
27
|
+
* `date`
|
|
28
|
+
* `checkbox`
|
|
29
|
+
* `singleSelect`, `multiSelect`
|
|
30
|
+
* `repeatGroup` (nested fieldsets)
|
|
40
31
|
|
|
41
|
-
|
|
32
|
+
### π Field Constraints
|
|
42
33
|
|
|
43
|
-
|
|
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
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
### π Conditional Logic
|
|
46
42
|
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
43
|
+
* `visibleIf`
|
|
44
|
+
* `requiredIf`
|
|
45
|
+
* Supports:
|
|
46
|
+
`equals`, `any`, `all`, `not`
|
|
47
|
+
* Fully integrated into validation.
|
|
50
48
|
|
|
51
|
-
|
|
49
|
+
### π₯ Validation Engine
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// [
|
|
58
|
-
// "shortText",
|
|
59
|
-
// "longText",
|
|
60
|
-
// "number",
|
|
61
|
-
// "date",
|
|
62
|
-
// "checkbox",
|
|
63
|
-
// "singleSelect",
|
|
64
|
-
// "multiSelect",
|
|
65
|
-
// ]
|
|
66
|
-
```
|
|
51
|
+
* Build response schema dynamically with conditions
|
|
52
|
+
* Throwing API: `validateReportResponse()`
|
|
53
|
+
* Non-throwing API: `validateReportResponseDetailed()`
|
|
54
|
+
* Rich `ResponseFieldError` objects with:
|
|
67
55
|
|
|
68
|
-
|
|
56
|
+
* section title
|
|
57
|
+
* field label
|
|
58
|
+
* error code
|
|
59
|
+
* full message
|
|
60
|
+
* nested repeatGroup row context
|
|
69
61
|
|
|
70
|
-
|
|
62
|
+
### π Template Migration & Normalization
|
|
71
63
|
|
|
72
|
-
|
|
64
|
+
* Legacy format migration (`fields β sections`)
|
|
65
|
+
* Automatic ID normalization & uniqueness enforcement
|
|
66
|
+
* Safe parsing from JSON or raw objects
|
|
73
67
|
|
|
74
|
-
|
|
68
|
+
### π Schema Diffing
|
|
75
69
|
|
|
76
|
-
|
|
77
|
-
import type { ReportTemplateSchema } from "@frt/report-core";
|
|
70
|
+
Detect:
|
|
78
71
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
sections: [
|
|
84
|
-
{
|
|
85
|
-
id: "section-overview",
|
|
86
|
-
title: "Incident overview",
|
|
87
|
-
fields: [
|
|
88
|
-
{
|
|
89
|
-
id: "summary",
|
|
90
|
-
type: "longText",
|
|
91
|
-
label: "What happened?",
|
|
92
|
-
required: true,
|
|
93
|
-
minLength: 10,
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
id: "status",
|
|
97
|
-
type: "singleSelect",
|
|
98
|
-
label: "Incident status",
|
|
99
|
-
required: true,
|
|
100
|
-
options: ["Pending review", "Resolved", "Escalated"],
|
|
101
|
-
defaultValue: "Resolved",
|
|
102
|
-
},
|
|
103
|
-
],
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
};
|
|
107
|
-
```
|
|
72
|
+
* Added / removed / reordered sections
|
|
73
|
+
* Added / removed / reordered fields
|
|
74
|
+
* Modified fields
|
|
75
|
+
* Nested diffs inside repeat groups
|
|
108
76
|
|
|
109
|
-
###
|
|
77
|
+
### π¦ JSON Schema Export
|
|
110
78
|
|
|
111
|
-
|
|
79
|
+
* Export a template as a valid JSON Schema (2020-12 draft)
|
|
80
|
+
* Includes vendor extensions:
|
|
112
81
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
82
|
+
* `x-frt-visibleIf`
|
|
83
|
+
* `x-frt-requiredIf`
|
|
84
|
+
* repeatGroup min/max
|
|
85
|
+
* placeholders
|
|
86
|
+
* Useful for OpenAPI, Postman, or other backend runtimes.
|
|
118
87
|
|
|
119
|
-
|
|
88
|
+
### π Field Registry
|
|
120
89
|
|
|
121
|
-
|
|
122
|
-
const parsed = parseReportTemplateSchema(raw);
|
|
90
|
+
Extend the system at runtime:
|
|
123
91
|
|
|
124
|
-
|
|
125
|
-
|
|
92
|
+
* Add custom types (`richText`, `fileUpload`, etc.)
|
|
93
|
+
* Override validation logic
|
|
94
|
+
* Provide metadata for UI packages
|
|
126
95
|
|
|
127
|
-
|
|
128
|
-
// always a non-empty, safe identifier
|
|
129
|
-
```
|
|
96
|
+
### 𧬠Type Inference
|
|
130
97
|
|
|
131
|
-
|
|
98
|
+
Get a fully typed response type from a template:
|
|
132
99
|
|
|
133
100
|
```ts
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const jsonString = serializeReportTemplateSchema(normalized);
|
|
137
|
-
// ready to store in DB, file, etc.
|
|
101
|
+
type MyResponse = InferResponse<typeof template>;
|
|
138
102
|
```
|
|
139
103
|
|
|
140
|
-
|
|
104
|
+
### π§Ύ Serialization Helpers
|
|
141
105
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
If you have a JSON string (e.g. from a form textarea):
|
|
106
|
+
Deterministic JSON output with sorting options:
|
|
145
107
|
|
|
146
108
|
```ts
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} catch (err) {
|
|
153
|
-
// invalid JSON or schema β show error to user
|
|
154
|
-
}
|
|
109
|
+
serializeReportTemplateSchema(template, {
|
|
110
|
+
pretty: true,
|
|
111
|
+
sortSectionsById: true,
|
|
112
|
+
sortFieldsById: true,
|
|
113
|
+
});
|
|
155
114
|
```
|
|
156
115
|
|
|
157
|
-
|
|
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;
|
|
116
|
+
Perfect for Git diffs and storage.
|
|
172
117
|
|
|
173
|
-
|
|
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
|
-
};
|
|
118
|
+
---
|
|
181
119
|
|
|
182
|
-
|
|
183
|
-
const safeResponse = responseSchema.parse(responseData);
|
|
120
|
+
# π¦ Installation
|
|
184
121
|
|
|
185
|
-
|
|
186
|
-
|
|
122
|
+
```bash
|
|
123
|
+
npm install @frt-platform/report-core zod
|
|
187
124
|
```
|
|
188
125
|
|
|
189
|
-
`
|
|
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`)
|
|
126
|
+
`zod` is a peer dependency.
|
|
197
127
|
|
|
198
128
|
---
|
|
199
129
|
|
|
200
|
-
|
|
130
|
+
# π Quickstart
|
|
201
131
|
|
|
202
|
-
|
|
132
|
+
## 1. Define a template
|
|
203
133
|
|
|
204
134
|
```ts
|
|
205
|
-
import type {
|
|
135
|
+
import type { ReportTemplateSchema } from "@frt-platform/report-core";
|
|
206
136
|
|
|
207
|
-
const
|
|
137
|
+
const template: ReportTemplateSchema = {
|
|
208
138
|
version: 1,
|
|
209
139
|
sections: [
|
|
210
140
|
{
|
|
211
|
-
id: "
|
|
141
|
+
id: "general",
|
|
142
|
+
title: "General Info",
|
|
212
143
|
fields: [
|
|
213
144
|
{
|
|
214
|
-
id: "
|
|
215
|
-
type: "
|
|
216
|
-
label: "
|
|
145
|
+
id: "title",
|
|
146
|
+
type: "shortText",
|
|
147
|
+
label: "Incident title",
|
|
217
148
|
required: true,
|
|
218
|
-
minLength: 10,
|
|
219
149
|
},
|
|
220
150
|
{
|
|
221
|
-
id: "
|
|
151
|
+
id: "severity",
|
|
222
152
|
type: "singleSelect",
|
|
223
|
-
label: "
|
|
153
|
+
label: "Severity",
|
|
224
154
|
required: true,
|
|
225
|
-
options: ["
|
|
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,
|
|
155
|
+
options: ["Low", "Medium", "High"],
|
|
237
156
|
},
|
|
238
157
|
{
|
|
239
|
-
id: "
|
|
240
|
-
type: "
|
|
241
|
-
label: "
|
|
242
|
-
|
|
158
|
+
id: "details",
|
|
159
|
+
type: "longText",
|
|
160
|
+
label: "Details",
|
|
161
|
+
minLength: 10,
|
|
243
162
|
},
|
|
244
163
|
],
|
|
245
164
|
},
|
|
246
165
|
],
|
|
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
166
|
};
|
|
262
167
|
```
|
|
263
168
|
|
|
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
|
-
|
|
272
|
-
## Using field defaults
|
|
273
|
-
|
|
274
|
-
The package includes core defaults for each field type (no UI, no icons):
|
|
275
|
-
|
|
276
|
-
```ts
|
|
277
|
-
import {
|
|
278
|
-
CORE_FIELD_DEFAULTS,
|
|
279
|
-
DEFAULT_FIELD_LABEL,
|
|
280
|
-
type ReportTemplateFieldType,
|
|
281
|
-
} from "@frt/report-core";
|
|
282
|
-
|
|
283
|
-
function createDefaultField(type: ReportTemplateFieldType) {
|
|
284
|
-
const defaults = CORE_FIELD_DEFAULTS[type] ?? {};
|
|
285
|
-
return {
|
|
286
|
-
id: "your-id-here",
|
|
287
|
-
type,
|
|
288
|
-
label: (defaults.label as string) ?? DEFAULT_FIELD_LABEL,
|
|
289
|
-
...defaults,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const field = createDefaultField("shortText");
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
This is especially useful in a form builder UI when the user adds a new field of a given type.
|
|
297
|
-
|
|
298
169
|
---
|
|
299
170
|
|
|
300
|
-
##
|
|
301
|
-
|
|
302
|
-
The core package exposes a generic `createUniqueId` helper:
|
|
171
|
+
## 2. Validate a response (throwing API)
|
|
303
172
|
|
|
304
173
|
```ts
|
|
305
|
-
import {
|
|
174
|
+
import { validateReportResponse } from "@frt-platform/report-core";
|
|
306
175
|
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
176
|
+
const parsed = validateReportResponse(template, {
|
|
177
|
+
title: "Broken fire alarm",
|
|
178
|
+
severity: "High",
|
|
179
|
+
details: "Triggered after smoke test",
|
|
180
|
+
});
|
|
310
181
|
```
|
|
311
182
|
|
|
312
|
-
|
|
183
|
+
If invalid β throws a ZodError.
|
|
313
184
|
|
|
314
185
|
---
|
|
315
186
|
|
|
316
|
-
##
|
|
317
|
-
|
|
318
|
-
If you had older templates that:
|
|
319
|
-
|
|
320
|
-
* Used a flat `fields` array instead of `sections`, or
|
|
321
|
-
* Used older field type names (`text`, `textarea`, `dropdown`, `multiselect`, β¦)
|
|
322
|
-
|
|
323
|
-
you can pass them through the migration pipeline:
|
|
187
|
+
## 3. Validate without throwing (UI-friendly)
|
|
324
188
|
|
|
325
189
|
```ts
|
|
326
|
-
import {
|
|
327
|
-
migrateLegacySchema,
|
|
328
|
-
parseReportTemplateSchema,
|
|
329
|
-
} from "@frt/report-core";
|
|
330
|
-
|
|
331
|
-
const legacy = {
|
|
332
|
-
title: "Old template",
|
|
333
|
-
description: "Using flat fields",
|
|
334
|
-
fields: [
|
|
335
|
-
{ id: "summary", type: "textarea", label: "Summary" },
|
|
336
|
-
{ id: "status", type: "dropdown", label: "Status", options: ["Open", "Closed"] },
|
|
337
|
-
],
|
|
338
|
-
};
|
|
190
|
+
import { validateReportResponseDetailed } from "@frt-platform/report-core";
|
|
339
191
|
|
|
340
|
-
const
|
|
341
|
-
|
|
192
|
+
const result = validateReportResponseDetailed(template, {
|
|
193
|
+
title: "",
|
|
194
|
+
severity: "High",
|
|
195
|
+
});
|
|
342
196
|
|
|
343
|
-
|
|
344
|
-
console.log(
|
|
197
|
+
if (!result.success) {
|
|
198
|
+
console.log(result.errors);
|
|
199
|
+
}
|
|
345
200
|
```
|
|
346
201
|
|
|
347
|
-
|
|
202
|
+
Produces:
|
|
348
203
|
|
|
349
204
|
```ts
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
+
]
|
|
353
215
|
```
|
|
354
216
|
|
|
355
217
|
---
|
|
356
218
|
|
|
357
|
-
|
|
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:
|
|
219
|
+
# π Conditional Logic Example
|
|
360
220
|
|
|
361
221
|
```ts
|
|
362
|
-
|
|
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
|
+
}
|
|
229
|
+
```
|
|
363
230
|
|
|
364
|
-
|
|
365
|
-
const afterTemplate = /* updated template */;
|
|
231
|
+
Behavior:
|
|
366
232
|
|
|
367
|
-
|
|
233
|
+
* If `follow_up_required = false` β field is **hidden** and **ignored**
|
|
234
|
+
* If `true` β field becomes **required**
|
|
368
235
|
|
|
369
|
-
|
|
370
|
-
console.log(diff.removedSections);
|
|
371
|
-
console.log(diff.fieldChanges);
|
|
372
|
-
```
|
|
236
|
+
---
|
|
373
237
|
|
|
374
|
-
|
|
238
|
+
# π Repeat Group Example
|
|
375
239
|
|
|
376
240
|
```ts
|
|
377
241
|
{
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
{
|
|
385
|
-
{
|
|
386
|
-
]
|
|
242
|
+
id: "injured",
|
|
243
|
+
type: "repeatGroup",
|
|
244
|
+
label: "Injured people",
|
|
245
|
+
min: 1,
|
|
246
|
+
max: 5,
|
|
247
|
+
fields: [
|
|
248
|
+
{ id: "name", type: "shortText", label: "Name", required: true },
|
|
249
|
+
{ id: "injury", type: "longText", label: "Injury description" }
|
|
250
|
+
]
|
|
387
251
|
}
|
|
388
252
|
```
|
|
389
253
|
|
|
390
|
-
|
|
254
|
+
Response shape:
|
|
391
255
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
256
|
+
```ts
|
|
257
|
+
injured: Array<{ name: string; injury?: string }>;
|
|
258
|
+
```
|
|
395
259
|
|
|
396
260
|
---
|
|
397
261
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
### Types
|
|
262
|
+
# π§© Field Registry (Custom Types)
|
|
401
263
|
|
|
402
264
|
```ts
|
|
403
|
-
import
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
265
|
+
import { FieldRegistry } from "@frt-platform/report-core";
|
|
266
|
+
import { z } from "zod";
|
|
267
|
+
|
|
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
|
+
});
|
|
410
276
|
```
|
|
411
277
|
|
|
412
|
-
|
|
413
|
-
* `ReportTemplateField` β one question/field in a section
|
|
414
|
-
* `ReportTemplateSection` β a logical grouping of fields
|
|
415
|
-
* `ReportTemplateSchema` β full template
|
|
416
|
-
* `InferResponse<TTemplate>` β builds a typed response object from a static template
|
|
417
|
-
|
|
418
|
-
### Constants
|
|
278
|
+
Now templates may include fields like:
|
|
419
279
|
|
|
420
280
|
```ts
|
|
421
|
-
|
|
422
|
-
REPORT_TEMPLATE_VERSION,
|
|
423
|
-
REPORT_TEMPLATE_FIELD_TYPES,
|
|
424
|
-
DEFAULT_FIELD_LABEL,
|
|
425
|
-
CORE_FIELD_DEFAULTS,
|
|
426
|
-
} from "@frt/report-core";
|
|
281
|
+
{ id: "body", type: "richText", label: "Report body" }
|
|
427
282
|
```
|
|
428
283
|
|
|
429
|
-
|
|
430
|
-
* `REPORT_TEMPLATE_FIELD_TYPES: readonly ReportTemplateFieldType[]`
|
|
431
|
-
* `DEFAULT_FIELD_LABEL: string`
|
|
432
|
-
* `CORE_FIELD_DEFAULTS: Record<ReportTemplateFieldType, Record<string, unknown>>`
|
|
284
|
+
---
|
|
433
285
|
|
|
434
|
-
|
|
286
|
+
# π§Ύ JSON Schema Export
|
|
435
287
|
|
|
436
288
|
```ts
|
|
437
|
-
import {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
ReportTemplateSchemaValidator,
|
|
441
|
-
parseReportTemplateSchema,
|
|
442
|
-
parseReportTemplateSchemaFromString,
|
|
443
|
-
normalizeReportTemplateSchema,
|
|
444
|
-
serializeReportTemplateSchema,
|
|
445
|
-
} from "@frt/report-core";
|
|
289
|
+
import { exportJSONSchema } from "@frt-platform/report-core";
|
|
290
|
+
|
|
291
|
+
const jsonSchema = exportJSONSchema(template);
|
|
446
292
|
```
|
|
447
293
|
|
|
448
|
-
|
|
294
|
+
Produces JSON Schema with:
|
|
449
295
|
|
|
450
|
-
|
|
451
|
-
|
|
296
|
+
* field types
|
|
297
|
+
* enums
|
|
298
|
+
* min/max constraints
|
|
299
|
+
* default values
|
|
300
|
+
* conditional logic preserved as custom `x-frt-*` properties
|
|
452
301
|
|
|
453
|
-
|
|
454
|
-
if (!result.success) {
|
|
455
|
-
// handle validation errors
|
|
456
|
-
}
|
|
457
|
-
```
|
|
302
|
+
---
|
|
458
303
|
|
|
459
|
-
|
|
304
|
+
# π Diff Templates
|
|
460
305
|
|
|
461
306
|
```ts
|
|
462
|
-
import {
|
|
463
|
-
```
|
|
307
|
+
import { diffTemplates } from "@frt-platform/report-core";
|
|
464
308
|
|
|
465
|
-
|
|
309
|
+
const diff = diffTemplates(oldTemplate, newTemplate);
|
|
310
|
+
```
|
|
466
311
|
|
|
467
|
-
|
|
312
|
+
Detects:
|
|
468
313
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
type InferResponse,
|
|
474
|
-
} from "@frt/report-core";
|
|
475
|
-
```
|
|
314
|
+
* added/removed/reordered sections
|
|
315
|
+
* added/removed/reordered fields
|
|
316
|
+
* modified fields
|
|
317
|
+
* nested diffs for repeat groups
|
|
476
318
|
|
|
477
|
-
|
|
319
|
+
Perfect for:
|
|
478
320
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
321
|
+
* Version history
|
|
322
|
+
* Audit logs
|
|
323
|
+
* Template editing UI
|
|
482
324
|
|
|
483
325
|
---
|
|
484
326
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
This package is intentionally minimal. You can layer it into:
|
|
327
|
+
# 𧬠Type Inference
|
|
488
328
|
|
|
489
|
-
|
|
329
|
+
Given a template:
|
|
490
330
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
331
|
+
```ts
|
|
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;
|
|
344
|
+
```
|
|
495
345
|
|
|
496
|
-
|
|
497
|
-
* Migrating old template shapes
|
|
498
|
-
* Ensuring consistent data before persisting to a DB
|
|
499
|
-
* Validating report responses before saving them
|
|
346
|
+
Infer response type:
|
|
500
347
|
|
|
501
|
-
|
|
348
|
+
```ts
|
|
349
|
+
type MyResponse = InferResponse<typeof myTemplate>;
|
|
350
|
+
```
|
|
502
351
|
|
|
503
|
-
|
|
352
|
+
Produces:
|
|
504
353
|
|
|
505
|
-
|
|
506
|
-
|
|
354
|
+
```ts
|
|
355
|
+
type MyResponse = {
|
|
356
|
+
title: string;
|
|
357
|
+
tags?: ("A" | "B")[];
|
|
358
|
+
};
|
|
359
|
+
```
|
|
507
360
|
|
|
508
|
-
|
|
361
|
+
---
|
|
509
362
|
|
|
510
|
-
|
|
511
|
-
* Store `ReportTemplateSchema` JSON in your DB (e.g. Mongo via Prisma)
|
|
512
|
-
* On response submission:
|
|
363
|
+
# π§Ύ Serialization
|
|
513
364
|
|
|
514
|
-
|
|
515
|
-
|
|
365
|
+
```ts
|
|
366
|
+
import { serializeReportTemplateSchema } from "@frt-platform/report-core";
|
|
516
367
|
|
|
517
|
-
|
|
368
|
+
const json = serializeReportTemplateSchema(template, {
|
|
369
|
+
pretty: true,
|
|
370
|
+
sortSectionsById: true,
|
|
371
|
+
sortFieldsById: true,
|
|
372
|
+
});
|
|
373
|
+
```
|
|
518
374
|
|
|
519
|
-
|
|
520
|
-
* Use them to render dynamic forms
|
|
521
|
-
* Use `buildResponseSchema` to validate and normalize responses
|
|
375
|
+
Useful for deterministic output in Git.
|
|
522
376
|
|
|
523
377
|
---
|
|
524
378
|
|
|
525
|
-
|
|
379
|
+
# π§± Roadmap
|
|
380
|
+
|
|
381
|
+
### Phase 1 β Core Maturation (βοΈ COMPLETE)
|
|
382
|
+
|
|
383
|
+
* Validation
|
|
384
|
+
* Conditional logic
|
|
385
|
+
* Diffing
|
|
386
|
+
* Field Registry
|
|
387
|
+
* Error helpers
|
|
388
|
+
* Serialization features
|
|
526
389
|
|
|
527
|
-
|
|
390
|
+
### Phase 2 β Advanced Field System (IN PROGRESS)
|
|
528
391
|
|
|
529
|
-
*
|
|
392
|
+
* Repeat group conditional rules (`minIf` / `maxIf`)
|
|
393
|
+
* Conditional logic inside group rows
|
|
394
|
+
* Computed fields (design)
|
|
395
|
+
* RichText / FileUpload via registry
|
|
530
396
|
|
|
531
|
-
|
|
532
|
-
* Field editors/renderers (short text, selects, etc.)
|
|
533
|
-
* Auto-generated `<ReportForm />` that consumes a template
|
|
534
|
-
* More advanced field types:
|
|
397
|
+
### Phase 3 β Reactions & Analytics (Planned)
|
|
535
398
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
* Conditional logic:
|
|
399
|
+
* Scoring rules
|
|
400
|
+
* Auto-tagging
|
|
401
|
+
* Suggested outcomes
|
|
540
402
|
|
|
541
|
-
|
|
542
|
-
* `requiredIf`
|
|
543
|
-
* Multi-language labels & descriptions
|
|
403
|
+
### Phase 4 β React UI Package (Planned)
|
|
544
404
|
|
|
545
|
-
|
|
405
|
+
* Form renderer
|
|
406
|
+
* Template builder
|
|
407
|
+
* Field palette
|
|
408
|
+
* Full ShadCN integration
|
|
546
409
|
|
|
547
410
|
---
|
|
548
411
|
|
|
549
|
-
|
|
412
|
+
# π License
|
|
550
413
|
|
|
551
|
-
MIT
|
|
414
|
+
MIT β feel free to use, extend, or contribute.
|