1 | function isClassConstructor(k) {
|
2 | return !!(k.prototype && k.prototype.constructor);
|
3 | }
|
4 | function isClassObject(k) {
|
5 | return !!(k.constructor && typeof k.constructor.name === 'string');
|
6 | }
|
7 | function compareNotFalsy(a, b) {
|
8 | return !!a && a === b;
|
9 | }
|
10 | function getFunctionName(R) {
|
11 | return R.toString().replace(/^function /, '').split('(')[0];
|
12 | }
|
13 | function functionToString(R) {
|
14 | return R.toString().replace(/^.+?\{/s, '').replace(/\}.*?$/s, '').trim().replace(/[\t\n\r ]*/g, ' ');
|
15 | }
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | function cyrb53(str, seed = 0) {
|
25 | let h1 = 0xdeadbeef ^ seed;
|
26 | let h2 = 0x41c6ce57 ^ seed;
|
27 | for (let i = 0, ch; i < str.length; i++) {
|
28 | ch = str.charCodeAt(i);
|
29 | h1 = Math.imul(h1 ^ ch, 2654435761);
|
30 | h2 = Math.imul(h2 ^ ch, 1597334677);
|
31 | }
|
32 | h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
33 | h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
34 | return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
35 | }
|
36 |
|
37 |
|
38 |
|
39 | function extractObjectFromClass(o, exclude = []) {
|
40 | Object.getOwnPropertyNames(o).map((prop) => {
|
41 | const val = o[prop];
|
42 | if (['constructor', ...exclude].includes(prop)) {
|
43 | return;
|
44 | }
|
45 | });
|
46 | return o;
|
47 | }
|
48 |
|
49 | function getTypeofDetailed(a) {
|
50 | const typeof_ = typeof a;
|
51 | const output = {
|
52 | typeof_,
|
53 | is: [],
|
54 | entry: a
|
55 | };
|
56 | if (typeof_ === 'object') {
|
57 | if (!a) {
|
58 | output.is = ['Null'];
|
59 | }
|
60 | else {
|
61 | |
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | output.id = a.constructor;
|
75 | if (output.id === Object) {
|
76 | output.is = ['object'];
|
77 | }
|
78 | else if (output.id === Array) {
|
79 | output.is = ['Array'];
|
80 | |
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | }
|
89 | else {
|
90 | output.is = ['Named'];
|
91 | }
|
92 | }
|
93 | }
|
94 | else if (typeof_ === 'function') {
|
95 | |
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 | if (!a.prototype) {
|
104 | |
105 |
|
106 |
|
107 | output.is = ['function'];
|
108 | }
|
109 | else {
|
110 | output.id = a.prototype.constructor;
|
111 | if (Object.getOwnPropertyNames(a.prototype).some((el) => el !== 'constructor')) {
|
112 | output.description = 'Can also be a class constructor in some cases';
|
113 | if (/[A-Z]/.test(a.prototype.constructor.name[0])) {
|
114 | output.is = ['Constructor', 'function'];
|
115 | }
|
116 | else {
|
117 | output.is = ['function', 'Constructor'];
|
118 | }
|
119 | }
|
120 | else {
|
121 | output.is = ['Constructor'];
|
122 | }
|
123 | }
|
124 | }
|
125 | else if (typeof_ === 'number') {
|
126 | if (isNaN(a)) {
|
127 | output.is = ['NaN'];
|
128 | }
|
129 | else if (!isFinite(a)) {
|
130 | output.is = ['Infinity'];
|
131 | }
|
132 | else if (Math.round(a) === a) {
|
133 | output.description = 'Integer';
|
134 | }
|
135 | }
|
136 | if (output.is.length === 0) {
|
137 | output.is = [typeof_];
|
138 | }
|
139 | return output;
|
140 | }
|
141 | function isArray(a, t) {
|
142 | return (t || getTypeofDetailed(a)).is[0] === 'Array';
|
143 | }
|
144 | function isObject(a, t) {
|
145 | return (t || getTypeofDetailed(a).is[0]) === 'object';
|
146 | }
|
147 |
|
148 | const MongoDateAdapter = {
|
149 | prefix: '',
|
150 | key: '$date',
|
151 | item: Date,
|
152 | fromJSON: (current) => new Date(current)
|
153 | };
|
154 | const MongoRegExpAdapter = {
|
155 | prefix: '',
|
156 | key: '$regex',
|
157 | item: RegExp,
|
158 | fromJSON(current, parent) {
|
159 | return new RegExp(current, parent.$options);
|
160 | },
|
161 | toJSON(_this, parent) {
|
162 | parent.$options = _this.flags;
|
163 | return _this.source;
|
164 | }
|
165 | };
|
166 |
|
167 | class Serialize {
|
168 | constructor(
|
169 | /**
|
170 | * For how to write a replacer and reviver, see
|
171 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
|
172 | */
|
173 | options = {}) {
|
174 | this.registrar = [];
|
175 | this.prefix = '__';
|
176 | this.stringifyFunction = JSON.stringify;
|
177 | this.parseFunction = JSON.parse;
|
178 | this.undefinedProxy = Symbol('undefined');
|
179 | this.prefix = typeof options.prefix === 'string' ? options.prefix : this.prefix;
|
180 | this.stringifyFunction = options.stringify || this.stringifyFunction;
|
181 | this.parseFunction = options.parse || this.parseFunction;
|
182 | this.register({ item: Date }, {
|
183 | item: RegExp,
|
184 | toJSON(_this) {
|
185 | const { source, flags } = _this;
|
186 | return { source, flags };
|
187 | },
|
188 | fromJSON(current) {
|
189 | const { source, flags } = current;
|
190 | return new RegExp(source, flags);
|
191 | }
|
192 | }, WriteOnlyFunctionAdapter, {
|
193 | item: Set,
|
194 | toJSON(_this) {
|
195 | return Array.from(_this);
|
196 | }
|
197 | }, {
|
198 | key: 'Infinity',
|
199 | toJSON(_this) {
|
200 | return _this.toString();
|
201 | },
|
202 | fromJSON(current) {
|
203 | return Number(current);
|
204 | }
|
205 | }, {
|
206 | key: 'bigint',
|
207 | toJSON(_this) {
|
208 | return _this.toString();
|
209 | },
|
210 | fromJSON(current) {
|
211 | return BigInt(current);
|
212 | }
|
213 | }, {
|
214 | key: 'symbol',
|
215 | toJSON(_this) {
|
216 | return {
|
217 | content: _this.toString(),
|
218 | rand: Math.random().toString(36).substr(2)
|
219 | };
|
220 | },
|
221 | fromJSON({ content }) {
|
222 | return Symbol(content.replace(/^Symbol\(/i, '').replace(/\)$/, ''));
|
223 | }
|
224 | }, {
|
225 | key: 'NaN',
|
226 | toJSON: () => 'NaN',
|
227 | fromJSON: () => NaN
|
228 | });
|
229 | }
|
230 | |
231 |
|
232 |
|
233 |
|
234 | register(...rs) {
|
235 | this.registrar.unshift(...rs.map((r) => {
|
236 | if (typeof r === 'function') {
|
237 | const { __prefix__: prefix, __key__: key, fromJSON, toJSON } = r;
|
238 | return {
|
239 | item: r,
|
240 | prefix,
|
241 | key,
|
242 | fromJSON,
|
243 | toJSON
|
244 | };
|
245 | }
|
246 | return r;
|
247 | }).map(({ item: R, prefix, key, toJSON, fromJSON }) => {
|
248 |
|
249 | fromJSON = typeof fromJSON === 'undefined'
|
250 | ? (arg) => isClassConstructor(R) ? new R(arg) : arg
|
251 | : (fromJSON || undefined);
|
252 | key = this.getKey(prefix, key || (isClassConstructor(R)
|
253 | ? R.prototype.constructor.name
|
254 | : typeof R === 'function' ? getFunctionName(R) : R));
|
255 | return {
|
256 | R,
|
257 | key,
|
258 | toJSON,
|
259 | fromJSON
|
260 | };
|
261 | }));
|
262 | }
|
263 | unregister(...rs) {
|
264 | this.registrar = this.registrar.filter(({ R, key }) => {
|
265 | return !rs.some((r) => {
|
266 | if (typeof r === 'function') {
|
267 | return !!R && r.constructor === R.constructor;
|
268 | }
|
269 | else {
|
270 | return compareNotFalsy(r.key, key) || (!!r.item && !!R && compareNotFalsy(r.item.constructor, R.constructor));
|
271 | }
|
272 | });
|
273 | });
|
274 | }
|
275 | |
276 |
|
277 |
|
278 |
|
279 | stringify(obj) {
|
280 | const clonedObj = this.deepCloneAndFindAndReplace([obj], 'jsonCompatible')[0];
|
281 | const keys = new Set();
|
282 | const getAndSortKeys = (a) => {
|
283 | if (a) {
|
284 | if (typeof a === 'object' && a.constructor.name === 'Object') {
|
285 | for (const k of Object.keys(a)) {
|
286 | keys.add(k);
|
287 | getAndSortKeys(a[k]);
|
288 | }
|
289 | }
|
290 | else if (Array.isArray(a)) {
|
291 | a.map((el) => getAndSortKeys(el));
|
292 | }
|
293 | }
|
294 | };
|
295 | getAndSortKeys(clonedObj);
|
296 | return this.stringifyFunction(clonedObj, Array.from(keys).sort());
|
297 | }
|
298 | hash(obj) {
|
299 | return cyrb53(this.stringify(obj)).toString(36);
|
300 | }
|
301 | clone(obj) {
|
302 | return this.deepCloneAndFindAndReplace([obj], 'clone')[0];
|
303 | }
|
304 | deepEqual(o1, o2) {
|
305 | const t1 = getTypeofDetailed(o1);
|
306 | const t2 = getTypeofDetailed(o2);
|
307 | if (t1.typeof_ === 'function' || t1.typeof_ === 'object') {
|
308 | if (isArray(o1, t1)) {
|
309 | return o1.map((el1, k) => {
|
310 | return this.deepEqual(el1, o2[k]);
|
311 | }).every((el) => el);
|
312 | }
|
313 | else if (isObject(o1, t1)) {
|
314 | return Object.entries(o1).map(([k, el1]) => {
|
315 | return this.deepEqual(el1, o2[k]);
|
316 | }).every((el) => el);
|
317 | }
|
318 | else {
|
319 | return this.hash(o1) === this.hash(o2);
|
320 | }
|
321 | }
|
322 | else if (t1.is[0] === 'NaN' && t2.is[0] === 'NaN') {
|
323 | |
324 |
|
325 |
|
326 | return this.hash(o1) === this.hash(o2);
|
327 | }
|
328 | return o1 === o2;
|
329 | }
|
330 | |
331 |
|
332 |
|
333 |
|
334 | parse(repr) {
|
335 | return this.parseFunction(repr, (_, v) => {
|
336 | if (v && typeof v === 'object') {
|
337 | for (const { key, fromJSON } of this.registrar) {
|
338 | if (v[key]) {
|
339 | return typeof fromJSON === 'function' ? fromJSON(v[key], v) : v;
|
340 | }
|
341 | }
|
342 | }
|
343 | return v;
|
344 | });
|
345 | }
|
346 | getKey(prefix, name) {
|
347 | return (typeof prefix === 'string' ? prefix : this.prefix) + (name || '');
|
348 | }
|
349 | |
350 |
|
351 |
|
352 |
|
353 |
|
354 | deepCloneAndFindAndReplace(o, type) {
|
355 | const t = getTypeofDetailed(o);
|
356 | if (t.is[0] === 'Array') {
|
357 | const obj = [];
|
358 | o.map((el, i) => {
|
359 | const v = this.deepCloneAndFindAndReplace(el, type);
|
360 | |
361 |
|
362 |
|
363 | if (v === this.undefinedProxy) {
|
364 | obj[i] = undefined;
|
365 | }
|
366 | else {
|
367 | obj[i] = v;
|
368 | }
|
369 | });
|
370 | return obj;
|
371 | }
|
372 | else if (t.is[0] === 'object') {
|
373 | const obj = {};
|
374 | Object.entries(o).map(([k, el]) => {
|
375 | const v = this.deepCloneAndFindAndReplace(el, type);
|
376 | if (v === undefined) ;
|
377 | else if (v === this.undefinedProxy) {
|
378 | obj[k] = undefined;
|
379 | }
|
380 | else {
|
381 | obj[k] = v;
|
382 | }
|
383 | });
|
384 | return obj;
|
385 | }
|
386 | else if (t.is[0] === 'Named') {
|
387 | const k = this.getKey(o.__prefix__, o.__name__ || o.constructor.name);
|
388 | for (const { R, key, toJSON, fromJSON } of this.registrar) {
|
389 | if ((!!R && compareNotFalsy(o.constructor, R)) ||
|
390 | compareNotFalsy(k, key)) {
|
391 | if (!fromJSON && type === 'clone') {
|
392 | continue;
|
393 | }
|
394 | const p = {};
|
395 | p[key] = ((toJSON || (!!R && R.prototype.toJSON) || o.toJSON || o.toString).bind(o))(o, p);
|
396 | if (p[key] === undefined) {
|
397 | return undefined;
|
398 | }
|
399 | else if (type === 'clone') {
|
400 | return fromJSON(p[key], p);
|
401 | }
|
402 | else {
|
403 | return p;
|
404 | }
|
405 | }
|
406 | }
|
407 | if (type === 'clone') {
|
408 | return o;
|
409 | }
|
410 | else {
|
411 | return {
|
412 | [k]: extractObjectFromClass(o)
|
413 | };
|
414 | }
|
415 | }
|
416 | else if (t.is[0] === 'Constructor' || t.is[0] === 'function' || t.is[0] === 'Infinity' ||
|
417 | t.is[0] === 'bigint' || t.is[0] === 'symbol' || t.is[0] === 'NaN') {
|
418 | let is = t.is[0];
|
419 | if (is === 'Constructor') {
|
420 | is = 'function';
|
421 | }
|
422 | |
423 |
|
424 |
|
425 |
|
426 | if (type === 'clone' && is !== 'function') {
|
427 | return o;
|
428 | }
|
429 | const k = this.getKey(undefined, is);
|
430 | const { R, toJSON, fromJSON } = this.registrar.filter(({ key }) => key === k)[0] || {};
|
431 | if (type === 'clone' && !fromJSON) {
|
432 | return o;
|
433 | }
|
434 | const p = {};
|
435 | p[k] = ((toJSON || (!!R && R.prototype.toJSON) || o.toJSON || o.toString).bind(o))(o, p);
|
436 | if (type === 'clone') {
|
437 | return fromJSON(p[k], p);
|
438 | }
|
439 | else if (p[k] === undefined) {
|
440 | return undefined;
|
441 | }
|
442 | else {
|
443 | return p;
|
444 | }
|
445 | }
|
446 | else if (t.is[0] === 'undefined') {
|
447 | if (type === 'clone') {
|
448 | return this.undefinedProxy;
|
449 | }
|
450 | const k = this.getKey(undefined, t.is[0]);
|
451 | const { R, toJSON } = this.registrar.filter(({ key }) => key === k)[0] || {};
|
452 | const p = {};
|
453 | p[k] = ((toJSON || (!!R && R.prototype.toJSON) || (() => { })).bind(o))(o, p);
|
454 | return p[k] === undefined ? undefined : p;
|
455 | }
|
456 | return o;
|
457 | }
|
458 | }
|
459 | const FullFunctionAdapter = {
|
460 | key: 'function',
|
461 | toJSON: (_this) => _this.toString().trim().replace(/\[native code\]/g, ' ').replace(/[\t\n\r ]+/g, ' '),
|
462 | fromJSON: (content) => {
|
463 |
|
464 | return new Function(`return ${content}`)();
|
465 | }
|
466 | };
|
467 | const WriteOnlyFunctionAdapter = Object.assign(Object.assign({}, FullFunctionAdapter), { fromJSON: null });
|
468 | const UndefinedAdapter = {
|
469 | key: 'undefined',
|
470 | toJSON: () => 'undefined',
|
471 | fromJSON: () => undefined
|
472 | };
|
473 |
|
474 | export { FullFunctionAdapter, MongoDateAdapter, MongoRegExpAdapter, Serialize, UndefinedAdapter, WriteOnlyFunctionAdapter, compareNotFalsy, cyrb53, extractObjectFromClass, functionToString, getFunctionName, isClassConstructor, isClassObject };
|
475 |
|