{"_id":"@demystify/play-packs","name":"@demystify/play-packs","dist-tags":{"latest":"0.2.0"},"versions":{"0.2.0":{"name":"@demystify/play-packs","version":"0.2.0","license":"Apache-2.0","homepage":"https://tools.demystifysystem.com/play","bugs":{"url":"https://github.com/demystify-systems/ds-tools-apis/issues"},"repository":{"type":"git","url":"git+https://github.com/demystify-systems/ds-tools-apis.git","directory":"packages/play-packs"},"author":{"name":"Demystify Systems"},"publishConfig":{"access":"public"},"private":false,"description":"Demystify Play content packs — SRI-verified same-origin download, IndexedDB store, quota guard and LRU eviction.","type":"module","sideEffects":false,"keywords":["demystify","play","offline","indexeddb","content-packs"],"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"devDependencies":{"@types/node":"^22.10.0","typescript":"^5.9.0","vitest":"^4.0.0"},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test":"vitest run"},"_id":"@demystify/play-packs@0.2.0","_integrity":"sha512-Z518ZY9EeXshsuT5eqJEypqO4FrD9tO6FTOa3FBjEw1zE3niFmwSTq9jHcGuPQsJ6PPNE1+FHPwGHOvMQxIJ8w==","_resolved":"/tmp/5bb262552264ae797de2d5d80b01be78/demystify-play-packs-0.2.0.tgz","_from":"file:demystify-play-packs-0.2.0.tgz","_nodeVersion":"22.23.1","_npmVersion":"10.9.8","dist":{"integrity":"sha512-Z518ZY9EeXshsuT5eqJEypqO4FrD9tO6FTOa3FBjEw1zE3niFmwSTq9jHcGuPQsJ6PPNE1+FHPwGHOvMQxIJ8w==","shasum":"d9acae15d0dca1dbe59a45aa5054de40cdecc25e","tarball":"https://registry.npmjs.org/@demystify/play-packs/-/play-packs-0.2.0.tgz","fileCount":15,"unpackedSize":43900,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIGxTTcM9JQM0mP0fIM8ar4XALO9m+CWJDxaMuwSdSBCcAiEArZISzV4Nq53AfO1/pUKXsytyl33/wY/h+vHC8M8vHDE="}]},"_npmUser":{"name":"demystifier09","email":"me@demystifysystem.com"},"directories":{},"maintainers":[{"name":"demystifier09","email":"me@demystifysystem.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/play-packs_0.2.0_1786215922729_0.5268608468680562"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-08T19:05:22.519Z","0.2.0":"2026-08-08T19:05:22.866Z","modified":"2026-08-08T19:05:23.137Z"},"maintainers":[{"name":"demystifier09","email":"me@demystifysystem.com"}],"description":"Demystify Play content packs — SRI-verified same-origin download, IndexedDB store, quota guard and LRU eviction.","homepage":"https://tools.demystifysystem.com/play","keywords":["demystify","play","offline","indexeddb","content-packs"],"repository":{"type":"git","url":"git+https://github.com/demystify-systems/ds-tools-apis.git","directory":"packages/play-packs"},"author":{"name":"Demystify Systems"},"bugs":{"url":"https://github.com/demystify-systems/ds-tools-apis/issues"},"license":"Apache-2.0","readme":"# @demystify/play-packs\n\nContent packs for [Demystify Play](https://tools.demystifysystem.com/play): SRI-verified\nsame-origin download, IndexedDB storage, a quota guard and LRU eviction.\n\n```bash\nnpm install @demystify/play-packs\n```\n\n## The constraint this is shaped around\n\n**Capacity is not the problem. Eviction is.** A modern browser offers roughly 60% of total disk per\norigin, which on any real phone is tens of gigabytes — far more than a whole game catalogue. What\nactually goes wrong is the browser reclaiming it: IndexedDB, Cache Storage and OPFS share one bucket\nand are evicted **all-or-nothing together**, so a single eviction takes every pack at once.\n\nEverything here follows from that:\n\n- **1.5× headroom** before a write, not 1.0×. The store needs room for the incoming blob *and* the\n  existing data while it writes, and filling the bucket to the brim is what triggers the eviction.\n- **The budget is checked before the download starts**, so a user on a full device is told up front\n  rather than after waiting for a transfer that cannot be stored.\n- **Reclaimed space is measured, not assumed.** \"12 MB freed\" that does not move the number is the\n  kind of small dishonesty that makes an entire offline claim untrustworthy.\n- **`persist()` is requested after a real interaction**, never on load. It does not increase quota —\n  it only exempts the origin from eviction — and an unprompted permission is a permission most people\n  decline.\n\n## Usage\n\n```ts\nimport { installPack, getPack, storageState, canInstall, formatBytes } from \"@demystify/play-packs\";\n\nconst state = await storageState();\nconst check = canInstall(state, pack.sizeBytes);\nif (!check.ok) return show(check.reason);        // actionable, before any transfer\n\nconst { pack, evicted } = await installPack(\n  { id, version, sizeBytes, integrity },          // integrity comes from the registry\n  `/play/packs/${id}.${version}.json`,\n);\n```\n\nThe download uses `fetch(url, { integrity })`, so **the browser** verifies the SHA-256 and rejects a\nmismatch before a byte reaches application code. Hashing it ourselves would mean buffering the whole\npack, calling `crypto.subtle.digest` (which cannot stream) and comparing — more code, more memory,\nand a check that runs after the bytes are already in the heap.\n\nPacks are same-origin, and not incidentally: an opaque cross-origin response inflates the reported\nstorage usage by ~7 MB each, which would make every quota calculation here meaningless.\n\n## Licence\n\nApache-2.0 · [Demystify Systems](https://demystifysystem.com)\n","readmeFilename":"README.md","_rev":"1-e56e0d0ba77d8b040f018159a3b129ef"}