hyperstorage-js 5.1.0 → 6.0.1

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/dist/index.mjs CHANGED
@@ -1,3 +1,861 @@
1
+ class DoubleIndexedKV {
2
+ constructor() {
3
+ this.keyToValue = new Map();
4
+ this.valueToKey = new Map();
5
+ }
6
+ set(key, value) {
7
+ this.keyToValue.set(key, value);
8
+ this.valueToKey.set(value, key);
9
+ }
10
+ getByKey(key) {
11
+ return this.keyToValue.get(key);
12
+ }
13
+ getByValue(value) {
14
+ return this.valueToKey.get(value);
15
+ }
16
+ clear() {
17
+ this.keyToValue.clear();
18
+ this.valueToKey.clear();
19
+ }
20
+ }
21
+
22
+ class Registry {
23
+ constructor(generateIdentifier) {
24
+ this.generateIdentifier = generateIdentifier;
25
+ this.kv = new DoubleIndexedKV();
26
+ }
27
+ register(value, identifier) {
28
+ if (this.kv.getByValue(value)) {
29
+ return;
30
+ }
31
+ if (!identifier) {
32
+ identifier = this.generateIdentifier(value);
33
+ }
34
+ this.kv.set(identifier, value);
35
+ }
36
+ clear() {
37
+ this.kv.clear();
38
+ }
39
+ getIdentifier(value) {
40
+ return this.kv.getByValue(value);
41
+ }
42
+ getValue(identifier) {
43
+ return this.kv.getByKey(identifier);
44
+ }
45
+ }
46
+
47
+ class ClassRegistry extends Registry {
48
+ constructor() {
49
+ super(c => c.name);
50
+ this.classToAllowedProps = new Map();
51
+ }
52
+ register(value, options) {
53
+ if (typeof options === 'object') {
54
+ if (options.allowProps) {
55
+ this.classToAllowedProps.set(value, options.allowProps);
56
+ }
57
+ super.register(value, options.identifier);
58
+ }
59
+ else {
60
+ super.register(value, options);
61
+ }
62
+ }
63
+ getAllowedProps(value) {
64
+ return this.classToAllowedProps.get(value);
65
+ }
66
+ }
67
+
68
+ function valuesOfObj(record) {
69
+ if ('values' in Object) {
70
+ // eslint-disable-next-line es5/no-es6-methods
71
+ return Object.values(record);
72
+ }
73
+ const values = [];
74
+ // eslint-disable-next-line no-restricted-syntax
75
+ for (const key in record) {
76
+ if (record.hasOwnProperty(key)) {
77
+ values.push(record[key]);
78
+ }
79
+ }
80
+ return values;
81
+ }
82
+ function find(record, predicate) {
83
+ const values = valuesOfObj(record);
84
+ if ('find' in values) {
85
+ // eslint-disable-next-line es5/no-es6-methods
86
+ return values.find(predicate);
87
+ }
88
+ const valuesNotNever = values;
89
+ for (let i = 0; i < valuesNotNever.length; i++) {
90
+ const value = valuesNotNever[i];
91
+ if (predicate(value)) {
92
+ return value;
93
+ }
94
+ }
95
+ return undefined;
96
+ }
97
+ function forEach(record, run) {
98
+ Object.entries(record).forEach(([key, value]) => run(value, key));
99
+ }
100
+ function includes(arr, value) {
101
+ return arr.indexOf(value) !== -1;
102
+ }
103
+ function findArr(record, predicate) {
104
+ for (let i = 0; i < record.length; i++) {
105
+ const value = record[i];
106
+ if (predicate(value)) {
107
+ return value;
108
+ }
109
+ }
110
+ return undefined;
111
+ }
112
+
113
+ class CustomTransformerRegistry {
114
+ constructor() {
115
+ this.transfomers = {};
116
+ }
117
+ register(transformer) {
118
+ this.transfomers[transformer.name] = transformer;
119
+ }
120
+ findApplicable(v) {
121
+ return find(this.transfomers, transformer => transformer.isApplicable(v));
122
+ }
123
+ findByName(name) {
124
+ return this.transfomers[name];
125
+ }
126
+ }
127
+
128
+ const getType$1 = (payload) => Object.prototype.toString.call(payload).slice(8, -1);
129
+ const isUndefined = (payload) => typeof payload === 'undefined';
130
+ const isNull = (payload) => payload === null;
131
+ const isPlainObject$1 = (payload) => {
132
+ if (typeof payload !== 'object' || payload === null)
133
+ return false;
134
+ if (payload === Object.prototype)
135
+ return false;
136
+ if (Object.getPrototypeOf(payload) === null)
137
+ return true;
138
+ return Object.getPrototypeOf(payload) === Object.prototype;
139
+ };
140
+ const isEmptyObject = (payload) => isPlainObject$1(payload) && Object.keys(payload).length === 0;
141
+ const isArray$1 = (payload) => Array.isArray(payload);
142
+ const isString = (payload) => typeof payload === 'string';
143
+ const isNumber = (payload) => typeof payload === 'number' && !isNaN(payload);
144
+ const isBoolean = (payload) => typeof payload === 'boolean';
145
+ const isRegExp = (payload) => payload instanceof RegExp;
146
+ const isMap = (payload) => payload instanceof Map;
147
+ const isSet = (payload) => payload instanceof Set;
148
+ const isSymbol = (payload) => getType$1(payload) === 'Symbol';
149
+ const isDate = (payload) => payload instanceof Date && !isNaN(payload.valueOf());
150
+ const isError = (payload) => payload instanceof Error;
151
+ const isNaNValue = (payload) => typeof payload === 'number' && isNaN(payload);
152
+ const isPrimitive = (payload) => isBoolean(payload) ||
153
+ isNull(payload) ||
154
+ isUndefined(payload) ||
155
+ isNumber(payload) ||
156
+ isString(payload) ||
157
+ isSymbol(payload);
158
+ const isBigint = (payload) => typeof payload === 'bigint';
159
+ const isInfinite = (payload) => payload === Infinity || payload === -Infinity;
160
+ const isTypedArray = (payload) => ArrayBuffer.isView(payload) && !(payload instanceof DataView);
161
+ const isURL = (payload) => payload instanceof URL;
162
+
163
+ const escapeKey = (key) => key.replace(/\\/g, '\\\\').replace(/\./g, '\\.');
164
+ const stringifyPath = (path) => path
165
+ .map(String)
166
+ .map(escapeKey)
167
+ .join('.');
168
+ const parsePath = (string, legacyPaths) => {
169
+ const result = [];
170
+ let segment = '';
171
+ for (let i = 0; i < string.length; i++) {
172
+ let char = string.charAt(i);
173
+ if (!legacyPaths && char === '\\') {
174
+ const escaped = string.charAt(i + 1);
175
+ if (escaped === '\\') {
176
+ segment += '\\';
177
+ i++;
178
+ continue;
179
+ }
180
+ else if (escaped !== '.') {
181
+ throw Error('invalid path');
182
+ }
183
+ }
184
+ const isEscapedDot = char === '\\' && string.charAt(i + 1) === '.';
185
+ if (isEscapedDot) {
186
+ segment += '.';
187
+ i++;
188
+ continue;
189
+ }
190
+ const isEndOfSegment = char === '.';
191
+ if (isEndOfSegment) {
192
+ result.push(segment);
193
+ segment = '';
194
+ continue;
195
+ }
196
+ segment += char;
197
+ }
198
+ const lastSegment = segment;
199
+ result.push(lastSegment);
200
+ return result;
201
+ };
202
+
203
+ function simpleTransformation(isApplicable, annotation, transform, untransform) {
204
+ return {
205
+ isApplicable,
206
+ annotation,
207
+ transform,
208
+ untransform,
209
+ };
210
+ }
211
+ const simpleRules = [
212
+ simpleTransformation(isUndefined, 'undefined', () => null, () => undefined),
213
+ simpleTransformation(isBigint, 'bigint', v => v.toString(), v => {
214
+ if (typeof BigInt !== 'undefined') {
215
+ return BigInt(v);
216
+ }
217
+ console.error('Please add a BigInt polyfill.');
218
+ return v;
219
+ }),
220
+ simpleTransformation(isDate, 'Date', v => v.toISOString(), v => new Date(v)),
221
+ simpleTransformation(isError, 'Error', (v, superJson) => {
222
+ const baseError = {
223
+ name: v.name,
224
+ message: v.message,
225
+ };
226
+ if ('cause' in v) {
227
+ baseError.cause = v.cause;
228
+ }
229
+ superJson.allowedErrorProps.forEach(prop => {
230
+ baseError[prop] = v[prop];
231
+ });
232
+ return baseError;
233
+ }, (v, superJson) => {
234
+ const e = new Error(v.message, { cause: v.cause });
235
+ e.name = v.name;
236
+ e.stack = v.stack;
237
+ superJson.allowedErrorProps.forEach(prop => {
238
+ e[prop] = v[prop];
239
+ });
240
+ return e;
241
+ }),
242
+ simpleTransformation(isRegExp, 'regexp', v => '' + v, regex => {
243
+ const body = regex.slice(1, regex.lastIndexOf('/'));
244
+ const flags = regex.slice(regex.lastIndexOf('/') + 1);
245
+ return new RegExp(body, flags);
246
+ }),
247
+ simpleTransformation(isSet, 'set',
248
+ // (sets only exist in es6+)
249
+ // eslint-disable-next-line es5/no-es6-methods
250
+ v => [...v.values()], v => new Set(v)),
251
+ simpleTransformation(isMap, 'map', v => [...v.entries()], v => new Map(v)),
252
+ simpleTransformation((v) => isNaNValue(v) || isInfinite(v), 'number', v => {
253
+ if (isNaNValue(v)) {
254
+ return 'NaN';
255
+ }
256
+ if (v > 0) {
257
+ return 'Infinity';
258
+ }
259
+ else {
260
+ return '-Infinity';
261
+ }
262
+ }, Number),
263
+ simpleTransformation((v) => v === 0 && 1 / v === -Infinity, 'number', () => {
264
+ return '-0';
265
+ }, Number),
266
+ simpleTransformation(isURL, 'URL', v => v.toString(), v => new URL(v)),
267
+ ];
268
+ function compositeTransformation(isApplicable, annotation, transform, untransform) {
269
+ return {
270
+ isApplicable,
271
+ annotation,
272
+ transform,
273
+ untransform,
274
+ };
275
+ }
276
+ const symbolRule = compositeTransformation((s, superJson) => {
277
+ if (isSymbol(s)) {
278
+ const isRegistered = !!superJson.symbolRegistry.getIdentifier(s);
279
+ return isRegistered;
280
+ }
281
+ return false;
282
+ }, (s, superJson) => {
283
+ const identifier = superJson.symbolRegistry.getIdentifier(s);
284
+ return ['symbol', identifier];
285
+ }, v => v.description, (_, a, superJson) => {
286
+ const value = superJson.symbolRegistry.getValue(a[1]);
287
+ if (!value) {
288
+ throw new Error('Trying to deserialize unknown symbol');
289
+ }
290
+ return value;
291
+ });
292
+ const constructorToName = [
293
+ Int8Array,
294
+ Uint8Array,
295
+ Int16Array,
296
+ Uint16Array,
297
+ Int32Array,
298
+ Uint32Array,
299
+ Float32Array,
300
+ Float64Array,
301
+ Uint8ClampedArray,
302
+ ].reduce((obj, ctor) => {
303
+ obj[ctor.name] = ctor;
304
+ return obj;
305
+ }, {});
306
+ const typedArrayRule = compositeTransformation(isTypedArray, v => ['typed-array', v.constructor.name], v => [...v], (v, a) => {
307
+ const ctor = constructorToName[a[1]];
308
+ if (!ctor) {
309
+ throw new Error('Trying to deserialize unknown typed array');
310
+ }
311
+ return new ctor(v);
312
+ });
313
+ function isInstanceOfRegisteredClass(potentialClass, superJson) {
314
+ if (potentialClass?.constructor) {
315
+ const isRegistered = !!superJson.classRegistry.getIdentifier(potentialClass.constructor);
316
+ return isRegistered;
317
+ }
318
+ return false;
319
+ }
320
+ const classRule = compositeTransformation(isInstanceOfRegisteredClass, (clazz, superJson) => {
321
+ const identifier = superJson.classRegistry.getIdentifier(clazz.constructor);
322
+ return ['class', identifier];
323
+ }, (clazz, superJson) => {
324
+ const allowedProps = superJson.classRegistry.getAllowedProps(clazz.constructor);
325
+ if (!allowedProps) {
326
+ return { ...clazz };
327
+ }
328
+ const result = {};
329
+ allowedProps.forEach(prop => {
330
+ result[prop] = clazz[prop];
331
+ });
332
+ return result;
333
+ }, (v, a, superJson) => {
334
+ const clazz = superJson.classRegistry.getValue(a[1]);
335
+ if (!clazz) {
336
+ throw new Error(`Trying to deserialize unknown class '${a[1]}' - check https://github.com/blitz-js/superjson/issues/116#issuecomment-773996564`);
337
+ }
338
+ return Object.assign(Object.create(clazz.prototype), v);
339
+ });
340
+ const customRule = compositeTransformation((value, superJson) => {
341
+ return !!superJson.customTransformerRegistry.findApplicable(value);
342
+ }, (value, superJson) => {
343
+ const transformer = superJson.customTransformerRegistry.findApplicable(value);
344
+ return ['custom', transformer.name];
345
+ }, (value, superJson) => {
346
+ const transformer = superJson.customTransformerRegistry.findApplicable(value);
347
+ return transformer.serialize(value);
348
+ }, (v, a, superJson) => {
349
+ const transformer = superJson.customTransformerRegistry.findByName(a[1]);
350
+ if (!transformer) {
351
+ throw new Error('Trying to deserialize unknown custom value');
352
+ }
353
+ return transformer.deserialize(v);
354
+ });
355
+ const compositeRules = [classRule, symbolRule, customRule, typedArrayRule];
356
+ const transformValue = (value, superJson) => {
357
+ const applicableCompositeRule = findArr(compositeRules, rule => rule.isApplicable(value, superJson));
358
+ if (applicableCompositeRule) {
359
+ return {
360
+ value: applicableCompositeRule.transform(value, superJson),
361
+ type: applicableCompositeRule.annotation(value, superJson),
362
+ };
363
+ }
364
+ const applicableSimpleRule = findArr(simpleRules, rule => rule.isApplicable(value, superJson));
365
+ if (applicableSimpleRule) {
366
+ return {
367
+ value: applicableSimpleRule.transform(value, superJson),
368
+ type: applicableSimpleRule.annotation,
369
+ };
370
+ }
371
+ return undefined;
372
+ };
373
+ const simpleRulesByAnnotation = {};
374
+ simpleRules.forEach(rule => {
375
+ simpleRulesByAnnotation[rule.annotation] = rule;
376
+ });
377
+ const untransformValue = (json, type, superJson) => {
378
+ if (isArray$1(type)) {
379
+ switch (type[0]) {
380
+ case 'symbol':
381
+ return symbolRule.untransform(json, type, superJson);
382
+ case 'class':
383
+ return classRule.untransform(json, type, superJson);
384
+ case 'custom':
385
+ return customRule.untransform(json, type, superJson);
386
+ case 'typed-array':
387
+ return typedArrayRule.untransform(json, type, superJson);
388
+ default:
389
+ throw new Error('Unknown transformation: ' + type);
390
+ }
391
+ }
392
+ else {
393
+ const transformation = simpleRulesByAnnotation[type];
394
+ if (!transformation) {
395
+ throw new Error('Unknown transformation: ' + type);
396
+ }
397
+ return transformation.untransform(json, superJson);
398
+ }
399
+ };
400
+
401
+ const getNthKey = (value, n) => {
402
+ if (n > value.size)
403
+ throw new Error('index out of bounds');
404
+ const keys = value.keys();
405
+ while (n > 0) {
406
+ keys.next();
407
+ n--;
408
+ }
409
+ return keys.next().value;
410
+ };
411
+ function validatePath(path) {
412
+ if (includes(path, '__proto__')) {
413
+ throw new Error('__proto__ is not allowed as a property');
414
+ }
415
+ if (includes(path, 'prototype')) {
416
+ throw new Error('prototype is not allowed as a property');
417
+ }
418
+ if (includes(path, 'constructor')) {
419
+ throw new Error('constructor is not allowed as a property');
420
+ }
421
+ }
422
+ const getDeep = (object, path) => {
423
+ validatePath(path);
424
+ for (let i = 0; i < path.length; i++) {
425
+ const key = path[i];
426
+ if (isSet(object)) {
427
+ object = getNthKey(object, +key);
428
+ }
429
+ else if (isMap(object)) {
430
+ const row = +key;
431
+ const type = +path[++i] === 0 ? 'key' : 'value';
432
+ const keyOfRow = getNthKey(object, row);
433
+ switch (type) {
434
+ case 'key':
435
+ object = keyOfRow;
436
+ break;
437
+ case 'value':
438
+ object = object.get(keyOfRow);
439
+ break;
440
+ }
441
+ }
442
+ else {
443
+ object = object[key];
444
+ }
445
+ }
446
+ return object;
447
+ };
448
+ const setDeep = (object, path, mapper) => {
449
+ validatePath(path);
450
+ if (path.length === 0) {
451
+ return mapper(object);
452
+ }
453
+ let parent = object;
454
+ for (let i = 0; i < path.length - 1; i++) {
455
+ const key = path[i];
456
+ if (isArray$1(parent)) {
457
+ const index = +key;
458
+ parent = parent[index];
459
+ }
460
+ else if (isPlainObject$1(parent)) {
461
+ parent = parent[key];
462
+ }
463
+ else if (isSet(parent)) {
464
+ const row = +key;
465
+ parent = getNthKey(parent, row);
466
+ }
467
+ else if (isMap(parent)) {
468
+ const isEnd = i === path.length - 2;
469
+ if (isEnd) {
470
+ break;
471
+ }
472
+ const row = +key;
473
+ const type = +path[++i] === 0 ? 'key' : 'value';
474
+ const keyOfRow = getNthKey(parent, row);
475
+ switch (type) {
476
+ case 'key':
477
+ parent = keyOfRow;
478
+ break;
479
+ case 'value':
480
+ parent = parent.get(keyOfRow);
481
+ break;
482
+ }
483
+ }
484
+ }
485
+ const lastKey = path[path.length - 1];
486
+ if (isArray$1(parent)) {
487
+ parent[+lastKey] = mapper(parent[+lastKey]);
488
+ }
489
+ else if (isPlainObject$1(parent)) {
490
+ parent[lastKey] = mapper(parent[lastKey]);
491
+ }
492
+ if (isSet(parent)) {
493
+ const oldValue = getNthKey(parent, +lastKey);
494
+ const newValue = mapper(oldValue);
495
+ if (oldValue !== newValue) {
496
+ parent.delete(oldValue);
497
+ parent.add(newValue);
498
+ }
499
+ }
500
+ if (isMap(parent)) {
501
+ const row = +path[path.length - 2];
502
+ const keyToRow = getNthKey(parent, row);
503
+ const type = +lastKey === 0 ? 'key' : 'value';
504
+ switch (type) {
505
+ case 'key': {
506
+ const newKey = mapper(keyToRow);
507
+ parent.set(newKey, parent.get(keyToRow));
508
+ if (newKey !== keyToRow) {
509
+ parent.delete(keyToRow);
510
+ }
511
+ break;
512
+ }
513
+ case 'value': {
514
+ parent.set(keyToRow, mapper(parent.get(keyToRow)));
515
+ break;
516
+ }
517
+ }
518
+ }
519
+ return object;
520
+ };
521
+
522
+ const enableLegacyPaths = (version) => version < 1;
523
+ function traverse(tree, walker, version, origin = []) {
524
+ if (!tree) {
525
+ return;
526
+ }
527
+ const legacyPaths = enableLegacyPaths(version);
528
+ if (!isArray$1(tree)) {
529
+ forEach(tree, (subtree, key) => traverse(subtree, walker, version, [
530
+ ...origin,
531
+ ...parsePath(key, legacyPaths),
532
+ ]));
533
+ return;
534
+ }
535
+ const [nodeValue, children] = tree;
536
+ if (children) {
537
+ forEach(children, (child, key) => {
538
+ traverse(child, walker, version, [
539
+ ...origin,
540
+ ...parsePath(key, legacyPaths),
541
+ ]);
542
+ });
543
+ }
544
+ walker(nodeValue, origin);
545
+ }
546
+ function applyValueAnnotations(plain, annotations, version, superJson) {
547
+ traverse(annotations, (type, path) => {
548
+ plain = setDeep(plain, path, v => untransformValue(v, type, superJson));
549
+ }, version);
550
+ return plain;
551
+ }
552
+ function applyReferentialEqualityAnnotations(plain, annotations, version) {
553
+ const legacyPaths = enableLegacyPaths(version);
554
+ function apply(identicalPaths, path) {
555
+ const object = getDeep(plain, parsePath(path, legacyPaths));
556
+ identicalPaths
557
+ .map(path => parsePath(path, legacyPaths))
558
+ .forEach(identicalObjectPath => {
559
+ plain = setDeep(plain, identicalObjectPath, () => object);
560
+ });
561
+ }
562
+ if (isArray$1(annotations)) {
563
+ const [root, other] = annotations;
564
+ root.forEach(identicalPath => {
565
+ plain = setDeep(plain, parsePath(identicalPath, legacyPaths), () => plain);
566
+ });
567
+ if (other) {
568
+ forEach(other, apply);
569
+ }
570
+ }
571
+ else {
572
+ forEach(annotations, apply);
573
+ }
574
+ return plain;
575
+ }
576
+ const isDeep = (object, superJson) => isPlainObject$1(object) ||
577
+ isArray$1(object) ||
578
+ isMap(object) ||
579
+ isSet(object) ||
580
+ isError(object) ||
581
+ isInstanceOfRegisteredClass(object, superJson);
582
+ function addIdentity(object, path, identities) {
583
+ const existingSet = identities.get(object);
584
+ if (existingSet) {
585
+ existingSet.push(path);
586
+ }
587
+ else {
588
+ identities.set(object, [path]);
589
+ }
590
+ }
591
+ function generateReferentialEqualityAnnotations(identitites, dedupe) {
592
+ const result = {};
593
+ let rootEqualityPaths = undefined;
594
+ identitites.forEach(paths => {
595
+ if (paths.length <= 1) {
596
+ return;
597
+ }
598
+ // if we're not deduping, all of these objects continue existing.
599
+ // putting the shortest path first makes it easier to parse for humans
600
+ // if we're deduping though, only the first entry will still exist, so we can't do this optimisation.
601
+ if (!dedupe) {
602
+ paths = paths
603
+ .map(path => path.map(String))
604
+ .sort((a, b) => a.length - b.length);
605
+ }
606
+ const [representativePath, ...identicalPaths] = paths;
607
+ if (representativePath.length === 0) {
608
+ rootEqualityPaths = identicalPaths.map(stringifyPath);
609
+ }
610
+ else {
611
+ result[stringifyPath(representativePath)] = identicalPaths.map(stringifyPath);
612
+ }
613
+ });
614
+ if (rootEqualityPaths) {
615
+ if (isEmptyObject(result)) {
616
+ return [rootEqualityPaths];
617
+ }
618
+ else {
619
+ return [rootEqualityPaths, result];
620
+ }
621
+ }
622
+ else {
623
+ return isEmptyObject(result) ? undefined : result;
624
+ }
625
+ }
626
+ const walker = (object, identities, superJson, dedupe, path = [], objectsInThisPath = [], seenObjects = new Map()) => {
627
+ const primitive = isPrimitive(object);
628
+ if (!primitive) {
629
+ addIdentity(object, path, identities);
630
+ const seen = seenObjects.get(object);
631
+ if (seen) {
632
+ // short-circuit result if we've seen this object before
633
+ return dedupe
634
+ ? {
635
+ transformedValue: null,
636
+ }
637
+ : seen;
638
+ }
639
+ }
640
+ if (!isDeep(object, superJson)) {
641
+ const transformed = transformValue(object, superJson);
642
+ const result = transformed
643
+ ? {
644
+ transformedValue: transformed.value,
645
+ annotations: [transformed.type],
646
+ }
647
+ : {
648
+ transformedValue: object,
649
+ };
650
+ if (!primitive) {
651
+ seenObjects.set(object, result);
652
+ }
653
+ return result;
654
+ }
655
+ if (includes(objectsInThisPath, object)) {
656
+ // prevent circular references
657
+ return {
658
+ transformedValue: null,
659
+ };
660
+ }
661
+ const transformationResult = transformValue(object, superJson);
662
+ const transformed = transformationResult?.value ?? object;
663
+ const transformedValue = isArray$1(transformed) ? [] : {};
664
+ const innerAnnotations = {};
665
+ forEach(transformed, (value, index) => {
666
+ if (index === '__proto__' ||
667
+ index === 'constructor' ||
668
+ index === 'prototype') {
669
+ throw new Error(`Detected property ${index}. This is a prototype pollution risk, please remove it from your object.`);
670
+ }
671
+ const recursiveResult = walker(value, identities, superJson, dedupe, [...path, index], [...objectsInThisPath, object], seenObjects);
672
+ transformedValue[index] = recursiveResult.transformedValue;
673
+ if (isArray$1(recursiveResult.annotations)) {
674
+ innerAnnotations[escapeKey(index)] = recursiveResult.annotations;
675
+ }
676
+ else if (isPlainObject$1(recursiveResult.annotations)) {
677
+ forEach(recursiveResult.annotations, (tree, key) => {
678
+ innerAnnotations[escapeKey(index) + '.' + key] = tree;
679
+ });
680
+ }
681
+ });
682
+ const result = isEmptyObject(innerAnnotations)
683
+ ? {
684
+ transformedValue,
685
+ annotations: !!transformationResult
686
+ ? [transformationResult.type]
687
+ : undefined,
688
+ }
689
+ : {
690
+ transformedValue,
691
+ annotations: !!transformationResult
692
+ ? [transformationResult.type, innerAnnotations]
693
+ : innerAnnotations,
694
+ };
695
+ if (!primitive) {
696
+ seenObjects.set(object, result);
697
+ }
698
+ return result;
699
+ };
700
+
701
+ /** Returns the object type of the given payload */
702
+ function getType(payload) {
703
+ return Object.prototype.toString.call(payload).slice(8, -1);
704
+ }
705
+
706
+ /** Returns whether the payload is an array */
707
+ function isArray(payload) {
708
+ return getType(payload) === 'Array';
709
+ }
710
+
711
+ /**
712
+ * Returns whether the payload is a plain JavaScript object (excluding special classes or objects
713
+ * with other prototypes)
714
+ */
715
+ function isPlainObject(payload) {
716
+ if (getType(payload) !== 'Object')
717
+ return false;
718
+ const prototype = Object.getPrototypeOf(payload);
719
+ return !!prototype && prototype.constructor === Object && prototype === Object.prototype;
720
+ }
721
+
722
+ function assignProp(carry, key, newVal, originalObject, includeNonenumerable) {
723
+ const propType = {}.propertyIsEnumerable.call(originalObject, key)
724
+ ? 'enumerable'
725
+ : 'nonenumerable';
726
+ if (propType === 'enumerable')
727
+ carry[key] = newVal;
728
+ if (includeNonenumerable && propType === 'nonenumerable') {
729
+ Object.defineProperty(carry, key, {
730
+ value: newVal,
731
+ enumerable: false,
732
+ writable: true,
733
+ configurable: true,
734
+ });
735
+ }
736
+ }
737
+ /**
738
+ * Copy (clone) an object and all its props recursively to get rid of any prop referenced of the
739
+ * original object. Arrays are also cloned, however objects inside arrays are still linked.
740
+ *
741
+ * @param target Target can be anything
742
+ * @param [options={}] See type {@link Options} for more details.
743
+ *
744
+ * - `{ props: ['key1'] }` will only copy the `key1` property. When using this you will need to cast
745
+ * the return type manually (in order to keep the TS implementation in here simple I didn't
746
+ * built a complex auto resolved type for those few cases people want to use this option)
747
+ * - `{ nonenumerable: true }` will copy all non-enumerable properties. Default is `{}`
748
+ *
749
+ * @returns The target with replaced values
750
+ */
751
+ function copy(target, options = {}) {
752
+ if (isArray(target)) {
753
+ return target.map((item) => copy(item, options));
754
+ }
755
+ if (!isPlainObject(target)) {
756
+ return target;
757
+ }
758
+ const props = Object.getOwnPropertyNames(target);
759
+ const symbols = Object.getOwnPropertySymbols(target);
760
+ return [...props, ...symbols].reduce((carry, key) => {
761
+ // Skip __proto__ properties to prevent prototype pollution
762
+ if (key === '__proto__')
763
+ return carry;
764
+ if (isArray(options.props) && !options.props.includes(key)) {
765
+ return carry;
766
+ }
767
+ const val = target[key];
768
+ const newVal = copy(val, options);
769
+ assignProp(carry, key, newVal, target, options.nonenumerable);
770
+ return carry;
771
+ }, {});
772
+ }
773
+
774
+ class SuperJSON {
775
+ /**
776
+ * @param dedupeReferentialEqualities If true, SuperJSON will make sure only one instance of referentially equal objects are serialized and the rest are replaced with `null`.
777
+ */
778
+ constructor({ dedupe = false, } = {}) {
779
+ this.classRegistry = new ClassRegistry();
780
+ this.symbolRegistry = new Registry(s => s.description ?? '');
781
+ this.customTransformerRegistry = new CustomTransformerRegistry();
782
+ this.allowedErrorProps = [];
783
+ this.dedupe = dedupe;
784
+ }
785
+ serialize(object) {
786
+ const identities = new Map();
787
+ const output = walker(object, identities, this, this.dedupe);
788
+ const res = {
789
+ json: output.transformedValue,
790
+ };
791
+ if (output.annotations) {
792
+ res.meta = {
793
+ ...res.meta,
794
+ values: output.annotations,
795
+ };
796
+ }
797
+ const equalityAnnotations = generateReferentialEqualityAnnotations(identities, this.dedupe);
798
+ if (equalityAnnotations) {
799
+ res.meta = {
800
+ ...res.meta,
801
+ referentialEqualities: equalityAnnotations,
802
+ };
803
+ }
804
+ if (res.meta)
805
+ res.meta.v = 1;
806
+ return res;
807
+ }
808
+ deserialize(payload, options) {
809
+ const { json, meta } = payload;
810
+ let result = options?.inPlace ? json : copy(json);
811
+ if (meta?.values) {
812
+ result = applyValueAnnotations(result, meta.values, meta.v ?? 0, this);
813
+ }
814
+ if (meta?.referentialEqualities) {
815
+ result = applyReferentialEqualityAnnotations(result, meta.referentialEqualities, meta.v ?? 0);
816
+ }
817
+ return result;
818
+ }
819
+ stringify(object) {
820
+ return JSON.stringify(this.serialize(object));
821
+ }
822
+ parse(string) {
823
+ return this.deserialize(JSON.parse(string), { inPlace: true });
824
+ }
825
+ registerClass(v, options) {
826
+ this.classRegistry.register(v, options);
827
+ }
828
+ registerSymbol(v, identifier) {
829
+ this.symbolRegistry.register(v, identifier);
830
+ }
831
+ registerCustom(transformer, name) {
832
+ this.customTransformerRegistry.register({
833
+ name,
834
+ ...transformer,
835
+ });
836
+ }
837
+ allowErrorProps(...props) {
838
+ this.allowedErrorProps.push(...props);
839
+ }
840
+ }
841
+ SuperJSON.defaultInstance = new SuperJSON();
842
+ SuperJSON.serialize = SuperJSON.defaultInstance.serialize.bind(SuperJSON.defaultInstance);
843
+ SuperJSON.deserialize = SuperJSON.defaultInstance.deserialize.bind(SuperJSON.defaultInstance);
844
+ SuperJSON.stringify = SuperJSON.defaultInstance.stringify.bind(SuperJSON.defaultInstance);
845
+ SuperJSON.parse = SuperJSON.defaultInstance.parse.bind(SuperJSON.defaultInstance);
846
+ SuperJSON.registerClass = SuperJSON.defaultInstance.registerClass.bind(SuperJSON.defaultInstance);
847
+ SuperJSON.registerSymbol = SuperJSON.defaultInstance.registerSymbol.bind(SuperJSON.defaultInstance);
848
+ SuperJSON.registerCustom = SuperJSON.defaultInstance.registerCustom.bind(SuperJSON.defaultInstance);
849
+ SuperJSON.allowErrorProps = SuperJSON.defaultInstance.allowErrorProps.bind(SuperJSON.defaultInstance);
850
+ SuperJSON.serialize;
851
+ SuperJSON.deserialize;
852
+ SuperJSON.stringify;
853
+ SuperJSON.parse;
854
+ SuperJSON.registerClass;
855
+ SuperJSON.registerCustom;
856
+ SuperJSON.registerSymbol;
857
+ SuperJSON.allowErrorProps;
858
+
1
859
  /**
2
860
  * @class HyperStorage
3
861
  * @classdesc A lightweight wrapper for localStorage/sessionStorage
@@ -7,7 +865,8 @@
7
865
  */
8
866
  class HyperStorage {
9
867
  /** Version of the library, injected via Rollup replace plugin. */
10
- static version = "5.1.0";
868
+ static version = "6.0.1";
869
+ static superjson = SuperJSON;
11
870
  /** Key name under which the data is stored. */
12
871
  itemName;
13
872
  /** Default value used when the key does not exist in storage. */
@@ -26,7 +885,7 @@ class HyperStorage {
26
885
  * @param {string} itemName - The key name under which the data will be stored.
27
886
  * @param {T} [defaultValue] - Default value assigned to the key if it does not exist yet.
28
887
  * @param {Object} [options={}] - Optional configuration parameters.
29
- * @param {(value: string) => string} [options.encodeFn] - Optional function to encode stored values.
888
+ * @param {(value: T) => string} [options.encodeFn] - Optional function to encode stored values.
30
889
  * @param {(value: string) => string} [options.decodeFn] - Optional function to decode stored values.
31
890
  * @param {Storage} [options.storage=window.localStorage] - Optional custom storage backend.
32
891
  *
@@ -42,10 +901,10 @@ class HyperStorage {
42
901
  this.defaultValue = defaultValue;
43
902
  if (encodeFn && typeof encodeFn !== 'function')
44
903
  throw new TypeError('encodeFn is defined but is not a function');
45
- this.encodeFn = encodeFn || ((v) => v);
904
+ this.encodeFn = encodeFn || ((v) => HyperStorage.superjson.stringify(v));
46
905
  if (decodeFn && typeof decodeFn !== 'function')
47
906
  throw new TypeError('decodeFn is defined but is not a function');
48
- this.decodeFn = decodeFn || ((v) => v);
907
+ this.decodeFn = decodeFn || ((v) => HyperStorage.superjson.parse(v));
49
908
  if (!(storage instanceof Storage))
50
909
  throw new TypeError('storage must be an instance of Storage');
51
910
  this.storage = storage;
@@ -58,24 +917,7 @@ class HyperStorage {
58
917
  set value(value) {
59
918
  // Cache real value
60
919
  this.#value = value;
61
- // Store stringified value with prefix to distinguish from raw strings
62
- let stringValue;
63
- if (typeof value === 'string') {
64
- if (value[0] === '\0')
65
- stringValue = '\0' + value;
66
- else
67
- stringValue = value;
68
- }
69
- else if (value === undefined ||
70
- (typeof value === 'number' &&
71
- (isNaN(value) || value === Infinity || value === -Infinity))) {
72
- // Manually stringify non-JSON values
73
- stringValue = '\0' + String(value);
74
- }
75
- else {
76
- stringValue = '\0' + JSON.stringify(value);
77
- }
78
- this.storage.setItem(this.itemName, this.encodeFn(stringValue));
920
+ this.storage.setItem(this.itemName, this.encodeFn(value));
79
921
  }
80
922
  /**
81
923
  * Gets the current cached value.
@@ -83,11 +925,11 @@ class HyperStorage {
83
925
  get value() {
84
926
  return this.#value ?? this.defaultValue;
85
927
  }
86
- /**
87
- * Allows using the setter with a callback.
88
- */
89
- set(callback) {
90
- return (this.value = callback(this.value));
928
+ set(keyOrCallback, value) {
929
+ if (typeof keyOrCallback === 'function')
930
+ return (this.value = keyOrCallback(this.value));
931
+ this.value[keyOrCallback] = value;
932
+ return this.value;
91
933
  }
92
934
  /**
93
935
  * Synchronizes the internal cache (`#value`) with the actual value in storage.
@@ -96,35 +938,19 @@ class HyperStorage {
96
938
  * Using this function should be avoided when possible and is not type safe.
97
939
  */
98
940
  sync(decodeFn = this.decodeFn) {
99
- let value = this.storage.getItem(this.itemName);
941
+ let json = this.storage.getItem(this.itemName);
100
942
  // Reset value to defaultValue if it does not exist in storage
101
- if (typeof value !== 'string')
943
+ if (typeof json !== 'string')
102
944
  return this.reset();
103
945
  // Reset value to defaultValue if the incoming value is not properly encoded
104
946
  try {
105
- value = decodeFn(value);
947
+ return (this.value = decodeFn(json));
106
948
  }
107
949
  catch (err) {
108
950
  this.reset();
109
951
  console.error(err);
110
952
  return err;
111
953
  }
112
- if (value[0] !== '\0')
113
- return (this.value = value); // Raw string value
114
- // Slice off '\0' prefix
115
- value = value.slice(1);
116
- if (value[0] === '\0')
117
- return (this.value = value); // Raw string value that started with '\0'
118
- // Parse non JSON
119
- if (value === 'undefined')
120
- return (this.value = undefined);
121
- if (value === 'NaN')
122
- return (this.value = NaN);
123
- if (value === 'Infinity')
124
- return (this.value = Infinity);
125
- if (value === '-Infinity')
126
- return (this.value = -Infinity);
127
- return (this.value = JSON.parse(value));
128
954
  }
129
955
  /**
130
956
  * Resets the stored value to its configured default.