is-kit 1.4.1 → 1.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/README.md +6 -2
- package/dist/index.d.mts +47 -6
- package/dist/index.d.ts +47 -6
- package/dist/index.js +44 -12
- package/dist/index.mjs +43 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,9 +69,9 @@ import {
|
|
|
69
69
|
and,
|
|
70
70
|
or,
|
|
71
71
|
not,
|
|
72
|
+
optionalKey,
|
|
72
73
|
struct,
|
|
73
74
|
oneOfValues,
|
|
74
|
-
optional,
|
|
75
75
|
isNumber,
|
|
76
76
|
isString,
|
|
77
77
|
predicateToRefine
|
|
@@ -94,7 +94,7 @@ const isUser = struct({
|
|
|
94
94
|
id: isPositive, // number > 0
|
|
95
95
|
name: isString, // string
|
|
96
96
|
role: isRole, // 'admin' | 'member'
|
|
97
|
-
nickname:
|
|
97
|
+
nickname: optionalKey(isShortString) // key may be absent; when present, string <= 3
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
// Use them
|
|
@@ -111,6 +111,10 @@ if (isUser(maybeUser)) {
|
|
|
111
111
|
}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
Use `optionalKey(...)` for key-level optional properties in `struct`.
|
|
115
|
+
Use `optional(...)` when the key exists but the value itself may be `undefined`.
|
|
116
|
+
Combine them as `optionalKey(optional(guard))` when both are allowed.
|
|
117
|
+
|
|
114
118
|
Composed guards stay reusable:
|
|
115
119
|
`isPositive` can be used standalone or as part of a `struct` definition.
|
|
116
120
|
|
package/dist/index.d.mts
CHANGED
|
@@ -13,13 +13,43 @@ type ParseResult<T> = {
|
|
|
13
13
|
};
|
|
14
14
|
type GuardedOf<F> = F extends (value: unknown) => value is infer G ? G : never;
|
|
15
15
|
type GuardedWithin<Fs, A> = Extract<Fs extends readonly unknown[] ? GuardedOf<Fs[number]> : never, A>;
|
|
16
|
-
type Schema = Readonly<Record<string, Predicate<unknown>>>;
|
|
17
|
-
type InferSchema<S extends Schema> = {
|
|
18
|
-
readonly [K in keyof S]: GuardedOf<S[K]>;
|
|
19
|
-
};
|
|
20
16
|
|
|
21
17
|
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
22
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Marks a `struct` schema field as optional at the key level.
|
|
21
|
+
*/
|
|
22
|
+
type OptionalSchemaField<G extends Predicate<unknown>> = Readonly<{
|
|
23
|
+
optional: true;
|
|
24
|
+
guard: G;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Field accepted by `struct` schemas.
|
|
28
|
+
*/
|
|
29
|
+
type SchemaField = Predicate<unknown> | OptionalSchemaField<Predicate<unknown>>;
|
|
30
|
+
/**
|
|
31
|
+
* Object schema consumed by `struct`.
|
|
32
|
+
*/
|
|
33
|
+
type Schema = Readonly<Record<string, SchemaField>>;
|
|
34
|
+
type Simplify<T> = {
|
|
35
|
+
[K in keyof T]: T[K];
|
|
36
|
+
};
|
|
37
|
+
type InferSchemaField<F> = F extends Predicate<unknown> ? GuardedOf<F> : F extends OptionalSchemaField<infer G> ? GuardedOf<G> : never;
|
|
38
|
+
type RequiredSchemaKeys<S extends Schema> = {
|
|
39
|
+
[K in keyof S]-?: S[K] extends OptionalSchemaField<Predicate<unknown>> ? never : K;
|
|
40
|
+
}[keyof S];
|
|
41
|
+
type OptionalSchemaKeys<S extends Schema> = {
|
|
42
|
+
[K in keyof S]-?: S[K] extends OptionalSchemaField<Predicate<unknown>> ? K : never;
|
|
43
|
+
}[keyof S];
|
|
44
|
+
/**
|
|
45
|
+
* Infers the readonly object type produced by a `struct` schema.
|
|
46
|
+
*/
|
|
47
|
+
type InferSchema<S extends Schema> = Simplify<{
|
|
48
|
+
readonly [K in RequiredSchemaKeys<S>]: InferSchemaField<S[K]>;
|
|
49
|
+
} & {
|
|
50
|
+
readonly [K in OptionalSchemaKeys<S>]?: InferSchemaField<S[K]>;
|
|
51
|
+
}>;
|
|
52
|
+
|
|
23
53
|
/**
|
|
24
54
|
* Wraps a user function as a typed predicate.
|
|
25
55
|
*
|
|
@@ -323,9 +353,20 @@ declare function oneOf<A, Fs extends readonly Predicate<A>[]>(...guards: Fs): (i
|
|
|
323
353
|
*/
|
|
324
354
|
declare function recordOf<KF extends Predicate<string>, VF extends Predicate<unknown>>(keyFunction: KF, valueFunction: VF): Predicate<Readonly<Record<GuardedOf<KF>, GuardedOf<VF>>>>;
|
|
325
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Marks a struct schema field as optional at the key level.
|
|
358
|
+
*
|
|
359
|
+
* When used inside `struct`, the property may be absent. If the property exists,
|
|
360
|
+
* its value must satisfy the wrapped guard.
|
|
361
|
+
*
|
|
362
|
+
* @param guard Guard used to validate the property value when the key exists.
|
|
363
|
+
* @returns Schema field marker understood by `struct`.
|
|
364
|
+
*/
|
|
365
|
+
declare function optionalKey<G extends Predicate<unknown>>(guard: G): OptionalSchemaField<G>;
|
|
326
366
|
/**
|
|
327
367
|
* Validates an object against a field-to-guard schema.
|
|
328
|
-
* Keys in the schema are required
|
|
368
|
+
* Keys in the schema are required unless wrapped with `optionalKey`; optionally
|
|
369
|
+
* rejects extra keys when `exact: true`.
|
|
329
370
|
*
|
|
330
371
|
* @param schema Record of property guards.
|
|
331
372
|
* @param options When `{ exact: true }`, disallows properties not in `schema`.
|
|
@@ -505,4 +546,4 @@ declare function narrowKeyTo<A, K extends keyof A>(guard: Guard<A>, key: K): <co
|
|
|
505
546
|
*/
|
|
506
547
|
declare const toBooleanPredicates: <A>(predicates: readonly ((value: A) => boolean)[]) => ReadonlyArray<(value: A) => boolean>;
|
|
507
548
|
|
|
508
|
-
export { type ChainResult, type Guard, type GuardedOf, type GuardedWithin, type InferSchema, type OutOfGuards, type ParseResult, type Predicate, type Primitive, type Refine, type RefineChain, type Refinement, type Schema, and, andAll, arrayOf, assert, define, equals, equalsBy, equalsKey, guardIn, hasKey, hasKeys, isArray, isArrayBuffer, isAsyncIterable, isBigInt, isBlob, isBoolean, isDataView, isDate, isError, isFiniteNumber, isFunction, isInfiniteNumber, isInstanceOf, isInteger, isIterable, isMap, isNaN, isNegative, isNull, isNumber, isNumberPrimitive, isObject, isPlainObject, isPositive, isPrimitive, isPromiseLike, isRegExp, isSafeInteger, isSet, isString, isSymbol, isTypedArray, isURL, isUndefined, isWeakMap, isWeakSet, isZero, narrowKeyTo, nonNull, not, nullable, nullish, oneOf, oneOfValues, optional, or, predicateToRefine, recordOf, required, safeParse, safeParseWith, struct, toBooleanPredicates, tupleOf };
|
|
549
|
+
export { type ChainResult, type Guard, type GuardedOf, type GuardedWithin, type InferSchema, type OptionalSchemaField, type OutOfGuards, type ParseResult, type Predicate, type Primitive, type Refine, type RefineChain, type Refinement, type Schema, type SchemaField, and, andAll, arrayOf, assert, define, equals, equalsBy, equalsKey, guardIn, hasKey, hasKeys, isArray, isArrayBuffer, isAsyncIterable, isBigInt, isBlob, isBoolean, isDataView, isDate, isError, isFiniteNumber, isFunction, isInfiniteNumber, isInstanceOf, isInteger, isIterable, isMap, isNaN, isNegative, isNull, isNumber, isNumberPrimitive, isObject, isPlainObject, isPositive, isPrimitive, isPromiseLike, isRegExp, isSafeInteger, isSet, isString, isSymbol, isTypedArray, isURL, isUndefined, isWeakMap, isWeakSet, isZero, narrowKeyTo, nonNull, not, nullable, nullish, oneOf, oneOfValues, optional, optionalKey, or, predicateToRefine, recordOf, required, safeParse, safeParseWith, struct, toBooleanPredicates, tupleOf };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,13 +13,43 @@ type ParseResult<T> = {
|
|
|
13
13
|
};
|
|
14
14
|
type GuardedOf<F> = F extends (value: unknown) => value is infer G ? G : never;
|
|
15
15
|
type GuardedWithin<Fs, A> = Extract<Fs extends readonly unknown[] ? GuardedOf<Fs[number]> : never, A>;
|
|
16
|
-
type Schema = Readonly<Record<string, Predicate<unknown>>>;
|
|
17
|
-
type InferSchema<S extends Schema> = {
|
|
18
|
-
readonly [K in keyof S]: GuardedOf<S[K]>;
|
|
19
|
-
};
|
|
20
16
|
|
|
21
17
|
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
22
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Marks a `struct` schema field as optional at the key level.
|
|
21
|
+
*/
|
|
22
|
+
type OptionalSchemaField<G extends Predicate<unknown>> = Readonly<{
|
|
23
|
+
optional: true;
|
|
24
|
+
guard: G;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Field accepted by `struct` schemas.
|
|
28
|
+
*/
|
|
29
|
+
type SchemaField = Predicate<unknown> | OptionalSchemaField<Predicate<unknown>>;
|
|
30
|
+
/**
|
|
31
|
+
* Object schema consumed by `struct`.
|
|
32
|
+
*/
|
|
33
|
+
type Schema = Readonly<Record<string, SchemaField>>;
|
|
34
|
+
type Simplify<T> = {
|
|
35
|
+
[K in keyof T]: T[K];
|
|
36
|
+
};
|
|
37
|
+
type InferSchemaField<F> = F extends Predicate<unknown> ? GuardedOf<F> : F extends OptionalSchemaField<infer G> ? GuardedOf<G> : never;
|
|
38
|
+
type RequiredSchemaKeys<S extends Schema> = {
|
|
39
|
+
[K in keyof S]-?: S[K] extends OptionalSchemaField<Predicate<unknown>> ? never : K;
|
|
40
|
+
}[keyof S];
|
|
41
|
+
type OptionalSchemaKeys<S extends Schema> = {
|
|
42
|
+
[K in keyof S]-?: S[K] extends OptionalSchemaField<Predicate<unknown>> ? K : never;
|
|
43
|
+
}[keyof S];
|
|
44
|
+
/**
|
|
45
|
+
* Infers the readonly object type produced by a `struct` schema.
|
|
46
|
+
*/
|
|
47
|
+
type InferSchema<S extends Schema> = Simplify<{
|
|
48
|
+
readonly [K in RequiredSchemaKeys<S>]: InferSchemaField<S[K]>;
|
|
49
|
+
} & {
|
|
50
|
+
readonly [K in OptionalSchemaKeys<S>]?: InferSchemaField<S[K]>;
|
|
51
|
+
}>;
|
|
52
|
+
|
|
23
53
|
/**
|
|
24
54
|
* Wraps a user function as a typed predicate.
|
|
25
55
|
*
|
|
@@ -323,9 +353,20 @@ declare function oneOf<A, Fs extends readonly Predicate<A>[]>(...guards: Fs): (i
|
|
|
323
353
|
*/
|
|
324
354
|
declare function recordOf<KF extends Predicate<string>, VF extends Predicate<unknown>>(keyFunction: KF, valueFunction: VF): Predicate<Readonly<Record<GuardedOf<KF>, GuardedOf<VF>>>>;
|
|
325
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Marks a struct schema field as optional at the key level.
|
|
358
|
+
*
|
|
359
|
+
* When used inside `struct`, the property may be absent. If the property exists,
|
|
360
|
+
* its value must satisfy the wrapped guard.
|
|
361
|
+
*
|
|
362
|
+
* @param guard Guard used to validate the property value when the key exists.
|
|
363
|
+
* @returns Schema field marker understood by `struct`.
|
|
364
|
+
*/
|
|
365
|
+
declare function optionalKey<G extends Predicate<unknown>>(guard: G): OptionalSchemaField<G>;
|
|
326
366
|
/**
|
|
327
367
|
* Validates an object against a field-to-guard schema.
|
|
328
|
-
* Keys in the schema are required
|
|
368
|
+
* Keys in the schema are required unless wrapped with `optionalKey`; optionally
|
|
369
|
+
* rejects extra keys when `exact: true`.
|
|
329
370
|
*
|
|
330
371
|
* @param schema Record of property guards.
|
|
331
372
|
* @param options When `{ exact: true }`, disallows properties not in `schema`.
|
|
@@ -505,4 +546,4 @@ declare function narrowKeyTo<A, K extends keyof A>(guard: Guard<A>, key: K): <co
|
|
|
505
546
|
*/
|
|
506
547
|
declare const toBooleanPredicates: <A>(predicates: readonly ((value: A) => boolean)[]) => ReadonlyArray<(value: A) => boolean>;
|
|
507
548
|
|
|
508
|
-
export { type ChainResult, type Guard, type GuardedOf, type GuardedWithin, type InferSchema, type OutOfGuards, type ParseResult, type Predicate, type Primitive, type Refine, type RefineChain, type Refinement, type Schema, and, andAll, arrayOf, assert, define, equals, equalsBy, equalsKey, guardIn, hasKey, hasKeys, isArray, isArrayBuffer, isAsyncIterable, isBigInt, isBlob, isBoolean, isDataView, isDate, isError, isFiniteNumber, isFunction, isInfiniteNumber, isInstanceOf, isInteger, isIterable, isMap, isNaN, isNegative, isNull, isNumber, isNumberPrimitive, isObject, isPlainObject, isPositive, isPrimitive, isPromiseLike, isRegExp, isSafeInteger, isSet, isString, isSymbol, isTypedArray, isURL, isUndefined, isWeakMap, isWeakSet, isZero, narrowKeyTo, nonNull, not, nullable, nullish, oneOf, oneOfValues, optional, or, predicateToRefine, recordOf, required, safeParse, safeParseWith, struct, toBooleanPredicates, tupleOf };
|
|
549
|
+
export { type ChainResult, type Guard, type GuardedOf, type GuardedWithin, type InferSchema, type OptionalSchemaField, type OutOfGuards, type ParseResult, type Predicate, type Primitive, type Refine, type RefineChain, type Refinement, type Schema, type SchemaField, and, andAll, arrayOf, assert, define, equals, equalsBy, equalsKey, guardIn, hasKey, hasKeys, isArray, isArrayBuffer, isAsyncIterable, isBigInt, isBlob, isBoolean, isDataView, isDate, isError, isFiniteNumber, isFunction, isInfiniteNumber, isInstanceOf, isInteger, isIterable, isMap, isNaN, isNegative, isNull, isNumber, isNumberPrimitive, isObject, isPlainObject, isPositive, isPrimitive, isPromiseLike, isRegExp, isSafeInteger, isSet, isString, isSymbol, isTypedArray, isURL, isUndefined, isWeakMap, isWeakSet, isZero, narrowKeyTo, nonNull, not, nullable, nullish, oneOf, oneOfValues, optional, optionalKey, or, predicateToRefine, recordOf, required, safeParse, safeParseWith, struct, toBooleanPredicates, tupleOf };
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ __export(index_exports, {
|
|
|
76
76
|
oneOf: () => oneOf,
|
|
77
77
|
oneOfValues: () => oneOfValues,
|
|
78
78
|
optional: () => optional,
|
|
79
|
+
optionalKey: () => optionalKey,
|
|
79
80
|
or: () => or,
|
|
80
81
|
predicateToRefine: () => predicateToRefine,
|
|
81
82
|
recordOf: () => recordOf,
|
|
@@ -219,25 +220,26 @@ var isBlob = typeof Blob !== "undefined" ? define((value) => value instanceof Bl
|
|
|
219
220
|
var isInstanceOf = (constructor) => define((value) => value instanceof constructor);
|
|
220
221
|
|
|
221
222
|
// src/core/equals.ts
|
|
223
|
+
var guardedBy = (guard) => define((input) => guard(input));
|
|
222
224
|
function equals(target) {
|
|
223
|
-
return (input) =>
|
|
224
|
-
return Object.is(input, target);
|
|
225
|
-
};
|
|
225
|
+
return define((input) => Object.is(input, target));
|
|
226
226
|
}
|
|
227
227
|
function equalsBy(guard, selector) {
|
|
228
|
-
const
|
|
229
|
-
|
|
228
|
+
const matchesGuard = guardedBy(guard);
|
|
229
|
+
const createComparator = (selectorFn) => (target) => define((input) => {
|
|
230
|
+
if (!matchesGuard(input)) return false;
|
|
230
231
|
return Object.is(selectorFn(input), target);
|
|
231
|
-
};
|
|
232
|
+
});
|
|
232
233
|
if (selector) {
|
|
233
234
|
return createComparator(selector);
|
|
234
235
|
}
|
|
235
|
-
return (
|
|
236
|
+
return (selectorFn) => createComparator(selectorFn);
|
|
236
237
|
}
|
|
237
238
|
function equalsKey(key, target) {
|
|
238
|
-
|
|
239
|
+
const hasMatchingKey = define((input) => {
|
|
239
240
|
return isObject(input) && Object.prototype.hasOwnProperty.call(input, key) && Object.is(input[key], target);
|
|
240
|
-
};
|
|
241
|
+
});
|
|
242
|
+
return (input) => hasMatchingKey(input);
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
// src/core/parse.ts
|
|
@@ -331,15 +333,44 @@ function recordOf(keyFunction, valueFunction) {
|
|
|
331
333
|
}
|
|
332
334
|
|
|
333
335
|
// src/core/combinators/struct.ts
|
|
334
|
-
var
|
|
336
|
+
var hasOwnKey = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
337
|
+
var isOptionalSchemaField = define(
|
|
338
|
+
// WHY: Optional fields are represented as a small tagged object so `struct`
|
|
339
|
+
// can distinguish schema metadata from plain predicate functions up front.
|
|
340
|
+
(field) => isObject(field) && field.optional === true && typeof field.guard === "function"
|
|
341
|
+
);
|
|
342
|
+
var hasRequiredKeys = (obj, entries) => (
|
|
343
|
+
// WHY: Required fields must be own properties; inherited values should not
|
|
344
|
+
// satisfy a schema because `struct` models object payload shape, not prototype chains.
|
|
345
|
+
entries.every(([key, guard]) => hasOwnKey(obj, key) && guard(obj[key]))
|
|
346
|
+
);
|
|
347
|
+
var hasValidOptionalKeys = (obj, entries) => (
|
|
348
|
+
// WHY: Optional keys are validated only when present. Missing keys stay valid
|
|
349
|
+
// without forcing callers to encode `undefined` into the value guard.
|
|
350
|
+
entries.every(([key, guard]) => !hasOwnKey(obj, key) || guard(obj[key]))
|
|
351
|
+
);
|
|
335
352
|
var hasOnlyAllowedKeys = (obj, allowed) => Object.keys(obj).every((key) => allowed.has(key));
|
|
353
|
+
function optionalKey(guard) {
|
|
354
|
+
return { optional: true, guard };
|
|
355
|
+
}
|
|
336
356
|
function struct(schema, options) {
|
|
337
|
-
const
|
|
357
|
+
const requiredEntries = [];
|
|
358
|
+
const optionalEntries = [];
|
|
359
|
+
const schemaKeys = [];
|
|
360
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
361
|
+
schemaKeys.push(key);
|
|
362
|
+
if (isOptionalSchemaField(field)) {
|
|
363
|
+
optionalEntries.push([key, field.guard]);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
requiredEntries.push([key, field]);
|
|
367
|
+
}
|
|
338
368
|
const allowed = options?.exact ? new Set(schemaKeys) : null;
|
|
339
369
|
return (input) => {
|
|
340
370
|
if (!isPlainObject(input)) return false;
|
|
341
371
|
const obj = input;
|
|
342
|
-
if (!hasRequiredKeys(obj,
|
|
372
|
+
if (!hasRequiredKeys(obj, requiredEntries)) return false;
|
|
373
|
+
if (!hasValidOptionalKeys(obj, optionalEntries)) return false;
|
|
343
374
|
if (!allowed) return true;
|
|
344
375
|
return hasOnlyAllowedKeys(obj, allowed);
|
|
345
376
|
};
|
|
@@ -462,6 +493,7 @@ function narrowKeyTo(guard, key) {
|
|
|
462
493
|
oneOf,
|
|
463
494
|
oneOfValues,
|
|
464
495
|
optional,
|
|
496
|
+
optionalKey,
|
|
465
497
|
or,
|
|
466
498
|
predicateToRefine,
|
|
467
499
|
recordOf,
|
package/dist/index.mjs
CHANGED
|
@@ -129,25 +129,26 @@ var isBlob = typeof Blob !== "undefined" ? define((value) => value instanceof Bl
|
|
|
129
129
|
var isInstanceOf = (constructor) => define((value) => value instanceof constructor);
|
|
130
130
|
|
|
131
131
|
// src/core/equals.ts
|
|
132
|
+
var guardedBy = (guard) => define((input) => guard(input));
|
|
132
133
|
function equals(target) {
|
|
133
|
-
return (input) =>
|
|
134
|
-
return Object.is(input, target);
|
|
135
|
-
};
|
|
134
|
+
return define((input) => Object.is(input, target));
|
|
136
135
|
}
|
|
137
136
|
function equalsBy(guard, selector) {
|
|
138
|
-
const
|
|
139
|
-
|
|
137
|
+
const matchesGuard = guardedBy(guard);
|
|
138
|
+
const createComparator = (selectorFn) => (target) => define((input) => {
|
|
139
|
+
if (!matchesGuard(input)) return false;
|
|
140
140
|
return Object.is(selectorFn(input), target);
|
|
141
|
-
};
|
|
141
|
+
});
|
|
142
142
|
if (selector) {
|
|
143
143
|
return createComparator(selector);
|
|
144
144
|
}
|
|
145
|
-
return (
|
|
145
|
+
return (selectorFn) => createComparator(selectorFn);
|
|
146
146
|
}
|
|
147
147
|
function equalsKey(key, target) {
|
|
148
|
-
|
|
148
|
+
const hasMatchingKey = define((input) => {
|
|
149
149
|
return isObject(input) && Object.prototype.hasOwnProperty.call(input, key) && Object.is(input[key], target);
|
|
150
|
-
};
|
|
150
|
+
});
|
|
151
|
+
return (input) => hasMatchingKey(input);
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
// src/core/parse.ts
|
|
@@ -241,15 +242,44 @@ function recordOf(keyFunction, valueFunction) {
|
|
|
241
242
|
}
|
|
242
243
|
|
|
243
244
|
// src/core/combinators/struct.ts
|
|
244
|
-
var
|
|
245
|
+
var hasOwnKey = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
246
|
+
var isOptionalSchemaField = define(
|
|
247
|
+
// WHY: Optional fields are represented as a small tagged object so `struct`
|
|
248
|
+
// can distinguish schema metadata from plain predicate functions up front.
|
|
249
|
+
(field) => isObject(field) && field.optional === true && typeof field.guard === "function"
|
|
250
|
+
);
|
|
251
|
+
var hasRequiredKeys = (obj, entries) => (
|
|
252
|
+
// WHY: Required fields must be own properties; inherited values should not
|
|
253
|
+
// satisfy a schema because `struct` models object payload shape, not prototype chains.
|
|
254
|
+
entries.every(([key, guard]) => hasOwnKey(obj, key) && guard(obj[key]))
|
|
255
|
+
);
|
|
256
|
+
var hasValidOptionalKeys = (obj, entries) => (
|
|
257
|
+
// WHY: Optional keys are validated only when present. Missing keys stay valid
|
|
258
|
+
// without forcing callers to encode `undefined` into the value guard.
|
|
259
|
+
entries.every(([key, guard]) => !hasOwnKey(obj, key) || guard(obj[key]))
|
|
260
|
+
);
|
|
245
261
|
var hasOnlyAllowedKeys = (obj, allowed) => Object.keys(obj).every((key) => allowed.has(key));
|
|
262
|
+
function optionalKey(guard) {
|
|
263
|
+
return { optional: true, guard };
|
|
264
|
+
}
|
|
246
265
|
function struct(schema, options) {
|
|
247
|
-
const
|
|
266
|
+
const requiredEntries = [];
|
|
267
|
+
const optionalEntries = [];
|
|
268
|
+
const schemaKeys = [];
|
|
269
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
270
|
+
schemaKeys.push(key);
|
|
271
|
+
if (isOptionalSchemaField(field)) {
|
|
272
|
+
optionalEntries.push([key, field.guard]);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
requiredEntries.push([key, field]);
|
|
276
|
+
}
|
|
248
277
|
const allowed = options?.exact ? new Set(schemaKeys) : null;
|
|
249
278
|
return (input) => {
|
|
250
279
|
if (!isPlainObject(input)) return false;
|
|
251
280
|
const obj = input;
|
|
252
|
-
if (!hasRequiredKeys(obj,
|
|
281
|
+
if (!hasRequiredKeys(obj, requiredEntries)) return false;
|
|
282
|
+
if (!hasValidOptionalKeys(obj, optionalEntries)) return false;
|
|
253
283
|
if (!allowed) return true;
|
|
254
284
|
return hasOnlyAllowedKeys(obj, allowed);
|
|
255
285
|
};
|
|
@@ -371,6 +401,7 @@ export {
|
|
|
371
401
|
oneOf,
|
|
372
402
|
oneOfValues,
|
|
373
403
|
optional,
|
|
404
|
+
optionalKey,
|
|
374
405
|
or,
|
|
375
406
|
predicateToRefine,
|
|
376
407
|
recordOf,
|