vanilla-create-storage 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # vanilla-storage
2
+
3
+ A small async storage abstraction for browser storage drivers.
4
+
5
+ ## Install
6
+
7
+ npm:
8
+
9
+ ```bash
10
+ npm install vanilla-create-storage
11
+ ```
12
+
13
+ script:
14
+
15
+ ```javascript
16
+ // umd: GlobalName: storage
17
+ <script src="https://unpkg.com/vanilla-create-storage/dist/index.umd.js"></script>
18
+ const { createStorage } = storage;
19
+
20
+ // es module
21
+ import { createStorage } from 'https://unpkg.com/vanilla-create-storage/dist/index.mjs';
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```js
27
+ import { createStorage } from 'vanilla-create-storage';
28
+
29
+ const storage = createStorage({
30
+ driver: 'indexedDB',
31
+ namespace: 'my-app',
32
+ fallback: ['localStorage', 'memory'],
33
+ ttl: 60_000,
34
+ });
35
+
36
+ await storage.set('user', { id: 1, name: 'Ada' });
37
+ await storage.set('token', 'abc', { ttl: 5 * 60_000 });
38
+
39
+ const user = await storage.get('user');
40
+ const token = await storage.get('token', { defaultValue: null });
41
+
42
+ await storage.delete('token');
43
+ await storage.clear();
44
+ ```
45
+
46
+ ## API
47
+
48
+ ```js
49
+ const storage = createStorage(options);
50
+
51
+ await storage.set(key, value, options);
52
+ await storage.get(key, options);
53
+ await storage.has(key);
54
+ await storage.delete(key);
55
+ await storage.clear();
56
+ await storage.keys();
57
+ await storage.values();
58
+ await storage.entries();
59
+ await storage.size();
60
+ await storage.prune();
61
+ await storage.close();
62
+ ```
63
+
64
+ ## Options
65
+
66
+ ```js
67
+ createStorage({
68
+ driver: 'localStorage',
69
+ namespace: 'vanilla-storage',
70
+ keySeparator: '::',
71
+ fallback: [],
72
+ ttl: null,
73
+ codec: 'json',
74
+ driverOptions: {},
75
+ });
76
+ ```
77
+
78
+ - `driver`: `'localStorage'`, `'sessionStorage'`, `'indexedDB'`, `'cookie'`, `'memory'`, an adapter, or an adapter factory.
79
+ - `namespace`: key prefix used to isolate data. Set `null` to disable namespacing.
80
+ - `fallback`: explicit fallback driver list. The library does not silently downgrade unless configured.
81
+ - `ttl`: default time to live in milliseconds.
82
+ - `codec`: `'json'`, `'raw-string'`, or a custom codec.
83
+ - `driverOptions`: options passed to adapters.
84
+
85
+ ## Driver Options
86
+
87
+ ```js
88
+ createStorage({
89
+ driver: 'cookie',
90
+ driverOptions: {
91
+ path: '/',
92
+ sameSite: 'lax',
93
+ secure: true,
94
+ },
95
+ });
96
+
97
+ createStorage({
98
+ driver: 'indexedDB',
99
+ driverOptions: {
100
+ dbName: 'MyAppStorage',
101
+ storeName: 'records',
102
+ version: 1,
103
+ },
104
+ });
105
+ ```
106
+
107
+ You can also scope options per driver:
108
+
109
+ ```js
110
+ createStorage({
111
+ driver: 'indexedDB',
112
+ fallback: ['localStorage'],
113
+ driverOptions: {
114
+ indexedDB: { dbName: 'MyAppStorage' },
115
+ cookie: { path: '/', sameSite: 'lax' },
116
+ },
117
+ });
118
+ ```
119
+
120
+ ## Design
121
+
122
+ - All public methods are async, including synchronous browser storage drivers.
123
+ - Stored records use a consistent envelope across drivers.
124
+ - `clear()` only removes keys inside the configured namespace.
125
+ - Expired records are lazily removed on read and can be eagerly removed with `prune()`.
126
+ - Fallbacks are explicit so capacity and persistence semantics do not change silently.
127
+
128
+ ## Translations
129
+
130
+ - [中文](README_zh.md)
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ var e=class extends Error{constructor(e,t={}){super(e),this.name=this.constructor.name,this.code=t.code||`STORAGE_ERROR`,t.driver&&(this.driver=t.driver),t.key&&(this.key=t.key),t.details&&(this.details=t.details),t.cause&&(this.cause=t.cause)}},t=class extends e{constructor(e,t={}){super(e,{...t,code:`DRIVER_UNAVAILABLE`})}},n=class extends e{constructor(e,t={}){super(e,{...t,code:`QUOTA_EXCEEDED`})}},r=class extends e{constructor(e,t={}){super(e,{...t,code:`SERIALIZATION_FAILED`})}},i=class extends e{constructor(e,t={}){super(e,{...t,code:`INVALID_RECORD`})}};function a(e){return e?.name===`QuotaExceededError`||e?.name===`NS_ERROR_DOM_QUOTA_REACHED`||e?.code===22||e?.code===1014}function o(t,r={}){return t instanceof e?t:a(t)?new n(`Storage quota exceeded.`,{...r,cause:t}):new e(t?.message||`Storage operation failed.`,{...r,cause:t})}const s=new Set([`expires`,`max-age`,`domain`,`path`,`secure`,`httponly`,`samesite`]);var c=class{constructor(e={}){this.name=`cookie`,this.document=e.document,this.defaults={path:`/`,sameSite:void 0,secure:void 0,domain:void 0,...e},delete this.defaults.document}_getDocument(){let e=this.document||(typeof globalThis<`u`?globalThis.document:void 0);if(!e?.cookie&&e?.cookie!==``)throw new t(`document.cookie is not available.`,{driver:this.name});return e}async isAvailable(){let e=`__vanilla_storage_test__${Date.now()}_${Math.random()}`;try{await this.setRaw(e,`1`);let t=await this.getRaw(e)===`1`;return await this.deleteRaw(e),t}catch{return!1}}async getRaw(e){try{let t=encodeURIComponent(e),n=this._getDocument().cookie.split(`;`);for(let e of n){let n=e.indexOf(`=`);if(n<0)continue;let r=e.slice(0,n).trim(),i=e.slice(n+1);if(r===t)return decodeURIComponent(i)}return}catch(t){throw o(t,{driver:this.name,key:e})}}async setRaw(e,t){try{this._setCookie(e,t,this.defaults)}catch(t){throw o(t,{driver:this.name,key:e})}}async deleteRaw(e){try{this._setCookie(e,``,{...this.defaults,expires:new Date(0),maxAge:0})}catch(t){throw o(t,{driver:this.name,key:e})}}async clearRaw(e=``){let t=await this.keysRaw(e);for(let e of t)await this.deleteRaw(e)}async keysRaw(e=``){try{let t=[],n=this._getDocument().cookie.split(`;`);for(let r of n){let n=r.indexOf(`=`);if(n<0)continue;let i=r.slice(0,n).trim(),a=decodeURIComponent(i);(!e||a.startsWith(e))&&t.push(a)}return t}catch(e){throw o(e,{driver:this.name})}}_setCookie(e,t,n){this._validateCookieKey(e);let r=`${encodeURIComponent(e)}=${encodeURIComponent(t)}`;if(n.expires){let e=n.expires instanceof Date?n.expires:new Date(n.expires);r+=`; Expires=${e.toUTCString()}`}if(n.maxAge!==void 0&&(r+=`; Max-Age=${Math.floor(n.maxAge)}`),n.domain&&(r+=`; Domain=${n.domain}`),n.path&&(r+=`; Path=${n.path}`),n.sameSite){let e=l(n.sameSite);r+=`; SameSite=${e}`}n.secure&&(r+=`; Secure`),this._getDocument().cookie=r}_validateCookieKey(e){if(s.has(e.toLowerCase()))throw new i(`Cookie key "${e}" is reserved.`,{driver:this.name,key:e});if(/[\s=;,]/.test(e))throw new i(`Cookie key cannot contain whitespace, equals, comma, or semicolon.`,{driver:this.name,key:e})}};function l(e){let t=String(e).toLowerCase();if(t===`strict`)return`Strict`;if(t===`lax`)return`Lax`;if(t===`none`)return`None`;throw new i(`Cookie sameSite must be "strict", "lax", or "none".`,{driver:`cookie`})}var u=class{constructor(e={}){this.name=`indexedDB`,this.dbName=e.dbName||`VanillaStorage`,this.storeName=e.storeName||`records`,this.version=e.version||1,this.indexedDB=e.indexedDB||d(),this.db=null,this.openPromise=null}async isAvailable(){if(!this.indexedDB)return!1;try{return await this._open(),!0}catch{return!1}}async getRaw(e){try{return await this._getRecordValue(e)}catch(t){throw o(t,{driver:this.name,key:e})}}async setRaw(e,t){try{await this._writeRecord(n=>n.put({key:e,value:t}))}catch(t){throw o(t,{driver:this.name,key:e})}}async deleteRaw(e){try{await this._writeRecord(t=>t.delete(e))}catch(t){throw o(t,{driver:this.name,key:e})}}async clearRaw(e=``){if(!e){await this._writeRecord(e=>e.clear());return}let t=await this.keysRaw(e);await this._writeRecord(e=>{for(let n of t)e.delete(n)})}async keysRaw(e=``){let t=await this._readAllKeys();return e?t.filter(t=>t.startsWith(e)):t}async close(){this.db&&(this.db.close(),this.db=null,this.openPromise=null)}async _open(){if(this.db)return this.db;if(!this.indexedDB)throw new t(`indexedDB is not available.`,{driver:this.name});return this.openPromise||(this.openPromise=new Promise((e,t)=>{let n=this.indexedDB.open(this.dbName,this.version);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(this.storeName)||e.createObjectStore(this.storeName,{keyPath:`key`})},n.onsuccess=()=>{this.db=n.result,this.db.onversionchange=()=>{this.close()},e(this.db)},n.onerror=()=>{t(n.error)},n.onblocked=()=>{t(new i(`IndexedDB upgrade is blocked by another open connection.`,{driver:this.name}))}})),this.openPromise}async _getRecordValue(e){let t=await this._open();return await new Promise((n,r)=>{let i=t.transaction(this.storeName,`readonly`),a=i.objectStore(this.storeName).get(e),o;i.oncomplete=()=>{n(o)},i.onerror=()=>r(i.error),i.onabort=()=>r(i.error),a.onsuccess=()=>{o=a.result&&a.result.value!==void 0?a.result.value:void 0},a.onerror=()=>r(a.error)})}async _writeRecord(e){let t=await this._open();return await new Promise((n,r)=>{let i=t.transaction(this.storeName,`readwrite`),a=i.objectStore(this.storeName);i.oncomplete=()=>n(void 0),i.onerror=()=>r(i.error),i.onabort=()=>r(i.error);try{let t=e(a);t&&(t.onerror=()=>r(t.error))}catch(e){i.abort(),r(e)}})}async _readAllKeys(){return typeof IDBObjectStore<`u`&&IDBObjectStore.prototype.getAllKeys?await this._requestKeysWithGetAllKeys():await this._requestKeysWithCursor()}async _requestKeysWithGetAllKeys(){let e=await this._open();return await new Promise((t,n)=>{let r=e.transaction(this.storeName,`readonly`),i=r.objectStore(this.storeName).getAllKeys(),a=[];r.oncomplete=()=>t(a),r.onerror=()=>n(r.error),r.onabort=()=>n(r.error),i.onsuccess=()=>{a=i.result.filter(e=>typeof e==`string`)},i.onerror=()=>n(i.error)})}async _requestKeysWithCursor(){let e=await this._open();return await new Promise((t,n)=>{let r=e.transaction(this.storeName,`readonly`),i=r.objectStore(this.storeName),a=[],o=i.openCursor();r.oncomplete=()=>t(a),r.onerror=()=>n(r.error),r.onabort=()=>n(r.error),o.onsuccess=()=>{let e=o.result;e&&(typeof e.key==`string`&&a.push(e.key),e.continue())},o.onerror=()=>n(o.error)})}};function d(){if(!(typeof globalThis>`u`))return globalThis.indexedDB}var f=class{constructor(e={}){this.name=`memory`,this.map=e.map||new Map}async isAvailable(){return!0}async getRaw(e){return this.map.has(e)?this.map.get(e):void 0}async setRaw(e,t){this.map.set(e,t)}async deleteRaw(e){this.map.delete(e)}async clearRaw(e=``){for(let t of this.map.keys())(!e||t.startsWith(e))&&this.map.delete(t)}async keysRaw(e=``){return Array.from(this.map.keys()).filter(t=>!e||t.startsWith(e))}};function p(){return`__vanilla_storage_test__${Date.now()}_${Math.random()}`}var m=class{constructor(e=`localStorage`,t={}){this.name=e,this.type=e,this.storage=t.storage||null,this.window=t.window}_getStorage(){if(this.storage)return this.storage;let e=(this.window||(typeof globalThis<`u`?globalThis:void 0))?.[this.type];if(!e)throw new t(`${this.type} is not available.`,{driver:this.name});return e}async isAvailable(){let e=p();try{let t=this._getStorage();return t.setItem(e,`1`),t.removeItem(e),!0}catch{return!1}}async getRaw(e){try{let t=this._getStorage().getItem(e);return t===null?void 0:t}catch(t){throw o(t,{driver:this.name,key:e})}}async setRaw(e,t){try{this._getStorage().setItem(e,t)}catch(t){throw o(t,{driver:this.name,key:e})}}async deleteRaw(e){try{this._getStorage().removeItem(e)}catch(t){throw o(t,{driver:this.name,key:e})}}async clearRaw(e=``){let t=this._getStorage(),n=await this.keysRaw(e);try{for(let e of n)t.removeItem(e)}catch(e){throw o(e,{driver:this.name})}}async keysRaw(e=``){let t=this._getStorage(),n=[];try{for(let r=0;r<t.length;r+=1){let i=t.key(r);i&&(!e||i.startsWith(e))&&n.push(i)}}catch(e){throw o(e,{driver:this.name})}return n}},h=class extends m{constructor(e={}){super(`localStorage`,e)}},g=class extends m{constructor(e={}){super(`sessionStorage`,e)}};const _={name:`json`,serialize(e){if(typeof e==`function`||typeof e==`symbol`)throw new r(`JSON codec cannot serialize functions or symbols.`);if(e===void 0)return`{"type":"undefined"}`;try{return JSON.stringify({type:`json`,value:e})}catch(e){throw new r(`Failed to serialize value with JSON codec.`,{cause:e})}},deserialize(e){try{let t=JSON.parse(e);if(t?.type===`undefined`)return;if(t?.type===`json`)return t.value;throw Error(`Invalid JSON codec payload.`)}catch(e){throw new r(`Failed to deserialize value with JSON codec.`,{cause:e})}}},v={name:`raw-string`,serialize(e){if(typeof e!=`string`)throw new r(`Raw string codec can only serialize string values.`);return e},deserialize(e){return e}},y=Symbol(`vanilla-storage.missing`);function b(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function x(e){return typeof e==`object`&&!!e}function S(e){if(typeof e!=`string`||e.length===0)throw TypeError(`Storage key must be a non-empty string.`);return e}function C(e){if(e===void 0)return`vanilla-storage`;if(e===null||e===!1)return``;if(typeof e!=`string`)throw TypeError(`Storage namespace must be a string.`);return e}function w(e){if(e===void 0)return`::`;if(typeof e!=`string`)throw TypeError(`Storage keySeparator must be a string.`);return e}function T(e,t){let n=e instanceof Date?e.getTime():e;if(!Number.isFinite(n))throw TypeError(`${t} must be a finite timestamp or Date.`);return n}function E(e,t=`ttl`){if(e==null||e===!1||e===1/0)return null;if(!Number.isFinite(e)||e<0)throw TypeError(`${t} must be a non-negative number.`);return e}function D(e){let t=new Set,n=[];for(let r of e){let e=typeof r==`string`?r:r?.name||r;t.has(e)||(t.add(e),n.push(r))}return n}const O={cookie:e=>new c(e),indexedDB:e=>new u(e),localStorage:e=>new h(e),memory:e=>new f(e),sessionStorage:e=>new g(e)},k={json:_,"raw-string":v};var A=class{constructor(e={}){let t=typeof e==`string`?{driver:e}:{...e};this.driver=t.driver||`localStorage`,this.fallback=M(t.fallback),this.namespace=C(t.namespace),this.keySeparator=w(t.keySeparator),this.defaultTtl=E(t.ttl),this.clock=t.clock||Date.now,this.driverOptions=t.driverOptions||{},this.adapters=t.adapters||{},this.codec=N(t.codec||_),this.codecs=P(t.codecs,this.codec),this.onDriverError=t.onDriverError,this._adapter=null,this._adapterPromise=null}get prefix(){return this.namespace?`${this.namespace}${this.keySeparator}`:``}get activeDriver(){return this._adapter?.name||null}get ready(){return this._getAdapter()}async set(e,t,n={}){let r=this._fullKey(e),i=await this._getAdapter(),a=this._encodeRecord(t,n);await i.setRaw(r,a)}async get(e,t={}){let n=await this._read(e,{deserialize:!0});return n===y?b(t,`defaultValue`)?t.defaultValue:void 0:n}async has(e){return await this._read(e,{deserialize:!1})!==y}async delete(e){await(await this._getAdapter()).deleteRaw(this._fullKey(e))}async remove(e){await this.delete(e)}async clear(){await(await this._getAdapter()).clearRaw(this.prefix)}async keys(){return await this._liveKeys()}async rawKeys(){return(await(await this._getAdapter()).keysRaw(this.prefix)).map(e=>e.slice(this.prefix.length))}async values(){return(await this.entries()).map(e=>e[1])}async entries(){let e=await this.keys(),t=[];for(let n of e){let e=await this._read(n,{deserialize:!0});e!==y&&t.push([n,e])}return t}async size(){return(await this.keys()).length}async prune(e={}){let t=await this._getAdapter(),n=await t.keysRaw(this.prefix),r=0;for(let i of n){let n=await t.getRaw(i);n!==void 0&&this._inspectRaw(n,i,{removeInvalid:e.removeInvalid===!0})===`delete`&&(await t.deleteRaw(i),r+=1)}return r}async close(){this._adapter?.close&&await this._adapter.close(),this._adapter=null,this._adapterPromise=null}async _read(e,t){let n=this._fullKey(e),r=await this._getAdapter(),i=await r.getRaw(n);if(i===void 0)return y;let a=this._decodeRecord(i,n);return this._isExpired(a)?(await r.deleteRaw(n),y):t.deserialize?this._deserializeRecord(a,n):!0}_encodeRecord(e,t){let n=N(t.codec||this.codec),i=this._resolveExpiresAt(t),a;this.codecs.set(n.name,n);try{a=n.serialize(e)}catch(e){throw e instanceof r?e:new r(`Failed to serialize value.`,{cause:e})}if(typeof a!=`string`)throw new r(`Storage codec serialize() must return a string.`);return JSON.stringify({v:1,codec:n.name||`custom`,expiresAt:i,value:a})}_decodeRecord(e,t){let n;try{n=JSON.parse(e)}catch(e){throw new i(`Stored record is not valid JSON.`,{key:t,cause:e})}if(!x(n)||n.v!==1||typeof n.value!=`string`||!b(n,`expiresAt`))throw new i(`Stored record has an unsupported format.`,{key:t,details:n});return n}_deserializeRecord(e,t){let n=this.codecs.get(e.codec);if(!n)throw new r(`No codec registered for stored record codec "${e.codec}".`,{key:t});try{return n.deserialize(e.value)}catch(e){throw e instanceof r?e:new r(`Failed to deserialize value.`,{key:t,cause:e})}}_inspectRaw(e,t,n){try{let n=this._decodeRecord(e,t);return this._isExpired(n)?`delete`:`keep`}catch(e){if(n.removeInvalid)return`delete`;throw e}}_isExpired(e){return e.expiresAt!==null&&e.expiresAt<=this.clock()}_resolveExpiresAt(e){if(b(e,`expiresAt`))return e.expiresAt===null||e.expiresAt===!1?null:T(e.expiresAt,`expiresAt`);let t=b(e,`ttl`)?E(e.ttl):this.defaultTtl;return t===null?null:this.clock()+t}_fullKey(e){return`${this.prefix}${S(e)}`}async _getAdapter(){return this._adapter?this._adapter:(this._adapterPromise||(this._adapterPromise=this._selectAdapter()),this._adapter=await this._adapterPromise,this._adapter)}async _selectAdapter(){let e=D([this.driver,...this.fallback]),n=[];for(let t of e)try{let e=this._createAdapter(t);if(typeof e.isAvailable!=`function`||await e.isAvailable())return e;n.push({driver:e.name,error:`Driver reported unavailable.`})}catch(e){let r=typeof t==`string`?t:t?.name||`custom`;n.push({driver:r,error:e}),this.onDriverError?.(e,r)}throw new t(`No configured storage driver is usable.`,{details:n})}_createAdapter(e){if(F(e))return e;if(typeof e==`function`){let t=e({driverOptions:this.driverOptions,storage:this});if(!F(t))throw TypeError(`Custom storage driver must return an adapter.`);return t}if(typeof e!=`string`)throw TypeError(`Storage driver must be a string, adapter, or factory.`);let n=this.adapters[e];if(n)return this._createAdapter(n);let r=O[e];if(!r)throw new t(`Unsupported storage driver: ${e}`,{driver:e});return r(this._driverOptions(e))}_driverOptions(e){let t=this.driverOptions||{};return[`cookie`,`indexedDB`,`localStorage`,`memory`,`sessionStorage`,`shared`].some(e=>b(t,e))?{...t.shared,...t[e]}:t}async _liveKeys(){let e=await this._getAdapter(),t=await e.keysRaw(this.prefix),n=[];for(let r of t){let t=await e.getRaw(r);if(t===void 0)continue;let i=this._decodeRecord(t,r);if(this._isExpired(i)){await e.deleteRaw(r);continue}n.push(r.slice(this.prefix.length))}return n}};function j(e){return new A(e)}function M(e){if(e==null||e===!1)return[];if(typeof e==`string`||F(e)||typeof e==`function`)return[e];if(!Array.isArray(e))throw TypeError(`Storage fallback must be a driver or an array.`);return e}function N(e){if(typeof e==`string`){let t=k[e];if(!t)throw new r(`Unsupported storage codec: ${e}`);return t}if(!e||typeof e.serialize!=`function`||typeof e.deserialize!=`function`)throw TypeError(`Storage codec must provide serialize(value) and deserialize(value).`);return{name:e.name||`custom`,serialize:e.serialize.bind(e),deserialize:e.deserialize.bind(e)}}function P(e,t){let n=new Map;for(let e of Object.values(k))n.set(e.name,e);if(n.set(t.name,t),!e)return n;let r=Array.isArray(e)?e:Object.values(e);for(let e of r){let t=N(e);n.set(t.name,t)}return n}function F(e){return e&&typeof e.getRaw==`function`&&typeof e.setRaw==`function`&&typeof e.deleteRaw==`function`&&typeof e.clearRaw==`function`&&typeof e.keysRaw==`function`}export{c as CookieAdapter,u as IndexedDBAdapter,h as LocalStorageAdapter,f as MemoryAdapter,g as SessionStorageAdapter,A as Storage,i as StorageDataError,n as StorageQuotaError,r as StorageSerializationError,t as StorageUnavailableError,e as VanillaStorageError,m as WebStorageAdapter,j as createStorage,_ as jsonCodec,v as rawStringCodec};
@@ -0,0 +1 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.storage={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class extends Error{constructor(e,t={}){super(e),this.name=this.constructor.name,this.code=t.code||`STORAGE_ERROR`,t.driver&&(this.driver=t.driver),t.key&&(this.key=t.key),t.details&&(this.details=t.details),t.cause&&(this.cause=t.cause)}},n=class extends t{constructor(e,t={}){super(e,{...t,code:`DRIVER_UNAVAILABLE`})}},r=class extends t{constructor(e,t={}){super(e,{...t,code:`QUOTA_EXCEEDED`})}},i=class extends t{constructor(e,t={}){super(e,{...t,code:`SERIALIZATION_FAILED`})}},a=class extends t{constructor(e,t={}){super(e,{...t,code:`INVALID_RECORD`})}};function o(e){return e?.name===`QuotaExceededError`||e?.name===`NS_ERROR_DOM_QUOTA_REACHED`||e?.code===22||e?.code===1014}function s(e,n={}){return e instanceof t?e:o(e)?new r(`Storage quota exceeded.`,{...n,cause:e}):new t(e?.message||`Storage operation failed.`,{...n,cause:e})}let c=new Set([`expires`,`max-age`,`domain`,`path`,`secure`,`httponly`,`samesite`]);var l=class{constructor(e={}){this.name=`cookie`,this.document=e.document,this.defaults={path:`/`,sameSite:void 0,secure:void 0,domain:void 0,...e},delete this.defaults.document}_getDocument(){let e=this.document||(typeof globalThis<`u`?globalThis.document:void 0);if(!e?.cookie&&e?.cookie!==``)throw new n(`document.cookie is not available.`,{driver:this.name});return e}async isAvailable(){let e=`__vanilla_storage_test__${Date.now()}_${Math.random()}`;try{await this.setRaw(e,`1`);let t=await this.getRaw(e)===`1`;return await this.deleteRaw(e),t}catch{return!1}}async getRaw(e){try{let t=encodeURIComponent(e),n=this._getDocument().cookie.split(`;`);for(let e of n){let n=e.indexOf(`=`);if(n<0)continue;let r=e.slice(0,n).trim(),i=e.slice(n+1);if(r===t)return decodeURIComponent(i)}return}catch(t){throw s(t,{driver:this.name,key:e})}}async setRaw(e,t){try{this._setCookie(e,t,this.defaults)}catch(t){throw s(t,{driver:this.name,key:e})}}async deleteRaw(e){try{this._setCookie(e,``,{...this.defaults,expires:new Date(0),maxAge:0})}catch(t){throw s(t,{driver:this.name,key:e})}}async clearRaw(e=``){let t=await this.keysRaw(e);for(let e of t)await this.deleteRaw(e)}async keysRaw(e=``){try{let t=[],n=this._getDocument().cookie.split(`;`);for(let r of n){let n=r.indexOf(`=`);if(n<0)continue;let i=r.slice(0,n).trim(),a=decodeURIComponent(i);(!e||a.startsWith(e))&&t.push(a)}return t}catch(e){throw s(e,{driver:this.name})}}_setCookie(e,t,n){this._validateCookieKey(e);let r=`${encodeURIComponent(e)}=${encodeURIComponent(t)}`;if(n.expires){let e=n.expires instanceof Date?n.expires:new Date(n.expires);r+=`; Expires=${e.toUTCString()}`}if(n.maxAge!==void 0&&(r+=`; Max-Age=${Math.floor(n.maxAge)}`),n.domain&&(r+=`; Domain=${n.domain}`),n.path&&(r+=`; Path=${n.path}`),n.sameSite){let e=u(n.sameSite);r+=`; SameSite=${e}`}n.secure&&(r+=`; Secure`),this._getDocument().cookie=r}_validateCookieKey(e){if(c.has(e.toLowerCase()))throw new a(`Cookie key "${e}" is reserved.`,{driver:this.name,key:e});if(/[\s=;,]/.test(e))throw new a(`Cookie key cannot contain whitespace, equals, comma, or semicolon.`,{driver:this.name,key:e})}};function u(e){let t=String(e).toLowerCase();if(t===`strict`)return`Strict`;if(t===`lax`)return`Lax`;if(t===`none`)return`None`;throw new a(`Cookie sameSite must be "strict", "lax", or "none".`,{driver:`cookie`})}var d=class{constructor(e={}){this.name=`indexedDB`,this.dbName=e.dbName||`VanillaStorage`,this.storeName=e.storeName||`records`,this.version=e.version||1,this.indexedDB=e.indexedDB||f(),this.db=null,this.openPromise=null}async isAvailable(){if(!this.indexedDB)return!1;try{return await this._open(),!0}catch{return!1}}async getRaw(e){try{return await this._getRecordValue(e)}catch(t){throw s(t,{driver:this.name,key:e})}}async setRaw(e,t){try{await this._writeRecord(n=>n.put({key:e,value:t}))}catch(t){throw s(t,{driver:this.name,key:e})}}async deleteRaw(e){try{await this._writeRecord(t=>t.delete(e))}catch(t){throw s(t,{driver:this.name,key:e})}}async clearRaw(e=``){if(!e){await this._writeRecord(e=>e.clear());return}let t=await this.keysRaw(e);await this._writeRecord(e=>{for(let n of t)e.delete(n)})}async keysRaw(e=``){let t=await this._readAllKeys();return e?t.filter(t=>t.startsWith(e)):t}async close(){this.db&&(this.db.close(),this.db=null,this.openPromise=null)}async _open(){if(this.db)return this.db;if(!this.indexedDB)throw new n(`indexedDB is not available.`,{driver:this.name});return this.openPromise||(this.openPromise=new Promise((e,t)=>{let n=this.indexedDB.open(this.dbName,this.version);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(this.storeName)||e.createObjectStore(this.storeName,{keyPath:`key`})},n.onsuccess=()=>{this.db=n.result,this.db.onversionchange=()=>{this.close()},e(this.db)},n.onerror=()=>{t(n.error)},n.onblocked=()=>{t(new a(`IndexedDB upgrade is blocked by another open connection.`,{driver:this.name}))}})),this.openPromise}async _getRecordValue(e){let t=await this._open();return await new Promise((n,r)=>{let i=t.transaction(this.storeName,`readonly`),a=i.objectStore(this.storeName).get(e),o;i.oncomplete=()=>{n(o)},i.onerror=()=>r(i.error),i.onabort=()=>r(i.error),a.onsuccess=()=>{o=a.result&&a.result.value!==void 0?a.result.value:void 0},a.onerror=()=>r(a.error)})}async _writeRecord(e){let t=await this._open();return await new Promise((n,r)=>{let i=t.transaction(this.storeName,`readwrite`),a=i.objectStore(this.storeName);i.oncomplete=()=>n(void 0),i.onerror=()=>r(i.error),i.onabort=()=>r(i.error);try{let t=e(a);t&&(t.onerror=()=>r(t.error))}catch(e){i.abort(),r(e)}})}async _readAllKeys(){return typeof IDBObjectStore<`u`&&IDBObjectStore.prototype.getAllKeys?await this._requestKeysWithGetAllKeys():await this._requestKeysWithCursor()}async _requestKeysWithGetAllKeys(){let e=await this._open();return await new Promise((t,n)=>{let r=e.transaction(this.storeName,`readonly`),i=r.objectStore(this.storeName).getAllKeys(),a=[];r.oncomplete=()=>t(a),r.onerror=()=>n(r.error),r.onabort=()=>n(r.error),i.onsuccess=()=>{a=i.result.filter(e=>typeof e==`string`)},i.onerror=()=>n(i.error)})}async _requestKeysWithCursor(){let e=await this._open();return await new Promise((t,n)=>{let r=e.transaction(this.storeName,`readonly`),i=r.objectStore(this.storeName),a=[],o=i.openCursor();r.oncomplete=()=>t(a),r.onerror=()=>n(r.error),r.onabort=()=>n(r.error),o.onsuccess=()=>{let e=o.result;e&&(typeof e.key==`string`&&a.push(e.key),e.continue())},o.onerror=()=>n(o.error)})}};function f(){if(!(typeof globalThis>`u`))return globalThis.indexedDB}var p=class{constructor(e={}){this.name=`memory`,this.map=e.map||new Map}async isAvailable(){return!0}async getRaw(e){return this.map.has(e)?this.map.get(e):void 0}async setRaw(e,t){this.map.set(e,t)}async deleteRaw(e){this.map.delete(e)}async clearRaw(e=``){for(let t of this.map.keys())(!e||t.startsWith(e))&&this.map.delete(t)}async keysRaw(e=``){return Array.from(this.map.keys()).filter(t=>!e||t.startsWith(e))}};function m(){return`__vanilla_storage_test__${Date.now()}_${Math.random()}`}var h=class{constructor(e=`localStorage`,t={}){this.name=e,this.type=e,this.storage=t.storage||null,this.window=t.window}_getStorage(){if(this.storage)return this.storage;let e=(this.window||(typeof globalThis<`u`?globalThis:void 0))?.[this.type];if(!e)throw new n(`${this.type} is not available.`,{driver:this.name});return e}async isAvailable(){let e=m();try{let t=this._getStorage();return t.setItem(e,`1`),t.removeItem(e),!0}catch{return!1}}async getRaw(e){try{let t=this._getStorage().getItem(e);return t===null?void 0:t}catch(t){throw s(t,{driver:this.name,key:e})}}async setRaw(e,t){try{this._getStorage().setItem(e,t)}catch(t){throw s(t,{driver:this.name,key:e})}}async deleteRaw(e){try{this._getStorage().removeItem(e)}catch(t){throw s(t,{driver:this.name,key:e})}}async clearRaw(e=``){let t=this._getStorage(),n=await this.keysRaw(e);try{for(let e of n)t.removeItem(e)}catch(e){throw s(e,{driver:this.name})}}async keysRaw(e=``){let t=this._getStorage(),n=[];try{for(let r=0;r<t.length;r+=1){let i=t.key(r);i&&(!e||i.startsWith(e))&&n.push(i)}}catch(e){throw s(e,{driver:this.name})}return n}},g=class extends h{constructor(e={}){super(`localStorage`,e)}},_=class extends h{constructor(e={}){super(`sessionStorage`,e)}};let v={name:`json`,serialize(e){if(typeof e==`function`||typeof e==`symbol`)throw new i(`JSON codec cannot serialize functions or symbols.`);if(e===void 0)return`{"type":"undefined"}`;try{return JSON.stringify({type:`json`,value:e})}catch(e){throw new i(`Failed to serialize value with JSON codec.`,{cause:e})}},deserialize(e){try{let t=JSON.parse(e);if(t?.type===`undefined`)return;if(t?.type===`json`)return t.value;throw Error(`Invalid JSON codec payload.`)}catch(e){throw new i(`Failed to deserialize value with JSON codec.`,{cause:e})}}},y={name:`raw-string`,serialize(e){if(typeof e!=`string`)throw new i(`Raw string codec can only serialize string values.`);return e},deserialize(e){return e}},b=Symbol(`vanilla-storage.missing`);function x(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function S(e){return typeof e==`object`&&!!e}function C(e){if(typeof e!=`string`||e.length===0)throw TypeError(`Storage key must be a non-empty string.`);return e}function w(e){if(e===void 0)return`vanilla-storage`;if(e===null||e===!1)return``;if(typeof e!=`string`)throw TypeError(`Storage namespace must be a string.`);return e}function T(e){if(e===void 0)return`::`;if(typeof e!=`string`)throw TypeError(`Storage keySeparator must be a string.`);return e}function E(e,t){let n=e instanceof Date?e.getTime():e;if(!Number.isFinite(n))throw TypeError(`${t} must be a finite timestamp or Date.`);return n}function D(e,t=`ttl`){if(e==null||e===!1||e===1/0)return null;if(!Number.isFinite(e)||e<0)throw TypeError(`${t} must be a non-negative number.`);return e}function O(e){let t=new Set,n=[];for(let r of e){let e=typeof r==`string`?r:r?.name||r;t.has(e)||(t.add(e),n.push(r))}return n}let k={cookie:e=>new l(e),indexedDB:e=>new d(e),localStorage:e=>new g(e),memory:e=>new p(e),sessionStorage:e=>new _(e)},A={json:v,"raw-string":y};var j=class{constructor(e={}){let t=typeof e==`string`?{driver:e}:{...e};this.driver=t.driver||`localStorage`,this.fallback=N(t.fallback),this.namespace=w(t.namespace),this.keySeparator=T(t.keySeparator),this.defaultTtl=D(t.ttl),this.clock=t.clock||Date.now,this.driverOptions=t.driverOptions||{},this.adapters=t.adapters||{},this.codec=P(t.codec||v),this.codecs=F(t.codecs,this.codec),this.onDriverError=t.onDriverError,this._adapter=null,this._adapterPromise=null}get prefix(){return this.namespace?`${this.namespace}${this.keySeparator}`:``}get activeDriver(){return this._adapter?.name||null}get ready(){return this._getAdapter()}async set(e,t,n={}){let r=this._fullKey(e),i=await this._getAdapter(),a=this._encodeRecord(t,n);await i.setRaw(r,a)}async get(e,t={}){let n=await this._read(e,{deserialize:!0});return n===b?x(t,`defaultValue`)?t.defaultValue:void 0:n}async has(e){return await this._read(e,{deserialize:!1})!==b}async delete(e){await(await this._getAdapter()).deleteRaw(this._fullKey(e))}async remove(e){await this.delete(e)}async clear(){await(await this._getAdapter()).clearRaw(this.prefix)}async keys(){return await this._liveKeys()}async rawKeys(){return(await(await this._getAdapter()).keysRaw(this.prefix)).map(e=>e.slice(this.prefix.length))}async values(){return(await this.entries()).map(e=>e[1])}async entries(){let e=await this.keys(),t=[];for(let n of e){let e=await this._read(n,{deserialize:!0});e!==b&&t.push([n,e])}return t}async size(){return(await this.keys()).length}async prune(e={}){let t=await this._getAdapter(),n=await t.keysRaw(this.prefix),r=0;for(let i of n){let n=await t.getRaw(i);n!==void 0&&this._inspectRaw(n,i,{removeInvalid:e.removeInvalid===!0})===`delete`&&(await t.deleteRaw(i),r+=1)}return r}async close(){this._adapter?.close&&await this._adapter.close(),this._adapter=null,this._adapterPromise=null}async _read(e,t){let n=this._fullKey(e),r=await this._getAdapter(),i=await r.getRaw(n);if(i===void 0)return b;let a=this._decodeRecord(i,n);return this._isExpired(a)?(await r.deleteRaw(n),b):t.deserialize?this._deserializeRecord(a,n):!0}_encodeRecord(e,t){let n=P(t.codec||this.codec),r=this._resolveExpiresAt(t),a;this.codecs.set(n.name,n);try{a=n.serialize(e)}catch(e){throw e instanceof i?e:new i(`Failed to serialize value.`,{cause:e})}if(typeof a!=`string`)throw new i(`Storage codec serialize() must return a string.`);return JSON.stringify({v:1,codec:n.name||`custom`,expiresAt:r,value:a})}_decodeRecord(e,t){let n;try{n=JSON.parse(e)}catch(e){throw new a(`Stored record is not valid JSON.`,{key:t,cause:e})}if(!S(n)||n.v!==1||typeof n.value!=`string`||!x(n,`expiresAt`))throw new a(`Stored record has an unsupported format.`,{key:t,details:n});return n}_deserializeRecord(e,t){let n=this.codecs.get(e.codec);if(!n)throw new i(`No codec registered for stored record codec "${e.codec}".`,{key:t});try{return n.deserialize(e.value)}catch(e){throw e instanceof i?e:new i(`Failed to deserialize value.`,{key:t,cause:e})}}_inspectRaw(e,t,n){try{let n=this._decodeRecord(e,t);return this._isExpired(n)?`delete`:`keep`}catch(e){if(n.removeInvalid)return`delete`;throw e}}_isExpired(e){return e.expiresAt!==null&&e.expiresAt<=this.clock()}_resolveExpiresAt(e){if(x(e,`expiresAt`))return e.expiresAt===null||e.expiresAt===!1?null:E(e.expiresAt,`expiresAt`);let t=x(e,`ttl`)?D(e.ttl):this.defaultTtl;return t===null?null:this.clock()+t}_fullKey(e){return`${this.prefix}${C(e)}`}async _getAdapter(){return this._adapter?this._adapter:(this._adapterPromise||(this._adapterPromise=this._selectAdapter()),this._adapter=await this._adapterPromise,this._adapter)}async _selectAdapter(){let e=O([this.driver,...this.fallback]),t=[];for(let n of e)try{let e=this._createAdapter(n);if(typeof e.isAvailable!=`function`||await e.isAvailable())return e;t.push({driver:e.name,error:`Driver reported unavailable.`})}catch(e){let r=typeof n==`string`?n:n?.name||`custom`;t.push({driver:r,error:e}),this.onDriverError?.(e,r)}throw new n(`No configured storage driver is usable.`,{details:t})}_createAdapter(e){if(I(e))return e;if(typeof e==`function`){let t=e({driverOptions:this.driverOptions,storage:this});if(!I(t))throw TypeError(`Custom storage driver must return an adapter.`);return t}if(typeof e!=`string`)throw TypeError(`Storage driver must be a string, adapter, or factory.`);let t=this.adapters[e];if(t)return this._createAdapter(t);let r=k[e];if(!r)throw new n(`Unsupported storage driver: ${e}`,{driver:e});return r(this._driverOptions(e))}_driverOptions(e){let t=this.driverOptions||{};return[`cookie`,`indexedDB`,`localStorage`,`memory`,`sessionStorage`,`shared`].some(e=>x(t,e))?{...t.shared,...t[e]}:t}async _liveKeys(){let e=await this._getAdapter(),t=await e.keysRaw(this.prefix),n=[];for(let r of t){let t=await e.getRaw(r);if(t===void 0)continue;let i=this._decodeRecord(t,r);if(this._isExpired(i)){await e.deleteRaw(r);continue}n.push(r.slice(this.prefix.length))}return n}};function M(e){return new j(e)}function N(e){if(e==null||e===!1)return[];if(typeof e==`string`||I(e)||typeof e==`function`)return[e];if(!Array.isArray(e))throw TypeError(`Storage fallback must be a driver or an array.`);return e}function P(e){if(typeof e==`string`){let t=A[e];if(!t)throw new i(`Unsupported storage codec: ${e}`);return t}if(!e||typeof e.serialize!=`function`||typeof e.deserialize!=`function`)throw TypeError(`Storage codec must provide serialize(value) and deserialize(value).`);return{name:e.name||`custom`,serialize:e.serialize.bind(e),deserialize:e.deserialize.bind(e)}}function F(e,t){let n=new Map;for(let e of Object.values(A))n.set(e.name,e);if(n.set(t.name,t),!e)return n;let r=Array.isArray(e)?e:Object.values(e);for(let e of r){let t=P(e);n.set(t.name,t)}return n}function I(e){return e&&typeof e.getRaw==`function`&&typeof e.setRaw==`function`&&typeof e.deleteRaw==`function`&&typeof e.clearRaw==`function`&&typeof e.keysRaw==`function`}e.CookieAdapter=l,e.IndexedDBAdapter=d,e.LocalStorageAdapter=g,e.MemoryAdapter=p,e.SessionStorageAdapter=_,e.Storage=j,e.StorageDataError=a,e.StorageQuotaError=r,e.StorageSerializationError=i,e.StorageUnavailableError=n,e.VanillaStorageError=t,e.WebStorageAdapter=h,e.createStorage=M,e.jsonCodec=v,e.rawStringCodec=y});
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "vanilla-create-storage",
3
+ "version": "0.2.0",
4
+ "private": false,
5
+ "description": "A small async storage abstraction for browser storage drivers.",
6
+ "keywords": [
7
+ "cookie",
8
+ "indexedDB",
9
+ "javascript",
10
+ "localStorage",
11
+ "ttl",
12
+ "vanilla"
13
+ ],
14
+ "homepage": "https://github.com/WangShai123/vanilla-storage",
15
+ "bugs": {
16
+ "url": "https://github.com/WangShai123/vanilla-storage/issues"
17
+ },
18
+ "license": "ISC",
19
+ "author": "Wang Shai <hiwangshai@gmail.com>",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/WangShai123/vanilla-storage.git"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "main": "./dist/index.mjs",
30
+ "module": "./dist/index.mjs",
31
+ "browser": "./dist/index.umd.js",
32
+ "unpkg": "./dist/index.umd.js",
33
+ "exports": {
34
+ ".": {
35
+ "browser": "./dist/index.umd.js",
36
+ "import": "./dist/index.mjs"
37
+ },
38
+ "./package.json": "./package.json"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "scripts": {
44
+ "build": "vp pack",
45
+ "dev": "vp dev",
46
+ "test": "vp test",
47
+ "check": "vp check",
48
+ "lint": "vp lint",
49
+ "prepublishOnly": "vp run build"
50
+ },
51
+ "devDependencies": {
52
+ "vite-plus": "^0.1.23"
53
+ },
54
+ "fullname": "vanilla-create-storage"
55
+ }