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.
Files changed (2) hide show
  1. package/README.md +139 -0
  2. 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.2.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",