blocks-schema 0.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/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/index.cjs +312 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +255 -0
- package/dist/index.d.ts +255 -0
- package/dist/index.js +275 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
// src/compose.ts
|
|
4
|
+
function applyOverride(node, override) {
|
|
5
|
+
return {
|
|
6
|
+
...node,
|
|
7
|
+
...override.type ? { type: override.type } : {},
|
|
8
|
+
props: { ...node.props, ...override.props },
|
|
9
|
+
...override.bindings ? { bindings: { ...node.bindings, ...override.bindings } } : {},
|
|
10
|
+
...override.actions ? { actions: { ...node.actions, ...override.actions } } : {}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function expandChild(node, options) {
|
|
14
|
+
if (node.type === "Fragment") {
|
|
15
|
+
const ref = node.props?.ref;
|
|
16
|
+
const fragment = typeof ref === "string" ? options.fragments?.[ref] : void 0;
|
|
17
|
+
return fragment ? [composeNode(fragment, options)] : [composeNode({ ...node, children: [] }, options)];
|
|
18
|
+
}
|
|
19
|
+
if (node.type === "Slot") {
|
|
20
|
+
const name = node.props?.name;
|
|
21
|
+
const filler = typeof name === "string" ? options.slots?.[name] : void 0;
|
|
22
|
+
if (filler === void 0) {
|
|
23
|
+
return (node.children ?? []).flatMap((child) => expandChild(child, options));
|
|
24
|
+
}
|
|
25
|
+
const nodes = Array.isArray(filler) ? filler : [filler];
|
|
26
|
+
return nodes.map((filled) => composeNode(filled, options));
|
|
27
|
+
}
|
|
28
|
+
return [composeNode(node, options)];
|
|
29
|
+
}
|
|
30
|
+
function composeNode(node, options) {
|
|
31
|
+
const override = options.overrides?.[node.key];
|
|
32
|
+
const base = override ? applyOverride(node, override) : node;
|
|
33
|
+
const children = (base.children ?? []).filter((child) => !options.overrides?.[child.key]?.remove).flatMap((child) => expandChild(child, options));
|
|
34
|
+
return { ...base, children };
|
|
35
|
+
}
|
|
36
|
+
function composeDocument(document, options = {}) {
|
|
37
|
+
return { ...document, page: composeNode(document.page, options) };
|
|
38
|
+
}
|
|
39
|
+
function composeNodeTree(node, options = {}) {
|
|
40
|
+
return composeNode(node, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/envelope.ts
|
|
44
|
+
var UI_DOCUMENT_FORMAT_VERSION = "1.0";
|
|
45
|
+
var UI_DOCUMENT_TYPE = "UISchema";
|
|
46
|
+
function isUIDocument(value) {
|
|
47
|
+
if (!value || typeof value !== "object") return false;
|
|
48
|
+
const candidate = value;
|
|
49
|
+
return candidate.type === UI_DOCUMENT_TYPE && candidate.formatVersion === UI_DOCUMENT_FORMAT_VERSION && !!candidate.page;
|
|
50
|
+
}
|
|
51
|
+
var isUISchema = isUIDocument;
|
|
52
|
+
function createDocument(page, options = {}) {
|
|
53
|
+
return {
|
|
54
|
+
formatVersion: UI_DOCUMENT_FORMAT_VERSION,
|
|
55
|
+
type: UI_DOCUMENT_TYPE,
|
|
56
|
+
id: options.id ?? "document",
|
|
57
|
+
...options.meta ? { meta: options.meta } : {},
|
|
58
|
+
page
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
var uiNodeConstraintsSchema = z.object({
|
|
62
|
+
minLength: z.number().int().nonnegative().optional(),
|
|
63
|
+
maxLength: z.number().int().nonnegative().optional(),
|
|
64
|
+
minValue: z.number().optional(),
|
|
65
|
+
maxValue: z.number().optional(),
|
|
66
|
+
pattern: z.string().optional(),
|
|
67
|
+
precision: z.number().int().nonnegative().optional(),
|
|
68
|
+
scale: z.number().int().nonnegative().optional()
|
|
69
|
+
});
|
|
70
|
+
var uiNodePropsSchema = z.looseObject({
|
|
71
|
+
fieldId: z.string().optional(),
|
|
72
|
+
name: z.string().optional(),
|
|
73
|
+
label: z.string().optional(),
|
|
74
|
+
description: z.string().optional(),
|
|
75
|
+
placeholder: z.string().optional(),
|
|
76
|
+
required: z.boolean().optional(),
|
|
77
|
+
hidden: z.boolean().optional(),
|
|
78
|
+
disabled: z.boolean().optional(),
|
|
79
|
+
defaultValue: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),
|
|
80
|
+
constraints: uiNodeConstraintsSchema.optional(),
|
|
81
|
+
className: z.string().optional()
|
|
82
|
+
});
|
|
83
|
+
var uiBindingSchema = z.record(z.string(), z.string());
|
|
84
|
+
var uiActionSchema = z.object({
|
|
85
|
+
type: z.enum(["flow", "handler"]),
|
|
86
|
+
flowId: z.string().optional(),
|
|
87
|
+
handler: z.string().optional(),
|
|
88
|
+
inputMapping: z.record(z.string(), z.string()).optional(),
|
|
89
|
+
params: z.record(z.string(), z.unknown()).optional()
|
|
90
|
+
});
|
|
91
|
+
var uiActionsSchema = z.record(z.string(), uiActionSchema);
|
|
92
|
+
var uiNodeSchema = z.lazy(
|
|
93
|
+
() => z.object({
|
|
94
|
+
type: z.string().min(1),
|
|
95
|
+
key: z.string().min(1),
|
|
96
|
+
props: uiNodePropsSchema.default({}),
|
|
97
|
+
children: z.array(uiNodeSchema).default([]),
|
|
98
|
+
bindings: uiBindingSchema.optional(),
|
|
99
|
+
actions: uiActionsSchema.optional()
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
var uiRegistrySourceSchema = z.object({
|
|
103
|
+
name: z.string().min(1),
|
|
104
|
+
url: z.string().min(1)
|
|
105
|
+
});
|
|
106
|
+
var uiDataSourceSchema = z.looseObject({
|
|
107
|
+
name: z.string().min(1),
|
|
108
|
+
table: z.string().optional(),
|
|
109
|
+
query: z.string().optional(),
|
|
110
|
+
variables: z.record(z.string(), z.unknown()).optional()
|
|
111
|
+
});
|
|
112
|
+
var uiDocumentMetadataSchema = z.looseObject({
|
|
113
|
+
title: z.string().optional(),
|
|
114
|
+
description: z.string().optional()
|
|
115
|
+
});
|
|
116
|
+
var uiDocumentSchema = z.object({
|
|
117
|
+
formatVersion: z.literal(UI_DOCUMENT_FORMAT_VERSION),
|
|
118
|
+
type: z.literal(UI_DOCUMENT_TYPE),
|
|
119
|
+
id: z.string().min(1),
|
|
120
|
+
meta: uiDocumentMetadataSchema.optional(),
|
|
121
|
+
registries: z.array(uiRegistrySourceSchema).optional(),
|
|
122
|
+
dataSources: z.array(uiDataSourceSchema).optional(),
|
|
123
|
+
page: uiNodeSchema
|
|
124
|
+
});
|
|
125
|
+
function parseDocument(value) {
|
|
126
|
+
return uiDocumentSchema.parse(value);
|
|
127
|
+
}
|
|
128
|
+
function safeParseDocument(value) {
|
|
129
|
+
return uiDocumentSchema.safeParse(value);
|
|
130
|
+
}
|
|
131
|
+
function parseNode(value) {
|
|
132
|
+
return uiNodeSchema.parse(value);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/json-schema.ts
|
|
136
|
+
function toDocumentJsonSchema() {
|
|
137
|
+
return z.toJSONSchema(uiDocumentSchema, { io: "input" });
|
|
138
|
+
}
|
|
139
|
+
function toNodeJsonSchema() {
|
|
140
|
+
return z.toJSONSchema(uiNodeSchema, { io: "input" });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/node.ts
|
|
144
|
+
var WIDGET_NODE_TYPES = [
|
|
145
|
+
"Input",
|
|
146
|
+
"Textarea",
|
|
147
|
+
"Select",
|
|
148
|
+
"RadioGroup",
|
|
149
|
+
"Checkbox",
|
|
150
|
+
"Switch",
|
|
151
|
+
"NumberInput",
|
|
152
|
+
"DatePicker",
|
|
153
|
+
"DateTimePicker",
|
|
154
|
+
"TimePicker",
|
|
155
|
+
"PhoneInput",
|
|
156
|
+
"CodeEditor",
|
|
157
|
+
"MarkdownEditor",
|
|
158
|
+
"JsonEditor",
|
|
159
|
+
"FileUpload"
|
|
160
|
+
];
|
|
161
|
+
var CONTAINER_NODE_TYPES = ["Page", "Form", "Grid", "GridColumn", "Section", "Tabs", "Tab"];
|
|
162
|
+
var BLOCK_NODE_TYPES = [
|
|
163
|
+
"DataTable",
|
|
164
|
+
"DetailPanel",
|
|
165
|
+
"RelationList",
|
|
166
|
+
"StatCard",
|
|
167
|
+
"Chart",
|
|
168
|
+
"ActionBar",
|
|
169
|
+
"Markdown",
|
|
170
|
+
"AgentChat",
|
|
171
|
+
"Button",
|
|
172
|
+
"Slot",
|
|
173
|
+
"Fragment",
|
|
174
|
+
"Custom"
|
|
175
|
+
];
|
|
176
|
+
var widgetTypes = new Set(WIDGET_NODE_TYPES);
|
|
177
|
+
var containerTypes = new Set(CONTAINER_NODE_TYPES);
|
|
178
|
+
var blockTypes = new Set(BLOCK_NODE_TYPES);
|
|
179
|
+
function isWidgetNodeType(type) {
|
|
180
|
+
return widgetTypes.has(type);
|
|
181
|
+
}
|
|
182
|
+
function isContainerNodeType(type) {
|
|
183
|
+
return containerTypes.has(type);
|
|
184
|
+
}
|
|
185
|
+
function isKnownNodeType(type) {
|
|
186
|
+
return widgetTypes.has(type) || containerTypes.has(type) || blockTypes.has(type);
|
|
187
|
+
}
|
|
188
|
+
function isWidgetNode(node) {
|
|
189
|
+
return isWidgetNodeType(node.type);
|
|
190
|
+
}
|
|
191
|
+
function isContainerNode(node) {
|
|
192
|
+
return isContainerNodeType(node.type);
|
|
193
|
+
}
|
|
194
|
+
function* walkNodes(node) {
|
|
195
|
+
yield node;
|
|
196
|
+
for (const child of node.children ?? []) {
|
|
197
|
+
yield* walkNodes(child);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function collectFieldNames(node) {
|
|
201
|
+
const names = [];
|
|
202
|
+
for (const current of walkNodes(node)) {
|
|
203
|
+
if (isWidgetNode(current) && typeof current.props?.name === "string") {
|
|
204
|
+
names.push(current.props.name);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return names;
|
|
208
|
+
}
|
|
209
|
+
function collectDefaultValues(node) {
|
|
210
|
+
const values = {};
|
|
211
|
+
for (const current of walkNodes(node)) {
|
|
212
|
+
if (!isWidgetNode(current) || typeof current.props?.name !== "string") continue;
|
|
213
|
+
if (current.props.defaultValue !== void 0) {
|
|
214
|
+
values[current.props.name] = current.props.defaultValue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return values;
|
|
218
|
+
}
|
|
219
|
+
function collectFieldConstraints(node) {
|
|
220
|
+
const result = {};
|
|
221
|
+
for (const current of walkNodes(node)) {
|
|
222
|
+
if (!isWidgetNode(current) || typeof current.props?.name !== "string") continue;
|
|
223
|
+
result[current.props.name] = {
|
|
224
|
+
constraints: current.props.constraints,
|
|
225
|
+
required: current.props.required
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
function findNodeByKey(node, key) {
|
|
231
|
+
for (const current of walkNodes(node)) {
|
|
232
|
+
if (current.key === key) return current;
|
|
233
|
+
}
|
|
234
|
+
return void 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/validation.ts
|
|
238
|
+
function validateField(value, constraints, required) {
|
|
239
|
+
const stringValue = value == null ? "" : String(value);
|
|
240
|
+
const isEmpty = stringValue.trim() === "";
|
|
241
|
+
if (required && isEmpty) {
|
|
242
|
+
return "This field is required";
|
|
243
|
+
}
|
|
244
|
+
if (isEmpty) return null;
|
|
245
|
+
if (constraints?.minLength != null && stringValue.length < constraints.minLength) {
|
|
246
|
+
return `Minimum ${constraints.minLength} characters required`;
|
|
247
|
+
}
|
|
248
|
+
if (constraints?.maxLength != null && stringValue.length > constraints.maxLength) {
|
|
249
|
+
return `Maximum ${constraints.maxLength} characters allowed`;
|
|
250
|
+
}
|
|
251
|
+
if (constraints?.minValue != null && typeof value === "number" && value < constraints.minValue) {
|
|
252
|
+
return `Minimum value is ${constraints.minValue}`;
|
|
253
|
+
}
|
|
254
|
+
if (constraints?.maxValue != null && typeof value === "number" && value > constraints.maxValue) {
|
|
255
|
+
return `Maximum value is ${constraints.maxValue}`;
|
|
256
|
+
}
|
|
257
|
+
if (constraints?.pattern) {
|
|
258
|
+
const regex = compilePattern(constraints.pattern);
|
|
259
|
+
if (regex && !regex.test(stringValue)) {
|
|
260
|
+
return "Invalid format";
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
function compilePattern(pattern) {
|
|
266
|
+
try {
|
|
267
|
+
return new RegExp(pattern);
|
|
268
|
+
} catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { BLOCK_NODE_TYPES, CONTAINER_NODE_TYPES, UI_DOCUMENT_FORMAT_VERSION, UI_DOCUMENT_TYPE, WIDGET_NODE_TYPES, collectDefaultValues, collectFieldConstraints, collectFieldNames, composeDocument, composeNodeTree, createDocument, findNodeByKey, isContainerNode, isContainerNodeType, isKnownNodeType, isUIDocument, isUISchema, isWidgetNode, isWidgetNodeType, parseDocument, parseNode, safeParseDocument, toDocumentJsonSchema, toNodeJsonSchema, uiActionSchema, uiActionsSchema, uiBindingSchema, uiDataSourceSchema, uiDocumentMetadataSchema, uiDocumentSchema, uiNodeConstraintsSchema, uiNodePropsSchema, uiNodeSchema, uiRegistrySourceSchema, validateField, walkNodes };
|
|
274
|
+
//# sourceMappingURL=index.js.map
|
|
275
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compose.ts","../src/envelope.ts","../src/zod.ts","../src/json-schema.ts","../src/node.ts","../src/validation.ts"],"names":["z"],"mappings":"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","file":"index.js","sourcesContent":["import type { UIDocument } from './envelope';\nimport type { UIActions, UIBinding, UINode, UINodeProps, UINodeType } from './node';\n\n/**\n * A patch applied to the node with a given `key`. Composition is per node, not\n * per document, so a generated default can be customized in a few places\n * without giving up generation.\n */\nexport interface NodeOverride {\n\ttype?: UINodeType;\n\tprops?: UINodeProps;\n\tbindings?: UIBinding;\n\tactions?: UIActions;\n\t/** Drop the node (and its subtree) from the composed document. */\n\tremove?: boolean;\n}\n\nexport type NodeOverrides = Record<string, NodeOverride>;\n\n/** Reusable subtrees addressed by `Fragment` nodes via `props.ref`. */\nexport type FragmentMap = Record<string, UINode>;\n\n/** Subtrees that fill `Slot` nodes, addressed by `props.name`. */\nexport type SlotMap = Record<string, UINode | UINode[]>;\n\nexport interface ComposeOptions {\n\tfragments?: FragmentMap;\n\tslots?: SlotMap;\n\toverrides?: NodeOverrides;\n}\n\nfunction applyOverride(node: UINode, override: NodeOverride): UINode {\n\treturn {\n\t\t...node,\n\t\t...(override.type ? { type: override.type } : {}),\n\t\tprops: { ...node.props, ...override.props },\n\t\t...(override.bindings ? { bindings: { ...node.bindings, ...override.bindings } } : {}),\n\t\t...(override.actions ? { actions: { ...node.actions, ...override.actions } } : {}),\n\t};\n}\n\nfunction expandChild(node: UINode, options: ComposeOptions): UINode[] {\n\tif (node.type === 'Fragment') {\n\t\tconst ref = node.props?.ref;\n\t\tconst fragment = typeof ref === 'string' ? options.fragments?.[ref] : undefined;\n\t\t// An unresolved reference stays in the tree so the renderer surfaces it\n\t\t// rather than silently dropping content.\n\t\treturn fragment ? [composeNode(fragment, options)] : [composeNode({ ...node, children: [] }, options)];\n\t}\n\n\tif (node.type === 'Slot') {\n\t\tconst name = node.props?.name;\n\t\tconst filler = typeof name === 'string' ? options.slots?.[name] : undefined;\n\t\tif (filler === undefined) {\n\t\t\t// No filler: fall back to the slot's own children (its default content).\n\t\t\treturn (node.children ?? []).flatMap((child) => expandChild(child, options));\n\t\t}\n\t\tconst nodes = Array.isArray(filler) ? filler : [filler];\n\t\treturn nodes.map((filled) => composeNode(filled, options));\n\t}\n\n\treturn [composeNode(node, options)];\n}\n\nfunction composeNode(node: UINode, options: ComposeOptions): UINode {\n\tconst override = options.overrides?.[node.key];\n\tconst base = override ? applyOverride(node, override) : node;\n\n\tconst children = (base.children ?? [])\n\t\t.filter((child) => !options.overrides?.[child.key]?.remove)\n\t\t.flatMap((child) => expandChild(child, options));\n\n\treturn { ...base, children };\n}\n\n/**\n * Compose a document: expand `Fragment` references, fill `Slot` nodes, then\n * apply per-node overrides. Pure — the input document is never mutated.\n */\nexport function composeDocument(document: UIDocument, options: ComposeOptions = {}): UIDocument {\n\treturn { ...document, page: composeNode(document.page, options) };\n}\n\nexport function composeNodeTree(node: UINode, options: ComposeOptions = {}): UINode {\n\treturn composeNode(node, options);\n}\n","import type { UINode } from './node';\n\nexport const UI_DOCUMENT_FORMAT_VERSION = '1.0';\nexport const UI_DOCUMENT_TYPE = 'UISchema';\n\nexport interface UIDocumentMetadata {\n\ttitle?: string;\n\tdescription?: string;\n\t[key: string]: unknown;\n}\n\n/**\n * A resolution source for node types: a shadcn-style registry URL template,\n * e.g. `https://constructive-io.github.io/blocks/r/{name}.json`.\n */\nexport interface UIRegistrySource {\n\tname: string;\n\turl: string;\n}\n\n/** A named, read-only query a document's blocks can bind against. */\nexport interface UIDataSource {\n\tname: string;\n\ttable?: string;\n\tquery?: string;\n\tvariables?: Record<string, unknown>;\n\tselect?: string;\n\twhere?: Record<string, unknown>;\n\torderBy?: unknown;\n\tfirst?: number;\n}\n\nexport interface UIDocument {\n\tformatVersion: typeof UI_DOCUMENT_FORMAT_VERSION;\n\ttype: typeof UI_DOCUMENT_TYPE;\n\tid: string;\n\tmeta?: UIDocumentMetadata;\n\tregistries?: UIRegistrySource[];\n\tdataSources?: UIDataSource[];\n\tpage: UINode;\n}\n\n/**\n * The name the deployed dashboard form builder uses for the same envelope.\n * Kept as an alias so existing `UISchema` consumers migrate by import swap.\n */\nexport type UISchema = UIDocument;\n\nexport function isUIDocument(value: unknown): value is UIDocument {\n\tif (!value || typeof value !== 'object') return false;\n\tconst candidate = value as Record<string, unknown>;\n\treturn (\n\t\tcandidate.type === UI_DOCUMENT_TYPE &&\n\t\tcandidate.formatVersion === UI_DOCUMENT_FORMAT_VERSION &&\n\t\t!!candidate.page\n\t);\n}\n\n/** @deprecated Use {@link isUIDocument}. */\nexport const isUISchema = isUIDocument;\n\nexport function createDocument(page: UINode, options: { id?: string; meta?: UIDocumentMetadata } = {}): UIDocument {\n\treturn {\n\t\tformatVersion: UI_DOCUMENT_FORMAT_VERSION,\n\t\ttype: UI_DOCUMENT_TYPE,\n\t\tid: options.id ?? 'document',\n\t\t...(options.meta ? { meta: options.meta } : {}),\n\t\tpage,\n\t};\n}\n","import { z } from 'zod';\n\nimport { UI_DOCUMENT_FORMAT_VERSION, UI_DOCUMENT_TYPE } from './envelope';\nimport type { UIDocument } from './envelope';\nimport type { UINode } from './node';\n\nexport const uiNodeConstraintsSchema = z.object({\n\tminLength: z.number().int().nonnegative().optional(),\n\tmaxLength: z.number().int().nonnegative().optional(),\n\tminValue: z.number().optional(),\n\tmaxValue: z.number().optional(),\n\tpattern: z.string().optional(),\n\tprecision: z.number().int().nonnegative().optional(),\n\tscale: z.number().int().nonnegative().optional(),\n});\n\nexport const uiNodePropsSchema = z.looseObject({\n\tfieldId: z.string().optional(),\n\tname: z.string().optional(),\n\tlabel: z.string().optional(),\n\tdescription: z.string().optional(),\n\tplaceholder: z.string().optional(),\n\trequired: z.boolean().optional(),\n\thidden: z.boolean().optional(),\n\tdisabled: z.boolean().optional(),\n\tdefaultValue: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),\n\tconstraints: uiNodeConstraintsSchema.optional(),\n\tclassName: z.string().optional(),\n});\n\nexport const uiBindingSchema = z.record(z.string(), z.string());\n\nexport const uiActionSchema = z.object({\n\ttype: z.enum(['flow', 'handler']),\n\tflowId: z.string().optional(),\n\thandler: z.string().optional(),\n\tinputMapping: z.record(z.string(), z.string()).optional(),\n\tparams: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport const uiActionsSchema = z.record(z.string(), uiActionSchema);\n\nexport const uiNodeSchema: z.ZodType<UINode> = z.lazy(() =>\n\tz.object({\n\t\ttype: z.string().min(1),\n\t\tkey: z.string().min(1),\n\t\tprops: uiNodePropsSchema.default({}),\n\t\tchildren: z.array(uiNodeSchema).default([]),\n\t\tbindings: uiBindingSchema.optional(),\n\t\tactions: uiActionsSchema.optional(),\n\t}),\n);\n\nexport const uiRegistrySourceSchema = z.object({\n\tname: z.string().min(1),\n\turl: z.string().min(1),\n});\n\nexport const uiDataSourceSchema = z.looseObject({\n\tname: z.string().min(1),\n\ttable: z.string().optional(),\n\tquery: z.string().optional(),\n\tvariables: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport const uiDocumentMetadataSchema = z.looseObject({\n\ttitle: z.string().optional(),\n\tdescription: z.string().optional(),\n});\n\nexport const uiDocumentSchema: z.ZodType<UIDocument> = z.object({\n\tformatVersion: z.literal(UI_DOCUMENT_FORMAT_VERSION),\n\ttype: z.literal(UI_DOCUMENT_TYPE),\n\tid: z.string().min(1),\n\tmeta: uiDocumentMetadataSchema.optional(),\n\tregistries: z.array(uiRegistrySourceSchema).optional(),\n\tdataSources: z.array(uiDataSourceSchema).optional(),\n\tpage: uiNodeSchema,\n});\n\n/** Throws a `ZodError` describing every problem in the document. */\nexport function parseDocument(value: unknown): UIDocument {\n\treturn uiDocumentSchema.parse(value);\n}\n\nexport function safeParseDocument(value: unknown) {\n\treturn uiDocumentSchema.safeParse(value);\n}\n\nexport function parseNode(value: unknown): UINode {\n\treturn uiNodeSchema.parse(value);\n}\n","import { z } from 'zod';\n\nimport { uiDocumentSchema, uiNodeSchema } from './zod';\n\n/**\n * JSON Schema for the document envelope, for agents emitting documents as tool\n * output and for registry/editor tooling that validates without importing zod.\n */\nexport function toDocumentJsonSchema(): Record<string, unknown> {\n\treturn z.toJSONSchema(uiDocumentSchema, { io: 'input' }) as Record<string, unknown>;\n}\n\nexport function toNodeJsonSchema(): Record<string, unknown> {\n\treturn z.toJSONSchema(uiNodeSchema, { io: 'input' }) as Record<string, unknown>;\n}\n","/**\n * Node-level types for the portable JSON UI document format.\n *\n * A document is a tree of typed nodes. A node's `type` is resolved to a\n * component by the renderer's widget registry, so this package never imports\n * React and stays usable on a server, in an agent, or in a validator.\n */\n\n/** Field widget node types (a form's leaves). */\nexport const WIDGET_NODE_TYPES = [\n\t'Input',\n\t'Textarea',\n\t'Select',\n\t'RadioGroup',\n\t'Checkbox',\n\t'Switch',\n\t'NumberInput',\n\t'DatePicker',\n\t'DateTimePicker',\n\t'TimePicker',\n\t'PhoneInput',\n\t'CodeEditor',\n\t'MarkdownEditor',\n\t'JsonEditor',\n\t'FileUpload',\n] as const;\n\n/** Layout node types that own children. */\nexport const CONTAINER_NODE_TYPES = ['Page', 'Form', 'Grid', 'GridColumn', 'Section', 'Tabs', 'Tab'] as const;\n\n/** Document-level blocks (screens, not fields). */\nexport const BLOCK_NODE_TYPES = [\n\t'DataTable',\n\t'DetailPanel',\n\t'RelationList',\n\t'StatCard',\n\t'Chart',\n\t'ActionBar',\n\t'Markdown',\n\t'AgentChat',\n\t'Button',\n\t'Slot',\n\t'Fragment',\n\t'Custom',\n] as const;\n\nexport type WidgetNodeType = (typeof WIDGET_NODE_TYPES)[number];\nexport type ContainerNodeType = (typeof CONTAINER_NODE_TYPES)[number];\nexport type BlockNodeType = (typeof BLOCK_NODE_TYPES)[number];\n\n/**\n * Known node types. Unknown strings stay valid: a registry may satisfy node\n * types this package has never heard of, and the renderer falls back to an\n * `UnknownBlock` rather than throwing.\n */\nexport type KnownNodeType = WidgetNodeType | ContainerNodeType | BlockNodeType;\nexport type UINodeType = KnownNodeType | (string & {});\n\nexport type InputType = 'text' | 'email' | 'url' | 'password' | 'tel' | 'search';\n\nexport interface UINodeConstraints {\n\tminLength?: number;\n\tmaxLength?: number;\n\tminValue?: number;\n\tmaxValue?: number;\n\tpattern?: string;\n\tprecision?: number;\n\tscale?: number;\n}\n\nexport interface UINodePropsBase {\n\tfieldId?: string;\n\tname?: string;\n\tlabel?: string;\n\tdescription?: string;\n\tplaceholder?: string;\n\trequired?: boolean;\n\thidden?: boolean;\n\tdisabled?: boolean;\n\tdefaultValue?: string | number | boolean | null;\n\tconstraints?: UINodeConstraints;\n\tclassName?: string;\n}\n\nexport type UINodeProps = UINodePropsBase & Record<string, unknown>;\n\n/** Prop name → template expression, e.g. `{ label: '{{ row.title }}' }`. */\nexport interface UIBinding {\n\t[propName: string]: string;\n}\n\nexport interface UIAction {\n\ttype: 'flow' | 'handler';\n\tflowId?: string;\n\thandler?: string;\n\tinputMapping?: Record<string, string>;\n\tparams?: Record<string, unknown>;\n}\n\n/** Event name → action, e.g. `{ submit: { type: 'flow', flowId } }`. */\nexport interface UIActions {\n\t[eventName: string]: UIAction;\n}\n\nexport interface UINode {\n\ttype: UINodeType;\n\tkey: string;\n\tprops: UINodeProps;\n\tchildren: UINode[];\n\tbindings?: UIBinding;\n\tactions?: UIActions;\n}\n\nconst widgetTypes = new Set<string>(WIDGET_NODE_TYPES);\nconst containerTypes = new Set<string>(CONTAINER_NODE_TYPES);\nconst blockTypes = new Set<string>(BLOCK_NODE_TYPES);\n\nexport function isWidgetNodeType(type: string): type is WidgetNodeType {\n\treturn widgetTypes.has(type);\n}\n\nexport function isContainerNodeType(type: string): type is ContainerNodeType {\n\treturn containerTypes.has(type);\n}\n\nexport function isKnownNodeType(type: string): type is KnownNodeType {\n\treturn widgetTypes.has(type) || containerTypes.has(type) || blockTypes.has(type);\n}\n\nexport function isWidgetNode(node: UINode): boolean {\n\treturn isWidgetNodeType(node.type);\n}\n\nexport function isContainerNode(node: UINode): boolean {\n\treturn isContainerNodeType(node.type);\n}\n\n/** Depth-first walk over a node and its descendants. */\nexport function* walkNodes(node: UINode): Generator<UINode> {\n\tyield node;\n\tfor (const child of node.children ?? []) {\n\t\tyield* walkNodes(child);\n\t}\n}\n\n/** Named fields in document order; widget nodes without a `name` are skipped. */\nexport function collectFieldNames(node: UINode): string[] {\n\tconst names: string[] = [];\n\tfor (const current of walkNodes(node)) {\n\t\tif (isWidgetNode(current) && typeof current.props?.name === 'string') {\n\t\t\tnames.push(current.props.name);\n\t\t}\n\t}\n\treturn names;\n}\n\n/** Default values declared by widget nodes, keyed by field name. */\nexport function collectDefaultValues(node: UINode): Record<string, unknown> {\n\tconst values: Record<string, unknown> = {};\n\tfor (const current of walkNodes(node)) {\n\t\tif (!isWidgetNode(current) || typeof current.props?.name !== 'string') continue;\n\t\tif (current.props.defaultValue !== undefined) {\n\t\t\tvalues[current.props.name] = current.props.defaultValue;\n\t\t}\n\t}\n\treturn values;\n}\n\nexport interface FieldConstraintEntry {\n\tconstraints?: UINodeConstraints;\n\trequired?: boolean;\n}\n\n/** Validation metadata declared by widget nodes, keyed by field name. */\nexport function collectFieldConstraints(node: UINode): Record<string, FieldConstraintEntry> {\n\tconst result: Record<string, FieldConstraintEntry> = {};\n\tfor (const current of walkNodes(node)) {\n\t\tif (!isWidgetNode(current) || typeof current.props?.name !== 'string') continue;\n\t\tresult[current.props.name] = {\n\t\t\tconstraints: current.props.constraints,\n\t\t\trequired: current.props.required,\n\t\t};\n\t}\n\treturn result;\n}\n\nexport function findNodeByKey(node: UINode, key: string): UINode | undefined {\n\tfor (const current of walkNodes(node)) {\n\t\tif (current.key === key) return current;\n\t}\n\treturn undefined;\n}\n","import type { UINodeConstraints } from './node';\n\n/**\n * Validate a single field value against the constraints declared on its node.\n * Returns a human-readable message, or `null` when the value is acceptable.\n */\nexport function validateField(value: unknown, constraints?: UINodeConstraints, required?: boolean): string | null {\n\tconst stringValue = value == null ? '' : String(value);\n\tconst isEmpty = stringValue.trim() === '';\n\n\tif (required && isEmpty) {\n\t\treturn 'This field is required';\n\t}\n\n\tif (isEmpty) return null;\n\n\tif (constraints?.minLength != null && stringValue.length < constraints.minLength) {\n\t\treturn `Minimum ${constraints.minLength} characters required`;\n\t}\n\n\tif (constraints?.maxLength != null && stringValue.length > constraints.maxLength) {\n\t\treturn `Maximum ${constraints.maxLength} characters allowed`;\n\t}\n\n\tif (constraints?.minValue != null && typeof value === 'number' && value < constraints.minValue) {\n\t\treturn `Minimum value is ${constraints.minValue}`;\n\t}\n\n\tif (constraints?.maxValue != null && typeof value === 'number' && value > constraints.maxValue) {\n\t\treturn `Maximum value is ${constraints.maxValue}`;\n\t}\n\n\tif (constraints?.pattern) {\n\t\tconst regex = compilePattern(constraints.pattern);\n\t\tif (regex && !regex.test(stringValue)) {\n\t\t\treturn 'Invalid format';\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Patterns arrive from documents authored elsewhere (a JSON Schema, a database\n * check constraint), so an uncompilable one must not take the form down — it is\n * reported as unconstrained rather than as a failed field.\n */\nfunction compilePattern(pattern: string): RegExp | null {\n\ttry {\n\t\treturn new RegExp(pattern);\n\t} catch {\n\t\treturn null;\n\t}\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "blocks-schema",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Portable JSON UI document format for Constructive Blocks: node types, runtime validation, and JSON Schema export",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://constructive-io.github.io/blocks/",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/constructive-io/blocks.git",
|
|
11
|
+
"directory": "packages/blocks-schema"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/constructive-io/blocks/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"require": {
|
|
28
|
+
"types": "./dist/index.d.cts",
|
|
29
|
+
"default": "./dist/index.cjs"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"main": "./dist/index.cjs",
|
|
35
|
+
"module": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"LICENSE",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"dev": "tsup --watch",
|
|
45
|
+
"lint:types": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"clean": "rm -rf dist"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"zod": "^4.3.4"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"tsup": "^8.5.1",
|
|
55
|
+
"typescript": "^5.9.3",
|
|
56
|
+
"vitest": "^3.2.4"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=24.0.0"
|
|
60
|
+
},
|
|
61
|
+
"gitHead": "b0fd1126b68b23543a51949c1e847c4d820863d3"
|
|
62
|
+
}
|