deep-guards 1.0.0 → 1.0.2

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
@@ -32,12 +32,12 @@ const vehicleGuard = isUnionOf(carGuard, bikeGuard);
32
32
  const value: unknown = { ... };
33
33
 
34
34
  if (vehicleGuard(value)) {
35
- value.wheels;
35
+ console.log(value.wheels);
36
36
 
37
37
  if (value.type === "car") {
38
38
  // value is a Car
39
39
  } else {
40
- // value is a bike
40
+ // value is a Bike
41
41
  }
42
42
  }
43
43
  ```
@@ -52,13 +52,18 @@ if (vehicleGuard(value)) {
52
52
  4. [isNot](#isnot)
53
53
  5. [isOneOf](#isoneof)
54
54
  6. [isUnionOf](#isunionof)
55
- 7. [isExact](#isexact)
55
+ 7. [isIntersectionOf](#isintersectionof)
56
+ 8. [isExact](#isexact)
56
57
  3. [Structures](#structures)
57
58
  1. [isAnyArray](#isanyarray)
58
59
  2. [isAnyRecord](#isanyrecord)
59
60
  3. [isArrayOf](#isarrayof)
60
61
  4. [isRecordOf](#isrecordof)
61
62
  5. [isObjectOf](#isobjectof)
63
+ 4. [Macros](#macros)
64
+ 1. [isDiscriminatedObjectOf](#isdiscriminatedobjectof)
65
+ 5. [guardOrThrow](#guardorthrow)
66
+ 6. [TypeFromGuard](#typefromguard)
62
67
 
63
68
  ## Terminology
64
69
 
@@ -121,7 +126,11 @@ It's very useful for enumerations, where you only have a few specific values, e.
121
126
 
122
127
  ### isUnionOf
123
128
 
124
- Higher order guard. This takes in any amount of guards as arguments, and then produces a guard which does a union over all the incoming guards.
129
+ Higher order guard. This takes in any amount of guards as arguments, and then produces a guard which does a union over all the incoming guards. This means that if _any_ one of the guards passes for the incoming value, then this will pass.
130
+
131
+ ### isIntersectionOf
132
+
133
+ Higher order guard. This takes in any amount of guards as arguments, and then produces a guard which does an intersection over all the incoming guards. This means that if _every_ one of the guards passes for the incoming value, then this will pass.
125
134
 
126
135
  ### isExact
127
136
 
@@ -165,3 +174,88 @@ As seen in the example at the start of this readme, you can do all sorts of comp
165
174
 
166
175
  NOTE: This throws an error if you give it an empty object.\
167
176
  It will also accept an object which contains keys which are not specified.
177
+
178
+ ## Macros
179
+
180
+ These are common use cases for guarding setups, where they are made entirely out of the above guard suite.
181
+
182
+ ### isDiscriminatedObjectOf
183
+
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>`.
185
+ 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
+
187
+ ```ts
188
+ interface Car {
189
+ wheels: 4;
190
+ owner: string;
191
+ passengers: {
192
+ name: string;
193
+ }[];
194
+ }
195
+
196
+ interface Bike {
197
+ wheels: 2;
198
+ owner: string;
199
+ storage?: string[];
200
+ }
201
+
202
+ type Vehicle = ({ type: "car" } & Car) | ({ type: "bike" } & Bike);
203
+
204
+ // Can then be represented like so in guards:
205
+
206
+ const carGuard = isObjectOf({
207
+ wheels: isExact(4),
208
+ owner: isString,
209
+ passengers: isArrayOf(
210
+ isObjectOf({
211
+ name: isString,
212
+ })
213
+ ),
214
+ });
215
+
216
+ const bikeGuard = isObjectOf({
217
+ wheels: isExact(2),
218
+ owner: isString,
219
+ storage: isOptional(isArrayOf(isString)),
220
+ });
221
+
222
+ const vehicleGuard = isUnionOf(
223
+ isDiscriminatedObjectOf("car", carGuard),
224
+ isDiscriminatedObjectOf("bike", bikeGuard)
225
+ );
226
+ ```
227
+
228
+ ## guardOrThrow
229
+
230
+ This package also includes a `guardOrThrow` method which when given an incoming value, a guard, and an optional hint message, will return a narrowed version of the value, or throw a `GuardError` containing that hint message.
231
+
232
+ You can then do things like:
233
+
234
+ ```ts
235
+ const cars = guardOrThrow(
236
+ JSON.parse(readFileSync("cars.json").toString()),
237
+ isArrayOf(isCar),
238
+ "Invalid car format"
239
+ );
240
+ ```
241
+
242
+ ## TypeFromGuard
243
+
244
+ This is a helper type, which will extract the type from a guard function to then let you use the type for other purposes.
245
+
246
+ An example use case for this is:
247
+
248
+ ```ts
249
+ const carGuard = isObjectOf({
250
+ type: isExact("car"),
251
+ wheels: isExact(4),
252
+ owner: isString,
253
+ passengers: isArrayOf(
254
+ isObjectOf({
255
+ name: isString,
256
+ })
257
+ ),
258
+ });
259
+
260
+ type Car = TypeFromGuard<typeof carGuard>;
261
+ ```
@@ -1,7 +1,7 @@
1
1
  function objectKeys(obj) {
2
2
  return Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj));
3
3
  }
4
- export const isAnyArray = value => Array.isArray(value);
4
+ export const isAnyArray = (value) => Array.isArray(value);
5
5
  export const isAnyRecord = (value) => value != null && typeof value === "object" && !Array.isArray(value);
6
6
  export function isArrayOf(guard) {
7
7
  if (typeof guard !== "function") {
@@ -19,7 +19,7 @@ export function isRecordOf(keyGuard, valueGuard) {
19
19
  return (value) => value != null &&
20
20
  typeof value === "object" &&
21
21
  !Array.isArray(value) &&
22
- objectKeys(value).every(key => keyGuard(key) && (valueGuard?.(value[key]) ?? true));
22
+ objectKeys(value).every((key) => keyGuard(key) && (valueGuard?.(value[key]) ?? true));
23
23
  }
24
24
  export function isObjectOf(schema) {
25
25
  const schemaKeys = objectKeys(schema);
@@ -30,12 +30,12 @@ export function isObjectOf(schema) {
30
30
  else if (schemaUnknown == null ||
31
31
  typeof schemaUnknown !== "object" ||
32
32
  Array.isArray(schemaUnknown) ||
33
- schemaKeys.some(key => typeof schemaUnknown[key] !== "function")) {
33
+ schemaKeys.some((key) => typeof schemaUnknown[key] !== "function")) {
34
34
  throw new TypeError(`isObjectOf expects a guard schema. Got instead: ${schemaUnknown}`);
35
35
  }
36
36
  return ((value) => value != null &&
37
37
  typeof value === "object" &&
38
38
  !Array.isArray(value) &&
39
- schemaKeys.every(key => key in value && schema[key](value[key])));
39
+ schemaKeys.every((key) => key in value && schema[key](value[key])));
40
40
  }
41
41
  //# 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,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAE1E,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,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAC3D,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,GAAG,CAAC,EAAE,CAAC,OAAQ,aAAmB,CAAC,GAAG,CAAC,KAAK,UAAU,CAAC,EACvE,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,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,MAAM,CAAC,GAAG,CAAC,CAAE,KAAW,CAAC,GAAG,CAAC,CAAC,CACtD,CAA2C,CAAC;AACjD,CAAC"}
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"}
package/dist/types.d.ts CHANGED
@@ -2,3 +2,4 @@ export type Guard<T> = (value: unknown) => value is T;
2
2
  export type GuardSchemaOf<O extends object> = {
3
3
  [K in keyof O]: Guard<O[K]>;
4
4
  };
5
+ export type TypeFromGuard<G extends Guard<unknown>> = G extends Guard<infer T> ? T : never;
package/package.json CHANGED
@@ -1,41 +1,42 @@
1
- {
2
- "author": "eniallator",
3
- "description": "Deep guarding package",
4
- "license": "Apache-2.0",
5
- "main": "dist/index.js",
6
- "name": "deep-guards",
7
- "packageManager": "yarn@3.8.2",
8
- "type": "module",
9
- "types": "dist/index.d.ts",
10
- "version": "1.0.0",
11
- "scripts": {
12
- "test": "jest",
13
- "build": "tsc",
14
- "lint": "eslint ./src",
15
- "typecheck": "tsc --noEmit",
16
- "findissues": "yarn typecheck && yarn lint"
17
- },
18
- "devDependencies": {
19
- "@babel/core": "^7.26.0",
20
- "@babel/preset-env": "^7.26.0",
21
- "@babel/preset-typescript": "^7.26.0",
22
- "@eslint/compat": "^1.2.3",
23
- "@eslint/js": "^9.15.0",
24
- "@jest/globals": "^29.7.0",
25
- "@types/babel__core": "^7.20.5",
26
- "@types/babel__preset-env": "^7.9.7",
27
- "@types/eslint": "^9.6.1",
28
- "@types/eslint__js": "^8.42.3",
29
- "@types/jest": "^29.5.14",
30
- "@typescript-eslint/eslint-plugin": "^8.15.0",
31
- "@typescript-eslint/parser": "^8.15.0",
32
- "babel-jest": "^29.7.0",
33
- "eslint": "^9.15.0",
34
- "eslint-plugin-eslint-comments": "^3.2.0",
35
- "eslint-plugin-import": "^2.31.0",
36
- "eslint-plugin-jest": "^28.9.0",
37
- "jest": "^29.7.0",
38
- "typescript": "^5.6.3",
39
- "typescript-eslint": "^8.15.0"
40
- }
41
- }
1
+ {
2
+ "author": "eniallator",
3
+ "description": "Deep guarding package",
4
+ "license": "Apache-2.0",
5
+ "main": "dist/index.js",
6
+ "name": "deep-guards",
7
+ "packageManager": "yarn@3.8.2",
8
+ "type": "module",
9
+ "types": "dist/index.d.ts",
10
+ "homepage": "https://github.com/eniallator/Deep-Guards",
11
+ "version": "1.0.2",
12
+ "scripts": {
13
+ "test": "jest",
14
+ "build": "tsc",
15
+ "lint": "eslint ./src",
16
+ "typecheck": "tsc --noEmit",
17
+ "findissues": "yarn typecheck && yarn lint"
18
+ },
19
+ "devDependencies": {
20
+ "@babel/core": "^7.26.0",
21
+ "@babel/preset-env": "^7.26.0",
22
+ "@babel/preset-typescript": "^7.26.0",
23
+ "@eslint/compat": "^1.2.3",
24
+ "@eslint/js": "^9.15.0",
25
+ "@jest/globals": "^29.7.0",
26
+ "@types/babel__core": "^7.20.5",
27
+ "@types/babel__preset-env": "^7.9.7",
28
+ "@types/eslint": "^9.6.1",
29
+ "@types/eslint__js": "^8.42.3",
30
+ "@types/jest": "^29.5.14",
31
+ "@typescript-eslint/eslint-plugin": "^8.15.0",
32
+ "@typescript-eslint/parser": "^8.15.0",
33
+ "babel-jest": "^29.7.0",
34
+ "eslint": "^9.15.0",
35
+ "eslint-plugin-eslint-comments": "^3.2.0",
36
+ "eslint-plugin-import": "^2.31.0",
37
+ "eslint-plugin-jest": "^28.9.0",
38
+ "jest": "^29.7.0",
39
+ "typescript": "^5.6.3",
40
+ "typescript-eslint": "^8.15.0"
41
+ }
42
+ }
package/src/compound.ts CHANGED
@@ -1,81 +1,101 @@
1
- import { Guard, GuardSchemaOf } from "./types.js";
2
-
3
- export function isOptional<T>(guard: Guard<T>): Guard<T | undefined> {
4
- if (typeof guard !== "function") {
5
- throw new TypeError(
6
- `isOptional expects a guard parameter. Got instead: ${guard}`
7
- );
8
- }
9
-
10
- return ((value) => value === undefined || guard(value)) as Guard<
11
- T | undefined
12
- >;
13
- }
14
-
15
- export function isNullable<T>(guard: Guard<T>): Guard<T | null | undefined> {
16
- if (typeof guard !== "function") {
17
- throw new TypeError(
18
- `isNullable expects a guard parameter. Got instead: ${guard}`
19
- );
20
- }
21
-
22
- return (value: unknown): value is T | null | undefined =>
23
- value == null || guard(value);
24
- }
25
-
26
- export function isNonNullable<T>(value: T | null | undefined): value is T {
27
- return value != null;
28
- }
29
-
30
- export function isNot<const N>(guard: Guard<N>) {
31
- if (typeof guard !== "function") {
32
- throw new TypeError(
33
- `isNot expects a guard parameter. Got instead: ${guard}`
34
- );
35
- }
36
-
37
- return <const T>(value: T | N): value is T => !guard(value);
38
- }
39
-
40
- export function isOneOf<
41
- const T extends (string | number | boolean | symbol | null | undefined)[]
42
- >(...values: T): Guard<(typeof values)[number]> {
43
- const valueSet = new Set(values);
44
- return (value: unknown): value is T[number] =>
45
- valueSet.has(value as T[number]);
46
- }
47
-
48
- export function isUnionOf<T extends readonly unknown[]>(
49
- ...guards: GuardSchemaOf<T>
50
- ): Guard<T[number]> {
51
- if (guards.every((guard) => typeof guard !== "function")) {
52
- throw new TypeError(
53
- `isUnionOf expects N guard parameters. Got instead: ${guards}`
54
- );
55
- }
56
-
57
- return (value): value is T => guards.some((guard) => guard(value));
58
- }
59
-
60
- function isEqual<T>(a: T, b: unknown): b is T {
61
- return (
62
- a === b ||
63
- (a != null &&
64
- b != null &&
65
- typeof a === "object" &&
66
- typeof b === "object" &&
67
- (Array.isArray(a)
68
- ? Array.isArray(b) &&
69
- a.length === b.length &&
70
- a.every((v, i) => isEqual(v, b[i]))
71
- : Object.keys(a).length === Object.keys(b).length &&
72
- Object.entries(a).every(
73
- ([k, v]) => k in b && isEqual(v, (b as Record<string, unknown>)[k])
74
- )))
75
- );
76
- }
77
-
78
- export function isExact<const T>(expected: T, deep: boolean = true): Guard<T> {
79
- return (value): value is T =>
80
- deep ? isEqual(expected, value) : expected === value;
81
- }
1
+ import { Guard, GuardSchemaOf } from "./types.js";
2
+
3
+ export function isOptional<T>(guard: Guard<T>): Guard<T | undefined> {
4
+ if (typeof guard !== "function") {
5
+ throw new TypeError(
6
+ `isOptional expects a guard parameter. Got instead: ${guard}`
7
+ );
8
+ }
9
+
10
+ return ((value) => value === undefined || guard(value)) as Guard<
11
+ T | undefined
12
+ >;
13
+ }
14
+
15
+ export function isNullable<T>(guard: Guard<T>): Guard<T | null | undefined> {
16
+ if (typeof guard !== "function") {
17
+ throw new TypeError(
18
+ `isNullable expects a guard parameter. Got instead: ${guard}`
19
+ );
20
+ }
21
+
22
+ return (value: unknown): value is T | null | undefined =>
23
+ value == null || guard(value);
24
+ }
25
+
26
+ export function isNonNullable<T>(value: T | null | undefined): value is T {
27
+ return value != null;
28
+ }
29
+
30
+ export function isNot<const N>(guard: Guard<N>) {
31
+ if (typeof guard !== "function") {
32
+ throw new TypeError(
33
+ `isNot expects a guard parameter. Got instead: ${guard}`
34
+ );
35
+ }
36
+
37
+ return <const T>(value: T | N): value is T => !guard(value);
38
+ }
39
+
40
+ export function isOneOf<
41
+ const T extends (string | number | boolean | symbol | null | undefined)[]
42
+ >(...values: T): Guard<(typeof values)[number]> {
43
+ const valueSet = new Set(values);
44
+ return (value: unknown): value is T[number] =>
45
+ valueSet.has(value as T[number]);
46
+ }
47
+
48
+ export function isUnionOf<T extends readonly unknown[]>(
49
+ ...guards: GuardSchemaOf<T>
50
+ ): Guard<T[number]> {
51
+ if (guards.every((guard) => typeof guard !== "function")) {
52
+ throw new TypeError(
53
+ `isUnionOf expects N guard parameters. Got instead: ${guards}`
54
+ );
55
+ }
56
+
57
+ return (value): value is T => guards.some((guard) => guard(value));
58
+ }
59
+
60
+ type ArrayToIntersection<A extends readonly unknown[]> = A extends [
61
+ infer T,
62
+ ...infer R
63
+ ]
64
+ ? T & ArrayToIntersection<R>
65
+ : unknown;
66
+
67
+ export function isIntersectionOf<T extends readonly unknown[]>(
68
+ ...guards: GuardSchemaOf<T>
69
+ ): Guard<ArrayToIntersection<T>> {
70
+ if (guards.every((guard) => typeof guard !== "function")) {
71
+ throw new TypeError(
72
+ `isIntersectionOf expects N guard parameters. Got instead: ${guards}`
73
+ );
74
+ }
75
+
76
+ return (value): value is ArrayToIntersection<T> =>
77
+ guards.every((guard) => guard(value));
78
+ }
79
+
80
+ function isEqual<T>(a: T, b: unknown): b is T {
81
+ 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
+ )))
95
+ );
96
+ }
97
+
98
+ export function isExact<const T>(expected: T, deep: boolean = true): Guard<T> {
99
+ return (value): value is T =>
100
+ deep ? isEqual(expected, value) : expected === value;
101
+ }
package/src/macros.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { isExact, isIntersectionOf } from "./compound";
2
+ import { isObjectOf } from "./structures";
3
+ import { Guard } from "./types";
4
+
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
+ >;
package/src/structures.ts CHANGED
@@ -1,83 +1,83 @@
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
- }
10
-
11
- export const isAnyArray: Guard<unknown[]> = (value) => Array.isArray(value);
12
-
13
- export const isAnyRecord: Guard<Record<ObjectKey, unknown>> = (
14
- value
15
- ): value is Record<ObjectKey, unknown> =>
16
- value != null && typeof value === "object" && !Array.isArray(value);
17
-
18
- export function isArrayOf<T>(guard: Guard<T>): Guard<T[]> {
19
- if (typeof guard !== "function") {
20
- throw new TypeError(
21
- `isArrayOf expects a guard parameter. Got instead: ${guard}`
22
- );
23
- }
24
-
25
- return (value): value is T[] => Array.isArray(value) && value.every(guard);
26
- }
27
-
28
- export function isRecordOf<K extends ObjectKey>(
29
- keyGuard: Guard<K>
30
- ): Guard<Record<K, unknown>>;
31
- export function isRecordOf<K extends ObjectKey, V>(
32
- keyGuard: Guard<K>,
33
- valueGuard: Guard<V>
34
- ): Guard<Record<K, V>>;
35
- export function isRecordOf<K extends ObjectKey, V>(
36
- keyGuard: Guard<K>,
37
- valueGuard?: Guard<V>
38
- ): Guard<Record<K, V>> {
39
- if (typeof keyGuard !== "function") {
40
- throw new TypeError(
41
- `isRecordOf keyGuard expects a guard parameter. Got instead: ${keyGuard}`
42
- );
43
- } else if (valueGuard != null && typeof valueGuard !== "function") {
44
- throw new TypeError(
45
- `isRecordOf valueGuard expects an optional guard parameter. Got instead: ${valueGuard}`
46
- );
47
- }
48
-
49
- return (value): value is Record<K, V> =>
50
- value != null &&
51
- typeof value === "object" &&
52
- !Array.isArray(value) &&
53
- objectKeys(value).every(
54
- (key) => keyGuard(key) && (valueGuard?.(value[key]) ?? true)
55
- );
56
- }
57
-
58
- export function isObjectOf<O extends object>(
59
- schema: GuardSchemaOf<O>
60
- ): O extends unknown[] ? never : Guard<O> {
61
- const schemaKeys = objectKeys(schema);
62
- const schemaUnknown = schema as unknown;
63
- if (schemaKeys.length === 0) {
64
- throw new Error("isObjectOf received an empty schema");
65
- } else if (
66
- schemaUnknown == null ||
67
- typeof schemaUnknown !== "object" ||
68
- Array.isArray(schemaUnknown) ||
69
- schemaKeys.some((key) => typeof (schemaUnknown as O)[key] !== "function")
70
- ) {
71
- throw new TypeError(
72
- `isObjectOf expects a guard schema. Got instead: ${schemaUnknown}`
73
- );
74
- }
75
-
76
- return ((value): value is O =>
77
- value != null &&
78
- typeof value === "object" &&
79
- !Array.isArray(value) &&
80
- schemaKeys.every(
81
- (key) => key in value && schema[key]((value as O)[key])
82
- )) as O extends unknown[] ? never : Guard<O>;
83
- }
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
+ }
10
+
11
+ export const isAnyArray: Guard<unknown[]> = (value) => Array.isArray(value);
12
+
13
+ export const isAnyRecord: Guard<Record<ObjectKey, unknown>> = (
14
+ value
15
+ ): value is Record<ObjectKey, unknown> =>
16
+ value != null && typeof value === "object" && !Array.isArray(value);
17
+
18
+ export function isArrayOf<T>(guard: Guard<T>): Guard<T[]> {
19
+ if (typeof guard !== "function") {
20
+ throw new TypeError(
21
+ `isArrayOf expects a guard parameter. Got instead: ${guard}`
22
+ );
23
+ }
24
+
25
+ return (value): value is T[] => Array.isArray(value) && value.every(guard);
26
+ }
27
+
28
+ export function isRecordOf<K extends ObjectKey>(
29
+ keyGuard: Guard<K>
30
+ ): Guard<Record<K, unknown>>;
31
+ export function isRecordOf<K extends ObjectKey, V>(
32
+ keyGuard: Guard<K>,
33
+ valueGuard: Guard<V>
34
+ ): Guard<Record<K, V>>;
35
+ export function isRecordOf<K extends ObjectKey, V>(
36
+ keyGuard: Guard<K>,
37
+ valueGuard?: Guard<V>
38
+ ): Guard<Record<K, V>> {
39
+ if (typeof keyGuard !== "function") {
40
+ throw new TypeError(
41
+ `isRecordOf keyGuard expects a guard parameter. Got instead: ${keyGuard}`
42
+ );
43
+ } else if (valueGuard != null && typeof valueGuard !== "function") {
44
+ throw new TypeError(
45
+ `isRecordOf valueGuard expects an optional guard parameter. Got instead: ${valueGuard}`
46
+ );
47
+ }
48
+
49
+ return (value): value is Record<K, V> =>
50
+ value != null &&
51
+ typeof value === "object" &&
52
+ !Array.isArray(value) &&
53
+ objectKeys(value).every(
54
+ (key) => keyGuard(key) && (valueGuard?.(value[key]) ?? true)
55
+ );
56
+ }
57
+
58
+ export function isObjectOf<O extends object>(
59
+ schema: GuardSchemaOf<O>
60
+ ): O extends unknown[] ? never : Guard<O> {
61
+ const schemaKeys = objectKeys(schema);
62
+ const schemaUnknown = schema as unknown;
63
+ if (
64
+ schemaUnknown == null ||
65
+ typeof schemaUnknown !== "object" ||
66
+ Array.isArray(schemaUnknown) ||
67
+ schemaKeys.some((key) => typeof (schemaUnknown as O)[key] !== "function")
68
+ ) {
69
+ throw new TypeError(
70
+ `isObjectOf expects a guard schema. Got instead: ${schemaUnknown}`
71
+ );
72
+ } else if (schemaKeys.length === 0) {
73
+ throw new Error("isObjectOf received an empty schema");
74
+ }
75
+
76
+ return ((value): value is O =>
77
+ value != null &&
78
+ typeof value === "object" &&
79
+ !Array.isArray(value) &&
80
+ schemaKeys.every(
81
+ (key) => key in value && schema[key]((value as O)[key])
82
+ )) as O extends unknown[] ? never : Guard<O>;
83
+ }
package/src/types.ts CHANGED
@@ -1,5 +1,9 @@
1
- export type Guard<T> = (value: unknown) => value is T;
2
-
3
- export type GuardSchemaOf<O extends object> = {
4
- [K in keyof O]: Guard<O[K]>;
5
- };
1
+ export type Guard<T> = (value: unknown) => value is T;
2
+
3
+ export type GuardSchemaOf<O extends object> = {
4
+ [K in keyof O]: Guard<O[K]>;
5
+ };
6
+
7
+ export type TypeFromGuard<G extends Guard<unknown>> = G extends Guard<infer T>
8
+ ? T
9
+ : never;
@@ -1,136 +1,151 @@
1
- import {
2
- isExact,
3
- isNot,
4
- isNullable,
5
- isNonNullable,
6
- isOneOf,
7
- isOptional,
8
- isUnionOf,
9
- } from "../src/compound";
10
- import { isNumber, isString } from "../src/primitives";
11
-
12
- describe("isOptional", () => {
13
- const guard = isOptional(isString);
14
-
15
- it("succeeds for the expected type or undefined", () => {
16
- expect(guard("foo")).toBe(true);
17
- expect(guard(undefined)).toBe(true);
18
- });
19
-
20
- it("fails for any other value", () => {
21
- expect(guard(null)).toBe(false);
22
- });
23
- });
24
-
25
- describe("isNullable", () => {
26
- const guard = isNullable(isString);
27
-
28
- it("succeeds for the expected type, null, or undefined", () => {
29
- expect(guard("foo")).toBe(true);
30
- expect(guard(null)).toBe(true);
31
- expect(guard(undefined)).toBe(true);
32
- });
33
-
34
- it("fails any other value", () => {
35
- expect(guard(1)).toBe(false);
36
- });
37
- });
38
-
39
- describe("isNonNullable", () => {
40
- const guard = isNonNullable;
41
-
42
- it("succeeds for the expected type, null, or undefined", () => {
43
- expect(guard("foo")).toBe(true);
44
- expect(guard(1)).toBe(true);
45
- });
46
-
47
- it("fails any other value", () => {
48
- expect(guard(null)).toBe(false);
49
- expect(guard(undefined)).toBe(false);
50
- });
51
- });
52
-
53
- describe("isNot", () => {
54
- const guard = isNot(isString);
55
-
56
- it("succeeds for any other value", () => {
57
- expect(guard(1)).toBe(true);
58
- });
59
-
60
- it("fails for the isNot type", () => {
61
- expect(guard("foo")).toBe(false);
62
- });
63
- });
64
-
65
- describe("isOneOf", () => {
66
- const guard = isOneOf(1, "foo", true);
67
-
68
- it("succeeds for all of the values", () => {
69
- expect(guard(1)).toBe(true);
70
- expect(guard("foo")).toBe(true);
71
- expect(guard(true)).toBe(true);
72
- });
73
-
74
- expect(guard(2)).toBe(false);
75
- it("fails any other value", () => {
76
- expect(guard(null)).toBe(false);
77
- expect(guard("bar")).toBe(false);
78
- expect(guard(false)).toBe(false);
79
- });
80
- });
81
-
82
- describe("isUnionOf", () => {
83
- const guard = isUnionOf(isString, isNumber);
84
- it("succeeds for the union types", () => {
85
- expect(guard(1)).toBe(true);
86
- expect(guard("foo")).toBe(true);
87
- });
88
-
89
- it("fails for any other type", () => {
90
- expect(guard(true)).toBe(false);
91
- expect(guard(null)).toBe(false);
92
- });
93
- });
94
-
95
- describe("isExact", () => {
96
- describe("deep", () => {
97
- const guard = isExact(
98
- { foo: "bar", hello: ["world", { key: "test" }] },
99
- true
100
- );
101
-
102
- it("succeeds for the exact value", () => {
103
- expect(guard({ foo: "bar", hello: ["world", { key: "test" }] })).toBe(
104
- true
105
- );
106
- });
107
-
108
- it("fails for any other value", () => {
109
- expect(guard({ foo: "baz", hello: ["world", { key: "test" }] })).toBe(
110
- false
111
- );
112
- expect(guard({ foo: "bar", hello: ["world", { key: "tester" }] })).toBe(
113
- false
114
- );
115
- expect(guard(1)).toBe(false);
116
- });
117
- });
118
-
119
- describe("shallow", () => {
120
- const guard = isExact("foo", false);
121
-
122
- it("succeeds for the exact value", () => {
123
- expect(guard("foo")).toBe(true);
124
- });
125
-
126
- it("fails for deep equality", () => {
127
- const guard = isExact(["foo", "bar", "baz", 1, 2, 3], false);
128
- expect(guard(["foo", "bar", "baz", 1, 2, 3])).toBe(false);
129
- });
130
-
131
- it("fails for any other value", () => {
132
- expect(guard("bar")).toBe(false);
133
- expect(guard(1)).toBe(false);
134
- });
135
- });
136
- });
1
+ import {
2
+ isExact,
3
+ isNot,
4
+ isNullable,
5
+ isNonNullable,
6
+ isOneOf,
7
+ isOptional,
8
+ isUnionOf,
9
+ isIntersectionOf,
10
+ } from "../src/compound";
11
+ import { isNumber, isString } from "../src/primitives";
12
+
13
+ describe("isOptional", () => {
14
+ const guard = isOptional(isString);
15
+
16
+ it("succeeds for the expected type or undefined", () => {
17
+ expect(guard("foo")).toBe(true);
18
+ expect(guard(undefined)).toBe(true);
19
+ });
20
+
21
+ it("fails for any other value", () => {
22
+ expect(guard(null)).toBe(false);
23
+ });
24
+ });
25
+
26
+ describe("isNullable", () => {
27
+ const guard = isNullable(isString);
28
+
29
+ it("succeeds for the expected type, null, or undefined", () => {
30
+ expect(guard("foo")).toBe(true);
31
+ expect(guard(null)).toBe(true);
32
+ expect(guard(undefined)).toBe(true);
33
+ });
34
+
35
+ it("fails any other value", () => {
36
+ expect(guard(1)).toBe(false);
37
+ });
38
+ });
39
+
40
+ describe("isNonNullable", () => {
41
+ it("succeeds for the expected type, null, or undefined", () => {
42
+ expect(isNonNullable("foo")).toBe(true);
43
+ expect(isNonNullable(1)).toBe(true);
44
+ });
45
+
46
+ it("fails any other value", () => {
47
+ expect(isNonNullable(null)).toBe(false);
48
+ expect(isNonNullable(undefined)).toBe(false);
49
+ });
50
+ });
51
+
52
+ describe("isNot", () => {
53
+ const guard = isNot(isString);
54
+
55
+ it("succeeds for any other value", () => {
56
+ expect(guard(1)).toBe(true);
57
+ });
58
+
59
+ it("fails for the isNot type", () => {
60
+ expect(guard("foo")).toBe(false);
61
+ });
62
+ });
63
+
64
+ describe("isOneOf", () => {
65
+ const guard = isOneOf(1, "foo", true);
66
+
67
+ it("succeeds for all of the values", () => {
68
+ expect(guard(1)).toBe(true);
69
+ expect(guard("foo")).toBe(true);
70
+ expect(guard(true)).toBe(true);
71
+ });
72
+
73
+ expect(guard(2)).toBe(false);
74
+ it("fails any other value", () => {
75
+ expect(guard(null)).toBe(false);
76
+ expect(guard("bar")).toBe(false);
77
+ expect(guard(false)).toBe(false);
78
+ });
79
+ });
80
+
81
+ describe("isUnionOf", () => {
82
+ const guard = isUnionOf(isString, isNumber);
83
+ it("succeeds for the union types", () => {
84
+ expect(guard(1)).toBe(true);
85
+ expect(guard("foo")).toBe(true);
86
+ });
87
+
88
+ it("fails for any other type", () => {
89
+ expect(guard(true)).toBe(false);
90
+ expect(guard(null)).toBe(false);
91
+ });
92
+ });
93
+
94
+ describe("isIntersectionOf", () => {
95
+ const guard = isIntersectionOf(
96
+ isOneOf("foo", "bar", "baz"),
97
+ isExact("foo", false)
98
+ );
99
+
100
+ it("succeeds for the intersection", () => {
101
+ expect(guard("foo")).toBe(true);
102
+ });
103
+
104
+ it("fails for any other type", () => {
105
+ expect(guard("bar")).toBe(false);
106
+ expect(guard(1)).toBe(false);
107
+ });
108
+ });
109
+
110
+ describe("isExact", () => {
111
+ describe("deep", () => {
112
+ const guard = isExact(
113
+ { foo: "bar", hello: ["world", { key: "test" }] },
114
+ true
115
+ );
116
+
117
+ it("succeeds for the exact value", () => {
118
+ expect(guard({ foo: "bar", hello: ["world", { key: "test" }] })).toBe(
119
+ true
120
+ );
121
+ });
122
+
123
+ it("fails for any other value", () => {
124
+ expect(guard({ foo: "baz", hello: ["world", { key: "test" }] })).toBe(
125
+ false
126
+ );
127
+ expect(guard({ foo: "bar", hello: ["world", { key: "tester" }] })).toBe(
128
+ false
129
+ );
130
+ expect(guard(1)).toBe(false);
131
+ });
132
+ });
133
+
134
+ describe("shallow", () => {
135
+ const guard = isExact("foo", false);
136
+
137
+ it("succeeds for the exact value", () => {
138
+ expect(guard("foo")).toBe(true);
139
+ });
140
+
141
+ it("fails for deep equality", () => {
142
+ const guard = isExact(["foo", "bar", "baz", 1, 2, 3], false);
143
+ expect(guard(["foo", "bar", "baz", 1, 2, 3])).toBe(false);
144
+ });
145
+
146
+ it("fails for any other value", () => {
147
+ expect(guard("bar")).toBe(false);
148
+ expect(guard(1)).toBe(false);
149
+ });
150
+ });
151
+ });