@oscarpalmer/jhunal 0.7.0 → 0.9.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/dist/constants.js CHANGED
@@ -1,3 +1,6 @@
1
+ const EXPRESSION_HAS_NUMBER = /\d+/;
2
+ const EXPRESSION_INDEX = /\.\d+$/;
3
+ const EXPRESSION_PROPERTY = /\.\$(required|type)(\.|$)/;
1
4
  const PROPERTY_REQUIRED = "$required";
2
5
  const PROPERTY_TYPE = "$type";
3
6
  const SCHEMATIC_NAME = "$schematic";
@@ -8,14 +11,12 @@ const TYPE_ALL = new Set([
8
11
  "bigint",
9
12
  "boolean",
10
13
  "date",
11
- "date-like",
12
14
  "function",
13
15
  "null",
14
16
  "number",
15
- "numerical",
16
17
  "string",
17
18
  "symbol",
18
19
  TYPE_OBJECT,
19
20
  TYPE_UNDEFINED
20
21
  ]);
21
- export { PROPERTY_REQUIRED, PROPERTY_TYPE, SCHEMATIC_NAME, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED };
22
+ export { EXPRESSION_HAS_NUMBER, EXPRESSION_INDEX, EXPRESSION_PROPERTY, PROPERTY_REQUIRED, PROPERTY_TYPE, SCHEMATIC_NAME, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED };
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
+ import { isInstance } from "./is.js";
1
2
  import { schematic } from "./schematic.js";
2
- export { schematic };
3
+ export { isInstance, schematic };
package/dist/is.js CHANGED
@@ -1,10 +1,12 @@
1
- import { SCHEMATIC_NAME } from "./constants.js";
2
- function isDateLike(value) {
3
- if (value instanceof Date) return true;
4
- if (typeof value === "number") return value >= -864e13 && value <= 864e13;
5
- return typeof value === "string" && !Number.isNaN(Date.parse(value));
1
+ import "./constants.js";
2
+ import { isConstructor } from "@oscarpalmer/atoms/is";
3
+ function isInstance(constructor) {
4
+ if (!isConstructor(constructor)) throw new TypeError("Expected a constructor function");
5
+ return (value) => {
6
+ return value instanceof constructor;
7
+ };
6
8
  }
7
9
  function isSchematic(value) {
8
10
  return typeof value === "object" && value !== null && "$schematic" in value && value["$schematic"] === true;
9
11
  }
10
- export { isDateLike, isSchematic };
12
+ export { isInstance, isSchematic };
@@ -1,23 +1,30 @@
1
- const PROPERTY_REQUIRED = "$required";
2
- const PROPERTY_TYPE = "$type";
3
- const SCHEMATIC_NAME = "$schematic";
4
- const TYPE_OBJECT = "object";
5
- const TYPE_UNDEFINED = "undefined";
6
- const TYPE_ALL = new Set([
7
- "array",
8
- "bigint",
9
- "boolean",
10
- "date",
11
- "date-like",
12
- "function",
13
- "null",
14
- "number",
15
- "numerical",
16
- "string",
17
- "symbol",
18
- TYPE_OBJECT,
19
- TYPE_UNDEFINED
20
- ]);
1
+ /**
2
+ * Is the value an array or a record?
3
+ * @param value Value to check
4
+ * @returns `true` if the value is an array or a record, otherwise `false`
5
+ */
6
+ function isArrayOrPlainObject(value) {
7
+ return Array.isArray(value) || isPlainObject(value);
8
+ }
9
+ /**
10
+ * Is the value a constructor function?
11
+ * @param value Value to check
12
+ * @returns `true` if the value is a constructor function, otherwise `false`
13
+ */
14
+ function isConstructor(value) {
15
+ return typeof value === "function" && value.prototype?.constructor === value;
16
+ }
17
+ /**
18
+ * Is the value a plain object?
19
+ * @param value Value to check
20
+ * @returns `true` if the value is a plain object, otherwise `false`
21
+ */
22
+ function isPlainObject(value) {
23
+ if (value === null || typeof value !== "object") return false;
24
+ if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
25
+ const prototype = Object.getPrototypeOf(value);
26
+ return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
27
+ }
21
28
  function compact(array, strict) {
22
29
  if (!Array.isArray(array)) return [];
23
30
  if (strict === true) return array.filter(Boolean);
@@ -29,6 +36,11 @@ function compact(array, strict) {
29
36
  }
30
37
  return compacted;
31
38
  }
39
+ /**
40
+ * Get the string value from any value
41
+ * @param value Original value
42
+ * @returns String representation of the value
43
+ */
32
44
  function getString(value) {
33
45
  if (typeof value === "string") return value;
34
46
  if (value == null) return "";
@@ -37,31 +49,45 @@ function getString(value) {
37
49
  const asString = String(value.valueOf?.() ?? value);
38
50
  return asString.startsWith("[object ") ? JSON.stringify(value) : asString;
39
51
  }
52
+ /**
53
+ * Join an array of values into a string
54
+ * @param value Array of values
55
+ * @param delimiter Delimiter to use between values
56
+ * @returns Joined string
57
+ */
40
58
  function join(value, delimiter) {
41
59
  return compact(value).map(getString).join(typeof delimiter === "string" ? delimiter : "");
42
60
  }
43
- function isArrayOrPlainObject(value) {
44
- return Array.isArray(value) || isPlainObject(value);
61
+ const EXPRESSION_HAS_NUMBER = /\d+/;
62
+ const EXPRESSION_INDEX = /\.\d+$/;
63
+ const EXPRESSION_PROPERTY = /\.\$(required|type)(\.|$)/;
64
+ const PROPERTY_REQUIRED = "$required";
65
+ const PROPERTY_TYPE = "$type";
66
+ const SCHEMATIC_NAME = "$schematic";
67
+ const TYPE_OBJECT = "object";
68
+ const TYPE_UNDEFINED = "undefined";
69
+ const TYPE_ALL = new Set([
70
+ "array",
71
+ "bigint",
72
+ "boolean",
73
+ "date",
74
+ "function",
75
+ "null",
76
+ "number",
77
+ "string",
78
+ "symbol",
79
+ TYPE_OBJECT,
80
+ TYPE_UNDEFINED
81
+ ]);
82
+ function isInstance(constructor) {
83
+ if (!isConstructor(constructor)) throw new TypeError("Expected a constructor function");
84
+ return (value) => {
85
+ return value instanceof constructor;
86
+ };
45
87
  }
46
- function isPlainObject(value) {
47
- if (value === null || typeof value !== "object") return false;
48
- if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
49
- const prototype = Object.getPrototypeOf(value);
50
- return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
88
+ function isSchematic(value) {
89
+ return typeof value === "object" && value !== null && SCHEMATIC_NAME in value && value[SCHEMATIC_NAME] === true;
51
90
  }
52
- new Set([
53
- Int8Array,
54
- Uint8Array,
55
- Uint8ClampedArray,
56
- Int16Array,
57
- Uint16Array,
58
- Int32Array,
59
- Uint32Array,
60
- Float32Array,
61
- Float64Array,
62
- BigInt64Array,
63
- BigUint64Array
64
- ]);
65
91
  function flattenObject(value, depth, smushed, prefix) {
66
92
  if (depth >= MAX_DEPTH) return {};
67
93
  if (smushed.has(value)) return smushed.get(value);
@@ -86,18 +112,15 @@ function flattenObject(value, depth, smushed, prefix) {
86
112
  smushed.set(value, flattened);
87
113
  return flattened;
88
114
  }
115
+ /**
116
+ * Smush an object into a flat object that uses dot notation keys
117
+ * @param value Object to smush
118
+ * @returns Smushed object with dot notation keys
119
+ */
89
120
  function smush(value) {
90
121
  return typeof value === "object" && value !== null ? flattenObject(value, 0, /* @__PURE__ */ new WeakMap()) : {};
91
122
  }
92
123
  var MAX_DEPTH = 100;
93
- function isDateLike(value) {
94
- if (value instanceof Date) return true;
95
- if (typeof value === "number") return value >= -864e13 && value <= 864e13;
96
- return typeof value === "string" && !Number.isNaN(Date.parse(value));
97
- }
98
- function isSchematic(value) {
99
- return typeof value === "object" && value !== null && SCHEMATIC_NAME in value && value[SCHEMATIC_NAME] === true;
100
- }
101
124
  function addPropertyType(to, key, values, required) {
102
125
  if (to.keys.set.has(key)) {
103
126
  const property = to.properties[key];
@@ -112,17 +135,33 @@ function addPropertyType(to, key, values, required) {
112
135
  }
113
136
  if (!required && !to.properties[key].types.includes(TYPE_UNDEFINED)) to.properties[key].types.push(TYPE_UNDEFINED);
114
137
  }
138
+ function getSchema(schema) {
139
+ const validated = {
140
+ enabled: false,
141
+ keys: {
142
+ array: [],
143
+ set: /* @__PURE__ */ new Set()
144
+ },
145
+ properties: {}
146
+ };
147
+ return typeof schema === "object" && schema !== null ? getValidatedSchema(schema, validated) : validated;
148
+ }
115
149
  function getTypes(value, validated, prefix) {
116
150
  const propertyTypes = [];
117
151
  const values = Array.isArray(value) ? value : [value];
118
152
  const { length } = values;
119
153
  for (let index = 0; index < length; index += 1) {
120
154
  const type = values[index];
121
- if (isSchematic(type) || typeof type === "string" && TYPE_ALL.has(type)) {
155
+ const typeOfType = typeof type;
156
+ if (isSchematic(type) || typeOfType === "string" && TYPE_ALL.has(type)) {
122
157
  propertyTypes.push(type);
123
158
  continue;
124
159
  }
125
- if (typeof type !== "object" || type === null) continue;
160
+ if (typeOfType === "function") {
161
+ propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
162
+ continue;
163
+ }
164
+ if (typeOfType !== "object" || type === null) continue;
126
165
  if (PROPERTY_TYPE in type) {
127
166
  propertyTypes.push(...getTypes(type[PROPERTY_TYPE], validated, prefix));
128
167
  continue;
@@ -144,8 +183,8 @@ function getValidatedSchema(schema, validated, prefix) {
144
183
  const key = keys[index];
145
184
  const value = smushed[key];
146
185
  if (Array.isArray(value)) arrayKeys.add(key);
147
- if (/\.\$(required|type)(\.|$)/.test(key)) continue;
148
- if (/\d+/.test(key) && arrayKeys.has(key.replace(/\.\d+$/, ""))) continue;
186
+ if (EXPRESSION_PROPERTY.test(key)) continue;
187
+ if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
149
188
  let required = true;
150
189
  if (typeof value === "object" && value !== null && PROPERTY_REQUIRED in value) required = typeof value[PROPERTY_REQUIRED] === "boolean" ? value[PROPERTY_REQUIRED] : true;
151
190
  const prefixedKey = `${prefix}${key}`;
@@ -155,18 +194,12 @@ function getValidatedSchema(schema, validated, prefix) {
155
194
  if (noPrefix) validated.keys.array.sort();
156
195
  return validated;
157
196
  }
158
- function validateSchema(schema) {
159
- const validated = {
160
- keys: {
161
- array: [],
162
- set: /* @__PURE__ */ new Set()
163
- },
164
- properties: {}
165
- };
166
- return typeof schema === "object" && schema !== null ? getValidatedSchema(schema, validated) : validated;
167
- }
168
197
  function validateType(type, value) {
169
- return typeof type === "string" ? validators[type](value) : type.is(value);
198
+ switch (true) {
199
+ case typeof type === "function": return type(value);
200
+ case typeof type === "string": return validators[type](value);
201
+ default: return type.is(value);
202
+ }
170
203
  }
171
204
  function validateValue(validated, obj) {
172
205
  if (typeof obj !== "object" || obj === null) return false;
@@ -189,7 +222,7 @@ function validateValue(validated, obj) {
189
222
  for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
190
223
  const type = property.types[typeIndex];
191
224
  if (validateType(type, value)) {
192
- if (type !== TYPE_OBJECT) ignore.add(key);
225
+ if (type !== "object") ignore.add(key);
193
226
  continue outer;
194
227
  }
195
228
  }
@@ -203,32 +236,35 @@ const validators = {
203
236
  bigint: (value) => typeof value === "bigint",
204
237
  boolean: (value) => typeof value === "boolean",
205
238
  date: (value) => value instanceof Date,
206
- "date-like": isDateLike,
207
239
  function: (value) => typeof value === "function",
208
240
  null: (value) => value === null,
209
241
  number: (value) => typeof value === "number" && !Number.isNaN(value),
210
- numerical: (value) => validators.bigint(value) || validators.number(value),
211
242
  object: (value) => typeof value === "object" && value !== null,
212
243
  string: (value) => typeof value === "string",
213
244
  symbol: (value) => typeof value === "symbol",
214
245
  undefined: (value) => value === void 0
215
246
  };
247
+ /**
248
+ * A schematic for validating objects
249
+ */
216
250
  var Schematic = class {
217
251
  #schema;
218
- #validatable;
219
- get validatable() {
220
- return this.#validatable;
252
+ get enabled() {
253
+ return this.#schema.enabled;
221
254
  }
222
255
  constructor(schema) {
223
256
  Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
224
- this.#schema = validateSchema(schema);
225
- this.#validatable = this.#schema.keys.array.length > 0;
257
+ this.#schema = getSchema(schema);
258
+ this.#schema.enabled = this.#schema.keys.array.length > 0;
226
259
  }
260
+ /**
261
+ * Does the value match the schema?
262
+ */
227
263
  is(value) {
228
- return this.#validatable && validateValue(this.#schema, value);
264
+ return this.#schema.enabled && validateValue(this.#schema, value);
229
265
  }
230
266
  };
231
267
  function schematic(schema) {
232
268
  return new Schematic(schema);
233
269
  }
234
- export { schematic };
270
+ export { isInstance, schematic };
package/dist/schematic.js CHANGED
@@ -1,19 +1,24 @@
1
1
  import { SCHEMATIC_NAME } from "./constants.js";
2
- import { validateSchema } from "./validation/schema.validation.js";
2
+ import { getSchema } from "./validation/schema.validation.js";
3
3
  import { validateValue } from "./validation/value.validation.js";
4
+ /**
5
+ * A schematic for validating objects
6
+ */
4
7
  var Schematic = class {
5
8
  #schema;
6
- #validatable;
7
- get validatable() {
8
- return this.#validatable;
9
+ get enabled() {
10
+ return this.#schema.enabled;
9
11
  }
10
12
  constructor(schema) {
11
13
  Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
12
- this.#schema = validateSchema(schema);
13
- this.#validatable = this.#schema.keys.array.length > 0;
14
+ this.#schema = getSchema(schema);
15
+ this.#schema.enabled = this.#schema.keys.array.length > 0;
14
16
  }
17
+ /**
18
+ * Does the value match the schema?
19
+ */
15
20
  is(value) {
16
- return this.#validatable && validateValue(this.#schema, value);
21
+ return this.#schema.enabled && validateValue(this.#schema, value);
17
22
  }
18
23
  };
19
24
  function schematic(schema) {
@@ -1,5 +1,6 @@
1
- import { PROPERTY_REQUIRED, PROPERTY_TYPE, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED } from "../constants.js";
2
- import { isSchematic } from "../is.js";
1
+ import { EXPRESSION_HAS_NUMBER, EXPRESSION_INDEX, EXPRESSION_PROPERTY, PROPERTY_REQUIRED, PROPERTY_TYPE, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED } from "../constants.js";
2
+ import { isInstance, isSchematic } from "../is.js";
3
+ import { isConstructor } from "@oscarpalmer/atoms/is";
3
4
  import { smush } from "@oscarpalmer/atoms/value";
4
5
  function addPropertyType(to, key, values, required) {
5
6
  if (to.keys.set.has(key)) {
@@ -15,17 +16,33 @@ function addPropertyType(to, key, values, required) {
15
16
  }
16
17
  if (!required && !to.properties[key].types.includes("undefined")) to.properties[key].types.push(TYPE_UNDEFINED);
17
18
  }
19
+ function getSchema(schema) {
20
+ const validated = {
21
+ enabled: false,
22
+ keys: {
23
+ array: [],
24
+ set: /* @__PURE__ */ new Set()
25
+ },
26
+ properties: {}
27
+ };
28
+ return typeof schema === "object" && schema !== null ? getValidatedSchema(schema, validated) : validated;
29
+ }
18
30
  function getTypes(value, validated, prefix) {
19
31
  const propertyTypes = [];
20
32
  const values = Array.isArray(value) ? value : [value];
21
33
  const { length } = values;
22
34
  for (let index = 0; index < length; index += 1) {
23
35
  const type = values[index];
24
- if (isSchematic(type) || typeof type === "string" && TYPE_ALL.has(type)) {
36
+ const typeOfType = typeof type;
37
+ if (isSchematic(type) || typeOfType === "string" && TYPE_ALL.has(type)) {
25
38
  propertyTypes.push(type);
26
39
  continue;
27
40
  }
28
- if (typeof type !== "object" || type === null) continue;
41
+ if (typeOfType === "function") {
42
+ propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
43
+ continue;
44
+ }
45
+ if (typeOfType !== "object" || type === null) continue;
29
46
  if ("$type" in type) {
30
47
  propertyTypes.push(...getTypes(type[PROPERTY_TYPE], validated, prefix));
31
48
  continue;
@@ -47,8 +64,8 @@ function getValidatedSchema(schema, validated, prefix) {
47
64
  const key = keys[index];
48
65
  const value = smushed[key];
49
66
  if (Array.isArray(value)) arrayKeys.add(key);
50
- if (/\.\$(required|type)(\.|$)/.test(key)) continue;
51
- if (/\d+/.test(key) && arrayKeys.has(key.replace(/\.\d+$/, ""))) continue;
67
+ if (EXPRESSION_PROPERTY.test(key)) continue;
68
+ if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
52
69
  let required = true;
53
70
  if (typeof value === "object" && value !== null && "$required" in value) required = typeof value["$required"] === "boolean" ? value[PROPERTY_REQUIRED] : true;
54
71
  const prefixedKey = `${prefix}${key}`;
@@ -58,14 +75,4 @@ function getValidatedSchema(schema, validated, prefix) {
58
75
  if (noPrefix) validated.keys.array.sort();
59
76
  return validated;
60
77
  }
61
- function validateSchema(schema) {
62
- const validated = {
63
- keys: {
64
- array: [],
65
- set: /* @__PURE__ */ new Set()
66
- },
67
- properties: {}
68
- };
69
- return typeof schema === "object" && schema !== null ? getValidatedSchema(schema, validated) : validated;
70
- }
71
- export { validateSchema };
78
+ export { getSchema };
@@ -1,8 +1,11 @@
1
- import { TYPE_OBJECT, TYPE_UNDEFINED } from "../constants.js";
2
- import { isDateLike } from "../is.js";
1
+ import "../constants.js";
3
2
  import { smush } from "@oscarpalmer/atoms/value";
4
3
  function validateType(type, value) {
5
- return typeof type === "string" ? validators[type](value) : type.is(value);
4
+ switch (true) {
5
+ case typeof type === "function": return type(value);
6
+ case typeof type === "string": return validators[type](value);
7
+ default: return type.is(value);
8
+ }
6
9
  }
7
10
  function validateValue(validated, obj) {
8
11
  if (typeof obj !== "object" || obj === null) return false;
@@ -39,11 +42,9 @@ var validators = {
39
42
  bigint: (value) => typeof value === "bigint",
40
43
  boolean: (value) => typeof value === "boolean",
41
44
  date: (value) => value instanceof Date,
42
- "date-like": isDateLike,
43
45
  function: (value) => typeof value === "function",
44
46
  null: (value) => value === null,
45
47
  number: (value) => typeof value === "number" && !Number.isNaN(value),
46
- numerical: (value) => validators.bigint(value) || validators.number(value),
47
48
  object: (value) => typeof value === "object" && value !== null,
48
49
  string: (value) => typeof value === "string",
49
50
  symbol: (value) => typeof value === "symbol",
package/package.json CHANGED
@@ -4,26 +4,26 @@
4
4
  "url": "https://oscarpalmer.se"
5
5
  },
6
6
  "dependencies": {
7
- "@oscarpalmer/atoms": "^0.123"
7
+ "@oscarpalmer/atoms": "^0.139"
8
8
  },
9
9
  "description": "Flies free beneath the glistening moons…",
10
10
  "devDependencies": {
11
- "@types/node": "^25",
11
+ "@types/node": "^25.3",
12
12
  "@vitest/coverage-istanbul": "^4",
13
- "jsdom": "^27.4",
14
- "oxfmt": "^0.21",
15
- "oxlint": "^1.36",
16
- "rolldown": "1.0.0-beta.57",
13
+ "jsdom": "^28.1",
14
+ "oxfmt": "^0.34",
15
+ "oxlint": "^1.49",
16
+ "rolldown": "1.0.0-rc.5",
17
17
  "tslib": "^2.8",
18
18
  "typescript": "^5.9",
19
- "vite": "8.0.0-beta.5",
19
+ "vite": "8.0.0-beta.15",
20
20
  "vitest": "^4"
21
21
  },
22
22
  "exports": {
23
23
  "./package.json": "./package.json",
24
24
  ".": {
25
- "types": "./types/index.d.ts",
26
- "default": "./dist/index.js"
25
+ "types": "./types/index.d.ts",
26
+ "default": "./dist/index.js"
27
27
  }
28
28
  },
29
29
  "files": ["dist", "src", "types"],
@@ -45,5 +45,5 @@
45
45
  },
46
46
  "type": "module",
47
47
  "types": "./types/index.d.ts",
48
- "version": "0.7.0"
48
+ "version": "0.9.0"
49
49
  }
package/src/constants.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import type {Values} from './models';
2
2
 
3
+ export const EXPRESSION_HAS_NUMBER = /\d+/;
4
+
5
+ export const EXPRESSION_INDEX = /\.\d+$/;
6
+
7
+ export const EXPRESSION_PROPERTY = /\.\$(required|type)(\.|$)/;
8
+
3
9
  export const PROPERTY_REQUIRED = '$required';
4
10
 
5
11
  export const PROPERTY_TYPE = '$type';
@@ -15,11 +21,9 @@ export const TYPE_ALL = new Set<keyof Values>([
15
21
  'bigint',
16
22
  'boolean',
17
23
  'date',
18
- 'date-like',
19
24
  'function',
20
25
  'null',
21
26
  'number',
22
- 'numerical',
23
27
  'string',
24
28
  'symbol',
25
29
  TYPE_OBJECT,
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
+ export {isInstance} from './is';
1
2
  export type {Schema, TypedSchema} from './models';
2
3
  export {schematic, type Schematic} from './schematic';
package/src/is.ts CHANGED
@@ -1,16 +1,18 @@
1
+ import {isConstructor} from '@oscarpalmer/atoms/is';
1
2
  import {SCHEMATIC_NAME} from './constants';
3
+ import type {Constructor} from './models';
2
4
  import type {Schematic} from './schematic';
3
5
 
4
- export function isDateLike(value: unknown): value is Date {
5
- if (value instanceof Date) {
6
- return true;
6
+ export function isInstance<Instance>(
7
+ constructor: Constructor<Instance>,
8
+ ): (value: unknown) => value is Instance {
9
+ if (!isConstructor(constructor)) {
10
+ throw new TypeError('Expected a constructor function');
7
11
  }
8
12
 
9
- if (typeof value === 'number') {
10
- return value >= -8_640e12 && value <= 8_640e12;
11
- }
12
-
13
- return typeof value === 'string' && !Number.isNaN(Date.parse(value));
13
+ return (value: unknown): value is Instance => {
14
+ return value instanceof constructor;
15
+ };
14
16
  }
15
17
 
16
18
  export function isSchematic(value: unknown): value is Schematic<never> {
package/src/models.ts CHANGED
@@ -1,58 +1,67 @@
1
1
  import type {PlainObject, Simplify} from '@oscarpalmer/atoms/models';
2
2
  import type {Schematic} from './schematic';
3
3
 
4
+ export type Constructor<Instance = any> = new (...args: any[]) => Instance;
5
+
6
+ type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
7
+ infer Head,
8
+ ...infer Tail,
9
+ ]
10
+ ? Head extends Seen[number]
11
+ ? DeduplicateTuple<Tail, Seen>
12
+ : DeduplicateTuple<Tail, [...Seen, Head]>
13
+ : Seen;
14
+
4
15
  /**
5
16
  * Infer the TypeScript type from a schema definition
6
17
  */
7
18
  export type Infer<Model extends Schema> = Simplify<
8
19
  {
9
- [Key in InferRequiredKeys<Model>]: Model[Key] extends Schematic<infer SchematicModel>
10
- ? SchematicModel
11
- : Model[Key] extends SchemaProperty
12
- ? InferPropertyValue<Model[Key]>
13
- : InferEntryValue<Model[Key]>;
20
+ [Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]>;
14
21
  } & {
15
- [Key in InferOptionalKeys<Model>]?: Model[Key] extends Schematic<infer SchematicModel>
16
- ? SchematicModel
17
- : Model[Key] extends SchemaProperty
18
- ? InferPropertyValue<Model[Key]>
19
- : never;
22
+ [Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]>;
20
23
  }
21
24
  >;
22
25
 
23
- type InferEntryValue<Value> = Value extends ValueName
24
- ? Values[Value]
25
- : Value extends ValueName[]
26
- ? Values[Value[number]]
27
- : never;
28
-
29
26
  type InferOptionalKeys<Model extends Schema> = keyof {
30
27
  [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never;
31
28
  };
32
29
 
33
30
  type InferPropertyType<Value> = Value extends (infer Item)[]
34
- ? InferTypeValue<Item>[]
35
- : InferTypeValue<Value>;
31
+ ? InferPropertyValue<Item>
32
+ : InferPropertyValue<Value>;
36
33
 
37
- type InferPropertyValue<Prop> = Prop extends SchemaProperty
38
- ? InferPropertyType<Prop['$type']>
39
- : never;
34
+ type InferPropertyValue<Value> =
35
+ Value extends Constructor<infer Instance>
36
+ ? Instance
37
+ : Value extends Schematic<infer Model>
38
+ ? Model
39
+ : Value extends ValueName
40
+ ? Values[Value & ValueName]
41
+ : Value extends Schema
42
+ ? Infer<Value>
43
+ : never;
40
44
 
41
45
  type InferRequiredKeys<Model extends Schema> = keyof {
42
46
  [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
43
47
  };
44
48
 
45
- type InferTypeValue<Value> = Value extends SchemaPropertyType
46
- ? Value extends Schema
47
- ? Infer<Value>
49
+ type InferSchemaEntry<Value> = Value extends (infer Item)[]
50
+ ? InferSchemaEntryValue<Item>
51
+ : InferSchemaEntryValue<Value>;
52
+
53
+ type InferSchemaEntryValue<Value> =
54
+ Value extends Constructor<infer Instance>
55
+ ? Instance
48
56
  : Value extends Schematic<infer Model>
49
57
  ? Model
50
- : Value extends ValueName
51
- ? Values[Value]
52
- : Value extends (infer Nested)[]
53
- ? InferTypeValue<Nested>[]
54
- : Value
55
- : never;
58
+ : Value extends SchemaProperty
59
+ ? InferPropertyType<Value['$type']>
60
+ : Value extends ValueName
61
+ ? Values[Value & ValueName]
62
+ : Value extends Schema
63
+ ? Infer<Value>
64
+ : never;
56
65
 
57
66
  type IsOptionalProperty<Value> = Value extends SchemaProperty
58
67
  ? Value['$required'] extends false
@@ -65,6 +74,14 @@ type LastOfUnion<Value> =
65
74
  ? Item
66
75
  : never;
67
76
 
77
+ type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail]
78
+ ? [ToValueType<Head>, ...MapToValueTypes<Tail>]
79
+ : [];
80
+
81
+ type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail]
82
+ ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>]
83
+ : [];
84
+
68
85
  /**
69
86
  * A nested schema with optional requirement flag
70
87
  */
@@ -83,10 +100,10 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
83
100
  */
84
101
  export type Schema = SchemaIndex;
85
102
 
86
- type SchemaEntry = NestedSchema | SchemaProperty | Schematic<unknown> | ValueName | ValueName[];
103
+ type SchemaEntry = Constructor | NestedSchema | SchemaProperty | Schematic<unknown> | ValueName;
87
104
 
88
105
  interface SchemaIndex {
89
- [key: string]: SchemaEntry;
106
+ [key: string]: SchemaEntry | SchemaEntry[];
90
107
  }
91
108
 
92
109
  /**
@@ -97,27 +114,67 @@ export type SchemaProperty = {
97
114
  $type: SchemaPropertyType | SchemaPropertyType[];
98
115
  };
99
116
 
100
- type SchemaPropertyType = Schema | Schematic<unknown> | ValueName;
117
+ type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
101
118
 
102
- type ToSchemaPropertyType<Value> = UnwrapSingle<UnionToTuple<ToSchemaPropertyTypeEach<Value>>>;
119
+ type ToSchemaPropertyType<Value> = UnwrapSingle<
120
+ DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>
121
+ >;
103
122
 
104
123
  type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject
105
124
  ? TypedSchema<Value>
106
- : ToValueName<Value>;
107
-
108
- type ToSchemaType<Value> = UnwrapSingle<UnionToTuple<ToValueName<Value>>>;
109
-
110
- type ToValueName<Value> = {
111
- [Key in Exclude<ValueName, ValueNameFallbacks>]: Value extends Values[Key] ? Key : never;
112
- }[Exclude<ValueName, ValueNameFallbacks>] extends infer Specific
113
- ? Specific extends never
114
- ? {[Key in ValueNameFallbacks]: Value extends Values[Key] ? Key : never}[ValueNameFallbacks]
115
- : Specific
116
- : never;
125
+ : ToValueType<Value>;
126
+
127
+ type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
128
+
129
+ type ToValueType<Value> = Value extends unknown[]
130
+ ? 'array'
131
+ : Value extends bigint
132
+ ? 'bigint'
133
+ : Value extends boolean
134
+ ? 'boolean'
135
+ : Value extends Date
136
+ ? 'date'
137
+ : Value extends Function
138
+ ? 'function'
139
+ : Value extends null
140
+ ? 'null'
141
+ : Value extends number
142
+ ? 'number'
143
+ : Value extends object
144
+ ? 'object' | ((value: unknown) => value is Value)
145
+ : Value extends string
146
+ ? 'string'
147
+ : Value extends symbol
148
+ ? 'symbol'
149
+ : Value extends undefined
150
+ ? 'undefined'
151
+ : (value: unknown) => value is Value;
152
+
153
+ type TuplePermutations<
154
+ Tuple extends unknown[],
155
+ Elput extends unknown[] = [],
156
+ > = Tuple['length'] extends 0
157
+ ? Elput
158
+ : {
159
+ [Key in keyof Tuple]: TuplePermutations<
160
+ TupleRemoveAt<Tuple, Key & `${number}`>,
161
+ [...Elput, Tuple[Key]]
162
+ >;
163
+ }[keyof Tuple & `${number}`];
164
+
165
+ type TupleRemoveAt<
166
+ Items extends unknown[],
167
+ Item extends string,
168
+ Prefix extends unknown[] = [],
169
+ > = Items extends [infer Head, ...infer Tail]
170
+ ? `${Prefix['length']}` extends Item
171
+ ? [...Prefix, ...Tail]
172
+ : TupleRemoveAt<Tail, Item, [...Prefix, Head]>
173
+ : Prefix;
117
174
 
118
175
  export type TypedPropertyOptional<Value> = {
119
176
  $required: false;
120
- $type: ToSchemaPropertyType<Value>;
177
+ $type: ToSchemaPropertyType<Exclude<Value, undefined>>;
121
178
  };
122
179
 
123
180
  export type TypedPropertyRequired<Value> = {
@@ -145,11 +202,11 @@ type TypedSchemaOptional<Model extends PlainObject> = {
145
202
  } & TypedSchema<Model>;
146
203
 
147
204
  type TypedSchemaRequired<Model extends PlainObject> = {
148
- $required?: boolean;
205
+ $required?: true;
149
206
  } & TypedSchema<Model>;
150
207
 
151
- type UnionToIntersection<Value> = (Value extends unknown ? (x: Value) => void : never) extends (
152
- x: infer Item,
208
+ type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends (
209
+ value: infer Item,
153
210
  ) => void
154
211
  ? Item
155
212
  : never;
@@ -158,7 +215,11 @@ type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never]
158
215
  ? Items
159
216
  : UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
160
217
 
161
- type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value;
218
+ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only]
219
+ ? Only
220
+ : Value['length'] extends 1 | 2 | 3 | 4 | 5
221
+ ? TuplePermutations<Value>
222
+ : Value;
162
223
 
163
224
  export type ValidatedProperty = {
164
225
  required: boolean;
@@ -168,6 +229,7 @@ export type ValidatedProperty = {
168
229
  export type ValidatedPropertyType = Schematic<unknown> | ValueName;
169
230
 
170
231
  export type ValidatedSchema = {
232
+ enabled: boolean;
171
233
  keys: {
172
234
  array: string[];
173
235
  set: Set<string>;
@@ -180,8 +242,6 @@ export type ValidatedSchema = {
180
242
  */
181
243
  export type ValueName = keyof Values;
182
244
 
183
- type ValueNameFallbacks = 'date-like' | 'numerical';
184
-
185
245
  /**
186
246
  * Map of type names to their TypeScript/validatable equivalents
187
247
  */
@@ -190,11 +250,9 @@ export type Values = {
190
250
  bigint: bigint;
191
251
  boolean: boolean;
192
252
  date: Date;
193
- 'date-like': number | string | Date;
194
253
  function: Function;
195
254
  null: null;
196
255
  number: number;
197
- numerical: bigint | number;
198
256
  object: object;
199
257
  string: string;
200
258
  symbol: symbol;
package/src/schematic.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type {PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import {SCHEMATIC_NAME} from './constants';
3
3
  import type {Infer, Schema, TypedSchema, ValidatedSchema} from './models';
4
- import {validateSchema} from './validation/schema.validation';
4
+ import {getSchema} from './validation/schema.validation';
5
5
  import {validateValue} from './validation/value.validation';
6
6
 
7
7
  /**
@@ -11,10 +11,9 @@ export class Schematic<Model> {
11
11
  declare private readonly $schematic: true;
12
12
 
13
13
  #schema: ValidatedSchema;
14
- #validatable: boolean;
15
14
 
16
- get validatable(): boolean {
17
- return this.#validatable;
15
+ get enabled(): boolean {
16
+ return this.#schema.enabled;
18
17
  }
19
18
 
20
19
  constructor(schema: Model) {
@@ -22,16 +21,16 @@ export class Schematic<Model> {
22
21
  value: true,
23
22
  });
24
23
 
25
- this.#schema = validateSchema(schema);
24
+ this.#schema = getSchema(schema);
26
25
 
27
- this.#validatable = this.#schema.keys.array.length > 0;
26
+ this.#schema.enabled = this.#schema.keys.array.length > 0;
28
27
  }
29
28
 
30
29
  /**
31
30
  * Does the value match the schema?
32
31
  */
33
32
  is(value: unknown): value is Model {
34
- return this.#validatable && validateValue(this.#schema, value);
33
+ return this.#schema.enabled && validateValue(this.#schema, value);
35
34
  }
36
35
  }
37
36
 
@@ -1,13 +1,17 @@
1
+ import {isConstructor} from '@oscarpalmer/atoms/is';
1
2
  import type {PlainObject} from '@oscarpalmer/atoms/models';
2
3
  import {smush} from '@oscarpalmer/atoms/value';
3
4
  import {
5
+ EXPRESSION_HAS_NUMBER,
6
+ EXPRESSION_INDEX,
7
+ EXPRESSION_PROPERTY,
4
8
  PROPERTY_REQUIRED,
5
9
  PROPERTY_TYPE,
6
10
  TYPE_ALL,
7
11
  TYPE_OBJECT,
8
12
  TYPE_UNDEFINED,
9
13
  } from '../constants';
10
- import {isSchematic} from '../is';
14
+ import {isInstance, isSchematic} from '../is';
11
15
  import type {Schema, ValidatedPropertyType, ValidatedSchema} from '../models';
12
16
 
13
17
  function addPropertyType(
@@ -39,6 +43,21 @@ function addPropertyType(
39
43
  }
40
44
  }
41
45
 
46
+ export function getSchema(schema: unknown): ValidatedSchema {
47
+ const validated: ValidatedSchema = {
48
+ enabled: false,
49
+ keys: {
50
+ array: [],
51
+ set: new Set<string>(),
52
+ },
53
+ properties: {},
54
+ };
55
+
56
+ return typeof schema === 'object' && schema !== null
57
+ ? getValidatedSchema(schema as Schema, validated)
58
+ : validated;
59
+ }
60
+
42
61
  function getTypes(
43
62
  value: unknown,
44
63
  validated: ValidatedSchema,
@@ -51,14 +70,21 @@ function getTypes(
51
70
 
52
71
  for (let index = 0; index < length; index += 1) {
53
72
  const type = values[index];
73
+ const typeOfType = typeof type;
54
74
 
55
- if (isSchematic(type) || (typeof type === 'string' && TYPE_ALL.has(type as never))) {
75
+ if (isSchematic(type) || (typeOfType === 'string' && TYPE_ALL.has(type as never))) {
56
76
  propertyTypes.push(type as never);
57
77
 
58
78
  continue;
59
79
  }
60
80
 
61
- if (typeof type !== 'object' || type === null) {
81
+ if (typeOfType === 'function') {
82
+ propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
83
+
84
+ continue;
85
+ }
86
+
87
+ if (typeOfType !== 'object' || type === null) {
62
88
  continue;
63
89
  }
64
90
 
@@ -100,11 +126,11 @@ function getValidatedSchema(
100
126
  arrayKeys.add(key);
101
127
  }
102
128
 
103
- if (/\.\$(required|type)(\.|$)/.test(key)) {
129
+ if (EXPRESSION_PROPERTY.test(key)) {
104
130
  continue;
105
131
  }
106
132
 
107
- if (/\d+/.test(key) && arrayKeys.has(key.replace(/\.\d+$/, ''))) {
133
+ if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ''))) {
108
134
  continue;
109
135
  }
110
136
 
@@ -129,17 +155,3 @@ function getValidatedSchema(
129
155
 
130
156
  return validated;
131
157
  }
132
-
133
- export function validateSchema(schema: unknown): ValidatedSchema {
134
- const validated: ValidatedSchema = {
135
- keys: {
136
- array: [],
137
- set: new Set<string>(),
138
- },
139
- properties: {},
140
- };
141
-
142
- return typeof schema === 'object' && schema !== null
143
- ? getValidatedSchema(schema as Schema, validated)
144
- : validated;
145
- }
@@ -1,11 +1,19 @@
1
1
  import type {PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import {smush} from '@oscarpalmer/atoms/value';
3
- import {TYPE_OBJECT, TYPE_UNDEFINED} from '../constants';
4
- import {isDateLike} from '../is';
3
+ import {TYPE_UNDEFINED} from '../constants';
5
4
  import type {ValidatedPropertyType, ValidatedSchema, Values} from '../models';
6
5
 
7
6
  export function validateType(type: ValidatedPropertyType, value: unknown): boolean {
8
- return typeof type === 'string' ? validators[type](value) : type.is(value);
7
+ switch (true) {
8
+ case typeof type === 'function':
9
+ return (type as any)(value);
10
+
11
+ case typeof type === 'string':
12
+ return validators[type](value);
13
+
14
+ default:
15
+ return type.is(value);
16
+ }
9
17
  }
10
18
 
11
19
  export function validateValue(validated: ValidatedSchema, obj: unknown): boolean {
@@ -50,7 +58,7 @@ export function validateValue(validated: ValidatedSchema, obj: unknown): boolean
50
58
  const type = property.types[typeIndex];
51
59
 
52
60
  if (validateType(type, value)) {
53
- if (type !== TYPE_OBJECT) {
61
+ if ((type as never) !== 'object') {
54
62
  ignore.add(key);
55
63
  }
56
64
 
@@ -75,11 +83,9 @@ const validators: Record<keyof Values, (value: unknown) => boolean> = {
75
83
  bigint: value => typeof value === 'bigint',
76
84
  boolean: value => typeof value === 'boolean',
77
85
  date: value => value instanceof Date,
78
- 'date-like': isDateLike,
79
86
  function: value => typeof value === 'function',
80
87
  null: value => value === null,
81
88
  number: value => typeof value === 'number' && !Number.isNaN(value),
82
- numerical: value => validators.bigint(value) || validators.number(value),
83
89
  object: value => typeof value === 'object' && value !== null,
84
90
  string: value => typeof value === 'string',
85
91
  symbol: value => typeof value === 'symbol',
@@ -1,4 +1,7 @@
1
1
  import type { Values } from './models';
2
+ export declare const EXPRESSION_HAS_NUMBER: RegExp;
3
+ export declare const EXPRESSION_INDEX: RegExp;
4
+ export declare const EXPRESSION_PROPERTY: RegExp;
2
5
  export declare const PROPERTY_REQUIRED = "$required";
3
6
  export declare const PROPERTY_TYPE = "$type";
4
7
  export declare const SCHEMATIC_NAME = "$schematic";
package/types/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ export { isInstance } from './is';
1
2
  export type { Schema, TypedSchema } from './models';
2
3
  export { schematic, type Schematic } from './schematic';
package/types/is.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Constructor } from './models';
1
2
  import type { Schematic } from './schematic';
2
- export declare function isDateLike(value: unknown): value is Date;
3
+ export declare function isInstance<Instance>(constructor: Constructor<Instance>): (value: unknown) => value is Instance;
3
4
  export declare function isSchematic(value: unknown): value is Schematic<never>;
package/types/models.d.ts CHANGED
@@ -1,25 +1,32 @@
1
1
  import type { PlainObject, Simplify } from '@oscarpalmer/atoms/models';
2
2
  import type { Schematic } from './schematic';
3
+ export type Constructor<Instance = any> = new (...args: any[]) => Instance;
4
+ type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
5
+ infer Head,
6
+ ...infer Tail
7
+ ] ? Head extends Seen[number] ? DeduplicateTuple<Tail, Seen> : DeduplicateTuple<Tail, [...Seen, Head]> : Seen;
3
8
  /**
4
9
  * Infer the TypeScript type from a schema definition
5
10
  */
6
11
  export type Infer<Model extends Schema> = Simplify<{
7
- [Key in InferRequiredKeys<Model>]: Model[Key] extends Schematic<infer SchematicModel> ? SchematicModel : Model[Key] extends SchemaProperty ? InferPropertyValue<Model[Key]> : InferEntryValue<Model[Key]>;
12
+ [Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]>;
8
13
  } & {
9
- [Key in InferOptionalKeys<Model>]?: Model[Key] extends Schematic<infer SchematicModel> ? SchematicModel : Model[Key] extends SchemaProperty ? InferPropertyValue<Model[Key]> : never;
14
+ [Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]>;
10
15
  }>;
11
- type InferEntryValue<Value> = Value extends ValueName ? Values[Value] : Value extends ValueName[] ? Values[Value[number]] : never;
12
16
  type InferOptionalKeys<Model extends Schema> = keyof {
13
17
  [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never;
14
18
  };
15
- type InferPropertyType<Value> = Value extends (infer Item)[] ? InferTypeValue<Item>[] : InferTypeValue<Value>;
16
- type InferPropertyValue<Prop> = Prop extends SchemaProperty ? InferPropertyType<Prop['$type']> : never;
19
+ type InferPropertyType<Value> = Value extends (infer Item)[] ? InferPropertyValue<Item> : InferPropertyValue<Value>;
20
+ type InferPropertyValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends ValueName ? Values[Value & ValueName] : Value extends Schema ? Infer<Value> : never;
17
21
  type InferRequiredKeys<Model extends Schema> = keyof {
18
22
  [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
19
23
  };
20
- type InferTypeValue<Value> = Value extends SchemaPropertyType ? Value extends Schema ? Infer<Value> : Value extends Schematic<infer Model> ? Model : Value extends ValueName ? Values[Value] : Value extends (infer Nested)[] ? InferTypeValue<Nested>[] : Value : never;
24
+ type InferSchemaEntry<Value> = Value extends (infer Item)[] ? InferSchemaEntryValue<Item> : InferSchemaEntryValue<Value>;
25
+ type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends SchemaProperty ? InferPropertyType<Value['$type']> : Value extends ValueName ? Values[Value & ValueName] : Value extends Schema ? Infer<Value> : never;
21
26
  type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : false;
22
27
  type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item ? Item : never;
28
+ type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToValueType<Head>, ...MapToValueTypes<Tail>] : [];
29
+ type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>] : [];
23
30
  /**
24
31
  * A nested schema with optional requirement flag
25
32
  */
@@ -34,9 +41,9 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
34
41
  * A schema for validating objects
35
42
  */
36
43
  export type Schema = SchemaIndex;
37
- type SchemaEntry = NestedSchema | SchemaProperty | Schematic<unknown> | ValueName | ValueName[];
44
+ type SchemaEntry = Constructor | NestedSchema | SchemaProperty | Schematic<unknown> | ValueName;
38
45
  interface SchemaIndex {
39
- [key: string]: SchemaEntry;
46
+ [key: string]: SchemaEntry | SchemaEntry[];
40
47
  }
41
48
  /**
42
49
  * A property definition with explicit type(s) and optional requirement flag
@@ -45,18 +52,21 @@ export type SchemaProperty = {
45
52
  $required?: boolean;
46
53
  $type: SchemaPropertyType | SchemaPropertyType[];
47
54
  };
48
- type SchemaPropertyType = Schema | Schematic<unknown> | ValueName;
49
- type ToSchemaPropertyType<Value> = UnwrapSingle<UnionToTuple<ToSchemaPropertyTypeEach<Value>>>;
50
- type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> : ToValueName<Value>;
51
- type ToSchemaType<Value> = UnwrapSingle<UnionToTuple<ToValueName<Value>>>;
52
- type ToValueName<Value> = {
53
- [Key in Exclude<ValueName, ValueNameFallbacks>]: Value extends Values[Key] ? Key : never;
54
- }[Exclude<ValueName, ValueNameFallbacks>] extends infer Specific ? Specific extends never ? {
55
- [Key in ValueNameFallbacks]: Value extends Values[Key] ? Key : never;
56
- }[ValueNameFallbacks] : Specific : never;
55
+ type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
56
+ type ToSchemaPropertyType<Value> = UnwrapSingle<DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>>;
57
+ type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
58
+ type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
59
+ type ToValueType<Value> = Value extends unknown[] ? 'array' : Value extends bigint ? 'bigint' : Value extends boolean ? 'boolean' : Value extends Date ? 'date' : Value extends Function ? 'function' : Value extends null ? 'null' : Value extends number ? 'number' : Value extends object ? 'object' | ((value: unknown) => value is Value) : Value extends string ? 'string' : Value extends symbol ? 'symbol' : Value extends undefined ? 'undefined' : (value: unknown) => value is Value;
60
+ type TuplePermutations<Tuple extends unknown[], Elput extends unknown[] = []> = Tuple['length'] extends 0 ? Elput : {
61
+ [Key in keyof Tuple]: TuplePermutations<TupleRemoveAt<Tuple, Key & `${number}`>, [
62
+ ...Elput,
63
+ Tuple[Key]
64
+ ]>;
65
+ }[keyof Tuple & `${number}`];
66
+ type TupleRemoveAt<Items extends unknown[], Item extends string, Prefix extends unknown[] = []> = Items extends [infer Head, ...infer Tail] ? `${Prefix['length']}` extends Item ? [...Prefix, ...Tail] : TupleRemoveAt<Tail, Item, [...Prefix, Head]> : Prefix;
57
67
  export type TypedPropertyOptional<Value> = {
58
68
  $required: false;
59
- $type: ToSchemaPropertyType<Value>;
69
+ $type: ToSchemaPropertyType<Exclude<Value, undefined>>;
60
70
  };
61
71
  export type TypedPropertyRequired<Value> = {
62
72
  $required?: true;
@@ -74,17 +84,18 @@ type TypedSchemaOptional<Model extends PlainObject> = {
74
84
  $required: false;
75
85
  } & TypedSchema<Model>;
76
86
  type TypedSchemaRequired<Model extends PlainObject> = {
77
- $required?: boolean;
87
+ $required?: true;
78
88
  } & TypedSchema<Model>;
79
- type UnionToIntersection<Value> = (Value extends unknown ? (x: Value) => void : never) extends (x: infer Item) => void ? Item : never;
89
+ type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends (value: infer Item) => void ? Item : never;
80
90
  type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never] ? Items : UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
81
- type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value;
91
+ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value['length'] extends 1 | 2 | 3 | 4 | 5 ? TuplePermutations<Value> : Value;
82
92
  export type ValidatedProperty = {
83
93
  required: boolean;
84
94
  types: ValidatedPropertyType[];
85
95
  };
86
96
  export type ValidatedPropertyType = Schematic<unknown> | ValueName;
87
97
  export type ValidatedSchema = {
98
+ enabled: boolean;
88
99
  keys: {
89
100
  array: string[];
90
101
  set: Set<string>;
@@ -95,7 +106,6 @@ export type ValidatedSchema = {
95
106
  * Valid type name strings
96
107
  */
97
108
  export type ValueName = keyof Values;
98
- type ValueNameFallbacks = 'date-like' | 'numerical';
99
109
  /**
100
110
  * Map of type names to their TypeScript/validatable equivalents
101
111
  */
@@ -104,11 +114,9 @@ export type Values = {
104
114
  bigint: bigint;
105
115
  boolean: boolean;
106
116
  date: Date;
107
- 'date-like': number | string | Date;
108
117
  function: Function;
109
118
  null: null;
110
119
  number: number;
111
- numerical: bigint | number;
112
120
  object: object;
113
121
  string: string;
114
122
  symbol: symbol;
@@ -6,7 +6,7 @@ import type { Infer, Schema, TypedSchema } from './models';
6
6
  export declare class Schematic<Model> {
7
7
  #private;
8
8
  private readonly $schematic;
9
- get validatable(): boolean;
9
+ get enabled(): boolean;
10
10
  constructor(schema: Model);
11
11
  /**
12
12
  * Does the value match the schema?
@@ -1,2 +1,2 @@
1
1
  import type { ValidatedSchema } from '../models';
2
- export declare function validateSchema(schema: unknown): ValidatedSchema;
2
+ export declare function getSchema(schema: unknown): ValidatedSchema;