tiny-essentials 1.19.2 → 1.19.3

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.
@@ -19272,6 +19272,12 @@ class TinyLocalStorage {
19272
19272
  /** @type {Storage} */
19273
19273
  #localStorage = window.localStorage;
19274
19274
 
19275
+ /** @type {string|null} */
19276
+ #dbKey = null;
19277
+
19278
+ /** @type {number} */
19279
+ #version = 0;
19280
+
19275
19281
  /** @type {(ev: StorageEvent) => any} */
19276
19282
  #storageEvent = (ev) => this.emit('storage', ev);
19277
19283
 
@@ -19279,11 +19285,84 @@ class TinyLocalStorage {
19279
19285
  * Initializes the TinyLocalStorage instance and sets up cross-tab sync.
19280
19286
  *
19281
19287
  * Adds listener for the native `storage` event to support tab synchronization.
19288
+ * @param {string} [dbName] - Unique database name.
19282
19289
  */
19283
- constructor() {
19290
+ constructor(dbName) {
19291
+ if (typeof dbName !== 'undefined' && typeof dbName !== 'string')
19292
+ throw new TypeError('TinyLocalStorage: dbName must be a string if provided.');
19293
+ if (typeof dbName === 'string') this.#dbKey = `LSDB::${dbName}`;
19284
19294
  window.addEventListener('storage', this.#storageEvent);
19285
19295
  }
19286
19296
 
19297
+ /**
19298
+ * Validates that a given key does not conflict with internal database keys.
19299
+ *
19300
+ * This method is used to prevent accidental overwriting of reserved `LSDB::` keys
19301
+ * in `localStorage`, which are used internally by TinyLocalStorage for versioning
19302
+ * and data management.
19303
+ *
19304
+ * @param {string} name - The key to validate before writing to localStorage.
19305
+ * @throws {Error} If the key starts with `LSDB::`.
19306
+ */
19307
+ #isProtectedDbKey(name) {
19308
+ if (typeof name === 'string' && name.startsWith('LSDB::'))
19309
+ throw new Error(`TinyLocalStorage: Key "${name}" may conflict with reserved dbKeys.`);
19310
+ }
19311
+
19312
+ /**
19313
+ * Updates the version of the storage and triggers migration if needed.
19314
+ *
19315
+ * @param {number} version - Desired version of the database.
19316
+ * @param {(oldVersion: number, newVersion: number) => void} onUpgrade - Callback to perform migration logic.
19317
+ * @throws {Error} If the database key has not been initialized.
19318
+ * @throws {TypeError} If `version` is not a valid positive number.
19319
+ * @throws {TypeError} If `onUpgrade` is not a function.
19320
+ */
19321
+ updateStorageVersion(version, onUpgrade) {
19322
+ if (typeof this.#dbKey !== 'string')
19323
+ throw new Error(
19324
+ 'TinyLocalStorage: Database key is not initialized. Set a valid dbName in the constructor.',
19325
+ );
19326
+ if (typeof version !== 'number' || Number.isNaN(version) || version < 1)
19327
+ throw new TypeError('TinyLocalStorage: version must be a positive number.');
19328
+ if (typeof onUpgrade !== 'function')
19329
+ throw new TypeError('TinyLocalStorage: onUpgrade must be a function.');
19330
+
19331
+ // @ts-ignore
19332
+ const savedVersion = parseInt(localStorage.getItem(this.#dbKey), 10) || 0;
19333
+ if (typeof savedVersion !== 'number' || Number.isNaN(savedVersion) || savedVersion < 0)
19334
+ throw new TypeError('TinyLocalStorage: saved version in localStorage is not a valid number.');
19335
+
19336
+ if (version < savedVersion)
19337
+ throw new Error(
19338
+ `TinyLocalStorage: Cannot downgrade database version from ${savedVersion} to ${version}.`,
19339
+ );
19340
+
19341
+ if (version > savedVersion) {
19342
+ onUpgrade(savedVersion, version);
19343
+ localStorage.setItem(this.#dbKey, String(version));
19344
+ this.#version = version;
19345
+ }
19346
+ }
19347
+
19348
+ /**
19349
+ * Gets the current database key used in localStorage.
19350
+ *
19351
+ * @returns {string|null} The database key, or null if not set.
19352
+ */
19353
+ getDbKey() {
19354
+ return this.#dbKey;
19355
+ }
19356
+
19357
+ /**
19358
+ * Gets the current version of the database.
19359
+ *
19360
+ * @returns {number} The current version number.
19361
+ */
19362
+ getVersion() {
19363
+ return this.#version;
19364
+ }
19365
+
19287
19366
  /**
19288
19367
  * Defines a custom storage interface (e.g. `sessionStorage`).
19289
19368
  *
@@ -19291,7 +19370,7 @@ class TinyLocalStorage {
19291
19370
  */
19292
19371
  setLocalStorage(localstorage) {
19293
19372
  if (!(localstorage instanceof Storage))
19294
- throw new Error('Argument must be a valid instance of Storage.');
19373
+ throw new TypeError('Argument must be a valid instance of Storage.');
19295
19374
  this.#localStorage = localstorage;
19296
19375
  }
19297
19376
 
@@ -19313,7 +19392,7 @@ class TinyLocalStorage {
19313
19392
  */
19314
19393
  #setJson(name, data) {
19315
19394
  if (typeof name !== 'string' || !name.length)
19316
- throw new Error('Key must be a non-empty string.');
19395
+ throw new TypeError('Key must be a non-empty string.');
19317
19396
  return TinyLocalStorage.encodeSpecialJson(data);
19318
19397
  }
19319
19398
 
@@ -19326,13 +19405,14 @@ class TinyLocalStorage {
19326
19405
  * @param {LocalStorageJsonValue} data - The data to be serialized and stored.
19327
19406
  */
19328
19407
  setJson(name, data) {
19408
+ this.#isProtectedDbKey(name);
19329
19409
  if (
19330
19410
  !isJsonObject(data) &&
19331
19411
  !Array.isArray(data) &&
19332
19412
  !(data instanceof Map) &&
19333
19413
  !(data instanceof Set)
19334
19414
  ) {
19335
- throw new Error('The storage value is not a valid JSON-compatible structure.');
19415
+ throw new TypeError('The storage value is not a valid JSON-compatible structure.');
19336
19416
  }
19337
19417
  const encoded = this.#setJson(name, data);
19338
19418
  this.emit('setJson', name, data);
@@ -19350,7 +19430,7 @@ class TinyLocalStorage {
19350
19430
  */
19351
19431
  #getJson(name, defaultData) {
19352
19432
  if (typeof name !== 'string' || !name.length)
19353
- throw new Error('Key must be a non-empty string.');
19433
+ throw new TypeError('Key must be a non-empty string.');
19354
19434
 
19355
19435
  const raw = this.#localStorage.getItem(name);
19356
19436
  const fallbackTypes = {
@@ -19404,7 +19484,8 @@ class TinyLocalStorage {
19404
19484
  * @param {Date} data
19405
19485
  */
19406
19486
  setDate(name, data) {
19407
- if (!(data instanceof Date)) throw new Error('Value must be a Date.');
19487
+ this.#isProtectedDbKey(name);
19488
+ if (!(data instanceof Date)) throw new TypeError('Value must be a Date.');
19408
19489
  const encoded = this.#setJson(name, data);
19409
19490
  this.emit('setDate', name, data);
19410
19491
  return this.#localStorage.setItem(name, JSON.stringify(encoded));
@@ -19426,7 +19507,8 @@ class TinyLocalStorage {
19426
19507
  * @param {RegExp} data
19427
19508
  */
19428
19509
  setRegExp(name, data) {
19429
- if (!(data instanceof RegExp)) throw new Error('Value must be a RegExp.');
19510
+ this.#isProtectedDbKey(name);
19511
+ if (!(data instanceof RegExp)) throw new TypeError('Value must be a RegExp.');
19430
19512
  const encoded = this.#setJson(name, data);
19431
19513
  this.emit('setRegExp', name, data);
19432
19514
  return this.#localStorage.setItem(name, JSON.stringify(encoded));
@@ -19448,7 +19530,8 @@ class TinyLocalStorage {
19448
19530
  * @param {bigint} data
19449
19531
  */
19450
19532
  setBigInt(name, data) {
19451
- if (typeof data !== 'bigint') throw new Error('Value must be a BigInt.');
19533
+ this.#isProtectedDbKey(name);
19534
+ if (typeof data !== 'bigint') throw new TypeError('Value must be a BigInt.');
19452
19535
  const encoded = this.#setJson(name, data);
19453
19536
  this.emit('setBigInt', name, data);
19454
19537
  return this.#localStorage.setItem(name, JSON.stringify(encoded));
@@ -19471,7 +19554,8 @@ class TinyLocalStorage {
19471
19554
  * @param {symbol} data
19472
19555
  */
19473
19556
  setSymbol(name, data) {
19474
- if (typeof data !== 'symbol') throw new Error('Value must be a Symbol.');
19557
+ this.#isProtectedDbKey(name);
19558
+ if (typeof data !== 'symbol') throw new TypeError('Value must be a Symbol.');
19475
19559
  const encoded = this.#setJson(name, data);
19476
19560
  this.emit('setSymbol', name, data);
19477
19561
  return this.#localStorage.setItem(name, JSON.stringify(encoded));
@@ -19504,8 +19588,9 @@ class TinyLocalStorage {
19504
19588
  * @param {any} data - The data to store.
19505
19589
  */
19506
19590
  setItem(name, data) {
19591
+ this.#isProtectedDbKey(name);
19507
19592
  if (typeof name !== 'string' || !name.length)
19508
- throw new Error('Key must be a non-empty string.');
19593
+ throw new TypeError('Key must be a non-empty string.');
19509
19594
  this.emit('setItem', name, data);
19510
19595
  return this.#localStorage.setItem(name, data);
19511
19596
  }
@@ -19518,7 +19603,7 @@ class TinyLocalStorage {
19518
19603
  */
19519
19604
  getItem(name) {
19520
19605
  if (typeof name !== 'string' || !name.length)
19521
- throw new Error('Key must be a non-empty string.');
19606
+ throw new TypeError('Key must be a non-empty string.');
19522
19607
  return this.#localStorage.getItem(name);
19523
19608
  }
19524
19609
 
@@ -19529,9 +19614,10 @@ class TinyLocalStorage {
19529
19614
  * @param {string} data - The string to store.
19530
19615
  */
19531
19616
  setString(name, data) {
19617
+ this.#isProtectedDbKey(name);
19532
19618
  if (typeof name !== 'string' || !name.length)
19533
- throw new Error('Key must be a non-empty string.');
19534
- if (typeof data !== 'string') throw new Error('Value must be a string.');
19619
+ throw new TypeError('Key must be a non-empty string.');
19620
+ if (typeof data !== 'string') throw new TypeError('Value must be a string.');
19535
19621
 
19536
19622
  this.emit('setString', name, data);
19537
19623
  return this.#localStorage.setItem(
@@ -19551,7 +19637,7 @@ class TinyLocalStorage {
19551
19637
  */
19552
19638
  getString(name) {
19553
19639
  if (typeof name !== 'string' || !name.length)
19554
- throw new Error('Key must be a non-empty string.');
19640
+ throw new TypeError('Key must be a non-empty string.');
19555
19641
  let value = this.#localStorage.getItem(name);
19556
19642
  try {
19557
19643
  /** @type {{ value: string; __string__: boolean }} */
@@ -19573,9 +19659,10 @@ class TinyLocalStorage {
19573
19659
  * @param {number} data - The number to store.
19574
19660
  */
19575
19661
  setNumber(name, data) {
19662
+ this.#isProtectedDbKey(name);
19576
19663
  if (typeof name !== 'string' || !name.length)
19577
- throw new Error('Key must be a non-empty string.');
19578
- if (typeof data !== 'number') throw new Error('Value must be a number.');
19664
+ throw new TypeError('Key must be a non-empty string.');
19665
+ if (typeof data !== 'number') throw new TypeError('Value must be a number.');
19579
19666
  this.emit('setNumber', name, data);
19580
19667
  return this.#localStorage.setItem(name, String(data));
19581
19668
  }
@@ -19588,7 +19675,7 @@ class TinyLocalStorage {
19588
19675
  */
19589
19676
  getNumber(name) {
19590
19677
  if (typeof name !== 'string' || !name.length)
19591
- throw new Error('Key must be a non-empty string.');
19678
+ throw new TypeError('Key must be a non-empty string.');
19592
19679
 
19593
19680
  /** @type {number|string|null} */
19594
19681
  let number = this.#localStorage.getItem(name);
@@ -19607,9 +19694,10 @@ class TinyLocalStorage {
19607
19694
  * @param {boolean} data - The boolean value to store.
19608
19695
  */
19609
19696
  setBool(name, data) {
19697
+ this.#isProtectedDbKey(name);
19610
19698
  if (typeof name !== 'string' || !name.length)
19611
- throw new Error('Key must be a non-empty string.');
19612
- if (typeof data !== 'boolean') throw new Error('Value must be a boolean.');
19699
+ throw new TypeError('Key must be a non-empty string.');
19700
+ if (typeof data !== 'boolean') throw new TypeError('Value must be a boolean.');
19613
19701
  this.emit('setBool', name, data);
19614
19702
  return this.#localStorage.setItem(name, String(data));
19615
19703
  }
@@ -19622,7 +19710,7 @@ class TinyLocalStorage {
19622
19710
  */
19623
19711
  getBool(name) {
19624
19712
  if (typeof name !== 'string' || !name.length)
19625
- throw new Error('Key must be a non-empty string.');
19713
+ throw new TypeError('Key must be a non-empty string.');
19626
19714
 
19627
19715
  const value = this.#localStorage.getItem(name);
19628
19716
  if (typeof value === 'boolean') return value;
@@ -19641,7 +19729,7 @@ class TinyLocalStorage {
19641
19729
  */
19642
19730
  removeItem(name) {
19643
19731
  if (typeof name !== 'string' || !name.length)
19644
- throw new Error('Key must be a non-empty string.');
19732
+ throw new TypeError('Key must be a non-empty string.');
19645
19733
 
19646
19734
  this.emit('removeItem', name);
19647
19735
  return this.#localStorage.removeItem(name);