exact-mirror 1.1.1 → 1.2.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 +31 -0
- package/dist/index.d.ts +135 -93
- package/dist/index.js +367 -0
- package/dist/index.mjs +349 -455
- package/package.json +3 -3
- package/dist/cjs/index.d.ts +0 -103
- package/dist/cjs/index.js +0 -495
package/README.md
CHANGED
|
@@ -50,3 +50,34 @@ const mirror = createMirror(shape)
|
|
|
50
50
|
|
|
51
51
|
console.log(mirror(value)) // {"id":0,"name":"saltyaom"}
|
|
52
52
|
```
|
|
53
|
+
|
|
54
|
+
## Decode / Encode
|
|
55
|
+
|
|
56
|
+
By default `createMirror` only **cleans** a value to the model's shape. Opt into `decode` or `encode` to also apply a TypeBox codec's transform at codec leaves during the same walk — a fast replacement for `Value.Decode` / `Value.Encode` (~350x faster on a nested + array schema).
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { Type as t } from 'typebox'
|
|
60
|
+
import { Compile } from 'typebox/compile'
|
|
61
|
+
import { createMirror } from 'exact-mirror'
|
|
62
|
+
|
|
63
|
+
const Numeric = t.Union([
|
|
64
|
+
t.Number(),
|
|
65
|
+
t
|
|
66
|
+
.Codec(t.String())
|
|
67
|
+
.Decode((v) => +v)
|
|
68
|
+
.Encode((v) => '' + v)
|
|
69
|
+
])
|
|
70
|
+
|
|
71
|
+
const shape = t.Object({ id: Numeric })
|
|
72
|
+
|
|
73
|
+
const decode = createMirror(shape, { decode: true, Compile })
|
|
74
|
+
|
|
75
|
+
decode({ id: '2' }) // { id: 2 } — parsed + cleaned in one pass
|
|
76
|
+
decode({ id: 2 }) // { id: 2 } — already-numeric branch, untouched
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- `decode: true` applies each codec's `~codec.decode` (parse input).
|
|
80
|
+
- `encode: true` applies each codec's `~codec.encode` (the reverse).
|
|
81
|
+
- Both default **off** — output is byte-identical to the pure clean.
|
|
82
|
+
|
|
83
|
+
> Decode/encode is a pure transform: the value is assumed to have already passed `Check`. Validate the input first, then mirror.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,103 +1,145 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { Static, TModule, TSchema } from "typebox";
|
|
2
|
+
import { Compile, Validator } from "typebox/compile";
|
|
3
|
+
|
|
4
|
+
//#region src/index.d.ts
|
|
3
5
|
interface BaseSchema {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
6
|
+
'~kind': string;
|
|
7
|
+
id?: string;
|
|
8
|
+
$id?: string;
|
|
9
|
+
type?: string;
|
|
10
|
+
$schema?: string;
|
|
11
|
+
const?: unknown[];
|
|
12
|
+
pattern?: string;
|
|
13
|
+
additionalItems?: boolean | AnySchema;
|
|
14
|
+
items?: AnySchema | AnySchema[];
|
|
15
|
+
required?: string[];
|
|
16
|
+
additionalProperties?: boolean | AnySchema;
|
|
17
|
+
definitions?: {
|
|
18
|
+
[name: string]: AnySchema;
|
|
19
|
+
};
|
|
20
|
+
properties?: {
|
|
21
|
+
[name: string]: AnySchema;
|
|
22
|
+
};
|
|
23
|
+
patternProperties?: {
|
|
24
|
+
[name: string]: AnySchema;
|
|
25
|
+
};
|
|
26
|
+
dependencies?: {
|
|
27
|
+
[name: string]: AnySchema | string[];
|
|
28
|
+
};
|
|
29
|
+
enum?: any[];
|
|
30
|
+
allOf?: AnySchema[];
|
|
31
|
+
anyOf?: AnySchema[];
|
|
32
|
+
oneOf?: AnySchema[];
|
|
33
|
+
not?: AnySchema;
|
|
34
|
+
$ref?: string;
|
|
35
|
+
$defs?: Record<string, AnySchema>;
|
|
34
36
|
}
|
|
35
37
|
type AnySchema = TSchema & BaseSchema;
|
|
36
|
-
|
|
38
|
+
declare const mergeObjectIntersection: (schema: AnySchema) => AnySchema;
|
|
37
39
|
type MaybeArray<T> = T | T[];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
40
|
+
interface Instruction<Emit extends boolean = false> {
|
|
41
|
+
optionals: string[];
|
|
42
|
+
optionalsInArray: string[][];
|
|
43
|
+
parentIsOptional: boolean;
|
|
44
|
+
array: number;
|
|
45
|
+
unions: Validator<any>[][];
|
|
46
|
+
unionKeys: Record<string, 1>;
|
|
47
|
+
sanitize: MaybeArray<(v: string) => string> | undefined;
|
|
48
|
+
fromUnion?: boolean;
|
|
49
|
+
emit?: Emit;
|
|
50
|
+
/**
|
|
51
|
+
* Apply a TypeBox codec's `~codec` transform at codec leaves during the
|
|
52
|
+
* mirror walk, instead of only cleaning the value.
|
|
53
|
+
*
|
|
54
|
+
* @default undefined — pure clean, no transform
|
|
55
|
+
*/
|
|
56
|
+
transform?: 'decode' | 'encode';
|
|
57
|
+
/**
|
|
58
|
+
* Codec transform functions collected during codegen, referenced from the
|
|
59
|
+
* generated source by index as `d.codecs[i]`
|
|
60
|
+
*/
|
|
61
|
+
codecs: Function[];
|
|
62
|
+
/**
|
|
63
|
+
* TypeCompiler is required when using Union
|
|
64
|
+
*
|
|
65
|
+
* Left as opt-in to reduce bundle size
|
|
66
|
+
* many end-user doesn't use Union
|
|
67
|
+
*
|
|
68
|
+
* @default undefined
|
|
69
|
+
*/
|
|
70
|
+
Compile?: typeof Compile;
|
|
71
|
+
typeCompilerWanred?: boolean;
|
|
72
|
+
modules?: TModule<{}>;
|
|
73
|
+
definitions: Record<string, AnySchema>;
|
|
74
|
+
/**
|
|
75
|
+
* Shared cyclic codegen state: generated per-definition mirror
|
|
76
|
+
* functions, grouped by `$defs` object identity
|
|
77
|
+
*/
|
|
78
|
+
cyclic: CyclicContext;
|
|
79
|
+
/**
|
|
80
|
+
* `$defs` group the current cyclic definition body is generated
|
|
81
|
+
* against, used to resolve `Ref` nodes to function calls
|
|
82
|
+
*/
|
|
83
|
+
cyclicDefs?: CyclicGroup;
|
|
84
|
+
recursion: number;
|
|
85
|
+
/**
|
|
86
|
+
* @default 8
|
|
87
|
+
*/
|
|
88
|
+
recursionLimit: number;
|
|
89
|
+
/**
|
|
90
|
+
* If incorrect type is passed to Union value, should it be removed?
|
|
91
|
+
*
|
|
92
|
+
* If you check a value later, it's recommended to set this to `false`
|
|
93
|
+
* otherwise, set this to true
|
|
94
|
+
*
|
|
95
|
+
* @default false
|
|
96
|
+
*/
|
|
97
|
+
removeUnknownUnionType: boolean;
|
|
84
98
|
}
|
|
85
|
-
|
|
99
|
+
declare function deepClone<T>(source: T, weak?: WeakMap<object, any>): T;
|
|
86
100
|
interface CyclicGroup {
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
defs: Record<string, AnySchema>;
|
|
102
|
+
names: Record<string, string>;
|
|
89
103
|
}
|
|
90
104
|
interface CyclicContext {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
105
|
+
groups: Map<object, CyclicGroup>;
|
|
106
|
+
fns: string[];
|
|
107
|
+
count: number;
|
|
94
108
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
109
|
+
interface Manifest {
|
|
110
|
+
source: string;
|
|
111
|
+
externals: {
|
|
112
|
+
unions: Validator<any, TSchema, unknown, unknown>[][];
|
|
113
|
+
codecs?: Function[];
|
|
114
|
+
hof?: Record<string, Function>;
|
|
115
|
+
};
|
|
101
116
|
}
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
declare const createMirror: <T extends TSchema, Emit extends boolean = false>(schema: T, {
|
|
118
|
+
Compile,
|
|
119
|
+
modules,
|
|
120
|
+
definitions,
|
|
121
|
+
sanitize,
|
|
122
|
+
recursionLimit,
|
|
123
|
+
removeUnknownUnionType,
|
|
124
|
+
emit,
|
|
125
|
+
decode,
|
|
126
|
+
encode
|
|
127
|
+
}?: Partial<Pick<Instruction<Emit>, "Compile" | "definitions" | "sanitize" | "modules" | "recursionLimit" | "removeUnknownUnionType" | "emit">> & {
|
|
128
|
+
/**
|
|
129
|
+
* Apply each codec's `~codec.decode` at codec leaves (parse input,
|
|
130
|
+
* e.g. numeric string → number) on top of the clean walk
|
|
131
|
+
*
|
|
132
|
+
* The value is assumed to have already passed `Check`
|
|
133
|
+
*
|
|
134
|
+
* @default false
|
|
135
|
+
*/
|
|
136
|
+
decode?: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Apply each codec's `~codec.encode` at codec leaves
|
|
139
|
+
*
|
|
140
|
+
* @default false
|
|
141
|
+
*/
|
|
142
|
+
encode?: boolean;
|
|
143
|
+
}) => Emit extends true ? Manifest : (v: Static<T>) => Static<T>;
|
|
144
|
+
//#endregion
|
|
145
|
+
export { Instruction, Manifest, createMirror, createMirror as default, deepClone, mergeObjectIntersection };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
|
|
2
|
+
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
const Kind = "~kind";
|
|
5
|
+
const Hint = "~hint";
|
|
6
|
+
const Codec = "~codec";
|
|
7
|
+
const isSpecialProperty = (name) => !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
8
|
+
const joinProperty = (v1, v2, isOptional = false) => {
|
|
9
|
+
if (typeof v2 === "number") return `${v1}[${v2}]`;
|
|
10
|
+
if (isSpecialProperty(v2)) return `${v1}${isOptional ? "?." : ""}[${JSON.stringify(v2)}]`;
|
|
11
|
+
return `${v1}${isOptional ? "?" : ""}.${v2}`;
|
|
12
|
+
};
|
|
13
|
+
const encodeProperty = (v) => isSpecialProperty(v) ? JSON.stringify(v) : v;
|
|
14
|
+
const sanitize = (key, sanitize = 0, schema) => {
|
|
15
|
+
if (schema.type !== "string" || schema.const || schema.trusted) return key;
|
|
16
|
+
let hof = "";
|
|
17
|
+
for (let i = sanitize - 1; i >= 0; i--) hof += `d.h${i}(`;
|
|
18
|
+
return hof + key + ")".repeat(sanitize);
|
|
19
|
+
};
|
|
20
|
+
const mergeObjectIntersection = (schema) => {
|
|
21
|
+
if (!schema.allOf || Kind in schema && (schema[Kind] !== "Intersect" || schema.type !== "object")) return schema;
|
|
22
|
+
const { allOf, ...newSchema } = schema;
|
|
23
|
+
newSchema.properties = {};
|
|
24
|
+
if (Kind in newSchema) newSchema[Kind] = "Object";
|
|
25
|
+
for (const type of allOf) {
|
|
26
|
+
if (type.type !== "object") continue;
|
|
27
|
+
const { properties, required, type: _, [Kind]: __, ...rest } = type;
|
|
28
|
+
if (required) newSchema.required = newSchema.required ? newSchema.required.concat(required) : required;
|
|
29
|
+
Object.assign(newSchema, rest);
|
|
30
|
+
for (const property in type.properties) newSchema.properties[property] = mergeObjectIntersection(type.properties[property]);
|
|
31
|
+
}
|
|
32
|
+
return newSchema;
|
|
33
|
+
};
|
|
34
|
+
const handleRecord = (schema, property, instruction) => {
|
|
35
|
+
const child = schema.patternProperties["^(.*)$"] ?? schema.patternProperties[Object.keys(schema.patternProperties)[0]];
|
|
36
|
+
if (!child) return property;
|
|
37
|
+
const i = instruction.array;
|
|
38
|
+
instruction.array++;
|
|
39
|
+
let v = `(()=>{const ar${i}s=Object.keys(${property}),ar${i}v={};for(let i=0;i<ar${i}s.length;i++){const ar${i}p=${property}[ar${i}s[i]];ar${i}v[ar${i}s[i]]=${mirror(child, `ar${i}p`, instruction)}`;
|
|
40
|
+
const optionals = instruction.optionalsInArray[i + 1];
|
|
41
|
+
if (optionals) {
|
|
42
|
+
for (let oi = 0; oi < optionals.length; oi++) {
|
|
43
|
+
const target = `ar${i}v[ar${i}s[i]]${optionals[oi]}`;
|
|
44
|
+
v += `;if(${target}===undefined)delete ${target}`;
|
|
45
|
+
}
|
|
46
|
+
instruction.optionalsInArray[i + 1] = [];
|
|
47
|
+
}
|
|
48
|
+
v += `}return ar${i}v})()`;
|
|
49
|
+
return v;
|
|
50
|
+
};
|
|
51
|
+
const handleTuple = (schema, property, instruction) => {
|
|
52
|
+
const i = instruction.array;
|
|
53
|
+
instruction.array++;
|
|
54
|
+
const isRoot = property === "v" && !instruction.fromUnion;
|
|
55
|
+
let v = "";
|
|
56
|
+
if (!isRoot) v = `(()=>{`;
|
|
57
|
+
v += `const ar${i}v=[`;
|
|
58
|
+
for (let i = 0; i < schema.length; i++) {
|
|
59
|
+
if (i !== 0) v += ",";
|
|
60
|
+
v += mirror(schema[i], joinProperty(property, i, instruction.parentIsOptional || instruction.fromUnion), instruction);
|
|
61
|
+
}
|
|
62
|
+
v += `];`;
|
|
63
|
+
if (!isRoot) v += `return ar${i}v})()`;
|
|
64
|
+
return v;
|
|
65
|
+
};
|
|
66
|
+
function deepClone(source, weak = /* @__PURE__ */ new WeakMap()) {
|
|
67
|
+
if (source === null || typeof source !== "object" || typeof source === "function") return source;
|
|
68
|
+
if (weak.has(source)) return weak.get(source);
|
|
69
|
+
if (Array.isArray(source)) {
|
|
70
|
+
const copy = new Array(source.length);
|
|
71
|
+
weak.set(source, copy);
|
|
72
|
+
for (let i = 0; i < source.length; i++) copy[i] = deepClone(source[i], weak);
|
|
73
|
+
return copy;
|
|
74
|
+
}
|
|
75
|
+
const keys = Object.keys(source).concat(Object.getOwnPropertySymbols(source));
|
|
76
|
+
const cloned = Object.create(null);
|
|
77
|
+
weak.set(source, cloned);
|
|
78
|
+
for (const key of keys) cloned[key] = deepClone(source[key], weak);
|
|
79
|
+
return cloned;
|
|
80
|
+
}
|
|
81
|
+
const handleCyclic = (schema, property, instruction) => {
|
|
82
|
+
const defs = schema.$defs;
|
|
83
|
+
let group = instruction.cyclic.groups.get(defs);
|
|
84
|
+
if (!group) {
|
|
85
|
+
group = {
|
|
86
|
+
defs,
|
|
87
|
+
names: {}
|
|
88
|
+
};
|
|
89
|
+
instruction.cyclic.groups.set(defs, group);
|
|
90
|
+
for (const name in defs) group.names[name] = `cy${instruction.cyclic.count++}`;
|
|
91
|
+
for (const name in defs) instruction.cyclic.fns.push(`function ${group.names[name]}(v){${mirror(defs[name], "v", {
|
|
92
|
+
...instruction,
|
|
93
|
+
cyclicDefs: group,
|
|
94
|
+
fromUnion: false,
|
|
95
|
+
parentIsOptional: false,
|
|
96
|
+
optionals: [],
|
|
97
|
+
optionalsInArray: [],
|
|
98
|
+
unionKeys: {},
|
|
99
|
+
array: 0,
|
|
100
|
+
recursion: 0
|
|
101
|
+
})}}`);
|
|
102
|
+
}
|
|
103
|
+
const fn = group.names[schema.$ref];
|
|
104
|
+
if (!fn) throw new Error(`[exact-mirror] cyclic reference "${schema.$ref}" is not found in $defs`);
|
|
105
|
+
return `(${property}==null?${property}:${fn}(${property}))`;
|
|
106
|
+
};
|
|
107
|
+
const withDefs = (type, group) => {
|
|
108
|
+
if (Kind in type) {
|
|
109
|
+
if (type[Kind] === "Cyclic") return type;
|
|
110
|
+
if (type[Kind] === "Ref" && type.$ref in group.defs) return Object.defineProperty({
|
|
111
|
+
$defs: group.defs,
|
|
112
|
+
$ref: type.$ref
|
|
113
|
+
}, Kind, { value: "Cyclic" });
|
|
114
|
+
}
|
|
115
|
+
let entry = "~check";
|
|
116
|
+
while (entry in group.defs) entry += "~";
|
|
117
|
+
const def = Object.create(Object.getPrototypeOf(type), Object.getOwnPropertyDescriptors(type));
|
|
118
|
+
def.$id = entry;
|
|
119
|
+
return Object.defineProperty({
|
|
120
|
+
$defs: {
|
|
121
|
+
...group.defs,
|
|
122
|
+
[entry]: def
|
|
123
|
+
},
|
|
124
|
+
$ref: entry
|
|
125
|
+
}, Kind, { value: "Cyclic" });
|
|
126
|
+
};
|
|
127
|
+
const handleUnion = (schemas, property, instruction) => {
|
|
128
|
+
if (instruction.Compile === void 0) {
|
|
129
|
+
if (!instruction.typeCompilerWanred) {
|
|
130
|
+
console.warn(/* @__PURE__ */ new Error("[exact-mirror] TypeBox's TypeCompiler is required to use Union"));
|
|
131
|
+
instruction.typeCompilerWanred = true;
|
|
132
|
+
}
|
|
133
|
+
return property;
|
|
134
|
+
}
|
|
135
|
+
instruction.unionKeys[property] = 1;
|
|
136
|
+
const ui = instruction.unions.length;
|
|
137
|
+
const typeChecks = instruction.unions[ui] = [];
|
|
138
|
+
let v = `(()=>{\n`;
|
|
139
|
+
const unwrapRef = (type) => {
|
|
140
|
+
if (!(Kind in type) || !type.$ref) return type;
|
|
141
|
+
if (type[Kind] === "This") return deepClone(instruction.definitions[type.$ref]);
|
|
142
|
+
return type;
|
|
143
|
+
};
|
|
144
|
+
let cleanThenCheck = "";
|
|
145
|
+
for (let i = 0; i < schemas.length; i++) {
|
|
146
|
+
let type = unwrapRef(schemas[i]);
|
|
147
|
+
if (Array.isArray(type.anyOf)) for (let i = 0; i < type.anyOf.length; i++) type.anyOf[i] = unwrapRef(type.anyOf[i]);
|
|
148
|
+
else if (type.items) if (Array.isArray(type.items)) for (let i = 0; i < type.items.length; i++) type.items[i] = unwrapRef(type.items[i]);
|
|
149
|
+
else type.items = unwrapRef(type.items);
|
|
150
|
+
typeChecks.push(instruction.Compile(instruction.cyclicDefs ? withDefs(type, instruction.cyclicDefs) : type));
|
|
151
|
+
v += `if(d.unions[${ui}][${i}].Check(${property})){return ${mirror(type, property, {
|
|
152
|
+
...instruction,
|
|
153
|
+
recursion: instruction.recursion + 1,
|
|
154
|
+
parentIsOptional: true,
|
|
155
|
+
fromUnion: true
|
|
156
|
+
})}}\n`;
|
|
157
|
+
cleanThenCheck += (i ? "" : "let ") + "tmp=" + mirror(type, property, {
|
|
158
|
+
...instruction,
|
|
159
|
+
recursion: instruction.recursion + 1,
|
|
160
|
+
parentIsOptional: true,
|
|
161
|
+
fromUnion: true
|
|
162
|
+
}) + `\nif(d.unions[${ui}][${i}].Check(tmp))return tmp\n`;
|
|
163
|
+
}
|
|
164
|
+
if (cleanThenCheck) v += cleanThenCheck;
|
|
165
|
+
v += `return ${instruction.removeUnknownUnionType ? "undefined" : property}`;
|
|
166
|
+
return v + `})()`;
|
|
167
|
+
};
|
|
168
|
+
const mirror = (schema, property, instruction) => {
|
|
169
|
+
if (!schema) return "";
|
|
170
|
+
const isRoot = property === "v" && !instruction.fromUnion;
|
|
171
|
+
const optionalsLength = instruction.optionals.length;
|
|
172
|
+
try {
|
|
173
|
+
if (instruction.transform && Codec in schema) {
|
|
174
|
+
const codec = schema[Codec][instruction.transform];
|
|
175
|
+
let ci = instruction.codecs.indexOf(codec);
|
|
176
|
+
if (ci === -1) ci = instruction.codecs.push(codec) - 1;
|
|
177
|
+
const body = mirrorNode(schema, `d.codecs[${ci}](${property})`, instruction);
|
|
178
|
+
return isRoot ? `return ${body}` : body;
|
|
179
|
+
}
|
|
180
|
+
if (Kind in schema && schema[Kind] === "Cyclic") {
|
|
181
|
+
const call = handleCyclic(schema, property, instruction);
|
|
182
|
+
return isRoot ? `return ${call}` : call;
|
|
183
|
+
}
|
|
184
|
+
return mirrorNode(schema, property, instruction);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
instruction.optionals.length = optionalsLength;
|
|
187
|
+
console.warn(/* @__PURE__ */ new Error("[exact-mirror] failed to generate mirror for a schema node, the node is passed through as-is. Please report this issue to https://github.com/elysiajs/exact-mirror/issues"), error);
|
|
188
|
+
return isRoot ? "return v" : property;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const mirrorNode = (schema, property, instruction) => {
|
|
192
|
+
const isRoot = property === "v" && !instruction.fromUnion;
|
|
193
|
+
if (instruction.cyclicDefs && Kind in schema && schema[Kind] === "Ref" && schema.$ref && schema.$ref in instruction.cyclicDefs.names) {
|
|
194
|
+
const call = `(${property}==null?${property}:${instruction.cyclicDefs.names[schema.$ref]}(${property}))`;
|
|
195
|
+
return isRoot ? `return ${call}` : call;
|
|
196
|
+
}
|
|
197
|
+
if (isRoot && schema.type !== "object" && schema.type !== "array" && !schema.anyOf) return `return ${sanitize("v", instruction.sanitize?.length, schema)}`;
|
|
198
|
+
if (instruction.recursion >= instruction.recursionLimit) return property;
|
|
199
|
+
let v = "";
|
|
200
|
+
if (schema.$id && Hint in schema) instruction.definitions[schema.$id] = schema;
|
|
201
|
+
switch (schema.type) {
|
|
202
|
+
case "object":
|
|
203
|
+
if (schema[Kind] === "Record") {
|
|
204
|
+
v = handleRecord(schema, property, instruction);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
schema = mergeObjectIntersection(schema);
|
|
208
|
+
if (!schema.properties) {
|
|
209
|
+
v = property;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
v += "{";
|
|
213
|
+
if (schema.additionalProperties) v += `...${property},`;
|
|
214
|
+
const keys = Object.keys(schema.properties);
|
|
215
|
+
for (let i = 0; i < keys.length; i++) {
|
|
216
|
+
const key = keys[i];
|
|
217
|
+
let isOptional = !schema.required || schema.required && !schema.required.includes(key) || Array.isArray(schema.properties[key].anyOf);
|
|
218
|
+
const name = joinProperty(property, key, instruction.parentIsOptional || instruction.fromUnion);
|
|
219
|
+
if (isOptional) {
|
|
220
|
+
const index = instruction.array;
|
|
221
|
+
if (property.startsWith("ar")) {
|
|
222
|
+
const dotIndex = name.indexOf(".");
|
|
223
|
+
let refName;
|
|
224
|
+
if (dotIndex >= 0) refName = name.slice(dotIndex);
|
|
225
|
+
else refName = name.slice(property.length);
|
|
226
|
+
const array = instruction.optionalsInArray;
|
|
227
|
+
if (array[index]) array[index].push(refName);
|
|
228
|
+
else array[index] = [refName];
|
|
229
|
+
} else instruction.optionals.push(name);
|
|
230
|
+
}
|
|
231
|
+
const child = schema.properties[key];
|
|
232
|
+
if (i !== 0) v += ",";
|
|
233
|
+
v += `${encodeProperty(key)}:${isOptional ? `${name}===undefined?undefined:` : ""}${mirror(child, name, {
|
|
234
|
+
...instruction,
|
|
235
|
+
recursion: instruction.recursion + 1,
|
|
236
|
+
parentIsOptional: isOptional
|
|
237
|
+
})}`;
|
|
238
|
+
}
|
|
239
|
+
v += "}";
|
|
240
|
+
break;
|
|
241
|
+
case "array":
|
|
242
|
+
if (!schema.items) {
|
|
243
|
+
v = property;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
if (schema.items.type !== "object" && schema.items.type !== "array") {
|
|
247
|
+
const cyclicItems = instruction.cyclicDefs !== void 0 && !Array.isArray(schema.items) && Kind in schema.items && schema.items[Kind] === "Ref" && schema.items.$ref !== void 0 && schema.items.$ref in instruction.cyclicDefs.names;
|
|
248
|
+
const codecItems = instruction.transform !== void 0 && !Array.isArray(schema.items) && Codec in schema.items;
|
|
249
|
+
if (Array.isArray(schema.items)) {
|
|
250
|
+
v = handleTuple(schema.items, property, instruction);
|
|
251
|
+
break;
|
|
252
|
+
} else if (!cyclicItems && !codecItems) {
|
|
253
|
+
if (isRoot && !Array.isArray(schema.items.anyOf)) return "return v";
|
|
254
|
+
else if (Kind in schema.items && schema.items.$ref && (schema.items[Kind] === "Ref" || schema.items[Kind] === "This")) v = mirror(deepClone(instruction.definitions[schema.items.$ref]), property, {
|
|
255
|
+
...instruction,
|
|
256
|
+
parentIsOptional: true,
|
|
257
|
+
recursion: instruction.recursion + 1
|
|
258
|
+
});
|
|
259
|
+
else if (!Array.isArray(schema.items.anyOf)) {
|
|
260
|
+
v = property;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const i = instruction.array;
|
|
266
|
+
instruction.array++;
|
|
267
|
+
let reference = property;
|
|
268
|
+
if (isRoot) v = `const ar${i}v=new Array(${property}.length);`;
|
|
269
|
+
else {
|
|
270
|
+
reference = `ar${i}s`;
|
|
271
|
+
v = `((${reference})=>{const ar${i}v=new Array(${reference}.length);`;
|
|
272
|
+
}
|
|
273
|
+
v += `for(let i=0;i<${reference}.length;i++){const ar${i}p=${reference}[i];ar${i}v[i]=${mirror(schema.items, `ar${i}p`, instruction)}`;
|
|
274
|
+
const optionals = instruction.optionalsInArray[i + 1];
|
|
275
|
+
if (optionals) {
|
|
276
|
+
for (let oi = 0; oi < optionals.length; oi++) {
|
|
277
|
+
const target = `ar${i}v[i]${optionals[oi]}`;
|
|
278
|
+
v += `;if(${target}===undefined)delete ${target}`;
|
|
279
|
+
}
|
|
280
|
+
instruction.optionalsInArray[i + 1] = [];
|
|
281
|
+
}
|
|
282
|
+
v += `}`;
|
|
283
|
+
if (!isRoot) v += `return ar${i}v})(${property})`;
|
|
284
|
+
break;
|
|
285
|
+
default:
|
|
286
|
+
if (schema.$ref && schema.$ref in instruction.definitions) return mirror(instruction.definitions[schema.$ref], property, instruction);
|
|
287
|
+
if (Array.isArray(schema.anyOf)) {
|
|
288
|
+
v = handleUnion(schema.anyOf, property, instruction);
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
v = sanitize(property, instruction.sanitize?.length, schema);
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
if (!isRoot) return v;
|
|
295
|
+
if (schema.type === "array") v = `${v}const x=ar0v;`;
|
|
296
|
+
else v = `const x=${v}\n`;
|
|
297
|
+
for (let i = 0; i < instruction.optionals.length; i++) {
|
|
298
|
+
const key = instruction.optionals[i];
|
|
299
|
+
const prop = key.slice(1);
|
|
300
|
+
v += `if(${key}===undefined`;
|
|
301
|
+
if (instruction.unionKeys[key]) v += `||x${prop}===undefined`;
|
|
302
|
+
const shouldQuestion = prop.charCodeAt(0) !== 63 && schema.type !== "array";
|
|
303
|
+
v += `)delete x${shouldQuestion ? prop.charCodeAt(0) === 91 ? "?." : "?" : ""}${prop}\n`;
|
|
304
|
+
}
|
|
305
|
+
return `${v}return x`;
|
|
306
|
+
};
|
|
307
|
+
const createMirror = (schema, { Compile, modules, definitions, sanitize, recursionLimit = 8, removeUnknownUnionType = false, emit, decode, encode } = Object.create(null)) => {
|
|
308
|
+
const unions = [];
|
|
309
|
+
const codecs = [];
|
|
310
|
+
const cyclic = {
|
|
311
|
+
groups: /* @__PURE__ */ new Map(),
|
|
312
|
+
fns: [],
|
|
313
|
+
count: 0
|
|
314
|
+
};
|
|
315
|
+
if (typeof sanitize === "function") sanitize = [sanitize];
|
|
316
|
+
const f = mirror(schema, "v", {
|
|
317
|
+
optionals: [],
|
|
318
|
+
optionalsInArray: [],
|
|
319
|
+
array: 0,
|
|
320
|
+
parentIsOptional: false,
|
|
321
|
+
unions,
|
|
322
|
+
unionKeys: Object.create(null),
|
|
323
|
+
Compile,
|
|
324
|
+
modules,
|
|
325
|
+
definitions: definitions ?? modules?.$defs ?? Object.create(null),
|
|
326
|
+
cyclic,
|
|
327
|
+
sanitize,
|
|
328
|
+
recursion: 0,
|
|
329
|
+
recursionLimit,
|
|
330
|
+
removeUnknownUnionType,
|
|
331
|
+
transform: decode ? "decode" : encode ? "encode" : void 0,
|
|
332
|
+
codecs
|
|
333
|
+
});
|
|
334
|
+
const fns = cyclic.fns.length ? cyclic.fns.join("\n") + "\n" : "";
|
|
335
|
+
if (!unions.length && !sanitize?.length && !codecs.length) {
|
|
336
|
+
if (emit) return {
|
|
337
|
+
source: fns + f,
|
|
338
|
+
externals: void 0
|
|
339
|
+
};
|
|
340
|
+
return Function("v", fns + f);
|
|
341
|
+
}
|
|
342
|
+
let hof;
|
|
343
|
+
if (sanitize?.length) {
|
|
344
|
+
hof = Object.create(null);
|
|
345
|
+
for (let i = 0; i < sanitize.length; i++) hof[`h${i}`] = sanitize[i];
|
|
346
|
+
}
|
|
347
|
+
const source = `${fns}return function mirror(v){${f}}`;
|
|
348
|
+
if (emit) return {
|
|
349
|
+
source,
|
|
350
|
+
externals: {
|
|
351
|
+
unions,
|
|
352
|
+
codecs: codecs.length ? codecs : void 0,
|
|
353
|
+
hof
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
const d = Object.create(null);
|
|
357
|
+
if (unions.length) d.unions = unions;
|
|
358
|
+
if (codecs.length) d.codecs = codecs;
|
|
359
|
+
if (hof) Object.assign(d, hof);
|
|
360
|
+
return Function("d", source)(d);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
//#endregion
|
|
364
|
+
exports.createMirror = createMirror;
|
|
365
|
+
exports.default = createMirror;
|
|
366
|
+
exports.deepClone = deepClone;
|
|
367
|
+
exports.mergeObjectIntersection = mergeObjectIntersection;
|