tiny-essentials 1.19.1 → 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.
@@ -445,6 +445,22 @@ const customEncoders = new Map();
445
445
  /** @type {Map<any, DecodeFn>} */
446
446
  const customDecoders = new Map();
447
447
 
448
+ /** @type {Set<any>} */
449
+ const customTypesFreezed = new Set([
450
+ 'string',
451
+ 'boolean',
452
+ 'number',
453
+ 'object',
454
+ 'array',
455
+ String,
456
+ Boolean,
457
+ Number,
458
+ Object,
459
+ Array,
460
+ BigInt,
461
+ Symbol,
462
+ ]);
463
+
448
464
  /**
449
465
  * A function that encodes a value into a serializable JSON-compatible format.
450
466
  *
@@ -717,25 +733,48 @@ class TinyLocalStorage {
717
733
  ///////////////////////////////////////////////////
718
734
 
719
735
  /**
720
- * Registers a new JSON-serializable type with its encoder and decoder.
736
+ * Checks whether a JSON-serializable type is already registered.
721
737
  *
722
- * @param {any} type - The type or primitive type name (e.g. `"bigint"`, `"symbol"`, etc).
723
- * @param {EncodeFn} encodeFn - The function that encodes the value.
724
- * @param {DecodeFn} decodeFn - An object with `check` and `decode` methods for restoring the value.
738
+ * @param {any} type - The type identifier, which can be a string (e.g., `"bigint"`, `"symbol"`)
739
+ * or a reference to a class/constructor function.
740
+ * @returns {boolean} True if the type has both an encoder and decoder registered.
725
741
  */
726
- static registerJsonType(type, encodeFn, decodeFn) {
742
+ static hasJsonType(type) {
743
+ return (customEncoders.has(type) && customDecoders.has(type)) || customTypesFreezed.has(type);
744
+ }
745
+
746
+ /**
747
+ * Registers a new JSON-serializable type with its encoder and decoder.
748
+ *
749
+ * @param {any} type - The type identifier, which can be a string (e.g., `"bigint"`, `"symbol"`)
750
+ * or a reference to a class/constructor function.
751
+ * @param {EncodeFn} encodeFn - A function that receives a value of this type and returns a JSON-safe representation.
752
+ * @param {DecodeFn} decodeFn - An object with `check(value)` to identify this type and `decode(value)` to restore it.
753
+ * @param {boolean} [freezeType=false] - If true, prevents this type from being unregistered later.
754
+ * @throws {Error} Throws an error if the type is frozen and cannot be registered.
755
+ */
756
+ static registerJsonType(type, encodeFn, decodeFn, freezeType = false) {
757
+ if (customTypesFreezed.has(type))
758
+ throw new Error(`Cannot register type "${type}" because it is frozen.`);
727
759
  customEncoders.set(type, encodeFn);
728
760
  customDecoders.set(type, decodeFn);
761
+ if (freezeType) customTypesFreezed.add(type);
729
762
  }
730
763
 
731
764
  /**
732
- * Removes a previously registered custom type from the encoding/decoding system.
765
+ * Unregisters a previously registered custom type from the encoding/decoding system.
733
766
  *
734
- * @param {string} type - The primitive name or constructor reference used in registration.
767
+ * @param {any} type - The primitive name or constructor reference used in the registration.
768
+ * @returns {boolean} Returns true if the type existed and was removed, or false if it wasn't found.
769
+ * @throws {Error} Throws an error if the type is frozen and cannot be unregistered.
735
770
  */
736
771
  static deleteJsonType(type) {
737
- customEncoders.delete(type);
738
- customDecoders.delete(type);
772
+ if (customTypesFreezed.has(type))
773
+ throw new Error(`Cannot remove type "${type}" because it is frozen.`);
774
+ let isDeleted = false;
775
+ if (customEncoders.delete(type)) isDeleted = true;
776
+ if (customDecoders.delete(type)) isDeleted = true;
777
+ return isDeleted;
739
778
  }
740
779
 
741
780
  //////////////////////////////////////////////////////
@@ -818,6 +857,12 @@ class TinyLocalStorage {
818
857
  /** @type {Storage} */
819
858
  #localStorage = window.localStorage;
820
859
 
860
+ /** @type {string|null} */
861
+ #dbKey = null;
862
+
863
+ /** @type {number} */
864
+ #version = 0;
865
+
821
866
  /** @type {(ev: StorageEvent) => any} */
822
867
  #storageEvent = (ev) => this.emit('storage', ev);
823
868
 
@@ -825,11 +870,84 @@ class TinyLocalStorage {
825
870
  * Initializes the TinyLocalStorage instance and sets up cross-tab sync.
826
871
  *
827
872
  * Adds listener for the native `storage` event to support tab synchronization.
873
+ * @param {string} [dbName] - Unique database name.
828
874
  */
829
- constructor() {
875
+ constructor(dbName) {
876
+ if (typeof dbName !== 'undefined' && typeof dbName !== 'string')
877
+ throw new TypeError('TinyLocalStorage: dbName must be a string if provided.');
878
+ if (typeof dbName === 'string') this.#dbKey = `LSDB::${dbName}`;
830
879
  window.addEventListener('storage', this.#storageEvent);
831
880
  }
832
881
 
882
+ /**
883
+ * Validates that a given key does not conflict with internal database keys.
884
+ *
885
+ * This method is used to prevent accidental overwriting of reserved `LSDB::` keys
886
+ * in `localStorage`, which are used internally by TinyLocalStorage for versioning
887
+ * and data management.
888
+ *
889
+ * @param {string} name - The key to validate before writing to localStorage.
890
+ * @throws {Error} If the key starts with `LSDB::`.
891
+ */
892
+ #isProtectedDbKey(name) {
893
+ if (typeof name === 'string' && name.startsWith('LSDB::'))
894
+ throw new Error(`TinyLocalStorage: Key "${name}" may conflict with reserved dbKeys.`);
895
+ }
896
+
897
+ /**
898
+ * Updates the version of the storage and triggers migration if needed.
899
+ *
900
+ * @param {number} version - Desired version of the database.
901
+ * @param {(oldVersion: number, newVersion: number) => void} onUpgrade - Callback to perform migration logic.
902
+ * @throws {Error} If the database key has not been initialized.
903
+ * @throws {TypeError} If `version` is not a valid positive number.
904
+ * @throws {TypeError} If `onUpgrade` is not a function.
905
+ */
906
+ updateStorageVersion(version, onUpgrade) {
907
+ if (typeof this.#dbKey !== 'string')
908
+ throw new Error(
909
+ 'TinyLocalStorage: Database key is not initialized. Set a valid dbName in the constructor.',
910
+ );
911
+ if (typeof version !== 'number' || Number.isNaN(version) || version < 1)
912
+ throw new TypeError('TinyLocalStorage: version must be a positive number.');
913
+ if (typeof onUpgrade !== 'function')
914
+ throw new TypeError('TinyLocalStorage: onUpgrade must be a function.');
915
+
916
+ // @ts-ignore
917
+ const savedVersion = parseInt(localStorage.getItem(this.#dbKey), 10) || 0;
918
+ if (typeof savedVersion !== 'number' || Number.isNaN(savedVersion) || savedVersion < 0)
919
+ throw new TypeError('TinyLocalStorage: saved version in localStorage is not a valid number.');
920
+
921
+ if (version < savedVersion)
922
+ throw new Error(
923
+ `TinyLocalStorage: Cannot downgrade database version from ${savedVersion} to ${version}.`,
924
+ );
925
+
926
+ if (version > savedVersion) {
927
+ onUpgrade(savedVersion, version);
928
+ localStorage.setItem(this.#dbKey, String(version));
929
+ this.#version = version;
930
+ }
931
+ }
932
+
933
+ /**
934
+ * Gets the current database key used in localStorage.
935
+ *
936
+ * @returns {string|null} The database key, or null if not set.
937
+ */
938
+ getDbKey() {
939
+ return this.#dbKey;
940
+ }
941
+
942
+ /**
943
+ * Gets the current version of the database.
944
+ *
945
+ * @returns {number} The current version number.
946
+ */
947
+ getVersion() {
948
+ return this.#version;
949
+ }
950
+
833
951
  /**
834
952
  * Defines a custom storage interface (e.g. `sessionStorage`).
835
953
  *
@@ -837,7 +955,7 @@ class TinyLocalStorage {
837
955
  */
838
956
  setLocalStorage(localstorage) {
839
957
  if (!(localstorage instanceof Storage))
840
- throw new Error('Argument must be a valid instance of Storage.');
958
+ throw new TypeError('Argument must be a valid instance of Storage.');
841
959
  this.#localStorage = localstorage;
842
960
  }
843
961
 
@@ -859,7 +977,7 @@ class TinyLocalStorage {
859
977
  */
860
978
  #setJson(name, data) {
861
979
  if (typeof name !== 'string' || !name.length)
862
- throw new Error('Key must be a non-empty string.');
980
+ throw new TypeError('Key must be a non-empty string.');
863
981
  return TinyLocalStorage.encodeSpecialJson(data);
864
982
  }
865
983
 
@@ -872,13 +990,14 @@ class TinyLocalStorage {
872
990
  * @param {LocalStorageJsonValue} data - The data to be serialized and stored.
873
991
  */
874
992
  setJson(name, data) {
993
+ this.#isProtectedDbKey(name);
875
994
  if (
876
995
  !isJsonObject(data) &&
877
996
  !Array.isArray(data) &&
878
997
  !(data instanceof Map) &&
879
998
  !(data instanceof Set)
880
999
  ) {
881
- throw new Error('The storage value is not a valid JSON-compatible structure.');
1000
+ throw new TypeError('The storage value is not a valid JSON-compatible structure.');
882
1001
  }
883
1002
  const encoded = this.#setJson(name, data);
884
1003
  this.emit('setJson', name, data);
@@ -896,7 +1015,7 @@ class TinyLocalStorage {
896
1015
  */
897
1016
  #getJson(name, defaultData) {
898
1017
  if (typeof name !== 'string' || !name.length)
899
- throw new Error('Key must be a non-empty string.');
1018
+ throw new TypeError('Key must be a non-empty string.');
900
1019
 
901
1020
  const raw = this.#localStorage.getItem(name);
902
1021
  const fallbackTypes = {
@@ -950,7 +1069,8 @@ class TinyLocalStorage {
950
1069
  * @param {Date} data
951
1070
  */
952
1071
  setDate(name, data) {
953
- if (!(data instanceof Date)) throw new Error('Value must be a Date.');
1072
+ this.#isProtectedDbKey(name);
1073
+ if (!(data instanceof Date)) throw new TypeError('Value must be a Date.');
954
1074
  const encoded = this.#setJson(name, data);
955
1075
  this.emit('setDate', name, data);
956
1076
  return this.#localStorage.setItem(name, JSON.stringify(encoded));
@@ -972,7 +1092,8 @@ class TinyLocalStorage {
972
1092
  * @param {RegExp} data
973
1093
  */
974
1094
  setRegExp(name, data) {
975
- if (!(data instanceof RegExp)) throw new Error('Value must be a RegExp.');
1095
+ this.#isProtectedDbKey(name);
1096
+ if (!(data instanceof RegExp)) throw new TypeError('Value must be a RegExp.');
976
1097
  const encoded = this.#setJson(name, data);
977
1098
  this.emit('setRegExp', name, data);
978
1099
  return this.#localStorage.setItem(name, JSON.stringify(encoded));
@@ -994,7 +1115,8 @@ class TinyLocalStorage {
994
1115
  * @param {bigint} data
995
1116
  */
996
1117
  setBigInt(name, data) {
997
- if (typeof data !== 'bigint') throw new Error('Value must be a BigInt.');
1118
+ this.#isProtectedDbKey(name);
1119
+ if (typeof data !== 'bigint') throw new TypeError('Value must be a BigInt.');
998
1120
  const encoded = this.#setJson(name, data);
999
1121
  this.emit('setBigInt', name, data);
1000
1122
  return this.#localStorage.setItem(name, JSON.stringify(encoded));
@@ -1017,7 +1139,8 @@ class TinyLocalStorage {
1017
1139
  * @param {symbol} data
1018
1140
  */
1019
1141
  setSymbol(name, data) {
1020
- if (typeof data !== 'symbol') throw new Error('Value must be a Symbol.');
1142
+ this.#isProtectedDbKey(name);
1143
+ if (typeof data !== 'symbol') throw new TypeError('Value must be a Symbol.');
1021
1144
  const encoded = this.#setJson(name, data);
1022
1145
  this.emit('setSymbol', name, data);
1023
1146
  return this.#localStorage.setItem(name, JSON.stringify(encoded));
@@ -1050,8 +1173,9 @@ class TinyLocalStorage {
1050
1173
  * @param {any} data - The data to store.
1051
1174
  */
1052
1175
  setItem(name, data) {
1176
+ this.#isProtectedDbKey(name);
1053
1177
  if (typeof name !== 'string' || !name.length)
1054
- throw new Error('Key must be a non-empty string.');
1178
+ throw new TypeError('Key must be a non-empty string.');
1055
1179
  this.emit('setItem', name, data);
1056
1180
  return this.#localStorage.setItem(name, data);
1057
1181
  }
@@ -1064,7 +1188,7 @@ class TinyLocalStorage {
1064
1188
  */
1065
1189
  getItem(name) {
1066
1190
  if (typeof name !== 'string' || !name.length)
1067
- throw new Error('Key must be a non-empty string.');
1191
+ throw new TypeError('Key must be a non-empty string.');
1068
1192
  return this.#localStorage.getItem(name);
1069
1193
  }
1070
1194
 
@@ -1075,12 +1199,19 @@ class TinyLocalStorage {
1075
1199
  * @param {string} data - The string to store.
1076
1200
  */
1077
1201
  setString(name, data) {
1202
+ this.#isProtectedDbKey(name);
1078
1203
  if (typeof name !== 'string' || !name.length)
1079
- throw new Error('Key must be a non-empty string.');
1080
- if (typeof data !== 'string') throw new Error('Value must be a string.');
1204
+ throw new TypeError('Key must be a non-empty string.');
1205
+ if (typeof data !== 'string') throw new TypeError('Value must be a string.');
1081
1206
 
1082
1207
  this.emit('setString', name, data);
1083
- return this.#localStorage.setItem(name, data);
1208
+ return this.#localStorage.setItem(
1209
+ name,
1210
+ JSON.stringify({
1211
+ __string__: true,
1212
+ value: data,
1213
+ }),
1214
+ );
1084
1215
  }
1085
1216
 
1086
1217
  /**
@@ -1091,8 +1222,17 @@ class TinyLocalStorage {
1091
1222
  */
1092
1223
  getString(name) {
1093
1224
  if (typeof name !== 'string' || !name.length)
1094
- throw new Error('Key must be a non-empty string.');
1225
+ throw new TypeError('Key must be a non-empty string.');
1095
1226
  let value = this.#localStorage.getItem(name);
1227
+ try {
1228
+ /** @type {{ value: string; __string__: boolean }} */
1229
+ // @ts-ignore
1230
+ value = JSON.parse(value);
1231
+ if (!isJsonObject(value) || !value.__string__ || typeof value.value !== 'string') return null;
1232
+ value = value.value;
1233
+ } catch {
1234
+ value = null;
1235
+ }
1096
1236
  if (typeof value === 'string') return value;
1097
1237
  return null;
1098
1238
  }
@@ -1104,9 +1244,10 @@ class TinyLocalStorage {
1104
1244
  * @param {number} data - The number to store.
1105
1245
  */
1106
1246
  setNumber(name, data) {
1247
+ this.#isProtectedDbKey(name);
1107
1248
  if (typeof name !== 'string' || !name.length)
1108
- throw new Error('Key must be a non-empty string.');
1109
- if (typeof data !== 'number') throw new Error('Value must be a number.');
1249
+ throw new TypeError('Key must be a non-empty string.');
1250
+ if (typeof data !== 'number') throw new TypeError('Value must be a number.');
1110
1251
  this.emit('setNumber', name, data);
1111
1252
  return this.#localStorage.setItem(name, String(data));
1112
1253
  }
@@ -1119,7 +1260,7 @@ class TinyLocalStorage {
1119
1260
  */
1120
1261
  getNumber(name) {
1121
1262
  if (typeof name !== 'string' || !name.length)
1122
- throw new Error('Key must be a non-empty string.');
1263
+ throw new TypeError('Key must be a non-empty string.');
1123
1264
 
1124
1265
  /** @type {number|string|null} */
1125
1266
  let number = this.#localStorage.getItem(name);
@@ -1138,9 +1279,10 @@ class TinyLocalStorage {
1138
1279
  * @param {boolean} data - The boolean value to store.
1139
1280
  */
1140
1281
  setBool(name, data) {
1282
+ this.#isProtectedDbKey(name);
1141
1283
  if (typeof name !== 'string' || !name.length)
1142
- throw new Error('Key must be a non-empty string.');
1143
- if (typeof data !== 'boolean') throw new Error('Value must be a boolean.');
1284
+ throw new TypeError('Key must be a non-empty string.');
1285
+ if (typeof data !== 'boolean') throw new TypeError('Value must be a boolean.');
1144
1286
  this.emit('setBool', name, data);
1145
1287
  return this.#localStorage.setItem(name, String(data));
1146
1288
  }
@@ -1153,7 +1295,7 @@ class TinyLocalStorage {
1153
1295
  */
1154
1296
  getBool(name) {
1155
1297
  if (typeof name !== 'string' || !name.length)
1156
- throw new Error('Key must be a non-empty string.');
1298
+ throw new TypeError('Key must be a non-empty string.');
1157
1299
 
1158
1300
  const value = this.#localStorage.getItem(name);
1159
1301
  if (typeof value === 'boolean') return value;
@@ -1172,7 +1314,7 @@ class TinyLocalStorage {
1172
1314
  */
1173
1315
  removeItem(name) {
1174
1316
  if (typeof name !== 'string' || !name.length)
1175
- throw new Error('Key must be a non-empty string.');
1317
+ throw new TypeError('Key must be a non-empty string.');
1176
1318
 
1177
1319
  this.emit('removeItem', name);
1178
1320
  return this.#localStorage.removeItem(name);
@@ -1209,6 +1351,7 @@ TinyLocalStorage.registerJsonType(
1209
1351
  decode: (value, decodeSpecialJson) =>
1210
1352
  new Map(value.data.map(([k, v]) => [k, decodeSpecialJson(v)])),
1211
1353
  },
1354
+ true,
1212
1355
  );
1213
1356
 
1214
1357
  // Set
@@ -1222,6 +1365,7 @@ TinyLocalStorage.registerJsonType(
1222
1365
  check: (value) => value.__set__,
1223
1366
  decode: (value, decodeSpecialJson) => new Set(value.data.map(decodeSpecialJson)),
1224
1367
  },
1368
+ true,
1225
1369
  );
1226
1370
 
1227
1371
  // Date
@@ -1235,6 +1379,7 @@ TinyLocalStorage.registerJsonType(
1235
1379
  check: (value) => value.__date__,
1236
1380
  decode: (value) => new Date(value.value),
1237
1381
  },
1382
+ true,
1238
1383
  );
1239
1384
 
1240
1385
  // Regex
@@ -1249,6 +1394,7 @@ TinyLocalStorage.registerJsonType(
1249
1394
  check: (value) => value.__regexp__,
1250
1395
  decode: (value) => new RegExp(value.source, value.flags),
1251
1396
  },
1397
+ true,
1252
1398
  );
1253
1399
 
1254
1400
  // Big Int
@@ -1262,6 +1408,7 @@ TinyLocalStorage.registerJsonType(
1262
1408
  check: (value) => value.__bigint__,
1263
1409
  decode: (value) => BigInt(value.value),
1264
1410
  },
1411
+ true,
1265
1412
  );
1266
1413
 
1267
1414
  // Symbol
@@ -1278,6 +1425,7 @@ TinyLocalStorage.registerJsonType(
1278
1425
  return key != null ? Symbol.for(key) : Symbol();
1279
1426
  },
1280
1427
  },
1428
+ true,
1281
1429
  );
1282
1430
 
1283
1431
  /* harmony default export */ const libs_TinyLocalStorage = (TinyLocalStorage);
@@ -1 +1 @@
1
- (()=>{"use strict";var e={d:(t,r)=>{for(var n in r)e.o(r,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:r[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};function r(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)&&"[object Object]"===Object.prototype.toString.call(e)}e.d(t,{TinyLocalStorage:()=>a});const n=class{#e=new Map;#t=10;#r=!1;setThrowOnMaxListeners(e){if("boolean"!=typeof e)throw new TypeError("setThrowOnMaxListeners(value): value must be a boolean");this.#r=e}getThrowOnMaxListeners(){return this.#r}#n(e,t,{once:r=!1}={}){let n=this.#e.get(e);Array.isArray(n)||(n=[],this.#e.set(e,n)),n.unshift({handler:t,config:{once:r}});const s=this.#t;if(s>0&&n.length>s){const t=`Possible memory leak detected. ${n.length} "${e}" listeners added. Use setMaxListeners() to increase limit.`;if(this.#r)throw new Error(t);console.warn(t)}}prependListener(e,t){if("string"!=typeof e)throw new TypeError("prepend(event, handler): event name must be a string");if("function"!=typeof t)throw new TypeError("prepend(event, handler): handler must be a function");this.#n(e,t)}prependListenerOnce(e,t){if("string"!=typeof e)throw new TypeError("prependOnceListener(event, handler): event name must be a string");if("function"!=typeof t)throw new TypeError("prependOnceListener(event, handler): handler must be a function");const r=(...n)=>{this.off(e,r),t(...n)};return this.#n(e,r,{once:!0}),r}#s(e,t,{once:r=!1}={}){let n=this.#e.get(e);Array.isArray(n)||(n=[],this.#e.set(e,n)),n.push({handler:t,config:{once:r}});const s=this.#t;if(s>0&&n.length>s){const t=`Possible memory leak detected. ${n.length} "${e}" listeners added. Use setMaxListeners() to increase limit.`;if(this.#r)throw new Error(t);console.warn(t)}}on(e,t){if("string"!=typeof e)throw new TypeError("on(event, handler): event name must be a string");if("function"!=typeof t)throw new TypeError("on(event, handler): handler must be a function");return this.#s(e,t)}once(e,t){if("string"!=typeof e)throw new TypeError("The event name must be a string.");if("function"!=typeof t)throw new TypeError("once(event, handler): handler must be a function");const r=n=>{this.off(e,r),"function"==typeof t&&t(n)};return this.#s(e,r,{once:!0}),r}appendListener(e,t){return this.on(e,t)}appendListenerOnce(e,t){return this.once(e,t)}off(e,t){if("string"!=typeof e)throw new TypeError("off(event, handler): event name must be a string");if("function"!=typeof t)throw new TypeError("off(event, handler): handler must be a function");const r=this.#e.get(e);if(!Array.isArray(r))return;const n=r.findIndex(e=>e.handler===t);-1!==n&&r.splice(n,1),0===r.length&&this.#e.delete(e)}offAll(e){if("string"!=typeof e)throw new TypeError("The event name must be a string.");this.#e.delete(e)}offAllTypes(){this.#e.clear()}listenerCount(e){if("string"!=typeof e)throw new TypeError("listenerCount(event): event name must be a string");const t=this.#e.get(e);return Array.isArray(t)?t.length:0}listeners(e){if("string"!=typeof e)throw new TypeError("listeners(event): event name must be a string");const t=this.#e.get(e);return Array.isArray(t)?[...t].filter(e=>!e.config.once).map(e=>e.handler):[]}onceListeners(e){if("string"!=typeof e)throw new TypeError("onceListeners(event): event name must be a string");const t=this.#e.get(e);return Array.isArray(t)?[...t].filter(e=>e.config.once).map(e=>e.handler):[]}allListeners(e){if("string"!=typeof e)throw new TypeError("allListeners(event): event name must be a string");const t=this.#e.get(e);return Array.isArray(t)?[...t].map(e=>e.handler):[]}eventNames(){return[...this.#e.keys()]}emit(e,...t){if("string"!=typeof e)throw new TypeError("emit(event, data): event name must be a string");const r=this.#e.get(e);return!(!Array.isArray(r)||0===r.length||(r.forEach(e=>e.handler(...t)),0))}setMaxListeners(e){if(!Number.isInteger(e)||e<0)throw new TypeError("setMaxListeners(n): n must be a non-negative integer");this.#t=e}getMaxListeners(){return this.#t}},s=new Map,o=new Map;class i{#o=new n;setThrowOnMaxListeners(e){return this.#o.setThrowOnMaxListeners(e)}getThrowOnMaxListeners(){return this.#o.getThrowOnMaxListeners()}prependListener(e,t){return this.#o.prependListener(e,t)}prependListenerOnce(e,t){return this.#o.prependListenerOnce(e,t)}appendListener(e,t){return this.#o.appendListener(e,t)}appendListenerOnce(e,t){return this.#o.appendListenerOnce(e,t)}on(e,t){return this.#o.on(e,t)}once(e,t){return this.#o.once(e,t)}off(e,t){return this.#o.off(e,t)}offAll(e){return this.#o.offAll(e)}offAllTypes(){return this.#o.offAllTypes()}listenerCount(e){return this.#o.listenerCount(e)}listeners(e){return this.#o.listeners(e)}onceListeners(e){return this.#o.onceListeners(e)}allListeners(e){return this.#o.allListeners(e)}eventNames(){return this.#o.eventNames()}emit(e,...t){return this.#o.emit(e,...t)}setMaxListeners(e){return this.#o.setMaxListeners(e)}getMaxListeners(){return this.#o.getMaxListeners()}static registerJsonType(e,t,r){s.set(e,t),o.set(e,r)}static deleteJsonType(e){s.delete(e),o.delete(e)}static encodeSpecialJson(e){if(void 0===e)return{__undefined__:!0};if(null===e)return{__null__:!0};for(const[t,r]of s.entries())if("string"!=typeof t&&e instanceof t||typeof e===t)return r(e,i.encodeSpecialJson);if(Array.isArray(e))return e.map(i.encodeSpecialJson);if(r(e)){const t={};for(const r in e)t[r]=i.encodeSpecialJson(e[r]);return t}return e}static decodeSpecialJson(e){const t=r(e);if(t){if(e.__undefined__)return;if(e.__null__)return null}if(Array.isArray(e))return e.map(i.decodeSpecialJson);if(t){for(const[t,r]of o.entries())if(r.check&&r.check(e))return r.decode(e,i.decodeSpecialJson);const t={};for(const r in e)t[r]=i.decodeSpecialJson(e[r]);return t}return e}#i=window.localStorage;#a=e=>this.emit("storage",e);constructor(){window.addEventListener("storage",this.#a)}setLocalStorage(e){if(!(e instanceof Storage))throw new Error("Argument must be a valid instance of Storage.");this.#i=e}localStorageExists(){return this.#i instanceof Storage}#l(e,t){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");return i.encodeSpecialJson(t)}setJson(e,t){if(!(r(t)||Array.isArray(t)||t instanceof Map||t instanceof Set))throw new Error("The storage value is not a valid JSON-compatible structure.");const n=this.#l(e,t);return this.emit("setJson",e,t),this.#i.setItem(e,JSON.stringify(n))}#c(e,t){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");const r=this.#i.getItem(e),n={obj:()=>({}),array:()=>[],map:()=>new Map,set:()=>new Set},s="function"==typeof n[t]?n[t]():null;let o;try{o=JSON.parse(r)}catch{return s}return{decoded:i.decodeSpecialJson(o),fallback:s}}getJson(e,t){const{decoded:n,fallback:s}=this.#c(e,t);return n instanceof Map||n instanceof Set||Array.isArray(n)||r(n)?n:s}setDate(e,t){if(!(t instanceof Date))throw new Error("Value must be a Date.");const r=this.#l(e,t);return this.emit("setDate",e,t),this.#i.setItem(e,JSON.stringify(r))}getDate(e){const t=this.#c(e).decoded;return t instanceof Date?t:null}setRegExp(e,t){if(!(t instanceof RegExp))throw new Error("Value must be a RegExp.");const r=this.#l(e,t);return this.emit("setRegExp",e,t),this.#i.setItem(e,JSON.stringify(r))}getRegExp(e){const t=this.#c(e).decoded;return t instanceof RegExp?t:null}setBigInt(e,t){if("bigint"!=typeof t)throw new Error("Value must be a BigInt.");const r=this.#l(e,t);return this.emit("setBigInt",e,t),this.#i.setItem(e,JSON.stringify(r))}getBigInt(e){const t=this.#c(e).decoded;return"bigint"==typeof t?t:null}setSymbol(e,t){if("symbol"!=typeof t)throw new Error("Value must be a Symbol.");const r=this.#l(e,t);return this.emit("setSymbol",e,t),this.#i.setItem(e,JSON.stringify(r))}getSymbol(e){const t=this.#c(e).decoded;return"symbol"==typeof t?t:null}getValue(e){return this.#c(e).decoded??null}setItem(e,t){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");return this.emit("setItem",e,t),this.#i.setItem(e,t)}getItem(e){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");return this.#i.getItem(e)}setString(e,t){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");if("string"!=typeof t)throw new Error("Value must be a string.");return this.emit("setString",e,t),this.#i.setItem(e,t)}getString(e){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");let t=this.#i.getItem(e);return"string"==typeof t?t:null}setNumber(e,t){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");if("number"!=typeof t)throw new Error("Value must be a number.");return this.emit("setNumber",e,t),this.#i.setItem(e,String(t))}getNumber(e){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");let t=this.#i.getItem(e);return"number"==typeof t||"string"==typeof t&&t.length>0&&(t=parseFloat(t),!Number.isNaN(t))?t:null}setBool(e,t){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");if("boolean"!=typeof t)throw new Error("Value must be a boolean.");return this.emit("setBool",e,t),this.#i.setItem(e,String(t))}getBool(e){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");const t=this.#i.getItem(e);if("boolean"==typeof t)return t;if("string"==typeof t){if("true"===t)return!0;if("false"===t)return!1}return null}removeItem(e){if("string"!=typeof e||!e.length)throw new Error("Key must be a non-empty string.");return this.emit("removeItem",e),this.#i.removeItem(e)}clearLocalStorage(){return this.#i.clear()}destroy(){window.removeEventListener("storage",this.#a),this.#o.offAllTypes()}}i.registerJsonType(Map,(e,t)=>({__map__:!0,data:Array.from(e.entries()).map(([e,r])=>[e,t(r)])}),{check:e=>e.__map__,decode:(e,t)=>new Map(e.data.map(([e,r])=>[e,t(r)]))}),i.registerJsonType(Set,(e,t)=>({__set__:!0,data:Array.from(e).map(t)}),{check:e=>e.__set__,decode:(e,t)=>new Set(e.data.map(t))}),i.registerJsonType(Date,e=>({__date__:!0,value:e.toISOString()}),{check:e=>e.__date__,decode:e=>new Date(e.value)}),i.registerJsonType(RegExp,e=>({__regexp__:!0,source:e.source,flags:e.flags}),{check:e=>e.__regexp__,decode:e=>new RegExp(e.source,e.flags)}),i.registerJsonType("bigint",e=>({__bigint__:!0,value:e.toString()}),{check:e=>e.__bigint__,decode:e=>BigInt(e.value)}),i.registerJsonType("symbol",e=>({__symbol__:!0,key:Symbol.keyFor(e)??e.description??null}),{check:e=>e.__symbol__,decode:e=>{const t=e.key;return null!=t?Symbol.for(t):Symbol()}});const a=i;window.TinyLocalStorage=t.TinyLocalStorage})();
1
+ (()=>{"use strict";var e={d:(t,r)=>{for(var n in r)e.o(r,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:r[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};function r(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)&&"[object Object]"===Object.prototype.toString.call(e)}e.d(t,{TinyLocalStorage:()=>l});const n=class{#e=new Map;#t=10;#r=!1;setThrowOnMaxListeners(e){if("boolean"!=typeof e)throw new TypeError("setThrowOnMaxListeners(value): value must be a boolean");this.#r=e}getThrowOnMaxListeners(){return this.#r}#n(e,t,{once:r=!1}={}){let n=this.#e.get(e);Array.isArray(n)||(n=[],this.#e.set(e,n)),n.unshift({handler:t,config:{once:r}});const s=this.#t;if(s>0&&n.length>s){const t=`Possible memory leak detected. ${n.length} "${e}" listeners added. Use setMaxListeners() to increase limit.`;if(this.#r)throw new Error(t);console.warn(t)}}prependListener(e,t){if("string"!=typeof e)throw new TypeError("prepend(event, handler): event name must be a string");if("function"!=typeof t)throw new TypeError("prepend(event, handler): handler must be a function");this.#n(e,t)}prependListenerOnce(e,t){if("string"!=typeof e)throw new TypeError("prependOnceListener(event, handler): event name must be a string");if("function"!=typeof t)throw new TypeError("prependOnceListener(event, handler): handler must be a function");const r=(...n)=>{this.off(e,r),t(...n)};return this.#n(e,r,{once:!0}),r}#s(e,t,{once:r=!1}={}){let n=this.#e.get(e);Array.isArray(n)||(n=[],this.#e.set(e,n)),n.push({handler:t,config:{once:r}});const s=this.#t;if(s>0&&n.length>s){const t=`Possible memory leak detected. ${n.length} "${e}" listeners added. Use setMaxListeners() to increase limit.`;if(this.#r)throw new Error(t);console.warn(t)}}on(e,t){if("string"!=typeof e)throw new TypeError("on(event, handler): event name must be a string");if("function"!=typeof t)throw new TypeError("on(event, handler): handler must be a function");return this.#s(e,t)}once(e,t){if("string"!=typeof e)throw new TypeError("The event name must be a string.");if("function"!=typeof t)throw new TypeError("once(event, handler): handler must be a function");const r=n=>{this.off(e,r),"function"==typeof t&&t(n)};return this.#s(e,r,{once:!0}),r}appendListener(e,t){return this.on(e,t)}appendListenerOnce(e,t){return this.once(e,t)}off(e,t){if("string"!=typeof e)throw new TypeError("off(event, handler): event name must be a string");if("function"!=typeof t)throw new TypeError("off(event, handler): handler must be a function");const r=this.#e.get(e);if(!Array.isArray(r))return;const n=r.findIndex(e=>e.handler===t);-1!==n&&r.splice(n,1),0===r.length&&this.#e.delete(e)}offAll(e){if("string"!=typeof e)throw new TypeError("The event name must be a string.");this.#e.delete(e)}offAllTypes(){this.#e.clear()}listenerCount(e){if("string"!=typeof e)throw new TypeError("listenerCount(event): event name must be a string");const t=this.#e.get(e);return Array.isArray(t)?t.length:0}listeners(e){if("string"!=typeof e)throw new TypeError("listeners(event): event name must be a string");const t=this.#e.get(e);return Array.isArray(t)?[...t].filter(e=>!e.config.once).map(e=>e.handler):[]}onceListeners(e){if("string"!=typeof e)throw new TypeError("onceListeners(event): event name must be a string");const t=this.#e.get(e);return Array.isArray(t)?[...t].filter(e=>e.config.once).map(e=>e.handler):[]}allListeners(e){if("string"!=typeof e)throw new TypeError("allListeners(event): event name must be a string");const t=this.#e.get(e);return Array.isArray(t)?[...t].map(e=>e.handler):[]}eventNames(){return[...this.#e.keys()]}emit(e,...t){if("string"!=typeof e)throw new TypeError("emit(event, data): event name must be a string");const r=this.#e.get(e);return!(!Array.isArray(r)||0===r.length||(r.forEach(e=>e.handler(...t)),0))}setMaxListeners(e){if(!Number.isInteger(e)||e<0)throw new TypeError("setMaxListeners(n): n must be a non-negative integer");this.#t=e}getMaxListeners(){return this.#t}},s=new Map,o=new Map,i=new Set(["string","boolean","number","object","array",String,Boolean,Number,Object,Array,BigInt,Symbol]);class a{#o=new n;setThrowOnMaxListeners(e){return this.#o.setThrowOnMaxListeners(e)}getThrowOnMaxListeners(){return this.#o.getThrowOnMaxListeners()}prependListener(e,t){return this.#o.prependListener(e,t)}prependListenerOnce(e,t){return this.#o.prependListenerOnce(e,t)}appendListener(e,t){return this.#o.appendListener(e,t)}appendListenerOnce(e,t){return this.#o.appendListenerOnce(e,t)}on(e,t){return this.#o.on(e,t)}once(e,t){return this.#o.once(e,t)}off(e,t){return this.#o.off(e,t)}offAll(e){return this.#o.offAll(e)}offAllTypes(){return this.#o.offAllTypes()}listenerCount(e){return this.#o.listenerCount(e)}listeners(e){return this.#o.listeners(e)}onceListeners(e){return this.#o.onceListeners(e)}allListeners(e){return this.#o.allListeners(e)}eventNames(){return this.#o.eventNames()}emit(e,...t){return this.#o.emit(e,...t)}setMaxListeners(e){return this.#o.setMaxListeners(e)}getMaxListeners(){return this.#o.getMaxListeners()}static hasJsonType(e){return s.has(e)&&o.has(e)||i.has(e)}static registerJsonType(e,t,r,n=!1){if(i.has(e))throw new Error(`Cannot register type "${e}" because it is frozen.`);s.set(e,t),o.set(e,r),n&&i.add(e)}static deleteJsonType(e){if(i.has(e))throw new Error(`Cannot remove type "${e}" because it is frozen.`);let t=!1;return s.delete(e)&&(t=!0),o.delete(e)&&(t=!0),t}static encodeSpecialJson(e){if(void 0===e)return{__undefined__:!0};if(null===e)return{__null__:!0};for(const[t,r]of s.entries())if("string"!=typeof t&&e instanceof t||typeof e===t)return r(e,a.encodeSpecialJson);if(Array.isArray(e))return e.map(a.encodeSpecialJson);if(r(e)){const t={};for(const r in e)t[r]=a.encodeSpecialJson(e[r]);return t}return e}static decodeSpecialJson(e){const t=r(e);if(t){if(e.__undefined__)return;if(e.__null__)return null}if(Array.isArray(e))return e.map(a.decodeSpecialJson);if(t){for(const[t,r]of o.entries())if(r.check&&r.check(e))return r.decode(e,a.decodeSpecialJson);const t={};for(const r in e)t[r]=a.decodeSpecialJson(e[r]);return t}return e}#i=window.localStorage;#a=null;#l=0;#c=e=>this.emit("storage",e);constructor(e){if(void 0!==e&&"string"!=typeof e)throw new TypeError("TinyLocalStorage: dbName must be a string if provided.");"string"==typeof e&&(this.#a=`LSDB::${e}`),window.addEventListener("storage",this.#c)}#h(e){if("string"==typeof e&&e.startsWith("LSDB::"))throw new Error(`TinyLocalStorage: Key "${e}" may conflict with reserved dbKeys.`)}updateStorageVersion(e,t){if("string"!=typeof this.#a)throw new Error("TinyLocalStorage: Database key is not initialized. Set a valid dbName in the constructor.");if("number"!=typeof e||Number.isNaN(e)||e<1)throw new TypeError("TinyLocalStorage: version must be a positive number.");if("function"!=typeof t)throw new TypeError("TinyLocalStorage: onUpgrade must be a function.");const r=parseInt(localStorage.getItem(this.#a),10)||0;if("number"!=typeof r||Number.isNaN(r)||r<0)throw new TypeError("TinyLocalStorage: saved version in localStorage is not a valid number.");if(e<r)throw new Error(`TinyLocalStorage: Cannot downgrade database version from ${r} to ${e}.`);e>r&&(t(r,e),localStorage.setItem(this.#a,String(e)),this.#l=e)}getDbKey(){return this.#a}getVersion(){return this.#l}setLocalStorage(e){if(!(e instanceof Storage))throw new TypeError("Argument must be a valid instance of Storage.");this.#i=e}localStorageExists(){return this.#i instanceof Storage}#y(e,t){if("string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");return a.encodeSpecialJson(t)}setJson(e,t){if(this.#h(e),!(r(t)||Array.isArray(t)||t instanceof Map||t instanceof Set))throw new TypeError("The storage value is not a valid JSON-compatible structure.");const n=this.#y(e,t);return this.emit("setJson",e,t),this.#i.setItem(e,JSON.stringify(n))}#g(e,t){if("string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");const r=this.#i.getItem(e),n={obj:()=>({}),array:()=>[],map:()=>new Map,set:()=>new Set},s="function"==typeof n[t]?n[t]():null;let o;try{o=JSON.parse(r)}catch{return s}return{decoded:a.decodeSpecialJson(o),fallback:s}}getJson(e,t){const{decoded:n,fallback:s}=this.#g(e,t);return n instanceof Map||n instanceof Set||Array.isArray(n)||r(n)?n:s}setDate(e,t){if(this.#h(e),!(t instanceof Date))throw new TypeError("Value must be a Date.");const r=this.#y(e,t);return this.emit("setDate",e,t),this.#i.setItem(e,JSON.stringify(r))}getDate(e){const t=this.#g(e).decoded;return t instanceof Date?t:null}setRegExp(e,t){if(this.#h(e),!(t instanceof RegExp))throw new TypeError("Value must be a RegExp.");const r=this.#y(e,t);return this.emit("setRegExp",e,t),this.#i.setItem(e,JSON.stringify(r))}getRegExp(e){const t=this.#g(e).decoded;return t instanceof RegExp?t:null}setBigInt(e,t){if(this.#h(e),"bigint"!=typeof t)throw new TypeError("Value must be a BigInt.");const r=this.#y(e,t);return this.emit("setBigInt",e,t),this.#i.setItem(e,JSON.stringify(r))}getBigInt(e){const t=this.#g(e).decoded;return"bigint"==typeof t?t:null}setSymbol(e,t){if(this.#h(e),"symbol"!=typeof t)throw new TypeError("Value must be a Symbol.");const r=this.#y(e,t);return this.emit("setSymbol",e,t),this.#i.setItem(e,JSON.stringify(r))}getSymbol(e){const t=this.#g(e).decoded;return"symbol"==typeof t?t:null}getValue(e){return this.#g(e).decoded??null}setItem(e,t){if(this.#h(e),"string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");return this.emit("setItem",e,t),this.#i.setItem(e,t)}getItem(e){if("string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");return this.#i.getItem(e)}setString(e,t){if(this.#h(e),"string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");if("string"!=typeof t)throw new TypeError("Value must be a string.");return this.emit("setString",e,t),this.#i.setItem(e,JSON.stringify({__string__:!0,value:t}))}getString(e){if("string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");let t=this.#i.getItem(e);try{if(t=JSON.parse(t),!r(t)||!t.__string__||"string"!=typeof t.value)return null;t=t.value}catch{t=null}return"string"==typeof t?t:null}setNumber(e,t){if(this.#h(e),"string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");if("number"!=typeof t)throw new TypeError("Value must be a number.");return this.emit("setNumber",e,t),this.#i.setItem(e,String(t))}getNumber(e){if("string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");let t=this.#i.getItem(e);return"number"==typeof t||"string"==typeof t&&t.length>0&&(t=parseFloat(t),!Number.isNaN(t))?t:null}setBool(e,t){if(this.#h(e),"string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");if("boolean"!=typeof t)throw new TypeError("Value must be a boolean.");return this.emit("setBool",e,t),this.#i.setItem(e,String(t))}getBool(e){if("string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");const t=this.#i.getItem(e);if("boolean"==typeof t)return t;if("string"==typeof t){if("true"===t)return!0;if("false"===t)return!1}return null}removeItem(e){if("string"!=typeof e||!e.length)throw new TypeError("Key must be a non-empty string.");return this.emit("removeItem",e),this.#i.removeItem(e)}clearLocalStorage(){return this.#i.clear()}destroy(){window.removeEventListener("storage",this.#c),this.#o.offAllTypes()}}a.registerJsonType(Map,(e,t)=>({__map__:!0,data:Array.from(e.entries()).map(([e,r])=>[e,t(r)])}),{check:e=>e.__map__,decode:(e,t)=>new Map(e.data.map(([e,r])=>[e,t(r)]))},!0),a.registerJsonType(Set,(e,t)=>({__set__:!0,data:Array.from(e).map(t)}),{check:e=>e.__set__,decode:(e,t)=>new Set(e.data.map(t))},!0),a.registerJsonType(Date,e=>({__date__:!0,value:e.toISOString()}),{check:e=>e.__date__,decode:e=>new Date(e.value)},!0),a.registerJsonType(RegExp,e=>({__regexp__:!0,source:e.source,flags:e.flags}),{check:e=>e.__regexp__,decode:e=>new RegExp(e.source,e.flags)},!0),a.registerJsonType("bigint",e=>({__bigint__:!0,value:e.toString()}),{check:e=>e.__bigint__,decode:e=>BigInt(e.value)},!0),a.registerJsonType("symbol",e=>({__symbol__:!0,key:Symbol.keyFor(e)??e.description??null}),{check:e=>e.__symbol__,decode:e=>{const t=e.key;return null!=t?Symbol.for(t):Symbol()}},!0);const l=a;window.TinyLocalStorage=t.TinyLocalStorage})();