resurrect-esm 2.0.3 → 2.0.4
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/.github/workflows/publish.yml +4 -5
- package/README.md +11 -3
- package/package.json +1 -1
- package/resurrect.test.ts +19 -0
- package/resurrect.ts +32 -25
|
@@ -8,14 +8,13 @@ jobs:
|
|
|
8
8
|
runs-on: ubuntu-latest
|
|
9
9
|
steps:
|
|
10
10
|
- uses: actions/checkout@v4
|
|
11
|
-
- uses: actions/setup-node@v4
|
|
12
|
-
with:
|
|
13
|
-
node-version: "24"
|
|
14
|
-
registry-url: https://registry.npmjs.org
|
|
15
11
|
- uses: pnpm/action-setup@v4
|
|
16
12
|
with:
|
|
17
13
|
version: 10
|
|
18
|
-
|
|
14
|
+
- uses: actions/setup-node@v4
|
|
15
|
+
with:
|
|
16
|
+
node-version: 24
|
|
17
|
+
cache: pnpm
|
|
19
18
|
- run: pnpm install
|
|
20
19
|
- run: pnpm test
|
|
21
20
|
- run: pnpm prepare
|
package/README.md
CHANGED
|
@@ -9,13 +9,14 @@ An ES6 module port of ResurrectTS.
|
|
|
9
9
|
> * ResurrectError is now a top-level export, instead of living inside a Resurrect object.
|
|
10
10
|
> * NamespaceResolver is now a top-level export, instead of living inside the Resurrect static constructor namespace.
|
|
11
11
|
> * NamespaceResolver now has a new method, `getConstructor(name: string)`, which should return the constructor function to generate the object (it will not be called directly, so the parameters are irrelevant).
|
|
12
|
-
> The bug found in skeeto/resurrect-js#11 has been fixed.
|
|
12
|
+
> * The bug found in skeeto/resurrect-js#11 has been fixed.
|
|
13
13
|
|
|
14
14
|
ResurrectJS preserves object behavior (prototypes) and reference
|
|
15
15
|
circularity with a special JSON encoding. Unlike flat JSON, it can
|
|
16
16
|
also properly resurrect these types of values:
|
|
17
17
|
|
|
18
18
|
* Date
|
|
19
|
+
* URL
|
|
19
20
|
* RegExp
|
|
20
21
|
* `undefined`
|
|
21
22
|
* NaN, Infinity, -Infinity
|
|
@@ -121,7 +122,7 @@ unwrapped. This means extra properties added to these objects will not
|
|
|
121
122
|
be preserved.
|
|
122
123
|
|
|
123
124
|
Functions cannot ever be serialized. Resurrect will throw an error if
|
|
124
|
-
a function is found when traversing a data structure.
|
|
125
|
+
a function is found when traversing a data structure, rather than just silently dropping the property or replacing it with `null` like JSON.stringify does.
|
|
125
126
|
|
|
126
127
|
### Custom Resolvers
|
|
127
128
|
|
|
@@ -132,9 +133,13 @@ the Foo constructor in this example,
|
|
|
132
133
|
```ts
|
|
133
134
|
import { Resurrect, NamespaceResolver } from "resurrect-esm";
|
|
134
135
|
const namespace = {
|
|
135
|
-
Foo: class {
|
|
136
|
+
Foo: class Foo {
|
|
136
137
|
constructor() {
|
|
137
138
|
this.bar = true;
|
|
139
|
+
Foo.bax(this);
|
|
140
|
+
}
|
|
141
|
+
static bax(obj) {
|
|
142
|
+
|
|
138
143
|
}
|
|
139
144
|
}
|
|
140
145
|
};
|
|
@@ -149,6 +154,9 @@ find the name of the constructor in the namespace when given the
|
|
|
149
154
|
constructor. Keep in mind that using this form will bind the variable
|
|
150
155
|
Foo to the surrounding function within the body of Foo.
|
|
151
156
|
|
|
157
|
+
If you're using a bundler, you **must** enable the "keep names" option
|
|
158
|
+
for at least the classes that will be stringified, so that Resurrect.js can get the correct `.name` from the constructor functions. (For esbuild, the option is `--keep-names`.)
|
|
159
|
+
|
|
152
160
|
## See Also
|
|
153
161
|
|
|
154
162
|
* [HydrateJS](https://github.com/nanodeath/HydrateJS)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resurrect-esm",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ResurrectJS preserves object behavior (prototypes) and reference circularity with a special JSON encoding. Unlike flat JSON, it can also properly resurrect self-referential objects, objects with shared structure, Dates, RegExps, HTMLElements, NaN, Infinity, undefined (which won't get turned into null), and any other custom object type given the proper resolver.",
|
|
6
6
|
"repository": {
|
package/resurrect.test.ts
CHANGED
|
@@ -66,7 +66,26 @@ function suite(opt?: ResurrectOptions) {
|
|
|
66
66
|
expect(() => roundtrip(obj)).toThrow(new ResurrectError("Can't serialize functions."));
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
test("can serialize URL objects ok", () => {
|
|
70
|
+
const obj = { a_url_object: new URL("about:test") };
|
|
71
|
+
expect(roundtrip(obj).a_url_object).toBeInstanceOf(URL);
|
|
72
|
+
});
|
|
73
|
+
|
|
69
74
|
if (defOpts.revive) {
|
|
75
|
+
test("can revive classes with cleanup true and false", () => {
|
|
76
|
+
const ns = {
|
|
77
|
+
Foo: class Foo {
|
|
78
|
+
bar: number;
|
|
79
|
+
constructor() {
|
|
80
|
+
this.bar = 1;
|
|
81
|
+
Foo.bax();
|
|
82
|
+
}
|
|
83
|
+
static bax() { }
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
expect(roundtrip(new ns.Foo(), { cleanup: false, resolver: new NamespaceResolver(ns) })).toBeInstanceOf(ns.Foo);
|
|
87
|
+
expect(roundtrip(new ns.Foo(), { cleanup: true, resolver: new NamespaceResolver(ns) })).toBeInstanceOf(ns.Foo);
|
|
88
|
+
});
|
|
70
89
|
test("revive and custom resolver", () => {
|
|
71
90
|
class Dog {
|
|
72
91
|
constructor(public loudness: number, public sound: string) { }
|
package/resurrect.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* ResurrectJS preserves object behavior (prototypes) and reference
|
|
7
7
|
* circularity with a special JSON encoding. Unlike regular JSON,
|
|
8
|
-
* Date, RegExp, DOM objects, and `undefined` are also properly
|
|
8
|
+
* Date, URL, RegExp, DOM objects, and `undefined` are also properly
|
|
9
9
|
* preserved.
|
|
10
10
|
*
|
|
11
11
|
* ## Examples
|
|
@@ -41,16 +41,10 @@
|
|
|
41
41
|
* string. This option must be consistent between both
|
|
42
42
|
* serialization and deserialization.
|
|
43
43
|
*
|
|
44
|
-
* cleanup (false): Perform full property cleanup after both
|
|
45
|
-
* serialization and deserialization using the `delete`
|
|
46
|
-
* operator. This may cause performance penalties (breaking hidden
|
|
47
|
-
* classes in V8) on objects that ResurrectJS touches, so enable
|
|
48
|
-
* with care.
|
|
49
|
-
*
|
|
50
44
|
* revive (true): Restore behavior (__proto__) to objects that have
|
|
51
45
|
* been resurrected. If this is set to false during serialization,
|
|
52
46
|
* resurrection information will not be encoded. You still get
|
|
53
|
-
* circularity and Date support.
|
|
47
|
+
* circularity and Date/URL support.
|
|
54
48
|
*
|
|
55
49
|
* resolver (Resurrect.NamespaceResolver(window)): Converts between
|
|
56
50
|
* a name and a prototype. Create a custom resolver if your
|
|
@@ -60,8 +54,7 @@
|
|
|
60
54
|
* For example,
|
|
61
55
|
*
|
|
62
56
|
* const necromancer = new Resurrect({
|
|
63
|
-
* prefix:
|
|
64
|
-
* cleanup: true
|
|
57
|
+
* prefix: "__#",
|
|
65
58
|
* });
|
|
66
59
|
*
|
|
67
60
|
* ## Caveats
|
|
@@ -141,9 +134,10 @@ export class Resurrect {
|
|
|
141
134
|
private static _isNumber = Resurrect._is("Number") as (obj: any) => obj is number;
|
|
142
135
|
private static _isFunction = Resurrect._is("Function") as (obj: any) => obj is Function;
|
|
143
136
|
private static _isDate = Resurrect._is("Date") as (obj: any) => obj is Date;
|
|
137
|
+
private static _isURL = Resurrect._is("URL") as (obj: any) => obj is URL;
|
|
144
138
|
private static _isRegExp = Resurrect._is("RegExp") as (obj: any) => obj is RegExp;
|
|
145
139
|
private static _isObject = Resurrect._is("Object") as (obj: any) => obj is object;
|
|
146
|
-
private static
|
|
140
|
+
private static _isAtom(object: any) {
|
|
147
141
|
return !Resurrect._isObject(object) && !Resurrect._isArray(object);
|
|
148
142
|
}
|
|
149
143
|
private static _isPrimitive(object: any) {
|
|
@@ -153,6 +147,11 @@ export class Resurrect {
|
|
|
153
147
|
Resurrect._isBoolean(object);
|
|
154
148
|
}
|
|
155
149
|
|
|
150
|
+
private _cleanup() {
|
|
151
|
+
this._cleanups.forEach(e => e());
|
|
152
|
+
this._cleanups = [];
|
|
153
|
+
}
|
|
154
|
+
|
|
156
155
|
/**
|
|
157
156
|
* Create a reference (encoding) to an object.
|
|
158
157
|
*/
|
|
@@ -181,12 +180,12 @@ export class Resurrect {
|
|
|
181
180
|
throw new ResurrectError("Constructor mismatch!");
|
|
182
181
|
} else {
|
|
183
182
|
object[this._protocode] = constructor;
|
|
184
|
-
this._cleanups.push(() =>
|
|
183
|
+
this._cleanups.push(() => delete object[this._protocode]);
|
|
185
184
|
}
|
|
186
185
|
}
|
|
187
186
|
}
|
|
188
187
|
object[this._refcode] = this._table!.length;
|
|
189
|
-
this._cleanups.push(() =>
|
|
188
|
+
this._cleanups.push(() => delete object[this._refcode]);
|
|
190
189
|
this._table!.push(object);
|
|
191
190
|
return object[this._refcode];
|
|
192
191
|
}
|
|
@@ -245,21 +244,21 @@ export class Resurrect {
|
|
|
245
244
|
* @returns A fresh copy of root to be serialized.
|
|
246
245
|
*/
|
|
247
246
|
private _visit(root: any, transform: (obj: any) => any, replacer?: (k: string, v: any) => any): any {
|
|
248
|
-
if (Resurrect.
|
|
247
|
+
if (Resurrect._isAtom(root)) {
|
|
249
248
|
return transform(root);
|
|
250
249
|
} else if (!this._isTagged(root)) {
|
|
251
250
|
let copy: any = null;
|
|
252
251
|
if (Resurrect._isArray(root)) {
|
|
253
252
|
copy = [];
|
|
254
253
|
root[this._refcode as any] = this._tag(copy);
|
|
255
|
-
this._cleanups.push(() =>
|
|
254
|
+
this._cleanups.push(() => delete root[this._refcode as any]);
|
|
256
255
|
for (let i = 0; i < root.length; i++) {
|
|
257
256
|
copy.push(this._visit(root[i], transform, replacer));
|
|
258
257
|
}
|
|
259
258
|
} else { /* Object */
|
|
260
259
|
copy = Object.create(Object.getPrototypeOf(root));
|
|
261
260
|
root[this._refcode as any] = this._tag(copy);
|
|
262
|
-
this._cleanups.push(() =>
|
|
261
|
+
this._cleanups.push(() => delete root[this._refcode]);
|
|
263
262
|
for (const key of Object.getOwnPropertyNames(root)) {
|
|
264
263
|
let value = root[key];
|
|
265
264
|
if (replacer && value !== undefined) {
|
|
@@ -288,6 +287,8 @@ export class Resurrect {
|
|
|
288
287
|
return this._builder("Resurrect.Node", [new XMLSerializer().serializeToString(atom)]);
|
|
289
288
|
} else if (Resurrect._isDate(atom)) {
|
|
290
289
|
return this._builder("Date", [atom.toISOString()]);
|
|
290
|
+
} else if (Resurrect._isURL(atom)) {
|
|
291
|
+
return this._builder("URL", [atom.href]);
|
|
291
292
|
} else if (Resurrect._isRegExp(atom)) {
|
|
292
293
|
return this._builder("RegExp", ("" + atom).match(/\/(.+)\/([a-z]*)/)!.slice(1));
|
|
293
294
|
} else if (atom === undefined) {
|
|
@@ -317,14 +318,14 @@ export class Resurrect {
|
|
|
317
318
|
/**
|
|
318
319
|
* Serialize an arbitrary JavaScript object, carefully preserving it.
|
|
319
320
|
*/
|
|
320
|
-
stringify(object: any, replacer?: any[] | ((k: string, v: any) => any), space?: string) {
|
|
321
|
+
stringify(object: any, replacer?: any[] | ((k: string, v: any) => any), space?: string | number) {
|
|
321
322
|
if (Resurrect._isFunction(replacer)) {
|
|
322
323
|
replacer = this._replacerWrapper(replacer);
|
|
323
324
|
} else if (Resurrect._isArray(replacer)) {
|
|
324
325
|
const acceptKeys = replacer;
|
|
325
326
|
replacer = (k, v) => acceptKeys.includes(k) ? v : undefined;
|
|
326
327
|
}
|
|
327
|
-
if (Resurrect.
|
|
328
|
+
if (Resurrect._isAtom(object)) {
|
|
328
329
|
return JSON.stringify(this._handleAtom(object), replacer, space);
|
|
329
330
|
} else {
|
|
330
331
|
this._cleanups = [];
|
|
@@ -332,22 +333,24 @@ export class Resurrect {
|
|
|
332
333
|
try {
|
|
333
334
|
this._visit(object, this._handleAtom.bind(this), replacer);
|
|
334
335
|
} catch (e) {
|
|
335
|
-
this.
|
|
336
|
+
this._cleanup();
|
|
336
337
|
throw e;
|
|
337
338
|
} finally {
|
|
338
339
|
for (let i = 0; i < table.length; i++) {
|
|
339
340
|
if (this.cleanup) {
|
|
340
|
-
delete table[i][this._origcode][this._refcode];
|
|
341
|
+
delete table[i]?.[this._origcode]?.[this._refcode];
|
|
341
342
|
} else {
|
|
342
|
-
const obj = table[i][this._origcode];
|
|
343
|
+
const obj = table[i]?.[this._origcode];
|
|
343
344
|
if (obj) obj[this._refcode] = null;
|
|
344
345
|
}
|
|
345
|
-
delete table[i][this._refcode];
|
|
346
|
-
delete table[i][this._origcode];
|
|
346
|
+
delete table[i]?.[this._refcode];
|
|
347
|
+
delete table[i]?.[this._origcode];
|
|
347
348
|
}
|
|
348
349
|
this._table = null;
|
|
349
350
|
}
|
|
350
|
-
|
|
351
|
+
const s = JSON.stringify(table, null, space);
|
|
352
|
+
if (this.cleanup) this._cleanup();
|
|
353
|
+
return s;
|
|
351
354
|
}
|
|
352
355
|
}
|
|
353
356
|
|
|
@@ -397,7 +400,7 @@ export class Resurrect {
|
|
|
397
400
|
for (let i = 0; i < this._table.length; i++) {
|
|
398
401
|
const object = this._table[i];
|
|
399
402
|
for (const key of Object.getOwnPropertyNames(object)) {
|
|
400
|
-
if (!(Resurrect.
|
|
403
|
+
if (!(Resurrect._isAtom(object[key]))) {
|
|
401
404
|
object[key] = this._decode(object[key]);
|
|
402
405
|
}
|
|
403
406
|
}
|
|
@@ -424,6 +427,7 @@ export interface ResurrectOptions {
|
|
|
424
427
|
* important that you don't use any properties beginning with this
|
|
425
428
|
* string. This option must be consistent between both
|
|
426
429
|
* serialization and deserialization.
|
|
430
|
+
* @default "#"
|
|
427
431
|
*/
|
|
428
432
|
prefix?: string;
|
|
429
433
|
/**
|
|
@@ -432,6 +436,7 @@ export interface ResurrectOptions {
|
|
|
432
436
|
* operator. This may cause performance penalties (breaking hidden
|
|
433
437
|
* classes in V8) on objects that ResurrectJS touches, so enable
|
|
434
438
|
* with care.
|
|
439
|
+
* @default false
|
|
435
440
|
*/
|
|
436
441
|
cleanup?: boolean;
|
|
437
442
|
/**
|
|
@@ -439,6 +444,7 @@ export interface ResurrectOptions {
|
|
|
439
444
|
* been resurrected. If this is set to false during serialization,
|
|
440
445
|
* resurrection information will not be encoded. You still get
|
|
441
446
|
* circularity and Date support.
|
|
447
|
+
* @default true
|
|
442
448
|
*/
|
|
443
449
|
revive?: boolean;
|
|
444
450
|
/**
|
|
@@ -447,6 +453,7 @@ export interface ResurrectOptions {
|
|
|
447
453
|
*
|
|
448
454
|
* If you're using ES6 modules for your custom classes, you WILL need
|
|
449
455
|
* to use this!
|
|
456
|
+
* @default undefined
|
|
450
457
|
*/
|
|
451
458
|
resolver?: NamespaceResolver;
|
|
452
459
|
}
|