@makehq/forman-schema 1.19.0 → 2.0.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/README.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  Conversion and validation utilities for Forman Schema.
4
4
 
5
+ ## v2.0.0 — validated values on every result
6
+
7
+ `validateForman` and `validateFormanWithDomains` now always return `normalizedValues` and
8
+ `appliedDefaults`. Nothing was removed or renamed and `valid`/`errors`/`warnings` are unaffected,
9
+ but a caller that deep-compares the whole result, or forwards it into a fixed-shape response, will
10
+ see two new keys — assert on the fields you care about, or drop the keys before forwarding.
11
+
12
+ Also new: `fillDefaults: 'always'`. See [Filling defaults](#filling-defaults).
13
+
5
14
  ## v1.14.0 — advanced field tracking
6
15
 
7
16
  Non-breaking minor release. New surface for working with `advanced: true` Forman fields:
@@ -137,7 +146,16 @@ Validate Forman values against a Forman Schema. Two entry points are available:
137
146
  - `validateForman(values, schema, options?)` — validate without domains.
138
147
  - `validateFormanWithDomains(domains, options?)` — validate multiple domains at once.
139
148
 
140
- Both return `{ valid: boolean, errors: { path: string, message: string }[] }`.
149
+ Both return `{ valid: boolean, errors: { path: string, message: string }[] }`, plus
150
+ `normalizedValues` (the input values per domain, with any filled defaults applied — see
151
+ [Filling defaults](#filling-defaults)) and `appliedDefaults` (what was filled, empty when
152
+ nothing was). The two are always present, so the consuming pattern is the same whether or
153
+ not default filling is enabled:
154
+
155
+ ```typescript
156
+ const { valid, errors, normalizedValues } = await validateForman(values, schema);
157
+ if (valid) persist(normalizedValues.default);
158
+ ```
141
159
 
142
160
  #### Basic validation
143
161
 
@@ -219,6 +237,43 @@ const result = await validateForman(values, schema, {
219
237
  });
220
238
  ```
221
239
 
240
+ #### Filling defaults
241
+
242
+ With `fillDefaults: 'requiredOnly'`, an omitted required field whose schema declares a usable
243
+ default (`null` and `''` cannot satisfy a required check) validates as that default instead of
244
+ failing as mandatory. With `fillDefaults: 'always'`, omitted optional fields with usable defaults
245
+ are filled too — the same modes as the platform's BlueprintValidator `useDefaults` option. The
246
+ filled value participates in the rest of the walk, so a filled boolean conditions its nested branch
247
+ exactly as a provided one would, and defaults under an armed branch fill recursively — including
248
+ fields injected by `rpc://`-resolved specs. Fills land in `normalizedValues` (the values with fills
249
+ applied; the input is never mutated, though subtrees nothing was written into are shared with it)
250
+ and are itemized in `appliedDefaults`, on the failure path too, so remaining errors can be repaired
251
+ on top of the filled values. Values you provide are never overwritten, **except `''`, which counts as
252
+ an omission and fills** — matching blueprint validation and the builder UI. Under `'always'` that
253
+ means an optional field you deliberately cleared comes back with its default; pass `'requiredOnly'`
254
+ if you need a cleared optional field left alone. An explicit `null` is a provided value: it never
255
+ fills and still fails as mandatory. Inactive nested branches are never filled.
256
+
257
+ ```typescript
258
+ const schema = [
259
+ {
260
+ name: 'fallbackEnabled',
261
+ type: 'boolean',
262
+ required: true,
263
+ default: false,
264
+ nested: [{ name: 'fallbackConnectionId', type: 'text', required: true }],
265
+ },
266
+ ];
267
+
268
+ const result = await validateForman({}, schema, { fillDefaults: 'requiredOnly' });
269
+ // {
270
+ // valid: true,
271
+ // errors: [],
272
+ // normalizedValues: { default: { fallbackEnabled: false } },
273
+ // appliedDefaults: [{ domain: 'default', path: 'fallbackEnabled', value: false }]
274
+ // }
275
+ ```
276
+
222
277
  #### Multi-domain validation
223
278
 
224
279
  Use `validateFormanWithDomains` to validate cross-domain schemas (e.g., `default` and `additional`).
package/dist/index.cjs CHANGED
@@ -115,6 +115,27 @@ function findValueInSelectOptions(field, value, optionsAndGroups) {
115
115
  const found = optionsAndGroups.find((option) => valueKey in option && matches(option));
116
116
  return found;
117
117
  }
118
+ function setIn(container, path, value) {
119
+ const [head, ...rest] = path;
120
+ if (head === void 0) return value;
121
+ if (typeof head === "number") {
122
+ const items = Array.isArray(container) ? container.slice() : [];
123
+ items[head] = setIn(items[head], rest, value);
124
+ return items;
125
+ }
126
+ const record = isObject(container) ? { ...container } : {};
127
+ record[head] = setIn(record[head], rest, value);
128
+ return record;
129
+ }
130
+ function setValueAtPath(values, path, value) {
131
+ const [head] = path;
132
+ if (typeof head !== "string") {
133
+ throw new Error(`Cannot write a value at path '${path.join(".")}': the first segment must be a field name.`);
134
+ }
135
+ const record = { ...values };
136
+ record[head] = setIn(record[head], path.slice(1), value);
137
+ return record;
138
+ }
118
139
  function pathToString(path) {
119
140
  let result = "";
120
141
  for (const key of path) {
@@ -1292,6 +1313,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
1292
1313
  seenFields: /* @__PURE__ */ new Set(),
1293
1314
  fieldStates: [],
1294
1315
  schemaFields: [],
1316
+ appliedDefaults: [],
1295
1317
  allowDynamicValues: domains[domain].allowDynamicValues ?? options?.allowDynamicValues ?? false,
1296
1318
  validateFields: (fields, context) => {
1297
1319
  return validateFormanValue(
@@ -1328,6 +1350,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
1328
1350
  path: [],
1329
1351
  tail: [],
1330
1352
  strict: options?.strict === true,
1353
+ fillDefaults: options?.fillDefaults,
1331
1354
  domainAliases: options?.domainAliases ?? {},
1332
1355
  validateNestedFields: () => {
1333
1356
  throw new Error("Cannot validate nested fields without parent field.");
@@ -1397,7 +1420,23 @@ async function validateFormanWithDomainsInternal(domains, options) {
1397
1420
  // built before the verdict, so a rejection can name the fields it rejected instead of
1398
1421
  // leaving the caller to guess at a sub-form it never saw. `schemas` stays success-only:
1399
1422
  // 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
1423
+ resolvedSchemas: options?.schemas ? Object.fromEntries(Object.keys(domains).map((domain) => [domain, roots[domain].schemaFields])) : void 0,
1424
+ normalizedValues: Object.fromEntries(
1425
+ Object.keys(domains).map((domain) => [
1426
+ domain,
1427
+ (roots[domain]?.appliedDefaults ?? []).reduce(
1428
+ (values, { path, value }) => setValueAtPath(values, path, value),
1429
+ domains[domain]?.values ?? {}
1430
+ )
1431
+ ])
1432
+ ),
1433
+ appliedDefaults: Object.keys(domains).flatMap(
1434
+ (domain) => roots[domain]?.appliedDefaults.map(({ path, value }) => ({
1435
+ domain,
1436
+ path: path.join("."),
1437
+ value
1438
+ })) ?? []
1439
+ )
1401
1440
  };
1402
1441
  }
1403
1442
  async function validateFormanValue(value, field, context) {
@@ -1429,6 +1468,14 @@ async function validateFormanValue(value, field, context) {
1429
1468
  };
1430
1469
  }
1431
1470
  const normalizedField = normalizeFormanFieldType(field);
1471
+ if (context.fillDefaults != null && !context.suppressRequired && (value === void 0 || value === "") && (context.fillDefaults === "always" || normalizedField.required)) {
1472
+ const fillable = normalizedField.default;
1473
+ if (fillable != null && fillable !== "") {
1474
+ const filled = isObject(fillable) || Array.isArray(fillable) ? structuredClone(fillable) : fillable;
1475
+ value = filled;
1476
+ context.roots[context.domain].appliedDefaults.push({ path: [...context.path], value: filled });
1477
+ }
1478
+ }
1432
1479
  if (normalizedField.required && !context.suppressRequired && (value == null || value === "")) {
1433
1480
  return {
1434
1481
  valid: false,
package/dist/index.d.cts CHANGED
@@ -225,7 +225,28 @@ type FormanValidationResult = {
225
225
  * which fields it rejected.
226
226
  */
227
227
  resolvedSchemas?: Record<string, FormanSchemaField[]>;
228
+ /**
229
+ * The input values with filled defaults applied, per domain, so a caller can persist or repair
230
+ * the filled configuration alongside any remaining errors. The input `values` are never
231
+ * mutated, but subtrees no default was written into are shared with the input — with no fills
232
+ * the domain's entry IS the caller's own object.
233
+ */
234
+ normalizedValues?: Record<string, Record<string, unknown>>;
235
+ /** The defaults that were filled (`options.fillDefaults`), in walk order within each domain. */
236
+ appliedDefaults?: {
237
+ domain: string;
238
+ path: string;
239
+ /** The default that was filled in. Loosely typed on purpose: `default` is declared as
240
+ * `FormanSchemaValue`, but schemas are JSON at source and may carry object or array
241
+ * defaults at runtime, which are filled as (cloned) values too. */
242
+ value: unknown;
243
+ }[];
228
244
  };
245
+ /**
246
+ * The type returned by `validateForman` and `validateFormanWithDomains`. The fields stay optional
247
+ * on the base type because intermediate results assembled during the walk do not carry them.
248
+ */
249
+ type FormanNormalizedValidationResult = FormanValidationResult & Required<Pick<FormanValidationResult, 'normalizedValues' | 'appliedDefaults'>>;
229
250
  type FormanSchemaFieldState = {
230
251
  mode?: 'chose' | 'edit';
231
252
  label?: string;
@@ -304,6 +325,20 @@ type FormanValidationOptions = {
304
325
  * returns (or resolves to) a result fragment that is spliced into the overall validation
305
326
  * result. When omitted, `json` fields with a `schema` pass without schema validation. */
306
327
  validateJson?(schema: JSONSchema7, value: unknown): FormanExternalValidationResult | Promise<FormanExternalValidationResult>;
328
+ /** Fill declared defaults for fields the caller omitted, mirroring BlueprintValidator's
329
+ * `useDefaults` modes. `'requiredOnly'` fills only required fields (instead of failing them
330
+ * as mandatory); `'always'` also fills omitted optional fields. A field is fillable when its
331
+ * value is `undefined` or `''` and its schema declares a default that is not `null` or `''`
332
+ * (a default that could not satisfy a required check) — the same fillable predicate as
333
+ * BlueprintValidator and the builder UI. The filled value flows through the rest of the
334
+ * walk, so a filled boolean conditions its nested branch exactly as a provided one would
335
+ * (`false` leaves the branch inactive), and defaults under an armed branch fill
336
+ * recursively, in the same single pass. Fills are reported on `normalizedValues` and
337
+ * `appliedDefaults`. Values the caller provided are never overwritten, except `''`, which
338
+ * counts as an omission and fills — so under `'always'` a deliberately cleared optional
339
+ * field comes back with its default. An explicit `null` is a provided value: it never fills
340
+ * and still fails as mandatory. Inactive branches are never filled. */
341
+ fillDefaults?: 'requiredOnly' | 'always';
307
342
  /** Maps domain names used in nested.domain to actual domain keys passed to validateFormanWithDomains */
308
343
  domainAliases?: Record<string, string>;
309
344
  /** Whether to allow dynamic values (IML expressions, unresolved RPC options).
@@ -400,7 +435,7 @@ declare function validateFormanWithDomains(domains: Record<string, {
400
435
  /** Whether the domain allows dynamic values (IML expressions, unresolved RPC select options).
401
436
  * Defaults to false. When false, IML expressions cause errors and unresolved RPC options are treated as errors. */
402
437
  allowDynamicValues?: boolean;
403
- }>, options?: FormanValidationOptions): Promise<FormanValidationResult>;
438
+ }>, options?: FormanValidationOptions): Promise<FormanNormalizedValidationResult>;
404
439
  /**
405
440
  * Validates a simple Forman values against a schema
406
441
  * @param values The values to validate
@@ -411,6 +446,6 @@ declare function validateFormanWithDomains(domains: Record<string, {
411
446
  * and backtick-escaping for keys containing dots (e.g. `"a.b[0].c"`, `` "`dotted.key`.child" ``).
412
447
  * @returns The validation result
413
448
  */
414
- declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanValidationResult>;
449
+ declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanNormalizedValidationResult>;
415
450
 
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 };
451
+ export { type FormanExternalValidationResult, type FormanJsonSchemaOptions, type FormanJsonSchemaResult, type FormanNormalizedValidationResult, 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
@@ -225,7 +225,28 @@ type FormanValidationResult = {
225
225
  * which fields it rejected.
226
226
  */
227
227
  resolvedSchemas?: Record<string, FormanSchemaField[]>;
228
+ /**
229
+ * The input values with filled defaults applied, per domain, so a caller can persist or repair
230
+ * the filled configuration alongside any remaining errors. The input `values` are never
231
+ * mutated, but subtrees no default was written into are shared with the input — with no fills
232
+ * the domain's entry IS the caller's own object.
233
+ */
234
+ normalizedValues?: Record<string, Record<string, unknown>>;
235
+ /** The defaults that were filled (`options.fillDefaults`), in walk order within each domain. */
236
+ appliedDefaults?: {
237
+ domain: string;
238
+ path: string;
239
+ /** The default that was filled in. Loosely typed on purpose: `default` is declared as
240
+ * `FormanSchemaValue`, but schemas are JSON at source and may carry object or array
241
+ * defaults at runtime, which are filled as (cloned) values too. */
242
+ value: unknown;
243
+ }[];
228
244
  };
245
+ /**
246
+ * The type returned by `validateForman` and `validateFormanWithDomains`. The fields stay optional
247
+ * on the base type because intermediate results assembled during the walk do not carry them.
248
+ */
249
+ type FormanNormalizedValidationResult = FormanValidationResult & Required<Pick<FormanValidationResult, 'normalizedValues' | 'appliedDefaults'>>;
229
250
  type FormanSchemaFieldState = {
230
251
  mode?: 'chose' | 'edit';
231
252
  label?: string;
@@ -304,6 +325,20 @@ type FormanValidationOptions = {
304
325
  * returns (or resolves to) a result fragment that is spliced into the overall validation
305
326
  * result. When omitted, `json` fields with a `schema` pass without schema validation. */
306
327
  validateJson?(schema: JSONSchema7, value: unknown): FormanExternalValidationResult | Promise<FormanExternalValidationResult>;
328
+ /** Fill declared defaults for fields the caller omitted, mirroring BlueprintValidator's
329
+ * `useDefaults` modes. `'requiredOnly'` fills only required fields (instead of failing them
330
+ * as mandatory); `'always'` also fills omitted optional fields. A field is fillable when its
331
+ * value is `undefined` or `''` and its schema declares a default that is not `null` or `''`
332
+ * (a default that could not satisfy a required check) — the same fillable predicate as
333
+ * BlueprintValidator and the builder UI. The filled value flows through the rest of the
334
+ * walk, so a filled boolean conditions its nested branch exactly as a provided one would
335
+ * (`false` leaves the branch inactive), and defaults under an armed branch fill
336
+ * recursively, in the same single pass. Fills are reported on `normalizedValues` and
337
+ * `appliedDefaults`. Values the caller provided are never overwritten, except `''`, which
338
+ * counts as an omission and fills — so under `'always'` a deliberately cleared optional
339
+ * field comes back with its default. An explicit `null` is a provided value: it never fills
340
+ * and still fails as mandatory. Inactive branches are never filled. */
341
+ fillDefaults?: 'requiredOnly' | 'always';
307
342
  /** Maps domain names used in nested.domain to actual domain keys passed to validateFormanWithDomains */
308
343
  domainAliases?: Record<string, string>;
309
344
  /** Whether to allow dynamic values (IML expressions, unresolved RPC options).
@@ -400,7 +435,7 @@ declare function validateFormanWithDomains(domains: Record<string, {
400
435
  /** Whether the domain allows dynamic values (IML expressions, unresolved RPC select options).
401
436
  * Defaults to false. When false, IML expressions cause errors and unresolved RPC options are treated as errors. */
402
437
  allowDynamicValues?: boolean;
403
- }>, options?: FormanValidationOptions): Promise<FormanValidationResult>;
438
+ }>, options?: FormanValidationOptions): Promise<FormanNormalizedValidationResult>;
404
439
  /**
405
440
  * Validates a simple Forman values against a schema
406
441
  * @param values The values to validate
@@ -411,6 +446,6 @@ declare function validateFormanWithDomains(domains: Record<string, {
411
446
  * and backtick-escaping for keys containing dots (e.g. `"a.b[0].c"`, `` "`dotted.key`.child" ``).
412
447
  * @returns The validation result
413
448
  */
414
- declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanValidationResult>;
449
+ declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanNormalizedValidationResult>;
415
450
 
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 };
451
+ export { type FormanExternalValidationResult, type FormanJsonSchemaOptions, type FormanJsonSchemaResult, type FormanNormalizedValidationResult, 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
@@ -83,6 +83,27 @@ function findValueInSelectOptions(field, value, optionsAndGroups) {
83
83
  const found = optionsAndGroups.find((option) => valueKey in option && matches(option));
84
84
  return found;
85
85
  }
86
+ function setIn(container, path, value) {
87
+ const [head, ...rest] = path;
88
+ if (head === void 0) return value;
89
+ if (typeof head === "number") {
90
+ const items = Array.isArray(container) ? container.slice() : [];
91
+ items[head] = setIn(items[head], rest, value);
92
+ return items;
93
+ }
94
+ const record = isObject(container) ? { ...container } : {};
95
+ record[head] = setIn(record[head], rest, value);
96
+ return record;
97
+ }
98
+ function setValueAtPath(values, path, value) {
99
+ const [head] = path;
100
+ if (typeof head !== "string") {
101
+ throw new Error(`Cannot write a value at path '${path.join(".")}': the first segment must be a field name.`);
102
+ }
103
+ const record = { ...values };
104
+ record[head] = setIn(record[head], path.slice(1), value);
105
+ return record;
106
+ }
86
107
  function pathToString(path) {
87
108
  let result = "";
88
109
  for (const key of path) {
@@ -1260,6 +1281,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
1260
1281
  seenFields: /* @__PURE__ */ new Set(),
1261
1282
  fieldStates: [],
1262
1283
  schemaFields: [],
1284
+ appliedDefaults: [],
1263
1285
  allowDynamicValues: domains[domain].allowDynamicValues ?? options?.allowDynamicValues ?? false,
1264
1286
  validateFields: (fields, context) => {
1265
1287
  return validateFormanValue(
@@ -1296,6 +1318,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
1296
1318
  path: [],
1297
1319
  tail: [],
1298
1320
  strict: options?.strict === true,
1321
+ fillDefaults: options?.fillDefaults,
1299
1322
  domainAliases: options?.domainAliases ?? {},
1300
1323
  validateNestedFields: () => {
1301
1324
  throw new Error("Cannot validate nested fields without parent field.");
@@ -1365,7 +1388,23 @@ async function validateFormanWithDomainsInternal(domains, options) {
1365
1388
  // built before the verdict, so a rejection can name the fields it rejected instead of
1366
1389
  // leaving the caller to guess at a sub-form it never saw. `schemas` stays success-only:
1367
1390
  // 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
1391
+ resolvedSchemas: options?.schemas ? Object.fromEntries(Object.keys(domains).map((domain) => [domain, roots[domain].schemaFields])) : void 0,
1392
+ normalizedValues: Object.fromEntries(
1393
+ Object.keys(domains).map((domain) => [
1394
+ domain,
1395
+ (roots[domain]?.appliedDefaults ?? []).reduce(
1396
+ (values, { path, value }) => setValueAtPath(values, path, value),
1397
+ domains[domain]?.values ?? {}
1398
+ )
1399
+ ])
1400
+ ),
1401
+ appliedDefaults: Object.keys(domains).flatMap(
1402
+ (domain) => roots[domain]?.appliedDefaults.map(({ path, value }) => ({
1403
+ domain,
1404
+ path: path.join("."),
1405
+ value
1406
+ })) ?? []
1407
+ )
1369
1408
  };
1370
1409
  }
1371
1410
  async function validateFormanValue(value, field, context) {
@@ -1397,6 +1436,14 @@ async function validateFormanValue(value, field, context) {
1397
1436
  };
1398
1437
  }
1399
1438
  const normalizedField = normalizeFormanFieldType(field);
1439
+ if (context.fillDefaults != null && !context.suppressRequired && (value === void 0 || value === "") && (context.fillDefaults === "always" || normalizedField.required)) {
1440
+ const fillable = normalizedField.default;
1441
+ if (fillable != null && fillable !== "") {
1442
+ const filled = isObject(fillable) || Array.isArray(fillable) ? structuredClone(fillable) : fillable;
1443
+ value = filled;
1444
+ context.roots[context.domain].appliedDefaults.push({ path: [...context.path], value: filled });
1445
+ }
1446
+ }
1400
1447
  if (normalizedField.required && !context.suppressRequired && (value == null || value === "")) {
1401
1448
  return {
1402
1449
  valid: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makehq/forman-schema",
3
- "version": "1.19.0",
3
+ "version": "2.0.0",
4
4
  "description": "Forman Schema Tools",
5
5
  "license": "MIT",
6
6
  "author": "Make",