seriall 1.0.0 → 1.1.0
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/README.md +467 -2
- package/dist/index.cjs +148 -94
- package/dist/index.d.cts +21 -8
- package/dist/index.d.mts +21 -8
- package/dist/index.mjs +148 -94
- package/package.json +7 -2
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/const/const.ts
|
|
2
|
-
var
|
|
3
|
-
var
|
|
2
|
+
var SIGNATURE_INDEX = 0;
|
|
3
|
+
var DATA_INDEX = 1;
|
|
4
4
|
var NO_TRANSFORM_DATA = void 0;
|
|
5
|
+
var PROTOCOL_VERSION = 1;
|
|
6
|
+
var LIB = "seriall";
|
|
5
7
|
var PRIORITY = {
|
|
6
8
|
CUSTOM_CLASS: 0,
|
|
7
9
|
NATIVE_CLASS: 1,
|
|
@@ -14,13 +16,13 @@ var isSpecialNumber = (value) => {
|
|
|
14
16
|
return [Infinity, -Infinity, NaN].includes(value) || Object.is(value, -0);
|
|
15
17
|
};
|
|
16
18
|
var encodeSpecialNumber = (value) => {
|
|
17
|
-
if (Number.isNaN(value)) return 0;
|
|
18
|
-
if (value === Infinity) return 1;
|
|
19
|
-
if (value === -Infinity) return 2;
|
|
20
|
-
if (Object.is(value, -0)) return 3;
|
|
19
|
+
if (Number.isNaN(value)) return [0];
|
|
20
|
+
if (value === Infinity) return [1];
|
|
21
|
+
if (value === -Infinity) return [2];
|
|
22
|
+
if (Object.is(value, -0)) return [3];
|
|
21
23
|
throw new Error(value + " is not a special number");
|
|
22
24
|
};
|
|
23
|
-
var decodeSpecialNumber = (value) => {
|
|
25
|
+
var decodeSpecialNumber = ([value]) => {
|
|
24
26
|
switch (value) {
|
|
25
27
|
case 0:
|
|
26
28
|
return NaN;
|
|
@@ -40,7 +42,7 @@ var isJsonPrimitive = (value) => {
|
|
|
40
42
|
if (typeof value === "number") {
|
|
41
43
|
return !isSpecialNumber(value);
|
|
42
44
|
}
|
|
43
|
-
return typeof value === "string" || typeof value === "boolean";
|
|
45
|
+
return typeof value === "string" || typeof value === "boolean" || value === null;
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
// src/utils/object.utils.ts
|
|
@@ -68,27 +70,30 @@ var serialize = (data, transformers) => {
|
|
|
68
70
|
const graph = [];
|
|
69
71
|
const seen = /* @__PURE__ */ new Map();
|
|
70
72
|
const addNodeToGraph = (node) => {
|
|
71
|
-
if (isJsonPrimitive(node)) {
|
|
72
|
-
const id2 = graph.length;
|
|
73
|
-
graph.push(node);
|
|
74
|
-
return id2;
|
|
75
|
-
}
|
|
76
73
|
let id = seen.get(node);
|
|
77
|
-
if (id !== void 0) {
|
|
74
|
+
if (id !== void 0 && !Object.is(node, -0)) {
|
|
78
75
|
return id;
|
|
79
76
|
}
|
|
80
77
|
id = graph.length;
|
|
81
78
|
seen.set(node, id);
|
|
79
|
+
if (isJsonPrimitive(node)) {
|
|
80
|
+
const id2 = graph.length;
|
|
81
|
+
graph.push(node);
|
|
82
|
+
return id2;
|
|
83
|
+
}
|
|
82
84
|
const transformer = transformers.find(
|
|
83
85
|
(transformer2) => transformer2.match(node)
|
|
84
86
|
);
|
|
85
87
|
if (transformer) {
|
|
86
|
-
graph.push(
|
|
88
|
+
graph.push([]);
|
|
87
89
|
const revivable = graph[id];
|
|
90
|
+
revivable[SIGNATURE_INDEX] = transformer.id;
|
|
88
91
|
const encoded = transformer.encode(node);
|
|
89
92
|
if (encoded !== NO_TRANSFORM_DATA) {
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
encoded.forEach((el, index) => {
|
|
94
|
+
const dataId = addNodeToGraph(el);
|
|
95
|
+
revivable[DATA_INDEX + index] = dataId;
|
|
96
|
+
});
|
|
92
97
|
}
|
|
93
98
|
return id;
|
|
94
99
|
}
|
|
@@ -101,21 +106,49 @@ var serialize = (data, transformers) => {
|
|
|
101
106
|
}
|
|
102
107
|
return id;
|
|
103
108
|
}
|
|
109
|
+
if (typeof node === "object") {
|
|
110
|
+
graph.push({});
|
|
111
|
+
const obj = graph[id];
|
|
112
|
+
for (const key in node) {
|
|
113
|
+
const elementId = addNodeToGraph(node[key]);
|
|
114
|
+
obj[key] = elementId;
|
|
115
|
+
}
|
|
116
|
+
return id;
|
|
117
|
+
}
|
|
104
118
|
throw new Error(
|
|
105
119
|
`No transformer found for the current value "${node?.toString()}", please add one`
|
|
106
120
|
);
|
|
107
121
|
};
|
|
108
122
|
addNodeToGraph(data);
|
|
109
|
-
|
|
123
|
+
const serialized = {
|
|
124
|
+
lib: LIB,
|
|
125
|
+
v: PROTOCOL_VERSION,
|
|
126
|
+
d: graph
|
|
127
|
+
};
|
|
128
|
+
return JSON.stringify(serialized);
|
|
110
129
|
};
|
|
111
130
|
|
|
112
131
|
// src/serialization/deserialize.ts
|
|
113
132
|
var deserialize = (data, transformers) => {
|
|
114
|
-
const
|
|
115
|
-
|
|
133
|
+
const { lib, v, d } = JSON.parse(data);
|
|
134
|
+
if (lib !== LIB) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Can't deserialize data, invalid lib metadata. Expected "${LIB}", got "${lib}"`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
if (v !== PROTOCOL_VERSION) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Can't deserialize data, invalid protocol version. Expected ${PROTOCOL_VERSION}, got ${v}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (!Array.isArray(d)) {
|
|
145
|
+
throw new Error("Invalid seriall payload: 'd' must be an array");
|
|
146
|
+
}
|
|
147
|
+
const graph = d;
|
|
148
|
+
const revived = [];
|
|
116
149
|
const reviveNode = (index) => {
|
|
117
|
-
if (revived
|
|
118
|
-
return revived
|
|
150
|
+
if (index in revived) {
|
|
151
|
+
return revived[index];
|
|
119
152
|
}
|
|
120
153
|
if (graph.length <= index || index < 0) {
|
|
121
154
|
throw new Error(`Invalid serialized graph reference: ${index}`);
|
|
@@ -124,37 +157,54 @@ var deserialize = (data, transformers) => {
|
|
|
124
157
|
if (isJsonPrimitive(node)) {
|
|
125
158
|
return node;
|
|
126
159
|
}
|
|
127
|
-
if (Array.isArray(node)) {
|
|
160
|
+
if (!Array.isArray(node) && typeof node === "object") {
|
|
161
|
+
const obj = {};
|
|
162
|
+
revived[index] = obj;
|
|
163
|
+
for (const key of Object.keys(node)) {
|
|
164
|
+
obj[key] = reviveNode(node[key]);
|
|
165
|
+
}
|
|
166
|
+
return obj;
|
|
167
|
+
}
|
|
168
|
+
if (typeof node[SIGNATURE_INDEX] !== "string") {
|
|
169
|
+
const typedNode2 = node;
|
|
128
170
|
const arr = [];
|
|
129
|
-
revived
|
|
130
|
-
for (const element of
|
|
171
|
+
revived[index] = arr;
|
|
172
|
+
for (const element of typedNode2) {
|
|
131
173
|
arr.push(reviveNode(element));
|
|
132
174
|
}
|
|
133
175
|
return arr;
|
|
134
176
|
}
|
|
135
|
-
const
|
|
136
|
-
const
|
|
177
|
+
const typedNode = node;
|
|
178
|
+
const transformerId = typedNode[SIGNATURE_INDEX];
|
|
179
|
+
const transformer = transformers[transformerId];
|
|
137
180
|
if (!transformerId) {
|
|
138
|
-
throw new Error(`No transformer found for node "${
|
|
181
|
+
throw new Error(`No transformer found for node "${typedNode}"`);
|
|
139
182
|
}
|
|
140
183
|
if (transformer === void 0) {
|
|
141
184
|
throw new Error(`No transformer found with id "${transformerId}"`);
|
|
142
185
|
}
|
|
143
186
|
const nonRecursiveTransformer = transformer;
|
|
144
|
-
|
|
145
|
-
if (dataId === void 0) {
|
|
187
|
+
if (!transformer.recursive && typedNode[DATA_INDEX] === void 0) {
|
|
146
188
|
const decoded = nonRecursiveTransformer.decode(NO_TRANSFORM_DATA);
|
|
147
|
-
revived
|
|
189
|
+
revived[index] = decoded;
|
|
148
190
|
return decoded;
|
|
149
191
|
}
|
|
150
192
|
if (!transformer.recursive) {
|
|
151
|
-
const
|
|
152
|
-
|
|
193
|
+
const revivedValues = new Array(typedNode.length - 1);
|
|
194
|
+
for (let i = 1; i < typedNode.length; i++) {
|
|
195
|
+
revivedValues[i - 1] = reviveNode(typedNode[i]);
|
|
196
|
+
}
|
|
197
|
+
const decoded = nonRecursiveTransformer.decode(revivedValues);
|
|
198
|
+
revived[index] = decoded;
|
|
153
199
|
return decoded;
|
|
154
200
|
}
|
|
155
201
|
const registerNode = (node2) => {
|
|
156
|
-
revived
|
|
157
|
-
|
|
202
|
+
revived[index] = node2;
|
|
203
|
+
const revivedValues = new Array(typedNode.length - 1);
|
|
204
|
+
for (let i = 1; i < typedNode.length; i++) {
|
|
205
|
+
revivedValues[i - 1] = reviveNode(typedNode[i]);
|
|
206
|
+
}
|
|
207
|
+
return revivedValues;
|
|
158
208
|
};
|
|
159
209
|
return transformer.decode(registerNode);
|
|
160
210
|
};
|
|
@@ -193,15 +243,15 @@ var primitivesTransformers = [
|
|
|
193
243
|
id: "sym",
|
|
194
244
|
priority: Transformer.PRIORITY.PRIMITIVE,
|
|
195
245
|
match: (node) => typeof node === "symbol",
|
|
196
|
-
encode: (node) =>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
246
|
+
encode: (node) => [
|
|
247
|
+
node.description,
|
|
248
|
+
Symbol.keyFor(node) === void 0 ? 0 : 1
|
|
249
|
+
],
|
|
200
250
|
decode: (encoded) => {
|
|
201
|
-
if (encoded
|
|
251
|
+
if (encoded[1] === 1) {
|
|
202
252
|
return Symbol.for(encoded.d);
|
|
203
253
|
}
|
|
204
|
-
return Symbol(encoded
|
|
254
|
+
return Symbol(encoded[0]);
|
|
205
255
|
}
|
|
206
256
|
}),
|
|
207
257
|
new Transformer({
|
|
@@ -218,49 +268,45 @@ var primitivesTransformers = [
|
|
|
218
268
|
encode: () => NO_TRANSFORM_DATA,
|
|
219
269
|
decode: () => void 0
|
|
220
270
|
}),
|
|
221
|
-
new Transformer({
|
|
222
|
-
id: "nul",
|
|
223
|
-
priority: Transformer.PRIORITY.PRIMITIVE,
|
|
224
|
-
match: (node) => node === null,
|
|
225
|
-
encode: () => NO_TRANSFORM_DATA,
|
|
226
|
-
decode: () => null
|
|
227
|
-
}),
|
|
228
271
|
new Transformer({
|
|
229
272
|
id: "big",
|
|
230
273
|
priority: Transformer.PRIORITY.PRIMITIVE,
|
|
231
274
|
match: (node) => typeof node === "bigint",
|
|
232
|
-
encode: (node) => node.toString(),
|
|
233
|
-
decode: (node) => BigInt(node)
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
275
|
+
encode: (node) => [node.toString()],
|
|
276
|
+
decode: ([node]) => BigInt(node)
|
|
277
|
+
})
|
|
278
|
+
];
|
|
279
|
+
var objectSymbolIndexingTransformer = new Transformer({
|
|
280
|
+
id: "obj",
|
|
281
|
+
priority: Transformer.PRIORITY.PRIMITIVE,
|
|
282
|
+
recursive: true,
|
|
283
|
+
match: (node) => !Array.isArray(node) && typeof node === "object",
|
|
284
|
+
encode: (node) => {
|
|
285
|
+
const obj = [];
|
|
286
|
+
for (const key of Reflect.ownKeys(node)) {
|
|
287
|
+
const value = node[key];
|
|
288
|
+
if (typeof value !== "function") {
|
|
243
289
|
obj.push([key, node[key]]);
|
|
244
290
|
}
|
|
245
|
-
return obj;
|
|
246
|
-
},
|
|
247
|
-
decode: (registerNode) => {
|
|
248
|
-
const obj = {};
|
|
249
|
-
const encoded = registerNode(obj);
|
|
250
|
-
for (const [key, value] of encoded) {
|
|
251
|
-
obj[key] = value;
|
|
252
|
-
}
|
|
253
|
-
return obj;
|
|
254
291
|
}
|
|
255
|
-
|
|
256
|
-
|
|
292
|
+
return obj;
|
|
293
|
+
},
|
|
294
|
+
decode: (registerNode) => {
|
|
295
|
+
const obj = {};
|
|
296
|
+
const encoded = registerNode(obj);
|
|
297
|
+
for (const [key, value] of encoded) {
|
|
298
|
+
obj[key] = value;
|
|
299
|
+
}
|
|
300
|
+
return obj;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
257
303
|
var nativeClassesTransformers = [
|
|
258
304
|
new Transformer({
|
|
259
305
|
id: "Dte",
|
|
260
306
|
priority: Transformer.PRIORITY.NATIVE_CLASS,
|
|
261
307
|
match: (node) => node instanceof Date,
|
|
262
|
-
encode: (node) => node.getTime(),
|
|
263
|
-
decode: (node) => new Date(node)
|
|
308
|
+
encode: (node) => [node.getTime()],
|
|
309
|
+
decode: ([node]) => new Date(node)
|
|
264
310
|
}),
|
|
265
311
|
new Transformer({
|
|
266
312
|
id: "Set",
|
|
@@ -296,29 +342,29 @@ var nativeClassesTransformers = [
|
|
|
296
342
|
id: "Rgx",
|
|
297
343
|
priority: Transformer.PRIORITY.NATIVE_CLASS,
|
|
298
344
|
match: (node) => node instanceof RegExp,
|
|
299
|
-
encode: (node) =>
|
|
300
|
-
decode: (node) => new RegExp(node
|
|
345
|
+
encode: (node) => [node.source, node.flags],
|
|
346
|
+
decode: (node) => new RegExp(node[0], node[1])
|
|
301
347
|
}),
|
|
302
348
|
new Transformer({
|
|
303
349
|
id: "Str",
|
|
304
350
|
priority: Transformer.PRIORITY.NATIVE_CLASS,
|
|
305
351
|
match: (node) => node instanceof String,
|
|
306
|
-
encode: (node) => node.valueOf(),
|
|
307
|
-
decode: (node) => new String(node)
|
|
352
|
+
encode: (node) => [node.valueOf()],
|
|
353
|
+
decode: ([node]) => new String(node)
|
|
308
354
|
}),
|
|
309
355
|
new Transformer({
|
|
310
356
|
id: "Num",
|
|
311
357
|
priority: Transformer.PRIORITY.NATIVE_CLASS,
|
|
312
358
|
match: (node) => node instanceof Number,
|
|
313
|
-
encode: (node) => node.valueOf(),
|
|
314
|
-
decode: (node) => new Number(node)
|
|
359
|
+
encode: (node) => [node.valueOf()],
|
|
360
|
+
decode: ([node]) => new Number(node)
|
|
315
361
|
}),
|
|
316
362
|
new Transformer({
|
|
317
363
|
id: "Bol",
|
|
318
364
|
priority: Transformer.PRIORITY.NATIVE_CLASS,
|
|
319
365
|
match: (node) => node instanceof Boolean,
|
|
320
|
-
encode: (node) => node.valueOf(),
|
|
321
|
-
decode: (node) => new Boolean(node)
|
|
366
|
+
encode: (node) => [node.valueOf()],
|
|
367
|
+
decode: ([node]) => new Boolean(node)
|
|
322
368
|
})
|
|
323
369
|
];
|
|
324
370
|
|
|
@@ -329,20 +375,21 @@ var SYMBOLS = { ENCODE, DECODE };
|
|
|
329
375
|
var SerializableClass = class {
|
|
330
376
|
constructor(..._) {
|
|
331
377
|
}
|
|
332
|
-
[ENCODE]
|
|
333
|
-
const encoded =
|
|
334
|
-
for (const key of
|
|
378
|
+
[ENCODE]() {
|
|
379
|
+
const encoded = [];
|
|
380
|
+
for (const key of Object.keys(this)) {
|
|
335
381
|
const value = this[key];
|
|
336
382
|
if (typeof value !== "function") {
|
|
337
|
-
encoded
|
|
383
|
+
encoded.push(this[key]);
|
|
338
384
|
}
|
|
339
385
|
}
|
|
340
386
|
return encoded;
|
|
341
|
-
}
|
|
387
|
+
}
|
|
342
388
|
static [DECODE] = function(registerNode) {
|
|
343
389
|
let revivedInstance = {};
|
|
344
390
|
const args = registerNode(revivedInstance);
|
|
345
|
-
|
|
391
|
+
const keys = Reflect.ownKeys(new this({}));
|
|
392
|
+
keys.forEach((key, index) => revivedInstance[key] = args[index]);
|
|
346
393
|
Object.setPrototypeOf(revivedInstance, this.prototype);
|
|
347
394
|
return revivedInstance;
|
|
348
395
|
};
|
|
@@ -361,23 +408,30 @@ var defaultOptions = {
|
|
|
361
408
|
enable: {
|
|
362
409
|
builtinPrimitiveTransformers: true,
|
|
363
410
|
builtinNativeClasses: true,
|
|
364
|
-
|
|
365
|
-
}
|
|
411
|
+
objectSymbolIndexing: false
|
|
412
|
+
},
|
|
413
|
+
classes: {}
|
|
366
414
|
};
|
|
367
415
|
var Serializer = class {
|
|
368
416
|
transformers = [];
|
|
369
|
-
|
|
417
|
+
transformersRecord = {};
|
|
370
418
|
constructor(options = {}) {
|
|
371
419
|
const opt = deepMerge(defaultOptions, options);
|
|
372
420
|
if (opt.enable.builtinPrimitiveTransformers)
|
|
373
421
|
primitivesTransformers.forEach(this.registerTransformer);
|
|
374
422
|
if (opt.enable.builtinNativeClasses)
|
|
375
423
|
nativeClassesTransformers.forEach(this.registerTransformer);
|
|
424
|
+
if (opt.enable.objectSymbolIndexing) {
|
|
425
|
+
this.registerTransformer(objectSymbolIndexingTransformer);
|
|
426
|
+
}
|
|
427
|
+
for (const className in opt.classes) {
|
|
428
|
+
this.registerClass(className, opt.classes[className]);
|
|
429
|
+
}
|
|
376
430
|
}
|
|
377
431
|
serialize = (data) => serialize(data, this.transformers);
|
|
378
|
-
deserialize = (data) => deserialize(data, this.
|
|
432
|
+
deserialize = (data) => deserialize(data, this.transformersRecord);
|
|
379
433
|
registerTransformer = (transformer) => {
|
|
380
|
-
if (
|
|
434
|
+
if (transformer.id in this.transformersRecord) {
|
|
381
435
|
throw new Error(
|
|
382
436
|
`A transformer with id "${transformer.id}" is already registered, chose another one`
|
|
383
437
|
);
|
|
@@ -386,11 +440,11 @@ var Serializer = class {
|
|
|
386
440
|
this.transformers = this.transformers.sort(
|
|
387
441
|
(a, b) => (a.priority ?? Infinity) - (b.priority ?? Infinity)
|
|
388
442
|
);
|
|
389
|
-
this.
|
|
443
|
+
this.transformersRecord[transformer.id] = transformer;
|
|
390
444
|
};
|
|
391
445
|
deregisterTransformer = (transformer) => {
|
|
392
446
|
const id = typeof transformer === "object" ? transformer.id : transformer;
|
|
393
|
-
if (!this.
|
|
447
|
+
if (!(id in this.transformersRecord)) {
|
|
394
448
|
throw new Error(
|
|
395
449
|
`No transformer found for id "${id}", cannot deregister it`
|
|
396
450
|
);
|
|
@@ -398,7 +452,7 @@ var Serializer = class {
|
|
|
398
452
|
this.transformers = this.transformers.filter(
|
|
399
453
|
(transformer2) => transformer2.id !== id
|
|
400
454
|
);
|
|
401
|
-
this.
|
|
455
|
+
delete this.transformersRecord[id];
|
|
402
456
|
};
|
|
403
457
|
registerClass = (name, clazz) => {
|
|
404
458
|
this.registerTransformer(createClassTransformer("$" + name, clazz));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "seriall",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A flexible js serializer, capable of serializing/deserializing any js data, while preserving referential integrity, non-json-primitive values, and even custom class instances",
|
|
5
5
|
"author": {
|
|
6
6
|
"email": "loic.duvail.pro@gmail.com",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"build:js": "node ./esbuild.js",
|
|
26
26
|
"build:types": "rollup -c",
|
|
27
27
|
"build:test": "node ./esbuild.test.js",
|
|
28
|
-
"
|
|
28
|
+
"build:benchmark": "node ./esbuild.benchmark.js",
|
|
29
|
+
"test": "npm run build:test && npx jest",
|
|
30
|
+
"bench": "npm run build:benchmark && node benchmark/benchmark.js /"
|
|
29
31
|
},
|
|
30
32
|
"keywords": [
|
|
31
33
|
"serializer",
|
|
@@ -43,10 +45,13 @@
|
|
|
43
45
|
"devDependencies": {
|
|
44
46
|
"@types/jest": "^30.0.0",
|
|
45
47
|
"@types/node": "^26.5.1",
|
|
48
|
+
"devalue": "^6.0.2",
|
|
46
49
|
"esbuild": "^0.28.2",
|
|
50
|
+
"flatted": "^3.4.4",
|
|
47
51
|
"jest": "^30.5.1",
|
|
48
52
|
"rollup": "^4.63.6",
|
|
49
53
|
"rollup-plugin-dts": "^6.5.1",
|
|
54
|
+
"superjson": "^2.2.6",
|
|
50
55
|
"typescript": "^6.0.3"
|
|
51
56
|
}
|
|
52
57
|
}
|