@oscarpalmer/jhunal 0.1.0 → 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.
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ function getTypes(value) {
6
+ return (Array.isArray(value) ? value : [value]).filter(
7
+ (item) => types.has(item)
8
+ );
9
+ }
10
+ const types = /* @__PURE__ */ new Set([
11
+ "array",
12
+ "bigint",
13
+ "boolean",
14
+ "date",
15
+ "function",
16
+ "null",
17
+ "number",
18
+ "object",
19
+ "string",
20
+ "symbol",
21
+ "undefined"
22
+ ]);
23
+
24
+ exports.getTypes = getTypes;
@@ -0,0 +1,20 @@
1
+ function getTypes(value) {
2
+ return (Array.isArray(value) ? value : [value]).filter(
3
+ (item) => types.has(item)
4
+ );
5
+ }
6
+ const types = /* @__PURE__ */ new Set([
7
+ "array",
8
+ "bigint",
9
+ "boolean",
10
+ "date",
11
+ "function",
12
+ "null",
13
+ "number",
14
+ "object",
15
+ "string",
16
+ "symbol",
17
+ "undefined"
18
+ ]);
19
+
20
+ export { getTypes };
package/dist/index.cjs CHANGED
@@ -1,2 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ const schematic = require('./schematic.cjs');
6
+
7
+
8
+
9
+ exports.schematic = schematic.schematic;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
-
1
+ export { schematic } from './schematic.js';
package/dist/model.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';
2
+
package/dist/model.js ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ const validation = require('./validation.cjs');
6
+
7
+ function schematic(schema) {
8
+ const validated = validation.getValidatedSchema(schema);
9
+ const canValidate = validated.length > 0;
10
+ return Object.freeze({
11
+ is: (value) => canValidate && validation.validate(validated, value)
12
+ });
13
+ }
14
+
15
+ exports.schematic = schematic;
@@ -0,0 +1,11 @@
1
+ import { getValidatedSchema, validate } from './validation.js';
2
+
3
+ function schematic(schema) {
4
+ const validated = getValidatedSchema(schema);
5
+ const canValidate = validated.length > 0;
6
+ return Object.freeze({
7
+ is: (value) => canValidate && validate(validated, value)
8
+ });
9
+ }
10
+
11
+ export { schematic };
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ const helpers = require('./helpers.cjs');
6
+
7
+ function getValidatedSchema(schema) {
8
+ const validated = {
9
+ keys: [],
10
+ length: 0,
11
+ properties: {}
12
+ };
13
+ if (typeof schema !== "object" || schema === null) {
14
+ return validated;
15
+ }
16
+ const keys = Object.keys(schema);
17
+ const { length } = keys;
18
+ for (let index = 0; index < length; index += 1) {
19
+ const key = keys[index];
20
+ const value = schema[key];
21
+ let required = true;
22
+ let valueTypes;
23
+ if (Array.isArray(value)) {
24
+ valueTypes = helpers.getTypes(value);
25
+ } else if (typeof value === "object" && value !== null) {
26
+ if (typeof value.required === "boolean") {
27
+ required = value.required;
28
+ }
29
+ valueTypes = helpers.getTypes(value.type);
30
+ } else {
31
+ valueTypes = helpers.getTypes(value);
32
+ }
33
+ if (valueTypes.length > 0) {
34
+ if (!required && !valueTypes.includes("undefined")) {
35
+ valueTypes.push("undefined");
36
+ }
37
+ validated.keys.push(key);
38
+ validated.properties[key] = {
39
+ required,
40
+ types: valueTypes
41
+ };
42
+ validated.length += 1;
43
+ }
44
+ }
45
+ return validated;
46
+ }
47
+ function validate(validated, obj) {
48
+ if (typeof obj !== "object" || obj === null) {
49
+ return false;
50
+ }
51
+ outer: for (let index = 0; index < validated.length; index += 1) {
52
+ const key = validated.keys[index];
53
+ const property = validated.properties[key];
54
+ const value = obj[key];
55
+ if (value === void 0 && property.required && !property.types.includes("undefined")) {
56
+ return false;
57
+ }
58
+ const typesLength = property.types.length;
59
+ if (typesLength === 1) {
60
+ if (!validators[property.types[0]](value)) {
61
+ return false;
62
+ }
63
+ continue;
64
+ }
65
+ for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
66
+ if (validators[property.types[typeIndex]](value)) {
67
+ continue outer;
68
+ }
69
+ }
70
+ return false;
71
+ }
72
+ return true;
73
+ }
74
+ const validators = {
75
+ array: Array.isArray,
76
+ bigint: (value) => typeof value === "bigint",
77
+ boolean: (value) => typeof value === "boolean",
78
+ date: (value) => value instanceof Date,
79
+ function: (value) => typeof value === "function",
80
+ null: (value) => value === null,
81
+ number: (value) => typeof value === "number",
82
+ object: (value) => typeof value === "object" && value !== null,
83
+ string: (value) => typeof value === "string",
84
+ symbol: (value) => typeof value === "symbol",
85
+ undefined: (value) => value === void 0
86
+ };
87
+
88
+ exports.getValidatedSchema = getValidatedSchema;
89
+ exports.validate = validate;
@@ -0,0 +1,84 @@
1
+ import { getTypes } from './helpers.js';
2
+
3
+ function getValidatedSchema(schema) {
4
+ const validated = {
5
+ keys: [],
6
+ length: 0,
7
+ properties: {}
8
+ };
9
+ if (typeof schema !== "object" || schema === null) {
10
+ return validated;
11
+ }
12
+ const keys = Object.keys(schema);
13
+ const { length } = keys;
14
+ for (let index = 0; index < length; index += 1) {
15
+ const key = keys[index];
16
+ const value = schema[key];
17
+ let required = true;
18
+ let valueTypes;
19
+ if (Array.isArray(value)) {
20
+ valueTypes = getTypes(value);
21
+ } else if (typeof value === "object" && value !== null) {
22
+ if (typeof value.required === "boolean") {
23
+ required = value.required;
24
+ }
25
+ valueTypes = getTypes(value.type);
26
+ } else {
27
+ valueTypes = getTypes(value);
28
+ }
29
+ if (valueTypes.length > 0) {
30
+ if (!required && !valueTypes.includes("undefined")) {
31
+ valueTypes.push("undefined");
32
+ }
33
+ validated.keys.push(key);
34
+ validated.properties[key] = {
35
+ required,
36
+ types: valueTypes
37
+ };
38
+ validated.length += 1;
39
+ }
40
+ }
41
+ return validated;
42
+ }
43
+ function validate(validated, obj) {
44
+ if (typeof obj !== "object" || obj === null) {
45
+ return false;
46
+ }
47
+ outer: for (let index = 0; index < validated.length; index += 1) {
48
+ const key = validated.keys[index];
49
+ const property = validated.properties[key];
50
+ const value = obj[key];
51
+ if (value === void 0 && property.required && !property.types.includes("undefined")) {
52
+ return false;
53
+ }
54
+ const typesLength = property.types.length;
55
+ if (typesLength === 1) {
56
+ if (!validators[property.types[0]](value)) {
57
+ return false;
58
+ }
59
+ continue;
60
+ }
61
+ for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
62
+ if (validators[property.types[typeIndex]](value)) {
63
+ continue outer;
64
+ }
65
+ }
66
+ return false;
67
+ }
68
+ return true;
69
+ }
70
+ const validators = {
71
+ array: Array.isArray,
72
+ bigint: (value) => typeof value === "bigint",
73
+ boolean: (value) => typeof value === "boolean",
74
+ date: (value) => value instanceof Date,
75
+ function: (value) => typeof value === "function",
76
+ null: (value) => value === null,
77
+ number: (value) => typeof value === "number",
78
+ object: (value) => typeof value === "object" && value !== null,
79
+ string: (value) => typeof value === "string",
80
+ symbol: (value) => typeof value === "symbol",
81
+ undefined: (value) => value === void 0
82
+ };
83
+
84
+ export { getValidatedSchema, validate };
package/package.json CHANGED
@@ -3,6 +3,10 @@
3
3
  "name": "Oscar Palmér",
4
4
  "url": "https://oscarpalmer.se"
5
5
  },
6
+ "dependencies": {
7
+ "@oscarpalmer/atoms": "^0.94",
8
+ "type-fest": "^4.39"
9
+ },
6
10
  "description": "Flies free beneath the glistening moons…",
7
11
  "devDependencies": {
8
12
  "@biomejs/biome": "^1.9",
@@ -46,5 +50,5 @@
46
50
  },
47
51
  "type": "module",
48
52
  "types": "./types/index.d.cts",
49
- "version": "0.1.0"
53
+ "version": "0.3.0"
50
54
  }
package/src/helpers.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type {Values} from './model';
2
+
3
+ export function getTypes(value: unknown): (keyof Values)[] {
4
+ return (Array.isArray(value) ? value : [value]).filter(item =>
5
+ types.has(item),
6
+ );
7
+ }
8
+
9
+ const types = new Set<keyof Values>([
10
+ 'array',
11
+ 'bigint',
12
+ 'boolean',
13
+ 'date',
14
+ 'function',
15
+ 'null',
16
+ 'number',
17
+ 'object',
18
+ 'string',
19
+ 'symbol',
20
+ 'undefined',
21
+ ]);
package/src/index.ts CHANGED
@@ -0,0 +1,2 @@
1
+ export type {Schema, Schematic} from './model';
2
+ export * from './schematic';
package/src/model.ts ADDED
@@ -0,0 +1,127 @@
1
+ import type {
2
+ OptionalKeysOf,
3
+ RequiredKeysOf,
4
+ Simplify,
5
+ UnionToTuple,
6
+ } from 'type-fest';
7
+
8
+ export type GetKey<Value> = {
9
+ [Key in keyof Values]: Value extends Values[Key] ? Key : never;
10
+ }[keyof Values];
11
+
12
+ export type GetTypes<Value extends unknown[]> = Value extends [infer Single]
13
+ ? Single
14
+ : Value;
15
+
16
+ export type GetType<Value> = GetTypes<UnionToTuple<GetKey<Value>>>;
17
+
18
+ export type InferProperty<Value> = Value extends Property
19
+ ? Value['type'] extends keyof Values
20
+ ? Values[Value['type']]
21
+ : Value['type'] extends (keyof Values)[]
22
+ ? Values[Value['type'][number]]
23
+ : never
24
+ : never;
25
+
26
+ export type InferValue<Value> = Value extends keyof Values
27
+ ? Values[Value]
28
+ : Value extends (keyof Values)[]
29
+ ? Values[Value[number]]
30
+ : never;
31
+
32
+ export type Inferred<Model extends Schema> = {
33
+ [Key in InferredRequiredProperties<Model>]: Model[Key] extends Property
34
+ ? InferProperty<Model[Key]>
35
+ : InferValue<Model[Key]>;
36
+ } & {
37
+ [Key in InferredOptionalProperties<Model>]?: Model[Key] extends Property
38
+ ? InferProperty<Model[Key]>
39
+ : never;
40
+ };
41
+
42
+ export type InferredOptionalProperties<Model extends Schema> = {
43
+ [Key in keyof Model]: Model[Key] extends Property
44
+ ? Model[Key]['required'] extends false
45
+ ? Key
46
+ : never
47
+ : never;
48
+ }[keyof Model];
49
+
50
+ export type InferredRequiredProperties<Model extends Schema> = {
51
+ [Key in keyof Model]: Model[Key] extends Property
52
+ ? Model[Key]['required'] extends false
53
+ ? never
54
+ : Key
55
+ : Key;
56
+ }[keyof Model];
57
+
58
+ export type OptionalProperty<Type> = {
59
+ required: false;
60
+ type: GetType<Type>;
61
+ };
62
+
63
+ export type Property = {
64
+ required?: boolean;
65
+ type: keyof Values | (keyof Values)[];
66
+ };
67
+
68
+ export type RequiredProperty<Type> = {
69
+ required: true;
70
+ type: GetType<Type>;
71
+ };
72
+
73
+ /**
74
+ * A schema for validating objects
75
+ */
76
+ export type Schema = Record<string, keyof Values | (keyof Values)[] | Property>;
77
+
78
+ /**
79
+ * A schematic for validating objects
80
+ */
81
+ export type Schematic<Model> = {
82
+ /**
83
+ * Does the value match the schema?
84
+ */
85
+ is(value: unknown): value is Model;
86
+ };
87
+
88
+ export type Typed = Record<string, unknown>;
89
+
90
+ /**
91
+ * A typed schema for validating objects
92
+ */
93
+ export type TypedSchema<Model extends Typed> = Simplify<
94
+ {
95
+ [Key in RequiredKeysOf<Model>]:
96
+ | GetType<Model[Key]>
97
+ | RequiredProperty<Model[Key]>;
98
+ } & {
99
+ [Key in OptionalKeysOf<Model>]: OptionalProperty<Model[Key]>;
100
+ }
101
+ >;
102
+
103
+ export type ValidatedProperty = {
104
+ required: boolean;
105
+ types: (keyof Values)[];
106
+ };
107
+
108
+ export type ValidatedSchema = {
109
+ keys: string[];
110
+ length: number;
111
+ properties: Record<string, ValidatedProperty>;
112
+ };
113
+
114
+ export type Values = {
115
+ array: unknown[];
116
+ bigint: bigint;
117
+ boolean: boolean;
118
+ date: Date;
119
+ // biome-ignore lint/complexity/noBannedTypes: it's the most basic value type, so I think it's ok
120
+ function: Function;
121
+ null: null;
122
+ number: number;
123
+ object: object;
124
+ string: string;
125
+ symbol: symbol;
126
+ undefined: undefined;
127
+ };
@@ -0,0 +1,26 @@
1
+ import type {Inferred, Schema, Schematic, Typed, TypedSchema} from './model';
2
+ import {getValidatedSchema, validate} from './validation';
3
+
4
+ /**
5
+ * Create a schematic from a typed schema
6
+ */
7
+ export function schematic<Model extends Typed>(
8
+ schema: TypedSchema<Model>,
9
+ ): Schematic<Model>;
10
+
11
+ /**
12
+ * Create a schematic from a schema
13
+ */
14
+ export function schematic<Model extends Schema>(
15
+ schema: Model,
16
+ ): Schematic<Inferred<Model>>;
17
+
18
+ export function schematic<Model extends Schema>(schema: Model) {
19
+ const validated = getValidatedSchema(schema);
20
+
21
+ const canValidate = validated.length > 0;
22
+
23
+ return Object.freeze({
24
+ is: (value: unknown) => canValidate && validate(validated, value),
25
+ });
26
+ }
@@ -0,0 +1,109 @@
1
+ import type {PlainObject} from '@oscarpalmer/atoms';
2
+ import {getTypes} from './helpers';
3
+ import type {Schema, ValidatedSchema, Values} from './model';
4
+
5
+ export function getValidatedSchema(schema: unknown): ValidatedSchema {
6
+ const validated: ValidatedSchema = {
7
+ keys: [],
8
+ length: 0,
9
+ properties: {},
10
+ };
11
+
12
+ if (typeof schema !== 'object' || schema === null) {
13
+ return validated;
14
+ }
15
+
16
+ const keys = Object.keys(schema);
17
+ const {length} = keys;
18
+
19
+ for (let index = 0; index < length; index += 1) {
20
+ const key = keys[index];
21
+ const value = (schema as Schema)[key];
22
+
23
+ let required = true;
24
+ let valueTypes: (keyof Values)[];
25
+
26
+ if (Array.isArray(value)) {
27
+ valueTypes = getTypes(value);
28
+ } else if (typeof value === 'object' && value !== null) {
29
+ if (typeof (value as PlainObject).required === 'boolean') {
30
+ required = (value as PlainObject).required as boolean;
31
+ }
32
+
33
+ valueTypes = getTypes((value as PlainObject).type);
34
+ } else {
35
+ valueTypes = getTypes(value);
36
+ }
37
+
38
+ if (valueTypes.length > 0) {
39
+ if (!required && !valueTypes.includes('undefined')) {
40
+ valueTypes.push('undefined');
41
+ }
42
+
43
+ validated.keys.push(key);
44
+
45
+ validated.properties[key] = {
46
+ required,
47
+ types: valueTypes,
48
+ };
49
+
50
+ validated.length += 1;
51
+ }
52
+ }
53
+
54
+ return validated;
55
+ }
56
+
57
+ export function validate(validated: ValidatedSchema, obj: unknown): boolean {
58
+ if (typeof obj !== 'object' || obj === null) {
59
+ return false;
60
+ }
61
+
62
+ outer: for (let index = 0; index < validated.length; index += 1) {
63
+ const key = validated.keys[index];
64
+ const property = validated.properties[key];
65
+ const value = (obj as PlainObject)[key];
66
+
67
+ if (
68
+ value === undefined &&
69
+ property.required &&
70
+ !property.types.includes('undefined')
71
+ ) {
72
+ return false;
73
+ }
74
+
75
+ const typesLength = property.types.length;
76
+
77
+ if (typesLength === 1) {
78
+ if (!validators[property.types[0]](value)) {
79
+ return false;
80
+ }
81
+
82
+ continue;
83
+ }
84
+
85
+ for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
86
+ if (validators[property.types[typeIndex]](value)) {
87
+ continue outer;
88
+ }
89
+ }
90
+
91
+ return false;
92
+ }
93
+
94
+ return true;
95
+ }
96
+
97
+ const validators: Record<keyof Values, (value: unknown) => boolean> = {
98
+ array: Array.isArray,
99
+ bigint: value => typeof value === 'bigint',
100
+ boolean: value => typeof value === 'boolean',
101
+ date: value => value instanceof Date,
102
+ function: value => typeof value === 'function',
103
+ null: value => value === null,
104
+ number: value => typeof value === 'number',
105
+ object: value => typeof value === 'object' && value !== null,
106
+ string: value => typeof value === 'string',
107
+ symbol: value => typeof value === 'symbol',
108
+ undefined: value => value === undefined,
109
+ };
@@ -0,0 +1,18 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ export type Values = {
4
+ array: unknown[];
5
+ bigint: bigint;
6
+ boolean: boolean;
7
+ date: Date;
8
+ function: Function;
9
+ null: null;
10
+ number: number;
11
+ object: object;
12
+ string: string;
13
+ symbol: symbol;
14
+ undefined: undefined;
15
+ };
16
+ export declare function getTypes(value: unknown): (keyof Values)[];
17
+
18
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { Values } from './model';
2
+ export declare function getTypes(value: unknown): (keyof Values)[];