@makehq/forman-schema 1.17.0 → 1.19.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 +33 -5
- package/dist/index.d.cts +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +33 -5
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -58,6 +58,9 @@ function noEmpty(text) {
|
|
|
58
58
|
function isObject(value) {
|
|
59
59
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
60
60
|
}
|
|
61
|
+
function isBooleanBranchNested(nested) {
|
|
62
|
+
return isObject(nested) && !("store" in nested) && ("true" in nested || "false" in nested);
|
|
63
|
+
}
|
|
61
64
|
function isOptionGroup(value) {
|
|
62
65
|
return "options" in value && Array.isArray(value.options);
|
|
63
66
|
}
|
|
@@ -1124,7 +1127,7 @@ function processRpcDirective(field, result, context) {
|
|
|
1124
1127
|
return result;
|
|
1125
1128
|
}
|
|
1126
1129
|
function extractNestedAndDomain(field) {
|
|
1127
|
-
const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : isObject(field.nested) ? field.nested.store : field.nested;
|
|
1130
|
+
const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : isBooleanBranchNested(field.nested) ? void 0 : isObject(field.nested) ? field.nested.store : field.nested;
|
|
1128
1131
|
const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : isObject(field.nested) && field.nested.domain ? field.nested.domain : void 0;
|
|
1129
1132
|
return { nested, domain };
|
|
1130
1133
|
}
|
|
@@ -1389,10 +1392,22 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1389
1392
|
states: errors.length === 0 && options?.states ? buildRestoreStructure(
|
|
1390
1393
|
Object.keys(domains).map((domain) => roots[domain].fieldStates.map((state) => ({ domain, ...state }))).flat()
|
|
1391
1394
|
) : void 0,
|
|
1392
|
-
schemas: errors.length === 0 && options?.schemas ? Object.fromEntries(Object.keys(domains).map((domain) => [domain, roots[domain].schemaFields])) : void 0
|
|
1395
|
+
schemas: errors.length === 0 && options?.schemas ? Object.fromEntries(Object.keys(domains).map((domain) => [domain, roots[domain].schemaFields])) : void 0,
|
|
1396
|
+
// Same spec as `schemas`, but also on the failure path. Remote-resolved fields are
|
|
1397
|
+
// built before the verdict, so a rejection can name the fields it rejected instead of
|
|
1398
|
+
// leaving the caller to guess at a sub-form it never saw. `schemas` stays success-only:
|
|
1399
|
+
// callers read its presence as a success signal.
|
|
1400
|
+
resolvedSchemas: options?.schemas ? Object.fromEntries(Object.keys(domains).map((domain) => [domain, roots[domain].schemaFields])) : void 0
|
|
1393
1401
|
};
|
|
1394
1402
|
}
|
|
1395
1403
|
async function validateFormanValue(value, field, context) {
|
|
1404
|
+
if (context.registerOnly) {
|
|
1405
|
+
return {
|
|
1406
|
+
valid: true,
|
|
1407
|
+
errors: [],
|
|
1408
|
+
warnings: []
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1396
1411
|
if (isVisualType(field.type)) {
|
|
1397
1412
|
return {
|
|
1398
1413
|
valid: true,
|
|
@@ -1414,7 +1429,7 @@ async function validateFormanValue(value, field, context) {
|
|
|
1414
1429
|
};
|
|
1415
1430
|
}
|
|
1416
1431
|
const normalizedField = normalizeFormanFieldType(field);
|
|
1417
|
-
if (normalizedField.required && (value == null || value === "")) {
|
|
1432
|
+
if (normalizedField.required && !context.suppressRequired && (value == null || value === "")) {
|
|
1418
1433
|
return {
|
|
1419
1434
|
valid: false,
|
|
1420
1435
|
errors: [
|
|
@@ -1584,7 +1599,7 @@ async function handleCollectionType2(value, field, context) {
|
|
|
1584
1599
|
continue;
|
|
1585
1600
|
}
|
|
1586
1601
|
if (context2.strict && !seen.has(subField2.name)) seen.add(subField2.name);
|
|
1587
|
-
if (path.length === 0) {
|
|
1602
|
+
if (path.length === 0 && !context2.registerOnly) {
|
|
1588
1603
|
context2.roots[context2.domain].schemaFields.push(clampFieldForSchema(subField2));
|
|
1589
1604
|
}
|
|
1590
1605
|
const result2 = await validateFormanValue(value[subField2.name], subField2, {
|
|
@@ -2177,7 +2192,7 @@ async function handlePrimitiveType2(value, field, context) {
|
|
|
2177
2192
|
}
|
|
2178
2193
|
const nested = extractNestedFromField(field);
|
|
2179
2194
|
if (nested) {
|
|
2180
|
-
const result = await handleNestedFields(nested, value, field, context);
|
|
2195
|
+
const result = FORMAN_TYPE_MAP2[field.type] === "boolean" ? await handleBooleanNestedFields(nested, value, field, context) : await handleNestedFields(nested, value, field, context);
|
|
2181
2196
|
errors.push(...result.errors);
|
|
2182
2197
|
warnings.push(...result.warnings);
|
|
2183
2198
|
}
|
|
@@ -2187,6 +2202,19 @@ async function handlePrimitiveType2(value, field, context) {
|
|
|
2187
2202
|
warnings
|
|
2188
2203
|
};
|
|
2189
2204
|
}
|
|
2205
|
+
async function handleBooleanNestedFields(nested, value, field, context) {
|
|
2206
|
+
if (isBooleanBranchNested(nested)) {
|
|
2207
|
+
const active2 = value === false ? nested.false : nested.true;
|
|
2208
|
+
const inactive = value === false ? nested.true : nested.false;
|
|
2209
|
+
const result = active2 ? await handleNestedFields(active2, value, field, context) : { valid: true, errors: [], warnings: [] };
|
|
2210
|
+
if (context.strict && inactive) {
|
|
2211
|
+
await handleNestedFields(inactive, value, field, { ...context, registerOnly: true });
|
|
2212
|
+
}
|
|
2213
|
+
return result;
|
|
2214
|
+
}
|
|
2215
|
+
const active = field.reversedNested === true ? value === false : value === true;
|
|
2216
|
+
return handleNestedFields(nested, value, field, active ? context : { ...context, suppressRequired: true });
|
|
2217
|
+
}
|
|
2190
2218
|
|
|
2191
2219
|
// src/json.ts
|
|
2192
2220
|
var JSON_PRIMITIVE_TYPE_MAP = {
|
package/dist/index.d.cts
CHANGED
|
@@ -47,8 +47,8 @@ type FormanSchemaField = {
|
|
|
47
47
|
advanced?: boolean;
|
|
48
48
|
/** Human readable label for the field */
|
|
49
49
|
label?: string;
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
nested?: FormanSchemaNested | FormanSchemaBooleanNested;
|
|
51
|
+
reversedNested?: boolean;
|
|
52
52
|
/** Validation rules */
|
|
53
53
|
validate?: FormanSchemaValidation;
|
|
54
54
|
/** Whether the field is disabled (`false` by default) */
|
|
@@ -186,6 +186,10 @@ type FormanSchemaExtendedNested = {
|
|
|
186
186
|
/** Domain for the nested fields */
|
|
187
187
|
domain?: string;
|
|
188
188
|
};
|
|
189
|
+
type FormanSchemaBooleanNested = {
|
|
190
|
+
true?: (FormanSchemaField | string)[] | string;
|
|
191
|
+
false?: (FormanSchemaField | string)[] | string;
|
|
192
|
+
};
|
|
189
193
|
/**
|
|
190
194
|
* Validation result
|
|
191
195
|
*/
|
|
@@ -212,8 +216,15 @@ type FormanValidationResult = {
|
|
|
212
216
|
}[];
|
|
213
217
|
/** States of fields grouped by domain */
|
|
214
218
|
states?: Record<string, FormanSchemaFieldState>;
|
|
215
|
-
/** Resolved schema fields per domain */
|
|
219
|
+
/** Resolved schema fields per domain. Requires `options.schemas`; only present when validation succeeded. */
|
|
216
220
|
schemas?: Record<string, FormanSchemaField[]>;
|
|
221
|
+
/**
|
|
222
|
+
* Resolved schema fields per domain. Requires `options.schemas`, and unlike {@link schemas} is
|
|
223
|
+
* present whether or not validation succeeded — including fields revealed by a remote-resolved
|
|
224
|
+
* (`rpc://`) sub-form, which cannot be known from the static schema alone. Lets a caller report
|
|
225
|
+
* which fields it rejected.
|
|
226
|
+
*/
|
|
227
|
+
resolvedSchemas?: Record<string, FormanSchemaField[]>;
|
|
217
228
|
};
|
|
218
229
|
type FormanSchemaFieldState = {
|
|
219
230
|
mode?: 'chose' | 'edit';
|
|
@@ -402,4 +413,4 @@ declare function validateFormanWithDomains(domains: Record<string, {
|
|
|
402
413
|
*/
|
|
403
414
|
declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanValidationResult>;
|
|
404
415
|
|
|
405
|
-
export { type FormanExternalValidationResult, type FormanJsonSchemaOptions, type FormanJsonSchemaResult, type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, type FormanValidationOptions, type FormanValidationResult, SchemaConversionError, resolveFormanFieldType, toFormanSchema, toJSONSchema, toJSONSchemaAdvanced, validateForman, validateFormanWithDomains };
|
|
416
|
+
export { type FormanExternalValidationResult, type FormanJsonSchemaOptions, type FormanJsonSchemaResult, type FormanSchemaBooleanNested, type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, type FormanValidationOptions, type FormanValidationResult, SchemaConversionError, resolveFormanFieldType, toFormanSchema, toJSONSchema, toJSONSchemaAdvanced, validateForman, validateFormanWithDomains };
|
package/dist/index.d.ts
CHANGED
|
@@ -47,8 +47,8 @@ type FormanSchemaField = {
|
|
|
47
47
|
advanced?: boolean;
|
|
48
48
|
/** Human readable label for the field */
|
|
49
49
|
label?: string;
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
nested?: FormanSchemaNested | FormanSchemaBooleanNested;
|
|
51
|
+
reversedNested?: boolean;
|
|
52
52
|
/** Validation rules */
|
|
53
53
|
validate?: FormanSchemaValidation;
|
|
54
54
|
/** Whether the field is disabled (`false` by default) */
|
|
@@ -186,6 +186,10 @@ type FormanSchemaExtendedNested = {
|
|
|
186
186
|
/** Domain for the nested fields */
|
|
187
187
|
domain?: string;
|
|
188
188
|
};
|
|
189
|
+
type FormanSchemaBooleanNested = {
|
|
190
|
+
true?: (FormanSchemaField | string)[] | string;
|
|
191
|
+
false?: (FormanSchemaField | string)[] | string;
|
|
192
|
+
};
|
|
189
193
|
/**
|
|
190
194
|
* Validation result
|
|
191
195
|
*/
|
|
@@ -212,8 +216,15 @@ type FormanValidationResult = {
|
|
|
212
216
|
}[];
|
|
213
217
|
/** States of fields grouped by domain */
|
|
214
218
|
states?: Record<string, FormanSchemaFieldState>;
|
|
215
|
-
/** Resolved schema fields per domain */
|
|
219
|
+
/** Resolved schema fields per domain. Requires `options.schemas`; only present when validation succeeded. */
|
|
216
220
|
schemas?: Record<string, FormanSchemaField[]>;
|
|
221
|
+
/**
|
|
222
|
+
* Resolved schema fields per domain. Requires `options.schemas`, and unlike {@link schemas} is
|
|
223
|
+
* present whether or not validation succeeded — including fields revealed by a remote-resolved
|
|
224
|
+
* (`rpc://`) sub-form, which cannot be known from the static schema alone. Lets a caller report
|
|
225
|
+
* which fields it rejected.
|
|
226
|
+
*/
|
|
227
|
+
resolvedSchemas?: Record<string, FormanSchemaField[]>;
|
|
217
228
|
};
|
|
218
229
|
type FormanSchemaFieldState = {
|
|
219
230
|
mode?: 'chose' | 'edit';
|
|
@@ -402,4 +413,4 @@ declare function validateFormanWithDomains(domains: Record<string, {
|
|
|
402
413
|
*/
|
|
403
414
|
declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanValidationResult>;
|
|
404
415
|
|
|
405
|
-
export { type FormanExternalValidationResult, type FormanJsonSchemaOptions, type FormanJsonSchemaResult, type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, type FormanValidationOptions, type FormanValidationResult, SchemaConversionError, resolveFormanFieldType, toFormanSchema, toJSONSchema, toJSONSchemaAdvanced, validateForman, validateFormanWithDomains };
|
|
416
|
+
export { type FormanExternalValidationResult, type FormanJsonSchemaOptions, type FormanJsonSchemaResult, type FormanSchemaBooleanNested, type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, type FormanValidationOptions, type FormanValidationResult, SchemaConversionError, resolveFormanFieldType, toFormanSchema, toJSONSchema, toJSONSchemaAdvanced, validateForman, validateFormanWithDomains };
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,9 @@ function noEmpty(text) {
|
|
|
26
26
|
function isObject(value) {
|
|
27
27
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28
28
|
}
|
|
29
|
+
function isBooleanBranchNested(nested) {
|
|
30
|
+
return isObject(nested) && !("store" in nested) && ("true" in nested || "false" in nested);
|
|
31
|
+
}
|
|
29
32
|
function isOptionGroup(value) {
|
|
30
33
|
return "options" in value && Array.isArray(value.options);
|
|
31
34
|
}
|
|
@@ -1092,7 +1095,7 @@ function processRpcDirective(field, result, context) {
|
|
|
1092
1095
|
return result;
|
|
1093
1096
|
}
|
|
1094
1097
|
function extractNestedAndDomain(field) {
|
|
1095
|
-
const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : isObject(field.nested) ? field.nested.store : field.nested;
|
|
1098
|
+
const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : isBooleanBranchNested(field.nested) ? void 0 : isObject(field.nested) ? field.nested.store : field.nested;
|
|
1096
1099
|
const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : isObject(field.nested) && field.nested.domain ? field.nested.domain : void 0;
|
|
1097
1100
|
return { nested, domain };
|
|
1098
1101
|
}
|
|
@@ -1357,10 +1360,22 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1357
1360
|
states: errors.length === 0 && options?.states ? buildRestoreStructure(
|
|
1358
1361
|
Object.keys(domains).map((domain) => roots[domain].fieldStates.map((state) => ({ domain, ...state }))).flat()
|
|
1359
1362
|
) : void 0,
|
|
1360
|
-
schemas: errors.length === 0 && options?.schemas ? Object.fromEntries(Object.keys(domains).map((domain) => [domain, roots[domain].schemaFields])) : void 0
|
|
1363
|
+
schemas: errors.length === 0 && options?.schemas ? Object.fromEntries(Object.keys(domains).map((domain) => [domain, roots[domain].schemaFields])) : void 0,
|
|
1364
|
+
// Same spec as `schemas`, but also on the failure path. Remote-resolved fields are
|
|
1365
|
+
// built before the verdict, so a rejection can name the fields it rejected instead of
|
|
1366
|
+
// leaving the caller to guess at a sub-form it never saw. `schemas` stays success-only:
|
|
1367
|
+
// callers read its presence as a success signal.
|
|
1368
|
+
resolvedSchemas: options?.schemas ? Object.fromEntries(Object.keys(domains).map((domain) => [domain, roots[domain].schemaFields])) : void 0
|
|
1361
1369
|
};
|
|
1362
1370
|
}
|
|
1363
1371
|
async function validateFormanValue(value, field, context) {
|
|
1372
|
+
if (context.registerOnly) {
|
|
1373
|
+
return {
|
|
1374
|
+
valid: true,
|
|
1375
|
+
errors: [],
|
|
1376
|
+
warnings: []
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1364
1379
|
if (isVisualType(field.type)) {
|
|
1365
1380
|
return {
|
|
1366
1381
|
valid: true,
|
|
@@ -1382,7 +1397,7 @@ async function validateFormanValue(value, field, context) {
|
|
|
1382
1397
|
};
|
|
1383
1398
|
}
|
|
1384
1399
|
const normalizedField = normalizeFormanFieldType(field);
|
|
1385
|
-
if (normalizedField.required && (value == null || value === "")) {
|
|
1400
|
+
if (normalizedField.required && !context.suppressRequired && (value == null || value === "")) {
|
|
1386
1401
|
return {
|
|
1387
1402
|
valid: false,
|
|
1388
1403
|
errors: [
|
|
@@ -1552,7 +1567,7 @@ async function handleCollectionType2(value, field, context) {
|
|
|
1552
1567
|
continue;
|
|
1553
1568
|
}
|
|
1554
1569
|
if (context2.strict && !seen.has(subField2.name)) seen.add(subField2.name);
|
|
1555
|
-
if (path.length === 0) {
|
|
1570
|
+
if (path.length === 0 && !context2.registerOnly) {
|
|
1556
1571
|
context2.roots[context2.domain].schemaFields.push(clampFieldForSchema(subField2));
|
|
1557
1572
|
}
|
|
1558
1573
|
const result2 = await validateFormanValue(value[subField2.name], subField2, {
|
|
@@ -2145,7 +2160,7 @@ async function handlePrimitiveType2(value, field, context) {
|
|
|
2145
2160
|
}
|
|
2146
2161
|
const nested = extractNestedFromField(field);
|
|
2147
2162
|
if (nested) {
|
|
2148
|
-
const result = await handleNestedFields(nested, value, field, context);
|
|
2163
|
+
const result = FORMAN_TYPE_MAP2[field.type] === "boolean" ? await handleBooleanNestedFields(nested, value, field, context) : await handleNestedFields(nested, value, field, context);
|
|
2149
2164
|
errors.push(...result.errors);
|
|
2150
2165
|
warnings.push(...result.warnings);
|
|
2151
2166
|
}
|
|
@@ -2155,6 +2170,19 @@ async function handlePrimitiveType2(value, field, context) {
|
|
|
2155
2170
|
warnings
|
|
2156
2171
|
};
|
|
2157
2172
|
}
|
|
2173
|
+
async function handleBooleanNestedFields(nested, value, field, context) {
|
|
2174
|
+
if (isBooleanBranchNested(nested)) {
|
|
2175
|
+
const active2 = value === false ? nested.false : nested.true;
|
|
2176
|
+
const inactive = value === false ? nested.true : nested.false;
|
|
2177
|
+
const result = active2 ? await handleNestedFields(active2, value, field, context) : { valid: true, errors: [], warnings: [] };
|
|
2178
|
+
if (context.strict && inactive) {
|
|
2179
|
+
await handleNestedFields(inactive, value, field, { ...context, registerOnly: true });
|
|
2180
|
+
}
|
|
2181
|
+
return result;
|
|
2182
|
+
}
|
|
2183
|
+
const active = field.reversedNested === true ? value === false : value === true;
|
|
2184
|
+
return handleNestedFields(nested, value, field, active ? context : { ...context, suppressRequired: true });
|
|
2185
|
+
}
|
|
2158
2186
|
|
|
2159
2187
|
// src/json.ts
|
|
2160
2188
|
var JSON_PRIMITIVE_TYPE_MAP = {
|