{"_id":"@ahmedtooper_npm/hamd-wasm","name":"@ahmedtooper_npm/hamd-wasm","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@ahmedtooper_npm/hamd-wasm","type":"module","collaborators":["Md. Ramjan Miah <ramjan@example.com>"],"description":"Unified, encrypted, type-safe browser storage — five backends (localStorage, sessionStorage, cookies, memory, IndexedDB) with AES-256-GCM, TTL, and cross-tab sync, built in Rust and compiled to WebAssembly","version":"0.1.0","license":"MIT","repository":{"type":"git","url":"git+https://github.com/AhmedTrooper/hamd-wasm.git"},"main":"hamd_wasm.js","homepage":"https://github.com/AhmedTrooper/hamd-wasm#readme","types":"hamd_wasm.d.ts","sideEffects":["./hamd_wasm.js","./snippets/*"],"keywords":["storage","localstorage","indexeddb","encryption","wasm"],"_id":"@ahmedtooper_npm/hamd-wasm@0.1.0","gitHead":"25b7b77bded9aea63103222deff7878a54a66d00","bugs":{"url":"https://github.com/AhmedTrooper/hamd-wasm/issues"},"_nodeVersion":"22.23.1","_npmVersion":"10.9.8","dist":{"integrity":"sha512-YLdfWskcdVNLk9UGlvYKBLX9xxCP9k3NUcsnR8gm/PX6z1vxQEzo7cRPenioKCtMe8A6rZlwpv9dFLqSDHEpQA==","shasum":"468f111e83039f2ff8a67be9b58062afd802d4b6","tarball":"https://registry.npmjs.org/@ahmedtooper_npm/hamd-wasm/-/hamd-wasm-0.1.0.tgz","fileCount":7,"unpackedSize":291882,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIECbVckdk4Oezc0UEOdvvvwXFJILbhkYJD4PVeKgz4BTAiEAnaCtO/CqqAwhFkjsXp84igf7aCZYNbBaBPbbd6QTpfI="}]},"_npmUser":{"name":"_ahmedtrooper_","email":"b220305006@cse.jnu.ac.bd"},"directories":{},"maintainers":[{"name":"_ahmedtrooper_","email":"b220305006@cse.jnu.ac.bd"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/hamd-wasm_0.1.0_1786630031299_0.46661229508807756"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-13T14:07:11.165Z","0.1.0":"2026-08-13T14:07:11.466Z","modified":"2026-08-13T14:07:11.663Z"},"maintainers":[{"name":"_ahmedtrooper_","email":"b220305006@cse.jnu.ac.bd"}],"description":"Unified, encrypted, type-safe browser storage — five backends (localStorage, sessionStorage, cookies, memory, IndexedDB) with AES-256-GCM, TTL, and cross-tab sync, built in Rust and compiled to WebAssembly","homepage":"https://github.com/AhmedTrooper/hamd-wasm#readme","keywords":["storage","localstorage","indexeddb","encryption","wasm"],"repository":{"type":"git","url":"git+https://github.com/AhmedTrooper/hamd-wasm.git"},"bugs":{"url":"https://github.com/AhmedTrooper/hamd-wasm/issues"},"license":"MIT","readme":"# hamd-wasm\n\n**Hamd — one API, five storages, encrypted, TTL-aware, binary-ready.** Rust → WebAssembly. Works anywhere JavaScript runs.\n\n> Pick a type, get a backend. `new Local()` = `localStorage`. `new Session()` = `sessionStorage`. `new Cookies()` = `document.cookie`. `new Memory()` = in-memory. `new IndexedDb()` = `IndexedDB` (async). All share the same methods.\n\n[![CI](https://github.com/AhmedTrooper/hamd-wasm/actions/workflows/ci.yml/badge.svg)](https://github.com/AhmedTrooper/hamd-wasm/actions) ![npm @ahmedtrooper/hamd-wasm](https://img.shields.io/npm/v/@ahmedtrooper/hamd-wasm?label=npm) ![crates.io](https://img.shields.io/crates/v/hamd-wasm) ![license MIT](https://img.shields.io/badge/license-MIT-black) ![FAANG 92%](https://img.shields.io/badge/FAANG-92%25-ready-0a7a42)\n\n```ts\nimport { Local, IndexedDb } from '@ahmedtrooper/hamd-wasm';\n\nconst store = new Local('myapp:');\nstore.set('user', { name: 'Alice', id: 101 });\nstore.get('user'); // → { name: 'Alice', id: 101 }\n\nconst db = new IndexedDb();\nawait db.setBytes('avatar', new Uint8Array([0, 255, 42]));\nawait db.getBytes('avatar'); // → Uint8Array\n```\n\n---\n\n## For non-developers — what is this?\n\n- **Browser storage** is where a website saves small data on your device (login, cart, settings). Different places exist: `localStorage` (stays), `sessionStorage` (tab-only), `cookies` (sent to server), `IndexedDB` (big files).\n- **Hamd** gives you **one simple way** to use any of them. Change `new Local()` to `new IndexedDb()` — your code stays the same.\n- **Encryption** means saved data looks like gibberish in DevTools — readable only with your 32-byte key.\n- **TTL** means “expire after 60 seconds” — good for OTPs, invites, temporary locks.\n- **Binary** means you can save images/files as `Uint8Array`, not just text.\n- **Sync** means if a user has two tabs open, a cart update in tab A appears in tab B.\n\n---\n\n## Install\n\n**JavaScript / TypeScript (npm)**\n\n```bash\nnpm install @ahmedtrooper/hamd-wasm\n# docs site: npm --prefix web install && npm --prefix web run build\n```\n\nNo Rust needed — npm ships `.wasm` + JS glue + `.d.ts`.\n\n**Rust (crates.io)**\n\n```toml\n[dependencies]\nhamd-wasm = \"0.1.0\"\n```\n\n**Build from source**\n\n```bash\ncargo fmt --all\ncargo clippy --target wasm32-unknown-unknown -- -D warnings\ncargo check --target wasm32-unknown-unknown\nwasm-pack build --target bundler --release --scope ahmedtrooper # pkg/ 188K wasm\n```\n\n---\n\n## Choose a backend\n\n| Type | Backed by | Sync / Async | Best for | Limit |\n| --- | --- | --- | --- | --- |\n| `new Local(prefix?)` | `window.localStorage` | sync | app data that survives restarts | ~5MB string (~3.6MB binary base64). Quota → purge expired retry |\n| `new Session(prefix?)` | `window.sessionStorage` | sync | tab-only data | ~5MB |\n| `new Cookies(prefix?)` | `document.cookie` | sync | server-readable tiny tokens | 4KB per cookie (`3900` guard, `SameSite=Lax`, `Secure` on https) |\n| `new Memory(prefix?)` | `HashMap` | sync | SSR, tests, fallback when `window` missing | unbounded |\n| `new IndexedDb(prefix?)` | `IndexedDB` `hamd v1 kv` | **async** (`Promise`) | files, images, large data | disk ~50% (GBs) |\n\nAll take an optional `prefix` (`hamd:` default) to isolate: `new Local('app:')` and `new Local('admin:')` never collide, `clear()` only deletes its prefix.\n\n```ts\nconst a = new Local('app:');\nconst b = new Local('admin:');\na.set('x', 1); b.set('x', 2);\na.clear(); // b still has 'x'\n```\n\n---\n\n## Complete API — every method, no omission\n\nEvery type implements the same names. `IndexedDb` returns `Promise` for storage ops; constructors, `enableEncryption`/`generateKey`, `subscribe` stay sync.\n\n### `new Type(prefix?)`\n\n```ts\nconst s1 = new Local();            // prefix \"hamd:\"\nconst s2 = new Local('myapp:');    // custom\nconst s3 = new Session(null);      // also \"hamd:\"\n```\n\n* `prefix` if `null/undefined` → `\"hamd:\"`. Use per feature: `orders:`, `auth:`.\n\n### `set(key, value, ttlMs?)` — save JSON\n\n```ts\nstore.set('user', { name: 'Alice', age: 25 });\nstore.set('count', 42);\nstore.set('otp', '123456', 60_000); // TTL 60s → envelope {__val, __exp: Date.now()+60_000}\n```\n\n* `key: string` — must satisfy **validation** (applies to every `key` param): non-empty, `≤256` bytes, no `\\0` `\\n` `\\r` → else `key must be non-empty` / `key too long: max 256 bytes` / `key contains invalid control characters`\n* `value: any` — `JSON.stringify`'d; primitives, objects, arrays all OK\n* `ttlMs?: number|null` — if given must be `finite && >0` else `ttlMs must be a positive finite number`\n* Storage is `hex(nonce||ciphertext)` if encryption enabled, else JSON or TTL-envelope JSON\n\n### `get(key)` — load JSON\n\n```ts\nstore.get('user');   // → {name:'Alice'} or null if missing/expired\nstore.get('otp');    // → value or null after 60s (entry auto-removed)\n```\n\n* Lazy-eviction: expired `has/get` deletes the raw key then returns `null`\n* Errors: `key` validation same as `set`; decryption `wrong key or corrupted data` if key mismatched\n\n### `setBytes(key, bytes, ttlMs?)` / `getBytes(key)` — save binary\n\n```ts\nconst bytes = new Uint8Array([0, 1, 255, 42]);\nstore.setBytes('avatar', bytes);           // sync backends: base64 {\"__bin\":true,\"data\":\"b64\"} + same TTL/encrypt\nstore.getBytes('avatar'); // → Uint8Array | undefined (null→undefined)\n\nconst db = new IndexedDb();\nawait db.setBytes('file', bytes, 60_000); // async, disk-backed, same envelope\nawait db.getBytes('file');                // → Uint8Array | undefined\n```\n\n* `bytes: Uint8Array` (`&[u8]` in Rust) — base64 envelope `__bin`; string backends guard `b64_len>4_800_000 → bytes too large for string storage, use IndexedDb` (covers `Local/Session` 5MB → ~3.6MB binary). `Cookies` also hits `3900` guard first.\n* `getBytes` returns `undefined` if missing/expired; throws `value is not binary data` if you call it on a `set`-saved JSON key\n\n### `remove(key)` / `clear()` — delete\n\n```ts\nstore.remove('user'); // validates key, broadcasts remove\nstore.clear();        // deletes only keys starting with this instance's prefix, broadcasts clear\n```\n\n* `clear` collects `raw_keys().filter(startsWith(prefix))` then `raw_remove` per key (IndexedDB batch single `Readwrite` txn)\n\n### `has(key)` / `keys()` / `length()` — inspect\n\n```ts\nstore.has('user'); // → boolean via !isNull(get)\nstore.keys();      // → string[] stripped of prefix  e.g. ['user','avatar']\nstore.length();    // → number counted under prefix\n```\n\n### `purgeExpired()` — proactively evict\n\n```ts\nstore.set('short', 'tmp', 40);\nawait new Promise(r => setTimeout(r, 100));\nstore.get('short'); // → null\n// or sweep all:\nstore.purgeExpired();\n```\n\n* Implemented as `for (k of stripPrefix(raw_keys)) get(k)` — triggers lazy expiry per key. `IndexedDb` async version `await`s each `get`.\n\n### `mset(entries, ttlMs?)` / `mget(keys)` — bulk\n\n```ts\nstore.mset({ a: 1, b: 2, c: 3 }, 5_000); // shared TTL\nstore.mget(['a','b','missing']); // → { a:1, b:2, missing: null } (sync) / Promise<object> (IndexedDb)\n// validation: mset keys must be strings, mget keys must be strings + validate_key each; non-string → \"mset keys must be strings\"/\"mget keys must be strings\"\n```\n\n### `enableEncryption(key)` / `generateKey()` — AES-256-GCM\n\n```ts\nconst s = new Local();\nconst key = s.generateKey(); // Uint8Array(32), also enables encryption for this instance\n\n// bring your own\ns.enableEncryption(my32Bytes); // throws \"key must be exactly 32 bytes\" if !=32\n\ns.set('secret', { ssn: '000' });  // stored as hex(nonce 12B || ciphertext+tag 16B)\ns.get('secret'); // wrong key → \"decryption failed: wrong key or corrupted data\" / \"hex decode: …\"/\"utf-8 decode: …\"\ns.setBytes('enc', new Uint8Array([1,2,3])); // also encrypted (encrypts the __bin JSON)\n\n// per-instance: enable right after new; keys zeroized on drop (ZeroizeOnDrop)\n```\n\n* Uses `aes-gcm` `Aes256Gcm` `getrandom` nonce per write; stored `hex` length `<28 → ciphertext too short`\n* Client-side only — protects at-rest DevTools view, not live XSS with key in memory\n\n### `subscribe(cb)` → `unsubscribe()` — cross-tab sync\n\n```ts\nconst s = new Local('app:');\nconst off = s.subscribe((action, key) => {\n  // action: \"set\" | \"remove\" | \"clear\",  key: string (\"\" for clear)\n  console.log(action, key);\n});\ns.set('cart', [1,2,3]); // broadcasts {action:'set', prefix:'app:', key:'cart'} filtered by prefix\noff(); // Closure::once_into_js → removes listener\n```\n\n* Channel `hamd-sync-{kind}` (`local/session/memory/cookies/indexeddb`) via `BroadcastChannel`; fallback for `local/session` on Safari via `localStorage __hamd_sync_{kind}` `storage` event with same `{action,prefix,key,ts}` and prefix filter. `unsubscribe` removes `message` or `storage` listener.\n\n### IndexedDB async notes\n\n```ts\nconst db = new IndexedDb('app:');\nawait db.set('k','v');  await db.get('k');  await db.has('k');\nawait db.keys(); await db.length(); await db.purgeExpired();\nawait db.mset({a:1});   await db.mget(['a']);\nawait db.setBytes('f', new Uint8Array([1])); await db.getBytes('f');\ndb.subscribe((a,k)=>{}); db.enableEncryption(key); db.generateKey();\n```\n\n* `IndexedDb` holds lazy `IdbDatabase` (`hamd v1 kv` store) with `cached_db`, `open_db` `onupgradeneeded`, `IDBRequest→Promise` self-cleaning (`onsuccess/onerror` cleared via `Rc<RefCell>`), batch deletes queued on single `Readwrite` txn before any `await`.\n* Design invariant: no `Mutex` held across `await` — lock→clone→drop→await (`db().await`, `raw_set` etc.)\n\n---\n\n## Errors — what you will see\n\n| Input | Error string |\n| --- | --- |\n| `key === \"\"` | `key must be non-empty` |\n| `key.length >256` | `key too long: max 256 bytes` |\n| `key` contains `\\0`/`\\n`/`\\r` | `key contains invalid control characters` |\n| `ttlMs` `NaN`, `Infinity`, `<=0` | `ttlMs must be a positive finite number` |\n| `enableEncryption` `len!=32` | `key must be exactly 32 bytes` |\n| `mset` key not string | `mset keys must be strings` |\n| `mget` key not string | `mget keys must be strings` |\n| `getBytes` on JSON value | `value is not binary data` |\n| `get` with wrong key | `decryption failed: wrong key or corrupted data` or `hex decode: …` |\n| `bytes` too big for string storage | `bytes too large for string storage, use IndexedDb` |\n| `raw_set` oversize `Cookies` | `quota exceeded after evicting expired entries` (after `purgeExpired` retry) |\n| `Cookies` no `HtmlDocument` | `no HtmlDocument` |\n\n---\n\n## Limits & quota\n\n| Backend | Cap | Handling |\n| --- | --- | --- |\n| `Local/Session` | ~5MB (string, +33% base64 → ~3.6MB binary) | `QuotaExceededError/code22` detected → `purgeExpired` then retry once |\n| `Cookies` | 4KB per cookie (`3900` guard) | `encode_uri_component`/`decode_uri_component` trim, `Secure` on `https:` |\n| `Memory` | unbounded `HashMap` | no quota, SSR-safe |\n| `IndexedDB` | disk ~50% (GBs) | async batch single txn, `QuotaExceeded` same retry |\n\n---\n\n## Installation details\n\n```bash\nnpm install @ahmedtrooper/hamd-wasm # npm @ahmedtrooper/hamd-wasm@0.1.0 87K tgz 7 files\n# Rust\n# Cargo.toml authors = [\"Md. Ramjan Miah <ramjan@example.com>\"] homepage https://github.com/AhmedTrooper/hamd-wasm#readme exclude = [\"pkg/\",\"target/\",\".github/\",\"web/\"]\n```\n\nBuilt with `package.metadata.wasm-pack.profile.release wasm-opt -Oz --enable-bulk-memory/sign-ext/mutable-globals/nontrapping` + `profile.release opt-level z lto codegen-units1 panic abort strip`. `pkg/` is gitignored (`/.gitignore` `/pkg/`).\n\n---\n\n## Architecture (source truth)\n\n```\nsrc/lib.rs       impl_storage! Local/Session/Memory/Cookies sync + IndexedDb async (prefix, validate_key, encrypt, ttl, sync, bulk, bytes)\nsrc/ops.rs       StorageOps raw_set/get/remove/keys + StorageError QuotaExceeded\nsrc/web.rs       window Storage Local/Session, quota_error\nsrc/cookie.rs    HtmlDocument.cookie encode/decode 3900 Secure\nsrc/memory.rs    HashMap\nsrc/idb.rs       open_db v1 kv, raw_set/get/raw_remove single txn, get_all_keys, request_promise (Rc<RefCell> handlers)\nsrc/crypto.rs    Aes256Gcm 12B nonce hex, 28B min, zeroize\nsrc/envelope.rs  wrap Object{__val,__exp:Date.now()+ttl} →stringify / unwrap Expired\nsrc/sync.rs      BroadcastChannel hamd-sync-{kind} + storage fallback, prefix-filtered\ntests/integration.rs 24 wasm-bindgen-test Chrome headless (TTL sleep, sync, encryption wrong-key, bytes, key validation)\nweb/             Vite 6 + Solid 1.9 docs site (supermodular routes: getting-started/storage/encryption/ttl/binary/sync/limits/api)\ndocs/            7 feature md files (single-source, mirrored here)\n```\n\n---\n\n## Development & release\n\n```bash\ncargo fmt --all\ncargo clippy --target wasm32-unknown-unknown -- -D warnings\ncargo check --target wasm32-unknown-unknown\ncargo test --target wasm32-unknown-unknown --no-run\nwasm-pack test --chrome --headless # 24 passed\nwasm-pack build --target bundler --release --scope ahmedtrooper # pkg/ 188K wasm\ncargo publish --dry-run # 25 files 99.5KiB\nwasm-pack pack # @ahmedtrooper/hamd-wasm 0.1.0 87K\nnpm --prefix web run build # 20K js/5K css\n```\n\nCI `.github/workflows/ci.yml`: `fmt/clippy/check/wasm-pack build/test` + `audit (cargo-audit)` + `coverage (cargo-llvm-cov)`. Release `.github/workflows/release.yml`: `Validate` → `cargo publish` `${{ secrets.CARGO_REGISTRY_TOKEN }}` + `wasm-pack build --scope ahmedtrooper` → `npm publish --access public` `${{ secrets.NPM_TOKEN }}` → GitHub Release on `v*` (`VERSION=$(cargo metadata…); TAG=v$VERSION; git tag $TAG; git push origin $TAG`). `v0.1.0` → `crates.io hamd-wasm 0.1.0` live, `@ahmedtrooper/hamd-wasm 0.1.0` live (unscoped `hamd-wasm` blocked `hash-wasm` similarity).\n\n## License\n\nMIT — [LICENSE](./LICENSE) © 2026 Md. Ramjan Miah\n","readmeFilename":"README.md","_rev":"1-447e00f075d060699e06c788bfa65989"}