deep-guards 1.0.3 → 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 +4 -1
- package/dist/compound.d.ts +2 -1
- package/dist/compound.js +23 -13
- package/dist/compound.js.map +1 -1
- package/dist/helpers.d.ts +9 -0
- package/dist/helpers.js +8 -0
- package/dist/helpers.js.map +1 -0
- package/dist/macros.d.ts +5 -1
- package/dist/macros.js +6 -2
- package/dist/macros.js.map +1 -1
- package/dist/structures.d.ts +4 -3
- package/dist/structures.js +6 -6
- package/dist/structures.js.map +1 -1
- package/dist/types.d.ts +0 -3
- package/package.json +1 -1
- package/src/compound.ts +23 -16
- package/src/helpers.ts +22 -0
- package/src/macros.ts +25 -8
- package/src/structures.ts +17 -14
- package/src/types.ts +0 -4
- package/tests/macros.test.ts +30 -7
- package/tests/structures.test.ts +71 -26
package/README.md
CHANGED
|
@@ -186,6 +186,8 @@ NOTE: This passes for empty records
|
|
|
186
186
|
|
|
187
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.
|
|
188
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
|
+
|
|
189
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.
|
|
190
192
|
|
|
191
193
|
NOTE: This throws an error if you give it an empty object.\
|
|
@@ -197,7 +199,8 @@ These are common use cases for guarding setups, where they are made entirely out
|
|
|
197
199
|
|
|
198
200
|
### isDiscriminatedObjectOf
|
|
199
201
|
|
|
200
|
-
This takes in a string literal
|
|
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
|
+
|
|
201
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:
|
|
202
205
|
|
|
203
206
|
```ts
|
package/dist/compound.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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;
|
package/dist/compound.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
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}`);
|
|
@@ -35,20 +36,29 @@ export function isIntersectionOf(...guards) {
|
|
|
35
36
|
}
|
|
36
37
|
return (value) => guards.every((guard) => guard(value));
|
|
37
38
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
typeof b === "object" &&
|
|
44
|
-
(Array.isArray(a)
|
|
45
|
-
? Array.isArray(b) &&
|
|
46
|
-
a.length === b.length &&
|
|
47
|
-
a.every((v, i) => isEqual(v, b[i]))
|
|
48
|
-
: Object.keys(a).length === Object.keys(b).length &&
|
|
49
|
-
Object.entries(a).every(([k, v]) => k in b && isEqual(v, b[k])))));
|
|
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])));
|
|
50
44
|
}
|
|
51
45
|
export function isExact(expected, deep = true) {
|
|
52
|
-
return (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)));
|
|
53
63
|
}
|
|
54
64
|
//# sourceMappingURL=compound.js.map
|
package/dist/compound.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compound.js","sourceRoot":"src/","sources":["compound.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compound.js","sourceRoot":"src/","sources":["compound.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
+
};
|
package/dist/helpers.js
ADDED
|
@@ -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/macros.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { ObjectKey } from "./helpers";
|
|
1
2
|
import { Guard } from "./types";
|
|
2
|
-
export declare
|
|
3
|
+
export declare function isDiscriminatedObjectOf<const T extends string, O extends object>(value: T, guard: Guard<O>): Guard<{
|
|
3
4
|
type: T;
|
|
4
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
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { isExact
|
|
1
|
+
import { isExact } from "./compound";
|
|
2
|
+
import { omit } from "./helpers";
|
|
2
3
|
import { isObjectOf } from "./structures";
|
|
3
|
-
export
|
|
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
|
+
}
|
|
4
8
|
//# sourceMappingURL=macros.js.map
|
package/dist/macros.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"macros.js","sourceRoot":"src/","sources":["macros.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,
|
|
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"}
|
package/dist/structures.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
6
|
export declare function isTupleOf<T extends readonly unknown[]>(...tupleGuards: GuardSchemaOf<T>): Guard<T>;
|
|
7
7
|
export declare function isRecordOf<K extends ObjectKey>(keyGuard: Guard<K>): Guard<Record<K, unknown>>;
|
|
8
8
|
export declare function isRecordOf<K extends ObjectKey, V>(keyGuard: Guard<K>, valueGuard: Guard<V>): Guard<Record<K, V>>;
|
|
9
|
-
|
|
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>;
|
|
10
11
|
export {};
|
package/dist/structures.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
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) {
|
|
@@ -29,9 +27,10 @@ export function isRecordOf(keyGuard, valueGuard) {
|
|
|
29
27
|
!Array.isArray(value) &&
|
|
30
28
|
objectKeys(value).every((key) => keyGuard(key) && (valueGuard?.(value[key]) ?? true));
|
|
31
29
|
}
|
|
32
|
-
export function isObjectOf(schema) {
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
export function isObjectOf(schema, exactKeys = false) {
|
|
31
|
+
const schemaUnknown = schema;
|
|
32
|
+
if (schemaUnknown == null || typeof schemaUnknown !== "object") {
|
|
33
|
+
throw new TypeError(`isObjectOf expects a guard schema object. Got instead: ${schemaUnknown}`);
|
|
35
34
|
}
|
|
36
35
|
const schemaKeys = objectKeys(schema);
|
|
37
36
|
if (Array.isArray(schema) ||
|
|
@@ -44,6 +43,7 @@ export function isObjectOf(schema) {
|
|
|
44
43
|
return ((value) => value != null &&
|
|
45
44
|
typeof value === "object" &&
|
|
46
45
|
!Array.isArray(value) &&
|
|
46
|
+
(!exactKeys || schemaKeys.length === objectKeys(value).length) &&
|
|
47
47
|
schemaKeys.every((key) => key in value && schema[key](value[key])));
|
|
48
48
|
}
|
|
49
49
|
//# sourceMappingURL=structures.js.map
|
package/dist/structures.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structures.js","sourceRoot":"src/","sources":["structures.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"structures.js","sourceRoot":"src/","sources":["structures.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,UAAU,EAAE,MAAM,WAAW,CAAC;AAGjE,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;AAED,MAAM,UAAU,SAAS,CACvB,GAAG,WAA6B;IAEhC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,UAAU,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,SAAS,CACjB,oDAAoD,IAAI,CAAC,SAAS,CAChE,WAAW,CACZ,EAAE,CACJ,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,KAAK,EAAc,EAAE,CAC3B,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACpB,KAAK,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM;QACnC,WAAW,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,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;AASD,MAAM,UAAU,UAAU,CACxB,MAAwB,EACxB,YAAqB,KAAK;IAE1B,MAAM,aAAa,GAAY,MAAM,CAAC;IACtC,IAAI,aAAa,IAAI,IAAI,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC;QAC/D,MAAM,IAAI,SAAS,CACjB,0DAA0D,aAAa,EAAE,CAC1E,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,IACE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACrB,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,UAAU,CAAC,EAC3D,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,yDAAyD,IAAI,CAAC,SAAS,CACrE,MAAM,CACP,EAAE,CACJ,CAAC;IACJ,CAAC;SAAM,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,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,CAAC,CAAC,SAAS,IAAI,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QAC9D,UAAU,CAAC,KAAK,CACd,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,MAAM,CAAC,GAAG,CAAC,CAAE,KAAW,CAAC,GAAG,CAAC,CAAC,CACxD,CAAuB,CAAC;AAC7B,CAAC"}
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
package/src/compound.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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") {
|
|
@@ -75,25 +76,31 @@ export function isIntersectionOf<T extends readonly unknown[]>(
|
|
|
75
76
|
guards.every((guard) => guard(value));
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
function
|
|
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));
|
|
79
82
|
return (
|
|
80
|
-
|
|
81
|
-
(
|
|
82
|
-
b != null &&
|
|
83
|
-
typeof a === "object" &&
|
|
84
|
-
typeof b === "object" &&
|
|
85
|
-
(Array.isArray(a)
|
|
86
|
-
? Array.isArray(b) &&
|
|
87
|
-
a.length === b.length &&
|
|
88
|
-
a.every((v, i) => isEqual(v, b[i]))
|
|
89
|
-
: Object.keys(a).length === Object.keys(b).length &&
|
|
90
|
-
Object.entries(a).every(
|
|
91
|
-
([k, v]) => k in b && isEqual(v, (b as Record<string, unknown>)[k])
|
|
92
|
-
)))
|
|
83
|
+
aKeys.length === bKeySet.size &&
|
|
84
|
+
aKeys.every((k) => bKeySet.has(k) && isExact(a[k], true)(b[k]))
|
|
93
85
|
);
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
export function isExact<const T>(expected: T, deep: boolean = true): Guard<T> {
|
|
97
89
|
return (value): value is T =>
|
|
98
|
-
|
|
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)));
|
|
99
106
|
}
|
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/macros.ts
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import { isExact
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
): Guard<{ type: T } & O
|
|
9
|
-
|
|
10
|
-
|
|
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/structures.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import {
|
|
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
|
|
|
@@ -72,12 +65,21 @@ export function isRecordOf<K extends ObjectKey, V>(
|
|
|
72
65
|
);
|
|
73
66
|
}
|
|
74
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
|
+
|
|
75
75
|
export function isObjectOf<O extends object>(
|
|
76
|
-
schema: GuardSchemaOf<O
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
schema: GuardSchemaOf<O>,
|
|
77
|
+
exactKeys: boolean = false
|
|
78
|
+
): IsObjectOfGuard<O> {
|
|
79
|
+
const schemaUnknown: unknown = schema;
|
|
80
|
+
if (schemaUnknown == null || typeof schemaUnknown !== "object") {
|
|
79
81
|
throw new TypeError(
|
|
80
|
-
`isObjectOf expects a guard schema object. Got instead: ${
|
|
82
|
+
`isObjectOf expects a guard schema object. Got instead: ${schemaUnknown}`
|
|
81
83
|
);
|
|
82
84
|
}
|
|
83
85
|
|
|
@@ -99,7 +101,8 @@ export function isObjectOf<O extends object>(
|
|
|
99
101
|
value != null &&
|
|
100
102
|
typeof value === "object" &&
|
|
101
103
|
!Array.isArray(value) &&
|
|
104
|
+
(!exactKeys || schemaKeys.length === objectKeys(value).length) &&
|
|
102
105
|
schemaKeys.every(
|
|
103
106
|
(key) => key in value && schema[key]((value as O)[key])
|
|
104
|
-
)) as
|
|
107
|
+
)) as IsObjectOfGuard<O>;
|
|
105
108
|
}
|
package/src/types.ts
CHANGED
package/tests/macros.test.ts
CHANGED
|
@@ -1,15 +1,38 @@
|
|
|
1
1
|
import { isDiscriminatedObjectOf, isObjectOf, isString } from "../src";
|
|
2
2
|
|
|
3
3
|
describe("isDiscriminatedObjectOf", () => {
|
|
4
|
-
|
|
4
|
+
describe("no key override", () => {
|
|
5
|
+
const guard = isDiscriminatedObjectOf(
|
|
6
|
+
"foo",
|
|
7
|
+
isObjectOf({ bar: isString }, true)
|
|
8
|
+
);
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
|
|
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
|
+
});
|
|
8
19
|
});
|
|
9
20
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
});
|
|
14
37
|
});
|
|
15
38
|
});
|
package/tests/structures.test.ts
CHANGED
|
@@ -65,35 +65,80 @@ describe("isTupleOf", () => {
|
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
describe("isObjectOf", () => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
});
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
});
|
|
86
99
|
});
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
+
});
|
|
97
142
|
});
|
|
98
143
|
});
|
|
99
144
|
|