@oscarpalmer/jhunal 0.2.0 → 0.4.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.
Files changed (42) hide show
  1. package/README.md +2 -0
  2. package/dist/helpers.cjs +45 -0
  3. package/dist/helpers.js +41 -0
  4. package/dist/index.cjs +4 -106
  5. package/dist/index.js +1 -107
  6. package/dist/is.cjs +19 -0
  7. package/dist/is.js +14 -0
  8. package/dist/model.cjs +2 -0
  9. package/dist/model.js +1 -0
  10. package/dist/schematic.cjs +17 -0
  11. package/dist/schematic.js +13 -0
  12. package/dist/validation/schema.validation.cjs +54 -0
  13. package/dist/validation/schema.validation.js +49 -0
  14. package/dist/validation/type.validation.cjs +33 -0
  15. package/dist/validation/type.validation.js +29 -0
  16. package/dist/validation/value.validation.cjs +35 -0
  17. package/dist/validation/value.validation.js +31 -0
  18. package/package.json +1 -1
  19. package/src/helpers.ts +49 -0
  20. package/src/index.ts +2 -282
  21. package/src/is.ts +22 -0
  22. package/src/model.ts +168 -0
  23. package/src/schematic.ts +30 -0
  24. package/src/validation/schema.validation.ts +63 -0
  25. package/src/validation/type.validation.ts +34 -0
  26. package/src/validation/value.validation.ts +46 -0
  27. package/types/helpers.d.cts +43 -0
  28. package/types/helpers.d.ts +2 -0
  29. package/types/index.d.cts +25 -14
  30. package/types/index.d.ts +2 -75
  31. package/types/is.d.cts +15 -0
  32. package/types/is.d.ts +3 -0
  33. package/types/model.d.cts +365 -0
  34. package/types/model.d.ts +91 -0
  35. package/types/schematic.d.cts +360 -0
  36. package/types/schematic.d.ts +9 -0
  37. package/types/validation/schema.validation.d.cts +55 -0
  38. package/types/validation/schema.validation.d.ts +3 -0
  39. package/types/validation/type.validation.d.cts +43 -0
  40. package/types/validation/type.validation.d.ts +2 -0
  41. package/types/validation/value.validation.d.cts +43 -0
  42. package/types/validation/value.validation.d.ts +2 -0
package/src/index.ts CHANGED
@@ -1,282 +1,2 @@
1
- import type {
2
- OptionalKeysOf,
3
- RequiredKeysOf,
4
- Simplify,
5
- UnionToTuple,
6
- } from 'type-fest';
7
- import type {PlainObject} from '@oscarpalmer/atoms/models';
8
-
9
- type GetKey<Value> = {
10
- [Key in keyof Values]: Value extends Values[Key] ? Key : never;
11
- }[keyof Values];
12
-
13
- type GetTypes<Value extends unknown[]> = Value extends [infer Single]
14
- ? Single
15
- : Value;
16
-
17
- type GetType<Value> = GetTypes<UnionToTuple<GetKey<Value>>>;
18
-
19
- type InferProperty<Value> = Value extends Property
20
- ? Value['type'] extends keyof Values
21
- ? Values[Value['type']]
22
- : Value['type'] extends (keyof Values)[]
23
- ? Values[Value['type'][number]]
24
- : never
25
- : never;
26
-
27
- type InferValue<Value> = Value extends keyof Values
28
- ? Values[Value]
29
- : Value extends (keyof Values)[]
30
- ? Values[Value[number]]
31
- : never;
32
-
33
- type Inferred<Model extends Schema> = {
34
- [Key in InferredRequiredProperties<Model>]: Model[Key] extends Property
35
- ? InferProperty<Model[Key]>
36
- : InferValue<Model[Key]>;
37
- } & {
38
- [Key in InferredOptionalProperties<Model>]?: Model[Key] extends Property
39
- ? InferProperty<Model[Key]>
40
- : never;
41
- };
42
-
43
- type InferredOptionalProperties<Model extends Schema> = {
44
- [Key in keyof Model]: Model[Key] extends Property
45
- ? Model[Key]['required'] extends false
46
- ? Key
47
- : never
48
- : never;
49
- }[keyof Model];
50
-
51
- type InferredRequiredProperties<Model extends Schema> = {
52
- [Key in keyof Model]: Model[Key] extends Property
53
- ? Model[Key]['required'] extends false
54
- ? never
55
- : Key
56
- : Key;
57
- }[keyof Model];
58
-
59
- type OptionalProperty<Type> = {
60
- required: false;
61
- type: GetType<Type>;
62
- };
63
-
64
- type Property = {
65
- required?: boolean;
66
- type: keyof Values | (keyof Values)[];
67
- };
68
-
69
- type RequiredProperty<Type> = {
70
- required: true;
71
- type: GetType<Type>;
72
- };
73
-
74
- /**
75
- * A schema for validating objects
76
- */
77
- export type Schema = Record<string, keyof Values | (keyof Values)[] | Property>;
78
-
79
- /**
80
- * A schematic for validating objects
81
- */
82
- export type Schematic<Model> = {
83
- /**
84
- * Does the value match the schema?
85
- */
86
- is(value: unknown): value is Model;
87
- };
88
-
89
- type Typed = Record<string, unknown>;
90
-
91
- /**
92
- * A typed schema for validating objects
93
- */
94
- export type TypedSchema<Model extends Typed> = Simplify<
95
- {
96
- [Key in RequiredKeysOf<Model>]:
97
- | GetType<Model[Key]>
98
- | RequiredProperty<Model[Key]>;
99
- } & {
100
- [Key in OptionalKeysOf<Model>]: OptionalProperty<Model[Key]>;
101
- }
102
- >;
103
-
104
- type ValidatedProperty = {
105
- required: boolean;
106
- types: (keyof Values)[];
107
- };
108
-
109
- type ValidatedSchema = {
110
- keys: string[];
111
- length: number;
112
- properties: Record<string, ValidatedProperty>;
113
- };
114
-
115
- type Values = {
116
- array: unknown[];
117
- bigint: bigint;
118
- boolean: boolean;
119
- date: Date;
120
- // biome-ignore lint/complexity/noBannedTypes: it's the most basic value type, so I think it's ok
121
- function: Function;
122
- null: null;
123
- number: number;
124
- object: object;
125
- string: string;
126
- symbol: symbol;
127
- undefined: undefined;
128
- };
129
-
130
- //
131
-
132
- const types = new Set<keyof Values>([
133
- 'array',
134
- 'bigint',
135
- 'boolean',
136
- 'date',
137
- 'function',
138
- 'null',
139
- 'number',
140
- 'object',
141
- 'string',
142
- 'symbol',
143
- 'undefined',
144
- ]);
145
-
146
- const validators: Record<keyof Values, (value: unknown) => boolean> = {
147
- array: Array.isArray,
148
- bigint: value => typeof value === 'bigint',
149
- boolean: value => typeof value === 'boolean',
150
- date: value => value instanceof Date,
151
- function: value => typeof value === 'function',
152
- null: value => value === null,
153
- number: value => typeof value === 'number',
154
- object: value => typeof value === 'object' && value !== null,
155
- string: value => typeof value === 'string',
156
- symbol: value => typeof value === 'symbol',
157
- undefined: value => value === undefined,
158
- };
159
-
160
- //
161
-
162
- function getTypes(value: unknown): (keyof Values)[] {
163
- return (Array.isArray(value) ? value : [value]).filter(item =>
164
- types.has(item),
165
- );
166
- }
167
-
168
- function getValidatedSchema(schema: unknown): ValidatedSchema {
169
- const validated: ValidatedSchema = {
170
- keys: [],
171
- length: 0,
172
- properties: {},
173
- };
174
-
175
- if (typeof schema !== 'object' || schema === null) {
176
- return validated;
177
- }
178
-
179
- const keys = Object.keys(schema);
180
- const {length} = keys;
181
-
182
- for (let index = 0; index < length; index += 1) {
183
- const key = keys[index];
184
- const value = (schema as Schema)[key];
185
-
186
- let required = true;
187
- let valueTypes: (keyof Values)[];
188
-
189
- if (Array.isArray(value)) {
190
- valueTypes = getTypes(value);
191
- } else if (typeof value === 'object' && value !== null) {
192
- if (typeof (value as PlainObject).required === 'boolean') {
193
- required = (value as PlainObject).required as boolean;
194
- }
195
-
196
- valueTypes = getTypes((value as PlainObject).type);
197
- } else {
198
- valueTypes = getTypes(value);
199
- }
200
-
201
- if (valueTypes.length > 0) {
202
- if (!required && !valueTypes.includes('undefined')) {
203
- valueTypes.push('undefined');
204
- }
205
-
206
- validated.keys.push(key);
207
-
208
- validated.properties[key] = {
209
- required,
210
- types: valueTypes,
211
- };
212
-
213
- validated.length += 1;
214
- }
215
- }
216
-
217
- return validated;
218
- }
219
-
220
- /**
221
- * Create a schematic from a typed schema
222
- */
223
- export function schematic<Model extends Typed>(
224
- schema: TypedSchema<Model>,
225
- ): Schematic<Model>;
226
-
227
- /**
228
- * Create a schematic from a schema
229
- */
230
- export function schematic<Model extends Schema>(
231
- schema: Model,
232
- ): Schematic<Inferred<Model>>;
233
-
234
- export function schematic<Model extends Schema>(schema: Model) {
235
- const validated = getValidatedSchema(schema);
236
-
237
- const canValidate = validated.length > 0;
238
-
239
- return Object.freeze({
240
- is: (value: unknown) => canValidate && validate(validated, value),
241
- });
242
- }
243
-
244
- function validate(validated: ValidatedSchema, obj: unknown): boolean {
245
- if (typeof obj !== 'object' || obj === null) {
246
- return false;
247
- }
248
-
249
- outer: for (let index = 0; index < validated.length; index += 1) {
250
- const key = validated.keys[index];
251
- const property = validated.properties[key];
252
- const value = (obj as PlainObject)[key];
253
-
254
- if (
255
- value === undefined &&
256
- property.required &&
257
- !property.types.includes('undefined')
258
- ) {
259
- return false;
260
- }
261
-
262
- const typesLength = property.types.length;
263
-
264
- if (typesLength === 1) {
265
- if (!validators[property.types[0]](value)) {
266
- return false;
267
- }
268
-
269
- continue;
270
- }
271
-
272
- for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
273
- if (validators[property.types[typeIndex]](value)) {
274
- continue outer;
275
- }
276
- }
277
-
278
- return false;
279
- }
280
-
281
- return true;
282
- }
1
+ export type {Schema, Schematic, TypedSchema} from './model';
2
+ export * from './schematic';
package/src/is.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type {Schematic} from './model';
2
+
3
+ export function isDateLike(value: unknown): value is Date {
4
+ if (value instanceof Date) {
5
+ return true;
6
+ }
7
+
8
+ if (typeof value === 'number') {
9
+ return value >= -8_640e12 && value <= 8_640e12;
10
+ }
11
+
12
+ return typeof value === 'string' && !Number.isNaN(Date.parse(value));
13
+ }
14
+
15
+ export function isSchematic(value: unknown): value is Schematic<never> {
16
+ return (
17
+ typeof value === 'object' &&
18
+ value !== null &&
19
+ '$schematic' in value &&
20
+ value.$schematic === true
21
+ );
22
+ }
package/src/model.ts ADDED
@@ -0,0 +1,168 @@
1
+ import type {
2
+ OptionalKeysOf,
3
+ RequiredKeysOf,
4
+ Simplify,
5
+ UnionToTuple,
6
+ } from 'type-fest';
7
+
8
+ export type AutoInferExclude = 'date-like' | 'numerical';
9
+
10
+ export type GetKey<Value> = {
11
+ [Key in Exclude<ValueKey, AutoInferExclude>]: Value extends Values[Key]
12
+ ? Key
13
+ : never;
14
+ }[Exclude<ValueKey, AutoInferExclude>] extends infer SpecificKey
15
+ ? SpecificKey extends never
16
+ ? {
17
+ [Key in AutoInferExclude]: Value extends Values[Key] ? Key : never;
18
+ }[AutoInferExclude]
19
+ : SpecificKey
20
+ : never;
21
+
22
+ export type GetTypes<Value extends unknown[]> = Value extends [infer Type]
23
+ ? Type
24
+ : Value;
25
+
26
+ export type GetType<Value> = GetTypes<UnionToTuple<GetKey<Value>>>;
27
+
28
+ export type InferProperty<Value> = Value extends Property
29
+ ? InferPropertyType<Value['type']>
30
+ : never;
31
+
32
+ type InferPropertyType<Value> = Value extends (infer Type)[]
33
+ ? InferPropertyTypeValue<Type>[]
34
+ : InferPropertyTypeValue<Value>;
35
+
36
+ type InferPropertyTypeValue<Value> = Value extends PropertyType
37
+ ? Value extends Schema
38
+ ? Inferred<Value>
39
+ : Value extends Schematic<infer Model>
40
+ ? Model
41
+ : Value extends ValueKey
42
+ ? Values[Value]
43
+ : Value extends (infer NestedElementType)[]
44
+ ? InferPropertyTypeValue<NestedElementType>[]
45
+ : Value
46
+ : never;
47
+
48
+ export type InferValue<Value> = Value extends ValueKey
49
+ ? Values[Value]
50
+ : Value extends ValueKey[]
51
+ ? Values[Value[number]]
52
+ : never;
53
+
54
+ export type Inferred<Model extends Schema> = Simplify<
55
+ {
56
+ [Key in InferredRequiredProperties<Model>]: Model[Key] extends Property
57
+ ? InferProperty<Model[Key]>
58
+ : InferValue<Model[Key]>;
59
+ } & {
60
+ [Key in InferredOptionalProperties<Model>]?: Model[Key] extends Property
61
+ ? InferProperty<Model[Key]>
62
+ : never;
63
+ }
64
+ >;
65
+
66
+ export type InferredOptionalProperties<Model extends Schema> = {
67
+ [Key in keyof Model]: Model[Key] extends Property
68
+ ? Model[Key]['required'] extends false
69
+ ? Key
70
+ : never
71
+ : never;
72
+ }[keyof Model];
73
+
74
+ export type InferredRequiredProperties<Model extends Schema> = {
75
+ [Key in keyof Model]: Model[Key] extends Property
76
+ ? Model[Key]['required'] extends false
77
+ ? never
78
+ : Key
79
+ : Key;
80
+ }[keyof Model];
81
+
82
+ export type OptionalProperty<Type> = {
83
+ required: false;
84
+ type: GetType<Type>;
85
+ };
86
+
87
+ export type Property = {
88
+ required?: boolean;
89
+ type: PropertyType | PropertyType[];
90
+ };
91
+
92
+ type PropertyType = Schema | Schematic<unknown> | ValueKey;
93
+
94
+ export type RequiredProperty<Type> = {
95
+ required: true;
96
+ type: GetType<Type>;
97
+ };
98
+
99
+ /**
100
+ * A schema for validating objects
101
+ */
102
+ export type Schema = {
103
+ [key: string]: Property | ValueKey | ValueKey[];
104
+ };
105
+
106
+ /**
107
+ * A schematic for validating objects
108
+ */
109
+ export type Schematic<Model> = {
110
+ /**
111
+ * Does the value match the schema?
112
+ */
113
+ is(value: unknown): value is Model;
114
+ };
115
+
116
+ export type Typed = Record<string, unknown>;
117
+
118
+ /**
119
+ * A typed schema for validating objects
120
+ */
121
+ export type TypedSchema<Model extends Typed> = Simplify<
122
+ {
123
+ [Key in RequiredKeysOf<Model>]:
124
+ | GetType<Model[Key]>
125
+ | RequiredProperty<Model[Key]>;
126
+ } & {
127
+ [Key in OptionalKeysOf<Model>]: OptionalProperty<Model[Key]>;
128
+ }
129
+ >;
130
+
131
+ export type ValidatedProperty = {
132
+ required: boolean;
133
+ types: ValidatedPropertyType[];
134
+ };
135
+
136
+ export type ValidatedPropertyType =
137
+ | Schematic<unknown>
138
+ | ValidatedSchema
139
+ | ValueKey;
140
+
141
+ export type ValidatedSchema = {
142
+ keys: string[];
143
+ length: number;
144
+ properties: ValidatedSchemaProperties;
145
+ };
146
+
147
+ type ValidatedSchemaProperties = {
148
+ [key: string]: ValidatedProperty;
149
+ };
150
+
151
+ export type ValueKey = keyof Values;
152
+
153
+ export type Values = {
154
+ array: unknown[];
155
+ bigint: bigint;
156
+ boolean: boolean;
157
+ date: Date;
158
+ 'date-like': number | string | Date;
159
+ // biome-ignore lint/complexity/noBannedTypes: it's the most basic value type, so I think it's ok
160
+ function: Function;
161
+ null: null;
162
+ number: number;
163
+ numerical: bigint | number;
164
+ object: object;
165
+ string: string;
166
+ symbol: symbol;
167
+ undefined: undefined;
168
+ };
@@ -0,0 +1,30 @@
1
+ import type {Inferred, Schema, Schematic, Typed, TypedSchema} from './model';
2
+ import {validateSchema} from './validation/schema.validation';
3
+ import {validateValue} from './validation/value.validation';
4
+
5
+ /**
6
+ * Create a schematic from a schema
7
+ */
8
+ export function schematic<Model extends Schema>(
9
+ schema: Model,
10
+ ): Schematic<Inferred<Model>>;
11
+
12
+ /**
13
+ * Create a schematic from a typed schema
14
+ */
15
+ export function schematic<Model extends Typed>(
16
+ schema: TypedSchema<Model>,
17
+ ): Schematic<Model>;
18
+
19
+ export function schematic<Model extends Schema>(
20
+ schema: Model,
21
+ ): Schematic<Model> {
22
+ const validated = validateSchema(schema);
23
+
24
+ const canValidate = validated.length > 0;
25
+
26
+ return Object.freeze({
27
+ $schematic: true,
28
+ is: (value: unknown) => canValidate && validateValue(validated, value),
29
+ }) as never;
30
+ }
@@ -0,0 +1,63 @@
1
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
2
+ import {getTypes} from '../helpers';
3
+ import type {Schema, ValidatedPropertyType, ValidatedSchema} from '../model';
4
+
5
+ export function validateSchema(schema: unknown): ValidatedSchema {
6
+ if (validatedSchemas.has(schema as never)) {
7
+ return validatedSchemas.get(schema as never) as ValidatedSchema;
8
+ }
9
+
10
+ const validated: ValidatedSchema = {
11
+ keys: [],
12
+ length: 0,
13
+ properties: {},
14
+ };
15
+
16
+ if (typeof schema !== 'object' || schema === null) {
17
+ return validated;
18
+ }
19
+
20
+ const keys = Object.keys(schema);
21
+ const {length} = keys;
22
+
23
+ for (let index = 0; index < length; index += 1) {
24
+ const key = keys[index];
25
+ const value = (schema as Schema)[key];
26
+
27
+ let required = true;
28
+ let types: ValidatedPropertyType[];
29
+
30
+ if (Array.isArray(value)) {
31
+ types = getTypes(value);
32
+ } else if (typeof value === 'object' && value !== null) {
33
+ if (typeof (value as PlainObject).required === 'boolean') {
34
+ required = (value as PlainObject).required as boolean;
35
+ }
36
+
37
+ types = getTypes((value as PlainObject).type);
38
+ } else {
39
+ types = getTypes(value);
40
+ }
41
+
42
+ if (types.length > 0) {
43
+ if (!required && !types.includes('undefined')) {
44
+ types.push('undefined');
45
+ }
46
+
47
+ validated.keys.push(key);
48
+
49
+ validated.properties[key] = {
50
+ required,
51
+ types,
52
+ };
53
+
54
+ validated.length += 1;
55
+ }
56
+ }
57
+
58
+ validatedSchemas.set(schema as never, validated);
59
+
60
+ return validated;
61
+ }
62
+
63
+ export const validatedSchemas = new WeakMap<Schema, ValidatedSchema>();
@@ -0,0 +1,34 @@
1
+ import {isDateLike, isSchematic} from '../is';
2
+ import type {ValidatedPropertyType, ValidatedSchema, Values} from '../model';
3
+ import {validateValue} from './value.validation';
4
+
5
+ export function validateType(
6
+ type: ValidatedPropertyType,
7
+ value: unknown,
8
+ ): boolean {
9
+ if (typeof type === 'string') {
10
+ return validators[type](value);
11
+ }
12
+
13
+ if (isSchematic(type)) {
14
+ return type.is(value);
15
+ }
16
+
17
+ return validateValue(type as ValidatedSchema, value);
18
+ }
19
+
20
+ const validators: Record<keyof Values, (value: unknown) => boolean> = {
21
+ array: Array.isArray,
22
+ bigint: value => typeof value === 'bigint',
23
+ boolean: value => typeof value === 'boolean',
24
+ date: value => value instanceof Date,
25
+ 'date-like': isDateLike,
26
+ function: value => typeof value === 'function',
27
+ null: value => value === null,
28
+ number: value => typeof value === 'number',
29
+ numerical: value => validators.bigint(value) || validators.number(value),
30
+ object: value => typeof value === 'object' && value !== null,
31
+ string: value => typeof value === 'string',
32
+ symbol: value => typeof value === 'symbol',
33
+ undefined: value => value === undefined,
34
+ };
@@ -0,0 +1,46 @@
1
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
2
+ import type {ValidatedSchema} from '../model';
3
+ import {validateType} from './type.validation';
4
+
5
+ export function validateValue(
6
+ validated: ValidatedSchema,
7
+ obj: unknown,
8
+ ): boolean {
9
+ if (typeof obj !== 'object' || obj === null) {
10
+ return false;
11
+ }
12
+
13
+ outer: for (let keyIndex = 0; keyIndex < validated.length; keyIndex += 1) {
14
+ const key = validated.keys[keyIndex];
15
+ const property = validated.properties[key];
16
+ const value = (obj as PlainObject)[key];
17
+
18
+ if (
19
+ value === undefined &&
20
+ property.required &&
21
+ !property.types.includes('undefined')
22
+ ) {
23
+ return false;
24
+ }
25
+
26
+ const typesLength = property.types.length;
27
+
28
+ if (typesLength === 1) {
29
+ if (!validateType(property.types[0], value)) {
30
+ return false;
31
+ }
32
+
33
+ continue;
34
+ }
35
+
36
+ for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
37
+ if (validateType(property.types[typeIndex], value)) {
38
+ continue outer;
39
+ }
40
+ }
41
+
42
+ return false;
43
+ }
44
+
45
+ return true;
46
+ }
@@ -0,0 +1,43 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ /**
4
+ * A schematic for validating objects
5
+ */
6
+ export type Schematic<Model> = {
7
+ /**
8
+ * Does the value match the schema?
9
+ */
10
+ is(value: unknown): value is Model;
11
+ };
12
+ export type ValidatedProperty = {
13
+ required: boolean;
14
+ types: ValidatedPropertyType[];
15
+ };
16
+ export type ValidatedPropertyType = Schematic<unknown> | ValidatedSchema | ValueKey;
17
+ export type ValidatedSchema = {
18
+ keys: string[];
19
+ length: number;
20
+ properties: ValidatedSchemaProperties;
21
+ };
22
+ export type ValidatedSchemaProperties = {
23
+ [key: string]: ValidatedProperty;
24
+ };
25
+ export type ValueKey = keyof Values;
26
+ export type Values = {
27
+ array: unknown[];
28
+ bigint: bigint;
29
+ boolean: boolean;
30
+ date: Date;
31
+ "date-like": number | string | Date;
32
+ function: Function;
33
+ null: null;
34
+ number: number;
35
+ numerical: bigint | number;
36
+ object: object;
37
+ string: string;
38
+ symbol: symbol;
39
+ undefined: undefined;
40
+ };
41
+ export declare function getTypes(value: unknown): ValidatedPropertyType[];
42
+
43
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ValidatedPropertyType } from './model';
2
+ export declare function getTypes(value: unknown): ValidatedPropertyType[];