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.
@@ -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
- cache: on
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",
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 isAtom(object: any) {
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(() => { delete object[this._protocode as any]; })
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(() => { delete object[this._refcode as any]; })
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.isAtom(root)) {
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(() => { delete root[this._refcode as any]; })
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(() => { delete root[this._refcode as any]; })
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.isAtom(object)) {
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._cleanups.forEach(e => e());
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
- return JSON.stringify(table, null, space);
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.isAtom(object[key]))) {
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
  }