@david200197/super-ls 1.0.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/EXPLAIN.md +612 -0
- package/README.md +486 -0
- package/TEST_DOCUMENTATION.md +255 -0
- package/index.js +615 -0
- package/jsconfig.json +13 -0
- package/mkctx.config.json +7 -0
- package/package.json +31 -0
- package/tests/super-ls.edge-cases.spec.js +911 -0
- package/tests/super-ls.normal-cases.spec.js +794 -0
package/index.js
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import { stringify, parse } from 'devalue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview SuperLocalStorage - Enhanced localStorage wrapper for Titan Planet
|
|
5
|
+
* that supports complex JavaScript types including Map, Set, Date, circular references,
|
|
6
|
+
* and custom class instances with automatic serialization/deserialization.
|
|
7
|
+
*
|
|
8
|
+
* @author Titan Planet
|
|
9
|
+
* @license MIT
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Constants
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/** @constant {string} Default prefix for all storage keys */
|
|
17
|
+
const DEFAULT_PREFIX = 'sls_';
|
|
18
|
+
|
|
19
|
+
/** @constant {string} Metadata key for identifying serialized class type */
|
|
20
|
+
const TYPE_MARKER = '__super_type__';
|
|
21
|
+
|
|
22
|
+
/** @constant {string} Metadata key for serialized class data */
|
|
23
|
+
const DATA_MARKER = '__data__';
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Type Definitions
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} SerializedClassWrapper
|
|
31
|
+
* @property {string} __super_type__ - The registered type name of the class
|
|
32
|
+
* @property {Object} __data__ - The serialized properties of the class instance
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {new (...args: any[]) => any} ClassConstructor
|
|
37
|
+
* A class constructor function
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} HydratableClass
|
|
42
|
+
* @property {function(Object): any} [hydrate] - Optional static method to create instance from data
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Helper Functions
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Checks if a value is a primitive (non-object) type
|
|
51
|
+
* @param {any} value - Value to check
|
|
52
|
+
* @returns {boolean} True if value is null, undefined, or a primitive
|
|
53
|
+
*/
|
|
54
|
+
const isPrimitive = (value) => value === null || typeof value !== 'object';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Checks if a value is a TypedArray (Uint8Array, Float32Array, etc.)
|
|
58
|
+
* @param {any} value - Value to check
|
|
59
|
+
* @returns {boolean} True if value is a TypedArray
|
|
60
|
+
*/
|
|
61
|
+
const isTypedArray = (value) => ArrayBuffer.isView(value) && !(value instanceof DataView);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Checks if a serialized object contains class type metadata
|
|
65
|
+
* @param {any} value - Value to check
|
|
66
|
+
* @returns {boolean} True if value has type wrapper markers
|
|
67
|
+
*/
|
|
68
|
+
const hasTypeWrapper = (value) => value[TYPE_MARKER] && value[DATA_MARKER] !== undefined;
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Main Class
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Enhanced localStorage wrapper that supports complex JavaScript types
|
|
76
|
+
* and custom class serialization/deserialization.
|
|
77
|
+
*
|
|
78
|
+
* @class SuperLocalStorage
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* // Basic usage with rich types
|
|
82
|
+
* import superLs from 'super-ls';
|
|
83
|
+
*
|
|
84
|
+
* const settings = new Map([['theme', 'dark'], ['lang', 'es']]);
|
|
85
|
+
* superLs.set('user_settings', settings);
|
|
86
|
+
*
|
|
87
|
+
* const recovered = superLs.get('user_settings');
|
|
88
|
+
* console.log(recovered.get('theme')); // 'dark'
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* // Usage with custom classes
|
|
92
|
+
* class Player {
|
|
93
|
+
* constructor(name = '', score = 0) {
|
|
94
|
+
* this.name = name;
|
|
95
|
+
* this.score = score;
|
|
96
|
+
* }
|
|
97
|
+
* addScore(points) {
|
|
98
|
+
* this.score += points;
|
|
99
|
+
* }
|
|
100
|
+
* }
|
|
101
|
+
*
|
|
102
|
+
* superLs.register(Player);
|
|
103
|
+
* superLs.set('player', new Player('Alice', 100));
|
|
104
|
+
*
|
|
105
|
+
* const player = superLs.get('player');
|
|
106
|
+
* player.addScore(50); // Methods work!
|
|
107
|
+
*/
|
|
108
|
+
export class SuperLocalStorage {
|
|
109
|
+
/**
|
|
110
|
+
* Creates a new SuperLocalStorage instance
|
|
111
|
+
* @param {string} [prefix='sls_'] - Prefix for all storage keys
|
|
112
|
+
*/
|
|
113
|
+
constructor(prefix = DEFAULT_PREFIX) {
|
|
114
|
+
/**
|
|
115
|
+
* Registry mapping type names to class constructors
|
|
116
|
+
* @type {Map<string, ClassConstructor>}
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
this.registry = new Map();
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Prefix prepended to all storage keys
|
|
123
|
+
* @type {string}
|
|
124
|
+
* @private
|
|
125
|
+
*/
|
|
126
|
+
this.prefix = prefix;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ========================================================================
|
|
130
|
+
// Public API
|
|
131
|
+
// ========================================================================
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Registers a class for serialization/deserialization support.
|
|
135
|
+
*
|
|
136
|
+
* Once registered, instances of this class can be stored and retrieved
|
|
137
|
+
* with their methods intact.
|
|
138
|
+
*
|
|
139
|
+
* @param {ClassConstructor & HydratableClass} ClassRef - The class constructor to register
|
|
140
|
+
* @param {string} [typeName=null] - Optional custom type name (defaults to class name)
|
|
141
|
+
* @throws {Error} If ClassRef is not a function/class
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* // Basic registration
|
|
145
|
+
* superLs.register(Player);
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* // Registration with custom name (useful for minified code or name collisions)
|
|
149
|
+
* superLs.register(Player, 'GamePlayer');
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* // Class with static hydrate method for complex constructors
|
|
153
|
+
* class Player {
|
|
154
|
+
* constructor(name, score) {
|
|
155
|
+
* if (!name) throw new Error('Name required');
|
|
156
|
+
* this.name = name;
|
|
157
|
+
* this.score = score;
|
|
158
|
+
* }
|
|
159
|
+
* static hydrate(data) {
|
|
160
|
+
* return new Player(data.name, data.score);
|
|
161
|
+
* }
|
|
162
|
+
* }
|
|
163
|
+
* superLs.register(Player);
|
|
164
|
+
*/
|
|
165
|
+
register(ClassRef, typeName = null) {
|
|
166
|
+
if (typeof ClassRef !== 'function') {
|
|
167
|
+
throw new Error('Invalid class: expected a constructor function');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const finalName = typeName || ClassRef.name;
|
|
171
|
+
this.registry.set(finalName, ClassRef);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Stores a value in localStorage with full type preservation.
|
|
176
|
+
*
|
|
177
|
+
* Supports: primitives, objects, arrays, Map, Set, Date, RegExp,
|
|
178
|
+
* TypedArrays, BigInt, circular references, undefined, NaN, Infinity,
|
|
179
|
+
* and registered class instances.
|
|
180
|
+
*
|
|
181
|
+
* @param {string} key - Storage key
|
|
182
|
+
* @param {any} value - Value to store
|
|
183
|
+
* @throws {Error} If value contains non-serializable types (functions, WeakMap, WeakSet)
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* // Store various types
|
|
187
|
+
* superLs.set('map', new Map([['key', 'value']]));
|
|
188
|
+
* superLs.set('set', new Set([1, 2, 3]));
|
|
189
|
+
* superLs.set('date', new Date());
|
|
190
|
+
* superLs.set('regex', /pattern/gi);
|
|
191
|
+
* superLs.set('bigint', BigInt('9007199254740991000'));
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* // Store circular references
|
|
195
|
+
* const obj = { name: 'circular' };
|
|
196
|
+
* obj.self = obj;
|
|
197
|
+
* superLs.set('circular', obj);
|
|
198
|
+
*/
|
|
199
|
+
set(key, value) {
|
|
200
|
+
const payload = this._toSerializable(value);
|
|
201
|
+
const serialized = stringify(payload);
|
|
202
|
+
t.ls.set(this.prefix + key, serialized);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Retrieves a value from localStorage with full type restoration.
|
|
207
|
+
*
|
|
208
|
+
* All types are automatically restored to their original form,
|
|
209
|
+
* including registered class instances with working methods.
|
|
210
|
+
*
|
|
211
|
+
* @param {string} key - Storage key
|
|
212
|
+
* @returns {any} The stored value with types restored, or null if key doesn't exist
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* const settings = superLs.get('user_settings');
|
|
216
|
+
* if (settings) {
|
|
217
|
+
* console.log(settings.get('theme')); // Map methods work
|
|
218
|
+
* }
|
|
219
|
+
*/
|
|
220
|
+
get(key) {
|
|
221
|
+
const raw = t.ls.get(this.prefix + key);
|
|
222
|
+
|
|
223
|
+
if (!raw) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const parsed = parse(raw);
|
|
228
|
+
return this._rehydrate(parsed, new WeakMap());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ========================================================================
|
|
232
|
+
// Private Methods - Serialization
|
|
233
|
+
// ========================================================================
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Recursively converts values to a serializable format.
|
|
237
|
+
*
|
|
238
|
+
* Registered class instances are wrapped with type metadata.
|
|
239
|
+
* Circular references are preserved using a WeakMap tracker.
|
|
240
|
+
*
|
|
241
|
+
* @param {any} value - Value to convert
|
|
242
|
+
* @param {WeakMap} [seen=new WeakMap()] - Tracks processed objects for circular reference handling
|
|
243
|
+
* @returns {any} Serializable representation of the value
|
|
244
|
+
* @private
|
|
245
|
+
*/
|
|
246
|
+
_toSerializable(value, seen = new WeakMap()) {
|
|
247
|
+
if (isPrimitive(value)) {
|
|
248
|
+
return value;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (seen.has(value)) {
|
|
252
|
+
return seen.get(value);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check registered classes first (before native types)
|
|
256
|
+
const classWrapper = this._tryWrapRegisteredClass(value, seen);
|
|
257
|
+
if (classWrapper) {
|
|
258
|
+
return classWrapper;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Handle native types that devalue supports
|
|
262
|
+
if (this._isNativelySerializable(value)) {
|
|
263
|
+
return value;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Handle collections and objects
|
|
267
|
+
return this._serializeCollection(value, seen);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Attempts to wrap a registered class instance with type metadata
|
|
272
|
+
* @param {any} value - Value to check and potentially wrap
|
|
273
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
274
|
+
* @returns {SerializedClassWrapper|null} Wrapped class or null if not a registered class
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
_tryWrapRegisteredClass(value, seen) {
|
|
278
|
+
for (const [name, Constructor] of this.registry.entries()) {
|
|
279
|
+
if (value instanceof Constructor) {
|
|
280
|
+
const wrapper = {
|
|
281
|
+
[TYPE_MARKER]: name,
|
|
282
|
+
[DATA_MARKER]: {}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
seen.set(value, wrapper);
|
|
286
|
+
|
|
287
|
+
for (const key of Object.keys(value)) {
|
|
288
|
+
wrapper[DATA_MARKER][key] = this._toSerializable(value[key], seen);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return wrapper;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Checks if a value is natively serializable by devalue
|
|
299
|
+
* @param {any} value - Value to check
|
|
300
|
+
* @returns {boolean} True if devalue handles this type natively
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
_isNativelySerializable(value) {
|
|
304
|
+
return value instanceof Date ||
|
|
305
|
+
value instanceof RegExp ||
|
|
306
|
+
isTypedArray(value);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Serializes collections (Array, Map, Set, Object)
|
|
311
|
+
* @param {any} value - Collection to serialize
|
|
312
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
313
|
+
* @returns {any} Serialized collection
|
|
314
|
+
* @private
|
|
315
|
+
*/
|
|
316
|
+
_serializeCollection(value, seen) {
|
|
317
|
+
if (Array.isArray(value)) {
|
|
318
|
+
return this._serializeArray(value, seen);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (value instanceof Map) {
|
|
322
|
+
return this._serializeMap(value, seen);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (value instanceof Set) {
|
|
326
|
+
return this._serializeSet(value, seen);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return this._serializeObject(value, seen);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Serializes an array, preserving sparse array holes
|
|
334
|
+
* @param {Array} value - Array to serialize
|
|
335
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
336
|
+
* @returns {Array} Serialized array
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
339
|
+
_serializeArray(value, seen) {
|
|
340
|
+
const arr = [];
|
|
341
|
+
seen.set(value, arr);
|
|
342
|
+
|
|
343
|
+
for (let i = 0; i < value.length; i++) {
|
|
344
|
+
if (i in value) {
|
|
345
|
+
arr[i] = this._toSerializable(value[i], seen);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (value.length > arr.length) {
|
|
350
|
+
arr.length = value.length;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return arr;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Serializes a Map, processing both keys and values
|
|
358
|
+
* @param {Map} value - Map to serialize
|
|
359
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
360
|
+
* @returns {Map} Serialized Map
|
|
361
|
+
* @private
|
|
362
|
+
*/
|
|
363
|
+
_serializeMap(value, seen) {
|
|
364
|
+
const newMap = new Map();
|
|
365
|
+
seen.set(value, newMap);
|
|
366
|
+
|
|
367
|
+
for (const [k, v] of value.entries()) {
|
|
368
|
+
newMap.set(
|
|
369
|
+
this._toSerializable(k, seen),
|
|
370
|
+
this._toSerializable(v, seen)
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return newMap;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Serializes a Set
|
|
379
|
+
* @param {Set} value - Set to serialize
|
|
380
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
381
|
+
* @returns {Set} Serialized Set
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
384
|
+
_serializeSet(value, seen) {
|
|
385
|
+
const newSet = new Set();
|
|
386
|
+
seen.set(value, newSet);
|
|
387
|
+
|
|
388
|
+
for (const item of value) {
|
|
389
|
+
newSet.add(this._toSerializable(item, seen));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return newSet;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Serializes a plain object or unregistered class instance
|
|
397
|
+
* @param {Object} value - Object to serialize
|
|
398
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
399
|
+
* @returns {Object} Serialized object
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
402
|
+
_serializeObject(value, seen) {
|
|
403
|
+
const obj = {};
|
|
404
|
+
seen.set(value, obj);
|
|
405
|
+
|
|
406
|
+
for (const key of Object.keys(value)) {
|
|
407
|
+
obj[key] = this._toSerializable(value[key], seen);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return obj;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ========================================================================
|
|
414
|
+
// Private Methods - Deserialization (Rehydration)
|
|
415
|
+
// ========================================================================
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Recursively rehydrates serialized data back to original types.
|
|
419
|
+
*
|
|
420
|
+
* Objects with type metadata are restored to class instances.
|
|
421
|
+
* Circular references are preserved using a WeakMap tracker.
|
|
422
|
+
*
|
|
423
|
+
* @param {any} value - Value to rehydrate
|
|
424
|
+
* @param {WeakMap} seen - Tracks processed objects for circular reference handling
|
|
425
|
+
* @returns {any} Rehydrated value with original types restored
|
|
426
|
+
* @private
|
|
427
|
+
*/
|
|
428
|
+
_rehydrate(value, seen) {
|
|
429
|
+
if (isPrimitive(value)) {
|
|
430
|
+
return value;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (seen.has(value)) {
|
|
434
|
+
return seen.get(value);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Check for wrapped class instances
|
|
438
|
+
if (hasTypeWrapper(value)) {
|
|
439
|
+
return this._rehydrateClass(value, seen);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Handle native types
|
|
443
|
+
if (value instanceof Date || value instanceof RegExp) {
|
|
444
|
+
return value;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Handle collections
|
|
448
|
+
return this._rehydrateCollection(value, seen);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Rehydrates a wrapped class instance back to its original class
|
|
453
|
+
* @param {SerializedClassWrapper} value - Wrapped class data
|
|
454
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
455
|
+
* @returns {any} Restored class instance or original value if class not registered
|
|
456
|
+
* @private
|
|
457
|
+
*/
|
|
458
|
+
_rehydrateClass(value, seen) {
|
|
459
|
+
const Constructor = this.registry.get(value[TYPE_MARKER]);
|
|
460
|
+
|
|
461
|
+
if (!Constructor) {
|
|
462
|
+
return this._rehydrateObject(value, seen);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Use placeholder for circular reference support
|
|
466
|
+
const placeholder = {};
|
|
467
|
+
seen.set(value, placeholder);
|
|
468
|
+
|
|
469
|
+
// Rehydrate nested data first
|
|
470
|
+
const hydratedData = {};
|
|
471
|
+
for (const key of Object.keys(value[DATA_MARKER])) {
|
|
472
|
+
hydratedData[key] = this._rehydrate(value[DATA_MARKER][key], seen);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Create instance using hydrate() or default constructor
|
|
476
|
+
const instance = this._createInstance(Constructor, hydratedData);
|
|
477
|
+
|
|
478
|
+
// Update placeholder to become the actual instance
|
|
479
|
+
Object.assign(placeholder, instance);
|
|
480
|
+
Object.setPrototypeOf(placeholder, Object.getPrototypeOf(instance));
|
|
481
|
+
|
|
482
|
+
return placeholder;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Creates a class instance from hydrated data
|
|
487
|
+
* @param {ClassConstructor & HydratableClass} Constructor - Class constructor
|
|
488
|
+
* @param {Object} data - Hydrated property data
|
|
489
|
+
* @returns {any} New class instance
|
|
490
|
+
* @private
|
|
491
|
+
*/
|
|
492
|
+
_createInstance(Constructor, data) {
|
|
493
|
+
if (typeof Constructor.hydrate === 'function') {
|
|
494
|
+
return Constructor.hydrate(data);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const instance = new Constructor();
|
|
498
|
+
Object.assign(instance, data);
|
|
499
|
+
return instance;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Rehydrates collections (Array, Map, Set, Object)
|
|
504
|
+
* @param {any} value - Collection to rehydrate
|
|
505
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
506
|
+
* @returns {any} Rehydrated collection
|
|
507
|
+
* @private
|
|
508
|
+
*/
|
|
509
|
+
_rehydrateCollection(value, seen) {
|
|
510
|
+
if (Array.isArray(value)) {
|
|
511
|
+
return this._rehydrateArray(value, seen);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (value instanceof Map) {
|
|
515
|
+
return this._rehydrateMap(value, seen);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (value instanceof Set) {
|
|
519
|
+
return this._rehydrateSet(value, seen);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (value.constructor === Object) {
|
|
523
|
+
return this._rehydrateObject(value, seen);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return value;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Rehydrates an array
|
|
531
|
+
* @param {Array} value - Array to rehydrate
|
|
532
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
533
|
+
* @returns {Array} Rehydrated array
|
|
534
|
+
* @private
|
|
535
|
+
*/
|
|
536
|
+
_rehydrateArray(value, seen) {
|
|
537
|
+
const arr = [];
|
|
538
|
+
seen.set(value, arr);
|
|
539
|
+
|
|
540
|
+
for (let i = 0; i < value.length; i++) {
|
|
541
|
+
arr[i] = this._rehydrate(value[i], seen);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return arr;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Rehydrates a Map, processing both keys and values
|
|
549
|
+
* @param {Map} value - Map to rehydrate
|
|
550
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
551
|
+
* @returns {Map} Rehydrated Map
|
|
552
|
+
* @private
|
|
553
|
+
*/
|
|
554
|
+
_rehydrateMap(value, seen) {
|
|
555
|
+
const newMap = new Map();
|
|
556
|
+
seen.set(value, newMap);
|
|
557
|
+
|
|
558
|
+
for (const [k, v] of value.entries()) {
|
|
559
|
+
newMap.set(
|
|
560
|
+
this._rehydrate(k, seen),
|
|
561
|
+
this._rehydrate(v, seen)
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return newMap;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Rehydrates a Set
|
|
570
|
+
* @param {Set} value - Set to rehydrate
|
|
571
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
572
|
+
* @returns {Set} Rehydrated Set
|
|
573
|
+
* @private
|
|
574
|
+
*/
|
|
575
|
+
_rehydrateSet(value, seen) {
|
|
576
|
+
const newSet = new Set();
|
|
577
|
+
seen.set(value, newSet);
|
|
578
|
+
|
|
579
|
+
for (const item of value) {
|
|
580
|
+
newSet.add(this._rehydrate(item, seen));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return newSet;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Rehydrates a plain object
|
|
588
|
+
* @param {Object} value - Object to rehydrate
|
|
589
|
+
* @param {WeakMap} seen - Circular reference tracker
|
|
590
|
+
* @returns {Object} Rehydrated object
|
|
591
|
+
* @private
|
|
592
|
+
*/
|
|
593
|
+
_rehydrateObject(value, seen) {
|
|
594
|
+
const obj = {};
|
|
595
|
+
seen.set(value, obj);
|
|
596
|
+
|
|
597
|
+
for (const key of Object.keys(value)) {
|
|
598
|
+
obj[key] = this._rehydrate(value[key], seen);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return obj;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ============================================================================
|
|
606
|
+
// Default Export
|
|
607
|
+
// ============================================================================
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Default SuperLocalStorage instance for convenient usage
|
|
611
|
+
* @type {SuperLocalStorage}
|
|
612
|
+
*/
|
|
613
|
+
const superLs = new SuperLocalStorage();
|
|
614
|
+
|
|
615
|
+
export default superLs;
|
package/jsconfig.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"src": ".",
|
|
3
|
+
"ignore": "mkctx.config.json, pnpm-lock.yaml, **/.titan/, mkctx/, node_modules/, .git/, dist/, build/, target/, .next/, out/, .cache, package-lock.json, *.log, temp/, tmp/, coverage/, .nyc_output, .env, .env.local, .env.development.local, .env.test.local, .env.production.local, npm-debug.log*, yarn-debug.log*, yarn-error.log*, .npm, .yarn-integrity, .parcel-cache, .vuepress/dist, .svelte-kit, **/*.rs.bk, .idea/, .vscode/, .DS_Store, Thumbs.db, *.swp, *.swo, .~lock.*, Cargo.lock, .cargo/registry/, .cargo/git/, .rustup/, *.pdb, *.dSYM/, *.so, *.dll, *.dylib, *.exe, *.lib, *.a, *.o, *.rlib, *.d, *.tmp, *.bak, *.orig, *.rej, *.pyc, *.pyo, *.class, *.jar, *.war, *.ear, *.zip, *.tar.gz, *.rar, *.7z, *.iso, *.img, *.dmg, *.pdf, *.doc, *.docx, *.xls, *.xlsx, *.ppt, *.pptx",
|
|
4
|
+
"output": "./mkctx",
|
|
5
|
+
"first_comment": "/* Project Context */",
|
|
6
|
+
"last_comment": "/* End of Context */"
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@david200197/super-ls",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A supercharged storage adapter for Titan Planet that enables storing complex objects, circular references, and Class instances with automatic rehydration.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "vitest run",
|
|
9
|
+
"test:watch": "vitest",
|
|
10
|
+
"test:cov": "vitest --coverage"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"titan planet",
|
|
14
|
+
"extension"
|
|
15
|
+
],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"devalue": "^5.6.2"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@titanpl/core": ">=1.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@titanpl/core": "latest",
|
|
26
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
27
|
+
"esbuild": "^0.27.2",
|
|
28
|
+
"titanpl-sdk": "latest",
|
|
29
|
+
"vitest": "^4.0.17"
|
|
30
|
+
}
|
|
31
|
+
}
|