Skip to content

Commit 41204c8

Browse files
authored
Add full ssr cookie support to storage package (#496)
2 parents 3fcb66b + c1b1921 commit 41204c8

File tree

4 files changed

+331
-150
lines changed

4 files changed

+331
-150
lines changed

packages/storage/README.md

Lines changed: 98 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
[![size](https://img.shields.io/npm/v/@solid-primitives/storage?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/storage)
1010
[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-3.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)
1111

12-
Creates a primitive to reactively access both synchronous and asynchronous persistent storage APIs similar to `localStorage`.
12+
Creates a primitive to reactively access both synchronous and asynchronous persistent storage APIs similar
13+
to `localStorage`.
1314

1415
## Installation
1516

@@ -24,8 +25,8 @@ yarn add @solid-primitives/storage
2425
`makePersisted` allows you to persist a signal or store in any synchronous or asynchronous Storage API:
2526

2627
```ts
27-
const [signal, setSignal] = makePersisted(createSignal("initial"), { storage: sessionStorage });
28-
const [store, setStore] = makePersisted(createStore({ test: true }), { name: "testing" });
28+
const [signal, setSignal] = makePersisted(createSignal("initial"), {storage: sessionStorage});
29+
const [store, setStore] = makePersisted(createStore({test: true}), {name: "testing"});
2930
type PersistedOptions<Type, StorageOptions> = {
3031
// localStorage is default
3132
storage?: Storage | StorageWithOptions | AsyncStorage | AsyncStorageWithOptions,
@@ -45,35 +46,64 @@ type PersistedOptions<Type, StorageOptions> = {
4546
- initial values of signals or stores are not persisted, so they can be safely changed
4647
- values persisted in asynchronous storage APIs will not overwrite already changed signals or stores
4748
- setting a persisted signal to undefined or null will remove the item from the storage
48-
- to use `makePersisted` with other state management APIs, you need some adapter that will project your API to either the output of `createSignal` or `createStore`
49+
- to use `makePersisted` with other state management APIs, you need some adapter that will project your API to either
50+
the output of `createSignal` or `createStore`
4951

5052
### Using `makePersisted` with resources
5153

52-
Instead of wrapping the resource itself, it is far simpler to use the `storage` option of the resource to provide a persisted signal or [deep signal](../resource/#createdeepsignal):
54+
Instead of wrapping the resource itself, it is far simpler to use the `storage` option of the resource to provide a
55+
persisted signal or [deep signal](../resource/#createdeepsignal):
5356

5457
```ts
55-
const [resource] = createResource(fetcher, { storage: makePersisted(createSignal()) });
58+
const [resource] = createResource(fetcher, {storage: makePersisted(createSignal())});
5659
```
5760

58-
If you are using an asynchronous storage to persist the state of a resource, it might receive an update due to being initialized from the storage before or after the fetcher resolved. If the initialization resolves after the fetcher, its result is discarded not to overwrite more current data.
61+
If you are using an asynchronous storage to persist the state of a resource, it might receive an update due to being
62+
initialized from the storage before or after the fetcher resolved. If the initialization resolves after the fetcher, its
63+
result is discarded not to overwrite more current data.
5964

6065
### Different storage APIs
6166

62-
In the browser, we already have `localStorage`, which persists values for the same hostname indefinitely, and `sessionStorage`, which does the same for the duration of the browser session, but loses persistence after closing the browser.
67+
#### LocalStorage, SessionStorage
6368

64-
As another storage, `cookieStorage` from this package can be used, which is a `localStorage`-like API to set cookies. It will work in the browser and also allows reading cookies on solid-start. Using it in the server without solid-start will not cause errors (unless you are using stackblitz), but instead emit a warning message. You can also supply your own implementations of `cookieStorage._read(key)` and `cookieStorage._write(key, value, options)` if neither of those fit your need.
69+
In the browser, we already have `localStorage`, which persists values for the same hostname indefinitely,
70+
and `sessionStorage`, which does the same for the duration of the browser session, but loses persistence after closing
71+
the browser.
6572

66-
If you are not using solid-start or are using stackblitz and want to use cookieStorage on the server, you can supply optional getRequest (either something like useRequest from solid-start or a function that returns the current request) and setCookie options.
73+
#### CookieStorage
6774

68-
There is also [`localForage`](https://localforage.github.io/localForage/), which uses `IndexedDB`, `WebSQL` or `localStorage` to provide an asynchronous Storage API that can ideally store much more than the few Megabytes that are available in most browsers.
75+
As another storage, `cookieStorage` from this package can be used, which is a `localStorage`-like API to set cookies. It
76+
will work in the browser and on solid-start, by parsing the `Cookie` and `Set-Cookie` header and altering
77+
the `Set-Cookie` header. Using it in the server without solid-start will not cause errors (unless
78+
you are using stackblitz), but instead emit a warning message. You can also supply your own implementations
79+
of `cookieStorage._read(key, options)` and `cookieStorage._write(key, value, options)` if neither of those fit your
80+
need.
81+
82+
If you are not using solid-start or are using stackblitz and want to use cookieStorage on the server, you can supply
83+
optional `getRequest` (either something like useRequest from solid-start or a function that returns the current request)
84+
and `setCookie` options.
85+
86+
When you are using vite and solid-start you want to always provide the `useRequest` function from solid start to
87+
the `getRequest` option, because of a technical limitation of vite.
88+
89+
> Please mind that `cookieStorage` **doesn't care** about the path and domain when reading cookies. This might cause issues when using
90+
> multiple cookies with the same key, but different path or domain.
91+
92+
#### IndexedDB, WebSQL
93+
94+
There is also [`localForage`](https://localforage.github.io/localForage/), which uses `IndexedDB`, `WebSQL`
95+
or `localStorage` to provide an asynchronous Storage API that can ideally store much more than the few Megabytes that
96+
are available in most browsers.
6997

7098
---
7199

72100
### Deprecated primitives:
73101

74-
The previous implementation proved to be confusing and cumbersome for most people who just wanted to persist their signals and stores, so they are now deprecated.
102+
The previous implementation proved to be confusing and cumbersome for most people who just wanted to persist their
103+
signals and stores, so they are now deprecated.
75104

76-
`createStorage` is meant to wrap any `localStorage`-like API to be as accessible as a [Solid Store](https://www.solidjs.com/docs/latest/api#createstore). The main differences are
105+
`createStorage` is meant to wrap any `localStorage`-like API to be as accessible as
106+
a [Solid Store](https://www.solidjs.com/docs/latest/api#createstore). The main differences are
77107

78108
- that this store is persisted in whatever API is used,
79109
- that you can only use the topmost layer of the object and
@@ -83,8 +113,11 @@ The previous implementation proved to be confusing and cumbersome for most peopl
83113
const [store, setStore, {
84114
remove: (key: string) => void;
85115
clear: () => void;
86-
toJSON: () => ({ [key: string]: string });
87-
}] = createStorage({ api: sessionStorage, prefix: 'my-app' });
116+
toJSON: () => ({[key: string]: string
117+
})
118+
;
119+
}]
120+
= createStorage({api: sessionStorage, prefix: 'my-app'});
88121

89122
setStore('key', 'value');
90123
store.key; // 'value'
@@ -93,16 +126,19 @@ store.key; // 'value'
93126
The props object support the following parameters:
94127

95128
`api`
96-
: An array of or a single `localStorage`-like storage API; default will be `localStorage` if it exists; an empty array or no API will not throw an error, but only ever get `null` and not actually persist anything
129+
: An array of or a single `localStorage`-like storage API; default will be `localStorage` if it exists; an empty array
130+
or no API will not throw an error, but only ever get `null` and not actually persist anything
97131

98132
`prefix`
99133
: A string that will be prefixed every key inside the API on set and get operations
100134

101135
`serializer / deserializer`
102-
: A set of function to filter the input and output; the `serializer` takes an arbitrary object and returns a string, e.g. `JSON.stringify`, whereas the `deserializer` takes a string and returns the requested object again.
136+
: A set of function to filter the input and output; the `serializer` takes an arbitrary object and returns a string,
137+
e.g. `JSON.stringify`, whereas the `deserializer` takes a string and returns the requested object again.
103138

104139
`options`
105-
: For APIs that support options as third argument in the `getItem` and `setItem` method (see helper type `StorageWithOptions<O>`), you can add options they will receive on every operation.
140+
: For APIs that support options as third argument in the `getItem` and `setItem` method (see helper
141+
type `StorageWithOptions<O>`), you can add options they will receive on every operation.
106142

107143
---
108144

@@ -118,7 +154,9 @@ createCookieStorage();
118154

119155
#### Asynchronous storage APIs
120156

121-
In case you have APIs that persist data on the server or via `ServiceWorker` in a [`CookieStore`](https://wicg.github.io/cookie-store/#CookieStore), you can wrap them into an asynchronous storage (`AsyncStorage` or `AsyncStorageWithOptions` API) and use them with `createAsyncStorage`:
157+
In case you have APIs that persist data on the server or via `ServiceWorker` in
158+
a [`CookieStore`](https://wicg.github.io/cookie-store/#CookieStore), you can wrap them into an asynchronous
159+
storage (`AsyncStorage` or `AsyncStorageWithOptions` API) and use them with `createAsyncStorage`:
122160

123161
```ts
124162
type CookieStoreOptions = {
@@ -144,36 +182,58 @@ const CookieStoreAPI: AsyncStorageWithOptions<CookieStoreOptions> = {
144182
const all = await cookieStore.getAll();
145183
return Object.keys(all)[index];
146184
}
147-
});
185+
}
186+
)
187+
;
148188

149189
const [cookies, setCookie, {
150-
remove: (key: string) => void;
151-
clear: () => void;
152-
toJSON: () => ({ [key: string]: string });
153-
}] = createAsyncStorage({ api: CookieStoreAPI, prefix: 'my-app', sync: false });
190+
remove: (key: string) => void;
191+
clear: () => void;
192+
toJSON: () => ({[key: string]: string
193+
})
194+
;
195+
}]
196+
= createAsyncStorage({api: CookieStoreAPI, prefix: 'my-app', sync: false});
154197

155198
await setStore('key', 'value');
156199
await store.key; // 'value'
157200
```
158201

159-
It works exactly like a synchronous storage, with the exception that you have to `await` every single return value. Once the `CookieStore` API becomes more prevalent, we will integrate support out of the box.
202+
It works exactly like a synchronous storage, with the exception that you have to `await` every single return value. Once
203+
the `CookieStore` API becomes more prevalent, we will integrate support out of the box.
160204

161205
If you cannot use `document.cookie`, you can overwrite the entry point using the following tuple:
162206

163207
```ts
164-
import { cookieStorage } from '@solid-primitives/storage';
165-
166-
cookieStorage._cookies = [object: { [name: string]: CookieProxy }, name: string];
208+
import {cookieStorage} from '@solid-primitives/storage';
209+
210+
cookieStorage._cookies = [object
211+
:
212+
{
213+
[name
214+
:
215+
string
216+
]:
217+
CookieProxy
218+
}
219+
,
220+
name: string
221+
]
222+
;
167223
```
168224

169225
If you need to abstract an API yourself, you can use a getter and a setter:
170226

171227
```ts
172228
const CookieAbstraction = {
173-
get cookie() { return myCookieJar.toString() }
229+
get cookie() {
230+
return myCookieJar.toString()
231+
}
174232
set cookie(cookie) {
175233
const data = {};
176-
cookie.replace(/([^=]+)=(?:([^;]+);?)/g, (_, key, value) => { data[key] = value });
234+
cookie.replace(/([^=]+)=(?:([^;]+);?)/g, (_, key, value) => {
235+
data[key] = value
236+
});
177237
myCookieJar.set(data);
178238
}
179239
}
@@ -182,10 +242,11 @@ cookieStorage._cookies = [CookieAbstraction, 'cookie'];
182242

183243
---
184244

185-
`createStorageSignal` is meant for those cases when you only need to conveniently access a single value instead of full access to the storage API:
245+
`createStorageSignal` is meant for those cases when you only need to conveniently access a single value instead of full
246+
access to the storage API:
186247

187248
```ts
188-
const [value, setValue] = createStorageSignal("value", { api: cookieStorage });
249+
const [value, setValue] = createStorageSignal("value", {api: cookieStorage});
189250

190251
setValue("value");
191252
value(); // 'value'
@@ -199,12 +260,15 @@ As a convenient additional method, you can also use `createCookieStorageSignal(k
199260

200261
The properties of your `createStorage`/`createAsyncStorage`/`createStorageSignal` props are:
201262

202-
- `api`: the (synchronous or asynchronous) [Storage-like API](https://developer.mozilla.org/de/docs/Web/API/Web_Storage_API), default is `localStorage`
263+
- `api`: the (synchronous or
264+
asynchronous) [Storage-like API](https://developer.mozilla.org/de/docs/Web/API/Web_Storage_API), default
265+
is `localStorage`
203266
- `deserializer` (optional): a `deserializer` or parser for the stored data
204267
- `serializer` (optional): a `serializer` or string converter for the stored data
205268
- `options` (optional): default options for the set-call of Storage-like API, if supported
206269
- `prefix` (optional): a prefix for the Storage keys
207-
- `sync` (optional): if set to false, [event synchronization](https://developer.mozilla.org/en-US/docs/Web/API/StorageEvent) is disabled
270+
- `sync` (optional): if set to
271+
false, [event synchronization](https://developer.mozilla.org/en-US/docs/Web/API/StorageEvent) is disabled
208272

209273
### Tools
210274

0 commit comments

Comments
 (0)