deep-guards 1.0.2 → 1.0.4

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
@@ -58,8 +58,9 @@ if (vehicleGuard(value)) {
58
58
  1. [isAnyArray](#isanyarray)
59
59
  2. [isAnyRecord](#isanyrecord)
60
60
  3. [isArrayOf](#isarrayof)
61
- 4. [isRecordOf](#isrecordof)
62
- 5. [isObjectOf](#isobjectof)
61
+ 4. [isTupleOf](#istupleof)
62
+ 5. [isRecordOf](#isrecordof)
63
+ 6. [isObjectOf](#isobjectof)
63
64
  4. [Macros](#macros)
64
65
  1. [isDiscriminatedObjectOf](#isdiscriminatedobjectof)
65
66
  5. [guardOrThrow](#guardorthrow)
@@ -160,6 +161,21 @@ Higher order guard. This will pass if the incoming value is an array which conta
160
161
 
161
162
  NOTE: This passes for empty arrays
162
163
 
164
+ ### isTupleOf
165
+
166
+ Higher order guard. This takes in any number of guards, and then checks that the incoming value is an array of the same size, with the guards guarding the items in the same order as they appear.
167
+
168
+ For example:
169
+
170
+ ```ts
171
+ const myTupleGuard = isTupleOf(isNumber, isString, isBoolean);
172
+ const value: unknown = [1, "foo", true];
173
+
174
+ if (myTupleGuard(value)) {
175
+ // value passes
176
+ }
177
+ ```
178
+
163
179
  ### isRecordOf
164
180
 
165
181
  Higher order guard. It has two guard parameters, where the first is the key guard, and then the second is a value guard which is optional. If you don't pass in a value guard, the returned guard function has `unknown`s as the value type.
@@ -170,6 +186,8 @@ NOTE: This passes for empty records
170
186
 
171
187
  This is a function which takes in a structured object, containing keys of type `string | number | symbol`, and then values which are guard functions.
172
188
 
189
+ This also takes in a second boolean parameter for if the guard should check that the keys exactly match the structured guard object's keys. It then doesn't let any "leaky" objects through, which have extra keys. This defaults to false.
190
+
173
191
  As seen in the example at the start of this readme, you can do all sorts of complex nesting, as this produces a guard in the end.
174
192
 
175
193
  NOTE: This throws an error if you give it an empty object.\
@@ -181,7 +199,8 @@ These are common use cases for guarding setups, where they are made entirely out
181
199
 
182
200
  ### isDiscriminatedObjectOf
183
201
 
184
- This takes in a string literal `type`, and an `isObjectOf` guard. This then combines the two, where the returned guard has the signature: `Guard<{ type: T } & O>`.
202
+ This takes in a string literal for the discriminated value, and an `isObjectOf` guard, and then an optional key specifying which key is for the discriminated union. This then combines the object with the discriminator, where the returned guard has the signature: `Guard<{ [key]: T } & O>`.
203
+
185
204
  This is good for use cases where you don't have a discriminator on an individual type, but then do have it on the union type. For example:
186
205
 
187
206
  ```ts
@@ -1,8 +1,15 @@
1
- import { Guard, GuardSchemaOf } from "./types.js";
1
+ import { GuardSchemaOf } from "./helpers";
2
+ import { Guard } from "./types";
2
3
  export declare function isOptional<T>(guard: Guard<T>): Guard<T | undefined>;
3
4
  export declare function isNullable<T>(guard: Guard<T>): Guard<T | null | undefined>;
4
5
  export declare function isNonNullable<T>(value: T | null | undefined): value is T;
5
6
  export declare function isNot<const N>(guard: Guard<N>): <const T>(value: T | N) => value is T;
6
7
  export declare function isOneOf<const T extends (string | number | boolean | symbol | null | undefined)[]>(...values: T): Guard<(typeof values)[number]>;
7
8
  export declare function isUnionOf<T extends readonly unknown[]>(...guards: GuardSchemaOf<T>): Guard<T[number]>;
9
+ type ArrayToIntersection<A extends readonly unknown[]> = A extends [
10
+ infer T,
11
+ ...infer R
12
+ ] ? T & ArrayToIntersection<R> : unknown;
13
+ export declare function isIntersectionOf<T extends readonly unknown[]>(...guards: GuardSchemaOf<T>): Guard<ArrayToIntersection<T>>;
8
14
  export declare function isExact<const T>(expected: T, deep?: boolean): Guard<T>;
15
+ export {};
package/dist/compound.js CHANGED
@@ -1,8 +1,9 @@
1
+ import { objectKeys } from "./helpers";
1
2
  export function isOptional(guard) {
2
3
  if (typeof guard !== "function") {
3
4
  throw new TypeError(`isOptional expects a guard parameter. Got instead: ${guard}`);
4
5
  }
5
- return ((value) => value === undefined || guard(value));
6
+ return (value) => value === undefined || guard(value);
6
7
  }
7
8
  export function isNullable(guard) {
8
9
  if (typeof guard !== "function") {
@@ -29,20 +30,35 @@ export function isUnionOf(...guards) {
29
30
  }
30
31
  return (value) => guards.some((guard) => guard(value));
31
32
  }
32
- function isEqual(a, b) {
33
- return (a === b ||
34
- (a != null &&
35
- b != null &&
36
- typeof a === "object" &&
37
- typeof b === "object" &&
38
- (Array.isArray(a)
39
- ? Array.isArray(b) &&
40
- a.length === b.length &&
41
- a.every((v, i) => isEqual(v, b[i]))
42
- : Object.keys(a).length === Object.keys(b).length &&
43
- Object.entries(a).every(([k, v]) => k in b && isEqual(v, b[k])))));
33
+ export function isIntersectionOf(...guards) {
34
+ if (guards.every((guard) => typeof guard !== "function")) {
35
+ throw new TypeError(`isIntersectionOf expects N guard parameters. Got instead: ${guards}`);
36
+ }
37
+ return (value) => guards.every((guard) => guard(value));
38
+ }
39
+ function objectEntriesChecks(a, b) {
40
+ const aKeys = objectKeys(a);
41
+ const bKeySet = new Set(objectKeys(b));
42
+ return (aKeys.length === bKeySet.size &&
43
+ aKeys.every((k) => bKeySet.has(k) && isExact(a[k], true)(b[k])));
44
44
  }
45
45
  export function isExact(expected, deep = true) {
46
- return (value) => deep ? isEqual(expected, value) : expected === value;
46
+ return (value) =>
47
+ // Shallow checks
48
+ expected === value ||
49
+ (Number.isNaN(expected) && Number.isNaN(value)) ||
50
+ (deep &&
51
+ (Array.isArray(expected)
52
+ ? // Array checks
53
+ Array.isArray(value) &&
54
+ expected.length === value.length &&
55
+ expected.every((v, i) => isExact(v, true)(value[i]))
56
+ : // Object checks
57
+ expected != null &&
58
+ value != null &&
59
+ typeof expected === "object" &&
60
+ typeof value === "object" &&
61
+ !Array.isArray(value) &&
62
+ objectEntriesChecks(expected, value)));
47
63
  }
48
64
  //# sourceMappingURL=compound.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"compound.js","sourceRoot":"src/","sources":["compound.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CAAI,KAAe;IAC3C,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CACjB,sDAAsD,KAAK,EAAE,CAC9D,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,CAErD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAI,KAAe;IAC3C,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CACjB,sDAAsD,KAAK,EAAE,CAC9D,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,KAAc,EAAiC,EAAE,CACvD,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,aAAa,CAAI,KAA2B;IAC1D,OAAO,KAAK,IAAI,IAAI,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,KAAK,CAAU,KAAe;IAC5C,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CACjB,iDAAiD,KAAK,EAAE,CACzD,CAAC;IACJ,CAAC;IAED,OAAO,CAAU,KAAY,EAAc,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,OAAO,CAErB,GAAG,MAAS;IACZ,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,KAAc,EAAsB,EAAE,CAC5C,QAAQ,CAAC,GAAG,CAAC,KAAkB,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,GAAG,MAAwB;IAE3B,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,UAAU,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,SAAS,CACjB,sDAAsD,MAAM,EAAE,CAC/D,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,KAAK,EAAc,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,OAAO,CAAI,CAAI,EAAE,CAAU;IAClC,OAAO,CACL,CAAC,KAAK,CAAC;QACP,CAAC,CAAC,IAAI,IAAI;YACR,CAAC,IAAI,IAAI;YACT,OAAO,CAAC,KAAK,QAAQ;YACrB,OAAO,CAAC,KAAK,QAAQ;YACrB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;gBACf,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;oBAChB,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;oBACrB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACrC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM;oBAC/C,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CACrB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,EAAG,CAA6B,CAAC,CAAC,CAAC,CAAC,CACpE,CAAC,CAAC,CACV,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,OAAO,CAAU,QAAW,EAAE,OAAgB,IAAI;IAChE,OAAO,CAAC,KAAK,EAAc,EAAE,CAC3B,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC;AACzD,CAAC"}
1
+ {"version":3,"file":"compound.js","sourceRoot":"src/","sources":["compound.ts"],"names":[],"mappings":"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"}
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Guard } from "./types.js";
1
+ import { Guard } from "./types";
2
2
  export declare class GuardError extends Error {
3
3
  name: string;
4
4
  }
@@ -0,0 +1,9 @@
1
+ import { Guard } from "./types";
2
+ export type GuardSchemaOf<O extends object> = {
3
+ [K in keyof O]: Guard<O[K]>;
4
+ };
5
+ export type ObjectKey = string | number | symbol;
6
+ export declare const objectKeys: <K extends ObjectKey>(obj: Record<K, unknown>) => K[];
7
+ export declare function omit<O extends object>(obj: O, key: keyof O): {
8
+ [K in keyof O as K extends typeof key ? never : K]: O[K];
9
+ };
@@ -0,0 +1,8 @@
1
+ export const objectKeys = (obj) => Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj));
2
+ export function omit(obj, key) {
3
+ const omitted = { ...obj };
4
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
5
+ delete omitted[key];
6
+ return omitted;
7
+ }
8
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"src/","sources":["helpers.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,UAAU,GAAG,CAAsB,GAAuB,EAAO,EAAE,CAC7E,MAAM,CAAC,mBAAmB,CAAC,GAAG,CAAS,CAAC,MAAM,CAC7C,MAAM,CAAC,qBAAqB,CAAC,GAAG,CAAQ,CACzC,CAAC;AAEJ,MAAM,UAAU,IAAI,CAClB,GAAM,EACN,GAAY;IAEZ,MAAM,OAAO,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;IAC3B,gEAAgE;IAChE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IACpB,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export * from "./compound.js";
2
- export * from "./errors.js";
3
- export * from "./primitives.js";
4
- export * from "./structures.js";
5
- export * from "./types.js";
1
+ export * from "./compound";
2
+ export * from "./errors";
3
+ export * from "./macros";
4
+ export * from "./primitives";
5
+ export * from "./structures";
6
+ export * from "./types";
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- export * from "./compound.js";
2
- export * from "./errors.js";
3
- export * from "./primitives.js";
4
- export * from "./structures.js";
5
- export * from "./types.js";
1
+ export * from "./compound";
2
+ export * from "./errors";
3
+ export * from "./macros";
4
+ export * from "./primitives";
5
+ export * from "./structures";
6
+ export * from "./types";
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"src/","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"src/","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { ObjectKey } from "./helpers";
2
+ import { Guard } from "./types";
3
+ export declare function isDiscriminatedObjectOf<const T extends string, O extends object>(value: T, guard: Guard<O>): Guard<{
4
+ type: T;
5
+ } & O>;
6
+ export declare function isDiscriminatedObjectOf<const T extends string, O extends object, const K extends ObjectKey>(value: T, guard: Guard<O>, key: K): Guard<{
7
+ [S in K]: T;
8
+ } & O>;
package/dist/macros.js ADDED
@@ -0,0 +1,8 @@
1
+ import { isExact } from "./compound";
2
+ import { omit } from "./helpers";
3
+ import { isObjectOf } from "./structures";
4
+ export function isDiscriminatedObjectOf(value, guard, key = "type") {
5
+ const discriminatorGuard = isObjectOf({ [key]: isExact(value) });
6
+ return (value) => discriminatorGuard(value) && guard(omit(value, key));
7
+ }
8
+ //# sourceMappingURL=macros.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"macros.js","sourceRoot":"src/","sources":["macros.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAa,IAAI,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAY1C,MAAM,UAAU,uBAAuB,CAIrC,KAAQ,EACR,KAAe,EACf,MAAiB,MAAM;IAEvB,MAAM,kBAAkB,GAAG,UAAU,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAE7D,CAAC;IACH,OAAO,CAAC,KAAK,EAAyC,EAAE,CACtD,kBAAkB,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;AACzD,CAAC"}
@@ -1,4 +1,4 @@
1
- import { Guard } from "./types.js";
1
+ import { Guard } from "./types";
2
2
  export declare const isUnknown: Guard<unknown>;
3
3
  export declare const isNull: Guard<null>;
4
4
  export declare const isUndefined: Guard<undefined>;
@@ -1,9 +1,11 @@
1
- import { Guard, GuardSchemaOf } from "./types.js";
2
- type ObjectKey = string | number | symbol;
1
+ import { GuardSchemaOf, ObjectKey } from "./helpers";
2
+ import { Guard } from "./types";
3
3
  export declare const isAnyArray: Guard<unknown[]>;
4
4
  export declare const isAnyRecord: Guard<Record<ObjectKey, unknown>>;
5
5
  export declare function isArrayOf<T>(guard: Guard<T>): Guard<T[]>;
6
+ export declare function isTupleOf<T extends readonly unknown[]>(...tupleGuards: GuardSchemaOf<T>): Guard<T>;
6
7
  export declare function isRecordOf<K extends ObjectKey>(keyGuard: Guard<K>): Guard<Record<K, unknown>>;
7
8
  export declare function isRecordOf<K extends ObjectKey, V>(keyGuard: Guard<K>, valueGuard: Guard<V>): Guard<Record<K, V>>;
8
- export declare function isObjectOf<O extends object>(schema: GuardSchemaOf<O>): O extends unknown[] ? never : Guard<O>;
9
+ type IsObjectOfGuard<O extends object> = O extends unknown[] ? never : {} extends O ? never : Guard<O>;
10
+ export declare function isObjectOf<O extends object>(schema: GuardSchemaOf<O>, exactKeys?: boolean): IsObjectOfGuard<O>;
9
11
  export {};
@@ -1,6 +1,4 @@
1
- function objectKeys(obj) {
2
- return Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj));
3
- }
1
+ import { objectKeys } from "./helpers";
4
2
  export const isAnyArray = (value) => Array.isArray(value);
5
3
  export const isAnyRecord = (value) => value != null && typeof value === "object" && !Array.isArray(value);
6
4
  export function isArrayOf(guard) {
@@ -9,6 +7,14 @@ export function isArrayOf(guard) {
9
7
  }
10
8
  return (value) => Array.isArray(value) && value.every(guard);
11
9
  }
10
+ export function isTupleOf(...tupleGuards) {
11
+ if (tupleGuards.some((guard) => typeof guard !== "function")) {
12
+ throw new TypeError(`isTupleOf expects guard parameters. Got instead: ${JSON.stringify(tupleGuards)}`);
13
+ }
14
+ return (value) => Array.isArray(value) &&
15
+ value.length === tupleGuards.length &&
16
+ tupleGuards.every((guard, i) => guard(value[i]));
17
+ }
12
18
  export function isRecordOf(keyGuard, valueGuard) {
13
19
  if (typeof keyGuard !== "function") {
14
20
  throw new TypeError(`isRecordOf keyGuard expects a guard parameter. Got instead: ${keyGuard}`);
@@ -21,21 +27,23 @@ export function isRecordOf(keyGuard, valueGuard) {
21
27
  !Array.isArray(value) &&
22
28
  objectKeys(value).every((key) => keyGuard(key) && (valueGuard?.(value[key]) ?? true));
23
29
  }
24
- export function isObjectOf(schema) {
25
- const schemaKeys = objectKeys(schema);
30
+ export function isObjectOf(schema, exactKeys = false) {
26
31
  const schemaUnknown = schema;
27
- if (schemaKeys.length === 0) {
28
- throw new Error("isObjectOf received an empty schema");
32
+ if (schemaUnknown == null || typeof schemaUnknown !== "object") {
33
+ throw new TypeError(`isObjectOf expects a guard schema object. Got instead: ${schemaUnknown}`);
34
+ }
35
+ const schemaKeys = objectKeys(schema);
36
+ if (Array.isArray(schema) ||
37
+ schemaKeys.some((key) => typeof schema[key] !== "function")) {
38
+ throw new TypeError(`isObjectOf expects a guard schema object. Got instead ${JSON.stringify(schema)}`);
29
39
  }
30
- else if (schemaUnknown == null ||
31
- typeof schemaUnknown !== "object" ||
32
- Array.isArray(schemaUnknown) ||
33
- schemaKeys.some((key) => typeof schemaUnknown[key] !== "function")) {
34
- throw new TypeError(`isObjectOf expects a guard schema. Got instead: ${schemaUnknown}`);
40
+ else if (schemaKeys.length === 0) {
41
+ throw new Error("isObjectOf received an empty schema");
35
42
  }
36
43
  return ((value) => value != null &&
37
44
  typeof value === "object" &&
38
45
  !Array.isArray(value) &&
46
+ (!exactKeys || schemaKeys.length === objectKeys(value).length) &&
39
47
  schemaKeys.every((key) => key in value && schema[key](value[key])));
40
48
  }
41
49
  //# sourceMappingURL=structures.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"structures.js","sourceRoot":"src/","sources":["structures.ts"],"names":[],"mappings":"AAIA,SAAS,UAAU,CAAsB,GAAuB;IAC9D,OAAQ,MAAM,CAAC,mBAAmB,CAAC,GAAG,CAAS,CAAC,MAAM,CACpD,MAAM,CAAC,qBAAqB,CAAC,GAAG,CAAQ,CACzC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAqB,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAE5E,MAAM,CAAC,MAAM,WAAW,GAAsC,CAC5D,KAAK,EACgC,EAAE,CACvC,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEtE,MAAM,UAAU,SAAS,CAAI,KAAe;IAC1C,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CACjB,qDAAqD,KAAK,EAAE,CAC7D,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,KAAK,EAAgB,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AASD,MAAM,UAAU,UAAU,CACxB,QAAkB,EAClB,UAAqB;IAErB,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,MAAM,IAAI,SAAS,CACjB,+DAA+D,QAAQ,EAAE,CAC1E,CAAC;IACJ,CAAC;SAAM,IAAI,UAAU,IAAI,IAAI,IAAI,OAAO,UAAU,KAAK,UAAU,EAAE,CAAC;QAClE,MAAM,IAAI,SAAS,CACjB,2EAA2E,UAAU,EAAE,CACxF,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,KAAK,EAAyB,EAAE,CACtC,KAAK,IAAI,IAAI;QACb,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACrB,UAAU,CAAC,KAAK,CAAC,CAAC,KAAK,CACrB,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAC7D,CAAC;AACN,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,MAAwB;IAExB,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,aAAa,GAAG,MAAiB,CAAC;IACxC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;SAAM,IACL,aAAa,IAAI,IAAI;QACrB,OAAO,aAAa,KAAK,QAAQ;QACjC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC;QAC5B,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAQ,aAAmB,CAAC,GAAG,CAAC,KAAK,UAAU,CAAC,EACzE,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,mDAAmD,aAAa,EAAE,CACnE,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,CAAC,KAAK,EAAc,EAAE,CAC5B,KAAK,IAAI,IAAI;QACb,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACrB,UAAU,CAAC,KAAK,CACd,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,MAAM,CAAC,GAAG,CAAC,CAAE,KAAW,CAAC,GAAG,CAAC,CAAC,CACxD,CAA2C,CAAC;AACjD,CAAC"}
1
+ {"version":3,"file":"structures.js","sourceRoot":"src/","sources":["structures.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,5 +1,2 @@
1
1
  export type Guard<T> = (value: unknown) => value is T;
2
- export type GuardSchemaOf<O extends object> = {
3
- [K in keyof O]: Guard<O[K]>;
4
- };
5
2
  export type TypeFromGuard<G extends Guard<unknown>> = G extends Guard<infer T> ? T : never;
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "type": "module",
9
9
  "types": "dist/index.d.ts",
10
10
  "homepage": "https://github.com/eniallator/Deep-Guards",
11
- "version": "1.0.2",
11
+ "version": "1.0.4",
12
12
  "scripts": {
13
13
  "test": "jest",
14
14
  "build": "tsc",
@@ -28,15 +28,15 @@
28
28
  "@types/eslint": "^9.6.1",
29
29
  "@types/eslint__js": "^8.42.3",
30
30
  "@types/jest": "^29.5.14",
31
- "@typescript-eslint/eslint-plugin": "^8.15.0",
32
- "@typescript-eslint/parser": "^8.15.0",
31
+ "@typescript-eslint/eslint-plugin": "^8.16.0",
32
+ "@typescript-eslint/parser": "^8.16.0",
33
33
  "babel-jest": "^29.7.0",
34
34
  "eslint": "^9.15.0",
35
35
  "eslint-plugin-eslint-comments": "^3.2.0",
36
36
  "eslint-plugin-import": "^2.31.0",
37
37
  "eslint-plugin-jest": "^28.9.0",
38
38
  "jest": "^29.7.0",
39
- "typescript": "^5.6.3",
40
- "typescript-eslint": "^8.15.0"
39
+ "typescript": "^5.7.2",
40
+ "typescript-eslint": "^8.16.0"
41
41
  }
42
42
  }
package/src/compound.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Guard, GuardSchemaOf } from "./types.js";
1
+ import { GuardSchemaOf, objectKeys } from "./helpers";
2
+ import { Guard } from "./types";
2
3
 
3
4
  export function isOptional<T>(guard: Guard<T>): Guard<T | undefined> {
4
5
  if (typeof guard !== "function") {
@@ -7,9 +8,7 @@ export function isOptional<T>(guard: Guard<T>): Guard<T | undefined> {
7
8
  );
8
9
  }
9
10
 
10
- return ((value) => value === undefined || guard(value)) as Guard<
11
- T | undefined
12
- >;
11
+ return (value): value is T | undefined => value === undefined || guard(value);
13
12
  }
14
13
 
15
14
  export function isNullable<T>(guard: Guard<T>): Guard<T | null | undefined> {
@@ -77,25 +76,31 @@ export function isIntersectionOf<T extends readonly unknown[]>(
77
76
  guards.every((guard) => guard(value));
78
77
  }
79
78
 
80
- function isEqual<T>(a: T, b: unknown): b is T {
79
+ function objectEntriesChecks<T extends object>(a: T, b: object): b is T {
80
+ const aKeys = objectKeys(a);
81
+ const bKeySet = new Set(objectKeys(b));
81
82
  return (
82
- a === b ||
83
- (a != null &&
84
- b != null &&
85
- typeof a === "object" &&
86
- typeof b === "object" &&
87
- (Array.isArray(a)
88
- ? Array.isArray(b) &&
89
- a.length === b.length &&
90
- a.every((v, i) => isEqual(v, b[i]))
91
- : Object.keys(a).length === Object.keys(b).length &&
92
- Object.entries(a).every(
93
- ([k, v]) => k in b && isEqual(v, (b as Record<string, unknown>)[k])
94
- )))
83
+ aKeys.length === bKeySet.size &&
84
+ aKeys.every((k) => bKeySet.has(k) && isExact(a[k], true)(b[k]))
95
85
  );
96
86
  }
97
87
 
98
88
  export function isExact<const T>(expected: T, deep: boolean = true): Guard<T> {
99
89
  return (value): value is T =>
100
- deep ? isEqual(expected, value) : expected === value;
90
+ // Shallow checks
91
+ expected === value ||
92
+ (Number.isNaN(expected) && Number.isNaN(value)) ||
93
+ (deep &&
94
+ (Array.isArray(expected)
95
+ ? // Array checks
96
+ Array.isArray(value) &&
97
+ expected.length === value.length &&
98
+ expected.every((v, i) => isExact(v, true)(value[i]))
99
+ : // Object checks
100
+ expected != null &&
101
+ value != null &&
102
+ typeof expected === "object" &&
103
+ typeof value === "object" &&
104
+ !Array.isArray(value) &&
105
+ objectEntriesChecks(expected, value)));
101
106
  }
package/src/errors.ts CHANGED
@@ -1,17 +1,17 @@
1
- import { Guard } from "./types.js";
2
-
3
- export class GuardError extends Error {
4
- name = "GuardError";
5
- }
6
-
7
- export function guardOrThrow<T>(
8
- value: unknown,
9
- guard: Guard<T>,
10
- hint?: string
11
- ): T {
12
- if (guard(value)) {
13
- return value;
14
- } else {
15
- throw new GuardError(hint ?? "Guard error");
16
- }
17
- }
1
+ import { Guard } from "./types";
2
+
3
+ export class GuardError extends Error {
4
+ name = "GuardError";
5
+ }
6
+
7
+ export function guardOrThrow<T>(
8
+ value: unknown,
9
+ guard: Guard<T>,
10
+ hint?: string
11
+ ): T {
12
+ if (guard(value)) {
13
+ return value;
14
+ } else {
15
+ throw new GuardError(hint ?? "Guard error");
16
+ }
17
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { Guard } from "./types";
2
+
3
+ export type GuardSchemaOf<O extends object> = {
4
+ [K in keyof O]: Guard<O[K]>;
5
+ };
6
+
7
+ export type ObjectKey = string | number | symbol;
8
+
9
+ export const objectKeys = <K extends ObjectKey>(obj: Record<K, unknown>): K[] =>
10
+ (Object.getOwnPropertyNames(obj) as K[]).concat(
11
+ Object.getOwnPropertySymbols(obj) as K[]
12
+ );
13
+
14
+ export function omit<O extends object>(
15
+ obj: O,
16
+ key: keyof O
17
+ ): { [K in keyof O as K extends typeof key ? never : K]: O[K] } {
18
+ const omitted = { ...obj };
19
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
20
+ delete omitted[key];
21
+ return omitted;
22
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
- export * from "./compound.js";
2
- export * from "./errors.js";
3
- export * from "./primitives.js";
4
- export * from "./structures.js";
5
- export * from "./types.js";
1
+ export * from "./compound";
2
+ export * from "./errors";
3
+ export * from "./macros";
4
+ export * from "./primitives";
5
+ export * from "./structures";
6
+ export * from "./types";
package/src/macros.ts CHANGED
@@ -1,11 +1,28 @@
1
- import { isExact, isIntersectionOf } from "./compound";
1
+ import { isExact } from "./compound";
2
+ import { ObjectKey, omit } from "./helpers";
2
3
  import { isObjectOf } from "./structures";
3
4
  import { Guard } from "./types";
4
5
 
5
- export const isDiscriminatedObjectOf = <T extends string, O extends object>(
6
- type: T,
7
- guard: Guard<O>
8
- ): Guard<{ type: T } & O> =>
9
- isIntersectionOf(isObjectOf({ type: isExact(type) }), guard) as Guard<
10
- { type: T } & O
11
- >;
6
+ export function isDiscriminatedObjectOf<
7
+ const T extends string,
8
+ O extends object
9
+ >(value: T, guard: Guard<O>): Guard<{ type: T } & O>;
10
+ export function isDiscriminatedObjectOf<
11
+ const T extends string,
12
+ O extends object,
13
+ const K extends ObjectKey
14
+ >(value: T, guard: Guard<O>, key: K): Guard<{ [S in K]: T } & O>;
15
+ export function isDiscriminatedObjectOf<
16
+ const T extends string,
17
+ O extends object
18
+ >(
19
+ value: T,
20
+ guard: Guard<O>,
21
+ key: ObjectKey = "type"
22
+ ): Guard<{ [S in typeof key]: T } & O> {
23
+ const discriminatorGuard = isObjectOf({ [key]: isExact(value) }) as Guard<{
24
+ [S in typeof key]: T;
25
+ }>;
26
+ return (value): value is { [S in typeof key]: T } & O =>
27
+ discriminatorGuard(value) && guard(omit(value, key));
28
+ }
package/src/primitives.ts CHANGED
@@ -1,23 +1,23 @@
1
- import { Guard } from "./types.js";
2
-
3
- export const isUnknown: Guard<unknown> = (_value): _value is unknown => true;
4
-
5
- export const isNull: Guard<null> = (value) => value === null;
6
-
7
- export const isUndefined: Guard<undefined> = (value) => value === undefined;
8
-
9
- export const isNumber: Guard<number> = (value) => typeof value === "number";
10
-
11
- export const isInteger: Guard<number> = (value): value is number =>
12
- Number.isInteger(value);
13
-
14
- export const isString: Guard<string> = (value) => typeof value === "string";
15
-
16
- export const isSymbol: Guard<symbol> = (value) => typeof value === "symbol";
17
-
18
- export const isBoolean: Guard<boolean> = (value) =>
19
- value === true || value === false;
20
-
21
- export const isFunction: Guard<(...args: unknown[]) => unknown> = (
22
- value
23
- ): value is (...args: unknown[]) => unknown => typeof value === "function";
1
+ import { Guard } from "./types";
2
+
3
+ export const isUnknown: Guard<unknown> = (_value): _value is unknown => true;
4
+
5
+ export const isNull: Guard<null> = (value) => value === null;
6
+
7
+ export const isUndefined: Guard<undefined> = (value) => value === undefined;
8
+
9
+ export const isNumber: Guard<number> = (value) => typeof value === "number";
10
+
11
+ export const isInteger: Guard<number> = (value): value is number =>
12
+ Number.isInteger(value);
13
+
14
+ export const isString: Guard<string> = (value) => typeof value === "string";
15
+
16
+ export const isSymbol: Guard<symbol> = (value) => typeof value === "symbol";
17
+
18
+ export const isBoolean: Guard<boolean> = (value) =>
19
+ value === true || value === false;
20
+
21
+ export const isFunction: Guard<(...args: unknown[]) => unknown> = (
22
+ value
23
+ ): value is (...args: unknown[]) => unknown => typeof value === "function";
package/src/structures.ts CHANGED
@@ -1,12 +1,5 @@
1
- import { Guard, GuardSchemaOf } from "./types.js";
2
-
3
- type ObjectKey = string | number | symbol;
4
-
5
- function objectKeys<K extends ObjectKey>(obj: Record<K, unknown>): K[] {
6
- return (Object.getOwnPropertyNames(obj) as K[]).concat(
7
- Object.getOwnPropertySymbols(obj) as K[]
8
- );
9
- }
1
+ import { GuardSchemaOf, ObjectKey, objectKeys } from "./helpers";
2
+ import { Guard } from "./types";
10
3
 
11
4
  export const isAnyArray: Guard<unknown[]> = (value) => Array.isArray(value);
12
5
 
@@ -25,6 +18,23 @@ export function isArrayOf<T>(guard: Guard<T>): Guard<T[]> {
25
18
  return (value): value is T[] => Array.isArray(value) && value.every(guard);
26
19
  }
27
20
 
21
+ export function isTupleOf<T extends readonly unknown[]>(
22
+ ...tupleGuards: GuardSchemaOf<T>
23
+ ): Guard<T> {
24
+ if (tupleGuards.some((guard) => typeof guard !== "function")) {
25
+ throw new TypeError(
26
+ `isTupleOf expects guard parameters. Got instead: ${JSON.stringify(
27
+ tupleGuards
28
+ )}`
29
+ );
30
+ }
31
+
32
+ return (value): value is T =>
33
+ Array.isArray(value) &&
34
+ value.length === tupleGuards.length &&
35
+ tupleGuards.every((guard, i) => guard(value[i]));
36
+ }
37
+
28
38
  export function isRecordOf<K extends ObjectKey>(
29
39
  keyGuard: Guard<K>
30
40
  ): Guard<Record<K, unknown>>;
@@ -55,19 +65,33 @@ export function isRecordOf<K extends ObjectKey, V>(
55
65
  );
56
66
  }
57
67
 
68
+ type IsObjectOfGuard<O extends object> = O extends unknown[]
69
+ ? never
70
+ : // eslint-disable-next-line @typescript-eslint/no-empty-object-type
71
+ {} extends O
72
+ ? never
73
+ : Guard<O>;
74
+
58
75
  export function isObjectOf<O extends object>(
59
- schema: GuardSchemaOf<O>
60
- ): O extends unknown[] ? never : Guard<O> {
76
+ schema: GuardSchemaOf<O>,
77
+ exactKeys: boolean = false
78
+ ): IsObjectOfGuard<O> {
79
+ const schemaUnknown: unknown = schema;
80
+ if (schemaUnknown == null || typeof schemaUnknown !== "object") {
81
+ throw new TypeError(
82
+ `isObjectOf expects a guard schema object. Got instead: ${schemaUnknown}`
83
+ );
84
+ }
85
+
61
86
  const schemaKeys = objectKeys(schema);
62
- const schemaUnknown = schema as unknown;
63
87
  if (
64
- schemaUnknown == null ||
65
- typeof schemaUnknown !== "object" ||
66
- Array.isArray(schemaUnknown) ||
67
- schemaKeys.some((key) => typeof (schemaUnknown as O)[key] !== "function")
88
+ Array.isArray(schema) ||
89
+ schemaKeys.some((key) => typeof schema[key] !== "function")
68
90
  ) {
69
91
  throw new TypeError(
70
- `isObjectOf expects a guard schema. Got instead: ${schemaUnknown}`
92
+ `isObjectOf expects a guard schema object. Got instead ${JSON.stringify(
93
+ schema
94
+ )}`
71
95
  );
72
96
  } else if (schemaKeys.length === 0) {
73
97
  throw new Error("isObjectOf received an empty schema");
@@ -77,7 +101,8 @@ export function isObjectOf<O extends object>(
77
101
  value != null &&
78
102
  typeof value === "object" &&
79
103
  !Array.isArray(value) &&
104
+ (!exactKeys || schemaKeys.length === objectKeys(value).length) &&
80
105
  schemaKeys.every(
81
106
  (key) => key in value && schema[key]((value as O)[key])
82
- )) as O extends unknown[] ? never : Guard<O>;
107
+ )) as IsObjectOfGuard<O>;
83
108
  }
package/src/types.ts CHANGED
@@ -1,9 +1,5 @@
1
1
  export type Guard<T> = (value: unknown) => value is T;
2
2
 
3
- export type GuardSchemaOf<O extends object> = {
4
- [K in keyof O]: Guard<O[K]>;
5
- };
6
-
7
3
  export type TypeFromGuard<G extends Guard<unknown>> = G extends Guard<infer T>
8
4
  ? T
9
5
  : never;
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  isExact,
3
+ isIntersectionOf,
4
+ isNonNullable,
3
5
  isNot,
4
6
  isNullable,
5
- isNonNullable,
7
+ isNumber,
6
8
  isOneOf,
7
9
  isOptional,
10
+ isString,
8
11
  isUnionOf,
9
- isIntersectionOf,
10
- } from "../src/compound";
11
- import { isNumber, isString } from "../src/primitives";
12
+ } from "../src";
12
13
 
13
14
  describe("isOptional", () => {
14
15
  const guard = isOptional(isString);
@@ -0,0 +1,38 @@
1
+ import { isDiscriminatedObjectOf, isObjectOf, isString } from "../src";
2
+
3
+ describe("isDiscriminatedObjectOf", () => {
4
+ describe("no key override", () => {
5
+ const guard = isDiscriminatedObjectOf(
6
+ "foo",
7
+ isObjectOf({ bar: isString }, true)
8
+ );
9
+
10
+ it("succeeds for an object of the value", () => {
11
+ expect(guard({ type: "foo", bar: "baz" })).toBe(true);
12
+ });
13
+
14
+ it("fails for any other value", () => {
15
+ expect(guard({ type: "foo" })).toBe(false);
16
+ expect(guard({ bar: "baz" })).toBe(false);
17
+ expect(guard(1)).toBe(false);
18
+ });
19
+ });
20
+
21
+ describe("overriding key", () => {
22
+ const guard = isDiscriminatedObjectOf(
23
+ "foo",
24
+ isObjectOf({ bar: isString }, true),
25
+ "test"
26
+ );
27
+
28
+ it("succeeds for an object of the value", () => {
29
+ expect(guard({ test: "foo", bar: "baz" })).toBe(true);
30
+ });
31
+
32
+ it("fails for any other value", () => {
33
+ expect(guard({ test: "foo" })).toBe(false);
34
+ expect(guard({ bar: "baz" })).toBe(false);
35
+ expect(guard(1)).toBe(false);
36
+ });
37
+ });
38
+ });
@@ -1,91 +1,91 @@
1
- import {
2
- isBoolean,
3
- isFunction,
4
- isInteger,
5
- isNull,
6
- isNumber,
7
- isString,
8
- isUndefined,
9
- isUnknown,
10
- } from "../src/primitives";
11
-
12
- describe("isUnknown", () => {
13
- it("succeeds for any value", () => {
14
- expect(isUnknown("unknown")).toBe(true);
15
- });
16
- });
17
-
18
- describe("isAnyFunction", () => {
19
- it("succeeds for a function", () => {
20
- expect(isFunction(() => {})).toBe(true);
21
- });
22
-
23
- it("fails for any other value", () => {
24
- expect(isFunction(1)).toBe(false);
25
- });
26
- });
27
-
28
- describe("isNull", () => {
29
- it("succeeds for null", () => {
30
- expect(isNull(null)).toBe(true);
31
- });
32
-
33
- it("fails for any other value", () => {
34
- expect(isNull(undefined)).toBe(false);
35
- });
36
- });
37
-
38
- describe("isUndefined", () => {
39
- it("succeeds for undefined", () => {
40
- expect(isUndefined(undefined)).toBe(true);
41
- });
42
-
43
- it("fails for any other value", () => {
44
- expect(isUndefined(null)).toBe(false);
45
- });
46
- });
47
-
48
- describe("isNumber", () => {
49
- it("succeeds for a number", () => {
50
- expect(isNumber(1.23)).toBe(true);
51
- });
52
-
53
- it("fails for any other type", () => {
54
- expect(isNumber("foo")).toBe(false);
55
- });
56
- });
57
-
58
- describe("isInteger", () => {
59
- it("succeeds for an integer", () => {
60
- expect(isInteger(1)).toBe(true);
61
- });
62
-
63
- it("fails for a non-integer", () => {
64
- expect(isInteger(1.23)).toBe(false);
65
- });
66
-
67
- it("fails for any other type", () => {
68
- expect(isInteger("foo")).toBe(false);
69
- });
70
- });
71
-
72
- describe("isString", () => {
73
- it("succeeds for a string", () => {
74
- expect(isString("Foo bar")).toBe(true);
75
- });
76
-
77
- it("fails for other types", () => {
78
- expect(isString(true)).toBe(false);
79
- });
80
- });
81
-
82
- describe("isBoolean", () => {
83
- it("succeeds for a boolean", () => {
84
- expect(isBoolean(true)).toBe(true);
85
- expect(isBoolean(false)).toBe(true);
86
- });
87
-
88
- it("fails for other types", () => {
89
- expect(isBoolean("foo")).toBe(false);
90
- });
91
- });
1
+ import {
2
+ isBoolean,
3
+ isFunction,
4
+ isInteger,
5
+ isNull,
6
+ isNumber,
7
+ isString,
8
+ isUndefined,
9
+ isUnknown,
10
+ } from "../src";
11
+
12
+ describe("isUnknown", () => {
13
+ it("succeeds for any value", () => {
14
+ expect(isUnknown("unknown")).toBe(true);
15
+ });
16
+ });
17
+
18
+ describe("isAnyFunction", () => {
19
+ it("succeeds for a function", () => {
20
+ expect(isFunction(() => {})).toBe(true);
21
+ });
22
+
23
+ it("fails for any other value", () => {
24
+ expect(isFunction(1)).toBe(false);
25
+ });
26
+ });
27
+
28
+ describe("isNull", () => {
29
+ it("succeeds for null", () => {
30
+ expect(isNull(null)).toBe(true);
31
+ });
32
+
33
+ it("fails for any other value", () => {
34
+ expect(isNull(undefined)).toBe(false);
35
+ });
36
+ });
37
+
38
+ describe("isUndefined", () => {
39
+ it("succeeds for undefined", () => {
40
+ expect(isUndefined(undefined)).toBe(true);
41
+ });
42
+
43
+ it("fails for any other value", () => {
44
+ expect(isUndefined(null)).toBe(false);
45
+ });
46
+ });
47
+
48
+ describe("isNumber", () => {
49
+ it("succeeds for a number", () => {
50
+ expect(isNumber(1.23)).toBe(true);
51
+ });
52
+
53
+ it("fails for any other type", () => {
54
+ expect(isNumber("foo")).toBe(false);
55
+ });
56
+ });
57
+
58
+ describe("isInteger", () => {
59
+ it("succeeds for an integer", () => {
60
+ expect(isInteger(1)).toBe(true);
61
+ });
62
+
63
+ it("fails for a non-integer", () => {
64
+ expect(isInteger(1.23)).toBe(false);
65
+ });
66
+
67
+ it("fails for any other type", () => {
68
+ expect(isInteger("foo")).toBe(false);
69
+ });
70
+ });
71
+
72
+ describe("isString", () => {
73
+ it("succeeds for a string", () => {
74
+ expect(isString("Foo bar")).toBe(true);
75
+ });
76
+
77
+ it("fails for other types", () => {
78
+ expect(isString(true)).toBe(false);
79
+ });
80
+ });
81
+
82
+ describe("isBoolean", () => {
83
+ it("succeeds for a boolean", () => {
84
+ expect(isBoolean(true)).toBe(true);
85
+ expect(isBoolean(false)).toBe(true);
86
+ });
87
+
88
+ it("fails for other types", () => {
89
+ expect(isBoolean("foo")).toBe(false);
90
+ });
91
+ });
@@ -1,110 +1,173 @@
1
- import { isNumber, isString, isSymbol } from "../src/primitives";
2
- import {
3
- isAnyArray,
4
- isAnyRecord,
5
- isArrayOf,
6
- isObjectOf,
7
- isRecordOf,
8
- } from "../src/structures";
9
-
10
- describe("isAnyArray", () => {
11
- it("succeeds for an array", () => {
12
- expect(isAnyArray([])).toBe(true);
13
- expect(isAnyArray(["foo", 1, true, null])).toBe(true);
14
- });
15
-
16
- it("fails for any other value", () => {
17
- expect(isAnyArray({})).toBe(false);
18
- expect(isAnyArray(1)).toBe(false);
19
- });
20
- });
21
-
22
- describe("isAnyRecord", () => {
23
- it("succeeds for a record", () => {
24
- expect(isAnyRecord({})).toBe(true);
25
- expect(isAnyRecord({ foo: "bar", baz: 1 })).toBe(true);
26
- });
27
-
28
- it("fails for any other value", () => {
29
- expect(isAnyRecord([])).toBe(false);
30
- expect(isAnyRecord(1)).toBe(false);
31
- });
32
- });
33
-
34
- describe("isArrayOf", () => {
35
- const guard = isArrayOf(isString);
36
-
37
- it("succeeds for an array of the value", () => {
38
- expect(guard([])).toBe(true);
39
- expect(guard(["foo", "bar", "baz"])).toBe(true);
40
- });
41
-
42
- it("fails for any other value", () => {
43
- expect(guard([1, 2, 3])).toBe(false);
44
- expect(guard(["foo", "bar", null])).toBe(false);
45
- expect(guard(1)).toBe(false);
46
- });
47
- });
48
-
49
- describe("isObjectOf", () => {
50
- const barSymbol = Symbol("bar");
51
- const guard = isObjectOf({
52
- foo: isString,
53
- [barSymbol]: isNumber,
54
- baz: isObjectOf({
55
- qux: isSymbol,
56
- }),
57
- });
58
-
59
- it("succeeds for an object of the value", () => {
60
- expect(
61
- guard({
62
- foo: "hello",
63
- [barSymbol]: 1,
64
- baz: { qux: Symbol("world!") },
65
- quux: "this should not be checked",
66
- })
67
- ).toBe(true);
68
- });
69
-
70
- it("fails for any other value", () => {
71
- expect(
72
- guard({
73
- foo: "hello",
74
- [barSymbol]: "FAIL",
75
- baz: { qux: Symbol("world!") },
76
- })
77
- ).toBe(false);
78
- expect(guard(1)).toBe(false);
79
- });
80
- });
81
-
82
- describe("isRecordOf", () => {
83
- describe("with valueGuard", () => {
84
- const guard = isRecordOf(isString, isNumber);
85
-
86
- it("succeeds for a record of the key/value types", () => {
87
- expect(guard({})).toBe(true);
88
- expect(guard({ foo: 1, bar: 2 })).toBe(true);
89
- });
90
-
91
- it("fails for any other value", () => {
92
- expect(guard({ foo: "bar", baz: 1 })).toBe(false);
93
- expect(guard(1)).toBe(false);
94
- });
95
- });
96
-
97
- describe("without valueGuard", () => {
98
- const guard = isRecordOf(isString);
99
-
100
- it("succeeds for a record with the key type", () => {
101
- expect(guard({})).toBe(true);
102
- expect(guard({ foo: 1, bar: "baz" })).toBe(true);
103
- });
104
-
105
- it("fails for any other value", () => {
106
- expect(guard({ 1: "foo", [Symbol("bar")]: 1 })).toBe(false);
107
- expect(guard(1)).toBe(false);
108
- });
109
- });
110
- });
1
+ import {
2
+ isAnyArray,
3
+ isAnyRecord,
4
+ isArrayOf,
5
+ isBoolean,
6
+ isNumber,
7
+ isObjectOf,
8
+ isRecordOf,
9
+ isString,
10
+ isSymbol,
11
+ isTupleOf,
12
+ } from "../src";
13
+
14
+ describe("isAnyArray", () => {
15
+ it("succeeds for an array", () => {
16
+ expect(isAnyArray([])).toBe(true);
17
+ expect(isAnyArray(["foo", 1, true, null])).toBe(true);
18
+ });
19
+
20
+ it("fails for any other value", () => {
21
+ expect(isAnyArray({})).toBe(false);
22
+ expect(isAnyArray(1)).toBe(false);
23
+ });
24
+ });
25
+
26
+ describe("isAnyRecord", () => {
27
+ it("succeeds for a record", () => {
28
+ expect(isAnyRecord({})).toBe(true);
29
+ expect(isAnyRecord({ foo: "bar", baz: 1 })).toBe(true);
30
+ });
31
+
32
+ it("fails for any other value", () => {
33
+ expect(isAnyRecord([])).toBe(false);
34
+ expect(isAnyRecord(1)).toBe(false);
35
+ });
36
+ });
37
+
38
+ describe("isArrayOf", () => {
39
+ const guard = isArrayOf(isString);
40
+
41
+ it("succeeds for an array of the value", () => {
42
+ expect(guard([])).toBe(true);
43
+ expect(guard(["foo", "bar", "baz"])).toBe(true);
44
+ });
45
+
46
+ it("fails for any other value", () => {
47
+ expect(guard([1, 2, 3])).toBe(false);
48
+ expect(guard(["foo", "bar", null])).toBe(false);
49
+ expect(guard(1)).toBe(false);
50
+ });
51
+ });
52
+
53
+ describe("isTupleOf", () => {
54
+ const guard = isTupleOf(isNumber, isString, isBoolean);
55
+
56
+ it("succeeds for an array of the value", () => {
57
+ expect(guard([1, "foo", true])).toBe(true);
58
+ });
59
+
60
+ it("fails for any other value", () => {
61
+ expect(guard([1, "foo", true, null])).toBe(false);
62
+ expect(guard([1, "foo"])).toBe(false);
63
+ expect(guard(1)).toBe(false);
64
+ });
65
+ });
66
+
67
+ describe("isObjectOf", () => {
68
+ describe("leaky", () => {
69
+ const barSymbol = Symbol("bar");
70
+ const guard = isObjectOf({
71
+ foo: isString,
72
+ [barSymbol]: isNumber,
73
+ baz: isObjectOf({
74
+ qux: isSymbol,
75
+ }),
76
+ });
77
+
78
+ it("succeeds for an object of the value", () => {
79
+ expect(
80
+ guard({
81
+ foo: "hello",
82
+ [barSymbol]: 1,
83
+ baz: { qux: Symbol("world!") },
84
+ quux: "this should not be checked",
85
+ })
86
+ ).toBe(true);
87
+ });
88
+
89
+ it("fails for any other value", () => {
90
+ expect(
91
+ guard({
92
+ foo: "hello",
93
+ [barSymbol]: "FAIL",
94
+ baz: { qux: Symbol("world!") },
95
+ })
96
+ ).toBe(false);
97
+ expect(guard(1)).toBe(false);
98
+ });
99
+ });
100
+
101
+ describe("exact keys", () => {
102
+ const barSymbol = Symbol("bar");
103
+ const guard = isObjectOf(
104
+ {
105
+ foo: isString,
106
+ [barSymbol]: isNumber,
107
+ baz: isObjectOf({
108
+ qux: isSymbol,
109
+ }),
110
+ },
111
+ true
112
+ );
113
+
114
+ it("succeeds for an object of the value", () => {
115
+ expect(
116
+ guard({
117
+ foo: "hello",
118
+ [barSymbol]: 1,
119
+ baz: { qux: Symbol("world!") },
120
+ })
121
+ ).toBe(true);
122
+ });
123
+
124
+ it("fails for any other value", () => {
125
+ expect(
126
+ guard({
127
+ foo: "hello",
128
+ [barSymbol]: 1,
129
+ baz: { qux: Symbol("world!") },
130
+ quux: "this should be checked",
131
+ })
132
+ ).toBe(false);
133
+ expect(
134
+ guard({
135
+ foo: "hello",
136
+ [barSymbol]: "FAIL",
137
+ baz: { qux: Symbol("world!") },
138
+ })
139
+ ).toBe(false);
140
+ expect(guard(1)).toBe(false);
141
+ });
142
+ });
143
+ });
144
+
145
+ describe("isRecordOf", () => {
146
+ describe("with valueGuard", () => {
147
+ const guard = isRecordOf(isString, isNumber);
148
+
149
+ it("succeeds for a record of the key/value types", () => {
150
+ expect(guard({})).toBe(true);
151
+ expect(guard({ foo: 1, bar: 2 })).toBe(true);
152
+ });
153
+
154
+ it("fails for any other value", () => {
155
+ expect(guard({ foo: "bar", baz: 1 })).toBe(false);
156
+ expect(guard(1)).toBe(false);
157
+ });
158
+ });
159
+
160
+ describe("without valueGuard", () => {
161
+ const guard = isRecordOf(isString);
162
+
163
+ it("succeeds for a record with the key type", () => {
164
+ expect(guard({})).toBe(true);
165
+ expect(guard({ foo: 1, bar: "baz" })).toBe(true);
166
+ });
167
+
168
+ it("fails for any other value", () => {
169
+ expect(guard({ 1: "foo", [Symbol("bar")]: 1 })).toBe(false);
170
+ expect(guard(1)).toBe(false);
171
+ });
172
+ });
173
+ });