{"version":3,"file":"compressed-storage.cjs","names":[],"sources":["../../src/utils/compressed-storage.ts"],"sourcesContent":["import { gunzipSync, gzipSync } from \"fflate\";\n\nimport { base64ToBytes, bytesToBase64 } from \"./base64\";\n\nimport { createJsonStorage, type JsonStorage, type StorageCodec } from \"./storage\";\n\n/**\n * Gzip-backed `localStorage`, for payloads that would otherwise eat the quota.\n *\n * Kept in its own module rather than folded into {@link storage} so that\n * importing the plain typed wrapper never drags `fflate` into the bundle.\n *\n * @tempest-limits empty-catch — every operation here is best-effort by\n * contract, exactly like {@link storage}. `localStorage` throws on quota\n * exhaustion, in Safari private mode, and when a cross-origin frame has storage\n * blocked; a caller persisting state has no recovery to run and no message to\n * show, so the write is dropped and the value stays in memory for the session.\n */\n\n/**\n * Prefix stamped on every value this module writes.\n *\n * It makes the format self-describing, which is what lets a read fall back to\n * plain JSON: a key that predates compression — or that a degraded write stored\n * uncompressed — is still readable, so turning compression on for an existing\n * key does not orphan the data already there. The character also cannot open a\n * JSON document, so the two cases can never be confused.\n */\nconst MARKER = \"~tgz1:\";\n\n/**\n * Serialize a value to a gzipped, base64 string carrying the format marker.\n *\n * Base64 costs a third more characters than the raw compressed bytes, and\n * `localStorage` bills two bytes per character on top of that. Packing the\n * bytes into UTF-16 code units directly would be denser, but lone surrogates\n * survive neither every storage implementation nor a JSON round-trip, and a\n * save that decodes to garbage is far worse than one that is bigger. Even with\n * that overhead a typical JSON document lands well under a third of its\n * uncompressed size.\n *\n * @typeParam T - The value being stored.\n * @param value - Any JSON-serializable value.\n * @returns The encoded string, ready for `localStorage`.\n */\nexport function compressToString<T>(value: T): string {\n    const json = JSON.stringify(value);\n    const bytes = gzipSync(new TextEncoder().encode(json));\n    return `${MARKER}${bytesToBase64(bytes)}`;\n}\n\n/**\n * Decode a string produced by {@link compressToString}.\n *\n * A string without the marker is parsed as plain JSON, so values written before\n * compression was enabled — or by a write that fell back after `gzipSync`\n * failed — still read back.\n *\n * @typeParam T - The expected value shape.\n * @param raw - The stored string.\n * @returns The decoded value.\n * @throws If the payload is neither valid compressed data nor valid JSON.\n */\nexport function decompressFromString<T>(raw: string): T {\n    if (!raw.startsWith(MARKER)) return JSON.parse(raw) as T;\n    const bytes = gunzipSync(base64ToBytes(raw.slice(MARKER.length)));\n    return JSON.parse(new TextDecoder().decode(bytes)) as T;\n}\n\n/**\n * Codec pair for {@link useLocalStorage}, so a compressed key gets the hook's\n * cross-tab sync and SSR guard for free.\n *\n * @example\n * const [save, setSave] = useLocalStorage(\"save\", EMPTY_SAVE, compressedStorageCodec);\n */\nexport const compressedStorageCodec: StorageCodec = {\n    serialize: compressToString,\n    deserialize: decompressFromString,\n};\n\n/**\n * Typed `localStorage` wrapper that gzips what it writes.\n *\n * {@link createJsonStorage} with {@link compressedStorageCodec}, so it is the\n * same implementation as {@link storage} and genuinely interchangeable with it —\n * `get`, `set` and `remove`, differing only in how the value is encoded. It used\n * to be a hand-written copy that had no `remove`, which made that promise false\n * for anybody who took the docstring at its word.\n *\n * When compression itself fails the value is written as plain JSON rather than\n * dropped: a slightly larger record still loads, an absent one does not. Only a\n * storage-level failure — quota, blocked storage — loses the write.\n */\nexport const compressedStorage: JsonStorage = createJsonStorage(compressedStorageCodec);\n"],"mappings":"mFA4BA,IAAM,EAAS,SAiBf,SAAgB,EAAoB,EAAkB,CAClD,IAAM,EAAO,KAAK,UAAU,CAAK,EAC3B,GAAA,EAAQ,EAAA,SAAA,CAAS,IAAI,YAAY,CAAC,CAAC,OAAO,CAAI,CAAC,EACrD,MAAO,GAAG,IAAS,EAAA,cAAc,CAAK,GAC1C,CAcA,SAAgB,EAAwB,EAAgB,CACpD,GAAI,CAAC,EAAI,WAAW,CAAM,EAAG,OAAO,KAAK,MAAM,CAAG,EAClD,IAAM,GAAA,EAAQ,EAAA,WAAA,CAAW,EAAA,cAAc,EAAI,MAAM,CAAa,CAAC,CAAC,EAChE,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC,OAAO,CAAK,CAAC,CACrD,CASA,IAAa,EAAuC,CAChD,UAAW,EACX,YAAa,CACjB,EAea,EAAiC,EAAA,kBAAkB,CAAsB"}