@makehq/forman-schema 1.8.4 → 1.9.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/index.cjs CHANGED
@@ -98,6 +98,60 @@ function findValueInSelectOptions(field, value, optionsAndGroups) {
98
98
  const found = optionsAndGroups.find((option) => "value" in option && option.value === value);
99
99
  return found;
100
100
  }
101
+ function pathToString(path) {
102
+ let result = "";
103
+ for (const key of path) {
104
+ if (typeof key === "number") {
105
+ result += `[${key}]`;
106
+ } else {
107
+ if (result.length > 0) {
108
+ result += ".";
109
+ }
110
+ if (key.includes("`")) {
111
+ throw new Error(`Invalid path key: backticks are not allowed in key "${key}"`);
112
+ }
113
+ result += key.includes(".") ? `\`${key}\`` : key;
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ function stringToPath(str) {
119
+ const path = [];
120
+ let i = 0;
121
+ while (i < str.length) {
122
+ if (str[i] === ".") {
123
+ i++;
124
+ if (i >= str.length) break;
125
+ }
126
+ if (str[i] === "[") {
127
+ const end = str.indexOf("]", i);
128
+ if (end === -1) {
129
+ throw new Error(`Invalid path: missing closing bracket in "${str}"`);
130
+ }
131
+ const num = Number(str.slice(i + 1, end));
132
+ if (!Number.isInteger(num) || num < 0) {
133
+ throw new Error(`Invalid path: non-numeric or negative index in "${str}"`);
134
+ }
135
+ path.push(num);
136
+ i = end + 1;
137
+ } else if (str[i] === "`") {
138
+ const end = str.indexOf("`", i + 1);
139
+ if (end === -1) {
140
+ throw new Error(`Invalid path: missing closing backtick in "${str}"`);
141
+ }
142
+ path.push(str.slice(i + 1, end));
143
+ i = end + 1;
144
+ } else {
145
+ let end = i;
146
+ while (end < str.length && str[end] !== "." && str[end] !== "[") {
147
+ end++;
148
+ }
149
+ path.push(str.slice(i, end));
150
+ i = end;
151
+ }
152
+ }
153
+ return path;
154
+ }
101
155
  function buildRestoreStructure(items) {
102
156
  const result = {};
103
157
  for (const item of items) {
@@ -1079,6 +1133,25 @@ async function validateFormanWithDomainsInternal(domains, options) {
1079
1133
  }
1080
1134
  );
1081
1135
  errors.push(...result.errors);
1136
+ const restoreExtras = options?.states && domains[domain]?.restoreExtras;
1137
+ if (restoreExtras) {
1138
+ const fieldStateMap = new Map(
1139
+ roots[domain].fieldStates.map(({ path, state }) => {
1140
+ return [pathToString(path), state];
1141
+ })
1142
+ );
1143
+ for (const [fieldPath, extra] of Object.entries(restoreExtras)) {
1144
+ const state = fieldStateMap.get(fieldPath);
1145
+ if (state) {
1146
+ state.extra = extra;
1147
+ } else {
1148
+ roots[domain].fieldStates.push({
1149
+ path: stringToPath(fieldPath),
1150
+ state: { extra }
1151
+ });
1152
+ }
1153
+ }
1154
+ }
1082
1155
  }
1083
1156
  return {
1084
1157
  valid: errors.length === 0,
@@ -1506,11 +1579,14 @@ async function handleSelectType(value, field, context) {
1506
1579
  if (resolved == null || typeof resolved !== "object") {
1507
1580
  return {
1508
1581
  valid: false,
1509
- errors: [...errors, {
1510
- domain: context.domain,
1511
- path: context.path.join("."),
1512
- message: `Remote resource ${optionsOrGroups} returned no data.`
1513
- }]
1582
+ errors: [
1583
+ ...errors,
1584
+ {
1585
+ domain: context.domain,
1586
+ path: context.path.join("."),
1587
+ message: `Remote resource ${optionsOrGroups} returned no data.`
1588
+ }
1589
+ ]
1514
1590
  };
1515
1591
  }
1516
1592
  optionsOrGroups = Array.isArray(resolved) ? resolved : [resolved];
@@ -1876,8 +1952,8 @@ function toJSONSchema(field) {
1876
1952
  function validateFormanWithDomains(domains, options) {
1877
1953
  return validateFormanWithDomainsInternal(domains, options);
1878
1954
  }
1879
- function validateForman(values, schema, options) {
1880
- return validateFormanWithDomains({ default: { values, schema } }, options);
1955
+ function validateForman(values, schema, options, restoreExtras) {
1956
+ return validateFormanWithDomains({ default: { values, schema, restoreExtras } }, options);
1881
1957
  }
1882
1958
  // Annotate the CommonJS export names for ESM import in node:
1883
1959
  0 && (module.exports = {
package/dist/index.d.cts CHANGED
@@ -205,6 +205,7 @@ type FormanSchemaFieldState = {
205
205
  label?: string;
206
206
  path?: Array<string>;
207
207
  data?: Record<string, unknown>;
208
+ extra?: Record<string, unknown>;
208
209
  nested?: Record<string, FormanSchemaFieldState>;
209
210
  items?: Record<string, FormanSchemaFieldState>[];
210
211
  };
@@ -239,14 +240,19 @@ declare function toJSONSchema(field: FormanSchemaField): JSONSchema7;
239
240
  declare function validateFormanWithDomains(domains: Record<string, {
240
241
  values: Record<string, unknown>;
241
242
  schema: FormanSchemaField[];
243
+ /** Extra values injected into restore states, keyed by string path (dot notation, `[index]`, backtick-escaping). */
244
+ restoreExtras?: Record<string, Record<string, unknown>>;
242
245
  }>, options?: FormanValidationOptions): Promise<FormanValidationResult>;
243
246
  /**
244
247
  * Validates a simple Forman values against a schema
245
248
  * @param values The values to validate
246
249
  * @param schema The schema to validate against
247
250
  * @param options The validation options
251
+ * @param restoreExtras Values to be injected into restore objects of particular fields.
252
+ * Keyed by string path using dot notation for nested keys, `[index]` for array indices,
253
+ * and backtick-escaping for keys containing dots (e.g. `"a.b[0].c"`, `` "`dotted.key`.child" ``).
248
254
  * @returns The validation result
249
255
  */
250
- declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions): Promise<FormanValidationResult>;
256
+ declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanValidationResult>;
251
257
 
252
258
  export { type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, toFormanSchema, toJSONSchema, validateForman, validateFormanWithDomains };
package/dist/index.d.ts CHANGED
@@ -205,6 +205,7 @@ type FormanSchemaFieldState = {
205
205
  label?: string;
206
206
  path?: Array<string>;
207
207
  data?: Record<string, unknown>;
208
+ extra?: Record<string, unknown>;
208
209
  nested?: Record<string, FormanSchemaFieldState>;
209
210
  items?: Record<string, FormanSchemaFieldState>[];
210
211
  };
@@ -239,14 +240,19 @@ declare function toJSONSchema(field: FormanSchemaField): JSONSchema7;
239
240
  declare function validateFormanWithDomains(domains: Record<string, {
240
241
  values: Record<string, unknown>;
241
242
  schema: FormanSchemaField[];
243
+ /** Extra values injected into restore states, keyed by string path (dot notation, `[index]`, backtick-escaping). */
244
+ restoreExtras?: Record<string, Record<string, unknown>>;
242
245
  }>, options?: FormanValidationOptions): Promise<FormanValidationResult>;
243
246
  /**
244
247
  * Validates a simple Forman values against a schema
245
248
  * @param values The values to validate
246
249
  * @param schema The schema to validate against
247
250
  * @param options The validation options
251
+ * @param restoreExtras Values to be injected into restore objects of particular fields.
252
+ * Keyed by string path using dot notation for nested keys, `[index]` for array indices,
253
+ * and backtick-escaping for keys containing dots (e.g. `"a.b[0].c"`, `` "`dotted.key`.child" ``).
248
254
  * @returns The validation result
249
255
  */
250
- declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions): Promise<FormanValidationResult>;
256
+ declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanValidationResult>;
251
257
 
252
258
  export { type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, toFormanSchema, toJSONSchema, validateForman, validateFormanWithDomains };
package/dist/index.js CHANGED
@@ -69,6 +69,60 @@ function findValueInSelectOptions(field, value, optionsAndGroups) {
69
69
  const found = optionsAndGroups.find((option) => "value" in option && option.value === value);
70
70
  return found;
71
71
  }
72
+ function pathToString(path) {
73
+ let result = "";
74
+ for (const key of path) {
75
+ if (typeof key === "number") {
76
+ result += `[${key}]`;
77
+ } else {
78
+ if (result.length > 0) {
79
+ result += ".";
80
+ }
81
+ if (key.includes("`")) {
82
+ throw new Error(`Invalid path key: backticks are not allowed in key "${key}"`);
83
+ }
84
+ result += key.includes(".") ? `\`${key}\`` : key;
85
+ }
86
+ }
87
+ return result;
88
+ }
89
+ function stringToPath(str) {
90
+ const path = [];
91
+ let i = 0;
92
+ while (i < str.length) {
93
+ if (str[i] === ".") {
94
+ i++;
95
+ if (i >= str.length) break;
96
+ }
97
+ if (str[i] === "[") {
98
+ const end = str.indexOf("]", i);
99
+ if (end === -1) {
100
+ throw new Error(`Invalid path: missing closing bracket in "${str}"`);
101
+ }
102
+ const num = Number(str.slice(i + 1, end));
103
+ if (!Number.isInteger(num) || num < 0) {
104
+ throw new Error(`Invalid path: non-numeric or negative index in "${str}"`);
105
+ }
106
+ path.push(num);
107
+ i = end + 1;
108
+ } else if (str[i] === "`") {
109
+ const end = str.indexOf("`", i + 1);
110
+ if (end === -1) {
111
+ throw new Error(`Invalid path: missing closing backtick in "${str}"`);
112
+ }
113
+ path.push(str.slice(i + 1, end));
114
+ i = end + 1;
115
+ } else {
116
+ let end = i;
117
+ while (end < str.length && str[end] !== "." && str[end] !== "[") {
118
+ end++;
119
+ }
120
+ path.push(str.slice(i, end));
121
+ i = end;
122
+ }
123
+ }
124
+ return path;
125
+ }
72
126
  function buildRestoreStructure(items) {
73
127
  const result = {};
74
128
  for (const item of items) {
@@ -1050,6 +1104,25 @@ async function validateFormanWithDomainsInternal(domains, options) {
1050
1104
  }
1051
1105
  );
1052
1106
  errors.push(...result.errors);
1107
+ const restoreExtras = options?.states && domains[domain]?.restoreExtras;
1108
+ if (restoreExtras) {
1109
+ const fieldStateMap = new Map(
1110
+ roots[domain].fieldStates.map(({ path, state }) => {
1111
+ return [pathToString(path), state];
1112
+ })
1113
+ );
1114
+ for (const [fieldPath, extra] of Object.entries(restoreExtras)) {
1115
+ const state = fieldStateMap.get(fieldPath);
1116
+ if (state) {
1117
+ state.extra = extra;
1118
+ } else {
1119
+ roots[domain].fieldStates.push({
1120
+ path: stringToPath(fieldPath),
1121
+ state: { extra }
1122
+ });
1123
+ }
1124
+ }
1125
+ }
1053
1126
  }
1054
1127
  return {
1055
1128
  valid: errors.length === 0,
@@ -1477,11 +1550,14 @@ async function handleSelectType(value, field, context) {
1477
1550
  if (resolved == null || typeof resolved !== "object") {
1478
1551
  return {
1479
1552
  valid: false,
1480
- errors: [...errors, {
1481
- domain: context.domain,
1482
- path: context.path.join("."),
1483
- message: `Remote resource ${optionsOrGroups} returned no data.`
1484
- }]
1553
+ errors: [
1554
+ ...errors,
1555
+ {
1556
+ domain: context.domain,
1557
+ path: context.path.join("."),
1558
+ message: `Remote resource ${optionsOrGroups} returned no data.`
1559
+ }
1560
+ ]
1485
1561
  };
1486
1562
  }
1487
1563
  optionsOrGroups = Array.isArray(resolved) ? resolved : [resolved];
@@ -1847,8 +1923,8 @@ function toJSONSchema(field) {
1847
1923
  function validateFormanWithDomains(domains, options) {
1848
1924
  return validateFormanWithDomainsInternal(domains, options);
1849
1925
  }
1850
- function validateForman(values, schema, options) {
1851
- return validateFormanWithDomains({ default: { values, schema } }, options);
1926
+ function validateForman(values, schema, options, restoreExtras) {
1927
+ return validateFormanWithDomains({ default: { values, schema, restoreExtras } }, options);
1852
1928
  }
1853
1929
  export {
1854
1930
  toFormanSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makehq/forman-schema",
3
- "version": "1.8.4",
3
+ "version": "1.9.0",
4
4
  "description": "Forman Schema Tools",
5
5
  "license": "MIT",
6
6
  "author": "Make",