@golemui/schemas 1.5.1 → 1.6.0-rc.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.
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Public types of the `@golemui/schemas/json-schema` entry: a converter from a JSON Schema (the
3
+ * shape of the form data) to a GolemUI form definition.
4
+ *
5
+ * The converter runs in browsers and Workers too, so these types describe plain JSON and never
6
+ * reference `@golemui/core`. The widget-set-specific part lives in a {@link Preset}.
7
+ */
8
+ /** A JSON value, as found in a JSON Schema or in a form definition. */
9
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
10
+ [key: string]: JsonValue;
11
+ };
12
+ /**
13
+ * A JSON Schema object. Keywords are read when needed, so draft-07, 2019-09, 2020-12 and
14
+ * OpenAPI 3.x schema objects all fit.
15
+ */
16
+ export type JsonSchema = {
17
+ [keyword: string]: unknown;
18
+ };
19
+ /** The JSON Schema types a node can resolve to. */
20
+ export type JsonType = 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
21
+ /** A text that is either literal or an i18n key, the JSON form of core's `Localizable`. */
22
+ export type Localizable = string | {
23
+ key: string;
24
+ default?: string;
25
+ params?: Record<string, unknown>;
26
+ };
27
+ /**
28
+ * A widget of the produced form definition, as plain JSON. Keys that are not listed here are
29
+ * allowed, for example `validator`, `defaultValue` or state-suffixed keys like `validator.us`.
30
+ */
31
+ export type FormWidgetJson = {
32
+ kind: 'input' | 'layout' | 'display' | 'action';
33
+ type: string;
34
+ uid?: string;
35
+ path?: string;
36
+ label?: Localizable;
37
+ props?: Record<string, unknown>;
38
+ children?: FormWidgetJson[];
39
+ include?: {
40
+ when: string;
41
+ } | {
42
+ in: string[];
43
+ };
44
+ [key: string]: unknown;
45
+ };
46
+ /** A form definition as plain JSON, ready to pass as `formDef` or to write to a file. */
47
+ export type FormDefinitionJson = {
48
+ $schema?: string;
49
+ /** State name to reactive expression, e.g. `{ if01: '$form.country === "US"' }`. */
50
+ states?: Record<string, string>;
51
+ form: FormWidgetJson[];
52
+ };
53
+ /**
54
+ * One node of the input schema, as rules and builders see it.
55
+ *
56
+ * @example
57
+ * // The `quantity` property of the items of a `lines` array:
58
+ * // { path: 'lines.items.quantity', name: 'quantity', type: 'integer', repeaterDepth: 1, ... }
59
+ */
60
+ export interface SchemaNode {
61
+ /** The node schema after `$ref` resolution, `allOf` merge and nullable unwrap. */
62
+ readonly schema: JsonSchema;
63
+ /** The node type, when the schema has exactly one type besides `null`. */
64
+ readonly type?: JsonType;
65
+ /** True when the schema also allows `null`. */
66
+ readonly nullable: boolean;
67
+ /**
68
+ * The form data path, `''` for the root. Array items use the `items` token that repeater
69
+ * templates expect, e.g. `lines.items.quantity`.
70
+ */
71
+ readonly path: string;
72
+ /** The property name. Undefined for the root and for array items. */
73
+ readonly name?: string;
74
+ /** The name of the `$defs`, `definitions` or `components/schemas` entry the node came from. */
75
+ readonly defName?: string;
76
+ /** JSON pointer to the node in the input schema, e.g. `/properties/lines/items`. */
77
+ readonly pointer: string;
78
+ /** True when the parent object always requires this property. */
79
+ readonly required: boolean;
80
+ /** How many array items the node is inside, e.g. 1 for `lines.items.quantity`. */
81
+ readonly repeaterDepth: number;
82
+ /** The value of the vendor keyword (`x-golemui` by default) on this node, when present. */
83
+ readonly hint?: WidgetPatch;
84
+ /** The parent node. Undefined for the root. */
85
+ readonly parent?: SchemaNode;
86
+ }
87
+ /**
88
+ * Widget fields that a customization layer sets. A layer that names a `widget` chooses the
89
+ * builder. The other fields are merged onto the built widget, `props` key by key.
90
+ */
91
+ export type WidgetPatch = {
92
+ /** The widget `type` to build, e.g. `textarea`. The preset must know it. */
93
+ widget?: string;
94
+ label?: Localizable;
95
+ props?: Record<string, unknown>;
96
+ validator?: Record<string, unknown>;
97
+ defaultValue?: JsonValue;
98
+ readonly?: boolean;
99
+ /** Width in the 12-column grid of the parent layout. */
100
+ size?: number;
101
+ uid?: string;
102
+ /** Leaves the node, and everything below it, out of the form. */
103
+ skip?: boolean;
104
+ /** Property order of an object node. `'*'` stands for every property not listed. */
105
+ order?: string[];
106
+ };
107
+ /**
108
+ * What a builder returns: one widget, several widgets, `null` to render nothing, or `undefined`
109
+ * to decline, so the next matching rule builds the node.
110
+ */
111
+ export type BuildResult = FormWidgetJson | FormWidgetJson[] | null | undefined;
112
+ /** A predicate rule. The first rule whose `when` returns true builds the node. */
113
+ export interface Rule {
114
+ /** Shown in diagnostics. */
115
+ name?: string;
116
+ when(node: SchemaNode): boolean;
117
+ build(node: SchemaNode, context: BuildContext): BuildResult;
118
+ }
119
+ /**
120
+ * An operator in a {@link DeclarativeRule} match: the value is one of a list, the keyword is
121
+ * present or absent, or the value matches a regular expression.
122
+ */
123
+ export type MatchOperator = {
124
+ $in: JsonValue[];
125
+ } | {
126
+ $exists: boolean;
127
+ } | {
128
+ $regex: string;
129
+ };
130
+ /**
131
+ * A rule that is plain JSON, so it can live in a config file or come from an MCP tool call.
132
+ *
133
+ * `match` keys are schema keywords, compared by deep equality or with a {@link MatchOperator}.
134
+ * These keys match node fields instead: `$type`, `$path` (a glob, `*` is one segment and `**`
135
+ * any number), `$name`, `$defName`, `$required` and `$inRepeater`.
136
+ *
137
+ * @example
138
+ * const emailRule: DeclarativeRule = {
139
+ * match: { $type: 'string', format: 'email' },
140
+ * widget: 'textinput',
141
+ * props: { icon: 'mail' },
142
+ * };
143
+ */
144
+ export interface DeclarativeRule extends WidgetPatch {
145
+ /** Shown in diagnostics. */
146
+ name?: string;
147
+ match: {
148
+ [key: string]: JsonValue | MatchOperator;
149
+ };
150
+ }
151
+ /** What builders get besides the node. */
152
+ export interface BuildContext {
153
+ /** Builds the node with the next matching rule, so a rule can change one detail of the default. */
154
+ next(node?: SchemaNode): BuildResult;
155
+ /** Builds any node with every layer, e.g. a child node. */
156
+ build(node: SchemaNode): BuildResult;
157
+ /**
158
+ * The child nodes in render order: the properties of an object node, or the positions of a
159
+ * tuple node (`prefixItems`), with paths like `point.0`.
160
+ */
161
+ children(node: SchemaNode): SchemaNode[];
162
+ /** Builds {@link BuildContext.children}, compiled conditionals included. */
163
+ buildChildren(node: SchemaNode): FormWidgetJson[];
164
+ /** The item node of an array node, with the path `<array path>.items`. */
165
+ item(node: SchemaNode): SchemaNode | undefined;
166
+ /** The widget label: the schema `title`, otherwise the property name in readable form. */
167
+ label(node: SchemaNode): Localizable | undefined;
168
+ /** The widget-set validator for the node, from {@link Preset.validator}. */
169
+ validator(node: SchemaNode): Record<string, unknown> | undefined;
170
+ /** The options of an `enum` node, or of a `oneOf`/`anyOf` node made of `const` values. */
171
+ enumOptions(node: SchemaNode): EnumOption[] | undefined;
172
+ /** Reports something the converter could not express exactly. */
173
+ diagnostic(diagnostic: DiagnosticInput): void;
174
+ }
175
+ /** One option of an enumeration. */
176
+ export type EnumOption = {
177
+ label: Localizable;
178
+ value: string | number | boolean | null;
179
+ };
180
+ /** Options of {@link Preset.group}. */
181
+ export type GroupOptions = {
182
+ /** Set for conditional branches. Without it, the form assigns a position uid. */
183
+ uid?: string;
184
+ /** Visibility condition, for conditional branches. */
185
+ include?: {
186
+ when: string;
187
+ };
188
+ /** The object title, when the preset renders one. */
189
+ title?: Localizable;
190
+ };
191
+ /** Builds one widget type for a node. */
192
+ export type WidgetBuilder = (node: SchemaNode, context: BuildContext) => FormWidgetJson;
193
+ /**
194
+ * The widget-set-specific part of the converter. A widget set ships one, e.g. the gui preset in
195
+ * `@golemui/gui-schemas/json-schema`.
196
+ */
197
+ export interface Preset {
198
+ /** Shown in diagnostics. */
199
+ name: string;
200
+ /** The default rules, checked after the user rules. */
201
+ rules: Rule[];
202
+ /** Builders by widget type, used when a layer names a `widget`. */
203
+ widgets: Record<string, WidgetBuilder>;
204
+ /** The widget-set validator for a node, `undefined` when there is nothing to validate. */
205
+ validator(node: SchemaNode): Record<string, unknown> | undefined;
206
+ /** Wraps widgets in a layout, for object groups and conditional branches. */
207
+ group(children: FormWidgetJson[], options: GroupOptions): FormWidgetJson;
208
+ /** Builds the top-level widget list, e.g. adds a submit button. */
209
+ root(children: FormWidgetJson[]): FormWidgetJson[];
210
+ /** Written to `$schema` of the form definition. */
211
+ schemaUrl?: string;
212
+ }
213
+ /** Options of the converter. */
214
+ export interface ConvertOptions {
215
+ preset: Preset;
216
+ /** User rules, predicate and declarative in one list, checked in order before the preset rules. */
217
+ rules?: (Rule | DeclarativeRule)[];
218
+ /**
219
+ * Patches by form data path (`''` for the root, `lines.items.quantity` inside arrays) or by
220
+ * definition name (`$defs/Address`). A path override wins over a definition name override.
221
+ */
222
+ overrides?: Record<string, WidgetPatch>;
223
+ /** The document `$ref` pointers resolve against, e.g. an OpenAPI document. Defaults to the schema. */
224
+ refRoot?: JsonSchema;
225
+ /** The vendor keyword read from the schema, or `false` to ignore hints. Defaults to `x-golemui`. */
226
+ vendorKeyword?: string | false;
227
+ /** How many times a `$ref` may repeat on one branch before recursion stops. Defaults to 2. */
228
+ maxRefDepth?: number;
229
+ /** Stops the conversion after this many nodes. Defaults to 2000. */
230
+ maxNodes?: number;
231
+ /** Last change to the produced form definition. */
232
+ transform?(definition: FormDefinitionJson): FormDefinitionJson;
233
+ }
234
+ /** What the converter returns. It never throws because of the schema shape. */
235
+ export interface ConvertResult {
236
+ formDefinition: FormDefinitionJson;
237
+ diagnostics: Diagnostic[];
238
+ }
239
+ /**
240
+ * How a diagnostic affects the form: `error` means the node is not rendered, `warning` means it
241
+ * is rendered in an approximate way, `info` is a note.
242
+ */
243
+ export type DiagnosticSeverity = 'error' | 'warning' | 'info';
244
+ /** Something the converter could not express exactly. */
245
+ export interface Diagnostic {
246
+ severity: DiagnosticSeverity;
247
+ /** A stable identifier, e.g. `recursive-ref`. */
248
+ code: string;
249
+ message: string;
250
+ /** The form data path of the node. */
251
+ path: string;
252
+ /** JSON pointer to the node in the input schema. */
253
+ pointer: string;
254
+ }
255
+ /** A diagnostic as a builder reports it. `path` and `pointer` default to the current node. */
256
+ export type DiagnosticInput = Omit<Diagnostic, 'path' | 'pointer'> & Partial<Pick<Diagnostic, 'path' | 'pointer'>>;
@@ -0,0 +1,34 @@
1
+ import { LiteralValue } from './expressions.js';
2
+ import { JsonSchema } from './types.js';
3
+ /** What the discriminator search needs to know about one branch of a union. */
4
+ export type UnionBranchInfo = {
5
+ /** The normalized branch schema. */
6
+ schema: JsonSchema;
7
+ /** The `$ref` the branch was written as, for OpenAPI `discriminator.mapping`. */
8
+ ref?: string;
9
+ /** The definition name of the branch, the OpenAPI value when there is no mapping. */
10
+ defName?: string;
11
+ };
12
+ /** The property that tells the branches apart, with the value of each branch in order. */
13
+ export type Discriminator = {
14
+ property: string;
15
+ values: LiteralValue[];
16
+ };
17
+ /**
18
+ * Finds the discriminator of a union of object branches.
19
+ *
20
+ * With OpenAPI `discriminator.propertyName` on the union schema, each branch value is the
21
+ * `const` of that property, otherwise the `discriminator.mapping` key that points to the branch
22
+ * `$ref`, otherwise the branch definition name. Without it, the discriminator is the first
23
+ * property of the first branch that has a `const` (or a one-value `enum`) in every branch.
24
+ * The values must be distinct.
25
+ *
26
+ * @returns The discriminator, or `undefined` when the branches cannot be told apart.
27
+ *
28
+ * @example
29
+ * discriminatorOf({}, [
30
+ * { schema: { properties: { method: { const: 'card' } } } },
31
+ * { schema: { properties: { method: { const: 'bank' } } } },
32
+ * ]) // { property: 'method', values: ['card', 'bank'] }
33
+ */
34
+ export declare function discriminatorOf(unionSchema: JsonSchema, branches: UnionBranchInfo[]): Discriminator | undefined;
@@ -0,0 +1,26 @@
1
+ import { BuildResult, FormWidgetJson, WidgetPatch } from './types.js';
2
+ /** A build result as a list: `null` and `undefined` become an empty list. */
3
+ export declare function asWidgetList(result: BuildResult): FormWidgetJson[];
4
+ /**
5
+ * Copies the widget fields of a patch onto a build result. When a node builds several widgets,
6
+ * the patch goes to the first one. The result is copied, never modified.
7
+ */
8
+ export declare function applyPatch(result: BuildResult, patch: WidgetPatch | undefined): BuildResult;
9
+ /**
10
+ * Adds a visibility condition to every widget of a build result. An existing `include.when` is
11
+ * combined with `&&`. An `include.in` (state-based) is replaced, it cannot be combined.
12
+ */
13
+ export declare function withIncludeCondition(result: BuildResult, when: string): BuildResult;
14
+ /**
15
+ * Removes every input whose `path`, and every widget whose `uid`, an earlier widget already
16
+ * has. Two inputs on one path would both write it, and the form treats a repeated uid as an
17
+ * error. Repeater templates are checked too.
18
+ *
19
+ * @param onRemoved - Called for each removed widget, with the field that repeats.
20
+ */
21
+ export declare function removeDuplicateWidgets(widgets: FormWidgetJson[], onRemoved: (widget: FormWidgetJson, field: 'path' | 'uid') => void): FormWidgetJson[];
22
+ /**
23
+ * Copies a widget without `undefined` values, in the key order of `KEY_ORDER`, and does the
24
+ * same for its children, its repeater template and the first level of its `props`.
25
+ */
26
+ export declare function cleanWidget(widget: FormWidgetJson): FormWidgetJson;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@golemui/schemas",
3
- "version": "1.5.1",
3
+ "version": "1.6.0-rc.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -23,6 +23,11 @@
23
23
  "import": "./generator.js",
24
24
  "require": "./generator.cjs"
25
25
  },
26
+ "./json-schema": {
27
+ "types": "./json-schema.d.ts",
28
+ "import": "./json-schema.js",
29
+ "require": "./json-schema.cjs"
30
+ },
26
31
  "./schemas/*.schema.json": "./schemas/*.schema.json",
27
32
  "./package.json": "./package.json"
28
33
  },