blocks-schema 0.2.0 → 0.4.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 +27 -0
- package/compose.d.ts +40 -0
- package/compose.js +35 -0
- package/core.d.ts +10 -0
- package/core.js +5 -0
- package/envelope.d.ts +48 -0
- package/envelope.js +21 -0
- package/esm/compose.js +29 -0
- package/esm/core.js +1 -0
- package/esm/envelope.js +16 -0
- package/esm/index.js +7 -0
- package/esm/json-schema.js +12 -0
- package/esm/node.js +83 -0
- package/esm/validation.js +11 -0
- package/esm/zod.js +54 -0
- package/index.d.ts +7 -0
- package/index.js +23 -0
- package/json-schema.d.ts +6 -0
- package/json-schema.js +16 -0
- package/node.d.ts +79 -0
- package/node.js +96 -0
- package/package.json +14 -30
- package/validation.d.ts +9 -0
- package/validation.js +14 -0
- package/zod.d.ts +74 -0
- package/zod.js +60 -0
- package/dist/index.cjs +0 -312
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -255
- package/dist/index.d.ts +0 -255
- package/dist/index.js +0 -275
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
# blocks-schema
|
|
2
2
|
|
|
3
|
+
<p align="center" width="100%">
|
|
4
|
+
<img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center" width="100%">
|
|
8
|
+
<a href="https://github.com/constructive-io/blocks/actions/workflows/ci.yml">
|
|
9
|
+
<img height="20" src="https://github.com/constructive-io/blocks/actions/workflows/ci.yml/badge.svg" />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/constructive-io/blocks/blob/main/LICENSE"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/blocks-schema"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/blocks?filename=packages%2Fblocks-schema%2Fpackage.json"/></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
3
15
|
Portable JSON UI document specification for Constructive Blocks: the storage
|
|
4
16
|
format, runtime validators, JSON Schema export, and pure document-manipulation
|
|
5
17
|
API. No React — this package is safe in servers, workers, and agents.
|
|
6
18
|
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add blocks-schema
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The package is built with `makage` and published from `dist`, so every module is
|
|
26
|
+
a root-level entry point and deep imports need no exports map:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { parseDocument } from 'blocks-schema';
|
|
30
|
+
import { composeDocument } from 'blocks-schema/compose';
|
|
31
|
+
import { validateField } from 'blocks-schema/validation';
|
|
32
|
+
```
|
|
33
|
+
|
|
7
34
|
## Overview
|
|
8
35
|
|
|
9
36
|
| Layer | Purpose | File |
|
package/compose.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { NodeOverride as GenericNodeOverride } from 'json-renderer';
|
|
2
|
+
import type { UIDocument } from './envelope';
|
|
3
|
+
import type { UIActions, UIBinding, UINode, UINodeProps, UINodeType } from './node';
|
|
4
|
+
/**
|
|
5
|
+
* A patch applied to the node with a given `key`. Composition is per node, not
|
|
6
|
+
* per document, so a generated default can be customized in a few places
|
|
7
|
+
* without giving up generation.
|
|
8
|
+
*/
|
|
9
|
+
export interface NodeOverride extends GenericNodeOverride<UINodeType, UINodeProps> {
|
|
10
|
+
type?: UINodeType;
|
|
11
|
+
props?: UINodeProps;
|
|
12
|
+
bindings?: UIBinding;
|
|
13
|
+
actions?: UIActions;
|
|
14
|
+
/** Drop the node (and its subtree) from the composed document. */
|
|
15
|
+
remove?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export type NodeOverrides = Record<string, NodeOverride>;
|
|
18
|
+
/** Reusable subtrees addressed by `Fragment` nodes via `props.ref`. */
|
|
19
|
+
export type FragmentMap = Record<string, UINode>;
|
|
20
|
+
/** Subtrees that fill `Slot` nodes, addressed by `props.name`. */
|
|
21
|
+
export type SlotMap = Record<string, UINode | UINode[]>;
|
|
22
|
+
export interface ComposeOptions {
|
|
23
|
+
fragments?: FragmentMap;
|
|
24
|
+
slots?: SlotMap;
|
|
25
|
+
overrides?: NodeOverrides;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Compose a document: expand `Fragment` references, fill `Slot` nodes, then
|
|
29
|
+
* apply per-node overrides. Pure — the input document is never mutated.
|
|
30
|
+
*/
|
|
31
|
+
export declare function composeDocument(document: UIDocument, options?: ComposeOptions): UIDocument;
|
|
32
|
+
export declare function composeNodeTree(node: UINode, options?: ComposeOptions): UINode;
|
|
33
|
+
/**
|
|
34
|
+
* Merge an overlay document onto a generated one by node `key`: hand-authored
|
|
35
|
+
* content wins per node, not per document.
|
|
36
|
+
*/
|
|
37
|
+
export declare function mergeDocuments(base: UIDocument, overlay: Partial<Omit<UIDocument, 'page'>> & {
|
|
38
|
+
page?: UINode;
|
|
39
|
+
}): UIDocument;
|
|
40
|
+
export declare function mergeNodes(base: UINode, overlay: UINode): UINode;
|
package/compose.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.composeDocument = composeDocument;
|
|
4
|
+
exports.composeNodeTree = composeNodeTree;
|
|
5
|
+
exports.mergeDocuments = mergeDocuments;
|
|
6
|
+
exports.mergeNodes = mergeNodes;
|
|
7
|
+
/**
|
|
8
|
+
* Document composition for Constructive documents: the generic ops from
|
|
9
|
+
* `json-renderer`, typed over this package's `UINode`/`UIDocument` and its
|
|
10
|
+
* `Fragment`/`Slot` vocabulary.
|
|
11
|
+
*/
|
|
12
|
+
const json_renderer_1 = require("json-renderer");
|
|
13
|
+
function genericOptions(options) {
|
|
14
|
+
return options;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compose a document: expand `Fragment` references, fill `Slot` nodes, then
|
|
18
|
+
* apply per-node overrides. Pure — the input document is never mutated.
|
|
19
|
+
*/
|
|
20
|
+
function composeDocument(document, options = {}) {
|
|
21
|
+
return (0, json_renderer_1.composeEnvelope)(document, genericOptions(options));
|
|
22
|
+
}
|
|
23
|
+
function composeNodeTree(node, options = {}) {
|
|
24
|
+
return (0, json_renderer_1.composeNodeTree)(node, genericOptions(options));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Merge an overlay document onto a generated one by node `key`: hand-authored
|
|
28
|
+
* content wins per node, not per document.
|
|
29
|
+
*/
|
|
30
|
+
function mergeDocuments(base, overlay) {
|
|
31
|
+
return (0, json_renderer_1.mergeEnvelopes)(base, overlay);
|
|
32
|
+
}
|
|
33
|
+
function mergeNodes(base, overlay) {
|
|
34
|
+
return (0, json_renderer_1.mergeNodeTrees)(base, overlay);
|
|
35
|
+
}
|
package/core.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generic core this package specializes.
|
|
3
|
+
*
|
|
4
|
+
* `blocks-schema` is the Constructive *vocabulary* over `json-renderer`'s
|
|
5
|
+
* framework-agnostic document format. These re-exports let a consumer name the
|
|
6
|
+
* generic types (and the renderer adapter contract) without adding a second
|
|
7
|
+
* dependency, and without this package restating them.
|
|
8
|
+
*/
|
|
9
|
+
export type { AnyDocumentEnvelope, AnyDocumentNode, BindingScope, ComposeVocabulary, DocumentDataSource, DocumentEnvelope, DocumentMetadata, DocumentNode, EnvelopeKind, FieldNodePredicate, FieldStateAccess, NodeAction, NodeConstraints, NodeRegistry, NodeResolution, RegistrySource, RenderContext, RenderContextBase, RendererAdapter, UnknownNodePolicy, } from 'json-renderer';
|
|
10
|
+
export { DOCUMENT_FORMAT_VERSION } from 'json-renderer';
|
package/core.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DOCUMENT_FORMAT_VERSION = void 0;
|
|
4
|
+
var json_renderer_1 = require("json-renderer");
|
|
5
|
+
Object.defineProperty(exports, "DOCUMENT_FORMAT_VERSION", { enumerable: true, get: function () { return json_renderer_1.DOCUMENT_FORMAT_VERSION; } });
|
package/envelope.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { DocumentDataSource, DocumentEnvelope, DocumentMetadata, EnvelopeKind, RegistrySource } from 'json-renderer';
|
|
2
|
+
import type { UINode } from './node';
|
|
3
|
+
export declare const UI_DOCUMENT_FORMAT_VERSION = "1.0";
|
|
4
|
+
export declare const UI_DOCUMENT_TYPE = "UISchema";
|
|
5
|
+
/** The envelope kind this package specializes out of the generic core. */
|
|
6
|
+
export declare const UI_DOCUMENT_KIND: EnvelopeKind<typeof UI_DOCUMENT_TYPE, typeof UI_DOCUMENT_FORMAT_VERSION>;
|
|
7
|
+
export interface UIDocumentMetadata extends DocumentMetadata {
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A resolution source for node types: a shadcn-style registry URL template,
|
|
14
|
+
* e.g. `https://constructive-io.github.io/blocks/r/{name}.json`.
|
|
15
|
+
*/
|
|
16
|
+
export type UIRegistrySource = RegistrySource;
|
|
17
|
+
/** A named, read-only query a document's blocks can bind against. */
|
|
18
|
+
export interface UIDataSource extends DocumentDataSource {
|
|
19
|
+
name: string;
|
|
20
|
+
table?: string;
|
|
21
|
+
query?: string;
|
|
22
|
+
variables?: Record<string, unknown>;
|
|
23
|
+
select?: string;
|
|
24
|
+
where?: Record<string, unknown>;
|
|
25
|
+
orderBy?: unknown;
|
|
26
|
+
first?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The Constructive UI document: the generic `DocumentEnvelope` pinned to this
|
|
30
|
+
* package's node vocabulary, envelope discriminator, and format version.
|
|
31
|
+
*/
|
|
32
|
+
export interface UIDocument extends DocumentEnvelope<UINode, typeof UI_DOCUMENT_TYPE, typeof UI_DOCUMENT_FORMAT_VERSION> {
|
|
33
|
+
meta?: UIDocumentMetadata;
|
|
34
|
+
registries?: UIRegistrySource[];
|
|
35
|
+
dataSources?: UIDataSource[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The name the deployed dashboard form builder uses for the same envelope.
|
|
39
|
+
* Kept as an alias so existing `UISchema` consumers migrate by import swap.
|
|
40
|
+
*/
|
|
41
|
+
export type UISchema = UIDocument;
|
|
42
|
+
export declare function isUIDocument(value: unknown): value is UIDocument;
|
|
43
|
+
/** @deprecated Use {@link isUIDocument}. */
|
|
44
|
+
export declare const isUISchema: typeof isUIDocument;
|
|
45
|
+
export declare function createDocument(page: UINode, options?: {
|
|
46
|
+
id?: string;
|
|
47
|
+
meta?: UIDocumentMetadata;
|
|
48
|
+
}): UIDocument;
|
package/envelope.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isUISchema = exports.UI_DOCUMENT_KIND = exports.UI_DOCUMENT_TYPE = exports.UI_DOCUMENT_FORMAT_VERSION = void 0;
|
|
4
|
+
exports.isUIDocument = isUIDocument;
|
|
5
|
+
exports.createDocument = createDocument;
|
|
6
|
+
const json_renderer_1 = require("json-renderer");
|
|
7
|
+
exports.UI_DOCUMENT_FORMAT_VERSION = '1.0';
|
|
8
|
+
exports.UI_DOCUMENT_TYPE = 'UISchema';
|
|
9
|
+
/** The envelope kind this package specializes out of the generic core. */
|
|
10
|
+
exports.UI_DOCUMENT_KIND = {
|
|
11
|
+
documentType: exports.UI_DOCUMENT_TYPE,
|
|
12
|
+
formatVersion: exports.UI_DOCUMENT_FORMAT_VERSION,
|
|
13
|
+
};
|
|
14
|
+
function isUIDocument(value) {
|
|
15
|
+
return (0, json_renderer_1.isDocumentEnvelope)(value, exports.UI_DOCUMENT_KIND);
|
|
16
|
+
}
|
|
17
|
+
/** @deprecated Use {@link isUIDocument}. */
|
|
18
|
+
exports.isUISchema = isUIDocument;
|
|
19
|
+
function createDocument(page, options = {}) {
|
|
20
|
+
return (0, json_renderer_1.createEnvelope)(exports.UI_DOCUMENT_KIND, page, options);
|
|
21
|
+
}
|
package/esm/compose.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document composition for Constructive documents: the generic ops from
|
|
3
|
+
* `json-renderer`, typed over this package's `UINode`/`UIDocument` and its
|
|
4
|
+
* `Fragment`/`Slot` vocabulary.
|
|
5
|
+
*/
|
|
6
|
+
import { composeEnvelope, composeNodeTree as composeGenericNodeTree, mergeEnvelopes, mergeNodeTrees } from 'json-renderer';
|
|
7
|
+
function genericOptions(options) {
|
|
8
|
+
return options;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Compose a document: expand `Fragment` references, fill `Slot` nodes, then
|
|
12
|
+
* apply per-node overrides. Pure — the input document is never mutated.
|
|
13
|
+
*/
|
|
14
|
+
export function composeDocument(document, options = {}) {
|
|
15
|
+
return composeEnvelope(document, genericOptions(options));
|
|
16
|
+
}
|
|
17
|
+
export function composeNodeTree(node, options = {}) {
|
|
18
|
+
return composeGenericNodeTree(node, genericOptions(options));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Merge an overlay document onto a generated one by node `key`: hand-authored
|
|
22
|
+
* content wins per node, not per document.
|
|
23
|
+
*/
|
|
24
|
+
export function mergeDocuments(base, overlay) {
|
|
25
|
+
return mergeEnvelopes(base, overlay);
|
|
26
|
+
}
|
|
27
|
+
export function mergeNodes(base, overlay) {
|
|
28
|
+
return mergeNodeTrees(base, overlay);
|
|
29
|
+
}
|
package/esm/core.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DOCUMENT_FORMAT_VERSION } from 'json-renderer';
|
package/esm/envelope.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createEnvelope, isDocumentEnvelope } from 'json-renderer';
|
|
2
|
+
export const UI_DOCUMENT_FORMAT_VERSION = '1.0';
|
|
3
|
+
export const UI_DOCUMENT_TYPE = 'UISchema';
|
|
4
|
+
/** The envelope kind this package specializes out of the generic core. */
|
|
5
|
+
export const UI_DOCUMENT_KIND = {
|
|
6
|
+
documentType: UI_DOCUMENT_TYPE,
|
|
7
|
+
formatVersion: UI_DOCUMENT_FORMAT_VERSION,
|
|
8
|
+
};
|
|
9
|
+
export function isUIDocument(value) {
|
|
10
|
+
return isDocumentEnvelope(value, UI_DOCUMENT_KIND);
|
|
11
|
+
}
|
|
12
|
+
/** @deprecated Use {@link isUIDocument}. */
|
|
13
|
+
export const isUISchema = isUIDocument;
|
|
14
|
+
export function createDocument(page, options = {}) {
|
|
15
|
+
return createEnvelope(UI_DOCUMENT_KIND, page, options);
|
|
16
|
+
}
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { toJsonSchema } from 'json-renderer';
|
|
2
|
+
import { uiDocumentSchema, uiNodeSchema } from './zod';
|
|
3
|
+
/**
|
|
4
|
+
* JSON Schema for the document envelope, for agents emitting documents as tool
|
|
5
|
+
* output and for registry/editor tooling that validates without importing zod.
|
|
6
|
+
*/
|
|
7
|
+
export function toDocumentJsonSchema() {
|
|
8
|
+
return toJsonSchema(uiDocumentSchema);
|
|
9
|
+
}
|
|
10
|
+
export function toNodeJsonSchema() {
|
|
11
|
+
return toJsonSchema(uiNodeSchema);
|
|
12
|
+
}
|
package/esm/node.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-level types for the Constructive JSON UI document format.
|
|
3
|
+
*
|
|
4
|
+
* The tree model, the walk, and the field-collection primitives live in
|
|
5
|
+
* `json-renderer`; this module owns the Constructive *vocabulary* — which node
|
|
6
|
+
* types exist and which of them are fields — and specializes the generic
|
|
7
|
+
* helpers over it. A node's `type` is resolved to a component by the renderer's
|
|
8
|
+
* widget registry, so this package never imports React and stays usable on a
|
|
9
|
+
* server, in an agent, or in a validator.
|
|
10
|
+
*/
|
|
11
|
+
import { collectDefaultValues as collectNodeDefaultValues, collectFieldConstraints as collectNodeFieldConstraints, collectFieldNames as collectNodeFieldNames, findNodeByKey as findNodeInTreeByKey, walkNodes as walkNodeTree, } from 'json-renderer';
|
|
12
|
+
/** Field widget node types (a form's leaves). */
|
|
13
|
+
export const WIDGET_NODE_TYPES = [
|
|
14
|
+
'Input',
|
|
15
|
+
'Textarea',
|
|
16
|
+
'Select',
|
|
17
|
+
'RadioGroup',
|
|
18
|
+
'Checkbox',
|
|
19
|
+
'Switch',
|
|
20
|
+
'NumberInput',
|
|
21
|
+
'DatePicker',
|
|
22
|
+
'DateTimePicker',
|
|
23
|
+
'TimePicker',
|
|
24
|
+
'PhoneInput',
|
|
25
|
+
'CodeEditor',
|
|
26
|
+
'MarkdownEditor',
|
|
27
|
+
'JsonEditor',
|
|
28
|
+
'FileUpload',
|
|
29
|
+
];
|
|
30
|
+
/** Layout node types that own children. */
|
|
31
|
+
export const CONTAINER_NODE_TYPES = ['Page', 'Form', 'Grid', 'GridColumn', 'Section', 'Tabs', 'Tab'];
|
|
32
|
+
/** Document-level blocks (screens, not fields). */
|
|
33
|
+
export const BLOCK_NODE_TYPES = [
|
|
34
|
+
'DataTable',
|
|
35
|
+
'DetailPanel',
|
|
36
|
+
'RelationList',
|
|
37
|
+
'StatCard',
|
|
38
|
+
'Chart',
|
|
39
|
+
'ActionBar',
|
|
40
|
+
'Markdown',
|
|
41
|
+
'AgentChat',
|
|
42
|
+
'Button',
|
|
43
|
+
'Slot',
|
|
44
|
+
'Fragment',
|
|
45
|
+
'Custom',
|
|
46
|
+
];
|
|
47
|
+
const widgetTypes = new Set(WIDGET_NODE_TYPES);
|
|
48
|
+
const containerTypes = new Set(CONTAINER_NODE_TYPES);
|
|
49
|
+
const blockTypes = new Set(BLOCK_NODE_TYPES);
|
|
50
|
+
export function isWidgetNodeType(type) {
|
|
51
|
+
return widgetTypes.has(type);
|
|
52
|
+
}
|
|
53
|
+
export function isContainerNodeType(type) {
|
|
54
|
+
return containerTypes.has(type);
|
|
55
|
+
}
|
|
56
|
+
export function isKnownNodeType(type) {
|
|
57
|
+
return widgetTypes.has(type) || containerTypes.has(type) || blockTypes.has(type);
|
|
58
|
+
}
|
|
59
|
+
export function isWidgetNode(node) {
|
|
60
|
+
return isWidgetNodeType(node.type);
|
|
61
|
+
}
|
|
62
|
+
export function isContainerNode(node) {
|
|
63
|
+
return isContainerNodeType(node.type);
|
|
64
|
+
}
|
|
65
|
+
/** Depth-first walk over a node and its descendants. */
|
|
66
|
+
export function walkNodes(node) {
|
|
67
|
+
return walkNodeTree(node);
|
|
68
|
+
}
|
|
69
|
+
/** Named fields in document order; widget nodes without a `name` are skipped. */
|
|
70
|
+
export function collectFieldNames(node) {
|
|
71
|
+
return collectNodeFieldNames(node, isWidgetNode);
|
|
72
|
+
}
|
|
73
|
+
/** Default values declared by widget nodes, keyed by field name. */
|
|
74
|
+
export function collectDefaultValues(node) {
|
|
75
|
+
return collectNodeDefaultValues(node, isWidgetNode);
|
|
76
|
+
}
|
|
77
|
+
/** Validation metadata declared by widget nodes, keyed by field name. */
|
|
78
|
+
export function collectFieldConstraints(node) {
|
|
79
|
+
return collectNodeFieldConstraints(node, isWidgetNode);
|
|
80
|
+
}
|
|
81
|
+
export function findNodeByKey(node, key) {
|
|
82
|
+
return findNodeInTreeByKey(node, key);
|
|
83
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { validateValue } from 'json-renderer';
|
|
2
|
+
/**
|
|
3
|
+
* Validate a single field value against the constraints declared on its node.
|
|
4
|
+
* Returns a human-readable message, or `null` when the value is acceptable.
|
|
5
|
+
*
|
|
6
|
+
* The check itself is `json-renderer`'s `validateValue`; this is the named,
|
|
7
|
+
* vocabulary-typed entry point Constructive consumers already import.
|
|
8
|
+
*/
|
|
9
|
+
export function validateField(value, constraints, required) {
|
|
10
|
+
return validateValue(value, constraints, required);
|
|
11
|
+
}
|
package/esm/zod.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime validation for Constructive documents. The tree and envelope rules
|
|
3
|
+
* come from `json-renderer`'s schema factories; this module narrows props and
|
|
4
|
+
* actions to the Constructive vocabulary.
|
|
5
|
+
*/
|
|
6
|
+
import { createDocumentSchema, createNodeSchema, dataSourceSchema, documentMetadataSchema, nodeBindingsSchema, nodeConstraintsSchema, nodePropsSchema, registrySourceSchema, } from 'json-renderer';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { UI_DOCUMENT_KIND } from './envelope';
|
|
9
|
+
export const uiNodeConstraintsSchema = nodeConstraintsSchema;
|
|
10
|
+
export const uiNodePropsSchema = nodePropsSchema.extend({
|
|
11
|
+
fieldId: z.string().optional(),
|
|
12
|
+
name: z.string().optional(),
|
|
13
|
+
label: z.string().optional(),
|
|
14
|
+
description: z.string().optional(),
|
|
15
|
+
placeholder: z.string().optional(),
|
|
16
|
+
required: z.boolean().optional(),
|
|
17
|
+
hidden: z.boolean().optional(),
|
|
18
|
+
disabled: z.boolean().optional(),
|
|
19
|
+
defaultValue: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),
|
|
20
|
+
constraints: uiNodeConstraintsSchema.optional(),
|
|
21
|
+
className: z.string().optional(),
|
|
22
|
+
});
|
|
23
|
+
export const uiBindingSchema = nodeBindingsSchema;
|
|
24
|
+
export const uiActionSchema = z.object({
|
|
25
|
+
type: z.enum(['flow', 'handler']),
|
|
26
|
+
flowId: z.string().optional(),
|
|
27
|
+
handler: z.string().optional(),
|
|
28
|
+
inputMapping: z.record(z.string(), z.string()).optional(),
|
|
29
|
+
params: z.record(z.string(), z.unknown()).optional(),
|
|
30
|
+
});
|
|
31
|
+
export const uiActionsSchema = z.record(z.string(), uiActionSchema);
|
|
32
|
+
export const uiNodeSchema = createNodeSchema({
|
|
33
|
+
propsSchema: uiNodePropsSchema,
|
|
34
|
+
actionsSchema: uiActionsSchema,
|
|
35
|
+
});
|
|
36
|
+
export const uiRegistrySourceSchema = registrySourceSchema;
|
|
37
|
+
export const uiDataSourceSchema = dataSourceSchema.extend({
|
|
38
|
+
table: z.string().optional(),
|
|
39
|
+
});
|
|
40
|
+
export const uiDocumentMetadataSchema = documentMetadataSchema;
|
|
41
|
+
export const uiDocumentSchema = createDocumentSchema({
|
|
42
|
+
kind: UI_DOCUMENT_KIND,
|
|
43
|
+
nodeSchema: uiNodeSchema,
|
|
44
|
+
});
|
|
45
|
+
/** Throws a `ZodError` describing every problem in the document. */
|
|
46
|
+
export function parseDocument(value) {
|
|
47
|
+
return uiDocumentSchema.parse(value);
|
|
48
|
+
}
|
|
49
|
+
export function safeParseDocument(value) {
|
|
50
|
+
return uiDocumentSchema.safeParse(value);
|
|
51
|
+
}
|
|
52
|
+
export function parseNode(value) {
|
|
53
|
+
return uiNodeSchema.parse(value);
|
|
54
|
+
}
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./compose"), exports);
|
|
18
|
+
__exportStar(require("./core"), exports);
|
|
19
|
+
__exportStar(require("./envelope"), exports);
|
|
20
|
+
__exportStar(require("./json-schema"), exports);
|
|
21
|
+
__exportStar(require("./node"), exports);
|
|
22
|
+
__exportStar(require("./validation"), exports);
|
|
23
|
+
__exportStar(require("./zod"), exports);
|
package/json-schema.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema for the document envelope, for agents emitting documents as tool
|
|
3
|
+
* output and for registry/editor tooling that validates without importing zod.
|
|
4
|
+
*/
|
|
5
|
+
export declare function toDocumentJsonSchema(): Record<string, unknown>;
|
|
6
|
+
export declare function toNodeJsonSchema(): Record<string, unknown>;
|
package/json-schema.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toDocumentJsonSchema = toDocumentJsonSchema;
|
|
4
|
+
exports.toNodeJsonSchema = toNodeJsonSchema;
|
|
5
|
+
const json_renderer_1 = require("json-renderer");
|
|
6
|
+
const zod_1 = require("./zod");
|
|
7
|
+
/**
|
|
8
|
+
* JSON Schema for the document envelope, for agents emitting documents as tool
|
|
9
|
+
* output and for registry/editor tooling that validates without importing zod.
|
|
10
|
+
*/
|
|
11
|
+
function toDocumentJsonSchema() {
|
|
12
|
+
return (0, json_renderer_1.toJsonSchema)(zod_1.uiDocumentSchema);
|
|
13
|
+
}
|
|
14
|
+
function toNodeJsonSchema() {
|
|
15
|
+
return (0, json_renderer_1.toJsonSchema)(zod_1.uiNodeSchema);
|
|
16
|
+
}
|
package/node.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { DocumentNode, FieldConstraintEntry, NodeActions, NodeBindings, NodeConstraints, NodeProps } from 'json-renderer';
|
|
2
|
+
export type { FieldConstraintEntry } from 'json-renderer';
|
|
3
|
+
/** Field widget node types (a form's leaves). */
|
|
4
|
+
export declare const WIDGET_NODE_TYPES: readonly ["Input", "Textarea", "Select", "RadioGroup", "Checkbox", "Switch", "NumberInput", "DatePicker", "DateTimePicker", "TimePicker", "PhoneInput", "CodeEditor", "MarkdownEditor", "JsonEditor", "FileUpload"];
|
|
5
|
+
/** Layout node types that own children. */
|
|
6
|
+
export declare const CONTAINER_NODE_TYPES: readonly ["Page", "Form", "Grid", "GridColumn", "Section", "Tabs", "Tab"];
|
|
7
|
+
/** Document-level blocks (screens, not fields). */
|
|
8
|
+
export declare const BLOCK_NODE_TYPES: readonly ["DataTable", "DetailPanel", "RelationList", "StatCard", "Chart", "ActionBar", "Markdown", "AgentChat", "Button", "Slot", "Fragment", "Custom"];
|
|
9
|
+
export type WidgetNodeType = (typeof WIDGET_NODE_TYPES)[number];
|
|
10
|
+
export type ContainerNodeType = (typeof CONTAINER_NODE_TYPES)[number];
|
|
11
|
+
export type BlockNodeType = (typeof BLOCK_NODE_TYPES)[number];
|
|
12
|
+
/**
|
|
13
|
+
* Known node types. Unknown strings stay valid: a registry may satisfy node
|
|
14
|
+
* types this package has never heard of, and the renderer falls back to an
|
|
15
|
+
* `UnknownBlock` rather than throwing.
|
|
16
|
+
*/
|
|
17
|
+
export type KnownNodeType = WidgetNodeType | ContainerNodeType | BlockNodeType;
|
|
18
|
+
export type UINodeType = KnownNodeType | (string & {});
|
|
19
|
+
export type InputType = 'text' | 'email' | 'url' | 'password' | 'tel' | 'search';
|
|
20
|
+
export interface UINodeConstraints extends NodeConstraints {
|
|
21
|
+
minLength?: number;
|
|
22
|
+
maxLength?: number;
|
|
23
|
+
minValue?: number;
|
|
24
|
+
maxValue?: number;
|
|
25
|
+
pattern?: string;
|
|
26
|
+
precision?: number;
|
|
27
|
+
scale?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface UINodePropsBase {
|
|
30
|
+
fieldId?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
label?: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
required?: boolean;
|
|
36
|
+
hidden?: boolean;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
defaultValue?: string | number | boolean | null;
|
|
39
|
+
constraints?: UINodeConstraints;
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
export type UINodeProps = UINodePropsBase & Record<string, unknown>;
|
|
43
|
+
/** Prop name → template expression, e.g. `{ label: '{{ row.title }}' }`. */
|
|
44
|
+
export interface UIBinding extends NodeBindings {
|
|
45
|
+
[propName: string]: string;
|
|
46
|
+
}
|
|
47
|
+
export interface UIAction {
|
|
48
|
+
type: 'flow' | 'handler';
|
|
49
|
+
flowId?: string;
|
|
50
|
+
handler?: string;
|
|
51
|
+
inputMapping?: Record<string, string>;
|
|
52
|
+
params?: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
/** Event name → action, e.g. `{ submit: { type: 'flow', flowId } }`. */
|
|
55
|
+
export interface UIActions {
|
|
56
|
+
[eventName: string]: UIAction;
|
|
57
|
+
}
|
|
58
|
+
/** The generic node tree pinned to the Constructive vocabulary and props. */
|
|
59
|
+
export interface UINode extends DocumentNode<UINodeType, UINodeProps> {
|
|
60
|
+
children: UINode[];
|
|
61
|
+
bindings?: UIBinding;
|
|
62
|
+
actions?: UIActions;
|
|
63
|
+
}
|
|
64
|
+
export declare function isWidgetNodeType(type: string): type is WidgetNodeType;
|
|
65
|
+
export declare function isContainerNodeType(type: string): type is ContainerNodeType;
|
|
66
|
+
export declare function isKnownNodeType(type: string): type is KnownNodeType;
|
|
67
|
+
export declare function isWidgetNode(node: UINode): boolean;
|
|
68
|
+
export declare function isContainerNode(node: UINode): boolean;
|
|
69
|
+
/** Depth-first walk over a node and its descendants. */
|
|
70
|
+
export declare function walkNodes(node: UINode): Generator<UINode>;
|
|
71
|
+
/** Named fields in document order; widget nodes without a `name` are skipped. */
|
|
72
|
+
export declare function collectFieldNames(node: UINode): string[];
|
|
73
|
+
/** Default values declared by widget nodes, keyed by field name. */
|
|
74
|
+
export declare function collectDefaultValues(node: UINode): Record<string, unknown>;
|
|
75
|
+
/** Validation metadata declared by widget nodes, keyed by field name. */
|
|
76
|
+
export declare function collectFieldConstraints(node: UINode): Record<string, FieldConstraintEntry>;
|
|
77
|
+
export declare function findNodeByKey(node: UINode, key: string): UINode | undefined;
|
|
78
|
+
/** Re-exported so `UINodeProps` consumers can name the generic props shape. */
|
|
79
|
+
export type { NodeActions, NodeBindings, NodeProps };
|
package/node.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BLOCK_NODE_TYPES = exports.CONTAINER_NODE_TYPES = exports.WIDGET_NODE_TYPES = void 0;
|
|
4
|
+
exports.isWidgetNodeType = isWidgetNodeType;
|
|
5
|
+
exports.isContainerNodeType = isContainerNodeType;
|
|
6
|
+
exports.isKnownNodeType = isKnownNodeType;
|
|
7
|
+
exports.isWidgetNode = isWidgetNode;
|
|
8
|
+
exports.isContainerNode = isContainerNode;
|
|
9
|
+
exports.walkNodes = walkNodes;
|
|
10
|
+
exports.collectFieldNames = collectFieldNames;
|
|
11
|
+
exports.collectDefaultValues = collectDefaultValues;
|
|
12
|
+
exports.collectFieldConstraints = collectFieldConstraints;
|
|
13
|
+
exports.findNodeByKey = findNodeByKey;
|
|
14
|
+
/**
|
|
15
|
+
* Node-level types for the Constructive JSON UI document format.
|
|
16
|
+
*
|
|
17
|
+
* The tree model, the walk, and the field-collection primitives live in
|
|
18
|
+
* `json-renderer`; this module owns the Constructive *vocabulary* — which node
|
|
19
|
+
* types exist and which of them are fields — and specializes the generic
|
|
20
|
+
* helpers over it. A node's `type` is resolved to a component by the renderer's
|
|
21
|
+
* widget registry, so this package never imports React and stays usable on a
|
|
22
|
+
* server, in an agent, or in a validator.
|
|
23
|
+
*/
|
|
24
|
+
const json_renderer_1 = require("json-renderer");
|
|
25
|
+
/** Field widget node types (a form's leaves). */
|
|
26
|
+
exports.WIDGET_NODE_TYPES = [
|
|
27
|
+
'Input',
|
|
28
|
+
'Textarea',
|
|
29
|
+
'Select',
|
|
30
|
+
'RadioGroup',
|
|
31
|
+
'Checkbox',
|
|
32
|
+
'Switch',
|
|
33
|
+
'NumberInput',
|
|
34
|
+
'DatePicker',
|
|
35
|
+
'DateTimePicker',
|
|
36
|
+
'TimePicker',
|
|
37
|
+
'PhoneInput',
|
|
38
|
+
'CodeEditor',
|
|
39
|
+
'MarkdownEditor',
|
|
40
|
+
'JsonEditor',
|
|
41
|
+
'FileUpload',
|
|
42
|
+
];
|
|
43
|
+
/** Layout node types that own children. */
|
|
44
|
+
exports.CONTAINER_NODE_TYPES = ['Page', 'Form', 'Grid', 'GridColumn', 'Section', 'Tabs', 'Tab'];
|
|
45
|
+
/** Document-level blocks (screens, not fields). */
|
|
46
|
+
exports.BLOCK_NODE_TYPES = [
|
|
47
|
+
'DataTable',
|
|
48
|
+
'DetailPanel',
|
|
49
|
+
'RelationList',
|
|
50
|
+
'StatCard',
|
|
51
|
+
'Chart',
|
|
52
|
+
'ActionBar',
|
|
53
|
+
'Markdown',
|
|
54
|
+
'AgentChat',
|
|
55
|
+
'Button',
|
|
56
|
+
'Slot',
|
|
57
|
+
'Fragment',
|
|
58
|
+
'Custom',
|
|
59
|
+
];
|
|
60
|
+
const widgetTypes = new Set(exports.WIDGET_NODE_TYPES);
|
|
61
|
+
const containerTypes = new Set(exports.CONTAINER_NODE_TYPES);
|
|
62
|
+
const blockTypes = new Set(exports.BLOCK_NODE_TYPES);
|
|
63
|
+
function isWidgetNodeType(type) {
|
|
64
|
+
return widgetTypes.has(type);
|
|
65
|
+
}
|
|
66
|
+
function isContainerNodeType(type) {
|
|
67
|
+
return containerTypes.has(type);
|
|
68
|
+
}
|
|
69
|
+
function isKnownNodeType(type) {
|
|
70
|
+
return widgetTypes.has(type) || containerTypes.has(type) || blockTypes.has(type);
|
|
71
|
+
}
|
|
72
|
+
function isWidgetNode(node) {
|
|
73
|
+
return isWidgetNodeType(node.type);
|
|
74
|
+
}
|
|
75
|
+
function isContainerNode(node) {
|
|
76
|
+
return isContainerNodeType(node.type);
|
|
77
|
+
}
|
|
78
|
+
/** Depth-first walk over a node and its descendants. */
|
|
79
|
+
function walkNodes(node) {
|
|
80
|
+
return (0, json_renderer_1.walkNodes)(node);
|
|
81
|
+
}
|
|
82
|
+
/** Named fields in document order; widget nodes without a `name` are skipped. */
|
|
83
|
+
function collectFieldNames(node) {
|
|
84
|
+
return (0, json_renderer_1.collectFieldNames)(node, isWidgetNode);
|
|
85
|
+
}
|
|
86
|
+
/** Default values declared by widget nodes, keyed by field name. */
|
|
87
|
+
function collectDefaultValues(node) {
|
|
88
|
+
return (0, json_renderer_1.collectDefaultValues)(node, isWidgetNode);
|
|
89
|
+
}
|
|
90
|
+
/** Validation metadata declared by widget nodes, keyed by field name. */
|
|
91
|
+
function collectFieldConstraints(node) {
|
|
92
|
+
return (0, json_renderer_1.collectFieldConstraints)(node, isWidgetNode);
|
|
93
|
+
}
|
|
94
|
+
function findNodeByKey(node, key) {
|
|
95
|
+
return (0, json_renderer_1.findNodeByKey)(node, key);
|
|
96
|
+
}
|