@makehq/forman-schema 1.8.4 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +85 -8
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +85 -8
- package/package.json +1 -1
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) {
|
|
@@ -531,7 +585,8 @@ function validateFormanField(field) {
|
|
|
531
585
|
}
|
|
532
586
|
function appendQueryString(path, domain, tail) {
|
|
533
587
|
if (path.startsWith("api://")) return path;
|
|
534
|
-
const
|
|
588
|
+
const existingParams = new Set(new URL(path).searchParams.keys());
|
|
589
|
+
const queryString = tail.filter((part) => !existingParams.has(part)).map((part) => `${encodeURIComponent(part)}={{${part}}}`).join("&");
|
|
535
590
|
if (!queryString) return path;
|
|
536
591
|
const separator = path.includes("?") ? "&" : "?";
|
|
537
592
|
return `${path}${separator}${queryString}`;
|
|
@@ -1079,6 +1134,25 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1079
1134
|
}
|
|
1080
1135
|
);
|
|
1081
1136
|
errors.push(...result.errors);
|
|
1137
|
+
const restoreExtras = options?.states && domains[domain]?.restoreExtras;
|
|
1138
|
+
if (restoreExtras) {
|
|
1139
|
+
const fieldStateMap = new Map(
|
|
1140
|
+
roots[domain].fieldStates.map(({ path, state }) => {
|
|
1141
|
+
return [pathToString(path), state];
|
|
1142
|
+
})
|
|
1143
|
+
);
|
|
1144
|
+
for (const [fieldPath, extra] of Object.entries(restoreExtras)) {
|
|
1145
|
+
const state = fieldStateMap.get(fieldPath);
|
|
1146
|
+
if (state) {
|
|
1147
|
+
state.extra = extra;
|
|
1148
|
+
} else {
|
|
1149
|
+
roots[domain].fieldStates.push({
|
|
1150
|
+
path: stringToPath(fieldPath),
|
|
1151
|
+
state: { extra }
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1082
1156
|
}
|
|
1083
1157
|
return {
|
|
1084
1158
|
valid: errors.length === 0,
|
|
@@ -1506,11 +1580,14 @@ async function handleSelectType(value, field, context) {
|
|
|
1506
1580
|
if (resolved == null || typeof resolved !== "object") {
|
|
1507
1581
|
return {
|
|
1508
1582
|
valid: false,
|
|
1509
|
-
errors: [
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1583
|
+
errors: [
|
|
1584
|
+
...errors,
|
|
1585
|
+
{
|
|
1586
|
+
domain: context.domain,
|
|
1587
|
+
path: context.path.join("."),
|
|
1588
|
+
message: `Remote resource ${optionsOrGroups} returned no data.`
|
|
1589
|
+
}
|
|
1590
|
+
]
|
|
1514
1591
|
};
|
|
1515
1592
|
}
|
|
1516
1593
|
optionsOrGroups = Array.isArray(resolved) ? resolved : [resolved];
|
|
@@ -1876,8 +1953,8 @@ function toJSONSchema(field) {
|
|
|
1876
1953
|
function validateFormanWithDomains(domains, options) {
|
|
1877
1954
|
return validateFormanWithDomainsInternal(domains, options);
|
|
1878
1955
|
}
|
|
1879
|
-
function validateForman(values, schema, options) {
|
|
1880
|
-
return validateFormanWithDomains({ default: { values, schema } }, options);
|
|
1956
|
+
function validateForman(values, schema, options, restoreExtras) {
|
|
1957
|
+
return validateFormanWithDomains({ default: { values, schema, restoreExtras } }, options);
|
|
1881
1958
|
}
|
|
1882
1959
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1883
1960
|
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) {
|
|
@@ -502,7 +556,8 @@ function validateFormanField(field) {
|
|
|
502
556
|
}
|
|
503
557
|
function appendQueryString(path, domain, tail) {
|
|
504
558
|
if (path.startsWith("api://")) return path;
|
|
505
|
-
const
|
|
559
|
+
const existingParams = new Set(new URL(path).searchParams.keys());
|
|
560
|
+
const queryString = tail.filter((part) => !existingParams.has(part)).map((part) => `${encodeURIComponent(part)}={{${part}}}`).join("&");
|
|
506
561
|
if (!queryString) return path;
|
|
507
562
|
const separator = path.includes("?") ? "&" : "?";
|
|
508
563
|
return `${path}${separator}${queryString}`;
|
|
@@ -1050,6 +1105,25 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1050
1105
|
}
|
|
1051
1106
|
);
|
|
1052
1107
|
errors.push(...result.errors);
|
|
1108
|
+
const restoreExtras = options?.states && domains[domain]?.restoreExtras;
|
|
1109
|
+
if (restoreExtras) {
|
|
1110
|
+
const fieldStateMap = new Map(
|
|
1111
|
+
roots[domain].fieldStates.map(({ path, state }) => {
|
|
1112
|
+
return [pathToString(path), state];
|
|
1113
|
+
})
|
|
1114
|
+
);
|
|
1115
|
+
for (const [fieldPath, extra] of Object.entries(restoreExtras)) {
|
|
1116
|
+
const state = fieldStateMap.get(fieldPath);
|
|
1117
|
+
if (state) {
|
|
1118
|
+
state.extra = extra;
|
|
1119
|
+
} else {
|
|
1120
|
+
roots[domain].fieldStates.push({
|
|
1121
|
+
path: stringToPath(fieldPath),
|
|
1122
|
+
state: { extra }
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1053
1127
|
}
|
|
1054
1128
|
return {
|
|
1055
1129
|
valid: errors.length === 0,
|
|
@@ -1477,11 +1551,14 @@ async function handleSelectType(value, field, context) {
|
|
|
1477
1551
|
if (resolved == null || typeof resolved !== "object") {
|
|
1478
1552
|
return {
|
|
1479
1553
|
valid: false,
|
|
1480
|
-
errors: [
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1554
|
+
errors: [
|
|
1555
|
+
...errors,
|
|
1556
|
+
{
|
|
1557
|
+
domain: context.domain,
|
|
1558
|
+
path: context.path.join("."),
|
|
1559
|
+
message: `Remote resource ${optionsOrGroups} returned no data.`
|
|
1560
|
+
}
|
|
1561
|
+
]
|
|
1485
1562
|
};
|
|
1486
1563
|
}
|
|
1487
1564
|
optionsOrGroups = Array.isArray(resolved) ? resolved : [resolved];
|
|
@@ -1847,8 +1924,8 @@ function toJSONSchema(field) {
|
|
|
1847
1924
|
function validateFormanWithDomains(domains, options) {
|
|
1848
1925
|
return validateFormanWithDomainsInternal(domains, options);
|
|
1849
1926
|
}
|
|
1850
|
-
function validateForman(values, schema, options) {
|
|
1851
|
-
return validateFormanWithDomains({ default: { values, schema } }, options);
|
|
1927
|
+
function validateForman(values, schema, options, restoreExtras) {
|
|
1928
|
+
return validateFormanWithDomains({ default: { values, schema, restoreExtras } }, options);
|
|
1852
1929
|
}
|
|
1853
1930
|
export {
|
|
1854
1931
|
toFormanSchema,
|