@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 CHANGED
@@ -1,188 +1,158 @@
1
1
  # `@sveltebase/state`
2
2
 
3
- Small rune-friendly state helpers for Svelte 5.
3
+ Small state helpers for Svelte 5.
4
4
 
5
- This package provides two classes:
5
+ This package exports two classes:
6
6
 
7
- - `State<T>` for simple in-memory reactive state
8
- - `PersistentState<TSchema>` for validated state that is persisted in cookies using `zod`
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
- ```bash
13
- bun add @sveltebase/state zod
14
- ```
12
+ ~~~bash
13
+ bun add @sveltebase/state svelte
14
+ ~~~
15
15
 
16
- You also need `svelte` as a peer dependency:
16
+ If you want to use `zod` as your schema library:
17
17
 
18
- ```bash
19
- bun add svelte
20
- ```
18
+ ~~~bash
19
+ bun add zod
20
+ ~~~
21
21
 
22
- ## What it exports
22
+ ## Exports
23
23
 
24
24
  - `State`
25
25
  - `PersistentState`
26
26
 
27
27
  ## `State`
28
28
 
29
- Use `State` when you want a tiny reactive wrapper around a value.
30
-
31
- ### Example
29
+ A tiny wrapper around a value.
32
30
 
33
- ```ts
31
+ ~~~ts
34
32
  import { State } from "@sveltebase/state";
35
33
 
36
- const counter = new State(0);
34
+ const count = new State(0);
37
35
 
38
- counter.current = 1;
39
- counter.set((value) => value + 1);
36
+ count.current = 1;
37
+ count.set((value) => value + 1);
40
38
 
41
- console.log(counter.current); // 2
42
- ```
39
+ console.log(count.current); // 2
40
+ ~~~
43
41
 
44
42
  ### API
45
43
 
46
- #### `new State(initialValue)`
47
-
48
- Creates a new reactive state container.
44
+ - `new State(initialValue)`
45
+ - `state.current`
46
+ - `state.set(updater)`
49
47
 
50
- #### `state.current`
51
-
52
- Gets or sets the current value.
48
+ ## `PersistentState`
53
49
 
54
- #### `state.set(updater)`
50
+ Cookie-backed state with schema validation.
55
51
 
56
- Updates the current value using a callback.
52
+ It:
57
53
 
58
- ## `PersistentState`
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
- Use `PersistentState` when you want state that:
59
+ ~~~ts
60
+ import { z } from "zod";
61
+ import { PersistentState } from "@sveltebase/state";
61
62
 
62
- - is validated with `zod`
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
- ## Install requirements
65
+ export const theme = new PersistentState("theme", themeSchema);
68
66
 
69
- `PersistentState` depends on `zod`, so make sure it is installed:
67
+ theme.current = "dark";
68
+ theme.set((value) => (value === "dark" ? "light" : "dark"));
69
+ ~~~
70
70
 
71
- ```bash
72
- bun add zod
73
- ```
71
+ ## SSR setup
74
72
 
75
- ## Example
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
- ```ts
78
- import { z } from "zod";
79
- import { PersistentState } from "@sveltebase/state";
75
+ ### `src/routes/+layout.server.ts`
80
76
 
81
- const themeSchema = z.enum(["light", "dark"]).default("light");
77
+ ~~~ts
78
+ export async function load({ cookies }) {
79
+ return {
80
+ cookies: cookies.getAll()
81
+ };
82
+ }
83
+ ~~~
82
84
 
83
- export const theme = new PersistentState("theme", themeSchema);
85
+ ### `src/routes/+layout.svelte`
84
86
 
85
- // read
86
- console.log(theme.current);
87
+ ~~~svelte
88
+ <script lang="ts">
89
+ import type { LayoutData } from "./$types";
90
+ import { locale } from "$lib/state";
87
91
 
88
- // write
89
- theme.current = "dark";
92
+ let { data }: { data: LayoutData } = $props();
90
93
 
91
- // update
92
- theme.set((value) => (value === "dark" ? "light" : "dark"));
93
- ```
94
+ locale.init(() => data.cookies);
95
+ </script>
94
96
 
95
- ## SSR usage
97
+ <slot />
98
+ ~~~
96
99
 
97
- When rendering on the server, you can initialize the state from cookies before using it.
100
+ ### `src/lib/state.ts`
98
101
 
99
- ```ts
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
- // for example, from a server load or request context:
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
- `PersistentState` stores the current value in a cookie using the key you pass to the constructor.
118
-
119
- - On the client, it reads the cookie during hydration
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 reactive state value.
121
+ Creates a persistent state value.
130
122
 
131
123
  - `key`: cookie name
132
- - `schema`: `zod` schema used to validate and parse the value
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 parsed current value.
128
+ Gets or sets the current value.
137
129
 
138
130
  ### `persistentState.set(updater)`
139
131
 
140
- Updates the current value using a callback.
132
+ Updates the current value.
141
133
 
142
134
  ### `persistentState.init(cookies)`
143
135
 
144
- Initializes the state from server-side cookies.
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
- ```ts
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` uses cookies for persistence
186
- - values are validated with `zod` before being accepted
187
- - if cookie data is invalid, the schema fallback/default is used
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
@@ -1,3 +1,3 @@
1
1
  export { PersistentState, State } from "./state.svelte.js";
2
- export type { MaybeGetter } from "./state.svelte.js";
2
+ export type { InferInput, InferOutput, MaybeGetter, StandardSchemaV1 } from "./state.svelte.js";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -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,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
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"}
@@ -1,17 +1,43 @@
1
- import { z } from "zod";
2
1
  export type MaybeGetter<T> = T | (() => T);
3
- export declare class PersistentState<TSchema extends z.ZodTypeAny> {
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(): z.output<TSchema>;
9
- set current(newValue: z.output<TSchema>);
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: z.output<TSchema>) => z.output<TSchema>): void;
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":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAE3C,qBAAa,eAAe,CAAC,OAAO,SAAS,CAAC,CAAC,UAAU;;IAIvD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAU;gBAEZ,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;IAmBxC,IAAI,OAAO,IAIW,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAFtC;IAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAEtC;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,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IAI9D,OAAO,CAAC,MAAM,CAAC,OAAO;CAiBvB;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"}
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"}
@@ -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.parse(newValue);
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.parse(JSON.parse(rawCookie.value));
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.parse(undefined);
54
+ return parseSchema(schema, undefined);
56
55
  }
57
56
  const rawCookie = Cookies.get(key);
58
57
  if (rawCookie) {
59
58
  try {
60
- return schema.parse(JSON.parse(rawCookie));
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.parse(undefined);
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.3",
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.3.3",
22
- "zod": "^4.1.11"
21
+ "@sveltebase/utils": "0.4.0"
23
22
  },
24
23
  "peerDependencies": {
25
24
  "svelte": "^5.0.0"