@lyeve-labs/client-svelte 0.1.4 → 0.1.5

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/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@lyeve-labs/client-svelte",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Svelte 5 reactive stores for the LyEve Core API - thin wrapper around @lyeve-labs/client using $state runes",
5
5
  "license": "MIT",
6
6
  "author": "LyEve <info@lyeve.com>",
7
7
  "type": "module",
8
- "packageManager": "pnpm@10.33.4",
8
+ "packageManager": "pnpm@11.25.0",
9
9
  "engines": {
10
10
  "node": ">=20"
11
11
  },
@@ -29,8 +29,11 @@
29
29
  },
30
30
  "files": [
31
31
  "dist",
32
+ "src",
32
33
  "!dist/**/*.test.*",
33
- "!dist/**/*.spec.*"
34
+ "!dist/**/*.spec.*",
35
+ "!src/**/*.test.*",
36
+ "!src/**/*.spec.*"
34
37
  ],
35
38
  "sideEffects": false,
36
39
  "publishConfig": {
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Ambient type declarations for Svelte 5 runes used in `.ts` source files.
3
+ *
4
+ * When the Svelte compiler processes these files (via the esbuild plugin at
5
+ * build time, or the consumer's SvelteKit pipeline from the `svelte` export
6
+ * condition), `$state`, `$derived`, and `$effect` are transformed into
7
+ * compiler-managed reactive primitives. These declarations allow TypeScript
8
+ * to type-check the source without the Svelte preprocessor.
9
+ *
10
+ * @see https://svelte.dev/docs/svelte/runes
11
+ */
12
+
13
+ /**
14
+ * Declares a reactive state variable.
15
+ * @template T - The type of the state value.
16
+ * @param initial - The initial value.
17
+ */
18
+ declare function $state<T>(initial: T): T;
19
+
20
+ /**
21
+ * Declares a derived value that updates when its dependencies change.
22
+ * @template T - The type of the derived value.
23
+ * @param expression - The expression to derive.
24
+ */
25
+ declare function $derived<T>(expression: T): T;
26
+
27
+ /**
28
+ * Runs a side-effect function when its dependencies change.
29
+ * @param fn - The effect function, optionally returning a cleanup function.
30
+ */
31
+ declare function $effect(fn: () => void | (() => void)): void;
@@ -0,0 +1,12 @@
1
+ // SvelteKit entry point (svelte export condition).
2
+ // Svelte's compiler handles $state runes from this file.
3
+ export {
4
+ createCmsClient,
5
+ createAsyncStore,
6
+ createMutation,
7
+ createAuthStore,
8
+ type SvelteCmsConfig,
9
+ type AsyncStore,
10
+ type AuthState,
11
+ type AuthStore,
12
+ } from "./runes.svelte.js";
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ // Re-exports for non-Svelte consumers (bundled by tsup).
2
+ // Implementation lives in runes.svelte.ts so Svelte's compiler handles
3
+ // $state runes natively via the "svelte" export condition.
4
+ export {
5
+ createCmsClient,
6
+ createAsyncStore,
7
+ createMutation,
8
+ createAuthStore,
9
+ type SvelteCmsConfig,
10
+ type AsyncStore,
11
+ type AuthState,
12
+ type AuthStore,
13
+ } from "./runes.svelte.js";
@@ -0,0 +1,209 @@
1
+ import { createClient, type HttpClient } from "@lyeve-labs/client";
2
+
3
+ // Client factory
4
+
5
+ export interface SvelteCmsConfig {
6
+ /** Base URL prepended to every request path. */
7
+ baseUrl?: string;
8
+ /**
9
+ * Callback returning headers added to every request.
10
+ * Called on every request so auth tokens can be refreshed without
11
+ * recreating the client.
12
+ */
13
+ getHeaders?: () => Record<string, string>;
14
+ }
15
+
16
+ /**
17
+ * Creates an HttpClient pre-configured with base URL and dynamic request
18
+ * headers. No Provider needed - Svelte callers just pass the client around.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const client = createCmsClient({
23
+ * baseUrl: 'https://cms.example.com',
24
+ * getHeaders: () => ({ Authorization: `Bearer ${token}` }),
25
+ * });
26
+ * ```
27
+ */
28
+ export function createCmsClient(config: SvelteCmsConfig): HttpClient {
29
+ const base = config.baseUrl ?? "";
30
+ return createClient((url, init) => {
31
+ const fullUrl = typeof url === "string" ? `${base}${url}` : url;
32
+ return fetch(fullUrl, {
33
+ ...init,
34
+ headers: { ...init?.headers, ...config.getHeaders?.() },
35
+ });
36
+ });
37
+ }
38
+
39
+ // Generic async store
40
+
41
+ export interface AsyncStore<T> {
42
+ readonly data: T | null;
43
+ readonly error: Error | null;
44
+ readonly loading: boolean;
45
+ refetch: () => void;
46
+ }
47
+
48
+ /**
49
+ * Reactive async data store using the $state rune.
50
+ * Fires the fetcher immediately and exposes reactive loading/data/error
51
+ * getters that Svelte components can read directly in markup.
52
+ *
53
+ * @example
54
+ * ```svelte
55
+ * <script lang="ts">
56
+ * const schemas = createAsyncStore((c) => getSchemas(c), client);
57
+ * </script>
58
+ * {#if schemas.loading}...{/if}
59
+ * ```
60
+ */
61
+ export function createAsyncStore<T>(
62
+ fetcher: (client: HttpClient) => Promise<T>,
63
+ client: HttpClient,
64
+ ): AsyncStore<T> {
65
+ let data = $state<T | null>(null);
66
+ let error = $state<Error | null>(null);
67
+ let loading = $state(true);
68
+
69
+ async function fetch() {
70
+ loading = true;
71
+ try {
72
+ data = await fetcher(client);
73
+ error = null;
74
+ } catch (e) {
75
+ error = e as Error;
76
+ } finally {
77
+ loading = false;
78
+ }
79
+ }
80
+
81
+ fetch();
82
+
83
+ return {
84
+ get data() {
85
+ return data;
86
+ },
87
+ get error() {
88
+ return error;
89
+ },
90
+ get loading() {
91
+ return loading;
92
+ },
93
+ refetch: fetch,
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Reactive mutation primitive using the $state rune.
99
+ * Returns data/error/loading state and a run function that triggers the
100
+ * mutation. Unlike createAsyncStore, the mutation does not fire immediately.
101
+ *
102
+ * @example
103
+ * ```svelte
104
+ * <script lang="ts">
105
+ * const createArticle = createMutation(
106
+ * (c, vars: { title: string }) => createArticle(c, vars),
107
+ * client,
108
+ * );
109
+ * </script>
110
+ * <button onclick={() => createArticle.run({ title: 'Hello' })}>
111
+ * {createArticle.loading ? 'Saving...' : 'Create'}
112
+ * </button>
113
+ * ```
114
+ */
115
+ export function createMutation<T, V>(
116
+ mutator: (client: HttpClient, vars: V) => Promise<T>,
117
+ client: HttpClient,
118
+ ) {
119
+ let data = $state<T | null>(null);
120
+ let error = $state<Error | null>(null);
121
+ let loading = $state(false);
122
+
123
+ async function run(vars: V): Promise<T> {
124
+ loading = true;
125
+ error = null;
126
+ try {
127
+ const result = await mutator(client, vars);
128
+ data = result;
129
+ return result;
130
+ } catch (e) {
131
+ error = e as Error;
132
+ throw e;
133
+ } finally {
134
+ loading = false;
135
+ }
136
+ }
137
+
138
+ return {
139
+ get data() {
140
+ return data;
141
+ },
142
+ get error() {
143
+ return error;
144
+ },
145
+ get loading() {
146
+ return loading;
147
+ },
148
+ run,
149
+ };
150
+ }
151
+
152
+ // Auth store
153
+
154
+ export interface AuthState {
155
+ user: { id: string; email: string; roles: string[] } | null;
156
+ token: string | null;
157
+ }
158
+
159
+ export interface AuthStore {
160
+ readonly user: AuthState["user"];
161
+ readonly token: string | null;
162
+ readonly isAuthenticated: boolean;
163
+ /** Set the current user and token after a successful login. */
164
+ setUser: (user: AuthState["user"], token: string | null) => void;
165
+ /** Clear auth state (e.g. after logout). */
166
+ clear: () => void;
167
+ /** Try to load the current user from the server using the stored token. */
168
+ load: () => Promise<void>;
169
+ }
170
+
171
+ /**
172
+ * Simple reactive auth store. The caller is responsible for calling
173
+ * {@link AuthStore.setUser} after login and {@link AuthStore.clear} after
174
+ * logout. Use {@link AuthStore.load} on app start to restore a session
175
+ * from an existing cookie.
176
+ */
177
+ export function createAuthStore(client: HttpClient): AuthStore {
178
+ let state = $state<AuthState>({ user: null, token: null });
179
+
180
+ return {
181
+ get user() {
182
+ return state.user;
183
+ },
184
+ get token() {
185
+ return state.token;
186
+ },
187
+ get isAuthenticated() {
188
+ return state.token !== null;
189
+ },
190
+ setUser(user: AuthState["user"], token: string | null) {
191
+ state = { user, token };
192
+ },
193
+ clear() {
194
+ state = { user: null, token: null };
195
+ },
196
+ async load() {
197
+ try {
198
+ const user = await client.get<{
199
+ id: string;
200
+ email: string;
201
+ roles: string[];
202
+ }>("/api/admin/auth/me");
203
+ state = { ...state, user };
204
+ } catch {
205
+ /* not logged in */
206
+ }
207
+ },
208
+ };
209
+ }