titanpl-superls 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/README.md ADDED
@@ -0,0 +1,486 @@
1
+ # ๐Ÿช @titanpl/super-ls
2
+
3
+ > A supercharged storage adapter for Titan Planet that enables storing complex objects, circular references, and Class instances with automatic rehydration.
4
+
5
+ `super-ls` extends the capabilities of the native `t.ls` API by using `devalue` for serialization. While standard `t.ls` is limited to simple JSON data, `super-ls` allows you to save and retrieve rich data structures effortlessly.
6
+
7
+ ---
8
+
9
+ ## โœจ Features
10
+
11
+ - **Rich Data Types**: Store `Map`, `Set`, `Date`, `RegExp`, `BigInt`, `TypedArray`, `undefined`, `NaN`, `Infinity`, and circular references
12
+ - **Class Hydration**: Register your custom classes and retrieve fully functional instances with methods intact
13
+ - **Dependency Injection Support**: Serialize/deserialize nested class instances and complex object graphs
14
+ - **Circular Reference Handling**: Automatic detection and preservation of circular references
15
+ - **Drop-in Library**: Works via standard ES module `import` without polluting the global `t` namespace
16
+ - **Titan Native Integration**: Built on top of `@titanpl/core`'s `t.ls` for persistence
17
+
18
+ ---
19
+
20
+ ## ๐Ÿ“ฆ Installation
21
+
22
+ Add `super-ls` to your Titan Planet project:
23
+
24
+ ```bash
25
+ npm install @david200197/super-ls
26
+ ```
27
+
28
+ ---
29
+
30
+ ## ๐Ÿš€ Usage
31
+
32
+ ### Basic Usage (Rich Data Types)
33
+
34
+ Store objects that standard JSON cannot handle:
35
+
36
+ ```javascript
37
+ import superLs from "@titanpl/super-ls";
38
+
39
+ // Maps
40
+ const settings = new Map([
41
+ ["theme", "dark"],
42
+ ["language", "en"]
43
+ ]);
44
+ superLs.set("user_settings", settings);
45
+
46
+ const recovered = superLs.get("user_settings");
47
+ console.log(recovered instanceof Map); // true
48
+ console.log(recovered.get("theme")); // "dark"
49
+
50
+ // Sets
51
+ superLs.set("tags", new Set(["javascript", "typescript", "nodejs"]));
52
+
53
+ // Dates
54
+ superLs.set("lastLogin", new Date());
55
+
56
+ // RegExp
57
+ superLs.set("emailPattern", /^[\w-]+@[\w-]+\.\w+$/i);
58
+
59
+ // BigInt
60
+ superLs.set("bigNumber", BigInt("9007199254740991000"));
61
+
62
+ // Circular References
63
+ const obj = { name: "circular" };
64
+ obj.self = obj;
65
+ superLs.set("circular", obj);
66
+
67
+ const restored = superLs.get("circular");
68
+ console.log(restored.self === restored); // true
69
+ ```
70
+
71
+ ### Class Hydration
72
+
73
+ The true power of `super-ls` lies in its ability to restore class instances with their methods intact.
74
+
75
+ #### 1. Define and Register Your Class
76
+
77
+ ```javascript
78
+ import superLs from "@titanpl/super-ls";
79
+
80
+ class Player {
81
+ constructor(name = "", score = 0) {
82
+ this.name = name;
83
+ this.score = score;
84
+ }
85
+
86
+ greet() {
87
+ return `Hello, I am ${this.name}!`;
88
+ }
89
+
90
+ addScore(points) {
91
+ this.score += points;
92
+ }
93
+ }
94
+
95
+ // Register before saving or loading
96
+ superLs.register(Player);
97
+ ```
98
+
99
+ #### 2. Save and Restore
100
+
101
+ ```javascript
102
+ const player = new Player("Alice", 100);
103
+ superLs.set("player_1", player);
104
+
105
+ // Later, in a different request...
106
+ const restored = superLs.get("player_1");
107
+
108
+ console.log(restored.name); // "Alice"
109
+ console.log(restored.greet()); // "Hello, I am Alice!"
110
+ console.log(restored instanceof Player); // true
111
+
112
+ restored.addScore(50); // Methods work!
113
+ console.log(restored.score); // 150
114
+ ```
115
+
116
+ ### Dependency Injection Pattern
117
+
118
+ `super-ls` supports nested class instances, making it perfect for DI patterns:
119
+
120
+ ```javascript
121
+ class Weapon {
122
+ constructor(name = "", damage = 0) {
123
+ this.name = name;
124
+ this.damage = damage;
125
+ }
126
+
127
+ attack() {
128
+ return `${this.name} deals ${this.damage} damage!`;
129
+ }
130
+ }
131
+
132
+ class Warrior {
133
+ constructor(name = "", weapon = null) {
134
+ this.name = name;
135
+ this.weapon = weapon;
136
+ }
137
+
138
+ fight() {
139
+ if (!this.weapon) return `${this.name} has no weapon!`;
140
+ return `${this.name}: ${this.weapon.attack()}`;
141
+ }
142
+ }
143
+
144
+ // Register ALL classes in the dependency chain
145
+ superLs.register(Weapon);
146
+ superLs.register(Warrior);
147
+
148
+ // Create nested instances
149
+ const sword = new Weapon("Excalibur", 50);
150
+ const arthur = new Warrior("Arthur", sword);
151
+
152
+ superLs.set("hero", arthur);
153
+
154
+ // Restore with full dependency graph
155
+ const restored = superLs.get("hero");
156
+ console.log(restored instanceof Warrior); // true
157
+ console.log(restored.weapon instanceof Weapon); // true
158
+ console.log(restored.fight()); // "Arthur: Excalibur deals 50 damage!"
159
+ ```
160
+
161
+ ### Custom Hydration (Complex Constructors)
162
+
163
+ For classes with required constructor arguments or complex initialization:
164
+
165
+ ```javascript
166
+ class ImmutableUser {
167
+ constructor(id, email) {
168
+ if (!id || !email) throw new Error("id and email required!");
169
+ this.id = id;
170
+ this.email = email;
171
+ Object.freeze(this);
172
+ }
173
+
174
+ // Static hydrate method for custom reconstruction
175
+ static hydrate(data) {
176
+ return new ImmutableUser(data.id, data.email);
177
+ }
178
+ }
179
+
180
+ superLs.register(ImmutableUser);
181
+
182
+ const user = new ImmutableUser(1, "alice@example.com");
183
+ superLs.set("user", user);
184
+
185
+ const restored = superLs.get("user"); // Works! Uses hydrate() internally
186
+ ```
187
+
188
+ ### Custom Type Names
189
+
190
+ Useful for minified code or avoiding name collisions:
191
+
192
+ ```javascript
193
+ // Two modules both export "User" class
194
+ import { User as AdminUser } from "./admin";
195
+ import { User as CustomerUser } from "./customer";
196
+
197
+ superLs.register(AdminUser, "AdminUser");
198
+ superLs.register(CustomerUser, "CustomerUser");
199
+ ```
200
+
201
+ ### Multiple Storage Instances
202
+
203
+ For isolated registries or different prefixes:
204
+
205
+ ```javascript
206
+ import { SuperLocalStorage } from "@titanpl/super-ls";
207
+
208
+ const gameStorage = new SuperLocalStorage("game_");
209
+ const userStorage = new SuperLocalStorage("user_");
210
+
211
+ gameStorage.register(Player);
212
+ userStorage.register(Profile);
213
+
214
+ // Keys are prefixed automatically
215
+ gameStorage.set("hero", player); // Stored as "game_hero"
216
+ userStorage.set("current", profile); // Stored as "user_current"
217
+ ```
218
+
219
+ ---
220
+
221
+ ## ๐Ÿ“š API Reference
222
+
223
+ ### `superLs.set(key, value)`
224
+
225
+ Stores any JavaScript value in Titan storage.
226
+
227
+ | Parameter | Type | Description |
228
+ |-----------|------|-------------|
229
+ | `key` | `string` | Storage key |
230
+ | `value` | `any` | Data to store |
231
+
232
+ **Supported types**: primitives, objects, arrays, `Map`, `Set`, `Date`, `RegExp`, `BigInt`, `TypedArray`, `undefined`, `NaN`, `Infinity`, circular references, registered class instances.
233
+
234
+ ```javascript
235
+ superLs.set("config", { theme: "dark", items: new Set([1, 2, 3]) });
236
+ ```
237
+
238
+ ### `superLs.get(key)`
239
+
240
+ Retrieves and deserializes a value with full type restoration.
241
+
242
+ | Parameter | Type | Description |
243
+ |-----------|------|-------------|
244
+ | `key` | `string` | Storage key |
245
+ | **Returns** | `any \| null` | Restored value or `null` if not found |
246
+
247
+ ```javascript
248
+ const config = superLs.get("config");
249
+ if (config) {
250
+ console.log(config.items instanceof Set); // true
251
+ }
252
+ ```
253
+
254
+ ### `superLs.register(ClassRef, typeName?)`
255
+
256
+ Registers a class for automatic serialization/deserialization.
257
+
258
+ | Parameter | Type | Description |
259
+ |-----------|------|-------------|
260
+ | `ClassRef` | `Function` | Class constructor |
261
+ | `typeName` | `string?` | Custom type name (defaults to `ClassRef.name`) |
262
+
263
+ ```javascript
264
+ superLs.register(Player);
265
+ superLs.register(Enemy, "GameEnemy"); // Custom name
266
+ ```
267
+
268
+ ### `new SuperLocalStorage(prefix?)`
269
+
270
+ Creates a new storage instance with isolated registry.
271
+
272
+ | Parameter | Type | Default | Description |
273
+ |-----------|------|---------|-------------|
274
+ | `prefix` | `string` | `"sls_"` | Key prefix for all operations |
275
+
276
+ ```javascript
277
+ import { SuperLocalStorage } from "@titanpl/super-ls";
278
+ const custom = new SuperLocalStorage("myapp_");
279
+ ```
280
+
281
+ ---
282
+
283
+ ## ๐ŸŽฏ When to Use Static `hydrate()` Method
284
+
285
+ By default, `super-ls` reconstructs class instances like this:
286
+
287
+ ```javascript
288
+ const instance = new Constructor(); // Calls constructor WITHOUT arguments
289
+ Object.assign(instance, data); // Copies properties
290
+ ```
291
+
292
+ This works **only if** your constructor can be called without arguments:
293
+
294
+ ```javascript
295
+ // โœ… WORKS - has default values
296
+ class Player {
297
+ constructor(name = '', score = 0) {
298
+ this.name = name;
299
+ this.score = score;
300
+ }
301
+ }
302
+ ```
303
+
304
+ But **fails** if constructor requires arguments:
305
+
306
+ ```javascript
307
+ // โŒ FAILS - required arguments
308
+ class Player {
309
+ constructor(name, score) {
310
+ if (!name) throw new Error('Name is required!');
311
+ this.name = name;
312
+ this.score = score;
313
+ }
314
+ }
315
+
316
+ // super-ls tries: new Player() โ†’ ๐Ÿ’ฅ Error!
317
+ ```
318
+
319
+ ### The Solution
320
+
321
+ Define a static `hydrate()` method that tells `super-ls` how to reconstruct your class:
322
+
323
+ ```javascript
324
+ class Player {
325
+ constructor(name, score) {
326
+ if (!name) throw new Error('Name is required!');
327
+ this.name = name;
328
+ this.score = score;
329
+ }
330
+
331
+ static hydrate(data) {
332
+ return new Player(data.name, data.score);
333
+ }
334
+ }
335
+ ```
336
+
337
+ ### Quick Reference
338
+
339
+ | Constructor Style | Needs `hydrate()`? | Example |
340
+ |-------------------|-------------------|---------|
341
+ | All params have defaults | โŒ No | `constructor(name = '', score = 0)` |
342
+ | No parameters | โŒ No | `constructor()` |
343
+ | Required parameters | โœ… Yes | `constructor(name, score)` |
344
+ | Has validation | โœ… Yes | `if (!name) throw new Error()` |
345
+ | Uses `Object.freeze()` | โœ… Yes | `Object.freeze(this)` |
346
+ | Private fields (`#prop`) | โœ… Yes | `this.#secret = value` |
347
+ | Destructuring params | โœ… Yes | `constructor({ name, score })` |
348
+
349
+ ### Examples
350
+
351
+ ```javascript
352
+ // โœ… NO hydrate needed - has defaults
353
+ class Counter {
354
+ constructor(value = 0) {
355
+ this.value = value;
356
+ }
357
+ }
358
+
359
+ // โŒ NEEDS hydrate - required params
360
+ class Email {
361
+ constructor(value) {
362
+ if (!value.includes('@')) throw new Error('Invalid');
363
+ this.value = value;
364
+ }
365
+ static hydrate(data) {
366
+ return new Email(data.value);
367
+ }
368
+ }
369
+
370
+ // โŒ NEEDS hydrate - Object.freeze()
371
+ class ImmutableConfig {
372
+ constructor(settings) {
373
+ this.settings = settings;
374
+ Object.freeze(this);
375
+ }
376
+ static hydrate(data) {
377
+ return new ImmutableConfig(data.settings);
378
+ }
379
+ }
380
+
381
+ // โŒ NEEDS hydrate - destructuring
382
+ class Player {
383
+ constructor({ name, score }) {
384
+ this.name = name;
385
+ this.score = score;
386
+ }
387
+ static hydrate(data) {
388
+ return new Player({ name: data.name, score: data.score });
389
+ }
390
+ }
391
+ ```
392
+
393
+ ---
394
+
395
+ ## โš ๏ธ Known Limitations
396
+
397
+ | Limitation | Behavior | Workaround |
398
+ |------------|----------|------------|
399
+ | **Functions** | Throws error | Store function results, not functions |
400
+ | **WeakMap / WeakSet** | Silently becomes `{}` | Use `Map` / `Set` instead |
401
+ | **Symbol properties** | Not serialized | Use string keys |
402
+ | **Sparse arrays** | Holes become `undefined` | Use dense arrays or objects |
403
+ | **Unregistered classes** | Become plain objects (methods lost) | Register all classes |
404
+ | **Getters/Setters** | Not serialized as values | Work via prototype after restoration |
405
+
406
+ ---
407
+
408
+ ## ๐Ÿ”ง Under the Hood
409
+
410
+ `super-ls` uses a two-phase transformation:
411
+
412
+ ### Serialization (`set`)
413
+ 1. Recursively traverse the value
414
+ 2. Wrap registered class instances with type metadata (`__super_type__`, `__data__`)
415
+ 3. Track circular references via `WeakMap`
416
+ 4. Serialize using `devalue` (handles `Map`, `Set`, `Date`, etc.)
417
+ 5. Store string in `t.ls`
418
+
419
+ ### Deserialization (`get`)
420
+ 1. Parse string using `devalue`
421
+ 2. Recursively traverse parsed data
422
+ 3. Detect type metadata and restore class instances
423
+ 4. Create instance using:
424
+ - `hydrate()` if available
425
+ - Otherwise: `new Constructor()` + `Object.assign()`
426
+ 5. Preserve circular references via placeholder morphing
427
+
428
+ For detailed technical documentation, see [EXPLAIN.md](./EXPLAIN.md).
429
+
430
+ ---
431
+
432
+ ## ๐Ÿงช Testing
433
+
434
+ The library includes comprehensive test suites:
435
+
436
+ ```bash
437
+ # Install dependencies
438
+ npm install
439
+
440
+ # Run all tests
441
+ npm test
442
+
443
+ # Run with coverage
444
+ npm run test:coverage
445
+ ```
446
+
447
+ **Test Coverage**: 73 tests across 2 suites
448
+ - Normal cases: 36 tests (basic types, class hydration, DI patterns)
449
+ - Edge cases: 37 tests (inheritance, circular refs, stress tests)
450
+
451
+ See [TEST_DOCUMENTATION.md](./TEST_DOCUMENTATION.md) for detailed test descriptions.
452
+
453
+ ---
454
+
455
+ ## ๐Ÿ“ Project Structure
456
+
457
+ ```
458
+ super-ls/
459
+ โ”œโ”€โ”€ index.js # Main implementation
460
+ โ”œโ”€โ”€ index.d.ts # TypeScript definitions
461
+ โ”œโ”€โ”€ package.json
462
+ โ”œโ”€โ”€ README.md # This file
463
+ โ”œโ”€โ”€ EXPLAIN.md # Technical deep-dive
464
+ โ”œโ”€โ”€ TEST_DOCUMENTATION.md # Test suite documentation
465
+ โ””โ”€โ”€ tests/
466
+ โ”œโ”€โ”€ super-ls.normal-cases.spec.js
467
+ โ””โ”€โ”€ super-ls.edge-cases.spec.js
468
+ ```
469
+
470
+ ---
471
+
472
+ ## ๐Ÿค Contributing
473
+
474
+ Contributions are welcome! Please:
475
+
476
+ 1. Fork the repository
477
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
478
+ 3. Write tests for new functionality
479
+ 4. Ensure all tests pass (`npm test`)
480
+ 5. Submit a Pull Request
481
+
482
+ ---
483
+
484
+ ## ๐Ÿ“„ License
485
+
486
+ ISC ยฉ Titan Planet