1
- import { addEventListener } from "$lib/internal/utils/event.js" ;
1
+ import { on } from "svelte/events" ;
2
+ import { createSubscriber } from "svelte/reactivity" ;
2
3
3
4
type Serializer < T > = {
4
5
serialize : ( value : T ) => string ;
5
6
deserialize : ( value : string ) => T ;
6
7
} ;
7
8
8
- type GetValueFromStorageResult < T > =
9
- | {
10
- found : true ;
11
- value : T ;
12
- }
13
- | {
14
- found : false ;
15
- value : null ;
16
- } ;
17
- function getValueFromStorage < T > ( {
18
- key,
19
- storage,
20
- serializer,
21
- } : {
22
- key : string ;
23
- storage : Storage | null ;
24
- serializer : Serializer < T > ;
25
- } ) : GetValueFromStorageResult < T > {
26
- if ( ! storage ) return { found : false , value : null } ;
27
-
28
- const value = storage . getItem ( key ) ;
29
-
30
- if ( value === null ) return { found : false , value : null } ;
31
-
32
- try {
33
- return {
34
- found : true ,
35
- value : serializer . deserialize ( value ) ,
36
- } ;
37
- } catch ( e ) {
38
- console . error ( `Error when parsing ${ value } from persisted store "${ key } "` , e ) ;
39
- return {
40
- found : false ,
41
- value : null ,
42
- } ;
43
- }
44
- }
45
-
46
- function setValueToStorage < T > ( {
47
- key,
48
- value,
49
- storage,
50
- serializer,
51
- } : {
52
- key : string ;
53
- value : T ;
54
- storage : Storage | null ;
55
- serializer : Serializer < T > ;
56
- } ) {
57
- if ( ! storage ) return ;
58
-
59
- try {
60
- storage . setItem ( key , serializer . serialize ( value ) ) ;
61
- } catch ( e ) {
62
- console . error ( `Error when writing value from persisted store "${ key } " to ${ storage } ` , e ) ;
63
- }
64
- }
65
-
66
9
type StorageType = "local" | "session" ;
67
10
68
- function getStorage ( storageType : StorageType ) : Storage | null {
69
- if ( typeof window === "undefined" ) return null ;
70
-
71
- const storageByStorageType = {
72
- local : localStorage ,
73
- session : sessionStorage ,
74
- } satisfies Record < StorageType , Storage > ;
75
-
76
- return storageByStorageType [ storageType ] ;
11
+ function getStorage ( storageType : StorageType ) : Storage {
12
+ switch ( storageType ) {
13
+ case "local" :
14
+ return localStorage ;
15
+ case "session" :
16
+ return sessionStorage ;
17
+ }
77
18
}
78
19
79
20
type PersistedStateOptions < T > = {
@@ -96,8 +37,9 @@ type PersistedStateOptions<T> = {
96
37
export class PersistedState < T > {
97
38
#current: T = $state ( ) ! ;
98
39
#key: string ;
99
- #storage: Storage | null ;
100
40
#serializer: Serializer < T > ;
41
+ #storage?: Storage ;
42
+ #subscribe?: VoidFunction ;
101
43
102
44
constructor ( key : string , initialValue : T , options : PersistedStateOptions < T > = { } ) {
103
45
const {
@@ -106,53 +48,59 @@ export class PersistedState<T> {
106
48
syncTabs = true ,
107
49
} = options ;
108
50
51
+ this . #current = initialValue ;
109
52
this . #key = key ;
110
- this . #storage = getStorage ( storageType ) ;
111
53
this . #serializer = serializer ;
112
54
113
- const valueFromStorage = getValueFromStorage ( {
114
- key : this . #key,
115
- storage : this . #storage,
116
- serializer : this . #serializer,
117
- } ) ;
118
-
119
- this . #current = valueFromStorage . found ? valueFromStorage . value : initialValue ;
55
+ if ( typeof window === "undefined" ) return ;
120
56
121
- $effect ( ( ) => {
122
- setValueToStorage ( {
123
- key : this . #key,
124
- value : this . #current,
125
- storage : this . #storage,
126
- serializer : this . #serializer,
127
- } ) ;
128
- } ) ;
129
-
130
- $effect ( ( ) => {
131
- if ( ! syncTabs || storageType !== "local" ) return ;
57
+ const storage = getStorage ( storageType ) ;
58
+ this . #storage = storage ;
132
59
133
- return addEventListener ( window , "storage" , this . #handleStorageEvent. bind ( this ) ) ;
134
- } ) ;
135
- }
136
-
137
- #handleStorageEvent( event : StorageEvent ) : void {
138
- if ( event . key !== this . #key || ! this . #storage) return ;
139
-
140
- const valueFromStorage = getValueFromStorage ( {
141
- key : this . #key,
142
- storage : this . #storage,
143
- serializer : this . #serializer,
144
- } ) ;
60
+ const existingValue = storage . getItem ( key ) ;
61
+ if ( existingValue !== null ) {
62
+ this . #deserialize( existingValue ) ;
63
+ }
145
64
146
- if ( valueFromStorage . found ) {
147
- this . #current = valueFromStorage . value ;
65
+ if ( syncTabs && storageType === "local" ) {
66
+ this . #subscribe = createSubscriber ( ( ) => {
67
+ return on ( window , "storage" , this . #handleStorageEvent) ;
68
+ } ) ;
148
69
}
149
70
}
150
71
151
72
get current ( ) : T {
73
+ this . #subscribe?.( ) ;
152
74
return this . #current;
153
75
}
154
76
155
77
set current ( newValue : T ) {
156
78
this . #current = newValue ;
79
+ this . #serialize( newValue ) ;
80
+ }
81
+
82
+ #handleStorageEvent = ( event : StorageEvent ) : void => {
83
+ if ( event . key !== this . #key || event . newValue === null ) return ;
84
+
85
+ this . #deserialize( event . newValue ) ;
86
+ } ;
87
+
88
+ #deserialize( value : string ) : void {
89
+ try {
90
+ this . #current = this . #serializer. deserialize ( value ) ;
91
+ } catch ( error ) {
92
+ console . error ( `Error when parsing "${ value } " from persisted store "${ this . #key} "` , error ) ;
93
+ }
94
+ }
95
+
96
+ #serialize( value : T ) : void {
97
+ try {
98
+ this . #storage?. setItem ( this . #key, this . #serializer. serialize ( value ) ) ;
99
+ } catch ( error ) {
100
+ console . error (
101
+ `Error when writing value from persisted store "${ this . #key} " to ${ this . #storage} ` ,
102
+ error
103
+ ) ;
104
+ }
157
105
}
158
106
}
0 commit comments