oto-storage 0.2.0 → 0.4.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 +139 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -49,6 +49,12 @@ Oto storage uses the JavaScript Proxy API to let you interact with browser stora
|
|
|
49
49
|
|
|
50
50
|
- **Collision Protection**: Automatic key prefixing (namespacing).
|
|
51
51
|
|
|
52
|
+
- **Default Values**: Define fallback values for missing keys with deep merge support.
|
|
53
|
+
|
|
54
|
+
- **TTL / Expiration**: Set automatic expiration for stored values.
|
|
55
|
+
|
|
56
|
+
- **Custom Encryption Hooks**: Encrypt/decrypt stored values with your own sync crypto callbacks.
|
|
57
|
+
|
|
52
58
|
### 🚀 Quick Start
|
|
53
59
|
|
|
54
60
|
**_1. Define your Schema_**
|
|
@@ -239,6 +245,139 @@ import { oto, version } from "oto-storage";
|
|
|
239
245
|
console.log(`Using oto-storage v${version}`);
|
|
240
246
|
```
|
|
241
247
|
|
|
248
|
+
**Default Values**
|
|
249
|
+
|
|
250
|
+
Provide default values that are returned when keys don't exist in storage. Defaults support deep merging with stored values.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
interface AppStorage {
|
|
254
|
+
theme: "light" | "dark";
|
|
255
|
+
user: { name: string; role: string; active: boolean };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const storage = oto<AppStorage>({
|
|
259
|
+
prefix: "app-",
|
|
260
|
+
defaults: {
|
|
261
|
+
theme: "light",
|
|
262
|
+
user: { name: "Anonymous", role: "guest", active: false },
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Returns default when not set
|
|
267
|
+
console.log(storage.theme); // "light"
|
|
268
|
+
|
|
269
|
+
// Stored values override defaults
|
|
270
|
+
storage.theme = "dark";
|
|
271
|
+
console.log(storage.theme); // "dark"
|
|
272
|
+
|
|
273
|
+
// Deep merge - partial updates preserve defaults
|
|
274
|
+
storage.user = { name: "Alice" }; // Only set name
|
|
275
|
+
console.log(storage.user);
|
|
276
|
+
// { name: "Alice", role: "guest", active: false }
|
|
277
|
+
|
|
278
|
+
// Access nested properties - stored value takes precedence over defaults
|
|
279
|
+
console.log(storage.user.name); // "Alice" (stored value)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**TTL / Expiration**
|
|
283
|
+
|
|
284
|
+
Automatically expire stored values after a specified time (in milliseconds). Expired keys are automatically deleted on access.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
interface SessionStorage {
|
|
288
|
+
token: string;
|
|
289
|
+
user: { id: string; name: string };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const storage = oto<SessionStorage>({
|
|
293
|
+
prefix: "session-",
|
|
294
|
+
ttl: 3600000, // 1 hour in milliseconds
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Store value - automatically wrapped with expiration
|
|
298
|
+
storage.token = "abc123";
|
|
299
|
+
|
|
300
|
+
// Value is accessible before expiration
|
|
301
|
+
console.log(storage.token); // "abc123"
|
|
302
|
+
|
|
303
|
+
// ... 1 hour later ...
|
|
304
|
+
|
|
305
|
+
// Expired key is auto-deleted and returns undefined
|
|
306
|
+
console.log(storage.token); // undefined
|
|
307
|
+
|
|
308
|
+
// TTL works with nested objects too
|
|
309
|
+
storage.user = { id: "1", name: "Alice" };
|
|
310
|
+
storage.user.name = "Bob"; // Updates are also TTL-protected
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Encryption**
|
|
314
|
+
|
|
315
|
+
Use custom synchronous `encrypt`/`decrypt` hooks to protect stored payloads at rest.
|
|
316
|
+
Security warning: `btoa`/`atob` is encoding, not cryptographic encryption. Use `encryption.encrypt`/`encryption.decrypt` with a real cipher (for example Web Crypto AES-GCM) in production.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
interface SecureStorage {
|
|
320
|
+
token: string;
|
|
321
|
+
profile: { id: string; name: string };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const keyPrefix = "my-secret-key:";
|
|
325
|
+
const secure = oto<SecureStorage>({
|
|
326
|
+
prefix: "secure-",
|
|
327
|
+
encryption: {
|
|
328
|
+
encrypt: (plainText) => btoa(`${keyPrefix}${plainText}`),
|
|
329
|
+
decrypt: (cipherText) => {
|
|
330
|
+
const decoded = atob(cipherText);
|
|
331
|
+
if (!decoded.startsWith(keyPrefix)) {
|
|
332
|
+
throw new Error("Invalid encryption key");
|
|
333
|
+
}
|
|
334
|
+
return decoded.slice(keyPrefix.length);
|
|
335
|
+
},
|
|
336
|
+
migrate: true, // Optional: auto-wrap existing plain JSON entries on read
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
secure.token = "abc123";
|
|
341
|
+
console.log(secure.token); // "abc123"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`encryption.migrate` helps adopt encryption without a one-time migration script by upgrading plain JSON entries as they are read.
|
|
345
|
+
Reserved keys note: `__oto_encrypted` and `__oto_payload` are used by the encryption envelope. If your stored object uses both keys with the same shape, it will be treated as encrypted data by `readStoredValue`/`isEncryptedWrapper`.
|
|
346
|
+
|
|
347
|
+
Encryption + TTL work together automatically. Expired encrypted entries are deleted on access, just like non-encrypted TTL values.
|
|
348
|
+
|
|
349
|
+
Security note: this feature protects data at rest in storage, but it does not protect against active XSS (malicious runtime code can access your encryption callbacks and decrypted values).
|
|
350
|
+
|
|
351
|
+
**Combining Defaults and TTL**
|
|
352
|
+
|
|
353
|
+
Use both features together for powerful patterns like session management:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
interface AuthStorage {
|
|
357
|
+
token: string | null;
|
|
358
|
+
user: { id: string; name: string } | null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const auth = oto<AuthStorage>({
|
|
362
|
+
prefix: "auth-",
|
|
363
|
+
ttl: 3600000, // 1 hour
|
|
364
|
+
defaults: {
|
|
365
|
+
token: null,
|
|
366
|
+
user: null,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Before login - returns defaults
|
|
371
|
+
console.log(auth.token); // null
|
|
372
|
+
|
|
373
|
+
// After login
|
|
374
|
+
auth.token = "secret-token";
|
|
375
|
+
auth.user = { id: "123", name: "Alice" };
|
|
376
|
+
|
|
377
|
+
// ... after 1 hour (token expires) ...
|
|
378
|
+
console.log(auth.token); // null (back to default)
|
|
379
|
+
```
|
|
380
|
+
|
|
242
381
|
### 🛠️ Architecture Decisions
|
|
243
382
|
|
|
244
383
|
**_Why Proxy?_**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oto-storage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A lightweight, type-safe wrapper for localStorage and sessionStorage using the Proxy API.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|
|
40
|
-
"url": "https://github.com/diomari/oto-storage"
|
|
40
|
+
"url": "git+https://github.com/diomari/oto-storage.git"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"typescript": "^5.3.3",
|