@jakobkg/shapes-ts 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,104 @@
1
1
  # shapes-ts
2
2
 
3
- Toy library to create runtime safe types in TS, see `main.ts` for example usage
3
+ Toy library to create runtime safe types in TS.
4
+
5
+ When describing the shape of your data in shapes-ts, you get the corresponding
6
+ TS type and a validation utility for free!
7
+
8
+ ```typescript
9
+ const Role = Shapes.object({
10
+ role: Shapes.string(),
11
+ actor: Shapes.string(),
12
+ });
13
+
14
+ const Movie = Shapes.object({
15
+ title: Shapes.string(),
16
+ rating: Shapes.number().where(
17
+ (n) => n >= 0 && n <= 5,
18
+ "rating must be in range 0.0-5.0",
19
+ ),
20
+ cast: Shapes.array(Role),
21
+ });
22
+
23
+ // This infers as
24
+ // {
25
+ // title: string,
26
+ // rating: number,
27
+ // cast: { role: string, actor: string }[]
28
+ // }
29
+ type Movie = Shapes.Type<typeof Movie>;
30
+
31
+ const movieData: unknown = getFromIMDB("tt23289160");
32
+
33
+ if (!Movie.check(movieData)) {
34
+ throw new Error("Unexpected data from API");
35
+ }
36
+
37
+ // Past this point, movieData is typed as Movie
38
+
39
+ for (const member of movieData.cast) {
40
+ console.log(`${member.actor} as ${member.role}`);
41
+ }
42
+ ```
43
+
44
+ ## Supported types
45
+
46
+ ### Primitives
47
+
48
+ - string
49
+ - number
50
+ - boolean
51
+
52
+ ...and literals of these
53
+
54
+ ### Composite types
55
+
56
+ - object
57
+ - array
58
+
59
+ ### Modified types
60
+
61
+ - Nullable types (`T | null`)
62
+ - Optional types (`T | undefined`)
63
+ - Unions (`A | B`)
64
+ - Intersections (`A & B`)
65
+
66
+ ## Predicates
67
+
68
+ Shapes can have additional validation checks added to them. These do not affect
69
+ the inferred type, but are still ran as part of a shape's `.check()` method to
70
+ help enforce constraints that are not easily modeled using the type system.
71
+
72
+ ```typescript
73
+ const Username = Shapes.string().where(
74
+ (str) => str.length < 10,
75
+ "must be less than 10 characters long",
76
+ );
77
+
78
+ Username.check("jakob :)"); // true
79
+ Username.check("CptAmericaFan207"); // false
80
+ ```
81
+
82
+ Predicates can be used with all types of shapes, and they may be combined both
83
+ by stacking them on a single shape and by combining shapes that already have
84
+ predicates on them.
85
+
86
+ ```typescript
87
+ const Fizz = Shapes.number().where(
88
+ (n) => n % 3 === 0,
89
+ "must be divisible by 3",
90
+ );
91
+
92
+ const Buzz = Shapes.number().where(
93
+ (n) => n % 5 === 0,
94
+ "must be divisible by 5",
95
+ );
96
+
97
+ const FizzBuzz = Fizz.and(Buzz);
98
+
99
+ Fizz.check(3); // true
100
+ FizzBuzz.check(3); // false
101
+ Fizz.check(5); // false
102
+ Buzz.check(5); // true
103
+ FizzBuzz.check(15); //true
104
+ ```
package/dist/shapes.d.ts CHANGED
@@ -1,13 +1,17 @@
1
+ declare const Predicates: unique symbol;
2
+ declare const Properties: unique symbol;
3
+ declare const AllowUnknownProperties: unique symbol;
1
4
  /**
2
5
  * Bridge to convert a Shape into a TS type.
3
6
  */
4
- export type Type<T extends Shape> = T["primitive"];
5
- type ShapePredicate<T> = (x: T) => boolean;
6
- interface Shape<T = any> {
7
+ export type Type<T> = T extends Shape<infer U> ? U : never;
8
+ type ShapePredicate<T> = {
9
+ predicate: (x: T) => boolean;
10
+ description: string | undefined;
11
+ };
12
+ interface Shape<T> {
7
13
  readonly typename: string;
8
- readonly primitive: T;
9
- readonly optional?: boolean;
10
- readonly predicates: ShapePredicate<T>[];
14
+ readonly [Predicates]: ShapePredicate<T>[];
11
15
  check(x: unknown): x is T;
12
16
  /**
13
17
  * Adds a custom validation predicate to the shape
@@ -15,35 +19,18 @@ interface Shape<T = any> {
15
19
  * @param description Optional description of the constraint for error messages
16
20
  * @returns A new shape with the additional validation
17
21
  */
18
- where(predicate: ShapePredicate<T>, description: string): Shape<T>;
19
- and<U>(other: Shape<U>): IntersectionShape<T, U>;
20
- or<U>(other: Shape<U>): UnionShape<T, U>;
22
+ where(predicate: (x: T) => boolean, description: string | undefined): Shape<T>;
23
+ and<U>(other: Shape<U>): Shape<T & U>;
24
+ or<U>(other: Shape<U>): Shape<T | U>;
21
25
  }
22
26
  interface ObjectShape<T = Record<string, unknown>> extends Shape<T> {
23
- readonly properties: Record<string, Shape<T>>;
24
- readonly allowUnknownProperties?: boolean;
27
+ readonly [Properties]: Record<string, Shape<T>>;
28
+ readonly [AllowUnknownProperties]?: boolean;
25
29
  }
26
30
  interface ObjectShapeOptions {
27
31
  readonly allowUnknownProperties?: boolean;
28
32
  readonly additionalPermittedProperties?: Record<string, Shape<Record<string, unknown>>>;
29
33
  }
30
- interface ArrayShape<T extends Shape> extends Shape<Array<T["primitive"]>> {
31
- readonly inner: T;
32
- }
33
- interface UnionShape<Left, Right> extends Shape<Left | Right> {
34
- left: Shape<Left>;
35
- right: Shape<Right>;
36
- }
37
- interface IntersectionShape<Left, Right> extends Shape<Left & Right> {
38
- left: Shape<Left>;
39
- right: Shape<Right>;
40
- }
41
- interface OptionalShape<T extends Shape> extends Shape<T["primitive"] | undefined> {
42
- readonly inner: T;
43
- }
44
- interface NullableShape<T extends Shape> extends Shape<T["primitive"] | null> {
45
- readonly inner: T;
46
- }
47
34
  /**
48
35
  * Creates a shape representing a JS `number`.
49
36
  * Does not support BigInt.
@@ -57,15 +44,18 @@ export declare function string(): Shape<string>;
57
44
  * Creates a shape representing a JS `boolean`.
58
45
  */
59
46
  export declare function boolean(): Shape<boolean>;
47
+ type Primitive = number | string | boolean | null | undefined;
48
+ export declare function literal<T extends Primitive>(x: T): Shape<T>;
60
49
  /**
61
50
  * Creates an array shape, representing `Array<T>` (aka `T[]`).
62
51
  * Takes the shape representing `T` as a parameter.
63
52
  */
64
- export declare function array<T extends Shape>(shape: T): ArrayShape<T>;
53
+ export declare function array<T>(shape: Shape<T>): Shape<T[]>;
65
54
  /**
66
55
  * Creates an object shape.
67
56
  *
68
57
  * @example
58
+ * ```typescript
69
59
  * const Group = Shapes.object({
70
60
  * id: Shapes.number(),
71
61
  * name: Shapes.string(),
@@ -78,19 +68,24 @@ export declare function array<T extends Shape>(shape: T): ArrayShape<T>;
78
68
  * Shapes.array(Group)
79
69
  * )
80
70
  * });
71
+ * ```
81
72
  */
82
- export declare function object<T extends Record<string, Shape>>(properties: T, options?: ObjectShapeOptions): ObjectShape<{
73
+ export declare function object<T extends Record<string, Shape<any>>>(properties: T, options?: ObjectShapeOptions): ObjectShape<{
83
74
  [K in keyof T]: Type<T[K]>;
84
75
  }>;
76
+ export declare function tuple<Shapes extends Shape<any>[], Partial extends {
77
+ [K in keyof Shapes]: Type<Shapes[K]>;
78
+ }, Tuple extends RestShape extends Shape<infer Rest> ? [...Partial, ...Rest[]] : [...Partial], RestShape extends Shape<any> | undefined = undefined>(shapes: [...Shapes], rest?: RestShape): Shape<Tuple>;
79
+ export { enumOf as enum };
80
+ declare function enumOf<T extends (string | number)[]>(...values: T): Shape<T[number]>;
85
81
  /**
86
82
  * Creates a shape representing an optional type.
87
83
  * `Shapes.optional(T)` corresponds to `T | undefined`
88
84
  */
89
- export declare function optional<T extends Shape>(shape: T): OptionalShape<T>;
85
+ export declare function optional<T>(shape: Shape<T>): Shape<T | undefined>;
90
86
  /**
91
87
  * Creates a shape representing a nullable type.
92
88
  * `Shapes.nullable(T)` corresponds to `T | null`
93
89
  */
94
- export declare function nullable<T extends Shape>(shape: T): NullableShape<T>;
95
- export {};
90
+ export declare function nullable<T>(shape: Shape<T>): Shape<T | null>;
96
91
  //# sourceMappingURL=shapes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shapes.d.ts","sourceRoot":"","sources":["../src/shapes/shapes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shapes.d.ts","sourceRoot":"","sources":["../src/shapes/shapes.ts"],"names":[],"mappings":"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"}
package/dist/shapes.js CHANGED
@@ -1,7 +1,4 @@
1
1
  "use strict";
2
- /// TYPE GUARDS
3
- // These are static functions to avoid allocating guards per Shape instance,
4
- // Shapes may reference these instead
5
2
  var __assign = (this && this.__assign) || function () {
6
3
  __assign = Object.assign || function(t) {
7
4
  for (var s, i = 1, n = arguments.length; i < n; i++) {
@@ -13,14 +10,34 @@ var __assign = (this && this.__assign) || function () {
13
10
  };
14
11
  return __assign.apply(this, arguments);
15
12
  };
13
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
14
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
15
+ if (ar || !(i in from)) {
16
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
17
+ ar[i] = from[i];
18
+ }
19
+ }
20
+ return to.concat(ar || Array.prototype.slice.call(from));
21
+ };
16
22
  Object.defineProperty(exports, "__esModule", { value: true });
17
23
  exports.number = number;
18
24
  exports.string = string;
19
25
  exports.boolean = boolean;
26
+ exports.literal = literal;
20
27
  exports.array = array;
21
28
  exports.object = object;
29
+ exports.tuple = tuple;
30
+ exports.enum = enumOf;
22
31
  exports.optional = optional;
23
32
  exports.nullable = nullable;
33
+ /// SYMBOLS
34
+ // These are here to hide some API bits from consumers
35
+ var Predicates = Symbol();
36
+ var Properties = Symbol();
37
+ var AllowUnknownProperties = Symbol();
38
+ /// TYPE GUARDS
39
+ // These are static functions to avoid allocating guards per Shape instance,
40
+ // Shapes may reference these instead
24
41
  function isString(input) {
25
42
  return typeof input === "string";
26
43
  }
@@ -33,9 +50,6 @@ function isObject(input) {
33
50
  function isArray(input) {
34
51
  return Array.isArray(input);
35
52
  }
36
- function isNull(input) {
37
- return input === null;
38
- }
39
53
  function isBoolean(input) {
40
54
  return typeof input === "boolean";
41
55
  }
@@ -43,54 +57,52 @@ function isUndefined(input) {
43
57
  return input === undefined;
44
58
  }
45
59
  function makeUnion(left, right) {
46
- return {
47
- typename: "".concat(left.typename, " | ").concat(right.typename),
48
- primitive: undefined,
49
- left: left,
50
- right: right,
51
- predicates: [],
52
- where: function (p) {
53
- this.predicates.push(p);
54
- return this;
55
- },
56
- check: function (x) {
60
+ var _a;
61
+ return _a = {
62
+ typename: "".concat(left.typename, " | ").concat(right.typename)
63
+ },
64
+ _a[Predicates] = [],
65
+ _a.where = function (predicate, description) {
66
+ var _a;
67
+ return __assign(__assign({}, this), (_a = {}, _a[Predicates] = __spreadArray(__spreadArray([], this[Predicates], true), [{ predicate: predicate, description: description }], false), _a));
68
+ },
69
+ _a.check = function (x) {
57
70
  return (left.check(x) || right.check(x)) &&
58
- this.predicates.every(function (p) { return p(x); });
59
- }, // TODO: refine for objects?
60
- and: function (other) {
71
+ this[Predicates].every(function (p) { return p.predicate(x); });
72
+ },
73
+ _a.and = function (other) {
61
74
  return makeIntersection(this, other);
62
75
  },
63
- or: function (other) {
76
+ _a.or = function (other) {
64
77
  return makeUnion(this, other);
65
78
  },
66
- };
79
+ _a;
67
80
  }
68
81
  function makeIntersection(left, right) {
69
- return {
70
- typename: "".concat(left.typename, " | ").concat(right.typename),
71
- primitive: undefined,
72
- left: __assign(__assign({}, left), {
73
- // Trickery: combine permitted properties of left and right
74
- properties: __assign(__assign({}, right.properties), left.properties) }),
75
- right: __assign(__assign({}, right), {
76
- // Trickery: combine permitted properties of left and right
77
- properties: __assign(__assign({}, left.properties), right.properties) }),
78
- predicates: [],
79
- where: function (p) {
80
- this.predicates.push(p);
81
- return this;
82
+ var _a;
83
+ return _a = {
84
+ typename: "".concat(left.typename, " & ").concat(right.typename)
82
85
  },
83
- check: function (x) {
84
- return this.left.check(x) && this.right.check(x) &&
85
- this.predicates.every(function (p) { return p(x); });
86
+ _a[Predicates] = [],
87
+ _a.where = function (predicate, description) {
88
+ var _a;
89
+ return __assign(__assign({}, this), (_a = {}, _a[Predicates] = __spreadArray(__spreadArray([], this[Predicates], true), [{ predicate: predicate, description: description }], false), _a));
86
90
  },
87
- and: function (other) {
91
+ _a.check = function (x) {
92
+ var _a, _b;
93
+ // Trickery: combine permitted properties of left and right
94
+ var tolerantLeft = __assign(__assign({}, left), (_a = {}, _a[Properties] = __assign(__assign({}, right[Properties]), left[Properties]), _a));
95
+ var tolerantRight = __assign(__assign({}, right), (_b = {}, _b[Properties] = __assign(__assign({}, left[Properties]), right[Properties]), _b));
96
+ return tolerantLeft.check(x) && tolerantRight.check(x) &&
97
+ this[Predicates].every(function (p) { return p.predicate(x); });
98
+ },
99
+ _a.and = function (other) {
88
100
  return makeIntersection(this, other);
89
101
  },
90
- or: function (other) {
102
+ _a.or = function (other) {
91
103
  return makeUnion(this, other);
92
104
  },
93
- };
105
+ _a;
94
106
  }
95
107
  /// PRIMITIVE SHAPE FACTORIES
96
108
  /**
@@ -98,70 +110,98 @@ function makeIntersection(left, right) {
98
110
  * Does not support BigInt.
99
111
  */
100
112
  function number() {
101
- return {
102
- typename: "number",
103
- predicates: [],
104
- primitive: undefined,
105
- check: function (x) {
106
- return isNumber(x) && this.predicates.every(function (p) { return p(x); });
107
- },
108
- where: function (p) {
109
- this.predicates.push(p);
110
- return this;
111
- },
112
- and: function (other) {
113
+ var _a;
114
+ return _a = {
115
+ typename: "number"
116
+ },
117
+ _a[Predicates] = [],
118
+ _a.check = function (x) {
119
+ return isNumber(x) && this[Predicates].every(function (p) { return p.predicate(x); });
120
+ },
121
+ _a.where = function (predicate, description) {
122
+ var _a;
123
+ return __assign(__assign({}, this), (_a = {}, _a[Predicates] = __spreadArray(__spreadArray([], this[Predicates], true), [{ predicate: predicate, description: description }], false), _a));
124
+ },
125
+ _a.and = function (other) {
113
126
  return makeIntersection(this, other);
114
127
  },
115
- or: function (other) {
128
+ _a.or = function (other) {
116
129
  return makeUnion(this, other);
117
130
  },
118
- };
131
+ _a;
119
132
  }
120
133
  /**
121
134
  * Creates a shape representing a JS `string`.
122
135
  */
123
136
  function string() {
124
- return {
125
- typename: "string",
126
- primitive: undefined,
127
- check: function (x) {
128
- return isString(x) && this.predicates.every(function (p) { return p(x); });
129
- },
130
- predicates: [],
131
- where: function (predicate) {
132
- this.predicates.push(predicate);
133
- return this;
134
- },
135
- and: function (other) {
137
+ var _a;
138
+ return _a = {
139
+ typename: "string",
140
+ check: function (x) {
141
+ return isString(x) && this[Predicates].every(function (p) { return p.predicate(x); });
142
+ }
143
+ },
144
+ _a[Predicates] = [],
145
+ _a.where = function (predicate, description) {
146
+ var _a;
147
+ return __assign(__assign({}, this), (_a = {}, _a[Predicates] = __spreadArray(__spreadArray([], this[Predicates], true), [{ predicate: predicate, description: description }], false), _a));
148
+ },
149
+ _a.and = function (other) {
136
150
  return makeIntersection(this, other);
137
151
  },
138
- or: function (other) {
152
+ _a.or = function (other) {
139
153
  return makeUnion(this, other);
140
154
  },
141
- };
155
+ _a;
142
156
  }
143
157
  /**
144
158
  * Creates a shape representing a JS `boolean`.
145
159
  */
146
160
  function boolean() {
147
- return {
148
- primitive: undefined,
149
- typename: "boolean",
150
- check: function (x) {
151
- return isBoolean(x) && this.predicates.every(function (p) { return p(x); });
152
- },
153
- predicates: [],
154
- where: function (predicate) {
155
- this.predicates.push(predicate);
156
- return this;
157
- },
158
- and: function (other) {
161
+ var _a;
162
+ return _a = {
163
+ typename: "boolean",
164
+ check: function (x) {
165
+ return isBoolean(x) && this[Predicates].every(function (p) { return p.predicate(x); });
166
+ }
167
+ },
168
+ _a[Predicates] = [],
169
+ _a.where = function (predicate, description) {
170
+ var _a;
171
+ return __assign(__assign({}, this), (_a = {}, _a[Predicates] = __spreadArray(__spreadArray([], this[Predicates], true), [{ predicate: predicate, description: description }], false), _a));
172
+ },
173
+ _a.and = function (other) {
159
174
  return makeIntersection(this, other);
160
175
  },
161
- or: function (other) {
176
+ _a.or = function (other) {
162
177
  return makeUnion(this, other);
163
178
  },
164
- };
179
+ _a;
180
+ }
181
+ function literal(x) {
182
+ var _a;
183
+ function matchesLiteral(u) {
184
+ return u === x;
185
+ }
186
+ return _a = {
187
+ typename: isUndefined(x) ? "undefined" : JSON.stringify(x)
188
+ },
189
+ _a[Predicates] = [],
190
+ _a.check = function (input) {
191
+ return matchesLiteral(input) &&
192
+ this[Predicates].every(function (p) { return p.predicate(input); });
193
+ },
194
+ _a.where = function (predicate, description) {
195
+ var _a;
196
+ return __assign(__assign({}, this), (_a = {}, _a[Predicates] = __spreadArray(__spreadArray([], this[Predicates], true), [{ predicate: predicate, description: description }], false), _a));
197
+ },
198
+ _a.and = function (other) {
199
+ return makeIntersection(this, other);
200
+ },
201
+ _a.or = function (other) {
202
+ return makeUnion(this, other);
203
+ },
204
+ _a;
165
205
  }
166
206
  /// COMPLEX SHAPE FACTORIES
167
207
  /**
@@ -169,39 +209,36 @@ function boolean() {
169
209
  * Takes the shape representing `T` as a parameter.
170
210
  */
171
211
  function array(shape) {
212
+ var _a;
172
213
  var typename = "Array<".concat(shape.typename, ">");
173
- return {
174
- typename: typename,
175
- inner: shape,
176
- primitive: undefined,
177
- check: function (input) {
178
- if (!isArray(input)) {
179
- return false;
180
- }
181
- return input.every(function (entry) {
182
- if (!shape.check(entry)) {
183
- return false;
214
+ return _a = {
215
+ typename: typename,
216
+ check: function (input) {
217
+ function innerMatches(x) {
218
+ return x.every(function (entry) { return shape.check(entry); });
184
219
  }
185
- return true;
186
- }) && this.predicates.every(function (p) { return p(input); });
220
+ return isArray(input) && innerMatches(input) &&
221
+ this[Predicates].every(function (p) { return p.predicate(input); });
222
+ }
187
223
  },
188
- predicates: [],
189
- where: function (predicate) {
190
- this.predicates.push(predicate);
191
- return this;
224
+ _a[Predicates] = [],
225
+ _a.where = function (predicate, description) {
226
+ var _a;
227
+ return __assign(__assign({}, this), (_a = {}, _a[Predicates] = __spreadArray(__spreadArray([], this[Predicates], true), [{ predicate: predicate, description: description }], false), _a));
192
228
  },
193
- and: function (other) {
229
+ _a.and = function (other) {
194
230
  return makeIntersection(this, other);
195
231
  },
196
- or: function (other) {
232
+ _a.or = function (other) {
197
233
  return makeUnion(this, other);
198
234
  },
199
- };
235
+ _a;
200
236
  }
201
237
  /**
202
238
  * Creates an object shape.
203
239
  *
204
240
  * @example
241
+ * ```typescript
205
242
  * const Group = Shapes.object({
206
243
  * id: Shapes.number(),
207
244
  * name: Shapes.string(),
@@ -214,54 +251,122 @@ function array(shape) {
214
251
  * Shapes.array(Group)
215
252
  * )
216
253
  * });
254
+ * ```
217
255
  */
256
+ // deno-lint-ignore no-explicit-any
218
257
  function object(properties, options) {
219
- var allowUnknownProperties = options === null || options === void 0 ? void 0 : options.allowUnknownProperties;
220
- return {
221
- typename: "Object",
222
- allowUnknownProperties: allowUnknownProperties,
223
- check: function (input) {
224
- // Check that input is an object
225
- if (!isObject(input)) {
226
- return false;
227
- }
228
- // Check for unknown properties
229
- if (!this.allowUnknownProperties) {
230
- for (var inputProperty in input) {
231
- if (!(inputProperty in this.properties)) {
232
- return false;
258
+ var _a;
259
+ var _b;
260
+ var allowUnknownProperties = (_b = options === null || options === void 0 ? void 0 : options.allowUnknownProperties) !== null && _b !== void 0 ? _b : false;
261
+ return _a = {
262
+ typename: "Object"
263
+ },
264
+ _a[AllowUnknownProperties] = allowUnknownProperties,
265
+ _a.check = function (input) {
266
+ var _this = this;
267
+ var matchesShape = function (input) {
268
+ // Check that input is an object
269
+ if (!isObject(input)) {
270
+ return false;
271
+ }
272
+ // Check for unknown properties
273
+ if (!_this[AllowUnknownProperties]) {
274
+ for (var inputProperty in input) {
275
+ if (!(inputProperty in _this[Properties])) {
276
+ return false;
277
+ }
233
278
  }
234
279
  }
235
- }
236
- // Check required properties and their types
237
- for (var property in properties) {
238
- var field = input[property];
239
- var propertyShape = properties[property];
240
- // Property presence check
241
- if (!(property in input) && !propertyShape.optional) {
242
- return false;
280
+ // Check required properties and their types
281
+ for (var property in _this[Properties]) {
282
+ var field = input[property];
283
+ var propertyShape = _this[Properties][property];
284
+ if (isUndefined(propertyShape))
285
+ return false;
286
+ // Property presence check
287
+ if (!(property in input) && !propertyShape.check(undefined)) {
288
+ return false;
289
+ }
290
+ // Property type check
291
+ if (!propertyShape.check(field)) {
292
+ return false;
293
+ }
243
294
  }
244
- // Property type check
245
- if (!propertyShape.check(field)) {
246
- return false;
295
+ return true;
296
+ };
297
+ return matchesShape(input) &&
298
+ this[Predicates].every(function (p) { return p.predicate(input); });
299
+ },
300
+ _a[Properties] = __assign(__assign({}, properties), options === null || options === void 0 ? void 0 : options.additionalPermittedProperties),
301
+ _a[Predicates] = [],
302
+ _a.where = function (predicate, description) {
303
+ var _a;
304
+ return __assign(__assign({}, this), (_a = {}, _a[Predicates] = __spreadArray(__spreadArray([], this[Predicates], true), [{ predicate: predicate, description: description }], false), _a));
305
+ },
306
+ _a.and = function (other) {
307
+ return makeIntersection(this, other);
308
+ },
309
+ _a.or = function (other) {
310
+ return makeUnion(this, other);
311
+ },
312
+ _a;
313
+ }
314
+ function tuple(shapes, rest) {
315
+ var _a;
316
+ var typename = "[".concat(shapes.map(function (s) { return s.typename; }).join(", "), "]");
317
+ return _a = {
318
+ typename: typename
319
+ },
320
+ _a[Predicates] = [],
321
+ _a.check = function (x) {
322
+ if (!isArray(x))
323
+ return false;
324
+ var xMightHaveRest = x.length > shapes.length;
325
+ var haveRestShape = rest !== undefined;
326
+ if (haveRestShape && xMightHaveRest) {
327
+ for (var idx = shapes.length; idx < x.length; idx++) {
328
+ if (!rest.check(x[idx]))
329
+ return false;
247
330
  }
248
331
  }
249
- return this.predicates.every(function (p) { return p(input); });
332
+ return shapes.every(function (shape, idx) { return shape.check(x[idx]); });
250
333
  },
251
- properties: __assign(__assign({}, properties), options === null || options === void 0 ? void 0 : options.additionalPermittedProperties),
252
- primitive: undefined,
253
- predicates: [],
254
- where: function (predicate) {
255
- this.predicates.push(predicate);
256
- return this;
334
+ _a.where = function (predicate, description) {
335
+ var _a;
336
+ return __assign(__assign({}, this), (_a = {}, _a[Predicates] = __spreadArray(__spreadArray([], this[Predicates], true), [{ predicate: predicate, description: description }], false), _a));
257
337
  },
258
- and: function (other) {
338
+ _a.and = function (other) {
259
339
  return makeIntersection(this, other);
260
340
  },
261
- or: function (other) {
341
+ _a.or = function (other) {
262
342
  return makeUnion(this, other);
263
343
  },
264
- };
344
+ _a;
345
+ }
346
+ function enumOf() {
347
+ var _a;
348
+ var values = [];
349
+ for (var _i = 0; _i < arguments.length; _i++) {
350
+ values[_i] = arguments[_i];
351
+ }
352
+ return _a = {
353
+ typename: "".concat(values.map(function (v) { return JSON.stringify(v); }).join(" | "))
354
+ },
355
+ _a[Predicates] = [],
356
+ _a.check = function (x) {
357
+ return (isNumber(x) || isString(x)) && values.includes(x);
358
+ },
359
+ _a.where = function (predicate, description) {
360
+ var _a;
361
+ return __assign(__assign({}, this), (_a = {}, _a[Predicates] = __spreadArray(__spreadArray([], this[Predicates], true), [{ predicate: predicate, description: description }], false), _a));
362
+ },
363
+ _a.and = function (other) {
364
+ return makeIntersection(this, other);
365
+ },
366
+ _a.or = function (other) {
367
+ return makeUnion(this, other);
368
+ },
369
+ _a;
265
370
  }
266
371
  /// TYPE MODIFIERS
267
372
  /**
@@ -269,50 +374,12 @@ function object(properties, options) {
269
374
  * `Shapes.optional(T)` corresponds to `T | undefined`
270
375
  */
271
376
  function optional(shape) {
272
- return {
273
- typename: "".concat(shape.typename, " | undefined"),
274
- inner: shape,
275
- optional: true,
276
- primitive: undefined,
277
- predicates: [],
278
- check: function (input) {
279
- return isUndefined(input) || shape.check(input);
280
- },
281
- where: function (predicate) {
282
- this.inner.predicates.push(predicate);
283
- return this;
284
- },
285
- and: function (other) {
286
- return makeIntersection(this, other);
287
- },
288
- or: function (other) {
289
- return makeUnion(this, other);
290
- },
291
- };
377
+ return makeUnion(shape, literal(undefined));
292
378
  }
293
379
  /**
294
380
  * Creates a shape representing a nullable type.
295
381
  * `Shapes.nullable(T)` corresponds to `T | null`
296
382
  */
297
383
  function nullable(shape) {
298
- var nullShape = {
299
- typename: "".concat(shape.typename, " | null"),
300
- check: function (input) {
301
- return isNull(input) || shape.check(input);
302
- },
303
- inner: shape,
304
- primitive: undefined,
305
- predicates: [],
306
- where: function (p) {
307
- this.inner.predicates.push(p);
308
- return this;
309
- },
310
- and: function (other) {
311
- return makeIntersection(this, other);
312
- },
313
- or: function (other) {
314
- return makeUnion(this, other);
315
- },
316
- };
317
- return nullShape;
384
+ return makeUnion(shape, literal(null));
318
385
  }
package/package.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "name": "@jakobkg/shapes-ts",
3
- "version": "0.1.1",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "https://codeberg.org/jakobkg/shapes-ts"
6
+ },
7
+ "homepage": "https://codeberg.org/jakobkg/shapes-ts#readme",
8
+ "license": "BSD-3-Clause",
9
+ "version": "0.3.0",
4
10
  "private": false,
5
11
  "main": "dist/index.js",
6
12
  "scripts": {