@petrarca/sonnet-core 0.4.1 → 0.5.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/dist/config/index.d.ts +78 -0
- package/dist/config/index.js +63 -0
- package/dist/config/index.js.map +1 -0
- package/dist/entityOptions/index.d.ts +2 -2
- package/dist/hooks/index.d.ts +1 -1
- package/dist/schema/index.d.ts +54 -5
- package/dist/schema/index.js +10 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/{types-B29bSeIg.d.ts → types-DJ4hb97y.d.ts} +8 -0
- package/package.json +5 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* App-wide component configuration contract.
|
|
6
|
+
*
|
|
7
|
+
* A consuming app sets default props for components once at the root via
|
|
8
|
+
* SonnetConfigProvider; individual component instances override via props.
|
|
9
|
+
* Resolution order is: instance prop > provider default > built-in default.
|
|
10
|
+
*
|
|
11
|
+
* The config is keyed by component name under `components`. Each component
|
|
12
|
+
* package augments `SonnetComponentDefaults` (declaration merging) with its
|
|
13
|
+
* own configurable-prop shape, so the type stays modular -- new components add
|
|
14
|
+
* their key without editing this file.
|
|
15
|
+
*
|
|
16
|
+
* Global, cross-cutting settings (locale, density, ...) can be added to
|
|
17
|
+
* `SonnetGlobalConfig` later without affecting per-component defaults.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Per-component default props, keyed by component name. Component packages
|
|
21
|
+
* extend this interface via declaration merging, e.g.:
|
|
22
|
+
*
|
|
23
|
+
* declare module "@petrarca/sonnet-core" {
|
|
24
|
+
* interface SonnetComponentDefaults {
|
|
25
|
+
* EntityTable?: { defaultProps?: Partial<EntityTableConfigurable> };
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
interface SonnetComponentDefaults {
|
|
30
|
+
}
|
|
31
|
+
/** Cross-cutting global settings. Extend via declaration merging as needed. */
|
|
32
|
+
interface SonnetGlobalConfig {
|
|
33
|
+
}
|
|
34
|
+
/** The full app-wide configuration object held by SonnetConfigProvider. */
|
|
35
|
+
interface SonnetConfig {
|
|
36
|
+
/** Cross-cutting globals (locale, density, ...). */
|
|
37
|
+
globals?: SonnetGlobalConfig;
|
|
38
|
+
/** Per-component default props, keyed by component name. */
|
|
39
|
+
components?: SonnetComponentDefaults;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* SonnetConfigProvider -- sets app-wide component defaults.
|
|
44
|
+
*
|
|
45
|
+
* Place once at the app root to define default props for components (and
|
|
46
|
+
* cross-cutting globals). Individual instances still override via props.
|
|
47
|
+
* Nested providers merge into their parent, so a subtree can refine or
|
|
48
|
+
* override outer defaults.
|
|
49
|
+
*
|
|
50
|
+
* Resolution order for any prop (see useComponentDefaults):
|
|
51
|
+
* instance prop > provider default > built-in default.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
interface SonnetConfigProviderProps {
|
|
55
|
+
/** The configuration to apply. Merged onto any parent provider's config. */
|
|
56
|
+
value: SonnetConfig;
|
|
57
|
+
children: React__default.ReactNode;
|
|
58
|
+
}
|
|
59
|
+
declare function SonnetConfigProvider({ value, children, }: SonnetConfigProviderProps): React__default.ReactElement;
|
|
60
|
+
|
|
61
|
+
declare const SonnetConfigContext: React.Context<SonnetConfig>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* useComponentDefaults -- resolve a component's props against app-wide config.
|
|
65
|
+
*
|
|
66
|
+
* Resolution order, per prop: instance prop > provider default > built-in.
|
|
67
|
+
* A component calls this at the top of its body:
|
|
68
|
+
*
|
|
69
|
+
* const resolved = useComponentDefaults("EntityTable", BUILTIN, props);
|
|
70
|
+
*
|
|
71
|
+
* where BUILTIN is a stable (module-level) object of the component's built-in
|
|
72
|
+
* defaults. With no provider in the tree, the result is BUILTIN merged with
|
|
73
|
+
* props -- identical to not using the config at all, so existing behavior is
|
|
74
|
+
* preserved.
|
|
75
|
+
*/
|
|
76
|
+
declare function useComponentDefaults<Props extends Record<string, unknown>>(componentName: string, builtinDefaults: Partial<Props>, props: Props): Props;
|
|
77
|
+
|
|
78
|
+
export { type SonnetComponentDefaults, type SonnetConfig, SonnetConfigContext, SonnetConfigProvider, type SonnetConfigProviderProps, type SonnetGlobalConfig, useComponentDefaults };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/config/SonnetConfigProvider.tsx
|
|
2
|
+
import { useContext, useMemo } from "react";
|
|
3
|
+
|
|
4
|
+
// src/config/SonnetConfigContext.ts
|
|
5
|
+
import { createContext } from "react";
|
|
6
|
+
var SonnetConfigContext = createContext({});
|
|
7
|
+
|
|
8
|
+
// src/config/SonnetConfigProvider.tsx
|
|
9
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
function mergeConfig(parent, child) {
|
|
11
|
+
const components = {
|
|
12
|
+
...parent.components
|
|
13
|
+
};
|
|
14
|
+
const childComponents = child.components;
|
|
15
|
+
if (childComponents) {
|
|
16
|
+
for (const name of Object.keys(childComponents)) {
|
|
17
|
+
components[name] = {
|
|
18
|
+
...components[name],
|
|
19
|
+
...childComponents[name],
|
|
20
|
+
defaultProps: {
|
|
21
|
+
...components[name]?.defaultProps,
|
|
22
|
+
...childComponents[name]?.defaultProps
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
globals: { ...parent.globals, ...child.globals },
|
|
29
|
+
components
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function SonnetConfigProvider({
|
|
33
|
+
value,
|
|
34
|
+
children
|
|
35
|
+
}) {
|
|
36
|
+
const parent = useContext(SonnetConfigContext);
|
|
37
|
+
const merged = useMemo(() => mergeConfig(parent, value), [parent, value]);
|
|
38
|
+
return /* @__PURE__ */ jsx(SonnetConfigContext.Provider, { value: merged, children });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/config/useComponentDefaults.ts
|
|
42
|
+
import { useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
43
|
+
function useComponentDefaults(componentName, builtinDefaults, props) {
|
|
44
|
+
const config = useContext2(SonnetConfigContext);
|
|
45
|
+
const components = config.components;
|
|
46
|
+
const providerDefaults = components?.[componentName]?.defaultProps;
|
|
47
|
+
return useMemo2(() => {
|
|
48
|
+
const resolved = {
|
|
49
|
+
...builtinDefaults,
|
|
50
|
+
...providerDefaults
|
|
51
|
+
};
|
|
52
|
+
for (const key of Object.keys(props)) {
|
|
53
|
+
if (props[key] !== void 0) resolved[key] = props[key];
|
|
54
|
+
}
|
|
55
|
+
return resolved;
|
|
56
|
+
}, [builtinDefaults, providerDefaults, props]);
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
SonnetConfigContext,
|
|
60
|
+
SonnetConfigProvider,
|
|
61
|
+
useComponentDefaults
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/config/SonnetConfigProvider.tsx","../../src/config/SonnetConfigContext.ts","../../src/config/useComponentDefaults.ts"],"sourcesContent":["/**\n * SonnetConfigProvider -- sets app-wide component defaults.\n *\n * Place once at the app root to define default props for components (and\n * cross-cutting globals). Individual instances still override via props.\n * Nested providers merge into their parent, so a subtree can refine or\n * override outer defaults.\n *\n * Resolution order for any prop (see useComponentDefaults):\n * instance prop > provider default > built-in default.\n */\n\nimport React, { useContext, useMemo } from \"react\";\nimport { SonnetConfigContext } from \"./SonnetConfigContext\";\nimport type { SonnetConfig } from \"./types\";\n\nexport interface SonnetConfigProviderProps {\n /** The configuration to apply. Merged onto any parent provider's config. */\n value: SonnetConfig;\n children: React.ReactNode;\n}\n\n// Merge child config onto parent. Globals shallow-merge; component defaults\n// merge per component name, with each component's defaultProps shallow-merged\n// (child wins). One level of nesting is sufficient for the defaultProps shape.\nfunction mergeConfig(parent: SonnetConfig, child: SonnetConfig): SonnetConfig {\n const components: Record<string, { defaultProps?: Record<string, unknown> }> =\n {\n ...(parent.components as Record<\n string,\n { defaultProps?: Record<string, unknown> }\n >),\n };\n const childComponents = child.components as\n | Record<string, { defaultProps?: Record<string, unknown> }>\n | undefined;\n if (childComponents) {\n for (const name of Object.keys(childComponents)) {\n components[name] = {\n ...components[name],\n ...childComponents[name],\n defaultProps: {\n ...components[name]?.defaultProps,\n ...childComponents[name]?.defaultProps,\n },\n };\n }\n }\n return {\n globals: { ...parent.globals, ...child.globals },\n components: components as SonnetConfig[\"components\"],\n };\n}\n\nexport function SonnetConfigProvider({\n value,\n children,\n}: SonnetConfigProviderProps): React.ReactElement {\n const parent = useContext(SonnetConfigContext);\n const merged = useMemo(() => mergeConfig(parent, value), [parent, value]);\n return (\n <SonnetConfigContext.Provider value={merged}>\n {children}\n </SonnetConfigContext.Provider>\n );\n}\n","/**\n * React context holding the resolved app-wide SonnetConfig.\n *\n * Nested providers merge into their parent (see SonnetConfigProvider), so a\n * provider deeper in the tree can refine or override an outer one for its\n * subtree. The default is an empty config -- with no provider, components fall\n * back to their built-in defaults, preserving existing behavior.\n */\n\nimport { createContext } from \"react\";\nimport type { SonnetConfig } from \"./types\";\n\nexport const SonnetConfigContext = createContext<SonnetConfig>({});\n","/**\n * useComponentDefaults -- resolve a component's props against app-wide config.\n *\n * Resolution order, per prop: instance prop > provider default > built-in.\n * A component calls this at the top of its body:\n *\n * const resolved = useComponentDefaults(\"EntityTable\", BUILTIN, props);\n *\n * where BUILTIN is a stable (module-level) object of the component's built-in\n * defaults. With no provider in the tree, the result is BUILTIN merged with\n * props -- identical to not using the config at all, so existing behavior is\n * preserved.\n */\n\nimport { useContext, useMemo } from \"react\";\nimport { SonnetConfigContext } from \"./SonnetConfigContext\";\n\nexport function useComponentDefaults<Props extends Record<string, unknown>>(\n componentName: string,\n builtinDefaults: Partial<Props>,\n props: Props,\n): Props {\n const config = useContext(SonnetConfigContext);\n const components = config.components as\n | Record<string, { defaultProps?: Partial<Props> } | undefined>\n | undefined;\n const providerDefaults = components?.[componentName]?.defaultProps;\n\n return useMemo(() => {\n // Spread order encodes precedence: builtin < provider < instance props.\n // Only undefined instance props fall through to provider/builtin, so an\n // explicit prop always wins.\n const resolved: Record<string, unknown> = {\n ...builtinDefaults,\n ...providerDefaults,\n };\n for (const key of Object.keys(props)) {\n if (props[key] !== undefined) resolved[key] = props[key];\n }\n return resolved as Props;\n }, [builtinDefaults, providerDefaults, props]);\n}\n"],"mappings":";AAYA,SAAgB,YAAY,eAAe;;;ACH3C,SAAS,qBAAqB;AAGvB,IAAM,sBAAsB,cAA4B,CAAC,CAAC;;;ADiD7D;AApCJ,SAAS,YAAY,QAAsB,OAAmC;AAC5E,QAAM,aACJ;AAAA,IACE,GAAI,OAAO;AAAA,EAIb;AACF,QAAM,kBAAkB,MAAM;AAG9B,MAAI,iBAAiB;AACnB,eAAW,QAAQ,OAAO,KAAK,eAAe,GAAG;AAC/C,iBAAW,IAAI,IAAI;AAAA,QACjB,GAAG,WAAW,IAAI;AAAA,QAClB,GAAG,gBAAgB,IAAI;AAAA,QACvB,cAAc;AAAA,UACZ,GAAG,WAAW,IAAI,GAAG;AAAA,UACrB,GAAG,gBAAgB,IAAI,GAAG;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS,EAAE,GAAG,OAAO,SAAS,GAAG,MAAM,QAAQ;AAAA,IAC/C;AAAA,EACF;AACF;AAEO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAAkD;AAChD,QAAM,SAAS,WAAW,mBAAmB;AAC7C,QAAM,SAAS,QAAQ,MAAM,YAAY,QAAQ,KAAK,GAAG,CAAC,QAAQ,KAAK,CAAC;AACxE,SACE,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,QAClC,UACH;AAEJ;;;AEnDA,SAAS,cAAAA,aAAY,WAAAC,gBAAe;AAG7B,SAAS,qBACd,eACA,iBACA,OACO;AACP,QAAM,SAASC,YAAW,mBAAmB;AAC7C,QAAM,aAAa,OAAO;AAG1B,QAAM,mBAAmB,aAAa,aAAa,GAAG;AAEtD,SAAOC,SAAQ,MAAM;AAInB,UAAM,WAAoC;AAAA,MACxC,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AACA,eAAW,OAAO,OAAO,KAAK,KAAK,GAAG;AACpC,UAAI,MAAM,GAAG,MAAM,OAAW,UAAS,GAAG,IAAI,MAAM,GAAG;AAAA,IACzD;AACA,WAAO;AAAA,EACT,GAAG,CAAC,iBAAiB,kBAAkB,KAAK,CAAC;AAC/C;","names":["useContext","useMemo","useContext","useMemo"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as EntityFetcherFactory, a as EntityOption, E as EntityOptionFetcher } from '../types-
|
|
2
|
-
export { c as EntityFetchParams, G as GraphqlFetcherConfig, R as RestFetcherConfig } from '../types-
|
|
1
|
+
import { b as EntityFetcherFactory, a as EntityOption, E as EntityOptionFetcher } from '../types-DJ4hb97y.js';
|
|
2
|
+
export { c as EntityFetchParams, G as GraphqlFetcherConfig, R as RestFetcherConfig } from '../types-DJ4hb97y.js';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
|
|
5
5
|
declare const FetcherContext: React.Context<EntityFetcherFactory | null>;
|
package/dist/hooks/index.d.ts
CHANGED
package/dist/schema/index.d.ts
CHANGED
|
@@ -85,6 +85,21 @@ type JsonSchema = {
|
|
|
85
85
|
"x-ui-order"?: string[];
|
|
86
86
|
[key: `x-${string}`]: unknown;
|
|
87
87
|
};
|
|
88
|
+
/**
|
|
89
|
+
* WidthToken -- the shared semantic sizing vocabulary used across the form
|
|
90
|
+
* layout system: per-field slot width (x-ui-width) and per-column width
|
|
91
|
+
* (UILayoutRowConfig.widths). "full" fills available space; sm/md/lg are
|
|
92
|
+
* fixed inline sizes. Raw CSS remains available as an advanced escape hatch.
|
|
93
|
+
*/
|
|
94
|
+
type WidthToken = "sm" | "md" | "lg" | "full";
|
|
95
|
+
/**
|
|
96
|
+
* The fixed rem values for each non-full WidthToken. This is the single source
|
|
97
|
+
* of truth for token sizing used by:
|
|
98
|
+
* - the CSS grid track map in JsonSchemaFormRenderer (sm -> "8rem" track)
|
|
99
|
+
* - the Tailwind class map in FormFieldRenderer (sm -> w-32 = 8rem)
|
|
100
|
+
* Consumers that need px can multiply by 16.
|
|
101
|
+
*/
|
|
102
|
+
declare const WIDTH_TOKEN_REMS: Record<WidthToken, string>;
|
|
88
103
|
/**
|
|
89
104
|
* UISchema - User Interface Schema configuration.
|
|
90
105
|
*
|
|
@@ -102,6 +117,8 @@ interface UISchema {
|
|
|
102
117
|
"x-ui-description"?: string;
|
|
103
118
|
"x-ui-placeholder"?: string;
|
|
104
119
|
"x-ui-help"?: string;
|
|
120
|
+
/** Semantic field-slot width (default "full"). */
|
|
121
|
+
"x-ui-width"?: WidthToken;
|
|
105
122
|
/** Column override -- merged over schema-level x-ui-column, UI schema wins. */
|
|
106
123
|
"x-ui-column"?: JsonSchemaProperty["x-ui-column"] & {
|
|
107
124
|
label?: string;
|
|
@@ -123,18 +140,45 @@ interface UISchema {
|
|
|
123
140
|
* UILayoutRowConfig -- one horizontal row in a multi-column object form layout.
|
|
124
141
|
*
|
|
125
142
|
* `fields` lists the field names to place in this row, left to right.
|
|
126
|
-
*
|
|
127
|
-
*
|
|
143
|
+
* Column sizing (grid mode) is resolved in this precedence:
|
|
144
|
+
* 1. `columns` -- raw CSS grid-template-columns (advanced escape hatch).
|
|
145
|
+
* 2. `widths` -- per-cell semantic tokens (the designer-friendly path).
|
|
146
|
+
* 3. default -- equal 1fr per field.
|
|
147
|
+
* `gap` overrides the parent layout gap for this row only.
|
|
148
|
+
*
|
|
149
|
+
* `group` renders the row inside a titled fieldset (presentation-only grouping
|
|
150
|
+
* over flat schema fields -- the data shape is unchanged).
|
|
128
151
|
*
|
|
129
152
|
* Examples:
|
|
130
|
-
* { fields: ["first_name", "last_name"],
|
|
153
|
+
* { fields: ["first_name", "last_name"], widths: ["md", "full"] }
|
|
131
154
|
* { fields: ["npi", "specialty", "email"], columns: "20% 1fr 1fr" }
|
|
132
|
-
* {
|
|
155
|
+
* { group: "Contact", fields: ["email", "phone"] }
|
|
133
156
|
*/
|
|
134
157
|
interface UILayoutRowConfig {
|
|
135
158
|
fields: string[];
|
|
136
159
|
columns?: string;
|
|
160
|
+
/**
|
|
161
|
+
* Per-cell semantic widths, aligned to `fields` by index. Translated to grid
|
|
162
|
+
* tracks: sm/md/lg -> fixed rem tracks, full -> 1fr. Ignored when `columns`
|
|
163
|
+
* is set (raw CSS wins) or in "flow" mode. Missing/short entries default to
|
|
164
|
+
* "full".
|
|
165
|
+
*/
|
|
166
|
+
widths?: WidthToken[];
|
|
137
167
|
gap?: number;
|
|
168
|
+
/**
|
|
169
|
+
* Optional title. When set, the row renders inside a <fieldset> with a
|
|
170
|
+
* <legend>, grouping its fields visually without nesting them in the schema.
|
|
171
|
+
*/
|
|
172
|
+
group?: string;
|
|
173
|
+
/**
|
|
174
|
+
* How this row arranges its fields:
|
|
175
|
+
* - "grid" (default): CSS grid using `columns`/`widths` (equal 1fr per field
|
|
176
|
+
* when neither is set).
|
|
177
|
+
* - "flow": flex row, fields at their natural width, left-to-right, wrapping.
|
|
178
|
+
* Use for compact toolbars / tight field groups. `columns`/`widths` are
|
|
179
|
+
* ignored.
|
|
180
|
+
*/
|
|
181
|
+
arrange?: "grid" | "flow";
|
|
138
182
|
}
|
|
139
183
|
/**
|
|
140
184
|
* UILayoutConfig -- layout configuration for ObjectWidget and ArrayWidget.
|
|
@@ -149,6 +193,7 @@ interface UILayoutConfig {
|
|
|
149
193
|
gap?: number;
|
|
150
194
|
labels?: boolean;
|
|
151
195
|
compact?: boolean;
|
|
196
|
+
labelPlacement?: "top" | "start";
|
|
152
197
|
}
|
|
153
198
|
/**
|
|
154
199
|
* ValueChange -- a single scalar or nested-object field that changed value.
|
|
@@ -218,6 +263,10 @@ interface UISchemaOptions {
|
|
|
218
263
|
rows?: number;
|
|
219
264
|
searchable?: boolean;
|
|
220
265
|
clearable?: boolean;
|
|
266
|
+
/** Label placement: "top" (default, above) or "start" (inline, RTL-safe). */
|
|
267
|
+
labelPlacement?: "top" | "start";
|
|
268
|
+
/** Semantic field-slot width (default "full"). */
|
|
269
|
+
width?: WidthToken;
|
|
221
270
|
[key: string]: any;
|
|
222
271
|
}
|
|
223
272
|
|
|
@@ -530,4 +579,4 @@ declare function validateFormData(schema: JsonSchema, data: Record<string, unkno
|
|
|
530
579
|
*/
|
|
531
580
|
declare function diffFormData(original: Record<string, unknown>, updated: Record<string, unknown>): FormDiff;
|
|
532
581
|
|
|
533
|
-
export { type ArrayItemChange, type FieldDiff, type FormDiff, type JsonSchema, type JsonSchemaProperty, type UILayoutConfig, type UILayoutRowConfig, type UISchema, type UISchemaOptions, type ValidationResult, type ValueChange, applySchemaDefaults, diffFormData, extractEnumValues, extractSchemaDefaults, generateJsonSchemaFromVariables, getFieldConstraints, getFieldDefault, getFieldHelpText, getFieldPlaceholder, getFieldTitle, getFieldType, getFieldWidget, getOptionalFields, getRequiredFields, getSchemaProperties, hasConditionals, hasProperties, isFieldOptional, isFieldRequired, isNullableBoolean, matchesConst, resolveRefs, resolveSchema, toJsonSchema, validateFormData, validateSchema };
|
|
582
|
+
export { type ArrayItemChange, type FieldDiff, type FormDiff, type JsonSchema, type JsonSchemaProperty, type UILayoutConfig, type UILayoutRowConfig, type UISchema, type UISchemaOptions, type ValidationResult, type ValueChange, WIDTH_TOKEN_REMS, type WidthToken, applySchemaDefaults, diffFormData, extractEnumValues, extractSchemaDefaults, generateJsonSchemaFromVariables, getFieldConstraints, getFieldDefault, getFieldHelpText, getFieldPlaceholder, getFieldTitle, getFieldType, getFieldWidget, getOptionalFields, getRequiredFields, getSchemaProperties, hasConditionals, hasProperties, isFieldOptional, isFieldRequired, isNullableBoolean, matchesConst, resolveRefs, resolveSchema, toJsonSchema, validateFormData, validateSchema };
|
package/dist/schema/index.js
CHANGED
|
@@ -6,6 +6,15 @@ import {
|
|
|
6
6
|
isEqual
|
|
7
7
|
} from "../chunk-XCCOEUVC.js";
|
|
8
8
|
|
|
9
|
+
// src/schema/types.ts
|
|
10
|
+
var WIDTH_TOKEN_REMS = {
|
|
11
|
+
sm: "8rem",
|
|
12
|
+
md: "12rem",
|
|
13
|
+
lg: "16rem",
|
|
14
|
+
full: "1fr"
|
|
15
|
+
// grid track only; field slots use w-full instead
|
|
16
|
+
};
|
|
17
|
+
|
|
9
18
|
// src/schema/schemaService.ts
|
|
10
19
|
function getSchemaProperties(schema) {
|
|
11
20
|
return schema.properties || {};
|
|
@@ -535,6 +544,7 @@ function diffFormData(original, updated) {
|
|
|
535
544
|
return diff;
|
|
536
545
|
}
|
|
537
546
|
export {
|
|
547
|
+
WIDTH_TOKEN_REMS,
|
|
538
548
|
applySchemaDefaults,
|
|
539
549
|
diffFormData,
|
|
540
550
|
extractEnumValues,
|
package/dist/schema/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/schema/schemaService.ts","../../src/schema/schemaResolver.ts","../../src/schema/schemaUtils.ts","../../src/schema/zodSchema.ts","../../src/schema/resolveRefs.ts","../../src/schema/validateFormData.ts","../../src/schema/diffFormData.ts"],"sourcesContent":["/**\n * Schema Service - Pure functions for JSON Schema manipulation.\n *\n * Generic, domain-agnostic utilities for reading and inspecting JSON\n * Schema structures. No React, no HTTP, no side effects.\n *\n * Domain-specific accessors (x-entity, x-graph) live in the consumer's\n * own service layer and import from here.\n */\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\nexport function getSchemaProperties(\n schema: JsonSchema,\n): Record<string, JsonSchemaProperty> {\n return schema.properties || {};\n}\n\nexport function isFieldRequired(\n schema: JsonSchema,\n fieldName: string,\n): boolean {\n return schema.required?.includes(fieldName) ?? false;\n}\n\nexport function getFieldDefault(property: JsonSchemaProperty): unknown {\n return property.default;\n}\n\ntype JsonSchemaType =\n | \"string\"\n | \"number\"\n | \"integer\"\n | \"boolean\"\n | \"object\"\n | \"array\"\n | \"null\";\n\n/**\n * Get the effective type of a property.\n *\n * Unwraps the nullable anyOf pattern produced by Zod / Pydantic Optional[T]:\n * `{ anyOf: [{ type: T }, { type: \"null\" }] }` → T.\n * Falls back to \"string\" when no type can be determined.\n */\nexport function getFieldType(property: JsonSchemaProperty): JsonSchemaType {\n if (Array.isArray(property.anyOf)) {\n const nonNull = property.anyOf.find(\n (t): t is { type: JsonSchemaType } => \"type\" in t && t.type !== \"null\",\n );\n if (nonNull) return nonNull.type;\n }\n return (property.type as JsonSchemaType) || \"string\";\n}\n\n/**\n * Returns true when a property uses the Pydantic Optional[bool] pattern:\n * anyOf containing both a boolean member and a null member.\n * Used for tri-state checkbox rendering.\n */\nexport function isNullableBoolean(property: JsonSchemaProperty): boolean {\n if (!Array.isArray(property.anyOf)) return false;\n const hasBoolean = property.anyOf.some((s) => s.type === \"boolean\");\n const hasNull = property.anyOf.some((s) => s.type === \"null\");\n return hasBoolean && hasNull;\n}\n\n/**\n * Extract enum values from a property schema.\n *\n * Handles both the simple case (`{ enum: [...] }`) and the nullable\n * pattern produced by Zod (`{ anyOf: [{ type: \"string\", enum: [...] }, { type: \"null\" }] }`).\n *\n * Returns the enum array if found, or an empty array.\n */\nexport function extractEnumValues(\n property: JsonSchemaProperty,\n): (string | number | boolean)[] {\n if (Array.isArray(property.enum) && property.enum.length > 0) {\n return property.enum;\n }\n if (Array.isArray(property.anyOf)) {\n for (const branch of property.anyOf) {\n if (Array.isArray(branch.enum) && branch.enum.length > 0) {\n return branch.enum;\n }\n }\n }\n return [];\n}\n\n/** Priority: x-ui-title > title > field name. */\nexport function getFieldTitle(\n fieldName: string,\n property: JsonSchemaProperty,\n): string {\n return property[\"x-ui-title\"] || property.title || fieldName;\n}\n\n/** Priority: x-ui-description > x-ui-help (legacy alias). */\nexport function getFieldHelpText(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-description\"] ?? property[\"x-ui-help\"];\n}\n\nexport function getFieldPlaceholder(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-placeholder\"];\n}\n\nexport function getFieldWidget(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-widget\"];\n}\n\nexport function isFieldOptional(\n schema: JsonSchema,\n fieldName: string,\n property: JsonSchemaProperty,\n): boolean {\n if (isFieldRequired(schema, fieldName)) {\n return false;\n }\n if (property.anyOf && Array.isArray(property.anyOf)) {\n return property.anyOf.some((t) => \"type\" in t && t.type === \"null\");\n }\n return true;\n}\n\nexport function getFieldConstraints(property: JsonSchemaProperty): {\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n pattern?: string;\n format?: string;\n} {\n return {\n minLength: property.minLength,\n maxLength: property.maxLength,\n minimum: property.minimum,\n maximum: property.maximum,\n pattern: property.pattern,\n format: property.format,\n };\n}\n\nexport function getRequiredFields(schema: JsonSchema): string[] {\n return schema.required || [];\n}\n\nexport function getOptionalFields(schema: JsonSchema): string[] {\n const allFields = Object.keys(schema.properties || {});\n const required = schema.required || [];\n return allFields.filter((field) => !required.includes(field));\n}\n\nexport function hasProperties(schema: JsonSchema): boolean {\n return Object.keys(schema.properties || {}).length > 0;\n}\n","/**\n * Schema Resolver - Resolves conditional schemas (if-then-else, allOf, etc.)\n *\n * This service handles JSON Schema conditionals by evaluating conditions\n * based on form data and merging the appropriate schema branches.\n *\n * Based on react-jsonschema-form's retrieveSchema implementation.\n *\n * Supported features:\n * - if-then-else conditionals\n * - allOf schema merging\n * - Nested conditionals (if-then-else inside allOf)\n *\n * @module services/schemaResolver\n */\n\nimport Ajv from \"ajv\";\nimport addFormats from \"ajv-formats\";\nimport type { JsonSchema } from \"./types\";\nimport { warnLog } from \"../logger\";\n\n// Singleton AJV instance\nconst ajv = new Ajv({\n allErrors: true,\n strict: false,\n validateFormats: true,\n // Allow unknown keywords (for x-ui-widget, x-ui-title, etc.)\n strictSchema: false,\n});\naddFormats(ajv);\n\n/**\n * Resolve conditional schema based on form data\n *\n * Evaluates if-then-else conditions and merges allOf schemas to produce\n * a final schema with only the properties that should be visible given\n * the current form data.\n *\n * @param schema - Original JSON Schema with conditionals\n * @param formData - Current form data to evaluate conditions against\n * @returns Resolved schema with conditionals merged\n *\n * @example\n * ```typescript\n * const schema = {\n * properties: { taxonomy: { type: 'string', enum: ['Tech', 'Arch'] } },\n * allOf: [\n * {\n * if: { properties: { taxonomy: { const: 'Tech' } } },\n * then: { properties: { tech_field: { type: 'string' } } }\n * }\n * ]\n * };\n *\n * const resolved = resolveSchema(schema, { taxonomy: 'Tech' });\n * // Result: { properties: { taxonomy: {...}, tech_field: {...} } }\n * ```\n */\nexport function resolveSchema(\n schema: JsonSchema,\n formData: Record<string, any>,\n): JsonSchema {\n if (!schema || typeof schema !== \"object\") {\n return schema;\n }\n\n // First, resolve allOf (which may contain if-then-else)\n let resolvedSchema = resolveAllOf(schema, formData);\n\n // Then, resolve any top-level if-then-else\n resolvedSchema = resolveConditionals(resolvedSchema, formData);\n\n return resolvedSchema;\n}\n\n/**\n * Resolve if-then-else conditionals\n *\n * Evaluates the 'if' condition against formData and merges the appropriate\n * branch ('then' or 'else') into the base schema.\n *\n * @param schema - Schema potentially containing if-then-else\n * @param formData - Current form data\n * @returns Schema with conditional merged\n */\nfunction resolveConditionals(schema: any, formData: Record<string, any>): any {\n if (!schema.if) {\n return schema;\n }\n\n const {\n if: ifSchema,\n then: thenSchema,\n else: elseSchema,\n ...baseSchema\n } = schema;\n\n try {\n // Compile and validate the 'if' condition\n const validator = ajv.compile(ifSchema);\n const conditionMet = validator(formData || {});\n\n // Choose the appropriate branch\n const branchSchema = conditionMet ? thenSchema : elseSchema;\n\n if (branchSchema && typeof branchSchema === \"object\") {\n // Merge the branch into the base schema\n return mergeSchemas(baseSchema, branchSchema);\n }\n\n return baseSchema;\n } catch (error) {\n warnLog(\"Error resolving conditional schema:\", error);\n return baseSchema;\n }\n}\n\n/**\n * Resolve allOf schemas\n *\n * Merges all schemas in the allOf array, recursively resolving any\n * conditionals within each subschema.\n *\n * @param schema - Schema potentially containing allOf\n * @param formData - Current form data\n * @returns Schema with allOf merged\n */\nfunction resolveAllOf(\n schema: JsonSchema,\n formData: Record<string, any>,\n): JsonSchema {\n if (!schema.allOf || !Array.isArray(schema.allOf)) {\n return schema;\n }\n\n const { allOf, ...baseSchema } = schema;\n\n // Resolve each allOf element (they might have if-then-else)\n const resolvedAllOf = allOf.map((subschema) => {\n if (typeof subschema !== \"object\") {\n return subschema;\n }\n\n // Recursively resolve conditionals in each allOf element\n return resolveConditionals(subschema, formData);\n });\n\n // Merge all resolved schemas together\n return resolvedAllOf.reduce(\n (acc, subschema) => mergeSchemas(acc, subschema),\n baseSchema,\n );\n}\n\n/**\n * Deep merge two schemas\n *\n * Merges two JSON Schemas together, with special handling for:\n * - properties: Deep merge\n * - required: Array union (unique values)\n * - Other keys: Override with schema2 value\n *\n * @param schema1 - Base schema\n * @param schema2 - Schema to merge in (takes precedence)\n * @returns Merged schema\n */\nfunction mergeSchemas(\n schema1: JsonSchema | any,\n schema2: JsonSchema | any,\n): JsonSchema {\n if (!schema2 || typeof schema2 !== \"object\") {\n return schema1;\n }\n if (!schema1 || typeof schema1 !== \"object\") {\n return schema2;\n }\n\n const merged = { ...schema1 };\n\n Object.keys(schema2).forEach((key) => {\n if (key === \"properties\" && schema1.properties && schema2.properties) {\n // Deep merge properties - for each property, merge the definitions\n merged.properties = { ...schema1.properties };\n Object.keys(schema2.properties).forEach((propName) => {\n if (merged.properties[propName]) {\n // Property exists in both schemas, deep merge them\n merged.properties[propName] = {\n ...merged.properties[propName],\n ...schema2.properties[propName],\n };\n } else {\n // Property only in schema2, just copy it\n merged.properties[propName] = schema2.properties[propName];\n }\n });\n } else if (key === \"required\" && schema1.required && schema2.required) {\n // Merge required arrays (unique values)\n const combinedRequired = [...schema1.required, ...schema2.required];\n merged.required = Array.from(new Set(combinedRequired));\n } else if (key === \"allOf\" && schema1.allOf && schema2.allOf) {\n // Concatenate allOf arrays\n merged.allOf = [...schema1.allOf, ...schema2.allOf];\n } else {\n // Override for other keys\n merged[key] = schema2[key];\n }\n });\n\n return merged;\n}\n\n/**\n * Validate formData against a schema\n *\n * Useful for checking if form data meets schema requirements,\n * including type validation and format checking.\n *\n * @param schema - JSON Schema to validate against\n * @param formData - Form data to validate\n * @returns Validation result with errors\n *\n * @example\n * ```typescript\n * const result = validateSchema(schema, { name: 'John', age: 'invalid' });\n * if (!result.valid) {\n * errorLog(result.errors); // AJV error objects\n * }\n * ```\n */\nexport function validateSchema(\n schema: JsonSchema,\n formData: Record<string, any>,\n): { valid: boolean; errors: any[] } {\n try {\n const validator = ajv.compile(schema);\n const valid = validator(formData);\n\n return {\n valid,\n errors: validator.errors || [],\n };\n } catch (error) {\n warnLog(\"Error validating schema:\", error);\n return {\n valid: false,\n errors: [{ message: \"Schema validation error\" }],\n };\n }\n}\n\n/**\n * Check if a value matches a const constraint\n *\n * Helper for evaluating const conditions in conditionals.\n *\n * @param value - Value to check\n * @param constValue - Expected const value\n * @returns true if value matches const\n */\nexport function matchesConst(value: any, constValue: any): boolean {\n return value === constValue;\n}\n\n/**\n * Check if a schema has conditionals\n *\n * Useful for determining if schema resolution is needed.\n *\n * @param schema - JSON Schema to check\n * @returns true if schema contains if-then-else or allOf\n */\nexport function hasConditionals(schema: JsonSchema): boolean {\n if (!schema || typeof schema !== \"object\") {\n return false;\n }\n\n return !!(schema.if || schema.allOf || schema.anyOf || schema.oneOf);\n}\n","/**\n * Schema utilities\n *\n * Pure functions for common JSON Schema operations:\n * - Applying schema defaults to form data\n * - Extracting default values from a schema\n * - Generating a schema from a list of template variables\n *\n * All functions are side-effect free.\n *\n * @module services/schema/schemaUtils\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\n/**\n * Merge schema default values into an existing data object.\n *\n * For each property in the schema that has a `default` value, if the\n * corresponding key is absent (`undefined`) in `data`, the default is applied.\n * Existing values — including `null` and `false` — are never overwritten.\n *\n * @param data - Existing form data (may be partial)\n * @param schema - JSON Schema containing property defaults\n * @returns New object with defaults filled in for missing keys\n */\nexport function applySchemaDefaults(\n data: Record<string, unknown>,\n schema: JsonSchema | JsonSchemaProperty,\n): Record<string, unknown> {\n const result = { ...data };\n const properties = schema.properties ?? {};\n\n for (const [fieldName, property] of Object.entries(properties)) {\n if (result[fieldName] === undefined && property.default !== undefined) {\n result[fieldName] = property.default;\n }\n }\n\n return result;\n}\n\n/**\n * Extract all default values declared in a JSON Schema.\n *\n * Walks the schema's top-level properties and collects any `default` values.\n * Properties without a `default` are omitted from the result.\n *\n * @param schema - JSON Schema to extract defaults from\n * @returns Record of field names to their default values\n *\n * @example\n * ```ts\n * extractSchemaDefaults({\n * type: 'object',\n * properties: {\n * name: { type: 'string', default: 'John' },\n * age: { type: 'number' },\n * },\n * });\n * // => { name: 'John' }\n * ```\n */\nexport function extractSchemaDefaults(\n schema: JsonSchema,\n): Record<string, unknown> {\n const defaults: Record<string, unknown> = {};\n const properties = schema.properties ?? {};\n\n for (const [fieldName, property] of Object.entries(properties)) {\n if (property.default !== undefined) {\n defaults[fieldName] = property.default;\n }\n }\n\n return defaults;\n}\n\n/**\n * Generate a JSON Schema from a list of template variable names.\n *\n * Each variable becomes a required string property with a capitalised title.\n * If an existing schema is provided, its property definitions are preserved\n * for variables that still appear in the list; new variables are added as plain\n * strings; properties no longer in the list are dropped.\n *\n * @param variables - Variable names to include in the schema\n * @param existingSchema - Optional schema to merge with (preserves extensions)\n * @returns A JSON Schema object with one property per variable\n *\n * @example\n * ```ts\n * generateJsonSchemaFromVariables(['productId', 'limit']);\n * // => {\n * // type: 'object',\n * // properties: {\n * // productId: { type: 'string', title: 'ProductId' },\n * // limit: { type: 'string', title: 'Limit' },\n * // },\n * // required: ['productId', 'limit'],\n * // }\n * ```\n */\nexport function generateJsonSchemaFromVariables(\n variables: string[],\n existingSchema?: { properties?: Record<string, JsonSchemaProperty> },\n): JsonSchema {\n const existingProperties = existingSchema?.properties ?? {};\n\n const properties: JsonSchema[\"properties\"] = {};\n for (const variable of variables) {\n properties[variable] = existingProperties[variable] ?? {\n type: \"string\",\n title: variable.charAt(0).toUpperCase() + variable.slice(1),\n };\n }\n\n return {\n title: \"\",\n type: \"object\",\n properties,\n required: variables,\n };\n}\n","/**\n * Zod-to-JSON-Schema conversion with x-ui-* metadata passthrough.\n *\n * Wraps z.toJSONSchema() with an override that copies all .meta() keys\n * into the generated JSON Schema output, including x-ui-* extension keys\n * used by the form renderer.\n *\n * @module lib/schema/zodSchema\n */\n\nimport { z } from \"zod\";\nimport type { JsonSchema } from \"./types\";\n\n/**\n * Convert a Zod schema to a JsonSchema compatible with the form renderer.\n *\n * All fields annotated with .meta() -- including x-ui-widget, x-ui-order,\n * x-ui-options, title, description -- are passed through to the output.\n *\n * @example\n * ```typescript\n * const MySchema = z.object({\n * name: z.string().meta({ title: \"Name\" }),\n * notes: z.string().optional().meta({ \"x-ui-widget\": \"textarea\" }),\n * });\n *\n * const jsonSchema = toJsonSchema(MySchema);\n * type MyData = z.infer<typeof MySchema>;\n * ```\n */\nexport function toJsonSchema(schema: z.ZodType): JsonSchema {\n return z.toJSONSchema(schema, {\n reused: \"inline\",\n override(ctx) {\n const def = (\n ctx.zodSchema as unknown as {\n _def?: { metadata?: Record<string, unknown> };\n }\n )._def;\n if (def?.metadata) {\n Object.assign(ctx.jsonSchema, def.metadata);\n }\n },\n }) as JsonSchema;\n}\n","/**\n * $ref Resolution -- resolves local JSON Schema references.\n *\n * Walks the schema tree and replaces { \"$ref\": \"#/$defs/name\" } nodes\n * with the corresponding sub-schema from the document's $defs (Draft\n * 2020-12) or definitions (Draft 7) block.\n *\n * Properties on the referencing node are merged on top of the resolved\n * target, so context-specific overrides (e.g. a custom title) work\n * without duplicating the definition.\n *\n * Only local refs (starting with \"#/\") are supported. External URIs\n * are left as-is -- a future pluggable SchemaResolver interface will\n * handle those.\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\ntype Defs = Record<string, JsonSchemaProperty>;\n\n// Extract the definition name from a local $ref pointer.\n// Supports \"#/$defs/name\" (2020-12) and \"#/definitions/name\" (Draft 7).\nfunction extractDefName(ref: string): string | null {\n const match = ref.match(/^#\\/(\\$defs|definitions)\\/(.+)$/);\n return match ? match[2] : null;\n}\n\n// Resolve a $ref node: look up the definition, merge overrides, recurse.\nfunction resolveRef(\n node: JsonSchemaProperty,\n defs: Defs,\n seen: Set<string>,\n): JsonSchemaProperty {\n const ref = node.$ref!;\n const name = extractDefName(ref);\n if (!name || !defs[name]) return node;\n if (seen.has(ref)) return node;\n seen.add(ref);\n\n const resolved = defs[name];\n const overrides = Object.fromEntries(\n Object.entries(node).filter(([k]) => k !== \"$ref\"),\n ) as JsonSchemaProperty;\n const merged =\n Object.keys(overrides).length > 0\n ? { ...resolved, ...overrides }\n : resolved;\n\n const result = resolveNode(merged, defs, seen);\n seen.delete(ref);\n return result;\n}\n\n// Resolve an array of schemas, returning null if nothing changed.\nfunction resolveList(\n list: JsonSchemaProperty[],\n defs: Defs,\n seen: Set<string>,\n): JsonSchemaProperty[] | null {\n const resolved = list.map((s) => resolveNode(s, defs, seen));\n return resolved.some((s, i) => s !== list[i]) ? resolved : null;\n}\n\n// Resolve properties map, returning null if nothing changed.\nfunction resolveProperties(\n props: Record<string, JsonSchemaProperty>,\n defs: Defs,\n seen: Set<string>,\n): Record<string, JsonSchemaProperty> | null {\n const entries = Object.entries(props);\n const resolved = entries.map(\n ([k, v]) => [k, resolveNode(v, defs, seen)] as const,\n );\n return resolved.some(([, v], i) => v !== entries[i][1])\n ? Object.fromEntries(resolved)\n : null;\n}\n\n// Resolve items, returning null if unchanged.\nfunction resolveItems(\n items: JsonSchemaProperty,\n defs: Defs,\n seen: Set<string>,\n): JsonSchemaProperty | null {\n const r = resolveNode(items, defs, seen);\n return r !== items ? r : null;\n}\n\n// Collect all resolved children of a node into a partial override object.\nfunction resolveChildren(\n node: JsonSchemaProperty,\n defs: Defs,\n seen: Set<string>,\n): Partial<JsonSchemaProperty> | null {\n const overrides: Partial<JsonSchemaProperty> = {};\n let changed = false;\n\n const props = node.properties\n ? resolveProperties(node.properties, defs, seen)\n : null;\n if (props) {\n overrides.properties = props;\n changed = true;\n }\n\n const items = node.items ? resolveItems(node.items, defs, seen) : null;\n if (items) {\n overrides.items = items;\n changed = true;\n }\n\n for (const key of [\"allOf\", \"anyOf\", \"oneOf\"] as const) {\n const list = node[key] ? resolveList(node[key]!, defs, seen) : null;\n if (list) {\n (overrides as Record<string, unknown>)[key] = list;\n changed = true;\n }\n }\n\n return changed ? overrides : null;\n}\n\nfunction resolveNode(\n node: JsonSchemaProperty,\n defs: Defs,\n seen: Set<string>,\n): JsonSchemaProperty {\n if (node.$ref) return resolveRef(node, defs, seen);\n const overrides = resolveChildren(node, defs, seen);\n return overrides ? { ...node, ...overrides } : node;\n}\n\n/**\n * Resolve all local $ref pointers in a JSON Schema document.\n *\n * Returns the schema unchanged if it contains no $defs/definitions\n * or no $ref nodes. The original schema object is never mutated.\n */\nexport function resolveRefs(schema: JsonSchema): JsonSchema {\n const defs: Defs = schema.$defs ?? schema.definitions ?? {};\n if (Object.keys(defs).length === 0) return schema;\n\n // Cast to JsonSchemaProperty so resolveNode can walk the full tree\n // including the root-level properties and allOf. The result is cast\n // back -- JsonSchema is a structural subtype of JsonSchemaProperty.\n const seen = new Set<string>();\n const resolved = resolveNode(\n schema as unknown as JsonSchemaProperty,\n defs,\n seen,\n );\n return resolved as unknown as JsonSchema;\n}\n","/**\n * Recursive form data validation against a JSON Schema.\n *\n * Validates required fields at every nesting level: top-level properties,\n * nested objects (type: \"object\"), and array items (type: \"array\" with\n * items.type: \"object\"). Returns a flat list of human-readable error\n * strings and a boolean summary.\n *\n * Pure function -- no React, no side effects.\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\nimport { getSchemaProperties, getFieldTitle } from \"./schemaService\";\n\nexport interface ValidationResult {\n errors: string[];\n isValid: boolean;\n}\n\n// Check whether a single value satisfies a \"required\" constraint.\nfunction isEmpty(value: unknown): boolean {\n return value === undefined || value === null || value === \"\";\n}\n\n// Build a display label for a field at the given path depth.\nfunction fieldLabel(\n path: string,\n fieldName: string,\n property: JsonSchemaProperty,\n): string {\n return path\n ? `${path} > ${getFieldTitle(fieldName, property)}`\n : getFieldTitle(fieldName, property);\n}\n\n// Check whether a value is a non-array plain object suitable for recursion.\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n// Validate array cardinality and recurse into object items.\nfunction validateArray(\n property: JsonSchemaProperty,\n value: unknown[],\n label: string,\n errors: string[],\n): void {\n if (property.minItems !== undefined && value.length < property.minItems) {\n errors.push(`${label} requires at least ${property.minItems} item(s)`);\n }\n if (property.maxItems !== undefined && value.length > property.maxItems) {\n errors.push(`${label} allows at most ${property.maxItems} item(s)`);\n }\n if (!property.items?.properties) return;\n const itemProperties = property.items.properties;\n const itemSchema = property.items;\n for (let i = 0; i < value.length; i++) {\n if (isPlainObject(value[i])) {\n validateObject(\n itemProperties,\n itemSchema,\n value[i] as Record<string, unknown>,\n `${label}[${i + 1}]`,\n errors,\n );\n }\n }\n}\n\n// Check whether a property+value pair represents a nested object to recurse into.\nfunction isNestedObject(\n property: JsonSchemaProperty,\n value: unknown,\n): value is Record<string, unknown> {\n return (\n property.type === \"object\" && !!property.properties && isPlainObject(value)\n );\n}\n\n// Validate a single field: check required, recurse into objects/arrays.\nfunction validateField(\n property: JsonSchemaProperty,\n value: unknown,\n required: boolean,\n label: string,\n errors: string[],\n): void {\n if (required && isEmpty(value)) {\n errors.push(`${label} is required`);\n return;\n }\n if (isNestedObject(property, value)) {\n validateObject(property.properties!, property, value, label, errors);\n return;\n }\n if (property.type === \"array\" && Array.isArray(value)) {\n validateArray(property, value, label, errors);\n }\n}\n\n// Validate the required fields of a single object against its schema.\nfunction validateObject(\n properties: Record<string, JsonSchemaProperty>,\n schema: { required?: string[] },\n data: Record<string, unknown>,\n path: string,\n errors: string[],\n): void {\n for (const fieldName of Object.keys(properties)) {\n const property = properties[fieldName];\n const required = schema.required?.includes(fieldName) ?? false;\n const label = fieldLabel(path, fieldName, property);\n validateField(property, data[fieldName], required, label, errors);\n }\n}\n\n/**\n * Validate form data against a JSON Schema, recursing into nested objects\n * and array items.\n */\nexport function validateFormData(\n schema: JsonSchema,\n data: Record<string, unknown>,\n): ValidationResult {\n const errors: string[] = [];\n const properties = getSchemaProperties(schema);\n validateObject(properties, schema, data, \"\", errors);\n return { errors, isValid: errors.length === 0 };\n}\n","/**\n * diffFormData -- structural diff between two form data snapshots.\n *\n * Produces a FormDiff that classifies every change at field level (value\n * changes, nested object changes) and at array item level (add, remove,\n * move, modify). Array items are matched by their form-internal `_fid`\n * field, which is assigned by ArrayWidget and never sent to the server.\n *\n * Items that lack a `_fid` (e.g. items that arrived before the form\n * system assigned IDs) are matched by position as a fallback.\n */\n\nimport { isEqual, FORM_ITEM_ID_FIELD } from \"../utils\";\nimport type {\n FormDiff,\n FieldDiff,\n ValueChange,\n ArrayItemChange,\n} from \"./types\";\n\n// Check whether both values are non-array plain objects suitable for recursion.\nfunction areBothPlainObjects(a: unknown, b: unknown): boolean {\n return (\n a !== null &&\n b !== null &&\n typeof a === \"object\" &&\n typeof b === \"object\" &&\n !Array.isArray(a) &&\n !Array.isArray(b)\n );\n}\n\n// Diff two plain objects recursively, collecting ValueChange entries.\nfunction diffObjects(\n original: Record<string, unknown>,\n updated: Record<string, unknown>,\n prefix: string,\n): ValueChange[] {\n const changes: ValueChange[] = [];\n const allKeys = new Set([...Object.keys(original), ...Object.keys(updated)]);\n\n for (const key of allKeys) {\n if (key === FORM_ITEM_ID_FIELD) continue;\n const path = prefix ? `${prefix}.${key}` : key;\n const a = original[key];\n const b = updated[key];\n if (isEqual(a, b)) continue;\n\n if (areBothPlainObjects(a, b)) {\n changes.push(\n ...diffObjects(\n a as Record<string, unknown>,\n b as Record<string, unknown>,\n path,\n ),\n );\n } else {\n changes.push({ type: \"value\", path, from: a, to: b });\n }\n }\n\n return changes;\n}\n\n// Strip _fid from an item before including it in diff output.\nfunction stripFid(item: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(item).filter(([k]) => k !== FORM_ITEM_ID_FIELD),\n );\n}\n\ntype FidMap = Map<string, { item: Record<string, unknown>; index: number }>;\n\n// Build identity map: fid -> { item, index }\nfunction buildFidMap(items: Record<string, unknown>[]): FidMap {\n const map: FidMap = new Map();\n for (let i = 0; i < items.length; i++) {\n const fid = items[i][FORM_ITEM_ID_FIELD] as string | undefined;\n if (fid) map.set(fid, { item: items[i], index: i });\n }\n return map;\n}\n\n// Detect removed and added items between two fid maps.\nfunction detectAddedRemoved(\n origByFid: FidMap,\n updByFid: FidMap,\n): ArrayItemChange[] {\n const changes: ArrayItemChange[] = [];\n for (const [fid, { item, index }] of origByFid) {\n if (!updByFid.has(fid)) {\n changes.push({ type: \"removed\", fid, index, item: stripFid(item) });\n }\n }\n for (const [fid, { item, index }] of updByFid) {\n if (!origByFid.has(fid)) {\n changes.push({ type: \"added\", fid, index, item: stripFid(item) });\n }\n }\n return changes;\n}\n\n// Detect moved and modified items present in both snapshots.\nfunction detectMovedModified(\n origByFid: FidMap,\n updByFid: FidMap,\n): ArrayItemChange[] {\n const changes: ArrayItemChange[] = [];\n for (const [fid, { item: updItem, index: updIndex }] of updByFid) {\n const orig = origByFid.get(fid);\n if (!orig) continue;\n if (orig.index !== updIndex) {\n changes.push({ type: \"moved\", fid, from: orig.index, to: updIndex });\n }\n const subChanges = diffObjects(orig.item, updItem, \"\");\n if (subChanges.length > 0) {\n changes.push({\n type: \"modified\",\n fid,\n index: updIndex,\n changes: subChanges,\n });\n }\n }\n return changes;\n}\n\n// Fallback diff for items without _fid: match by position.\nfunction diffUnfidItems(\n original: Record<string, unknown>[],\n updated: Record<string, unknown>[],\n): ArrayItemChange[] {\n const unfidOrig = original.filter((i) => !i[FORM_ITEM_ID_FIELD]);\n const unfidUpd = updated.filter((i) => !i[FORM_ITEM_ID_FIELD]);\n const changes: ArrayItemChange[] = [];\n const len = Math.min(unfidOrig.length, unfidUpd.length);\n for (let i = 0; i < len; i++) {\n if (isEqual(unfidOrig[i], unfidUpd[i])) continue;\n const subChanges = diffObjects(unfidOrig[i], unfidUpd[i], \"\");\n if (subChanges.length > 0) {\n changes.push({\n type: \"modified\",\n fid: `__pos_${i}`,\n index: i,\n changes: subChanges,\n });\n }\n }\n return changes;\n}\n\n// Diff two arrays of objects using _fid for item identity.\nfunction diffArrays(\n original: Record<string, unknown>[],\n updated: Record<string, unknown>[],\n field: string,\n): FieldDiff {\n const origByFid = buildFidMap(original);\n const updByFid = buildFidMap(updated);\n const changes: ArrayItemChange[] = [\n ...detectAddedRemoved(origByFid, updByFid),\n ...detectMovedModified(origByFid, updByFid),\n ...diffUnfidItems(original, updated),\n ];\n return { type: \"array\", field, changes };\n}\n\n/**\n * Compute a structural diff between two form data snapshots.\n *\n * Returns a FormDiff array with one entry per changed top-level field.\n * Fields whose value is deeply equal are omitted.\n *\n * Array items are matched by their `_fid` field (assigned by ArrayWidget).\n * Items without `_fid` fall back to positional matching.\n */\n// Check whether an array contains object items (vs scalar items).\nfunction isObjectArray(arr: unknown[]): boolean {\n return arr.some((i) => i !== null && typeof i === \"object\");\n}\n\n// Diff a single top-level field, returning the entries to add to the FormDiff.\nfunction diffField(\n key: string,\n a: unknown,\n b: unknown,\n): (ValueChange | FieldDiff)[] {\n if (Array.isArray(a) || Array.isArray(b)) {\n const origArr = Array.isArray(a) ? a : [];\n const updArr = Array.isArray(b) ? b : [];\n if (isObjectArray(origArr) || isObjectArray(updArr)) {\n return [\n diffArrays(\n origArr as Record<string, unknown>[],\n updArr as Record<string, unknown>[],\n key,\n ),\n ];\n }\n return [{ type: \"value\", path: key, from: a, to: b }];\n }\n if (areBothPlainObjects(a, b)) {\n return diffObjects(\n a as Record<string, unknown>,\n b as Record<string, unknown>,\n key,\n );\n }\n return [{ type: \"value\", path: key, from: a, to: b }];\n}\n\n/**\n * Compute a structural diff between two form data snapshots.\n */\nexport function diffFormData(\n original: Record<string, unknown>,\n updated: Record<string, unknown>,\n): FormDiff {\n const diff: FormDiff = [];\n const allKeys = new Set([...Object.keys(original), ...Object.keys(updated)]);\n\n for (const key of allKeys) {\n if (key === FORM_ITEM_ID_FIELD) continue;\n const a = original[key];\n const b = updated[key];\n if (isEqual(a, b)) continue;\n diff.push(...diffField(key, a, b));\n }\n\n return diff;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/schema/types.ts","../../src/schema/schemaService.ts","../../src/schema/schemaResolver.ts","../../src/schema/schemaUtils.ts","../../src/schema/zodSchema.ts","../../src/schema/resolveRefs.ts","../../src/schema/validateFormData.ts","../../src/schema/diffFormData.ts"],"sourcesContent":["/**\n * Generic JSON Schema type definitions.\n *\n * These types follow Draft 2020-12 and include x-ui-* extension points\n * for form/widget integration. They are domain-agnostic -- no\n * application-specific entity concepts live here.\n */\n\n/**\n * JSON Schema property definition following Draft 2020-12 specification.\n * Supports basic types, validation constraints, conditionals, and custom extensions.\n */\nexport type JsonSchemaProperty = {\n type?:\n | \"string\"\n | \"number\"\n | \"integer\"\n | \"boolean\"\n | \"array\"\n | \"object\"\n | \"null\";\n title?: string;\n description?: string;\n default?: any;\n\n // Validation constraints\n const?: any;\n enum?: any[];\n pattern?: string;\n format?: string;\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n\n // Array types\n items?: JsonSchemaProperty;\n minItems?: number;\n maxItems?: number;\n\n // Object types\n properties?: Record<string, JsonSchemaProperty>;\n required?: string[];\n\n // Schema references ($ref to $defs / definitions within the same document)\n $ref?: string;\n\n // Combining schemas (Draft 7+)\n allOf?: JsonSchemaProperty[];\n anyOf?: JsonSchemaProperty[];\n oneOf?: JsonSchemaProperty[];\n not?: JsonSchemaProperty;\n\n // Conditional schemas (Draft 7+)\n if?: JsonSchemaProperty;\n then?: JsonSchemaProperty;\n else?: JsonSchemaProperty;\n\n // Field ordering within nested objects (same as JsonSchema's x-ui-order)\n \"x-ui-order\"?: string[];\n\n // Custom UI hints (x-ui-* extensions align with UISchema)\n \"x-ui-widget\"?: string;\n \"x-ui-options\"?: Record<string, any>;\n \"x-ui-layout\"?: UILayoutConfig;\n \"x-ui-title\"?: string;\n \"x-ui-help\"?: string;\n \"x-ui-placeholder\"?: string;\n \"x-ui-description\"?: string;\n\n // Table column extensions\n \"x-ui-column\"?: {\n /** Display order (ascending). Properties without order are appended sorted by key. */\n order?: number;\n /** Exclude this column from the table entirely. */\n hidden?: boolean;\n /** Fixed column width in pixels. */\n width?: number;\n /** Minimum column width in pixels. */\n minWidth?: number;\n };\n /** Cell renderer name. Inferred from type+format when absent. */\n \"x-ui-cell\"?: string;\n /** Cell renderer configuration options (colors, href template, etc.). */\n \"x-ui-cell-options\"?: Record<string, unknown>;\n};\n\n/**\n * JSON Schema following Draft 2020-12 specification.\n *\n * This is the generic base type. Domain-specific consumers extend it\n * with their own x-* extension keys via intersection types in their\n * own type files.\n */\nexport type JsonSchema = {\n $schema?: string;\n title: string;\n type: \"object\";\n properties: Record<string, JsonSchemaProperty>;\n required?: string[];\n\n // Combining schemas (Draft 7+)\n allOf?: JsonSchemaProperty[];\n anyOf?: JsonSchemaProperty[];\n oneOf?: JsonSchemaProperty[];\n not?: JsonSchemaProperty;\n\n // Conditional schemas (Draft 7+)\n if?: JsonSchemaProperty;\n then?: JsonSchemaProperty;\n else?: JsonSchemaProperty;\n\n // Reusable sub-schema definitions (Draft 2020-12: $defs, Draft 7: definitions)\n $defs?: Record<string, JsonSchemaProperty>;\n definitions?: Record<string, JsonSchemaProperty>;\n\n // UI customization (optional)\n \"x-ui-order\"?: string[];\n\n // Allow domain-specific extension keys (x-entity, x-graph, x-custom, etc.)\n [key: `x-${string}`]: unknown;\n};\n\n/**\n * WidthToken -- the shared semantic sizing vocabulary used across the form\n * layout system: per-field slot width (x-ui-width) and per-column width\n * (UILayoutRowConfig.widths). \"full\" fills available space; sm/md/lg are\n * fixed inline sizes. Raw CSS remains available as an advanced escape hatch.\n */\nexport type WidthToken = \"sm\" | \"md\" | \"lg\" | \"full\";\n\n/**\n * The fixed rem values for each non-full WidthToken. This is the single source\n * of truth for token sizing used by:\n * - the CSS grid track map in JsonSchemaFormRenderer (sm -> \"8rem\" track)\n * - the Tailwind class map in FormFieldRenderer (sm -> w-32 = 8rem)\n * Consumers that need px can multiply by 16.\n */\nexport const WIDTH_TOKEN_REMS: Record<WidthToken, string> = {\n sm: \"8rem\",\n md: \"12rem\",\n lg: \"16rem\",\n full: \"1fr\", // grid track only; field slots use w-full instead\n};\n\n/**\n * UISchema - User Interface Schema configuration.\n *\n * Defines how form fields should be rendered and behave in the UI.\n * Separate from JSON Schema (which defines data structure) and focuses\n * on presentation, widgets, and user interaction.\n */\nexport interface UISchema {\n /** Widget name (string) or direct component reference */\n \"x-ui-widget\"?: string | object;\n \"x-ui-options\"?: UISchemaOptions;\n \"x-ui-globalOptions\"?: UISchemaOptions;\n \"x-ui-order\"?: string[];\n \"x-ui-title\"?: string | false;\n \"x-ui-description\"?: string;\n \"x-ui-placeholder\"?: string;\n \"x-ui-help\"?: string;\n /** Semantic field-slot width (default \"full\"). */\n \"x-ui-width\"?: WidthToken;\n /** Column override -- merged over schema-level x-ui-column, UI schema wins. */\n \"x-ui-column\"?: JsonSchemaProperty[\"x-ui-column\"] & { label?: string };\n /** Cell renderer override. */\n \"x-ui-cell\"?: string;\n /** Cell renderer options override -- shallow-merged over schema-level x-ui-cell-options. */\n \"x-ui-cell-options\"?: Record<string, unknown>;\n /** Per-field UISchema for nested sub-forms (ObjectWidget / ArrayWidget) */\n \"x-ui-fields\"?: { [fieldName: string]: UISchema };\n /** Layout config forwarded to the active template (ObjectWidget / ArrayWidget) */\n \"x-ui-layout\"?: UILayoutConfig;\n /** Extension keys not yet enumerated above */\n [key: `x-ui-${string}`]: unknown;\n}\n\n/**\n * UILayoutRowConfig -- one horizontal row in a multi-column object form layout.\n *\n * `fields` lists the field names to place in this row, left to right.\n * Column sizing (grid mode) is resolved in this precedence:\n * 1. `columns` -- raw CSS grid-template-columns (advanced escape hatch).\n * 2. `widths` -- per-cell semantic tokens (the designer-friendly path).\n * 3. default -- equal 1fr per field.\n * `gap` overrides the parent layout gap for this row only.\n *\n * `group` renders the row inside a titled fieldset (presentation-only grouping\n * over flat schema fields -- the data shape is unchanged).\n *\n * Examples:\n * { fields: [\"first_name\", \"last_name\"], widths: [\"md\", \"full\"] }\n * { fields: [\"npi\", \"specialty\", \"email\"], columns: \"20% 1fr 1fr\" }\n * { group: \"Contact\", fields: [\"email\", \"phone\"] }\n */\nexport interface UILayoutRowConfig {\n fields: string[];\n columns?: string;\n /**\n * Per-cell semantic widths, aligned to `fields` by index. Translated to grid\n * tracks: sm/md/lg -> fixed rem tracks, full -> 1fr. Ignored when `columns`\n * is set (raw CSS wins) or in \"flow\" mode. Missing/short entries default to\n * \"full\".\n */\n widths?: WidthToken[];\n gap?: number;\n /**\n * Optional title. When set, the row renders inside a <fieldset> with a\n * <legend>, grouping its fields visually without nesting them in the schema.\n */\n group?: string;\n /**\n * How this row arranges its fields:\n * - \"grid\" (default): CSS grid using `columns`/`widths` (equal 1fr per field\n * when neither is set).\n * - \"flow\": flex row, fields at their natural width, left-to-right, wrapping.\n * Use for compact toolbars / tight field groups. `columns`/`widths` are\n * ignored.\n */\n arrange?: \"grid\" | \"flow\";\n}\n\n/**\n * UILayoutConfig -- layout configuration for ObjectWidget and ArrayWidget.\n *\n * Configures the built-in default templates. Has no effect when a custom\n * template is registered for the field.\n */\nexport interface UILayoutConfig {\n // Selects which built-in item template to use (default: \"vertical\")\n direction?: \"vertical\" | \"horizontal\";\n\n // Multi-column row layout for object forms. Each entry defines one\n // horizontal row of fields. Fields not mentioned in any row are rendered\n // in a vertical stack below the rows.\n rows?: UILayoutRowConfig[];\n\n // CSS grid-template-columns for field cells (horizontal mode only).\n // The remove-button column (32px) is appended automatically when canRemove\n // is true -- do not include it here.\n // Default: equal 1fr columns for each field.\n columns?: string;\n\n // Gap between cells in pixels (default: 24 vertical, 8 horizontal)\n gap?: number;\n\n // Show per-field labels (default: true vertical, false horizontal)\n labels?: boolean;\n\n // Compact widget sizing -- h-8 / text-sm instead of full height\n compact?: boolean;\n\n // Label placement for fields in this container: \"top\" (default) renders the\n // label above the control; \"start\" renders it inline on the reading-start\n // side (RTL-safe: left in LTR, right in RTL). Logical values follow the MUI\n // convention. Inherited by the container's fields unless overridden per field\n // via x-ui-options.\n labelPlacement?: \"top\" | \"start\";\n}\n\n/**\n * ValueChange -- a single scalar or nested-object field that changed value.\n *\n * `path` is a dot-separated field path: \"name\", \"address.city\".\n */\nexport type ValueChange = {\n type: \"value\";\n path: string;\n from: unknown;\n to: unknown;\n};\n\n/**\n * ArrayItemChange -- one item-level change within an array field.\n *\n * Items are identified by their form-internal `_fid` (never the server ID).\n * A single item can appear as both \"moved\" and \"modified\" if it was\n * repositioned and its values also changed -- these are emitted as two\n * separate entries with the same `fid`.\n */\nexport type ArrayItemChange =\n | { type: \"added\"; fid: string; index: number; item: unknown }\n | { type: \"removed\"; fid: string; index: number; item: unknown }\n | { type: \"moved\"; fid: string; from: number; to: number }\n | { type: \"modified\"; fid: string; index: number; changes: ValueChange[] };\n\n/**\n * FieldDiff -- the diff for one top-level form field.\n */\nexport type FieldDiff =\n | ValueChange\n | { type: \"array\"; field: string; changes: ArrayItemChange[] };\n\n/**\n * FormDiff -- structured diff produced by diffFormData().\n *\n * One entry per changed top-level field. Fields whose value is deeply\n * equal between original and updated are omitted.\n */\nexport type FormDiff = FieldDiff[];\n\n/**\n * UISchemaOptions - Widget configuration options.\n */\nexport interface UISchemaOptions {\n label?: string | false;\n disabled?: boolean;\n readonly?: boolean;\n compact?: boolean;\n enumNames?: Record<string | number, string>;\n inputType?: string;\n rows?: number;\n searchable?: boolean;\n clearable?: boolean;\n /** Label placement: \"top\" (default, above) or \"start\" (inline, RTL-safe). */\n labelPlacement?: \"top\" | \"start\";\n /** Semantic field-slot width (default \"full\"). */\n width?: WidthToken;\n [key: string]: any;\n}\n","/**\n * Schema Service - Pure functions for JSON Schema manipulation.\n *\n * Generic, domain-agnostic utilities for reading and inspecting JSON\n * Schema structures. No React, no HTTP, no side effects.\n *\n * Domain-specific accessors (x-entity, x-graph) live in the consumer's\n * own service layer and import from here.\n */\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\nexport function getSchemaProperties(\n schema: JsonSchema,\n): Record<string, JsonSchemaProperty> {\n return schema.properties || {};\n}\n\nexport function isFieldRequired(\n schema: JsonSchema,\n fieldName: string,\n): boolean {\n return schema.required?.includes(fieldName) ?? false;\n}\n\nexport function getFieldDefault(property: JsonSchemaProperty): unknown {\n return property.default;\n}\n\ntype JsonSchemaType =\n | \"string\"\n | \"number\"\n | \"integer\"\n | \"boolean\"\n | \"object\"\n | \"array\"\n | \"null\";\n\n/**\n * Get the effective type of a property.\n *\n * Unwraps the nullable anyOf pattern produced by Zod / Pydantic Optional[T]:\n * `{ anyOf: [{ type: T }, { type: \"null\" }] }` → T.\n * Falls back to \"string\" when no type can be determined.\n */\nexport function getFieldType(property: JsonSchemaProperty): JsonSchemaType {\n if (Array.isArray(property.anyOf)) {\n const nonNull = property.anyOf.find(\n (t): t is { type: JsonSchemaType } => \"type\" in t && t.type !== \"null\",\n );\n if (nonNull) return nonNull.type;\n }\n return (property.type as JsonSchemaType) || \"string\";\n}\n\n/**\n * Returns true when a property uses the Pydantic Optional[bool] pattern:\n * anyOf containing both a boolean member and a null member.\n * Used for tri-state checkbox rendering.\n */\nexport function isNullableBoolean(property: JsonSchemaProperty): boolean {\n if (!Array.isArray(property.anyOf)) return false;\n const hasBoolean = property.anyOf.some((s) => s.type === \"boolean\");\n const hasNull = property.anyOf.some((s) => s.type === \"null\");\n return hasBoolean && hasNull;\n}\n\n/**\n * Extract enum values from a property schema.\n *\n * Handles both the simple case (`{ enum: [...] }`) and the nullable\n * pattern produced by Zod (`{ anyOf: [{ type: \"string\", enum: [...] }, { type: \"null\" }] }`).\n *\n * Returns the enum array if found, or an empty array.\n */\nexport function extractEnumValues(\n property: JsonSchemaProperty,\n): (string | number | boolean)[] {\n if (Array.isArray(property.enum) && property.enum.length > 0) {\n return property.enum;\n }\n if (Array.isArray(property.anyOf)) {\n for (const branch of property.anyOf) {\n if (Array.isArray(branch.enum) && branch.enum.length > 0) {\n return branch.enum;\n }\n }\n }\n return [];\n}\n\n/** Priority: x-ui-title > title > field name. */\nexport function getFieldTitle(\n fieldName: string,\n property: JsonSchemaProperty,\n): string {\n return property[\"x-ui-title\"] || property.title || fieldName;\n}\n\n/** Priority: x-ui-description > x-ui-help (legacy alias). */\nexport function getFieldHelpText(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-description\"] ?? property[\"x-ui-help\"];\n}\n\nexport function getFieldPlaceholder(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-placeholder\"];\n}\n\nexport function getFieldWidget(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-widget\"];\n}\n\nexport function isFieldOptional(\n schema: JsonSchema,\n fieldName: string,\n property: JsonSchemaProperty,\n): boolean {\n if (isFieldRequired(schema, fieldName)) {\n return false;\n }\n if (property.anyOf && Array.isArray(property.anyOf)) {\n return property.anyOf.some((t) => \"type\" in t && t.type === \"null\");\n }\n return true;\n}\n\nexport function getFieldConstraints(property: JsonSchemaProperty): {\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n pattern?: string;\n format?: string;\n} {\n return {\n minLength: property.minLength,\n maxLength: property.maxLength,\n minimum: property.minimum,\n maximum: property.maximum,\n pattern: property.pattern,\n format: property.format,\n };\n}\n\nexport function getRequiredFields(schema: JsonSchema): string[] {\n return schema.required || [];\n}\n\nexport function getOptionalFields(schema: JsonSchema): string[] {\n const allFields = Object.keys(schema.properties || {});\n const required = schema.required || [];\n return allFields.filter((field) => !required.includes(field));\n}\n\nexport function hasProperties(schema: JsonSchema): boolean {\n return Object.keys(schema.properties || {}).length > 0;\n}\n","/**\n * Schema Resolver - Resolves conditional schemas (if-then-else, allOf, etc.)\n *\n * This service handles JSON Schema conditionals by evaluating conditions\n * based on form data and merging the appropriate schema branches.\n *\n * Based on react-jsonschema-form's retrieveSchema implementation.\n *\n * Supported features:\n * - if-then-else conditionals\n * - allOf schema merging\n * - Nested conditionals (if-then-else inside allOf)\n *\n * @module services/schemaResolver\n */\n\nimport Ajv from \"ajv\";\nimport addFormats from \"ajv-formats\";\nimport type { JsonSchema } from \"./types\";\nimport { warnLog } from \"../logger\";\n\n// Singleton AJV instance\nconst ajv = new Ajv({\n allErrors: true,\n strict: false,\n validateFormats: true,\n // Allow unknown keywords (for x-ui-widget, x-ui-title, etc.)\n strictSchema: false,\n});\naddFormats(ajv);\n\n/**\n * Resolve conditional schema based on form data\n *\n * Evaluates if-then-else conditions and merges allOf schemas to produce\n * a final schema with only the properties that should be visible given\n * the current form data.\n *\n * @param schema - Original JSON Schema with conditionals\n * @param formData - Current form data to evaluate conditions against\n * @returns Resolved schema with conditionals merged\n *\n * @example\n * ```typescript\n * const schema = {\n * properties: { taxonomy: { type: 'string', enum: ['Tech', 'Arch'] } },\n * allOf: [\n * {\n * if: { properties: { taxonomy: { const: 'Tech' } } },\n * then: { properties: { tech_field: { type: 'string' } } }\n * }\n * ]\n * };\n *\n * const resolved = resolveSchema(schema, { taxonomy: 'Tech' });\n * // Result: { properties: { taxonomy: {...}, tech_field: {...} } }\n * ```\n */\nexport function resolveSchema(\n schema: JsonSchema,\n formData: Record<string, any>,\n): JsonSchema {\n if (!schema || typeof schema !== \"object\") {\n return schema;\n }\n\n // First, resolve allOf (which may contain if-then-else)\n let resolvedSchema = resolveAllOf(schema, formData);\n\n // Then, resolve any top-level if-then-else\n resolvedSchema = resolveConditionals(resolvedSchema, formData);\n\n return resolvedSchema;\n}\n\n/**\n * Resolve if-then-else conditionals\n *\n * Evaluates the 'if' condition against formData and merges the appropriate\n * branch ('then' or 'else') into the base schema.\n *\n * @param schema - Schema potentially containing if-then-else\n * @param formData - Current form data\n * @returns Schema with conditional merged\n */\nfunction resolveConditionals(schema: any, formData: Record<string, any>): any {\n if (!schema.if) {\n return schema;\n }\n\n const {\n if: ifSchema,\n then: thenSchema,\n else: elseSchema,\n ...baseSchema\n } = schema;\n\n try {\n // Compile and validate the 'if' condition\n const validator = ajv.compile(ifSchema);\n const conditionMet = validator(formData || {});\n\n // Choose the appropriate branch\n const branchSchema = conditionMet ? thenSchema : elseSchema;\n\n if (branchSchema && typeof branchSchema === \"object\") {\n // Merge the branch into the base schema\n return mergeSchemas(baseSchema, branchSchema);\n }\n\n return baseSchema;\n } catch (error) {\n warnLog(\"Error resolving conditional schema:\", error);\n return baseSchema;\n }\n}\n\n/**\n * Resolve allOf schemas\n *\n * Merges all schemas in the allOf array, recursively resolving any\n * conditionals within each subschema.\n *\n * @param schema - Schema potentially containing allOf\n * @param formData - Current form data\n * @returns Schema with allOf merged\n */\nfunction resolveAllOf(\n schema: JsonSchema,\n formData: Record<string, any>,\n): JsonSchema {\n if (!schema.allOf || !Array.isArray(schema.allOf)) {\n return schema;\n }\n\n const { allOf, ...baseSchema } = schema;\n\n // Resolve each allOf element (they might have if-then-else)\n const resolvedAllOf = allOf.map((subschema) => {\n if (typeof subschema !== \"object\") {\n return subschema;\n }\n\n // Recursively resolve conditionals in each allOf element\n return resolveConditionals(subschema, formData);\n });\n\n // Merge all resolved schemas together\n return resolvedAllOf.reduce(\n (acc, subschema) => mergeSchemas(acc, subschema),\n baseSchema,\n );\n}\n\n/**\n * Deep merge two schemas\n *\n * Merges two JSON Schemas together, with special handling for:\n * - properties: Deep merge\n * - required: Array union (unique values)\n * - Other keys: Override with schema2 value\n *\n * @param schema1 - Base schema\n * @param schema2 - Schema to merge in (takes precedence)\n * @returns Merged schema\n */\nfunction mergeSchemas(\n schema1: JsonSchema | any,\n schema2: JsonSchema | any,\n): JsonSchema {\n if (!schema2 || typeof schema2 !== \"object\") {\n return schema1;\n }\n if (!schema1 || typeof schema1 !== \"object\") {\n return schema2;\n }\n\n const merged = { ...schema1 };\n\n Object.keys(schema2).forEach((key) => {\n if (key === \"properties\" && schema1.properties && schema2.properties) {\n // Deep merge properties - for each property, merge the definitions\n merged.properties = { ...schema1.properties };\n Object.keys(schema2.properties).forEach((propName) => {\n if (merged.properties[propName]) {\n // Property exists in both schemas, deep merge them\n merged.properties[propName] = {\n ...merged.properties[propName],\n ...schema2.properties[propName],\n };\n } else {\n // Property only in schema2, just copy it\n merged.properties[propName] = schema2.properties[propName];\n }\n });\n } else if (key === \"required\" && schema1.required && schema2.required) {\n // Merge required arrays (unique values)\n const combinedRequired = [...schema1.required, ...schema2.required];\n merged.required = Array.from(new Set(combinedRequired));\n } else if (key === \"allOf\" && schema1.allOf && schema2.allOf) {\n // Concatenate allOf arrays\n merged.allOf = [...schema1.allOf, ...schema2.allOf];\n } else {\n // Override for other keys\n merged[key] = schema2[key];\n }\n });\n\n return merged;\n}\n\n/**\n * Validate formData against a schema\n *\n * Useful for checking if form data meets schema requirements,\n * including type validation and format checking.\n *\n * @param schema - JSON Schema to validate against\n * @param formData - Form data to validate\n * @returns Validation result with errors\n *\n * @example\n * ```typescript\n * const result = validateSchema(schema, { name: 'John', age: 'invalid' });\n * if (!result.valid) {\n * errorLog(result.errors); // AJV error objects\n * }\n * ```\n */\nexport function validateSchema(\n schema: JsonSchema,\n formData: Record<string, any>,\n): { valid: boolean; errors: any[] } {\n try {\n const validator = ajv.compile(schema);\n const valid = validator(formData);\n\n return {\n valid,\n errors: validator.errors || [],\n };\n } catch (error) {\n warnLog(\"Error validating schema:\", error);\n return {\n valid: false,\n errors: [{ message: \"Schema validation error\" }],\n };\n }\n}\n\n/**\n * Check if a value matches a const constraint\n *\n * Helper for evaluating const conditions in conditionals.\n *\n * @param value - Value to check\n * @param constValue - Expected const value\n * @returns true if value matches const\n */\nexport function matchesConst(value: any, constValue: any): boolean {\n return value === constValue;\n}\n\n/**\n * Check if a schema has conditionals\n *\n * Useful for determining if schema resolution is needed.\n *\n * @param schema - JSON Schema to check\n * @returns true if schema contains if-then-else or allOf\n */\nexport function hasConditionals(schema: JsonSchema): boolean {\n if (!schema || typeof schema !== \"object\") {\n return false;\n }\n\n return !!(schema.if || schema.allOf || schema.anyOf || schema.oneOf);\n}\n","/**\n * Schema utilities\n *\n * Pure functions for common JSON Schema operations:\n * - Applying schema defaults to form data\n * - Extracting default values from a schema\n * - Generating a schema from a list of template variables\n *\n * All functions are side-effect free.\n *\n * @module services/schema/schemaUtils\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\n/**\n * Merge schema default values into an existing data object.\n *\n * For each property in the schema that has a `default` value, if the\n * corresponding key is absent (`undefined`) in `data`, the default is applied.\n * Existing values — including `null` and `false` — are never overwritten.\n *\n * @param data - Existing form data (may be partial)\n * @param schema - JSON Schema containing property defaults\n * @returns New object with defaults filled in for missing keys\n */\nexport function applySchemaDefaults(\n data: Record<string, unknown>,\n schema: JsonSchema | JsonSchemaProperty,\n): Record<string, unknown> {\n const result = { ...data };\n const properties = schema.properties ?? {};\n\n for (const [fieldName, property] of Object.entries(properties)) {\n if (result[fieldName] === undefined && property.default !== undefined) {\n result[fieldName] = property.default;\n }\n }\n\n return result;\n}\n\n/**\n * Extract all default values declared in a JSON Schema.\n *\n * Walks the schema's top-level properties and collects any `default` values.\n * Properties without a `default` are omitted from the result.\n *\n * @param schema - JSON Schema to extract defaults from\n * @returns Record of field names to their default values\n *\n * @example\n * ```ts\n * extractSchemaDefaults({\n * type: 'object',\n * properties: {\n * name: { type: 'string', default: 'John' },\n * age: { type: 'number' },\n * },\n * });\n * // => { name: 'John' }\n * ```\n */\nexport function extractSchemaDefaults(\n schema: JsonSchema,\n): Record<string, unknown> {\n const defaults: Record<string, unknown> = {};\n const properties = schema.properties ?? {};\n\n for (const [fieldName, property] of Object.entries(properties)) {\n if (property.default !== undefined) {\n defaults[fieldName] = property.default;\n }\n }\n\n return defaults;\n}\n\n/**\n * Generate a JSON Schema from a list of template variable names.\n *\n * Each variable becomes a required string property with a capitalised title.\n * If an existing schema is provided, its property definitions are preserved\n * for variables that still appear in the list; new variables are added as plain\n * strings; properties no longer in the list are dropped.\n *\n * @param variables - Variable names to include in the schema\n * @param existingSchema - Optional schema to merge with (preserves extensions)\n * @returns A JSON Schema object with one property per variable\n *\n * @example\n * ```ts\n * generateJsonSchemaFromVariables(['productId', 'limit']);\n * // => {\n * // type: 'object',\n * // properties: {\n * // productId: { type: 'string', title: 'ProductId' },\n * // limit: { type: 'string', title: 'Limit' },\n * // },\n * // required: ['productId', 'limit'],\n * // }\n * ```\n */\nexport function generateJsonSchemaFromVariables(\n variables: string[],\n existingSchema?: { properties?: Record<string, JsonSchemaProperty> },\n): JsonSchema {\n const existingProperties = existingSchema?.properties ?? {};\n\n const properties: JsonSchema[\"properties\"] = {};\n for (const variable of variables) {\n properties[variable] = existingProperties[variable] ?? {\n type: \"string\",\n title: variable.charAt(0).toUpperCase() + variable.slice(1),\n };\n }\n\n return {\n title: \"\",\n type: \"object\",\n properties,\n required: variables,\n };\n}\n","/**\n * Zod-to-JSON-Schema conversion with x-ui-* metadata passthrough.\n *\n * Wraps z.toJSONSchema() with an override that copies all .meta() keys\n * into the generated JSON Schema output, including x-ui-* extension keys\n * used by the form renderer.\n *\n * @module lib/schema/zodSchema\n */\n\nimport { z } from \"zod\";\nimport type { JsonSchema } from \"./types\";\n\n/**\n * Convert a Zod schema to a JsonSchema compatible with the form renderer.\n *\n * All fields annotated with .meta() -- including x-ui-widget, x-ui-order,\n * x-ui-options, title, description -- are passed through to the output.\n *\n * @example\n * ```typescript\n * const MySchema = z.object({\n * name: z.string().meta({ title: \"Name\" }),\n * notes: z.string().optional().meta({ \"x-ui-widget\": \"textarea\" }),\n * });\n *\n * const jsonSchema = toJsonSchema(MySchema);\n * type MyData = z.infer<typeof MySchema>;\n * ```\n */\nexport function toJsonSchema(schema: z.ZodType): JsonSchema {\n return z.toJSONSchema(schema, {\n reused: \"inline\",\n override(ctx) {\n const def = (\n ctx.zodSchema as unknown as {\n _def?: { metadata?: Record<string, unknown> };\n }\n )._def;\n if (def?.metadata) {\n Object.assign(ctx.jsonSchema, def.metadata);\n }\n },\n }) as JsonSchema;\n}\n","/**\n * $ref Resolution -- resolves local JSON Schema references.\n *\n * Walks the schema tree and replaces { \"$ref\": \"#/$defs/name\" } nodes\n * with the corresponding sub-schema from the document's $defs (Draft\n * 2020-12) or definitions (Draft 7) block.\n *\n * Properties on the referencing node are merged on top of the resolved\n * target, so context-specific overrides (e.g. a custom title) work\n * without duplicating the definition.\n *\n * Only local refs (starting with \"#/\") are supported. External URIs\n * are left as-is -- a future pluggable SchemaResolver interface will\n * handle those.\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\ntype Defs = Record<string, JsonSchemaProperty>;\n\n// Extract the definition name from a local $ref pointer.\n// Supports \"#/$defs/name\" (2020-12) and \"#/definitions/name\" (Draft 7).\nfunction extractDefName(ref: string): string | null {\n const match = ref.match(/^#\\/(\\$defs|definitions)\\/(.+)$/);\n return match ? match[2] : null;\n}\n\n// Resolve a $ref node: look up the definition, merge overrides, recurse.\nfunction resolveRef(\n node: JsonSchemaProperty,\n defs: Defs,\n seen: Set<string>,\n): JsonSchemaProperty {\n const ref = node.$ref!;\n const name = extractDefName(ref);\n if (!name || !defs[name]) return node;\n if (seen.has(ref)) return node;\n seen.add(ref);\n\n const resolved = defs[name];\n const overrides = Object.fromEntries(\n Object.entries(node).filter(([k]) => k !== \"$ref\"),\n ) as JsonSchemaProperty;\n const merged =\n Object.keys(overrides).length > 0\n ? { ...resolved, ...overrides }\n : resolved;\n\n const result = resolveNode(merged, defs, seen);\n seen.delete(ref);\n return result;\n}\n\n// Resolve an array of schemas, returning null if nothing changed.\nfunction resolveList(\n list: JsonSchemaProperty[],\n defs: Defs,\n seen: Set<string>,\n): JsonSchemaProperty[] | null {\n const resolved = list.map((s) => resolveNode(s, defs, seen));\n return resolved.some((s, i) => s !== list[i]) ? resolved : null;\n}\n\n// Resolve properties map, returning null if nothing changed.\nfunction resolveProperties(\n props: Record<string, JsonSchemaProperty>,\n defs: Defs,\n seen: Set<string>,\n): Record<string, JsonSchemaProperty> | null {\n const entries = Object.entries(props);\n const resolved = entries.map(\n ([k, v]) => [k, resolveNode(v, defs, seen)] as const,\n );\n return resolved.some(([, v], i) => v !== entries[i][1])\n ? Object.fromEntries(resolved)\n : null;\n}\n\n// Resolve items, returning null if unchanged.\nfunction resolveItems(\n items: JsonSchemaProperty,\n defs: Defs,\n seen: Set<string>,\n): JsonSchemaProperty | null {\n const r = resolveNode(items, defs, seen);\n return r !== items ? r : null;\n}\n\n// Collect all resolved children of a node into a partial override object.\nfunction resolveChildren(\n node: JsonSchemaProperty,\n defs: Defs,\n seen: Set<string>,\n): Partial<JsonSchemaProperty> | null {\n const overrides: Partial<JsonSchemaProperty> = {};\n let changed = false;\n\n const props = node.properties\n ? resolveProperties(node.properties, defs, seen)\n : null;\n if (props) {\n overrides.properties = props;\n changed = true;\n }\n\n const items = node.items ? resolveItems(node.items, defs, seen) : null;\n if (items) {\n overrides.items = items;\n changed = true;\n }\n\n for (const key of [\"allOf\", \"anyOf\", \"oneOf\"] as const) {\n const list = node[key] ? resolveList(node[key]!, defs, seen) : null;\n if (list) {\n (overrides as Record<string, unknown>)[key] = list;\n changed = true;\n }\n }\n\n return changed ? overrides : null;\n}\n\nfunction resolveNode(\n node: JsonSchemaProperty,\n defs: Defs,\n seen: Set<string>,\n): JsonSchemaProperty {\n if (node.$ref) return resolveRef(node, defs, seen);\n const overrides = resolveChildren(node, defs, seen);\n return overrides ? { ...node, ...overrides } : node;\n}\n\n/**\n * Resolve all local $ref pointers in a JSON Schema document.\n *\n * Returns the schema unchanged if it contains no $defs/definitions\n * or no $ref nodes. The original schema object is never mutated.\n */\nexport function resolveRefs(schema: JsonSchema): JsonSchema {\n const defs: Defs = schema.$defs ?? schema.definitions ?? {};\n if (Object.keys(defs).length === 0) return schema;\n\n // Cast to JsonSchemaProperty so resolveNode can walk the full tree\n // including the root-level properties and allOf. The result is cast\n // back -- JsonSchema is a structural subtype of JsonSchemaProperty.\n const seen = new Set<string>();\n const resolved = resolveNode(\n schema as unknown as JsonSchemaProperty,\n defs,\n seen,\n );\n return resolved as unknown as JsonSchema;\n}\n","/**\n * Recursive form data validation against a JSON Schema.\n *\n * Validates required fields at every nesting level: top-level properties,\n * nested objects (type: \"object\"), and array items (type: \"array\" with\n * items.type: \"object\"). Returns a flat list of human-readable error\n * strings and a boolean summary.\n *\n * Pure function -- no React, no side effects.\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\nimport { getSchemaProperties, getFieldTitle } from \"./schemaService\";\n\nexport interface ValidationResult {\n errors: string[];\n isValid: boolean;\n}\n\n// Check whether a single value satisfies a \"required\" constraint.\nfunction isEmpty(value: unknown): boolean {\n return value === undefined || value === null || value === \"\";\n}\n\n// Build a display label for a field at the given path depth.\nfunction fieldLabel(\n path: string,\n fieldName: string,\n property: JsonSchemaProperty,\n): string {\n return path\n ? `${path} > ${getFieldTitle(fieldName, property)}`\n : getFieldTitle(fieldName, property);\n}\n\n// Check whether a value is a non-array plain object suitable for recursion.\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n// Validate array cardinality and recurse into object items.\nfunction validateArray(\n property: JsonSchemaProperty,\n value: unknown[],\n label: string,\n errors: string[],\n): void {\n if (property.minItems !== undefined && value.length < property.minItems) {\n errors.push(`${label} requires at least ${property.minItems} item(s)`);\n }\n if (property.maxItems !== undefined && value.length > property.maxItems) {\n errors.push(`${label} allows at most ${property.maxItems} item(s)`);\n }\n if (!property.items?.properties) return;\n const itemProperties = property.items.properties;\n const itemSchema = property.items;\n for (let i = 0; i < value.length; i++) {\n if (isPlainObject(value[i])) {\n validateObject(\n itemProperties,\n itemSchema,\n value[i] as Record<string, unknown>,\n `${label}[${i + 1}]`,\n errors,\n );\n }\n }\n}\n\n// Check whether a property+value pair represents a nested object to recurse into.\nfunction isNestedObject(\n property: JsonSchemaProperty,\n value: unknown,\n): value is Record<string, unknown> {\n return (\n property.type === \"object\" && !!property.properties && isPlainObject(value)\n );\n}\n\n// Validate a single field: check required, recurse into objects/arrays.\nfunction validateField(\n property: JsonSchemaProperty,\n value: unknown,\n required: boolean,\n label: string,\n errors: string[],\n): void {\n if (required && isEmpty(value)) {\n errors.push(`${label} is required`);\n return;\n }\n if (isNestedObject(property, value)) {\n validateObject(property.properties!, property, value, label, errors);\n return;\n }\n if (property.type === \"array\" && Array.isArray(value)) {\n validateArray(property, value, label, errors);\n }\n}\n\n// Validate the required fields of a single object against its schema.\nfunction validateObject(\n properties: Record<string, JsonSchemaProperty>,\n schema: { required?: string[] },\n data: Record<string, unknown>,\n path: string,\n errors: string[],\n): void {\n for (const fieldName of Object.keys(properties)) {\n const property = properties[fieldName];\n const required = schema.required?.includes(fieldName) ?? false;\n const label = fieldLabel(path, fieldName, property);\n validateField(property, data[fieldName], required, label, errors);\n }\n}\n\n/**\n * Validate form data against a JSON Schema, recursing into nested objects\n * and array items.\n */\nexport function validateFormData(\n schema: JsonSchema,\n data: Record<string, unknown>,\n): ValidationResult {\n const errors: string[] = [];\n const properties = getSchemaProperties(schema);\n validateObject(properties, schema, data, \"\", errors);\n return { errors, isValid: errors.length === 0 };\n}\n","/**\n * diffFormData -- structural diff between two form data snapshots.\n *\n * Produces a FormDiff that classifies every change at field level (value\n * changes, nested object changes) and at array item level (add, remove,\n * move, modify). Array items are matched by their form-internal `_fid`\n * field, which is assigned by ArrayWidget and never sent to the server.\n *\n * Items that lack a `_fid` (e.g. items that arrived before the form\n * system assigned IDs) are matched by position as a fallback.\n */\n\nimport { isEqual, FORM_ITEM_ID_FIELD } from \"../utils\";\nimport type {\n FormDiff,\n FieldDiff,\n ValueChange,\n ArrayItemChange,\n} from \"./types\";\n\n// Check whether both values are non-array plain objects suitable for recursion.\nfunction areBothPlainObjects(a: unknown, b: unknown): boolean {\n return (\n a !== null &&\n b !== null &&\n typeof a === \"object\" &&\n typeof b === \"object\" &&\n !Array.isArray(a) &&\n !Array.isArray(b)\n );\n}\n\n// Diff two plain objects recursively, collecting ValueChange entries.\nfunction diffObjects(\n original: Record<string, unknown>,\n updated: Record<string, unknown>,\n prefix: string,\n): ValueChange[] {\n const changes: ValueChange[] = [];\n const allKeys = new Set([...Object.keys(original), ...Object.keys(updated)]);\n\n for (const key of allKeys) {\n if (key === FORM_ITEM_ID_FIELD) continue;\n const path = prefix ? `${prefix}.${key}` : key;\n const a = original[key];\n const b = updated[key];\n if (isEqual(a, b)) continue;\n\n if (areBothPlainObjects(a, b)) {\n changes.push(\n ...diffObjects(\n a as Record<string, unknown>,\n b as Record<string, unknown>,\n path,\n ),\n );\n } else {\n changes.push({ type: \"value\", path, from: a, to: b });\n }\n }\n\n return changes;\n}\n\n// Strip _fid from an item before including it in diff output.\nfunction stripFid(item: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(item).filter(([k]) => k !== FORM_ITEM_ID_FIELD),\n );\n}\n\ntype FidMap = Map<string, { item: Record<string, unknown>; index: number }>;\n\n// Build identity map: fid -> { item, index }\nfunction buildFidMap(items: Record<string, unknown>[]): FidMap {\n const map: FidMap = new Map();\n for (let i = 0; i < items.length; i++) {\n const fid = items[i][FORM_ITEM_ID_FIELD] as string | undefined;\n if (fid) map.set(fid, { item: items[i], index: i });\n }\n return map;\n}\n\n// Detect removed and added items between two fid maps.\nfunction detectAddedRemoved(\n origByFid: FidMap,\n updByFid: FidMap,\n): ArrayItemChange[] {\n const changes: ArrayItemChange[] = [];\n for (const [fid, { item, index }] of origByFid) {\n if (!updByFid.has(fid)) {\n changes.push({ type: \"removed\", fid, index, item: stripFid(item) });\n }\n }\n for (const [fid, { item, index }] of updByFid) {\n if (!origByFid.has(fid)) {\n changes.push({ type: \"added\", fid, index, item: stripFid(item) });\n }\n }\n return changes;\n}\n\n// Detect moved and modified items present in both snapshots.\nfunction detectMovedModified(\n origByFid: FidMap,\n updByFid: FidMap,\n): ArrayItemChange[] {\n const changes: ArrayItemChange[] = [];\n for (const [fid, { item: updItem, index: updIndex }] of updByFid) {\n const orig = origByFid.get(fid);\n if (!orig) continue;\n if (orig.index !== updIndex) {\n changes.push({ type: \"moved\", fid, from: orig.index, to: updIndex });\n }\n const subChanges = diffObjects(orig.item, updItem, \"\");\n if (subChanges.length > 0) {\n changes.push({\n type: \"modified\",\n fid,\n index: updIndex,\n changes: subChanges,\n });\n }\n }\n return changes;\n}\n\n// Fallback diff for items without _fid: match by position.\nfunction diffUnfidItems(\n original: Record<string, unknown>[],\n updated: Record<string, unknown>[],\n): ArrayItemChange[] {\n const unfidOrig = original.filter((i) => !i[FORM_ITEM_ID_FIELD]);\n const unfidUpd = updated.filter((i) => !i[FORM_ITEM_ID_FIELD]);\n const changes: ArrayItemChange[] = [];\n const len = Math.min(unfidOrig.length, unfidUpd.length);\n for (let i = 0; i < len; i++) {\n if (isEqual(unfidOrig[i], unfidUpd[i])) continue;\n const subChanges = diffObjects(unfidOrig[i], unfidUpd[i], \"\");\n if (subChanges.length > 0) {\n changes.push({\n type: \"modified\",\n fid: `__pos_${i}`,\n index: i,\n changes: subChanges,\n });\n }\n }\n return changes;\n}\n\n// Diff two arrays of objects using _fid for item identity.\nfunction diffArrays(\n original: Record<string, unknown>[],\n updated: Record<string, unknown>[],\n field: string,\n): FieldDiff {\n const origByFid = buildFidMap(original);\n const updByFid = buildFidMap(updated);\n const changes: ArrayItemChange[] = [\n ...detectAddedRemoved(origByFid, updByFid),\n ...detectMovedModified(origByFid, updByFid),\n ...diffUnfidItems(original, updated),\n ];\n return { type: \"array\", field, changes };\n}\n\n/**\n * Compute a structural diff between two form data snapshots.\n *\n * Returns a FormDiff array with one entry per changed top-level field.\n * Fields whose value is deeply equal are omitted.\n *\n * Array items are matched by their `_fid` field (assigned by ArrayWidget).\n * Items without `_fid` fall back to positional matching.\n */\n// Check whether an array contains object items (vs scalar items).\nfunction isObjectArray(arr: unknown[]): boolean {\n return arr.some((i) => i !== null && typeof i === \"object\");\n}\n\n// Diff a single top-level field, returning the entries to add to the FormDiff.\nfunction diffField(\n key: string,\n a: unknown,\n b: unknown,\n): (ValueChange | FieldDiff)[] {\n if (Array.isArray(a) || Array.isArray(b)) {\n const origArr = Array.isArray(a) ? a : [];\n const updArr = Array.isArray(b) ? b : [];\n if (isObjectArray(origArr) || isObjectArray(updArr)) {\n return [\n diffArrays(\n origArr as Record<string, unknown>[],\n updArr as Record<string, unknown>[],\n key,\n ),\n ];\n }\n return [{ type: \"value\", path: key, from: a, to: b }];\n }\n if (areBothPlainObjects(a, b)) {\n return diffObjects(\n a as Record<string, unknown>,\n b as Record<string, unknown>,\n key,\n );\n }\n return [{ type: \"value\", path: key, from: a, to: b }];\n}\n\n/**\n * Compute a structural diff between two form data snapshots.\n */\nexport function diffFormData(\n original: Record<string, unknown>,\n updated: Record<string, unknown>,\n): FormDiff {\n const diff: FormDiff = [];\n const allKeys = new Set([...Object.keys(original), ...Object.keys(updated)]);\n\n for (const key of allKeys) {\n if (key === FORM_ITEM_ID_FIELD) continue;\n const a = original[key];\n const b = updated[key];\n if (isEqual(a, b)) continue;\n diff.push(...diffField(key, a, b));\n }\n\n return diff;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -41,6 +41,14 @@ interface RestFetcherConfig {
|
|
|
41
41
|
* Override when the endpoint uses a different filter key (e.g. "name").
|
|
42
42
|
*/
|
|
43
43
|
searchKey?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Fixed query parameters sent on every request, in addition to the search
|
|
46
|
+
* term and limit. Use to scope the option list (e.g. { account_id: "acc_1" }
|
|
47
|
+
* to fetch only entities within one account). Values are static per config;
|
|
48
|
+
* to scope dynamically, build the config at render time with the current
|
|
49
|
+
* value.
|
|
50
|
+
*/
|
|
51
|
+
extraParams?: Record<string, string>;
|
|
44
52
|
}
|
|
45
53
|
/**
|
|
46
54
|
* Configuration for creating a GraphQL-backed fetcher.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@petrarca/sonnet-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Shared utilities, schema tools, and hooks for the Petrarca Sonnet component library",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"./entityOptions": {
|
|
30
30
|
"import": "./dist/entityOptions/index.js",
|
|
31
31
|
"types": "./dist/entityOptions/index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./config": {
|
|
34
|
+
"import": "./dist/config/index.js",
|
|
35
|
+
"types": "./dist/config/index.d.ts"
|
|
32
36
|
}
|
|
33
37
|
},
|
|
34
38
|
"files": [
|