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.
- package/dist/v1/TinyEssentials.js +109 -21
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyLocalStorage.js +109 -21
- package/dist/v1/TinyLocalStorage.min.js +1 -1
- package/dist/v1/TinyUploadClicker.js +109 -21
- package/dist/v1/TinyUploadClicker.min.js +1 -1
- package/dist/v1/libs/TinyLocalStorage.cjs +109 -21
- package/dist/v1/libs/TinyLocalStorage.d.mts +29 -0
- package/dist/v1/libs/TinyLocalStorage.mjs +97 -21
- package/docs/v1/libs/TinyLocalStorage.md +53 -0
- package/package.json +1 -1
|
@@ -857,6 +857,12 @@ class TinyLocalStorage {
|
|
|
857
857
|
/** @type {Storage} */
|
|
858
858
|
#localStorage = window.localStorage;
|
|
859
859
|
|
|
860
|
+
/** @type {string|null} */
|
|
861
|
+
#dbKey = null;
|
|
862
|
+
|
|
863
|
+
/** @type {number} */
|
|
864
|
+
#version = 0;
|
|
865
|
+
|
|
860
866
|
/** @type {(ev: StorageEvent) => any} */
|
|
861
867
|
#storageEvent = (ev) => this.emit('storage', ev);
|
|
862
868
|
|
|
@@ -864,11 +870,84 @@ class TinyLocalStorage {
|
|
|
864
870
|
* Initializes the TinyLocalStorage instance and sets up cross-tab sync.
|
|
865
871
|
*
|
|
866
872
|
* Adds listener for the native `storage` event to support tab synchronization.
|
|
873
|
+
* @param {string} [dbName] - Unique database name.
|
|
867
874
|
*/
|
|
868
|
-
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}`;
|
|
869
879
|
window.addEventListener('storage', this.#storageEvent);
|
|
870
880
|
}
|
|
871
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
|
+
|
|
872
951
|
/**
|
|
873
952
|
* Defines a custom storage interface (e.g. `sessionStorage`).
|
|
874
953
|
*
|
|
@@ -876,7 +955,7 @@ class TinyLocalStorage {
|
|
|
876
955
|
*/
|
|
877
956
|
setLocalStorage(localstorage) {
|
|
878
957
|
if (!(localstorage instanceof Storage))
|
|
879
|
-
throw new
|
|
958
|
+
throw new TypeError('Argument must be a valid instance of Storage.');
|
|
880
959
|
this.#localStorage = localstorage;
|
|
881
960
|
}
|
|
882
961
|
|
|
@@ -898,7 +977,7 @@ class TinyLocalStorage {
|
|
|
898
977
|
*/
|
|
899
978
|
#setJson(name, data) {
|
|
900
979
|
if (typeof name !== 'string' || !name.length)
|
|
901
|
-
throw new
|
|
980
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
902
981
|
return TinyLocalStorage.encodeSpecialJson(data);
|
|
903
982
|
}
|
|
904
983
|
|
|
@@ -911,13 +990,14 @@ class TinyLocalStorage {
|
|
|
911
990
|
* @param {LocalStorageJsonValue} data - The data to be serialized and stored.
|
|
912
991
|
*/
|
|
913
992
|
setJson(name, data) {
|
|
993
|
+
this.#isProtectedDbKey(name);
|
|
914
994
|
if (
|
|
915
995
|
!isJsonObject(data) &&
|
|
916
996
|
!Array.isArray(data) &&
|
|
917
997
|
!(data instanceof Map) &&
|
|
918
998
|
!(data instanceof Set)
|
|
919
999
|
) {
|
|
920
|
-
throw new
|
|
1000
|
+
throw new TypeError('The storage value is not a valid JSON-compatible structure.');
|
|
921
1001
|
}
|
|
922
1002
|
const encoded = this.#setJson(name, data);
|
|
923
1003
|
this.emit('setJson', name, data);
|
|
@@ -935,7 +1015,7 @@ class TinyLocalStorage {
|
|
|
935
1015
|
*/
|
|
936
1016
|
#getJson(name, defaultData) {
|
|
937
1017
|
if (typeof name !== 'string' || !name.length)
|
|
938
|
-
throw new
|
|
1018
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
939
1019
|
|
|
940
1020
|
const raw = this.#localStorage.getItem(name);
|
|
941
1021
|
const fallbackTypes = {
|
|
@@ -989,7 +1069,8 @@ class TinyLocalStorage {
|
|
|
989
1069
|
* @param {Date} data
|
|
990
1070
|
*/
|
|
991
1071
|
setDate(name, data) {
|
|
992
|
-
|
|
1072
|
+
this.#isProtectedDbKey(name);
|
|
1073
|
+
if (!(data instanceof Date)) throw new TypeError('Value must be a Date.');
|
|
993
1074
|
const encoded = this.#setJson(name, data);
|
|
994
1075
|
this.emit('setDate', name, data);
|
|
995
1076
|
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
@@ -1011,7 +1092,8 @@ class TinyLocalStorage {
|
|
|
1011
1092
|
* @param {RegExp} data
|
|
1012
1093
|
*/
|
|
1013
1094
|
setRegExp(name, data) {
|
|
1014
|
-
|
|
1095
|
+
this.#isProtectedDbKey(name);
|
|
1096
|
+
if (!(data instanceof RegExp)) throw new TypeError('Value must be a RegExp.');
|
|
1015
1097
|
const encoded = this.#setJson(name, data);
|
|
1016
1098
|
this.emit('setRegExp', name, data);
|
|
1017
1099
|
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
@@ -1033,7 +1115,8 @@ class TinyLocalStorage {
|
|
|
1033
1115
|
* @param {bigint} data
|
|
1034
1116
|
*/
|
|
1035
1117
|
setBigInt(name, data) {
|
|
1036
|
-
|
|
1118
|
+
this.#isProtectedDbKey(name);
|
|
1119
|
+
if (typeof data !== 'bigint') throw new TypeError('Value must be a BigInt.');
|
|
1037
1120
|
const encoded = this.#setJson(name, data);
|
|
1038
1121
|
this.emit('setBigInt', name, data);
|
|
1039
1122
|
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
@@ -1056,7 +1139,8 @@ class TinyLocalStorage {
|
|
|
1056
1139
|
* @param {symbol} data
|
|
1057
1140
|
*/
|
|
1058
1141
|
setSymbol(name, data) {
|
|
1059
|
-
|
|
1142
|
+
this.#isProtectedDbKey(name);
|
|
1143
|
+
if (typeof data !== 'symbol') throw new TypeError('Value must be a Symbol.');
|
|
1060
1144
|
const encoded = this.#setJson(name, data);
|
|
1061
1145
|
this.emit('setSymbol', name, data);
|
|
1062
1146
|
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
@@ -1089,8 +1173,9 @@ class TinyLocalStorage {
|
|
|
1089
1173
|
* @param {any} data - The data to store.
|
|
1090
1174
|
*/
|
|
1091
1175
|
setItem(name, data) {
|
|
1176
|
+
this.#isProtectedDbKey(name);
|
|
1092
1177
|
if (typeof name !== 'string' || !name.length)
|
|
1093
|
-
throw new
|
|
1178
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
1094
1179
|
this.emit('setItem', name, data);
|
|
1095
1180
|
return this.#localStorage.setItem(name, data);
|
|
1096
1181
|
}
|
|
@@ -1103,7 +1188,7 @@ class TinyLocalStorage {
|
|
|
1103
1188
|
*/
|
|
1104
1189
|
getItem(name) {
|
|
1105
1190
|
if (typeof name !== 'string' || !name.length)
|
|
1106
|
-
throw new
|
|
1191
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
1107
1192
|
return this.#localStorage.getItem(name);
|
|
1108
1193
|
}
|
|
1109
1194
|
|
|
@@ -1114,9 +1199,10 @@ class TinyLocalStorage {
|
|
|
1114
1199
|
* @param {string} data - The string to store.
|
|
1115
1200
|
*/
|
|
1116
1201
|
setString(name, data) {
|
|
1202
|
+
this.#isProtectedDbKey(name);
|
|
1117
1203
|
if (typeof name !== 'string' || !name.length)
|
|
1118
|
-
throw new
|
|
1119
|
-
if (typeof data !== 'string') throw new
|
|
1204
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
1205
|
+
if (typeof data !== 'string') throw new TypeError('Value must be a string.');
|
|
1120
1206
|
|
|
1121
1207
|
this.emit('setString', name, data);
|
|
1122
1208
|
return this.#localStorage.setItem(
|
|
@@ -1136,7 +1222,7 @@ class TinyLocalStorage {
|
|
|
1136
1222
|
*/
|
|
1137
1223
|
getString(name) {
|
|
1138
1224
|
if (typeof name !== 'string' || !name.length)
|
|
1139
|
-
throw new
|
|
1225
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
1140
1226
|
let value = this.#localStorage.getItem(name);
|
|
1141
1227
|
try {
|
|
1142
1228
|
/** @type {{ value: string; __string__: boolean }} */
|
|
@@ -1158,9 +1244,10 @@ class TinyLocalStorage {
|
|
|
1158
1244
|
* @param {number} data - The number to store.
|
|
1159
1245
|
*/
|
|
1160
1246
|
setNumber(name, data) {
|
|
1247
|
+
this.#isProtectedDbKey(name);
|
|
1161
1248
|
if (typeof name !== 'string' || !name.length)
|
|
1162
|
-
throw new
|
|
1163
|
-
if (typeof data !== 'number') throw new
|
|
1249
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
1250
|
+
if (typeof data !== 'number') throw new TypeError('Value must be a number.');
|
|
1164
1251
|
this.emit('setNumber', name, data);
|
|
1165
1252
|
return this.#localStorage.setItem(name, String(data));
|
|
1166
1253
|
}
|
|
@@ -1173,7 +1260,7 @@ class TinyLocalStorage {
|
|
|
1173
1260
|
*/
|
|
1174
1261
|
getNumber(name) {
|
|
1175
1262
|
if (typeof name !== 'string' || !name.length)
|
|
1176
|
-
throw new
|
|
1263
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
1177
1264
|
|
|
1178
1265
|
/** @type {number|string|null} */
|
|
1179
1266
|
let number = this.#localStorage.getItem(name);
|
|
@@ -1192,9 +1279,10 @@ class TinyLocalStorage {
|
|
|
1192
1279
|
* @param {boolean} data - The boolean value to store.
|
|
1193
1280
|
*/
|
|
1194
1281
|
setBool(name, data) {
|
|
1282
|
+
this.#isProtectedDbKey(name);
|
|
1195
1283
|
if (typeof name !== 'string' || !name.length)
|
|
1196
|
-
throw new
|
|
1197
|
-
if (typeof data !== 'boolean') throw new
|
|
1284
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
1285
|
+
if (typeof data !== 'boolean') throw new TypeError('Value must be a boolean.');
|
|
1198
1286
|
this.emit('setBool', name, data);
|
|
1199
1287
|
return this.#localStorage.setItem(name, String(data));
|
|
1200
1288
|
}
|
|
@@ -1207,7 +1295,7 @@ class TinyLocalStorage {
|
|
|
1207
1295
|
*/
|
|
1208
1296
|
getBool(name) {
|
|
1209
1297
|
if (typeof name !== 'string' || !name.length)
|
|
1210
|
-
throw new
|
|
1298
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
1211
1299
|
|
|
1212
1300
|
const value = this.#localStorage.getItem(name);
|
|
1213
1301
|
if (typeof value === 'boolean') return value;
|
|
@@ -1226,7 +1314,7 @@ class TinyLocalStorage {
|
|
|
1226
1314
|
*/
|
|
1227
1315
|
removeItem(name) {
|
|
1228
1316
|
if (typeof name !== 'string' || !name.length)
|
|
1229
|
-
throw new
|
|
1317
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
1230
1318
|
|
|
1231
1319
|
this.emit('removeItem', name);
|
|
1232
1320
|
return this.#localStorage.removeItem(name);
|
|
@@ -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:()=>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=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 a.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:a.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,JSON.stringify({__string__:!0,value: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);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("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()}}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})();
|
|
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})();
|
|
@@ -12610,6 +12610,12 @@ class TinyLocalStorage {
|
|
|
12610
12610
|
/** @type {Storage} */
|
|
12611
12611
|
#localStorage = window.localStorage;
|
|
12612
12612
|
|
|
12613
|
+
/** @type {string|null} */
|
|
12614
|
+
#dbKey = null;
|
|
12615
|
+
|
|
12616
|
+
/** @type {number} */
|
|
12617
|
+
#version = 0;
|
|
12618
|
+
|
|
12613
12619
|
/** @type {(ev: StorageEvent) => any} */
|
|
12614
12620
|
#storageEvent = (ev) => this.emit('storage', ev);
|
|
12615
12621
|
|
|
@@ -12617,11 +12623,84 @@ class TinyLocalStorage {
|
|
|
12617
12623
|
* Initializes the TinyLocalStorage instance and sets up cross-tab sync.
|
|
12618
12624
|
*
|
|
12619
12625
|
* Adds listener for the native `storage` event to support tab synchronization.
|
|
12626
|
+
* @param {string} [dbName] - Unique database name.
|
|
12620
12627
|
*/
|
|
12621
|
-
constructor() {
|
|
12628
|
+
constructor(dbName) {
|
|
12629
|
+
if (typeof dbName !== 'undefined' && typeof dbName !== 'string')
|
|
12630
|
+
throw new TypeError('TinyLocalStorage: dbName must be a string if provided.');
|
|
12631
|
+
if (typeof dbName === 'string') this.#dbKey = `LSDB::${dbName}`;
|
|
12622
12632
|
window.addEventListener('storage', this.#storageEvent);
|
|
12623
12633
|
}
|
|
12624
12634
|
|
|
12635
|
+
/**
|
|
12636
|
+
* Validates that a given key does not conflict with internal database keys.
|
|
12637
|
+
*
|
|
12638
|
+
* This method is used to prevent accidental overwriting of reserved `LSDB::` keys
|
|
12639
|
+
* in `localStorage`, which are used internally by TinyLocalStorage for versioning
|
|
12640
|
+
* and data management.
|
|
12641
|
+
*
|
|
12642
|
+
* @param {string} name - The key to validate before writing to localStorage.
|
|
12643
|
+
* @throws {Error} If the key starts with `LSDB::`.
|
|
12644
|
+
*/
|
|
12645
|
+
#isProtectedDbKey(name) {
|
|
12646
|
+
if (typeof name === 'string' && name.startsWith('LSDB::'))
|
|
12647
|
+
throw new Error(`TinyLocalStorage: Key "${name}" may conflict with reserved dbKeys.`);
|
|
12648
|
+
}
|
|
12649
|
+
|
|
12650
|
+
/**
|
|
12651
|
+
* Updates the version of the storage and triggers migration if needed.
|
|
12652
|
+
*
|
|
12653
|
+
* @param {number} version - Desired version of the database.
|
|
12654
|
+
* @param {(oldVersion: number, newVersion: number) => void} onUpgrade - Callback to perform migration logic.
|
|
12655
|
+
* @throws {Error} If the database key has not been initialized.
|
|
12656
|
+
* @throws {TypeError} If `version` is not a valid positive number.
|
|
12657
|
+
* @throws {TypeError} If `onUpgrade` is not a function.
|
|
12658
|
+
*/
|
|
12659
|
+
updateStorageVersion(version, onUpgrade) {
|
|
12660
|
+
if (typeof this.#dbKey !== 'string')
|
|
12661
|
+
throw new Error(
|
|
12662
|
+
'TinyLocalStorage: Database key is not initialized. Set a valid dbName in the constructor.',
|
|
12663
|
+
);
|
|
12664
|
+
if (typeof version !== 'number' || Number.isNaN(version) || version < 1)
|
|
12665
|
+
throw new TypeError('TinyLocalStorage: version must be a positive number.');
|
|
12666
|
+
if (typeof onUpgrade !== 'function')
|
|
12667
|
+
throw new TypeError('TinyLocalStorage: onUpgrade must be a function.');
|
|
12668
|
+
|
|
12669
|
+
// @ts-ignore
|
|
12670
|
+
const savedVersion = parseInt(localStorage.getItem(this.#dbKey), 10) || 0;
|
|
12671
|
+
if (typeof savedVersion !== 'number' || Number.isNaN(savedVersion) || savedVersion < 0)
|
|
12672
|
+
throw new TypeError('TinyLocalStorage: saved version in localStorage is not a valid number.');
|
|
12673
|
+
|
|
12674
|
+
if (version < savedVersion)
|
|
12675
|
+
throw new Error(
|
|
12676
|
+
`TinyLocalStorage: Cannot downgrade database version from ${savedVersion} to ${version}.`,
|
|
12677
|
+
);
|
|
12678
|
+
|
|
12679
|
+
if (version > savedVersion) {
|
|
12680
|
+
onUpgrade(savedVersion, version);
|
|
12681
|
+
localStorage.setItem(this.#dbKey, String(version));
|
|
12682
|
+
this.#version = version;
|
|
12683
|
+
}
|
|
12684
|
+
}
|
|
12685
|
+
|
|
12686
|
+
/**
|
|
12687
|
+
* Gets the current database key used in localStorage.
|
|
12688
|
+
*
|
|
12689
|
+
* @returns {string|null} The database key, or null if not set.
|
|
12690
|
+
*/
|
|
12691
|
+
getDbKey() {
|
|
12692
|
+
return this.#dbKey;
|
|
12693
|
+
}
|
|
12694
|
+
|
|
12695
|
+
/**
|
|
12696
|
+
* Gets the current version of the database.
|
|
12697
|
+
*
|
|
12698
|
+
* @returns {number} The current version number.
|
|
12699
|
+
*/
|
|
12700
|
+
getVersion() {
|
|
12701
|
+
return this.#version;
|
|
12702
|
+
}
|
|
12703
|
+
|
|
12625
12704
|
/**
|
|
12626
12705
|
* Defines a custom storage interface (e.g. `sessionStorage`).
|
|
12627
12706
|
*
|
|
@@ -12629,7 +12708,7 @@ class TinyLocalStorage {
|
|
|
12629
12708
|
*/
|
|
12630
12709
|
setLocalStorage(localstorage) {
|
|
12631
12710
|
if (!(localstorage instanceof Storage))
|
|
12632
|
-
throw new
|
|
12711
|
+
throw new TypeError('Argument must be a valid instance of Storage.');
|
|
12633
12712
|
this.#localStorage = localstorage;
|
|
12634
12713
|
}
|
|
12635
12714
|
|
|
@@ -12651,7 +12730,7 @@ class TinyLocalStorage {
|
|
|
12651
12730
|
*/
|
|
12652
12731
|
#setJson(name, data) {
|
|
12653
12732
|
if (typeof name !== 'string' || !name.length)
|
|
12654
|
-
throw new
|
|
12733
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
12655
12734
|
return TinyLocalStorage.encodeSpecialJson(data);
|
|
12656
12735
|
}
|
|
12657
12736
|
|
|
@@ -12664,13 +12743,14 @@ class TinyLocalStorage {
|
|
|
12664
12743
|
* @param {LocalStorageJsonValue} data - The data to be serialized and stored.
|
|
12665
12744
|
*/
|
|
12666
12745
|
setJson(name, data) {
|
|
12746
|
+
this.#isProtectedDbKey(name);
|
|
12667
12747
|
if (
|
|
12668
12748
|
!isJsonObject(data) &&
|
|
12669
12749
|
!Array.isArray(data) &&
|
|
12670
12750
|
!(data instanceof Map) &&
|
|
12671
12751
|
!(data instanceof Set)
|
|
12672
12752
|
) {
|
|
12673
|
-
throw new
|
|
12753
|
+
throw new TypeError('The storage value is not a valid JSON-compatible structure.');
|
|
12674
12754
|
}
|
|
12675
12755
|
const encoded = this.#setJson(name, data);
|
|
12676
12756
|
this.emit('setJson', name, data);
|
|
@@ -12688,7 +12768,7 @@ class TinyLocalStorage {
|
|
|
12688
12768
|
*/
|
|
12689
12769
|
#getJson(name, defaultData) {
|
|
12690
12770
|
if (typeof name !== 'string' || !name.length)
|
|
12691
|
-
throw new
|
|
12771
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
12692
12772
|
|
|
12693
12773
|
const raw = this.#localStorage.getItem(name);
|
|
12694
12774
|
const fallbackTypes = {
|
|
@@ -12742,7 +12822,8 @@ class TinyLocalStorage {
|
|
|
12742
12822
|
* @param {Date} data
|
|
12743
12823
|
*/
|
|
12744
12824
|
setDate(name, data) {
|
|
12745
|
-
|
|
12825
|
+
this.#isProtectedDbKey(name);
|
|
12826
|
+
if (!(data instanceof Date)) throw new TypeError('Value must be a Date.');
|
|
12746
12827
|
const encoded = this.#setJson(name, data);
|
|
12747
12828
|
this.emit('setDate', name, data);
|
|
12748
12829
|
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
@@ -12764,7 +12845,8 @@ class TinyLocalStorage {
|
|
|
12764
12845
|
* @param {RegExp} data
|
|
12765
12846
|
*/
|
|
12766
12847
|
setRegExp(name, data) {
|
|
12767
|
-
|
|
12848
|
+
this.#isProtectedDbKey(name);
|
|
12849
|
+
if (!(data instanceof RegExp)) throw new TypeError('Value must be a RegExp.');
|
|
12768
12850
|
const encoded = this.#setJson(name, data);
|
|
12769
12851
|
this.emit('setRegExp', name, data);
|
|
12770
12852
|
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
@@ -12786,7 +12868,8 @@ class TinyLocalStorage {
|
|
|
12786
12868
|
* @param {bigint} data
|
|
12787
12869
|
*/
|
|
12788
12870
|
setBigInt(name, data) {
|
|
12789
|
-
|
|
12871
|
+
this.#isProtectedDbKey(name);
|
|
12872
|
+
if (typeof data !== 'bigint') throw new TypeError('Value must be a BigInt.');
|
|
12790
12873
|
const encoded = this.#setJson(name, data);
|
|
12791
12874
|
this.emit('setBigInt', name, data);
|
|
12792
12875
|
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
@@ -12809,7 +12892,8 @@ class TinyLocalStorage {
|
|
|
12809
12892
|
* @param {symbol} data
|
|
12810
12893
|
*/
|
|
12811
12894
|
setSymbol(name, data) {
|
|
12812
|
-
|
|
12895
|
+
this.#isProtectedDbKey(name);
|
|
12896
|
+
if (typeof data !== 'symbol') throw new TypeError('Value must be a Symbol.');
|
|
12813
12897
|
const encoded = this.#setJson(name, data);
|
|
12814
12898
|
this.emit('setSymbol', name, data);
|
|
12815
12899
|
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
@@ -12842,8 +12926,9 @@ class TinyLocalStorage {
|
|
|
12842
12926
|
* @param {any} data - The data to store.
|
|
12843
12927
|
*/
|
|
12844
12928
|
setItem(name, data) {
|
|
12929
|
+
this.#isProtectedDbKey(name);
|
|
12845
12930
|
if (typeof name !== 'string' || !name.length)
|
|
12846
|
-
throw new
|
|
12931
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
12847
12932
|
this.emit('setItem', name, data);
|
|
12848
12933
|
return this.#localStorage.setItem(name, data);
|
|
12849
12934
|
}
|
|
@@ -12856,7 +12941,7 @@ class TinyLocalStorage {
|
|
|
12856
12941
|
*/
|
|
12857
12942
|
getItem(name) {
|
|
12858
12943
|
if (typeof name !== 'string' || !name.length)
|
|
12859
|
-
throw new
|
|
12944
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
12860
12945
|
return this.#localStorage.getItem(name);
|
|
12861
12946
|
}
|
|
12862
12947
|
|
|
@@ -12867,9 +12952,10 @@ class TinyLocalStorage {
|
|
|
12867
12952
|
* @param {string} data - The string to store.
|
|
12868
12953
|
*/
|
|
12869
12954
|
setString(name, data) {
|
|
12955
|
+
this.#isProtectedDbKey(name);
|
|
12870
12956
|
if (typeof name !== 'string' || !name.length)
|
|
12871
|
-
throw new
|
|
12872
|
-
if (typeof data !== 'string') throw new
|
|
12957
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
12958
|
+
if (typeof data !== 'string') throw new TypeError('Value must be a string.');
|
|
12873
12959
|
|
|
12874
12960
|
this.emit('setString', name, data);
|
|
12875
12961
|
return this.#localStorage.setItem(
|
|
@@ -12889,7 +12975,7 @@ class TinyLocalStorage {
|
|
|
12889
12975
|
*/
|
|
12890
12976
|
getString(name) {
|
|
12891
12977
|
if (typeof name !== 'string' || !name.length)
|
|
12892
|
-
throw new
|
|
12978
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
12893
12979
|
let value = this.#localStorage.getItem(name);
|
|
12894
12980
|
try {
|
|
12895
12981
|
/** @type {{ value: string; __string__: boolean }} */
|
|
@@ -12911,9 +12997,10 @@ class TinyLocalStorage {
|
|
|
12911
12997
|
* @param {number} data - The number to store.
|
|
12912
12998
|
*/
|
|
12913
12999
|
setNumber(name, data) {
|
|
13000
|
+
this.#isProtectedDbKey(name);
|
|
12914
13001
|
if (typeof name !== 'string' || !name.length)
|
|
12915
|
-
throw new
|
|
12916
|
-
if (typeof data !== 'number') throw new
|
|
13002
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
13003
|
+
if (typeof data !== 'number') throw new TypeError('Value must be a number.');
|
|
12917
13004
|
this.emit('setNumber', name, data);
|
|
12918
13005
|
return this.#localStorage.setItem(name, String(data));
|
|
12919
13006
|
}
|
|
@@ -12926,7 +13013,7 @@ class TinyLocalStorage {
|
|
|
12926
13013
|
*/
|
|
12927
13014
|
getNumber(name) {
|
|
12928
13015
|
if (typeof name !== 'string' || !name.length)
|
|
12929
|
-
throw new
|
|
13016
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
12930
13017
|
|
|
12931
13018
|
/** @type {number|string|null} */
|
|
12932
13019
|
let number = this.#localStorage.getItem(name);
|
|
@@ -12945,9 +13032,10 @@ class TinyLocalStorage {
|
|
|
12945
13032
|
* @param {boolean} data - The boolean value to store.
|
|
12946
13033
|
*/
|
|
12947
13034
|
setBool(name, data) {
|
|
13035
|
+
this.#isProtectedDbKey(name);
|
|
12948
13036
|
if (typeof name !== 'string' || !name.length)
|
|
12949
|
-
throw new
|
|
12950
|
-
if (typeof data !== 'boolean') throw new
|
|
13037
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
13038
|
+
if (typeof data !== 'boolean') throw new TypeError('Value must be a boolean.');
|
|
12951
13039
|
this.emit('setBool', name, data);
|
|
12952
13040
|
return this.#localStorage.setItem(name, String(data));
|
|
12953
13041
|
}
|
|
@@ -12960,7 +13048,7 @@ class TinyLocalStorage {
|
|
|
12960
13048
|
*/
|
|
12961
13049
|
getBool(name) {
|
|
12962
13050
|
if (typeof name !== 'string' || !name.length)
|
|
12963
|
-
throw new
|
|
13051
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
12964
13052
|
|
|
12965
13053
|
const value = this.#localStorage.getItem(name);
|
|
12966
13054
|
if (typeof value === 'boolean') return value;
|
|
@@ -12979,7 +13067,7 @@ class TinyLocalStorage {
|
|
|
12979
13067
|
*/
|
|
12980
13068
|
removeItem(name) {
|
|
12981
13069
|
if (typeof name !== 'string' || !name.length)
|
|
12982
|
-
throw new
|
|
13070
|
+
throw new TypeError('Key must be a non-empty string.');
|
|
12983
13071
|
|
|
12984
13072
|
this.emit('removeItem', name);
|
|
12985
13073
|
return this.#localStorage.removeItem(name);
|