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 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,
@@ -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 createComparator = (selectorFn) => (target) => (input) => {
229
- if (!guard(input)) return false;
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 (selector2) => createComparator(selector2);
236
+ return (selectorFn) => createComparator(selectorFn);
236
237
  }
237
238
  function equalsKey(key, target) {
238
- return (input) => {
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 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
+ );
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 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
+ }
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, schema, schemaKeys)) return false;
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 createComparator = (selectorFn) => (target) => (input) => {
139
- if (!guard(input)) return false;
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 (selector2) => createComparator(selector2);
145
+ return (selectorFn) => createComparator(selectorFn);
146
146
  }
147
147
  function equalsKey(key, target) {
148
- return (input) => {
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 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
+ );
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 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
+ }
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, schema, schemaKeys)) return false;
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "is-kit",
3
- "version": "1.4.1",
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",