@jqhtml/core 2.3.59 → 2.3.64

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.
Files changed (89) hide show
  1. package/dist/boot.d.ts +0 -0
  2. package/dist/boot.d.ts.map +0 -0
  3. package/dist/cache-key-serializer.d.ts +0 -0
  4. package/dist/cache-key-serializer.d.ts.map +0 -0
  5. package/dist/component-cache.d.ts +0 -0
  6. package/dist/component-cache.d.ts.map +0 -0
  7. package/dist/component-events.d.ts +0 -0
  8. package/dist/component-events.d.ts.map +0 -0
  9. package/dist/component-name.d.ts +13 -0
  10. package/dist/component-name.d.ts.map +1 -0
  11. package/dist/component-queue.d.ts +0 -0
  12. package/dist/component-queue.d.ts.map +0 -0
  13. package/dist/component-registry.d.ts +0 -0
  14. package/dist/component-registry.d.ts.map +1 -1
  15. package/dist/component.d.ts +2 -6
  16. package/dist/component.d.ts.map +1 -1
  17. package/dist/config.d.ts +0 -0
  18. package/dist/config.d.ts.map +0 -0
  19. package/dist/data-proxy.d.ts +0 -0
  20. package/dist/data-proxy.d.ts.map +0 -0
  21. package/dist/debug-entry.d.ts +0 -0
  22. package/dist/debug-entry.d.ts.map +0 -0
  23. package/dist/debug.d.ts +0 -0
  24. package/dist/debug.d.ts.map +0 -0
  25. package/dist/index.cjs +60 -44
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +58 -45
  30. package/dist/index.js.map +1 -1
  31. package/dist/instruction-processor.d.ts +2 -1
  32. package/dist/instruction-processor.d.ts.map +1 -1
  33. package/dist/jqhtml-core.esm.js +59 -46
  34. package/dist/jqhtml-core.esm.js.map +1 -1
  35. package/dist/jqhtml-debug.esm.js +0 -0
  36. package/dist/jqhtml-debug.esm.js.map +0 -0
  37. package/dist/jquery-plugin.d.ts +0 -0
  38. package/dist/jquery-plugin.d.ts.map +1 -1
  39. package/dist/lifecycle-manager.d.ts +0 -0
  40. package/dist/lifecycle-manager.d.ts.map +0 -0
  41. package/dist/load-coordinator.d.ts +0 -0
  42. package/dist/load-coordinator.d.ts.map +0 -0
  43. package/dist/local-storage.d.ts +0 -0
  44. package/dist/local-storage.d.ts.map +0 -0
  45. package/dist/preload-data.d.ts +0 -0
  46. package/dist/preload-data.d.ts.map +0 -0
  47. package/dist/template-renderer.d.ts +0 -0
  48. package/dist/template-renderer.d.ts.map +0 -0
  49. package/dist/viewport.d.ts +0 -0
  50. package/dist/viewport.d.ts.map +0 -0
  51. package/package.json +1 -1
  52. package/dist/boot.js +0 -130
  53. package/dist/boot.js.map +0 -1
  54. package/dist/cache-key-serializer.js +0 -140
  55. package/dist/cache-key-serializer.js.map +0 -1
  56. package/dist/component-cache.js +0 -270
  57. package/dist/component-cache.js.map +0 -1
  58. package/dist/component-events.js +0 -141
  59. package/dist/component-events.js.map +0 -1
  60. package/dist/component-queue.js +0 -118
  61. package/dist/component-queue.js.map +0 -1
  62. package/dist/component-registry.js +0 -298
  63. package/dist/component-registry.js.map +0 -1
  64. package/dist/component.js +0 -1892
  65. package/dist/component.js.map +0 -1
  66. package/dist/config.js +0 -64
  67. package/dist/config.js.map +0 -1
  68. package/dist/data-proxy.js +0 -223
  69. package/dist/data-proxy.js.map +0 -1
  70. package/dist/debug-entry.js +0 -8
  71. package/dist/debug-entry.js.map +0 -1
  72. package/dist/debug.js +0 -203
  73. package/dist/debug.js.map +0 -1
  74. package/dist/instruction-processor.js +0 -563
  75. package/dist/instruction-processor.js.map +0 -1
  76. package/dist/jquery-plugin.js +0 -472
  77. package/dist/jquery-plugin.js.map +0 -1
  78. package/dist/lifecycle-manager.js +0 -201
  79. package/dist/lifecycle-manager.js.map +0 -1
  80. package/dist/load-coordinator.js +0 -309
  81. package/dist/load-coordinator.js.map +0 -1
  82. package/dist/local-storage.js +0 -699
  83. package/dist/local-storage.js.map +0 -1
  84. package/dist/preload-data.js +0 -136
  85. package/dist/preload-data.js.map +0 -1
  86. package/dist/template-renderer.js +0 -278
  87. package/dist/template-renderer.js.map +0 -1
  88. package/dist/viewport.js +0 -69
  89. package/dist/viewport.js.map +0 -1
@@ -1,699 +0,0 @@
1
- /**
2
- * Jqhtml_Local_Storage - Scoped local storage for JQHTML component caching
3
- *
4
- * Provides safe, scoped access to localStorage with automatic handling of
5
- * unavailable storage, quota exceeded errors, and scope invalidation.
6
- *
7
- * Key Features:
8
- * - **Manual scoping**: Cache key must be set via set_cache_key() before use
9
- * - **Graceful degradation**: Returns null when storage unavailable or cache key not set
10
- * - **Quota management**: Auto-clears storage when full and retries operation
11
- * - **Scope validation**: Clears storage when cache key changes
12
- * - **Developer-friendly keys**: Scoped suffix allows easy inspection in dev tools
13
- * - **Class-aware serialization**: ES6 class instances serialize and restore properly
14
- *
15
- * Class-Aware Serialization:
16
- * ES6 class instances can be serialized and deserialized if registered via
17
- * register_cache_class(). Classes are wrapped as {__jqhtml_class__: "Name", __jqhtml_props__: {...}}
18
- * and restored to proper instances on retrieval. Nested class instances in objects
19
- * and arrays are handled recursively.
20
- *
21
- * Scoping Strategy:
22
- * Storage is scoped by a user-provided cache key (typically a session identifier,
23
- * user ID, or combination of relevant scope factors). This scope is stored in
24
- * `_jqhtml_cache_key`. If the cache key changes between page loads, all JQHTML
25
- * storage is cleared to prevent stale data from different sessions/users.
26
- *
27
- * Key Format:
28
- * Keys are stored as: `jqhtml::developer_key::cache_key`
29
- * Example: `jqhtml::User_Profile_data::user_123`
30
- *
31
- * The `jqhtml::` prefix identifies JQHTML keys, allowing safe clearing of only our keys
32
- * without affecting other JavaScript libraries. This enables transparent coexistence
33
- * with third-party libraries that also use browser storage.
34
- *
35
- * Quota Exceeded Handling:
36
- * When storage quota is exceeded during a set operation, only JQHTML keys (prefixed with
37
- * `jqhtml::`) are cleared, preserving other libraries' data. The operation is then retried
38
- * once. This ensures the application continues functioning even when storage is full.
39
- *
40
- * Usage:
41
- * // Register classes that need to be cached (call once at startup)
42
- * jqhtml.register_cache_class(Contact_Model);
43
- * jqhtml.register_cache_class(User_Profile);
44
- *
45
- * // Must set cache key first (typically done once on page load)
46
- * Jqhtml_Local_Storage.set_cache_key('user_123');
47
- *
48
- * // Then use storage normally - ES6 classes serialize automatically
49
- * Jqhtml_Local_Storage.set('cached_component', {
50
- * contact: new Contact_Model(),
51
- * timestamp: Date.now()
52
- * });
53
- * const cached = Jqhtml_Local_Storage.get('cached_component');
54
- * // cached.contact instanceof Contact_Model === true
55
- *
56
- * IMPORTANT - Volatile Storage:
57
- * Storage can be cleared at any time due to:
58
- * - User clearing browser data
59
- * - Private browsing mode restrictions
60
- * - Quota exceeded errors
61
- * - Cache key changes
62
- * - Browser storage unavailable
63
- *
64
- * Therefore, NEVER store critical data. Only use for:
65
- * - Cached component HTML (performance optimization)
66
- * - Transient UI state (convenience, not required)
67
- *
68
- * @internal This class is not exposed in the public API
69
- */
70
- // ============================================================================
71
- // Class Registry for Serialization
72
- // ============================================================================
73
- // Registry mapping class names to constructors
74
- const class_registry = Object.create(null);
75
- // Markers for serialized class instances (unique to avoid collisions with user data)
76
- // Replaced at build time by rollup (see rollup.config.js).
77
- const CORE_VERSION = '__VERSION__';
78
- const CLASS_MARKER = '__jqhtml_class__';
79
- const PROPS_MARKER = '__jqhtml_props__';
80
- /**
81
- * Register a class for cache serialization/deserialization.
82
- * Must be called before attempting to cache instances of this class.
83
- *
84
- * @param klass - The class constructor to register
85
- * @throws Error if klass is not a named function/class
86
- */
87
- export function register_cache_class(klass) {
88
- if (typeof klass !== 'function' || !klass.name) {
89
- throw new Error('[JQHTML Cache] register_cache_class requires a named class constructor');
90
- }
91
- class_registry[klass.name] = klass;
92
- }
93
- /**
94
- * Check if a class is registered for caching
95
- */
96
- export function is_cache_class_registered(class_name) {
97
- return class_name in class_registry;
98
- }
99
- // ============================================================================
100
- // Serialization (Object → String)
101
- // ============================================================================
102
- /**
103
- * Serialize a value to JSON string, handling ES6 class instances recursively.
104
- * Returns null if serialization fails for any reason.
105
- *
106
- * @param value - The value to serialize
107
- * @param verbose - Whether to log warnings
108
- * @returns Serialized JSON string, or null if serialization failed
109
- */
110
- function serialize_value(value, verbose) {
111
- try {
112
- const seen = new WeakSet();
113
- const processed = process_for_serialization(value, verbose, seen);
114
- if (processed === undefined) {
115
- // Serialization failed - undefined signals failure
116
- return null;
117
- }
118
- return JSON.stringify(processed);
119
- }
120
- catch (e) {
121
- if (verbose) {
122
- console.warn('[JQHTML Cache] Serialization failed:', e);
123
- }
124
- return null;
125
- }
126
- }
127
- /**
128
- * Recursively process a value for serialization.
129
- * Returns undefined if serialization should fail (unserializable value encountered).
130
- *
131
- * @param value - The value to process
132
- * @param verbose - Whether to log warnings
133
- * @param seen - WeakSet to detect circular references
134
- * @returns Processed value ready for JSON.stringify, or undefined if failed
135
- */
136
- function process_for_serialization(value, verbose, seen) {
137
- // Handle null and undefined
138
- if (value === null) {
139
- return null;
140
- }
141
- if (value === undefined) {
142
- return undefined; // JSON.stringify will omit this property
143
- }
144
- // Handle primitives
145
- if (typeof value !== 'object') {
146
- // string, number, boolean are fine
147
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
148
- return value;
149
- }
150
- // bigint, symbol, function cannot be serialized
151
- if (verbose) {
152
- console.warn(`[JQHTML Cache] Cannot serialize ${typeof value} - value will be omitted`);
153
- }
154
- // Return undefined to omit this value (not fail entire serialization)
155
- return undefined;
156
- }
157
- // Handle circular references.
158
- // `seen` tracks only the current ancestor path (the DFS stack): an object is
159
- // added on the way down and removed on the way back up (see finally below).
160
- // This flags a genuine cycle (a back-edge to an ancestor) while allowing a
161
- // shared reference in a DAG to be serialized independently on each visit.
162
- // JSON round-trip semantics do not preserve identity anyway, so emitting a
163
- // copy per reference is correct; silently omitting the property is not.
164
- if (seen.has(value)) {
165
- if (verbose) {
166
- console.warn('[JQHTML Cache] Circular reference detected - cannot serialize');
167
- }
168
- return undefined; // Fail entire serialization
169
- }
170
- seen.add(value);
171
- try {
172
- // Handle arrays
173
- if (Array.isArray(value)) {
174
- const result = [];
175
- for (let i = 0; i < value.length; i++) {
176
- const item = value[i];
177
- const processed = process_for_serialization(item, verbose, seen);
178
- // For arrays, we keep undefined as null to preserve indices
179
- result.push(processed === undefined ? null : processed);
180
- }
181
- return result;
182
- }
183
- // Handle Date objects specially
184
- if (value instanceof Date) {
185
- return {
186
- [CLASS_MARKER]: 'Date',
187
- [PROPS_MARKER]: value.toISOString()
188
- };
189
- }
190
- // Handle Map objects
191
- if (value instanceof Map) {
192
- const entries = [];
193
- for (const [k, v] of value) {
194
- const processedKey = process_for_serialization(k, verbose, seen);
195
- const processedValue = process_for_serialization(v, verbose, seen);
196
- entries.push([processedKey, processedValue]);
197
- }
198
- return {
199
- [CLASS_MARKER]: 'Map',
200
- [PROPS_MARKER]: entries
201
- };
202
- }
203
- // Handle Set objects
204
- if (value instanceof Set) {
205
- const items = [];
206
- for (const item of value) {
207
- items.push(process_for_serialization(item, verbose, seen));
208
- }
209
- return {
210
- [CLASS_MARKER]: 'Set',
211
- [PROPS_MARKER]: items
212
- };
213
- }
214
- // Get the constructor
215
- const ctor = value.constructor;
216
- // Handle registered class instances
217
- if (ctor && ctor.name && class_registry[ctor.name]) {
218
- const props = {};
219
- // Extract own enumerable properties (bypass any toJSON method)
220
- for (const key of Object.keys(value)) {
221
- const propValue = value[key];
222
- const processed = process_for_serialization(propValue, verbose, seen);
223
- // Only include if not undefined (failed serialization)
224
- if (processed !== undefined || propValue === undefined) {
225
- props[key] = processed;
226
- }
227
- }
228
- return {
229
- [CLASS_MARKER]: ctor.name,
230
- [PROPS_MARKER]: props
231
- };
232
- }
233
- // Handle plain objects (constructor is Object or no constructor)
234
- if (ctor === Object || ctor === undefined || ctor === null) {
235
- const result = {};
236
- for (const key of Object.keys(value)) {
237
- const propValue = value[key];
238
- const processed = process_for_serialization(propValue, verbose, seen);
239
- // Only include if not undefined (unless original was undefined)
240
- if (processed !== undefined || propValue === undefined) {
241
- result[key] = processed;
242
- }
243
- }
244
- return result;
245
- }
246
- // Unregistered class instance - convert to plain object for hot/cold parity
247
- // This ensures developers catch missing class registrations during development
248
- // (methods will be lost, only properties preserved)
249
- if (verbose) {
250
- console.warn(`[JQHTML Cache] Converting unregistered class "${ctor.name}" to plain object. ` +
251
- `Methods will be lost. Call jqhtml.register_cache_class(${ctor.name}) to preserve the class.`);
252
- }
253
- // Convert to plain object - properties are preserved, prototype methods are lost
254
- const result = {};
255
- for (const key of Object.keys(value)) {
256
- const propValue = value[key];
257
- const processed = process_for_serialization(propValue, verbose, seen);
258
- // Only include if not undefined (unless original was undefined)
259
- if (processed !== undefined || propValue === undefined) {
260
- result[key] = processed;
261
- }
262
- }
263
- return result;
264
- }
265
- finally {
266
- // Pop this object off the ancestor path now that its subtree is done.
267
- seen.delete(value);
268
- }
269
- }
270
- // ============================================================================
271
- // Deserialization (String → Object)
272
- // ============================================================================
273
- /**
274
- * Deserialize a JSON string, restoring ES6 class instances.
275
- * Returns null if deserialization fails.
276
- *
277
- * @param str - The JSON string to deserialize
278
- * @param verbose - Whether to log warnings
279
- * @returns Deserialized value with class instances restored, or null if failed
280
- */
281
- function deserialize_value(str, verbose) {
282
- try {
283
- const parsed = JSON.parse(str);
284
- return process_for_deserialization(parsed, verbose);
285
- }
286
- catch (e) {
287
- if (verbose) {
288
- console.warn('[JQHTML Cache] Deserialization failed:', e);
289
- }
290
- return null;
291
- }
292
- }
293
- /**
294
- * Recursively process a parsed value, restoring class instances.
295
- *
296
- * @param value - The parsed value to process
297
- * @param verbose - Whether to log warnings
298
- * @returns Processed value with class instances restored
299
- */
300
- function process_for_deserialization(value, verbose) {
301
- // Handle primitives and null
302
- if (value === null || value === undefined || typeof value !== 'object') {
303
- return value;
304
- }
305
- // Handle arrays
306
- if (Array.isArray(value)) {
307
- return value.map(item => process_for_deserialization(item, verbose));
308
- }
309
- // Check for class marker
310
- if (value[CLASS_MARKER] !== undefined && value[PROPS_MARKER] !== undefined) {
311
- const class_name = value[CLASS_MARKER];
312
- const props = value[PROPS_MARKER];
313
- // Handle built-in types
314
- if (class_name === 'Date') {
315
- return new Date(props);
316
- }
317
- if (class_name === 'Map') {
318
- const map = new Map();
319
- for (const [k, v] of props) {
320
- map.set(process_for_deserialization(k, verbose), process_for_deserialization(v, verbose));
321
- }
322
- return map;
323
- }
324
- if (class_name === 'Set') {
325
- const set = new Set();
326
- for (const item of props) {
327
- set.add(process_for_deserialization(item, verbose));
328
- }
329
- return set;
330
- }
331
- // Look up registered class
332
- const klass = class_registry[class_name];
333
- if (!klass) {
334
- if (verbose) {
335
- console.warn(`[JQHTML Cache] Cannot restore class "${class_name}" - not registered. ` +
336
- `Data will be returned as plain object. ` +
337
- `Call jqhtml.register_cache_class(${class_name}) to restore class instances.`);
338
- }
339
- // Return as plain object rather than failing completely
340
- return process_for_deserialization(props, verbose);
341
- }
342
- // Create instance without calling constructor and assign properties
343
- try {
344
- const instance = Object.create(klass.prototype);
345
- const processed_props = process_for_deserialization(props, verbose);
346
- Object.assign(instance, processed_props);
347
- return instance;
348
- }
349
- catch (e) {
350
- if (verbose) {
351
- console.warn(`[JQHTML Cache] Failed to restore class "${class_name}":`, e);
352
- }
353
- // Return null to signal cache should be treated as miss
354
- return null;
355
- }
356
- }
357
- // Plain object - process recursively
358
- const result = {};
359
- for (const key of Object.keys(value)) {
360
- result[key] = process_for_deserialization(value[key], verbose);
361
- }
362
- return result;
363
- }
364
- export class Jqhtml_Local_Storage {
365
- /**
366
- * Set the cache key and initialize storage
367
- * Must be called before any get/set operations
368
- * @param {string} cache_key - Unique identifier for this cache scope
369
- * @param {CacheMode} cache_mode - Cache strategy: 'data' (default, recommended) or 'html'
370
- */
371
- static set_cache_key(cache_key, cache_mode = 'data') {
372
- this._cache_key = cache_key;
373
- // Key SHAPE is a function of the library (see cache-key-serializer.ts), so an
374
- // upgrade that changes it must invalidate everything persisted under the old shape.
375
- // Version the SCOPE MARKER rather than the key itself: _validate_scope() already
376
- // clears every jqhtml key when this value changes, and leaving the per-entry key
377
- // format alone keeps stored keys readable and avoids repeating the version in each.
378
- this._scope_marker = `${CORE_VERSION}::${cache_key}`;
379
- this._cache_mode = cache_mode;
380
- this._init();
381
- }
382
- /**
383
- * Check if cache key has been set
384
- * @returns {boolean} True if cache key is configured
385
- */
386
- static has_cache_key() {
387
- return this._cache_key !== null;
388
- }
389
- /**
390
- * Get the current cache mode
391
- * @returns {CacheMode} Current cache mode ('data' or 'html')
392
- */
393
- static get_cache_mode() {
394
- return this._cache_mode;
395
- }
396
- /**
397
- * Initialize storage system and validate scope
398
- * Called automatically after cache key is set
399
- * @private
400
- */
401
- static _init() {
402
- // Check storage availability
403
- if (this._storage_available === null) {
404
- this._storage_available = this._is_storage_available();
405
- }
406
- if (!this._storage_available || !this._cache_key) {
407
- return;
408
- }
409
- // Validate scope
410
- this._validate_scope();
411
- this._initialized = true;
412
- }
413
- /**
414
- * Check if localStorage is available
415
- * @returns {boolean}
416
- * @private
417
- */
418
- static _is_storage_available() {
419
- try {
420
- const storage = window.localStorage;
421
- const test = '__jqhtml_storage_test__';
422
- storage.setItem(test, test);
423
- storage.removeItem(test);
424
- return true;
425
- }
426
- catch (e) {
427
- return false;
428
- }
429
- }
430
- /**
431
- * Check if verbose mode is enabled
432
- * @private
433
- */
434
- static _is_verbose() {
435
- return window.jqhtml?.debug?.verbose === true;
436
- }
437
- /**
438
- * Validate storage scope and clear JQHTML keys if cache key changed
439
- * Only clears keys prefixed with 'jqhtml::' to preserve other libraries' data
440
- * @private
441
- */
442
- static _validate_scope() {
443
- if (!this._storage_available) {
444
- return;
445
- }
446
- try {
447
- const stored_key = localStorage.getItem('_jqhtml_cache_key');
448
- // If the scope marker (core version + developer key) changed, clear only JQHTML keys
449
- if (stored_key !== null && stored_key !== this._scope_marker) {
450
- console.log('[JQHTML Local Storage] Cache scope changed, clearing JQHTML keys:', {
451
- old_key: stored_key,
452
- new_key: this._scope_marker,
453
- });
454
- this._clear_jqhtml_keys();
455
- localStorage.setItem('_jqhtml_cache_key', this._scope_marker);
456
- }
457
- else if (stored_key === null) {
458
- // First time JQHTML is using this storage - just set the key, don't clear
459
- console.log('[JQHTML Local Storage] Initializing cache scope (first use):', {
460
- new_key: this._scope_marker,
461
- });
462
- localStorage.setItem('_jqhtml_cache_key', this._scope_marker);
463
- }
464
- }
465
- catch (e) {
466
- console.error('[JQHTML Local Storage] Failed to validate scope:', e);
467
- }
468
- }
469
- /**
470
- * Clear only JQHTML keys from storage (keys starting with 'jqhtml::')
471
- * Preserves keys from other libraries
472
- * @private
473
- */
474
- static _clear_jqhtml_keys() {
475
- if (!this._storage_available) {
476
- return;
477
- }
478
- const keys_to_remove = [];
479
- // Collect all JQHTML keys
480
- for (let i = 0; i < localStorage.length; i++) {
481
- const key = localStorage.key(i);
482
- if (key && key.startsWith('jqhtml::')) {
483
- keys_to_remove.push(key);
484
- }
485
- }
486
- // Remove collected keys
487
- keys_to_remove.forEach(key => {
488
- try {
489
- localStorage.removeItem(key);
490
- }
491
- catch (e) {
492
- console.error('[JQHTML Local Storage] Failed to remove key:', key, e);
493
- }
494
- });
495
- console.log(`[JQHTML Local Storage] Cleared ${keys_to_remove.length} JQHTML keys`);
496
- }
497
- /**
498
- * Build scoped key with JQHTML namespace prefix
499
- * @param {string} key - Developer-provided key
500
- * @returns {string}
501
- * @private
502
- */
503
- static _build_key(key) {
504
- return `jqhtml::${key}::${this._cache_key}`;
505
- }
506
- /**
507
- * Check if storage is ready for use
508
- * @returns {boolean}
509
- * @private
510
- */
511
- static _is_ready() {
512
- return this._storage_available === true && this._cache_key !== null && this._initialized;
513
- }
514
- /**
515
- * Set item in localStorage with class-aware serialization.
516
- *
517
- * If serialization fails (e.g., unregistered class instances, circular refs),
518
- * the existing cache entry is removed and nothing is stored.
519
- *
520
- * @param {string} key - Storage key
521
- * @param {*} value - Value to store (primitives, objects, arrays, or registered class instances)
522
- */
523
- static set(key, value) {
524
- if (!this._is_ready()) {
525
- return;
526
- }
527
- const verbose = this._is_verbose();
528
- const scoped_key = this._build_key(key);
529
- // Serialize with class-aware processing
530
- const serialized = serialize_value(value, verbose);
531
- if (serialized === null) {
532
- // Serialization failed - remove any existing cache entry
533
- if (verbose) {
534
- console.warn(`[JQHTML Cache] Failed to serialize value for key "${key}". ` +
535
- `Removing existing cache entry if present.`);
536
- }
537
- try {
538
- localStorage.removeItem(scoped_key);
539
- }
540
- catch (e) {
541
- // Ignore removal errors
542
- }
543
- return;
544
- }
545
- // Check size before attempting to store (1MB limit)
546
- const size_bytes = new Blob([serialized]).size;
547
- const size_mb = size_bytes / (1024 * 1024);
548
- if (size_mb > 1) {
549
- if (verbose) {
550
- console.warn(`[JQHTML Cache] Skipping set - value too large (${size_mb.toFixed(2)}MB > 1MB limit)`, { key, size_bytes, size_mb });
551
- }
552
- // Remove existing cache entry since we can't update it
553
- try {
554
- localStorage.removeItem(scoped_key);
555
- }
556
- catch (e) {
557
- // Ignore removal errors
558
- }
559
- return;
560
- }
561
- this._set_item(key, serialized);
562
- }
563
- /**
564
- * Get item from localStorage with class-aware deserialization.
565
- *
566
- * If deserialization fails, returns null (as if no cache exists).
567
- *
568
- * @param {string} key - Storage key
569
- * @returns {*|null} Deserialized value with class instances restored, or null if not found/failed
570
- */
571
- static get(key) {
572
- if (!this._is_ready()) {
573
- return null;
574
- }
575
- const verbose = this._is_verbose();
576
- const scoped_key = this._build_key(key);
577
- try {
578
- const serialized = localStorage.getItem(scoped_key);
579
- if (serialized === null) {
580
- return null;
581
- }
582
- const result = deserialize_value(serialized, verbose);
583
- if (result === null) {
584
- // Deserialization failed - treat as cache miss
585
- if (verbose) {
586
- console.warn(`[JQHTML Cache] Failed to deserialize value for key "${key}". ` +
587
- `Treating as cache miss.`);
588
- }
589
- return null;
590
- }
591
- return result;
592
- }
593
- catch (e) {
594
- if (verbose) {
595
- console.warn('[JQHTML Cache] Failed to get item:', e);
596
- }
597
- return null;
598
- }
599
- }
600
- /**
601
- * Remove item from localStorage
602
- * @param {string} key - Storage key
603
- */
604
- static remove(key) {
605
- if (!this._is_ready()) {
606
- return;
607
- }
608
- this._remove_item(key);
609
- }
610
- /**
611
- * Perform a serialize/deserialize round-trip on a value.
612
- *
613
- * This ensures "hot" data (fresh from on_load) behaves identically to "cold" data
614
- * (restored from cache). Unregistered class instances will be converted to plain
615
- * objects, exactly as they would be if restored from cache.
616
- *
617
- * Use this to normalize data after on_load() so developers catch missing class
618
- * registrations immediately rather than only after a page reload.
619
- *
620
- * @param {any} value - The value to normalize
621
- * @returns {any} The value after serialize/deserialize round-trip, or original if serialization fails
622
- */
623
- static normalize_for_cache(value) {
624
- const verbose = this._is_verbose();
625
- // Serialize to JSON string
626
- const serialized = serialize_value(value, verbose);
627
- if (serialized === null) {
628
- // Serialization failed completely - return original
629
- // (This happens with circular references or other fatal issues)
630
- if (verbose) {
631
- console.warn('[JQHTML Cache] normalize_for_cache: Serialization failed, returning original value');
632
- }
633
- return value;
634
- }
635
- // Deserialize back to object
636
- const deserialized = deserialize_value(serialized, verbose);
637
- if (deserialized === null) {
638
- // Deserialization failed - return original
639
- if (verbose) {
640
- console.warn('[JQHTML Cache] normalize_for_cache: Deserialization failed, returning original value');
641
- }
642
- return value;
643
- }
644
- return deserialized;
645
- }
646
- /**
647
- * Internal set implementation with scope validation and quota handling
648
- * @param {string} key
649
- * @param {string} serialized - Pre-serialized JSON string
650
- * @private
651
- */
652
- static _set_item(key, serialized) {
653
- // Validate scope before every write
654
- this._validate_scope();
655
- const scoped_key = this._build_key(key);
656
- try {
657
- localStorage.setItem(scoped_key, serialized);
658
- }
659
- catch (e) {
660
- // Check if quota exceeded
661
- if (e.name === 'QuotaExceededError' || e.code === 22) {
662
- console.warn('[JQHTML Local Storage] Quota exceeded, clearing JQHTML keys and retrying');
663
- // Clear only JQHTML keys and retry once
664
- this._clear_jqhtml_keys();
665
- localStorage.setItem('_jqhtml_cache_key', this._cache_key);
666
- try {
667
- localStorage.setItem(scoped_key, serialized);
668
- }
669
- catch (retry_error) {
670
- console.error('[JQHTML Local Storage] Failed to set item after clearing JQHTML keys:', retry_error);
671
- }
672
- }
673
- else {
674
- console.error('[JQHTML Local Storage] Failed to set item:', e);
675
- }
676
- }
677
- }
678
- /**
679
- * Internal remove implementation
680
- * @param {string} key
681
- * @private
682
- */
683
- static _remove_item(key) {
684
- const scoped_key = this._build_key(key);
685
- try {
686
- localStorage.removeItem(scoped_key);
687
- }
688
- catch (e) {
689
- console.error('[JQHTML Local Storage] Failed to remove item:', e);
690
- }
691
- }
692
- }
693
- Jqhtml_Local_Storage._cache_key = null;
694
- /** Versioned scope identity - compared in _validate_scope() to clear on a core upgrade. */
695
- Jqhtml_Local_Storage._scope_marker = null;
696
- Jqhtml_Local_Storage._cache_mode = 'data';
697
- Jqhtml_Local_Storage._storage_available = null;
698
- Jqhtml_Local_Storage._initialized = false;
699
- //# sourceMappingURL=local-storage.js.map