-
Notifications
You must be signed in to change notification settings - Fork 2
/
constructors.ts
112 lines (108 loc) · 3.78 KB
/
constructors.ts
1
2
3
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
34
35
36
37
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// deno-lint-ignore-file no-explicit-any no-explicit-any
/** a map with class names as keys and the respective serializers as values */
export type ConstructorMap<C = any> = Map<string, SerializableConstructor<C>>;
/** a serializer for values or buckets */
export type SerializableConstructor<C, V = any> =
| ValueConstructor<C, V>
| BucketContructor<C, V>;
/** common properties of all serializers */
export interface DecomposableConstructor<C, V = any> {
/** class constructor */
instance: new () => C;
/** converts an instance to a value array */
from(instance: C): V[];
}
/** a serializer for a value that does not contain nested values */
export interface ValueConstructor<C, V = any>
extends DecomposableConstructor<C, V> {
/** creates a class from a value array */
create(val: V[]): C;
}
export interface BucketContructor<C, V = any>
extends DecomposableConstructor<C, V> {
/** stubs a class instance that can be re-hydrated */
stub: () => C;
/** re-hydrates a class instance with its nested values */
hydrate: (stub: C, val: V[]) => void;
}
/** label for plain JS object types */
export const PLAIN_OBJECT_LABEL = "";
/**
* Globally available constructor map that holds sensible default serializers
* for the following values:
* - Error
* - Uint8Array
* - Map
* - Set
* - Date
* - RegExp
* - URL
*
* You can modify this if you want, but remember that it is global state.
*
* This map will be used as the default value for all calls to `parse`,
* `stringify`, `listify`, and `delistify` if you do not specify your own
* constructor map explictily.
*/
export const GLOBAL_CONSTRUCTOR_MAP: ConstructorMap = globalConstructorMap();
const enc = new TextEncoder();
const dec8 = new TextDecoder("utf-8");
/** creates a new global constructor map as found in GLOBAL_CONSTRUCTOR_MAP */
export function globalConstructorMap(): ConstructorMap {
const error: BucketContructor<Error> = {
instance: Error,
from: (err) => {
const res: unknown[] = [err.name, err.message];
if (err.stack !== undefined) res.push(err.stack);
if (err.cause !== undefined) {
if (err.stack === undefined) res.push(undefined);
res.push(err.cause);
}
return res;
},
stub: () => new Error(),
hydrate: (err, [name, message, stack, cause]) => {
err.name = name;
err.message = message;
if (stack === undefined) delete err.stack;
else err.stack = stack;
if (cause !== undefined) err.cause = cause;
},
};
const uint8Array: ValueConstructor<Uint8Array, string> = {
instance: Uint8Array,
from: (arr) => [btoa(dec8.decode(arr))],
create: ([data]) => enc.encode(atob(data)),
};
const map: BucketContructor<Map<any, any>, Array<[any, any]>> = {
instance: Map,
from: (m) => [...m.entries()],
stub: () => new Map(),
hydrate: (m, entries) => entries.forEach(([k, v]) => m.set(k, v)),
};
const set: BucketContructor<Set<any>, any[]> = {
instance: Set,
from: (s) => [...s.values()],
stub: () => new Set(),
hydrate: (s, values) => values.forEach((v) => s.add(v)),
};
const date: ValueConstructor<Date, string> = {
instance: Date,
from: (d) => [d.toJSON()],
create: ([json]) => new Date(json),
};
const regex: ValueConstructor<RegExp, string> = {
instance: RegExp as unknown as new () => RegExp,
from: ({ source, flags }) => flags ? [source, flags] : [source],
create: ([source, flags]) => new RegExp(source, flags),
};
const url: ValueConstructor<URL, string> = {
instance: URL as unknown as new () => URL,
from: (url) => [url.href],
create: ([href]) => new URL(href),
};
const res: ConstructorMap = new Map();
const constructors = [error, uint8Array, map, set, date, regex, url];
for (const c of constructors) res.set(c.instance.name, c);
return res;
}