resurrect-esm 2.0.4 → 2.0.6

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.
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "cSpell.words": [
3
3
  "kybernetikos",
4
+ "minifiers",
4
5
  "registrator",
5
6
  "skeeto"
6
7
  ]
package/README.md CHANGED
@@ -5,11 +5,11 @@ An ES6 module port of ResurrectTS.
5
5
  > [!CAUTION]
6
6
  > This is *not* just a naive `exports.foo` => `export {foo}` rewrite. Some API changes have been made:
7
7
  >
8
- > * All of the "irrelevant" methods of Resurrect have been renamed using private names (that are then mangled further by esbuild).
9
- > * ResurrectError is now a top-level export, instead of living inside a Resurrect object.
10
- > * NamespaceResolver is now a top-level export, instead of living inside the Resurrect static constructor namespace.
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.
8
+ > * All of the "irrelevant" methods of Resurrect have been renamed using private `#names`.
9
+ > * `ResurrectError` is now a top-level export, instead of living inside a `Resurrect` object.
10
+ > * `NamespaceResolver` is now a top-level export, instead of living inside the `Resurrect` static constructor namespace.
11
+ > * `NamespaceResolver` now has a new method, `getConstructor(name: string)`, which should return the constructor function to generate the object (the constructor itself will not be called directly, so the parameters are irrelevant).
12
+ > * The bug found in [skeeto/resurrect-js#11][issue11] 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
@@ -62,30 +62,13 @@ const date = necromancer.resurrect(json);
62
62
  Options are provided to the constructor as an object with these
63
63
  properties:
64
64
 
65
- * *prefix* (`"#"`): A prefix string used for temporary properties added
66
- to objects during serialization and deserialization. It is
67
- important that you don't use any properties beginning with this
68
- string. This option must be consistent between both serialization
69
- and deserialization.
65
+ * *prefix* (default: `"#"`): A prefix string used for temporary properties added to objects during serialization and deserialization. It is important that you don't use any properties beginning with this string. This option also must be the same between both serialization and deserialization.
70
66
 
71
- * *cleanup* (`false`): Perform full property cleanup after both
72
- serialization and deserialization using the `delete` operator.
73
- This may cause performance penalties (i.e. breaking hidden
74
- classes in V8) on objects that ResurrectJS touches, so enable
75
- with care.
67
+ * *cleanup* (default: false): Perform full property cleanup after both serialization and deserialization using the `delete` operator. This may cause performance penalties (i.e. breaking hidden classes and forcing de-optimization) on objects that Resurrect touches, so enable with care.
76
68
 
77
- * *revive* (`true`): Restore behavior (`__proto__`) to objects that
78
- have been resurrected. If this is set to false during
79
- serialization, resurrection information will not be encoded. You
80
- still get circularity and Date support.
69
+ * *revive* (default: true): Restore behavior (`__proto__`) to objects that have been resurrected. If this is set to false during serialization, resurrection information will not be encoded. You still get circularity and Date/HTML/URL support.
81
70
 
82
- * *resolver* (`NamespaceResolver`): Converts between a name
83
- and a prototype. Create a custom resolver if your constructors
84
- are not stored in global variables. The resolver has two methods:
85
- getName(object) and getPrototype(string).
86
-
87
- > [!CAUTION]
88
- > If you're using ES6 modules for your custom classes, you MUST use a custom resolver since module scope is not global scope!
71
+ * *resolver* (`NamespaceResolver`, default undefined): Converts between a name and a prototype. Create a custom resolver if your constructors are not stored in global variables. The resolver has three methods: `getName(object)`, `getConstructor(string)`, and `getPrototype(string)`.
89
72
 
90
73
  For example,
91
74
 
@@ -99,66 +82,51 @@ const necromancer = new Resurrect({
99
82
 
100
83
  ## Methods
101
84
 
102
- Only two methods are significant when using ResurrectJS.
85
+ There are only two public methods:
103
86
 
104
- * `.stringify(object[, replacer[, space]])`: Serializes an arbitrary
105
- object or value into a string. The `replacer` and `space`
106
- arguments are the same as [JSON.stringify][json-mdn], being
107
- passed through to this method. Note that the replacer will *not*
108
- be called for ResurrectJS's intrusive keys.
87
+ * `.stringify(object[, replacer[, space]])`: Serializes an arbitrary object or value into a string. The `replacer` and `space` arguments are the same as [JSON.stringify][json-mdn], being passed through to this method. Note that the replacer will *not* be called for Resurrect's intrusive keys (i.e. the ones that are created using the `.prefix` option).
109
88
 
110
- * `.resurrect(string)`: Deserializes an object stored in a string by
111
- a previous call to `.stringify()`. Circularity and, optionally,
112
- behavior (prototype chain) will be restored.
89
+ * `.resurrect(string)`: Deserializes an object stored in a string by a previous call to `.stringify()`. Circularity and, optionally, behavior (prototype chain) will be restored.
113
90
 
114
91
  ## Restrictions
115
92
 
116
- With the default resolver, all constructors must be named and stored
117
- in the global variable under that name. This is required so that the
118
- prototypes can be looked up and reconnected at resurrection time.
93
+ With the default resolver, all constructors must be named and stored in the global scope under that name. This is required so that the prototypes can be looked up and reconnected at resurrection time.
94
+
95
+ > [!CAUTION]
96
+ > If you're using ES6 modules for your custom classes, you MUST use a custom resolver since module scope is not global scope!
119
97
 
120
- The wrapper objects Boolean, String, and Number will be
121
- unwrapped. This means extra properties added to these objects will not
122
- be preserved.
98
+ The wrapper objects Boolean, String, and Number will be unwrapped. This means extra properties added to these objects will not be preserved.
123
99
 
124
- Functions cannot ever be serialized. Resurrect will throw an error if
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.
100
+ Functions cannot ever be serialized. Resurrect will throw an error if 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.
126
101
 
127
102
  ### Custom Resolvers
128
103
 
129
- There is a caveat with the provided resolver, NamespaceResolver: all
130
- constructors *must* be explicitly named when defined. For example, see
131
- the Foo constructor in this example,
104
+ New in 2.0.5: the NamespaceResolver will now use the name on the namespace object of whatever you pass in instead of the `.name` of the object's constructor. This is to better support minifiers that rename the class and produce code output similar to this:
132
105
 
133
106
  ```ts
134
- import { Resurrect, NamespaceResolver } from "resurrect-esm";
135
- const namespace = {
136
- Foo: class Foo {
137
- constructor() {
138
- this.bar = true;
139
- Foo.bax(this);
140
- }
141
- static bax(obj) {
142
-
143
- }
107
+ import { Resurrect as r, NamespaceResolver as e } from "resurrect-esm";
108
+ var c = class d {
109
+ constructor() {
110
+ this.bar = true;
111
+ d.bax(this);
112
+ }
113
+ static bax(obj) {
114
+
144
115
  }
145
116
  };
146
- const necromancer = new Resurrect({
147
- resolver: new NamespaceResolver(namespace)
117
+ var n = {
118
+ Foo: c, // <== Always uses name given here
119
+ };
120
+ var a = new r({
121
+ resolver: new e(n)
148
122
  });
149
123
  ```
150
124
 
151
- The constructor been assigned to the Foo property *and* the function
152
- itself has been given a matching name. This is how the resolver will
153
- find the name of the constructor in the namespace when given the
154
- constructor. Keep in mind that using this form will bind the variable
155
- Foo to the surrounding function within the body of Foo.
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`.)
125
+ In the serializer above, the "name" given to the class will always be "Foo", even though the `.name` of the constructor is "c" and might be different on the next build.
159
126
 
160
127
  ## See Also
161
128
 
162
129
  * [HydrateJS](https://github.com/nanodeath/HydrateJS)
163
130
 
164
131
  [json-mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
132
+ [issue11]: https://github.com/skeeto/resurrect-js/issues/11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resurrect-esm",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
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": {
@@ -20,7 +20,7 @@
20
20
  "url": "https://github.com/dragoncoder047"
21
21
  }
22
22
  ],
23
- "license": "UNLICENSE",
23
+ "license": "CC0-1.0",
24
24
  "bugs": {
25
25
  "url": "https://github.com/dragoncoder047/resurrect-esm/issues"
26
26
  },
@@ -37,6 +37,9 @@
37
37
  "build": "pnpm esbuild --sourcemap --platform=browser --target=esnext --format=esm resurrect.ts --outfile=resurrect.js",
38
38
  "test": "pnpm bun test",
39
39
  "test:watch": "pnpm test --watch",
40
- "prepare": "pnpm build --minify --mangle-props=^_"
40
+ "prepare": "pnpm build --minify"
41
+ },
42
+ "dependencies": {
43
+ "lib0": "^0.2.117"
41
44
  }
42
45
  }
package/resurrect.test.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { GlobalRegistrator } from "@happy-dom/global-registrator";
2
2
  import { describe, expect, test } from "bun:test";
3
- import { NamespaceResolver, Resurrect, ResurrectError, ResurrectOptions } from "./resurrect";
3
+ import { FakeNode, NamespaceResolver, Resurrect, ResurrectError, ResurrectOptions } from "./resurrect";
4
4
 
5
5
  GlobalRegistrator.register();
6
6
 
@@ -55,7 +55,7 @@ function suite(opt?: ResurrectOptions) {
55
55
  });
56
56
 
57
57
  test("serialization with DOM elements", () => {
58
- const obj = new Resurrect.Node("<span id=foo><a id=1></a></span>");
58
+ const obj = new FakeNode("<span id=foo><a id=1></a></span>");
59
59
  const roundtripped = roundtrip(obj);
60
60
  expect(roundtripped).toBeInstanceOf(HTMLSpanElement);
61
61
  expect(roundtripped.firstChild).toBeInstanceOf(HTMLAnchorElement);
@@ -98,6 +98,16 @@ function suite(opt?: ResurrectOptions) {
98
98
  expect(roundtripped).toBeInstanceOf(Dog);
99
99
  expect(roundtripped.woof()).toEqual("wowwowwow!");
100
100
  });
101
+ test("revive/serialize works with minifier-renamed classes", () => {
102
+ class z {
103
+ constructor(public foo: number) { };
104
+ }
105
+ const obj = new z(1);
106
+ const roundtripped = roundtrip(obj, {
107
+ resolver: new NamespaceResolver({ Foo: z }),
108
+ });
109
+ expect(roundtripped).toBeInstanceOf(z);
110
+ })
101
111
  test("can't serialize anonymous classes", () => {
102
112
  const obj = new class {
103
113
  foo: number;
package/resurrect.ts CHANGED
@@ -1,203 +1,141 @@
1
1
  /**
2
- * # ResurrectJS
3
- * @version 1.0.3
4
- * @license Public Domain
5
- *
6
- * ResurrectJS preserves object behavior (prototypes) and reference
7
- * circularity with a special JSON encoding. Unlike regular JSON,
8
- * Date, URL, RegExp, DOM objects, and `undefined` are also properly
9
- * preserved.
10
- *
11
- * ## Examples
12
- *
13
- * function Foo() {}
14
- * Foo.prototype.greet = function() { return "hello"; };
15
- *
16
- * // Behavior is preserved:
17
- * const necromancer = new Resurrect();
18
- * const json = necromancer.stringify(new Foo());
19
- * const foo = necromancer.resurrect(json);
20
- * foo.greet(); // => "hello"
21
- *
22
- * // References to the same object are preserved:
23
- * json = necromancer.stringify([foo, foo]);
24
- * const array = necromancer.resurrect(json);
25
- * array[0] === array[1]; // => true
26
- * array[1].greet(); // => "hello"
27
- *
28
- * // Dates are restored properly
29
- * json = necromancer.stringify(new Date());
30
- * const date = necromancer.resurrect(json);
31
- * Object.prototype.toString.call(date); // => "[object Date]"
32
- *
33
- * ## Options
34
- *
35
- * Options are provided to the constructor as an object with these
36
- * properties:
37
- *
38
- * prefix ('#'): A prefix string used for temporary properties added
39
- * to objects during serialization and deserialization. It is
40
- * important that you don't use any properties beginning with this
41
- * string. This option must be consistent between both
42
- * serialization and deserialization.
43
- *
44
- * revive (true): Restore behavior (__proto__) to objects that have
45
- * been resurrected. If this is set to false during serialization,
46
- * resurrection information will not be encoded. You still get
47
- * circularity and Date/URL support.
48
- *
49
- * resolver (Resurrect.NamespaceResolver(window)): Converts between
50
- * a name and a prototype. Create a custom resolver if your
51
- * constructors are not stored in global variables. The resolver
52
- * has two methods: getName(object) and getPrototype(string).
53
- *
54
- * For example,
55
- *
56
- * const necromancer = new Resurrect({
57
- * prefix: "__#",
58
- * });
59
- *
60
- * ## Caveats
61
- *
62
- * * With the default resolver, all constructors must be named and
63
- * stored in the global variable under that name. This is required
64
- * so that the prototypes can be looked up and reconnected at
65
- * resurrection time.
66
- *
67
- * * The wrapper objects Boolean, String, and Number will be
68
- * unwrapped. This means extra properties added to these objects
69
- * will not be preserved.
70
- *
71
- * * Functions cannot ever be serialized. Resurrect will throw an
72
- * error if a function is found when traversing a data structure.
73
- *
2
+ * resurrect-esm, a fork of resurrect-ts, which is in turn a fork of resurrect.js
3
+ * @version 2.0.6
4
+ * @license CC0
74
5
  * @see http://nullprogram.com/blog/2013/03/28/
75
6
  */
76
7
 
8
+ import { parse, stringify } from "lib0/json";
9
+ import { keys } from "lib0/object";
10
+
11
+ const getPrototypeOf = Object.getPrototypeOf;
12
+ const getOwnPropertyNames = Object.getOwnPropertyNames;
13
+ const create = Object.create;
14
+ /**
15
+ * Portable access to the global object (window, global).
16
+ * Uses indirect eval.
17
+ * @constant
18
+ */
19
+ const GLOBAL: typeof globalThis = globalThis ?? (0, eval)("this");
20
+
21
+ /**
22
+ * Escape special regular expression characters in a string.
23
+ * Uses `RegExp.escape` if available, otherwise falls back to http://stackoverflow.com/a/6969486.
24
+ * @param {string} string
25
+ * @returns {string} The string escaped for exact matches.
26
+ */
27
+ const escapeRegExp = RegExp.escape ?? ((string: string) => string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"));
28
+
29
+ function is(type: string) {
30
+ const string = `[object ${type}]`;
31
+ return (obj: any) => {
32
+ return {}.toString.call(obj) === string;
33
+ };
34
+ }
35
+
36
+ const isArray = is("Array") as (obj: any) => obj is any[];
37
+ const isString = is("String") as (obj: any) => obj is string;
38
+ const isBoolean = is("Boolean") as (obj: any) => obj is boolean;
39
+ const isNumber = is("Number") as (obj: any) => obj is number;
40
+ const isFunction = is("Function") as (obj: any) => obj is Function;
41
+ const isDate = is("Date") as (obj: any) => obj is Date;
42
+ const isURL = is("URL") as (obj: any) => obj is URL;
43
+ const isRegExp = is("RegExp") as (obj: any) => obj is RegExp;
44
+ const isObject = is("Object") as (obj: any) => obj is object;
45
+ function isAtom(object: any) {
46
+ return !isObject(object) && !isArray(object);
47
+ }
48
+ function isPrimitive(object: any) {
49
+ return object == null ||
50
+ isNumber(object) ||
51
+ isString(object) ||
52
+ isBoolean(object);
53
+ }
54
+
55
+ /**
56
+ * Create a DOM node from HTML source; behaves like a constructor.
57
+ */
58
+ export const FakeNode = class {
59
+ constructor(html: string) {
60
+ const div = document.createElement("a");
61
+ div.innerHTML = html;
62
+ return div.firstChild as HTMLElement;
63
+ }
64
+ } as new (x: string) => HTMLElement;
65
+
66
+
77
67
  export class Resurrect {
78
- private _table: any[] | null;
79
- private _cleanups: (() => void)[] = [];
68
+ #table: any[] | null;
69
+ #cleanups: (() => void)[] = [];
80
70
  prefix: string;
81
71
  cleanup: boolean;
82
72
  revive: boolean;
83
- get _refcode() { return this.prefix + "#" };
84
- get _backrefcode() { return this.prefix + "=" };
85
- get _protocode() { return this.prefix + "+" };
86
- get _origcode() { return this.prefix + "&" };
87
- get _buildcode() { return this.prefix + "@" };
88
- get _valuecode() { return this.prefix + "_" };
73
+ get #refcode() { return this.prefix + "#" };
74
+ get #backrefcode() { return this.prefix + "=" };
75
+ get #protocode() { return this.prefix + "+" };
76
+ get #origcode() { return this.prefix + "&" };
77
+ get #buildcode() { return this.prefix + "@" };
78
+ get #valuecode() { return this.prefix + "_" };
89
79
  resolver: NamespaceResolver;
90
80
  constructor(opt: ResurrectOptions = {}) {
91
- this._table = null;
81
+ this.#table = null;
92
82
  this.prefix = opt.prefix ?? "#";
93
83
  this.cleanup = opt.cleanup ?? false;
94
84
  this.revive = opt.revive ?? true;
95
- this.resolver = opt.resolver ?? new NamespaceResolver(Resurrect.GLOBAL as any);
96
- }
97
-
98
- /**
99
- * Portable access to the global object (window, global).
100
- * Uses indirect eval.
101
- * @constant
102
- */
103
- static readonly GLOBAL: typeof globalThis = globalThis ?? (0, eval)("this");
104
-
105
- /**
106
- * Escape special regular expression characters in a string.
107
- * Uses `RegExp.escape` if available, otherwise falls back to http://stackoverflow.com/a/6969486.
108
- * @param {string} string
109
- * @returns {string} The string escaped for exact matches.
110
- */
111
- private static _escapeRegExp = RegExp.escape ?? ((string: string) => string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"));
112
-
113
- /**
114
- * Create a DOM node from HTML source; behaves like a constructor.
115
- */
116
- static Node = class {
117
- constructor(html: string) {
118
- const div = document.createElement("a");
119
- div.innerHTML = html;
120
- return div.firstChild as HTMLElement;
121
- }
122
- } as new (x: string) => HTMLElement;
123
-
124
- private static _is(type: string) {
125
- const string = `[object ${type}]`;
126
- return (obj: any) => {
127
- return {}.toString.call(obj) === string;
128
- };
129
- }
130
-
131
- private static _isArray = Resurrect._is("Array") as (obj: any) => obj is any[];
132
- private static _isString = Resurrect._is("String") as (obj: any) => obj is string;
133
- private static _isBoolean = Resurrect._is("Boolean") as (obj: any) => obj is boolean;
134
- private static _isNumber = Resurrect._is("Number") as (obj: any) => obj is number;
135
- private static _isFunction = Resurrect._is("Function") as (obj: any) => obj is Function;
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;
138
- private static _isRegExp = Resurrect._is("RegExp") as (obj: any) => obj is RegExp;
139
- private static _isObject = Resurrect._is("Object") as (obj: any) => obj is object;
140
- private static _isAtom(object: any) {
141
- return !Resurrect._isObject(object) && !Resurrect._isArray(object);
142
- }
143
- private static _isPrimitive(object: any) {
144
- return object == null ||
145
- Resurrect._isNumber(object) ||
146
- Resurrect._isString(object) ||
147
- Resurrect._isBoolean(object);
85
+ this.resolver = opt.resolver ?? new NamespaceResolver(GLOBAL as any);
148
86
  }
149
87
 
150
- private _cleanup() {
151
- this._cleanups.forEach(e => e());
152
- this._cleanups = [];
88
+ #cleanup() {
89
+ this.#cleanups.forEach(e => e());
90
+ this.#cleanups = [];
153
91
  }
154
92
 
155
93
  /**
156
94
  * Create a reference (encoding) to an object.
157
95
  */
158
- private _ref(object: any) {
96
+ #reference(object: any) {
159
97
  return {
160
- [this._backrefcode]: object === undefined ? -1 : object[this._refcode],
98
+ [this.#backrefcode]: object === undefined ? -1 : object[this.#refcode],
161
99
  };
162
100
  }
163
101
 
164
102
  /**
165
103
  * Lookup an object in the table by reference object.
166
104
  */
167
- private _deref(ref: any) {
168
- return this._table![ref[this._backrefcode]];
105
+ #dereference(ref: any) {
106
+ return this.#table![ref[this.#backrefcode]];
169
107
  }
170
108
 
171
109
  /**
172
110
  * Put a temporary identifier on an object and store it in the table.
173
111
  */
174
- private _tag(object: any): number {
112
+ #tagObject(object: any): number {
175
113
  if (this.revive) {
176
114
  const constructor = this.resolver.getName(object);
177
115
  if (constructor) {
178
- const proto = Object.getPrototypeOf(object);
116
+ const proto = getPrototypeOf(object);
179
117
  if (this.resolver.getPrototype(constructor) !== proto) {
180
118
  throw new ResurrectError("Constructor mismatch!");
181
119
  } else {
182
- object[this._protocode] = constructor;
183
- this._cleanups.push(() => delete object[this._protocode]);
120
+ object[this.#protocode] = constructor;
121
+ this.#cleanups.push(() => delete object[this.#protocode]);
184
122
  }
185
123
  }
186
124
  }
187
- object[this._refcode] = this._table!.length;
188
- this._cleanups.push(() => delete object[this._refcode]);
189
- this._table!.push(object);
190
- return object[this._refcode];
125
+ object[this.#refcode] = this.#table!.length;
126
+ this.#cleanups.push(() => delete object[this.#refcode]);
127
+ this.#table!.push(object);
128
+ return object[this.#refcode];
191
129
  }
192
130
 
193
131
  /**
194
132
  * Create a builder object (encoding) for serialization.
195
133
  * @param value The value to pass to the constructor.
196
134
  */
197
- private _builder(name: string, value: any): object {
135
+ #createBuilderObject(name: string, value: any): object {
198
136
  return {
199
- [this._buildcode]: name,
200
- [this._valuecode]: value
137
+ [this.#buildcode]: name,
138
+ [this.#valuecode]: value
201
139
  };
202
140
  }
203
141
 
@@ -206,11 +144,11 @@ export class Resurrect {
206
144
  * @see http://stackoverflow.com/a/14378462
207
145
  * @see http://nullprogram.com/blog/2013/03/24/
208
146
  */
209
- private _build(ref: any): any {
210
- const type = this.resolver.getConstructor(ref[this._buildcode]);
147
+ #buildFromEncoding(ref: any): any {
148
+ const type = this.resolver.getConstructor(ref[this.#buildcode]);
211
149
  /* Brilliant hack by kybernetikos: */
212
- const result: any = new (type.bind.apply(type, [null].concat(ref[this._valuecode]) as [any, any[]]))();
213
- if (Resurrect._isPrimitive(result)) {
150
+ const result: any = new (type.bind.apply(type, [null].concat(ref[this.#valuecode]) as [any, any[]]))();
151
+ if (isPrimitive(result)) {
214
152
  return result.valueOf(); // unwrap
215
153
  } else {
216
154
  return result;
@@ -221,11 +159,11 @@ export class Resurrect {
221
159
  * Dereference or build an object or value from an encoding.
222
160
  * @method
223
161
  */
224
- private _decode(ref: object): object | undefined {
225
- if (this._backrefcode in ref) {
226
- return this._deref(ref);
227
- } else if (this._buildcode in ref) {
228
- return this._build(ref);
162
+ #decodeReference(ref: object): object | undefined {
163
+ if (this.#backrefcode in ref) {
164
+ return this.#dereference(ref);
165
+ } else if (this.#buildcode in ref) {
166
+ return this.#buildFromEncoding(ref);
229
167
  } else {
230
168
  throw new ResurrectError("Unknown encoding.");
231
169
  }
@@ -234,8 +172,8 @@ export class Resurrect {
234
172
  /**
235
173
  * @returns {boolean} True if the provided object is tagged for serialization.
236
174
  */
237
- private _isTagged(object: any): boolean {
238
- return (this._refcode in object) && (object[this._refcode] != null);
175
+ #isTagged(object: any): boolean {
176
+ return (this.#refcode in object) && (object[this.#refcode] != null);
239
177
  }
240
178
 
241
179
 
@@ -243,58 +181,58 @@ export class Resurrect {
243
181
  * Visit root and all its ancestors, visiting atoms with f.
244
182
  * @returns A fresh copy of root to be serialized.
245
183
  */
246
- private _visit(root: any, transform: (obj: any) => any, replacer?: (k: string, v: any) => any): any {
247
- if (Resurrect._isAtom(root)) {
184
+ #visit(root: any, transform: (obj: any) => any, replacer?: (k: string, v: any) => any): any {
185
+ if (isAtom(root)) {
248
186
  return transform(root);
249
- } else if (!this._isTagged(root)) {
187
+ } else if (!this.#isTagged(root)) {
250
188
  let copy: any = null;
251
- if (Resurrect._isArray(root)) {
189
+ if (isArray(root)) {
252
190
  copy = [];
253
- root[this._refcode as any] = this._tag(copy);
254
- this._cleanups.push(() => delete root[this._refcode as any]);
191
+ root[this.#refcode as any] = this.#tagObject(copy);
192
+ this.#cleanups.push(() => delete root[this.#refcode as any]);
255
193
  for (let i = 0; i < root.length; i++) {
256
- copy.push(this._visit(root[i], transform, replacer));
194
+ copy.push(this.#visit(root[i], transform, replacer));
257
195
  }
258
196
  } else { /* Object */
259
- copy = Object.create(Object.getPrototypeOf(root));
260
- root[this._refcode as any] = this._tag(copy);
261
- this._cleanups.push(() => delete root[this._refcode]);
262
- for (const key of Object.getOwnPropertyNames(root)) {
197
+ copy = create(getPrototypeOf(root));
198
+ root[this.#refcode as any] = this.#tagObject(copy);
199
+ this.#cleanups.push(() => delete root[this.#refcode]);
200
+ for (const key of getOwnPropertyNames(root)) {
263
201
  let value = root[key];
264
202
  if (replacer && value !== undefined) {
265
203
  // Call replacer like JSON.stringify's replacer
266
204
  value = replacer.call(root, key, root[key]);
267
205
  if (value === undefined) continue; // Omit from result
268
206
  }
269
- copy[key] = this._visit(value, transform, replacer);
207
+ copy[key] = this.#visit(value, transform, replacer);
270
208
  }
271
209
  }
272
- copy[this._origcode] = root;
273
- return this._ref(copy);
210
+ copy[this.#origcode] = root;
211
+ return this.#reference(copy);
274
212
  } else {
275
- return this._ref(root);
213
+ return this.#reference(root);
276
214
  }
277
215
  }
278
216
 
279
217
  /**
280
218
  * Manage special atom values, possibly returning an encoding.
281
219
  */
282
- private _handleAtom(atom: any): any {
283
- const Node = Resurrect.GLOBAL.Node || function () { };
284
- if (Resurrect._isFunction(atom)) {
220
+ #encodeAtom(atom: any): any {
221
+ const Node = GLOBAL.Node || function () { };
222
+ if (isFunction(atom)) {
285
223
  throw new ResurrectError("Can't serialize functions.");
286
224
  } else if (atom instanceof Node) {
287
- return this._builder("Resurrect.Node", [new XMLSerializer().serializeToString(atom)]);
288
- } else if (Resurrect._isDate(atom)) {
289
- return this._builder("Date", [atom.toISOString()]);
290
- } else if (Resurrect._isURL(atom)) {
291
- return this._builder("URL", [atom.href]);
292
- } else if (Resurrect._isRegExp(atom)) {
293
- return this._builder("RegExp", ("" + atom).match(/\/(.+)\/([a-z]*)/)!.slice(1));
225
+ return this.#createBuilderObject("Resurrect.Node", [new XMLSerializer().serializeToString(atom)]);
226
+ } else if (isDate(atom)) {
227
+ return this.#createBuilderObject("Date", [atom.toISOString()]);
228
+ } else if (isURL(atom)) {
229
+ return this.#createBuilderObject("URL", [atom.href]);
230
+ } else if (isRegExp(atom)) {
231
+ return this.#createBuilderObject("RegExp", ("" + atom).match(/\/(.+)\/([a-z]*)/)!.slice(1));
294
232
  } else if (atom === undefined) {
295
- return this._ref(undefined);
296
- } else if (Resurrect._isNumber(atom) && (isNaN(atom) || !isFinite(atom))) {
297
- return this._builder("Number", ["" + atom]);
233
+ return this.#reference(undefined);
234
+ } else if (isNumber(atom) && (isNaN(atom) || !isFinite(atom))) {
235
+ return this.#createBuilderObject("Number", ["" + atom]);
298
236
  } else {
299
237
  return atom;
300
238
  }
@@ -304,8 +242,8 @@ export class Resurrect {
304
242
  * Hides intrusive keys from a user-supplied replacer.
305
243
  * @method
306
244
  */
307
- private _replacerWrapper<K extends string, V, U>(replacer: (k: K, v: V) => U): (k: K, v: V) => U | V {
308
- const skip = new RegExp("^" + Resurrect._escapeRegExp(this.prefix));
245
+ #replacerWrapper<K extends string, V, U>(replacer: (k: K, v: V) => U): (k: K, v: V) => U | V {
246
+ const skip = new RegExp("^" + escapeRegExp(this.prefix));
309
247
  return (k, v) => {
310
248
  if (skip.test(k)) {
311
249
  return v;
@@ -319,37 +257,37 @@ export class Resurrect {
319
257
  * Serialize an arbitrary JavaScript object, carefully preserving it.
320
258
  */
321
259
  stringify(object: any, replacer?: any[] | ((k: string, v: any) => any), space?: string | number) {
322
- if (Resurrect._isFunction(replacer)) {
323
- replacer = this._replacerWrapper(replacer);
324
- } else if (Resurrect._isArray(replacer)) {
260
+ if (isFunction(replacer)) {
261
+ replacer = this.#replacerWrapper(replacer);
262
+ } else if (isArray(replacer)) {
325
263
  const acceptKeys = replacer;
326
264
  replacer = (k, v) => acceptKeys.includes(k) ? v : undefined;
327
265
  }
328
- if (Resurrect._isAtom(object)) {
329
- return JSON.stringify(this._handleAtom(object), replacer, space);
266
+ if (isAtom(object)) {
267
+ return stringify(this.#encodeAtom(object), replacer, space);
330
268
  } else {
331
- this._cleanups = [];
332
- const table = this._table = [] as any[];
269
+ this.#cleanups = [];
270
+ const table = this.#table = [] as any[];
333
271
  try {
334
- this._visit(object, this._handleAtom.bind(this), replacer);
272
+ this.#visit(object, this.#encodeAtom.bind(this), replacer);
335
273
  } catch (e) {
336
- this._cleanup();
274
+ this.#cleanup();
337
275
  throw e;
338
276
  } finally {
339
277
  for (let i = 0; i < table.length; i++) {
340
278
  if (this.cleanup) {
341
- delete table[i]?.[this._origcode]?.[this._refcode];
279
+ delete table[i]?.[this.#origcode]?.[this.#refcode];
342
280
  } else {
343
- const obj = table[i]?.[this._origcode];
344
- if (obj) obj[this._refcode] = null;
281
+ const obj = table[i]?.[this.#origcode];
282
+ if (obj) obj[this.#refcode] = null;
345
283
  }
346
- delete table[i]?.[this._refcode];
347
- delete table[i]?.[this._origcode];
284
+ delete table[i]?.[this.#refcode];
285
+ delete table[i]?.[this.#origcode];
348
286
  }
349
- this._table = null;
287
+ this.#table = null;
350
288
  }
351
- const s = JSON.stringify(table, null, space);
352
- if (this.cleanup) this._cleanup();
289
+ const s = stringify(table, null, space);
290
+ if (this.cleanup) this.#cleanup();
353
291
  return s;
354
292
  }
355
293
  }
@@ -358,21 +296,21 @@ export class Resurrect {
358
296
  * Restore the `__proto__` of the given object to the proper value.
359
297
  * @method
360
298
  */
361
- private _fixPrototype<T extends object>(object: T): T {
362
- if (this._protocode in object) {
363
- const name = (object as any)[this._protocode];
299
+ #fixPrototype<T extends object>(object: T): T {
300
+ if (this.#protocode in object) {
301
+ const name = (object as any)[this.#protocode];
364
302
  const prototype = this.resolver.getPrototype(name);
365
303
  if ("__proto__" in object) {
366
304
  object.__proto__ = prototype;
367
305
  if (this.cleanup) {
368
- delete (object as any)[this._protocode];
306
+ delete (object as any)[this.#protocode];
369
307
  }
370
308
  return object;
371
309
  }
372
- // IE
373
- const copy = Object.create(prototype);
374
- for (const key of Object.getOwnPropertyNames(object)) {
375
- if (key !== this._protocode) {
310
+ // IE. Who uses this anyway...
311
+ const copy = create(prototype);
312
+ for (const key of getOwnPropertyNames(object)) {
313
+ if (key !== this.#protocode) {
376
314
  copy[key] = (object as any)[key];
377
315
  }
378
316
  }
@@ -386,34 +324,34 @@ export class Resurrect {
386
324
  */
387
325
  resurrect(string: string): any {
388
326
  let result = null;
389
- const data = JSON.parse(string);
327
+ const data = parse(string);
390
328
  try {
391
- if (Resurrect._isArray(data)) {
392
- this._table = data;
329
+ if (isArray(data)) {
330
+ this.#table = data;
393
331
  /* Restore __proto__. */
394
332
  if (this.revive) {
395
- for (let i = 0; i < this._table.length; i++) {
396
- this._table[i] = this._fixPrototype(this._table[i]);
333
+ for (let i = 0; i < this.#table.length; i++) {
334
+ this.#table[i] = this.#fixPrototype(this.#table[i]);
397
335
  }
398
336
  }
399
337
  /* Re-establish object references and construct atoms. */
400
- for (let i = 0; i < this._table.length; i++) {
401
- const object = this._table[i];
402
- for (const key of Object.getOwnPropertyNames(object)) {
403
- if (!(Resurrect._isAtom(object[key]))) {
404
- object[key] = this._decode(object[key]);
338
+ for (let i = 0; i < this.#table.length; i++) {
339
+ const object = this.#table[i];
340
+ for (const key of getOwnPropertyNames(object)) {
341
+ if (!(isAtom(object[key]))) {
342
+ object[key] = this.#decodeReference(object[key]);
405
343
  }
406
344
  }
407
345
  }
408
- result = this._table[0];
409
- } else if (Resurrect._isObject(data)) {
410
- this._table = [];
411
- result = this._decode(data);
346
+ result = this.#table[0];
347
+ } else if (isObject(data)) {
348
+ this.#table = [];
349
+ result = this.#decodeReference(data);
412
350
  } else {
413
351
  result = data;
414
352
  }
415
353
  } finally {
416
- this._table = null;
354
+ this.#table = null;
417
355
  }
418
356
  return result;
419
357
  }
@@ -483,9 +421,10 @@ export class NamespaceResolver {
483
421
  * @returns null if the constructor is `Object` or `Array`.
484
422
  */
485
423
  getName(object: object): string | null {
486
- let constructor = object.constructor.name;
424
+ const constructorFun = object.constructor;
425
+ let constructor = keys(this.scope).find(realName => this.scope[realName] === constructorFun) ?? constructorFun.name;
487
426
  if (constructor == null) { // IE
488
- constructor = /^\s*function\s*([A-Za-z0-9_$]*)/.exec("" + object.constructor)?.[1] ?? "";
427
+ constructor = /^\s*function\s*([A-Za-z0-9_$]*)/.exec("" + constructorFun)?.[1] ?? "";
489
428
  }
490
429
  if (constructor === "") {
491
430
  throw new ResurrectError("Can't serialize objects with anonymous constructors.");
@@ -499,8 +438,8 @@ export class NamespaceResolver {
499
438
  * object's constructor isn't in the namespace.
500
439
  */
501
440
  getConstructor(name: string): new (...args: any[]) => any {
502
- return (name === "Resurrect.Node" ? Resurrect.Node : this.scope[name] ?? name.split(/\./).reduce((object, name) => {
441
+ return (name === "Resurrect.Node" ? FakeNode : this.scope[name] ?? name.split(/\./).reduce((object, name) => {
503
442
  return (object as any)[name];
504
- }, Resurrect.GLOBAL)) as unknown as new () => any;
443
+ }, GLOBAL)) as unknown as new () => any;
505
444
  }
506
445
  }
package/tsconfig.json CHANGED
@@ -14,6 +14,9 @@
14
14
  "lib": [
15
15
  "esnext",
16
16
  "dom",
17
+ ],
18
+ "types": [
19
+ "bun",
17
20
  ]
18
21
  }
19
22
  }