@friendofsvelte/state 0.0.4 → 0.0.6

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.
package/README.md CHANGED
@@ -19,73 +19,65 @@ npm install @friendofsvelte/state
19
19
 
20
20
  ## Quick Start
21
21
 
22
- 1. Define your types in your `app.d.ts`:
22
+ 1. Define your state using `PersistentState`:
23
23
 
24
24
  ```typescript
25
- declare global {
26
- interface PodTypeRegistry {
27
- layout: {
28
- bg: string;
29
- };
30
- userSettings: {
31
- theme: 'light' | 'dark';
32
- fontSize: number;
33
- };
34
- }
35
- }
25
+ // new.svelte.ts / js
26
+ import { PersistentState } from '@friendofsvelte/state';
36
27
 
37
- export {};
28
+ export const box = new PersistentState('box', {
29
+ color: '#ff3e00',
30
+ dimensions: [100, 100]
31
+ }, 'sessionStorage');
38
32
  ```
39
33
 
40
34
  2. Use in your components:
41
35
 
42
36
  ```svelte
43
37
  <script lang="ts">
44
- import { pod } from '@friendofsvelte/state';
45
-
46
- // Initialize with value
47
- let app = pod('layout', 'localStorage', {
48
- bg: 'lightblue'
49
- });
50
-
51
- // Or use existing value
52
- let settings = pod('userSettings');
38
+ import { box } from '$lib/new.svelte';
39
+
40
+ const listColors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink', 'brown'];
41
+
42
+ function switchNextColor() {
43
+ const currentIndex = listColors.indexOf(box.current.color);
44
+ const nextIndex = currentIndex + 1;
45
+ if (nextIndex >= listColors.length) {
46
+ box.current.color = listColors[0];
47
+ } else {
48
+ box.current.color = listColors[nextIndex];
49
+ }
50
+ }
53
51
  </script>
54
52
 
55
- <div style="background-color: {app.bg}">
56
- <!-- Your content -->
53
+ <div
54
+ style="background-color: {box.current.color}; width: 100px; height: 100px; color: gray; text-align: center;"
55
+ class="m-2 rounded-2xl"
56
+ >
57
+ {box.current.color}
57
58
  </div>
59
+
60
+ <button onclick={switchNextColor} class="bg-gray-700 m-2 px-3 rounded-2xl text-gray-200">
61
+ Change color
62
+ </button>
58
63
  ```
59
64
 
60
65
  ## API Reference
61
66
 
62
- ### `pod<K>(key: K, storage?: StorageType, context?: GetTypeFromRegistry<K>)`
67
+ ### `PersistentState<T>(key: string, initial?: T, storageType: StorageType = 'localStorage')`
63
68
 
64
69
  Creates or retrieves a persistent state container.
65
70
 
66
71
  Parameters:
67
72
  - `key`: Unique identifier for the state container
68
- - `storage`: (Optional) Storage type - 'localStorage' or 'sessionStorage' (default: 'localStorage')
69
- - `context`: (Optional) Initial state value
73
+ - `initial`: (Optional) Initial state value
74
+ - `storageType`: (Optional) Storage type - 'localStorage' or 'sessionStorage' (default: 'localStorage')
70
75
 
71
76
  Returns:
72
- - A reactive state object of type `GetTypeFromRegistry<K>`
73
-
74
- ## Type Safety
77
+ - A reactive state object of type `T`
75
78
 
76
- Pod State provides complete type safety through TypeScript. The global `PodTypeRegistry` interface allows you to define types for all your state containers in one place:
79
+ > Inspired by: Rich-Harris' [local-storage-test](https://github.com/Rich-Harris/local-storage-test/blob/main/src/lib/storage.svelte.ts)
77
80
 
78
- ```typescript
79
- interface PodTypeRegistry {
80
- layout: {
81
- bg: string;
82
- };
83
- userSettings: {
84
- theme: 'light' | 'dark';
85
- fontSize: number;
86
- };
87
- }
88
- ```
89
81
 
90
82
  ## Examples
91
83
 
@@ -93,47 +85,33 @@ interface PodTypeRegistry {
93
85
 
94
86
  ```svelte
95
87
  <script lang="ts">
96
- import { pod } from '@friendofsvelte/state';
97
-
98
- let app = pod('layout', 'localStorage', {
99
- bg: 'lightblue'
100
- });
101
- </script>
102
-
103
- <button onclick={() => app.bg = 'lightgreen'}>
104
- Change Background
105
- </button>
106
- ```
88
+ import { PersistentState } from '@friendofsvelte/state';
107
89
 
108
- ### Shared State
90
+ const box = new PersistentState('box', {
91
+ color: '#ff3e00',
92
+ dimensions: [100, 100]
93
+ }, 'sessionStorage');
109
94
 
110
- ```svelte
111
- <!-- ComponentA.svelte -->
112
- <script>
113
- import { pod } from '@friendofsvelte/state';
114
- let settings = pod('userSettings');
115
- </script>
116
-
117
- <!-- ComponentB.svelte -->
118
- <script>
119
- import { pod } from '@friendofsvelte/state';
120
- let settings = pod('userSettings');
121
- // Will automatically sync with ComponentA
95
+ function switchNextColor() {
96
+ const colors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink', 'brown'];
97
+ const currentIndex = colors.indexOf(box.current.color);
98
+ box.current.color = colors[(currentIndex + 1) % colors.length];
99
+ }
122
100
  </script>
123
- ```
124
-
125
- ## Testing
126
101
 
127
- Pod State includes a test suite to ensure reliability. Run tests with:
102
+ <div style="background-color: {box.current.color}; width: 100px; height: 100px; color: gray; text-align: center;" class="m-2 rounded-2xl">
103
+ {box.current.color}
104
+ </div>
128
105
 
129
- ```bash
130
- npm test
106
+ <button onclick={switchNextColor} class="bg-gray-700 m-2 px-3 rounded-2xl text-gray-200">
107
+ Change color
108
+ </button>
131
109
  ```
132
110
 
133
111
  ## Contributing
134
112
 
135
- Contributions are welcome! Please feel free to submit a Pull Request.
113
+ Contributions are welcome! Please feel free to submit a [Pull Request](https://github.com/friendofsvelte/state/pulls).
136
114
 
137
115
  ## License
138
116
 
139
- MIT License - see LICENSE file for details
117
+ MIT License - see LICENSE file for details
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { pod } from './state.svelte';
1
+ export { PersistentState } from './storage.svelte.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Reexport your entry components here
2
- export { pod } from './state.svelte';
2
+ export { PersistentState } from './storage.svelte.js';
@@ -0,0 +1,5 @@
1
+ import { PersistentState } from "./storage.svelte.js";
2
+ export declare const box: PersistentState<{
3
+ color: string;
4
+ dimensions: number[];
5
+ }>;
@@ -0,0 +1,5 @@
1
+ import { PersistentState } from "./storage.svelte.js";
2
+ export const box = new PersistentState('box', {
3
+ color: '#ff3e00',
4
+ dimensions: [100, 100]
5
+ }, 'sessionStorage');
@@ -0,0 +1,7 @@
1
+ export type StorageType = 'localStorage' | 'sessionStorage';
2
+ export declare class PersistentState<T> {
3
+ #private;
4
+ constructor(key: string, initial?: T, storageType?: StorageType);
5
+ get current(): any;
6
+ set current(value: any);
7
+ }
@@ -0,0 +1,80 @@
1
+ // See our inspiration: https://github.com/Rich-Harris/local-storage-test/blob/main/src/lib/storage.svelte.ts
2
+ import { tick } from 'svelte';
3
+ export class PersistentState {
4
+ #key;
5
+ #version = $state(0);
6
+ #listeners = 0;
7
+ #value;
8
+ #storage;
9
+ #handler = (e) => {
10
+ if (e.storageArea !== this.#storage)
11
+ return;
12
+ if (e.key !== this.#key)
13
+ return;
14
+ this.#version += 1;
15
+ };
16
+ constructor(key, initial, storageType = 'localStorage') {
17
+ this.#key = key;
18
+ this.#value = initial;
19
+ this.#storage = storageType === 'localStorage' ? localStorage : sessionStorage;
20
+ if (typeof this.#storage !== 'undefined') {
21
+ if (this.#storage.getItem(key) === null) {
22
+ this.#storage.setItem(key, JSON.stringify(initial));
23
+ }
24
+ }
25
+ }
26
+ get current() {
27
+ this.#version;
28
+ const root = typeof this.#storage !== 'undefined'
29
+ ? JSON.parse(this.#storage.getItem(this.#key))
30
+ : this.#value;
31
+ const proxies = new WeakMap();
32
+ const proxy = (value) => {
33
+ if (typeof value !== 'object' || value === null) {
34
+ return value;
35
+ }
36
+ let p = proxies.get(value);
37
+ if (!p) {
38
+ p = new Proxy(value, {
39
+ get: (target, property) => {
40
+ this.#version;
41
+ return proxy(Reflect.get(target, property));
42
+ },
43
+ set: (target, property, value) => {
44
+ this.#version += 1;
45
+ Reflect.set(target, property, value);
46
+ if (typeof this.#storage !== 'undefined') {
47
+ this.#storage.setItem(this.#key, JSON.stringify(root));
48
+ }
49
+ return true;
50
+ }
51
+ });
52
+ proxies.set(value, p);
53
+ }
54
+ return p;
55
+ };
56
+ if ($effect.tracking()) {
57
+ $effect(() => {
58
+ if (this.#listeners === 0) {
59
+ window.addEventListener('storage', this.#handler);
60
+ }
61
+ this.#listeners += 1;
62
+ return () => {
63
+ tick().then(() => {
64
+ this.#listeners -= 1;
65
+ if (this.#listeners === 0) {
66
+ window.removeEventListener('storage', this.#handler);
67
+ }
68
+ });
69
+ };
70
+ });
71
+ }
72
+ return proxy(root);
73
+ }
74
+ set current(value) {
75
+ if (typeof this.#storage !== 'undefined') {
76
+ this.#storage.setItem(this.#key, JSON.stringify(value));
77
+ }
78
+ this.#version += 1;
79
+ }
80
+ }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@friendofsvelte/state",
3
- "version": "0.0.4",
3
+ "description": "Persistent Svelte 5 State, localStorage & sessionStorage",
4
+ "version": "0.0.6",
4
5
  "scripts": {
5
6
  "dev": "vite dev",
6
7
  "build": "vite build && npm run prepack",
@@ -60,5 +61,9 @@
60
61
  "typescript-eslint": "^8.20.0",
61
62
  "vite": "^6.0.0",
62
63
  "vitest": "^3.0.0"
64
+ },
65
+ "repository": {
66
+ "type": "git",
67
+ "url": "https://github.com/friendofsvelte/state.git"
63
68
  }
64
69
  }
@@ -1,18 +0,0 @@
1
- type StorageType = 'localStorage' | 'sessionStorage';
2
- type TypeRegistry = {
3
- [K in PropertyKey]: unknown;
4
- };
5
- declare global {
6
- interface PodTypeRegistry extends TypeRegistry {
7
- }
8
- }
9
- type GetTypeFromRegistry<K extends keyof PodTypeRegistry> = PodTypeRegistry[K] extends never ? unknown : PodTypeRegistry[K];
10
- /**
11
- * Get a persistent state from storage, or initialize with an optional context.
12
- * @param key - The key to store the state.
13
- * @param storage - The storage type to use.
14
- * @param context - The initial state or override.
15
- * @param override - Whether to override the stored value with the provided context.
16
- */
17
- export declare function pod<K extends keyof PodTypeRegistry>(key: K, storage: StorageType, context?: GetTypeFromRegistry<K>, override?: boolean): GetTypeFromRegistry<K>;
18
- export {};
@@ -1,47 +0,0 @@
1
- import { getContext, setContext, untrack } from 'svelte';
2
- const EMPTY = Symbol('___empty____');
3
- function makeContextKey(key, storage) {
4
- return `${String(key)}__${storage}__pod`;
5
- }
6
- function track(key, storage, context, override = false) {
7
- const contextKey = makeContextKey(key, storage);
8
- if (context === EMPTY) {
9
- return getContext(contextKey);
10
- }
11
- let state = $state(context);
12
- if (typeof window !== 'undefined') {
13
- const storedValue = untrack(() => window[storage].getItem(String(key)));
14
- if (storedValue && !override) {
15
- try {
16
- state = JSON.parse(storedValue);
17
- }
18
- catch {
19
- state = {};
20
- }
21
- }
22
- else if (override || !storedValue) {
23
- state = context ?? {};
24
- const json = JSON.stringify($state.snapshot(state));
25
- window[storage].setItem(String(key), json);
26
- }
27
- if (context !== EMPTY) {
28
- $effect.pre(() => {
29
- const json = JSON.stringify($state.snapshot(state));
30
- window[storage].setItem(String(key), json);
31
- });
32
- }
33
- }
34
- return setContext(contextKey, state);
35
- }
36
- /**
37
- * Get a persistent state from storage, or initialize with an optional context.
38
- * @param key - The key to store the state.
39
- * @param storage - The storage type to use.
40
- * @param context - The initial state or override.
41
- * @param override - Whether to override the stored value with the provided context.
42
- */
43
- export function pod(key, storage,
44
- // @ts-expect-error: To allow the context to be optional
45
- context = EMPTY, override = false) {
46
- return track(key, storage, context, override);
47
- }