is-kit 1.4.2 → 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 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: optional(isShortString) // string <= 3 | undefined
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; optionally rejects extra keys when `exact: true`.
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; optionally rejects extra keys when `exact: true`.
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,
@@ -332,15 +333,44 @@ function recordOf(keyFunction, valueFunction) {
332
333
  }
333
334
 
334
335
  // src/core/combinators/struct.ts
335
- var hasRequiredKeys = (obj, schema, keys) => keys.every((key) => key in obj && schema[key](obj[key]));
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
+ );
336
352
  var hasOnlyAllowedKeys = (obj, allowed) => Object.keys(obj).every((key) => allowed.has(key));
353
+ function optionalKey(guard) {
354
+ return { optional: true, guard };
355
+ }
337
356
  function struct(schema, options) {
338
- const schemaKeys = Object.keys(schema);
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
+ }
339
368
  const allowed = options?.exact ? new Set(schemaKeys) : null;
340
369
  return (input) => {
341
370
  if (!isPlainObject(input)) return false;
342
371
  const obj = input;
343
- if (!hasRequiredKeys(obj, schema, schemaKeys)) return false;
372
+ if (!hasRequiredKeys(obj, requiredEntries)) return false;
373
+ if (!hasValidOptionalKeys(obj, optionalEntries)) return false;
344
374
  if (!allowed) return true;
345
375
  return hasOnlyAllowedKeys(obj, allowed);
346
376
  };
@@ -463,6 +493,7 @@ function narrowKeyTo(guard, key) {
463
493
  oneOf,
464
494
  oneOfValues,
465
495
  optional,
496
+ optionalKey,
466
497
  or,
467
498
  predicateToRefine,
468
499
  recordOf,
package/dist/index.mjs CHANGED
@@ -242,15 +242,44 @@ function recordOf(keyFunction, valueFunction) {
242
242
  }
243
243
 
244
244
  // src/core/combinators/struct.ts
245
- var hasRequiredKeys = (obj, schema, keys) => keys.every((key) => key in obj && schema[key](obj[key]));
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
+ );
246
261
  var hasOnlyAllowedKeys = (obj, allowed) => Object.keys(obj).every((key) => allowed.has(key));
262
+ function optionalKey(guard) {
263
+ return { optional: true, guard };
264
+ }
247
265
  function struct(schema, options) {
248
- const schemaKeys = Object.keys(schema);
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
+ }
249
277
  const allowed = options?.exact ? new Set(schemaKeys) : null;
250
278
  return (input) => {
251
279
  if (!isPlainObject(input)) return false;
252
280
  const obj = input;
253
- if (!hasRequiredKeys(obj, schema, schemaKeys)) return false;
281
+ if (!hasRequiredKeys(obj, requiredEntries)) return false;
282
+ if (!hasValidOptionalKeys(obj, optionalEntries)) return false;
254
283
  if (!allowed) return true;
255
284
  return hasOnlyAllowedKeys(obj, allowed);
256
285
  };
@@ -372,6 +401,7 @@ export {
372
401
  oneOf,
373
402
  oneOfValues,
374
403
  optional,
404
+ optionalKey,
375
405
  or,
376
406
  predicateToRefine,
377
407
  recordOf,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "is-kit",
3
- "version": "1.4.2",
3
+ "version": "1.5.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",