{"_id":"@classic-homes/offline-svelte","_rev":"4-8672568f121a4826b64da88725d5ff75","name":"@classic-homes/offline-svelte","dist-tags":{"latest":"0.1.4"},"versions":{"0.1.1":{"name":"@classic-homes/offline-svelte","version":"0.1.1","keywords":["svelte","offline","persistence","storage","cache","classic-theme"],"license":"MIT","_id":"@classic-homes/offline-svelte@0.1.1","maintainers":[{"name":"cloidolt","email":"cloidolt@classichomes.com"},{"name":"loidolt","email":"loidolt@gmail.com"}],"homepage":"https://github.com/Classic-Homes/classic-theme#readme","bugs":{"url":"https://github.com/Classic-Homes/classic-theme/issues"},"dist":{"shasum":"ccc6558ad0e049f958f8aa74d37f1d510f62e3fd","tarball":"https://registry.npmjs.org/@classic-homes/offline-svelte/-/offline-svelte-0.1.1.tgz","fileCount":22,"integrity":"sha512-S/8OtFUlNw1yHVZcaKmwPtMdTCzVDb0JejRr5pAQic6D9Zgc9UFscZl3NlYh8ym6TrrUZZ2j8WLc8sQhOcxVXA==","signatures":[{"sig":"MEUCIQDsX3kOgeQdOJTQkSudXIQ5phGl1bTeUC/T7KRgreSxTAIgU842cpzVGUPfSvg2iiMOdUm6As5Q9xBbT5t5NF9f3C4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":90439},"type":"module","types":"./dist/index.d.ts","svelte":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","svelte":"./dist/index.js","default":"./dist/index.js"},"./useFileStorage":{"types":"./dist/useFileStorage.svelte.d.ts","svelte":"./dist/useFileStorage.svelte.js","default":"./dist/useFileStorage.svelte.js"},"./useOfflineCache":{"types":"./dist/useOfflineCache.svelte.d.ts","svelte":"./dist/useOfflineCache.svelte.js","default":"./dist/useOfflineCache.svelte.js"},"./usePersistedForm":{"types":"./dist/usePersistedForm.svelte.d.ts","svelte":"./dist/usePersistedForm.svelte.js","default":"./dist/usePersistedForm.svelte.js"}},"gitHead":"c3d90bd688049e21d4e8f2569351a8e18a67171b","scripts":{"dev":"svelte-package -i src --watch","test":"vitest run","build":"svelte-package -i src","clean":"rm -rf dist","typecheck":"svelte-check --tsconfig ./tsconfig.json","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"cloidolt","email":"cloidolt@classichomes.com"},"repository":{"url":"git+https://github.com/Classic-Homes/classic-theme.git","type":"git","directory":"packages/offline-svelte"},"_npmVersion":"11.8.0","description":"Svelte bindings for offline persistence and caching","directories":{},"sideEffects":false,"_nodeVersion":"22.22.0","dependencies":{"@classic-homes/offline-core":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^3.23.0","jsdom":"^25.0.0","svelte":"^5.0.0","vitest":"^2.0.0","typescript":"^5.3.0","svelte-check":"^4.0.0","fake-indexeddb":"^6.0.0","@sveltejs/package":"^2.3.0","@vitest/coverage-v8":"^2.0.0","@testing-library/svelte":"^5.0.0","@sveltejs/vite-plugin-svelte":"^4.0.0"},"peerDependencies":{"zod":"^3.23.0","svelte":"^5.0.0"},"peerDependenciesMeta":{"zod":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/offline-svelte_0.1.1_1770154782667_0.11073766837472987","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@classic-homes/offline-svelte","version":"0.1.2","keywords":["svelte","offline","persistence","storage","cache","classic-theme"],"license":"MIT","_id":"@classic-homes/offline-svelte@0.1.2","maintainers":[{"name":"cloidolt","email":"cloidolt@classichomes.com"},{"name":"loidolt","email":"loidolt@gmail.com"}],"homepage":"https://github.com/Classic-Homes/classic-theme#readme","bugs":{"url":"https://github.com/Classic-Homes/classic-theme/issues"},"dist":{"shasum":"a60e32f672cb8fb9331f1b08d349ebbcf6539c58","tarball":"https://registry.npmjs.org/@classic-homes/offline-svelte/-/offline-svelte-0.1.2.tgz","fileCount":22,"integrity":"sha512-Yrmc51To/MIyeS3zHIgrHgbKdFsSO09/AX3EeFiU4268PJbDAaYwo+4Pe1hX3aLJIPynY0mxdYu4I9Pk6dUvAQ==","signatures":[{"sig":"MEQCIFofj8d91pEY0sk11Lud9G8EoAdc3nNNEuUayxPeE7wfAiBrKUEdAlf38nssMEm3aVxutjENvFYbFTnzHp1SJlMzMQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":90439},"type":"module","types":"./dist/index.d.ts","svelte":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","svelte":"./dist/index.js","default":"./dist/index.js"},"./useFileStorage":{"types":"./dist/useFileStorage.svelte.d.ts","svelte":"./dist/useFileStorage.svelte.js","default":"./dist/useFileStorage.svelte.js"},"./useOfflineCache":{"types":"./dist/useOfflineCache.svelte.d.ts","svelte":"./dist/useOfflineCache.svelte.js","default":"./dist/useOfflineCache.svelte.js"},"./usePersistedForm":{"types":"./dist/usePersistedForm.svelte.d.ts","svelte":"./dist/usePersistedForm.svelte.js","default":"./dist/usePersistedForm.svelte.js"}},"gitHead":"b0babfe2495175cf7fd3f778353fc5d358c35d37","scripts":{"dev":"svelte-package -i src --watch","test":"vitest run","build":"svelte-package -i src","clean":"rm -rf dist","typecheck":"svelte-check --tsconfig ./tsconfig.json","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:08f774bb-48c6-49b7-881c-f62487214d87"}},"repository":{"url":"git+https://github.com/Classic-Homes/classic-theme.git","type":"git","directory":"packages/offline-svelte"},"_npmVersion":"11.9.0","description":"Svelte bindings for offline persistence and caching","directories":{},"sideEffects":false,"_nodeVersion":"22.22.0","dependencies":{"@classic-homes/offline-core":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^3.23.0","jsdom":"^25.0.0","svelte":"^5.0.0","vitest":"^2.0.0","typescript":"^5.3.0","svelte-check":"^4.0.0","fake-indexeddb":"^6.0.0","@sveltejs/package":"^2.3.0","@vitest/coverage-v8":"^2.0.0","@testing-library/svelte":"^5.0.0","@sveltejs/vite-plugin-svelte":"^4.0.0"},"peerDependencies":{"zod":"^3.23.0","svelte":"^5.0.0"},"peerDependenciesMeta":{"zod":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/offline-svelte_0.1.2_1770311569902_0.30932457341138386","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@classic-homes/offline-svelte","version":"0.1.3","keywords":["svelte","offline","persistence","storage","cache","classic-theme"],"license":"MIT","_id":"@classic-homes/offline-svelte@0.1.3","maintainers":[{"name":"cloidolt","email":"cloidolt@classichomes.com"},{"name":"loidolt","email":"loidolt@gmail.com"}],"homepage":"https://github.com/Classic-Homes/classic-theme#readme","bugs":{"url":"https://github.com/Classic-Homes/classic-theme/issues"},"dist":{"shasum":"4bf3929591318052f30a1ec5e8bc57f667239a1e","tarball":"https://registry.npmjs.org/@classic-homes/offline-svelte/-/offline-svelte-0.1.3.tgz","fileCount":22,"integrity":"sha512-gdqUoUhebo8DUM1NLQiYcpURPuO8tNmm4QSkwrexZ1lOna21qKo+fn3pl6BA9ZSzZlUqxeLyu9UXJOSojriA1g==","signatures":[{"sig":"MEYCIQCM6etdL8SDhf61Gt6NWpBZyXb69jTyhRyEq5MiRqWhagIhANTjvy0JprEs5Od9Nv63ZsAHqreRclXZdJMN8bEcbKmA","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":90439},"type":"module","types":"./dist/index.d.ts","svelte":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","svelte":"./dist/index.js","default":"./dist/index.js"},"./useFileStorage":{"types":"./dist/useFileStorage.svelte.d.ts","svelte":"./dist/useFileStorage.svelte.js","default":"./dist/useFileStorage.svelte.js"},"./useOfflineCache":{"types":"./dist/useOfflineCache.svelte.d.ts","svelte":"./dist/useOfflineCache.svelte.js","default":"./dist/useOfflineCache.svelte.js"},"./usePersistedForm":{"types":"./dist/usePersistedForm.svelte.d.ts","svelte":"./dist/usePersistedForm.svelte.js","default":"./dist/usePersistedForm.svelte.js"}},"gitHead":"ea3ec070df7c7f1f3bdbedc461dc77c92610065a","scripts":{"dev":"svelte-package -i src --watch","test":"vitest run","build":"svelte-package -i src","clean":"rm -rf dist","typecheck":"svelte-check --tsconfig ./tsconfig.json","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:08f774bb-48c6-49b7-881c-f62487214d87"}},"repository":{"url":"git+https://github.com/Classic-Homes/classic-theme.git","type":"git","directory":"packages/offline-svelte"},"_npmVersion":"11.9.0","description":"Svelte bindings for offline persistence and caching","directories":{},"sideEffects":false,"_nodeVersion":"22.22.0","dependencies":{"@classic-homes/offline-core":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^3.23.0","jsdom":"^25.0.0","svelte":"^5.0.0","vitest":"^2.0.0","typescript":"^5.3.0","svelte-check":"^4.0.0","fake-indexeddb":"^6.0.0","@sveltejs/package":"^2.3.0","@vitest/coverage-v8":"^2.0.0","@testing-library/svelte":"^5.0.0","@sveltejs/vite-plugin-svelte":"^4.0.0"},"peerDependencies":{"zod":"^3.23.0","svelte":"^5.0.0"},"peerDependenciesMeta":{"zod":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/offline-svelte_0.1.3_1770419789820_0.5780467087728265","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@classic-homes/offline-svelte","version":"0.1.4","description":"Svelte bindings for offline persistence and caching","type":"module","svelte":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","svelte":"./dist/index.js","default":"./dist/index.js"},"./useFileStorage":{"types":"./dist/useFileStorage.svelte.d.ts","svelte":"./dist/useFileStorage.svelte.js","default":"./dist/useFileStorage.svelte.js"},"./usePersistedForm":{"types":"./dist/usePersistedForm.svelte.d.ts","svelte":"./dist/usePersistedForm.svelte.js","default":"./dist/usePersistedForm.svelte.js"},"./useOfflineCache":{"types":"./dist/useOfflineCache.svelte.d.ts","svelte":"./dist/useOfflineCache.svelte.js","default":"./dist/useOfflineCache.svelte.js"}},"sideEffects":false,"scripts":{"build":"svelte-package -i src","dev":"svelte-package -i src --watch","clean":"node ../../scripts/rm.mjs dist","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage"},"keywords":["svelte","offline","persistence","storage","cache","classic-theme"],"license":"MIT","repository":{"type":"git","url":"git+https://github.com/Classic-Homes/classic-theme.git","directory":"packages/offline-svelte"},"publishConfig":{"access":"public"},"peerDependencies":{"svelte":"^5.0.0","zod":"^3.23.0"},"peerDependenciesMeta":{"zod":{"optional":true}},"dependencies":{"@classic-homes/offline-core":"*"},"devDependencies":{"@sveltejs/package":"^2.3.0","@sveltejs/vite-plugin-svelte":"^7.0.0","@testing-library/svelte":"^5.0.0","@vitest/coverage-v8":"^4.1.10","fake-indexeddb":"^6.0.0","jsdom":"^25.0.0","svelte":"^5.0.0","svelte-check":"^4.0.0","typescript":"^5.3.0","vitest":"^4.1.10","zod":"^3.23.0"},"_id":"@classic-homes/offline-svelte@0.1.4","gitHead":"8bfbfff777da09171cf0daba0d3f0885954a3c9b","bugs":{"url":"https://github.com/Classic-Homes/classic-theme/issues"},"homepage":"https://github.com/Classic-Homes/classic-theme#readme","_nodeVersion":"22.23.1","_npmVersion":"11.5.1","dist":{"integrity":"sha512-tCCWR+O9eJqdLu1vx0aKb9GTDVlXm8IhEeb1sInDiUqc/oLCTQi0c0dwHOWPWAeWLY0KA/i7WA4z0/KXwXywpQ==","shasum":"2eb4c43ec6016e452b750ceaaa437c3163cabcae","tarball":"https://registry.npmjs.org/@classic-homes/offline-svelte/-/offline-svelte-0.1.4.tgz","fileCount":22,"unpackedSize":90844,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDPoFC7xEX3rYfhcK9VK+Y7zZFEe5Zl/3BIYuBsS3qahAIgeAI+7QL4YMx6Zrwr6EoY7NQwrJ0b+m6Wok1qLQBfP50="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:08f774bb-48c6-49b7-881c-f62487214d87"}},"directories":{},"maintainers":[{"name":"cloidolt","email":"cloidolt@classichomes.com"},{"name":"loidolt","email":"loidolt@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/offline-svelte_0.1.4_1783699774164_0.46520241106459026"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-03T21:39:42.603Z","modified":"2026-07-10T16:09:34.495Z","0.1.1":"2026-02-03T21:39:42.844Z","0.1.2":"2026-02-05T17:12:50.051Z","0.1.3":"2026-02-06T23:16:30.005Z","0.1.4":"2026-07-10T16:09:34.355Z"},"bugs":{"url":"https://github.com/Classic-Homes/classic-theme/issues"},"license":"MIT","homepage":"https://github.com/Classic-Homes/classic-theme#readme","keywords":["svelte","offline","persistence","storage","cache","classic-theme"],"repository":{"type":"git","url":"git+https://github.com/Classic-Homes/classic-theme.git","directory":"packages/offline-svelte"},"description":"Svelte bindings for offline persistence and caching","maintainers":[{"name":"cloidolt","email":"cloidolt@classichomes.com"},{"name":"loidolt","email":"loidolt@gmail.com"}],"readme":"# @classic-homes/offline-svelte\n\nSvelte 5 composables for offline persistence and caching. Provides reactive file storage, form draft persistence, and data caching using Svelte's runes.\n\n## Features\n\n- **useFileStorage**: Reactive file storage with validation and previews\n- **usePersistedForm**: Automatic form draft persistence\n- **useOfflineCache**: Data caching with TTL and stale-while-revalidate\n- **Svelte 5 Runes**: Built with `$state` and `$effect` for optimal reactivity\n- **TypeScript**: Full type safety with generics\n\n## Installation\n\n```bash\nnpm install @classic-homes/offline-svelte @classic-homes/offline-core\n```\n\n## Quick Start\n\n```svelte\n<script lang=\"ts\">\n  import { onMount } from 'svelte';\n  import { initOfflineStorage } from '@classic-homes/offline-core';\n  import { useFileStorage, usePersistedForm, useOfflineCache } from '@classic-homes/offline-svelte';\n\n  // Initialize storage at app startup (e.g., in +layout.svelte)\n  onMount(async () => {\n    await initOfflineStorage();\n  });\n</script>\n```\n\n## Composables\n\n### useFileStorage\n\nManage file attachments with validation, preview generation, and persistence.\n\n```svelte\n<script lang=\"ts\">\n  import { useFileStorage } from '@classic-homes/offline-svelte';\n\n  const documents = useFileStorage({\n    formKey: 'contact-form',\n    fieldName: 'documents',\n    maxFiles: 5,\n    maxSizeBytes: 10 * 1024 * 1024, // 10MB\n    acceptedTypes: ['application/pdf', 'image/*'],\n    onValidationError: (errors) => {\n      errors.forEach((e) => toast.error(e.message));\n    },\n  });\n\n  async function handleDrop(files: File[]) {\n    await documents.addFiles(files);\n  }\n</script>\n\n{#if documents.isLoading}\n  <Spinner />\n{:else}\n  <ul>\n    {#each documents.files as file (file.id)}\n      <li>\n        {#if file.previewUrl}\n          <img src={file.previewUrl} alt={file.filename} />\n        {/if}\n        <span>{file.filename}</span>\n        <button onclick={() => documents.removeFile(file.id)}>Remove</button>\n      </li>\n    {/each}\n  </ul>\n{/if}\n\n<DropZone ondrop={handleDrop} />\n```\n\n#### Options\n\n| Option              | Type       | Description                                      |\n| ------------------- | ---------- | ------------------------------------------------ |\n| `formKey`           | `string`   | Form identifier                                  |\n| `fieldName`         | `string`   | Field name within the form                       |\n| `maxFiles`          | `number`   | Maximum number of files                          |\n| `maxSizeBytes`      | `number`   | Maximum file size in bytes                       |\n| `acceptedTypes`     | `string[]` | Accepted MIME types (supports wildcards)         |\n| `expirationMs`      | `number`   | File expiration time                             |\n| `generatePreviews`  | `boolean`  | Generate preview URLs for images (default: true) |\n| `onValidationError` | `function` | Callback for validation errors                   |\n\n#### Return Value\n\n| Property            | Type              | Description                      |\n| ------------------- | ----------------- | -------------------------------- |\n| `files`             | `PersistedFile[]` | Current files (reactive)         |\n| `fileIds`           | `string[]`        | File IDs for form submission     |\n| `isLoading`         | `boolean`         | Loading state                    |\n| `error`             | `Error \\| null`   | Current error                    |\n| `addFiles`          | `function`        | Add files (validates and stores) |\n| `removeFile`        | `function`        | Remove a file by ID              |\n| `clearFiles`        | `function`        | Remove all files                 |\n| `reload`            | `function`        | Reload from storage              |\n| `getFilesForSubmit` | `function`        | Get File objects for FormData    |\n\n### usePersistedForm\n\nAutomatically persist form state across page reloads.\n\n```svelte\n<script lang=\"ts\">\n  import { usePersistedForm } from '@classic-homes/offline-svelte';\n  import { z } from 'zod';\n\n  const schema = z.object({\n    name: z.string().min(1),\n    email: z.string().email(),\n    message: z.string(),\n  });\n\n  const form = usePersistedForm({\n    formKey: 'contact-form',\n    defaultValues: { name: '', email: '', message: '' },\n    schema,\n    debounceMs: 500,\n    excludeFields: ['password'],\n  });\n\n  async function handleSubmit() {\n    if (!form.isValid) return;\n    await submitForm(form.values);\n    await form.clearDraft();\n  }\n</script>\n\n<form onsubmit|preventDefault={handleSubmit}>\n  {#if form.hasDraft}\n    <button type=\"button\" onclick={form.restoreDraft}> Restore draft </button>\n  {/if}\n\n  <input\n    name=\"name\"\n    value={form.values.name}\n    oninput={(e) => form.setValue('name', e.currentTarget.value)}\n  />\n  {#if form.errors.name}\n    <span class=\"error\">{form.errors.name}</span>\n  {/if}\n\n  <input\n    name=\"email\"\n    value={form.values.email}\n    oninput={(e) => form.setValue('email', e.currentTarget.value)}\n  />\n  {#if form.errors.email}\n    <span class=\"error\">{form.errors.email}</span>\n  {/if}\n\n  <textarea\n    name=\"message\"\n    value={form.values.message}\n    oninput={(e) => form.setValue('message', e.currentTarget.value)}\n  ></textarea>\n\n  <button type=\"submit\" disabled={!form.isValid || form.isSubmitting}> Submit </button>\n</form>\n```\n\n#### Options\n\n| Option          | Type        | Description                                |\n| --------------- | ----------- | ------------------------------------------ |\n| `formKey`       | `string`    | Unique form identifier                     |\n| `defaultValues` | `T`         | Default form values                        |\n| `schema`        | `ZodSchema` | Optional Zod validation schema             |\n| `debounceMs`    | `number`    | Debounce time for auto-save (default: 500) |\n| `excludeFields` | `string[]`  | Fields to exclude from persistence         |\n| `expirationMs`  | `number`    | Draft expiration time                      |\n| `autoRestore`   | `boolean`   | Auto-restore on mount (default: false)     |\n\n#### Return Value\n\n| Property       | Type             | Description                      |\n| -------------- | ---------------- | -------------------------------- |\n| `values`       | `T`              | Current form values (reactive)   |\n| `errors`       | `FieldErrors<T>` | Validation errors by field       |\n| `isValid`      | `boolean`        | Whether form passes validation   |\n| `isDirty`      | `boolean`        | Whether form has unsaved changes |\n| `hasDraft`     | `boolean`        | Whether a draft exists           |\n| `draftInfo`    | `object`         | Draft metadata                   |\n| `setValue`     | `function`       | Set a field value                |\n| `setValues`    | `function`       | Set multiple values              |\n| `reset`        | `function`       | Reset to default values          |\n| `saveDraft`    | `function`       | Manually save draft              |\n| `restoreDraft` | `function`       | Restore from draft               |\n| `clearDraft`   | `function`       | Delete the draft                 |\n\n### useOfflineCache\n\nCache data with TTL and stale-while-revalidate support.\n\n```svelte\n<script lang=\"ts\">\n  import { useOfflineCache } from '@classic-homes/offline-svelte';\n\n  interface User {\n    id: string;\n    name: string;\n    email: string;\n  }\n\n  let { userId } = $props<{ userId: string }>();\n\n  const userCache = useOfflineCache<User>({\n    key: `user:${userId}`,\n    ttl: 5 * 60 * 1000, // 5 minutes\n    tags: ['user'],\n    staleWhileRevalidate: true,\n    fetcher: () => fetchUser(userId), // Auto-fetch on mount\n  });\n\n  // Or manually fetch\n  $effect(() => {\n    userCache.get(() => fetchUser(userId));\n  });\n</script>\n\n{#if userCache.isLoading}\n  <Spinner />\n{:else if userCache.error}\n  <Error error={userCache.error} />\n{:else if userCache.data}\n  <div>\n    <h1>{userCache.data.name}</h1>\n    {#if userCache.isStale}\n      <span>Updating...</span>\n    {/if}\n    <button onclick={() => userCache.refresh(() => fetchUser(userId))}> Refresh </button>\n  </div>\n{/if}\n```\n\n#### Options\n\n| Option                 | Type       | Description                           |\n| ---------------------- | ---------- | ------------------------------------- |\n| `key`                  | `string`   | Cache key                             |\n| `ttl`                  | `number`   | Time to live in milliseconds          |\n| `tags`                 | `string[]` | Tags for bulk invalidation            |\n| `staleWhileRevalidate` | `boolean`  | Return stale data while fetching      |\n| `staleTime`            | `number`   | How long stale data is acceptable     |\n| `fetcher`              | `function` | Auto-fetch function on initialization |\n\n#### Return Value\n\n| Property       | Type             | Description            |\n| -------------- | ---------------- | ---------------------- |\n| `data`         | `T \\| null`      | Cached data (reactive) |\n| `isLoading`    | `boolean`        | Loading state          |\n| `isStale`      | `boolean`        | Whether data is stale  |\n| `error`        | `Error \\| null`  | Current error          |\n| `ttlRemaining` | `number \\| null` | Time until expiration  |\n| `get`          | `function`       | Get cached or fetch    |\n| `set`          | `function`       | Set cache value        |\n| `invalidate`   | `function`       | Invalidate this key    |\n| `refresh`      | `function`       | Force re-fetch         |\n| `has`          | `function`       | Check if cached        |\n\n### Utility Functions\n\n```svelte\n<script lang=\"ts\">\n  import { invalidateCacheByTag, invalidateAllCache } from '@classic-homes/offline-svelte';\n\n  async function handleLogout() {\n    // Invalidate all user-related cache entries\n    await invalidateCacheByTag('user');\n  }\n\n  async function clearAllCache() {\n    await invalidateAllCache();\n  }\n</script>\n```\n\n## Subpath Exports\n\nImport individual composables for better tree-shaking:\n\n```typescript\n// Import specific composable\nimport { useFileStorage } from '@classic-homes/offline-svelte/useFileStorage';\nimport { usePersistedForm } from '@classic-homes/offline-svelte/usePersistedForm';\nimport { useOfflineCache } from '@classic-homes/offline-svelte/useOfflineCache';\n```\n\n## TypeScript\n\nAll composables are fully typed with generics:\n\n```svelte\n<script lang=\"ts\">\n  interface User {\n    id: string;\n    name: string;\n    email: string;\n  }\n\n  const userCache = useOfflineCache<User>({ key: 'user' });\n  // userCache.data is User | null\n\n  const form = usePersistedForm<User>({\n    formKey: 'user',\n    defaultValues: { id: '', name: '', email: '' },\n  });\n  // form.values is User\n  // form.setValue('name', value) - 'name' is typed\n</script>\n```\n\n## Svelte 5 Runes\n\nThe composables use Svelte 5's runes for reactivity:\n\n- `$state` for reactive values\n- `$effect` for side effects and cleanup\n- Getter functions for derived values\n\nThis means the returned properties are reactive without needing stores:\n\n```svelte\n<script lang=\"ts\">\n  const cache = useOfflineCache({ key: 'data' });\n\n  // cache.data, cache.isLoading, etc. are reactive\n  // No need for $: reactive statements\n</script>\n\n<!-- Automatically updates when data changes -->\n{#if cache.data}\n  <pre>{JSON.stringify(cache.data, null, 2)}</pre>\n{/if}\n```\n\n## Testing\n\n```bash\nnpm test           # Run tests\nnpm run test:watch # Watch mode\nnpm run test:coverage # With coverage\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}