is-kit 1.4.2 → 1.6.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
@@ -69,9 +69,11 @@ import {
69
69
  and,
70
70
  or,
71
71
  not,
72
+ mapOf,
73
+ optionalKey,
74
+ setOf,
72
75
  struct,
73
76
  oneOfValues,
74
- optional,
75
77
  isNumber,
76
78
  isString,
77
79
  predicateToRefine
@@ -90,11 +92,13 @@ const isOther = not(isShortOrPositive);
90
92
 
91
93
  // Define object shapes
92
94
  const isRole = oneOfValues(['admin', 'member'] as const);
95
+ const isTags = setOf(isString);
96
+ const isScores = mapOf(isString, isNumber);
93
97
  const isUser = struct({
94
98
  id: isPositive, // number > 0
95
99
  name: isString, // string
96
100
  role: isRole, // 'admin' | 'member'
97
- nickname: optional(isShortString) // string <= 3 | undefined
101
+ nickname: optionalKey(isShortString) // key may be absent; when present, string <= 3
98
102
  });
99
103
 
100
104
  // Use them
@@ -102,6 +106,8 @@ isPositive(3); // true
102
106
  isShortOrPositive('foo'); // true
103
107
  isShortOrPositive(false); // false
104
108
  isOther('x'); // true
109
+ isTags(new Set(['new', 'vip'])); // true
110
+ isScores(new Map([['math', 98]])); // true
105
111
 
106
112
  const maybeUser: unknown = { id: 7, name: 'Rin', role: 'admin' };
107
113
  if (isUser(maybeUser)) {
@@ -111,10 +117,41 @@ if (isUser(maybeUser)) {
111
117
  }
112
118
  ```
113
119
 
120
+ Use `optionalKey(...)` for key-level optional properties in `struct`.
121
+ Use `optional(...)` when the key exists but the value itself may be `undefined`.
122
+ Combine them as `optionalKey(optional(guard))` when both are allowed.
123
+
114
124
  Composed guards stay reusable:
115
125
  `isPositive` can be used standalone or as part of a `struct` definition.
116
126
 
117
- When validating complex shapes, reach for `struct` and friends like `arrayOf`, `recordOf`, or `oneOf`.
127
+ When validating complex shapes, reach for `struct` and collection
128
+ combinators like `arrayOf`, `setOf`, `mapOf`, `recordOf`, or `oneOf`.
129
+
130
+ ### Collection combinators
131
+
132
+ Use `arrayOf`, `setOf`, `mapOf`, and `recordOf` to validate homogeneous
133
+ collections while preserving precise readonly types.
134
+
135
+ ```ts
136
+ import {
137
+ arrayOf,
138
+ mapOf,
139
+ recordOf,
140
+ setOf,
141
+ isNumber,
142
+ isString
143
+ } from 'is-kit';
144
+
145
+ const isStringArray = arrayOf(isString);
146
+ const isStringSet = setOf(isString);
147
+ const isScoreMap = mapOf(isString, isNumber);
148
+ const isStringRecord = recordOf(isString, isString);
149
+
150
+ isStringArray(['a', 'b']); // true
151
+ isStringSet(new Set(['a', 'b'])); // true
152
+ isScoreMap(new Map([['math', 98]])); // true
153
+ isStringRecord({ a: 'x', b: 'y' }); // true
154
+ ```
118
155
 
119
156
  ### Primitive guards
120
157
 
@@ -161,6 +198,8 @@ isInfiniteNumber(1); // false
161
198
 
162
199
  Object/built-in guards cover arrays, dates, maps/sets, and more. Use
163
200
  `isInstanceOf` when you want a reusable guard from a class constructor.
201
+ For content-aware map/set validation, pair `isMap` / `isSet` with `mapOf`
202
+ and `setOf`.
164
203
 
165
204
  ```ts
166
205
  import { isArray, isDate, isInstanceOf } from 'is-kit';
@@ -180,7 +219,7 @@ isAnimal({}); // false
180
219
 
181
220
  - **Define once**: `define<T>(fn)` turns a plain function into a type guard.
182
221
  - **Upgrade predicates**: `predicateToRefine(fn)` adds narrowing.
183
- - **Compose freely**: `and`, `or`, `not`, `oneOf`, `arrayOf`, `struct` …
222
+ - **Compose freely**: `and`, `or`, `not`, `oneOf`, `arrayOf`, `setOf`, `mapOf`, `struct` …
184
223
  - **Stay ergonomic**: helpers like `nullable`, `optional`, `equals`, `safeParse`, `assert`, `hasKey`, `hasKeys`, `narrowKeyTo`.
185
224
 
186
225
  ### Key Helpers
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
  *
@@ -295,6 +325,23 @@ declare const isPrimitive: Guard<string | number | bigint | boolean | symbol | n
295
325
  */
296
326
  declare function arrayOf<F extends Predicate<unknown>>(elementGuard: F): Predicate<readonly GuardedOf<F>[]>;
297
327
 
328
+ /**
329
+ * Validates a set where every value satisfies the provided guard.
330
+ *
331
+ * @param valueGuard Guard applied to each set value.
332
+ * @returns Predicate narrowing to a readonly set of the guarded value type.
333
+ */
334
+ declare function setOf<VF extends Predicate<unknown>>(valueGuard: VF): Predicate<ReadonlySet<GuardedOf<VF>>>;
335
+
336
+ /**
337
+ * Validates a map where every key and value satisfies the provided guards.
338
+ *
339
+ * @param keyGuard Guard applied to each map key.
340
+ * @param valueGuard Guard applied to each map value.
341
+ * @returns Predicate narrowing to a readonly map with guarded key/value types.
342
+ */
343
+ declare function mapOf<KF extends Predicate<unknown>, VF extends Predicate<unknown>>(keyGuard: KF, valueGuard: VF): Predicate<ReadonlyMap<GuardedOf<KF>, GuardedOf<VF>>>;
344
+
298
345
  /**
299
346
  * Validates a fixed-length tuple by applying element-wise guards.
300
347
  *
@@ -323,9 +370,20 @@ declare function oneOf<A, Fs extends readonly Predicate<A>[]>(...guards: Fs): (i
323
370
  */
324
371
  declare function recordOf<KF extends Predicate<string>, VF extends Predicate<unknown>>(keyFunction: KF, valueFunction: VF): Predicate<Readonly<Record<GuardedOf<KF>, GuardedOf<VF>>>>;
325
372
 
373
+ /**
374
+ * Marks a struct schema field as optional at the key level.
375
+ *
376
+ * When used inside `struct`, the property may be absent. If the property exists,
377
+ * its value must satisfy the wrapped guard.
378
+ *
379
+ * @param guard Guard used to validate the property value when the key exists.
380
+ * @returns Schema field marker understood by `struct`.
381
+ */
382
+ declare function optionalKey<G extends Predicate<unknown>>(guard: G): OptionalSchemaField<G>;
326
383
  /**
327
384
  * Validates an object against a field-to-guard schema.
328
- * Keys in the schema are required; optionally rejects extra keys when `exact: true`.
385
+ * Keys in the schema are required unless wrapped with `optionalKey`; optionally
386
+ * rejects extra keys when `exact: true`.
329
387
  *
330
388
  * @param schema Record of property guards.
331
389
  * @param options When `{ exact: true }`, disallows properties not in `schema`.
@@ -505,4 +563,4 @@ declare function narrowKeyTo<A, K extends keyof A>(guard: Guard<A>, key: K): <co
505
563
  */
506
564
  declare const toBooleanPredicates: <A>(predicates: readonly ((value: A) => boolean)[]) => ReadonlyArray<(value: A) => boolean>;
507
565
 
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 };
566
+ 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, mapOf, narrowKeyTo, nonNull, not, nullable, nullish, oneOf, oneOfValues, optional, optionalKey, or, predicateToRefine, recordOf, required, safeParse, safeParseWith, setOf, 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
  *
@@ -295,6 +325,23 @@ declare const isPrimitive: Guard<string | number | bigint | boolean | symbol | n
295
325
  */
296
326
  declare function arrayOf<F extends Predicate<unknown>>(elementGuard: F): Predicate<readonly GuardedOf<F>[]>;
297
327
 
328
+ /**
329
+ * Validates a set where every value satisfies the provided guard.
330
+ *
331
+ * @param valueGuard Guard applied to each set value.
332
+ * @returns Predicate narrowing to a readonly set of the guarded value type.
333
+ */
334
+ declare function setOf<VF extends Predicate<unknown>>(valueGuard: VF): Predicate<ReadonlySet<GuardedOf<VF>>>;
335
+
336
+ /**
337
+ * Validates a map where every key and value satisfies the provided guards.
338
+ *
339
+ * @param keyGuard Guard applied to each map key.
340
+ * @param valueGuard Guard applied to each map value.
341
+ * @returns Predicate narrowing to a readonly map with guarded key/value types.
342
+ */
343
+ declare function mapOf<KF extends Predicate<unknown>, VF extends Predicate<unknown>>(keyGuard: KF, valueGuard: VF): Predicate<ReadonlyMap<GuardedOf<KF>, GuardedOf<VF>>>;
344
+
298
345
  /**
299
346
  * Validates a fixed-length tuple by applying element-wise guards.
300
347
  *
@@ -323,9 +370,20 @@ declare function oneOf<A, Fs extends readonly Predicate<A>[]>(...guards: Fs): (i
323
370
  */
324
371
  declare function recordOf<KF extends Predicate<string>, VF extends Predicate<unknown>>(keyFunction: KF, valueFunction: VF): Predicate<Readonly<Record<GuardedOf<KF>, GuardedOf<VF>>>>;
325
372
 
373
+ /**
374
+ * Marks a struct schema field as optional at the key level.
375
+ *
376
+ * When used inside `struct`, the property may be absent. If the property exists,
377
+ * its value must satisfy the wrapped guard.
378
+ *
379
+ * @param guard Guard used to validate the property value when the key exists.
380
+ * @returns Schema field marker understood by `struct`.
381
+ */
382
+ declare function optionalKey<G extends Predicate<unknown>>(guard: G): OptionalSchemaField<G>;
326
383
  /**
327
384
  * Validates an object against a field-to-guard schema.
328
- * Keys in the schema are required; optionally rejects extra keys when `exact: true`.
385
+ * Keys in the schema are required unless wrapped with `optionalKey`; optionally
386
+ * rejects extra keys when `exact: true`.
329
387
  *
330
388
  * @param schema Record of property guards.
331
389
  * @param options When `{ exact: true }`, disallows properties not in `schema`.
@@ -505,4 +563,4 @@ declare function narrowKeyTo<A, K extends keyof A>(guard: Guard<A>, key: K): <co
505
563
  */
506
564
  declare const toBooleanPredicates: <A>(predicates: readonly ((value: A) => boolean)[]) => ReadonlyArray<(value: A) => boolean>;
507
565
 
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 };
566
+ 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, mapOf, narrowKeyTo, nonNull, not, nullable, nullish, oneOf, oneOfValues, optional, optionalKey, or, predicateToRefine, recordOf, required, safeParse, safeParseWith, setOf, struct, toBooleanPredicates, tupleOf };
package/dist/index.js CHANGED
@@ -68,6 +68,7 @@ __export(index_exports, {
68
68
  isWeakMap: () => isWeakMap,
69
69
  isWeakSet: () => isWeakSet,
70
70
  isZero: () => isZero,
71
+ mapOf: () => mapOf,
71
72
  narrowKeyTo: () => narrowKeyTo,
72
73
  nonNull: () => nonNull,
73
74
  not: () => not,
@@ -76,12 +77,14 @@ __export(index_exports, {
76
77
  oneOf: () => oneOf,
77
78
  oneOfValues: () => oneOfValues,
78
79
  optional: () => optional,
80
+ optionalKey: () => optionalKey,
79
81
  or: () => or,
80
82
  predicateToRefine: () => predicateToRefine,
81
83
  recordOf: () => recordOf,
82
84
  required: () => required,
83
85
  safeParse: () => safeParse,
84
86
  safeParseWith: () => safeParseWith,
87
+ setOf: () => setOf,
85
88
  struct: () => struct,
86
89
  toBooleanPredicates: () => toBooleanPredicates,
87
90
  tupleOf: () => tupleOf
@@ -302,6 +305,29 @@ function arrayOf(elementGuard) {
302
305
  };
303
306
  }
304
307
 
308
+ // src/core/combinators/set.ts
309
+ function setOf(valueGuard) {
310
+ return define((input) => {
311
+ if (!isSet(input)) return false;
312
+ for (const value of input.values()) {
313
+ if (!valueGuard(value)) return false;
314
+ }
315
+ return true;
316
+ });
317
+ }
318
+
319
+ // src/core/combinators/map.ts
320
+ function mapOf(keyGuard, valueGuard) {
321
+ return define((input) => {
322
+ if (!isMap(input)) return false;
323
+ for (const [key, value] of input.entries()) {
324
+ if (!keyGuard(key)) return false;
325
+ if (!valueGuard(value)) return false;
326
+ }
327
+ return true;
328
+ });
329
+ }
330
+
305
331
  // src/core/combinators/tuple.ts
306
332
  function tupleOf(...guards) {
307
333
  return (input) => {
@@ -332,15 +358,44 @@ function recordOf(keyFunction, valueFunction) {
332
358
  }
333
359
 
334
360
  // src/core/combinators/struct.ts
335
- var hasRequiredKeys = (obj, schema, keys) => keys.every((key) => key in obj && schema[key](obj[key]));
361
+ var hasOwnKey = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
362
+ var isOptionalSchemaField = define(
363
+ // WHY: Optional fields are represented as a small tagged object so `struct`
364
+ // can distinguish schema metadata from plain predicate functions up front.
365
+ (field) => isObject(field) && field.optional === true && typeof field.guard === "function"
366
+ );
367
+ var hasRequiredKeys = (obj, entries) => (
368
+ // WHY: Required fields must be own properties; inherited values should not
369
+ // satisfy a schema because `struct` models object payload shape, not prototype chains.
370
+ entries.every(([key, guard]) => hasOwnKey(obj, key) && guard(obj[key]))
371
+ );
372
+ var hasValidOptionalKeys = (obj, entries) => (
373
+ // WHY: Optional keys are validated only when present. Missing keys stay valid
374
+ // without forcing callers to encode `undefined` into the value guard.
375
+ entries.every(([key, guard]) => !hasOwnKey(obj, key) || guard(obj[key]))
376
+ );
336
377
  var hasOnlyAllowedKeys = (obj, allowed) => Object.keys(obj).every((key) => allowed.has(key));
378
+ function optionalKey(guard) {
379
+ return { optional: true, guard };
380
+ }
337
381
  function struct(schema, options) {
338
- const schemaKeys = Object.keys(schema);
382
+ const requiredEntries = [];
383
+ const optionalEntries = [];
384
+ const schemaKeys = [];
385
+ for (const [key, field] of Object.entries(schema)) {
386
+ schemaKeys.push(key);
387
+ if (isOptionalSchemaField(field)) {
388
+ optionalEntries.push([key, field.guard]);
389
+ continue;
390
+ }
391
+ requiredEntries.push([key, field]);
392
+ }
339
393
  const allowed = options?.exact ? new Set(schemaKeys) : null;
340
394
  return (input) => {
341
395
  if (!isPlainObject(input)) return false;
342
396
  const obj = input;
343
- if (!hasRequiredKeys(obj, schema, schemaKeys)) return false;
397
+ if (!hasRequiredKeys(obj, requiredEntries)) return false;
398
+ if (!hasValidOptionalKeys(obj, optionalEntries)) return false;
344
399
  if (!allowed) return true;
345
400
  return hasOnlyAllowedKeys(obj, allowed);
346
401
  };
@@ -455,6 +510,7 @@ function narrowKeyTo(guard, key) {
455
510
  isWeakMap,
456
511
  isWeakSet,
457
512
  isZero,
513
+ mapOf,
458
514
  narrowKeyTo,
459
515
  nonNull,
460
516
  not,
@@ -463,12 +519,14 @@ function narrowKeyTo(guard, key) {
463
519
  oneOf,
464
520
  oneOfValues,
465
521
  optional,
522
+ optionalKey,
466
523
  or,
467
524
  predicateToRefine,
468
525
  recordOf,
469
526
  required,
470
527
  safeParse,
471
528
  safeParseWith,
529
+ setOf,
472
530
  struct,
473
531
  toBooleanPredicates,
474
532
  tupleOf
package/dist/index.mjs CHANGED
@@ -212,6 +212,29 @@ function arrayOf(elementGuard) {
212
212
  };
213
213
  }
214
214
 
215
+ // src/core/combinators/set.ts
216
+ function setOf(valueGuard) {
217
+ return define((input) => {
218
+ if (!isSet(input)) return false;
219
+ for (const value of input.values()) {
220
+ if (!valueGuard(value)) return false;
221
+ }
222
+ return true;
223
+ });
224
+ }
225
+
226
+ // src/core/combinators/map.ts
227
+ function mapOf(keyGuard, valueGuard) {
228
+ return define((input) => {
229
+ if (!isMap(input)) return false;
230
+ for (const [key, value] of input.entries()) {
231
+ if (!keyGuard(key)) return false;
232
+ if (!valueGuard(value)) return false;
233
+ }
234
+ return true;
235
+ });
236
+ }
237
+
215
238
  // src/core/combinators/tuple.ts
216
239
  function tupleOf(...guards) {
217
240
  return (input) => {
@@ -242,15 +265,44 @@ function recordOf(keyFunction, valueFunction) {
242
265
  }
243
266
 
244
267
  // src/core/combinators/struct.ts
245
- var hasRequiredKeys = (obj, schema, keys) => keys.every((key) => key in obj && schema[key](obj[key]));
268
+ var hasOwnKey = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
269
+ var isOptionalSchemaField = define(
270
+ // WHY: Optional fields are represented as a small tagged object so `struct`
271
+ // can distinguish schema metadata from plain predicate functions up front.
272
+ (field) => isObject(field) && field.optional === true && typeof field.guard === "function"
273
+ );
274
+ var hasRequiredKeys = (obj, entries) => (
275
+ // WHY: Required fields must be own properties; inherited values should not
276
+ // satisfy a schema because `struct` models object payload shape, not prototype chains.
277
+ entries.every(([key, guard]) => hasOwnKey(obj, key) && guard(obj[key]))
278
+ );
279
+ var hasValidOptionalKeys = (obj, entries) => (
280
+ // WHY: Optional keys are validated only when present. Missing keys stay valid
281
+ // without forcing callers to encode `undefined` into the value guard.
282
+ entries.every(([key, guard]) => !hasOwnKey(obj, key) || guard(obj[key]))
283
+ );
246
284
  var hasOnlyAllowedKeys = (obj, allowed) => Object.keys(obj).every((key) => allowed.has(key));
285
+ function optionalKey(guard) {
286
+ return { optional: true, guard };
287
+ }
247
288
  function struct(schema, options) {
248
- const schemaKeys = Object.keys(schema);
289
+ const requiredEntries = [];
290
+ const optionalEntries = [];
291
+ const schemaKeys = [];
292
+ for (const [key, field] of Object.entries(schema)) {
293
+ schemaKeys.push(key);
294
+ if (isOptionalSchemaField(field)) {
295
+ optionalEntries.push([key, field.guard]);
296
+ continue;
297
+ }
298
+ requiredEntries.push([key, field]);
299
+ }
249
300
  const allowed = options?.exact ? new Set(schemaKeys) : null;
250
301
  return (input) => {
251
302
  if (!isPlainObject(input)) return false;
252
303
  const obj = input;
253
- if (!hasRequiredKeys(obj, schema, schemaKeys)) return false;
304
+ if (!hasRequiredKeys(obj, requiredEntries)) return false;
305
+ if (!hasValidOptionalKeys(obj, optionalEntries)) return false;
254
306
  if (!allowed) return true;
255
307
  return hasOnlyAllowedKeys(obj, allowed);
256
308
  };
@@ -364,6 +416,7 @@ export {
364
416
  isWeakMap,
365
417
  isWeakSet,
366
418
  isZero,
419
+ mapOf,
367
420
  narrowKeyTo,
368
421
  nonNull,
369
422
  not,
@@ -372,12 +425,14 @@ export {
372
425
  oneOf,
373
426
  oneOfValues,
374
427
  optional,
428
+ optionalKey,
375
429
  or,
376
430
  predicateToRefine,
377
431
  recordOf,
378
432
  required,
379
433
  safeParse,
380
434
  safeParseWith,
435
+ setOf,
381
436
  struct,
382
437
  toBooleanPredicates,
383
438
  tupleOf
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "is-kit",
3
- "version": "1.4.2",
3
+ "version": "1.6.0",
4
4
  "description": "Make 'isXXX' easier. Let's make your code type safe and more readable!",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",