@sveltebase/state 0.3.3 → 0.4.0
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 +81 -111
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/state.svelte.d.ts +31 -5
- package/dist/state.svelte.d.ts.map +1 -1
- package/dist/state.svelte.js +15 -6
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -1,188 +1,158 @@
|
|
|
1
1
|
# `@sveltebase/state`
|
|
2
2
|
|
|
3
|
-
Small
|
|
3
|
+
Small state helpers for Svelte 5.
|
|
4
4
|
|
|
5
|
-
This package
|
|
5
|
+
This package exports two classes:
|
|
6
6
|
|
|
7
|
-
- `State<T
|
|
8
|
-
- `PersistentState<TSchema
|
|
7
|
+
- `State<T>`: simple reactive in-memory state
|
|
8
|
+
- `PersistentState<TSchema>`: reactive state backed by cookies and validated with a Standard Schema-compatible validator
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
bun add @sveltebase/state
|
|
14
|
-
|
|
12
|
+
~~~bash
|
|
13
|
+
bun add @sveltebase/state svelte
|
|
14
|
+
~~~
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
If you want to use `zod` as your schema library:
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
bun add
|
|
20
|
-
|
|
18
|
+
~~~bash
|
|
19
|
+
bun add zod
|
|
20
|
+
~~~
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Exports
|
|
23
23
|
|
|
24
24
|
- `State`
|
|
25
25
|
- `PersistentState`
|
|
26
26
|
|
|
27
27
|
## `State`
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
### Example
|
|
29
|
+
A tiny wrapper around a value.
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
~~~ts
|
|
34
32
|
import { State } from "@sveltebase/state";
|
|
35
33
|
|
|
36
|
-
const
|
|
34
|
+
const count = new State(0);
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
count.current = 1;
|
|
37
|
+
count.set((value) => value + 1);
|
|
40
38
|
|
|
41
|
-
console.log(
|
|
42
|
-
|
|
39
|
+
console.log(count.current); // 2
|
|
40
|
+
~~~
|
|
43
41
|
|
|
44
42
|
### API
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
- `new State(initialValue)`
|
|
45
|
+
- `state.current`
|
|
46
|
+
- `state.set(updater)`
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Gets or sets the current value.
|
|
48
|
+
## `PersistentState`
|
|
53
49
|
|
|
54
|
-
|
|
50
|
+
Cookie-backed state with schema validation.
|
|
55
51
|
|
|
56
|
-
|
|
52
|
+
It:
|
|
57
53
|
|
|
58
|
-
|
|
54
|
+
- reads from cookies
|
|
55
|
+
- validates with a Standard Schema-compatible validator
|
|
56
|
+
- writes updates back to cookies
|
|
57
|
+
- can load the initial value during SSR
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
~~~ts
|
|
60
|
+
import { z } from "zod";
|
|
61
|
+
import { PersistentState } from "@sveltebase/state";
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
- hydrates from cookies in the browser
|
|
64
|
-
- can be initialized from request cookies during SSR
|
|
65
|
-
- writes changes back to cookies automatically
|
|
63
|
+
const themeSchema = z.enum(["light", "dark"]).default("light");
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
export const theme = new PersistentState("theme", themeSchema);
|
|
68
66
|
|
|
69
|
-
|
|
67
|
+
theme.current = "dark";
|
|
68
|
+
theme.set((value) => (value === "dark" ? "light" : "dark"));
|
|
69
|
+
~~~
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
bun add zod
|
|
73
|
-
```
|
|
71
|
+
## SSR setup
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
For SSR, return all cookies from `+layout.server.ts`, then initialize the state with `state.init(() => data.cookies)` so it loads the server value first.
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
import { z } from "zod";
|
|
79
|
-
import { PersistentState } from "@sveltebase/state";
|
|
75
|
+
### `src/routes/+layout.server.ts`
|
|
80
76
|
|
|
81
|
-
|
|
77
|
+
~~~ts
|
|
78
|
+
export async function load({ cookies }) {
|
|
79
|
+
return {
|
|
80
|
+
cookies: cookies.getAll()
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
~~~
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
### `src/routes/+layout.svelte`
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
~~~svelte
|
|
88
|
+
<script lang="ts">
|
|
89
|
+
import type { LayoutData } from "./$types";
|
|
90
|
+
import { locale } from "$lib/state";
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
theme.current = "dark";
|
|
92
|
+
let { data }: { data: LayoutData } = $props();
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
+
locale.init(() => data.cookies);
|
|
95
|
+
</script>
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
<slot />
|
|
98
|
+
~~~
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
### `src/lib/state.ts`
|
|
98
101
|
|
|
99
|
-
|
|
102
|
+
~~~ts
|
|
100
103
|
import { z } from "zod";
|
|
101
104
|
import { PersistentState } from "@sveltebase/state";
|
|
102
105
|
|
|
103
106
|
const localeSchema = z.enum(["en", "uz"]).default("en");
|
|
104
107
|
|
|
105
108
|
export const locale = new PersistentState("locale", localeSchema);
|
|
109
|
+
~~~
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
locale.init([
|
|
109
|
-
{ name: "locale", value: "\"uz\"" }
|
|
110
|
-
]);
|
|
111
|
-
|
|
112
|
-
console.log(locale.current); // "uz"
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## How persistence works
|
|
111
|
+
With this setup:
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
- On updates, it writes the new value back to the cookie
|
|
121
|
-
- On the server, you can call `init(cookies)` to sync the initial value from request cookies
|
|
122
|
-
|
|
123
|
-
The stored cookie value is JSON-encoded and validated with your `zod` schema.
|
|
113
|
+
- on the server, `init` reads from `data.cookies`
|
|
114
|
+
- the initial SSR render uses that cookie value
|
|
115
|
+
- in the browser, updates keep syncing back to cookies
|
|
124
116
|
|
|
125
117
|
## API
|
|
126
118
|
|
|
127
119
|
### `new PersistentState(key, schema)`
|
|
128
120
|
|
|
129
|
-
Creates a persistent
|
|
121
|
+
Creates a persistent state value.
|
|
130
122
|
|
|
131
123
|
- `key`: cookie name
|
|
132
|
-
- `schema`:
|
|
124
|
+
- `schema`: Standard Schema-compatible validator used for parsing and validation, such as a `zod` schema
|
|
133
125
|
|
|
134
126
|
### `persistentState.current`
|
|
135
127
|
|
|
136
|
-
Gets or sets the
|
|
128
|
+
Gets or sets the current value.
|
|
137
129
|
|
|
138
130
|
### `persistentState.set(updater)`
|
|
139
131
|
|
|
140
|
-
Updates the current value
|
|
132
|
+
Updates the current value.
|
|
141
133
|
|
|
142
134
|
### `persistentState.init(cookies)`
|
|
143
135
|
|
|
144
|
-
|
|
136
|
+
Loads the value from server cookies.
|
|
137
|
+
|
|
138
|
+
It accepts either:
|
|
139
|
+
|
|
140
|
+
- a cookie array
|
|
141
|
+
- a getter function like `() => data.cookies`
|
|
145
142
|
|
|
146
|
-
Expected shape:
|
|
143
|
+
Expected cookie shape:
|
|
147
144
|
|
|
148
|
-
|
|
145
|
+
~~~ts
|
|
149
146
|
type Cookie = {
|
|
150
147
|
name: string;
|
|
151
148
|
value: string;
|
|
152
149
|
};
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
## Example with Svelte
|
|
156
|
-
|
|
157
|
-
```svelte
|
|
158
|
-
<script lang="ts">
|
|
159
|
-
import { z } from "zod";
|
|
160
|
-
import { State, PersistentState } from "@sveltebase/state";
|
|
161
|
-
|
|
162
|
-
const count = new State(0);
|
|
163
|
-
const locale = new PersistentState("locale", z.enum(["en", "uz"]).default("en"));
|
|
164
|
-
|
|
165
|
-
function increment() {
|
|
166
|
-
count.set((value) => value + 1);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function toggleLocale() {
|
|
170
|
-
locale.current = locale.current === "en" ? "uz" : "en";
|
|
171
|
-
}
|
|
172
|
-
</script>
|
|
173
|
-
|
|
174
|
-
<button onclick={increment}>
|
|
175
|
-
Count: {count.current}
|
|
176
|
-
</button>
|
|
177
|
-
|
|
178
|
-
<button onclick={toggleLocale}>
|
|
179
|
-
Locale: {locale.current}
|
|
180
|
-
</button>
|
|
181
|
-
```
|
|
150
|
+
~~~
|
|
182
151
|
|
|
183
152
|
## Notes
|
|
184
153
|
|
|
185
|
-
- `PersistentState`
|
|
186
|
-
-
|
|
187
|
-
-
|
|
154
|
+
- `PersistentState` stores JSON in cookies
|
|
155
|
+
- invalid cookie data falls back to the schema result
|
|
156
|
+
- `init(...)` is for server-side initialization
|
|
157
|
+
- any Standard Schema-compatible library can be used here, including `zod`
|
|
188
158
|
- this package is designed for Svelte 5
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EACV,UAAU,EACV,WAAW,EACX,WAAW,EACX,gBAAgB,EACjB,MAAM,mBAAmB,CAAC"}
|
package/dist/state.svelte.d.ts
CHANGED
|
@@ -1,17 +1,43 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
export type MaybeGetter<T> = T | (() => T);
|
|
3
|
-
export
|
|
2
|
+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
3
|
+
readonly "~standard": {
|
|
4
|
+
readonly version: 1;
|
|
5
|
+
readonly vendor: string;
|
|
6
|
+
readonly validate: (value: unknown) => {
|
|
7
|
+
readonly value: Output;
|
|
8
|
+
readonly issues?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
readonly issues: ReadonlyArray<{
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}>;
|
|
13
|
+
} | Promise<{
|
|
14
|
+
readonly value: Output;
|
|
15
|
+
readonly issues?: undefined;
|
|
16
|
+
} | {
|
|
17
|
+
readonly issues: ReadonlyArray<{
|
|
18
|
+
readonly message: string;
|
|
19
|
+
}>;
|
|
20
|
+
}>;
|
|
21
|
+
readonly types?: {
|
|
22
|
+
readonly input: Input;
|
|
23
|
+
readonly output: Output;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export type InferInput<TSchema extends StandardSchemaV1> = NonNullable<TSchema["~standard"]["types"]>["input"];
|
|
28
|
+
export type InferOutput<TSchema extends StandardSchemaV1> = NonNullable<TSchema["~standard"]["types"]>["output"];
|
|
29
|
+
export declare class PersistentState<TSchema extends StandardSchemaV1> {
|
|
4
30
|
#private;
|
|
5
31
|
private storageKey;
|
|
6
32
|
private schema;
|
|
7
33
|
constructor(key: string, schema: TSchema);
|
|
8
|
-
get current():
|
|
9
|
-
set current(newValue:
|
|
34
|
+
get current(): InferOutput<TSchema>;
|
|
35
|
+
set current(newValue: InferOutput<TSchema>);
|
|
10
36
|
init(cookies: MaybeGetter<{
|
|
11
37
|
name: string;
|
|
12
38
|
value: string;
|
|
13
39
|
}[]>): void;
|
|
14
|
-
set(fn: (value:
|
|
40
|
+
set(fn: (value: InferOutput<TSchema>) => InferOutput<TSchema>): void;
|
|
15
41
|
private static hydrate;
|
|
16
42
|
}
|
|
17
43
|
export declare class State<T> {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.svelte.d.ts","sourceRoot":"","sources":["../src/state.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state.svelte.d.ts","sourceRoot":"","sources":["../src/state.svelte.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAE3C,MAAM,WAAW,gBAAgB,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;IAC/D,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,QAAQ,EAAE,CACjB,KAAK,EAAE,OAAO,KAEZ;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;SAAE,GACvD;YAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;gBAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;aAAE,CAAC,CAAA;SAAE,GAChE,OAAO,CACH;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;SAAE,GACvD;YAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;gBAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;aAAE,CAAC,CAAA;SAAE,CACnE,CAAC;QACN,QAAQ,CAAC,KAAK,CAAC,EAAE;YACf,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;YACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;SACzB,CAAC;KACH,CAAC;CACH;AAED,MAAM,MAAM,UAAU,CAAC,OAAO,SAAS,gBAAgB,IACrD,WAAW,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAEtD,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,gBAAgB,IACtD,WAAW,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEvD,qBAAa,eAAe,CAAC,OAAO,SAAS,gBAAgB;;IAI3D,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAU;gBAEZ,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;IAmBxC,IAAI,OAAO,IAIW,WAAW,CAAC,OAAO,CAAC,CAFzC;IAED,IAAI,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC,EAEzC;IAEM,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAyB5D,GAAG,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC;IAIpE,OAAO,CAAC,MAAM,CAAC,OAAO;CAoBvB;AAED,qBAAa,KAAK,CAAC,CAAC;;gBAGN,YAAY,EAAE,CAAC;IAI3B,IAAI,OAAO,IAIQ,CAAC,CAFnB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,EAEnB;IAED,GAAG,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC;CAGxB"}
|
package/dist/state.svelte.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { Cookies } from "@sveltebase/utils";
|
|
3
2
|
export class PersistentState {
|
|
4
3
|
#value = $state();
|
|
@@ -25,7 +24,7 @@ export class PersistentState {
|
|
|
25
24
|
return this.#value;
|
|
26
25
|
}
|
|
27
26
|
set current(newValue) {
|
|
28
|
-
this.#value = this.schema
|
|
27
|
+
this.#value = parseSchema(this.schema, newValue);
|
|
29
28
|
}
|
|
30
29
|
init(cookies) {
|
|
31
30
|
if (this.#initialized || hasWindow()) {
|
|
@@ -38,7 +37,7 @@ export class PersistentState {
|
|
|
38
37
|
return;
|
|
39
38
|
}
|
|
40
39
|
try {
|
|
41
|
-
const parsed = this.schema
|
|
40
|
+
const parsed = parseSchema(this.schema, JSON.parse(rawCookie.value));
|
|
42
41
|
if (JSON.stringify(parsed) !== JSON.stringify(this.#value)) {
|
|
43
42
|
this.#value = parsed;
|
|
44
43
|
}
|
|
@@ -52,18 +51,18 @@ export class PersistentState {
|
|
|
52
51
|
}
|
|
53
52
|
static hydrate(key, schema) {
|
|
54
53
|
if (!hasWindow()) {
|
|
55
|
-
return schema
|
|
54
|
+
return parseSchema(schema, undefined);
|
|
56
55
|
}
|
|
57
56
|
const rawCookie = Cookies.get(key);
|
|
58
57
|
if (rawCookie) {
|
|
59
58
|
try {
|
|
60
|
-
return schema
|
|
59
|
+
return parseSchema(schema, JSON.parse(rawCookie));
|
|
61
60
|
}
|
|
62
61
|
catch {
|
|
63
62
|
console.warn(`[PersistentState] Invalid data for "${key}". Resetting.`);
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
|
-
return schema
|
|
65
|
+
return parseSchema(schema, undefined);
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
68
|
export class State {
|
|
@@ -84,6 +83,16 @@ export class State {
|
|
|
84
83
|
function unwrap(value) {
|
|
85
84
|
return typeof value === "function" ? value() : value;
|
|
86
85
|
}
|
|
86
|
+
function parseSchema(schema, value) {
|
|
87
|
+
const result = schema["~standard"].validate(value);
|
|
88
|
+
if (result instanceof Promise) {
|
|
89
|
+
throw new Error("[PersistentState] Async schemas are not supported.");
|
|
90
|
+
}
|
|
91
|
+
if (result.issues) {
|
|
92
|
+
throw new Error(result.issues.map((issue) => issue.message).join(", ") || "Validation failed.");
|
|
93
|
+
}
|
|
94
|
+
return result.value;
|
|
95
|
+
}
|
|
87
96
|
function hasWindow() {
|
|
88
97
|
return typeof window !== "undefined";
|
|
89
98
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltebase/state",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@sveltebase/utils": "0.
|
|
22
|
-
"zod": "^4.1.11"
|
|
21
|
+
"@sveltebase/utils": "0.4.0"
|
|
23
22
|
},
|
|
24
23
|
"peerDependencies": {
|
|
25
24
|
"svelte": "^5.0.0"
|