@lyeve-labs/client-svelte 0.1.3 → 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/README.md CHANGED
@@ -1,20 +1,20 @@
1
- # @lyeve/cms-client-svelte
1
+ # @lyeve-labs/client-svelte
2
2
 
3
3
  Svelte 5 reactive stores for the LyEve CMS API. Thin wrapper around
4
- `@lyeve/cms-client` using Svelte 5 runes (`$state`).
4
+ `@lyeve-labs/client` using Svelte 5 runes (`$state`).
5
5
 
6
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
7
  [![Svelte 5](https://img.shields.io/badge/svelte-5-ff3e00.svg)](https://svelte.dev)
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-3178c6.svg)](https://www.typescriptlang.org)
9
9
 
10
10
  ```bash
11
- pnpm add @lyeve/cms-client @lyeve/cms-client-svelte
11
+ pnpm add @lyeve-labs/client @lyeve-labs/client-svelte
12
12
  ```
13
13
 
14
14
  ```svelte
15
15
  <script lang="ts">
16
- import { createCmsClient, createAsyncStore } from '@lyeve/cms-client-svelte';
17
- import { getSchemas } from '@lyeve/cms-client-rest';
16
+ import { createCmsClient, createAsyncStore } from '@lyeve-labs/client-svelte';
17
+ import { getSchemas } from '@lyeve-labs/client-rest';
18
18
 
19
19
  const client = createCmsClient({
20
20
  baseUrl: 'https://cms.example.com',
@@ -55,24 +55,27 @@ No Provider needed. Create a client, pass it in, done.
55
55
 
56
56
  - **Node 20** or newer
57
57
  - **Svelte 5.0** or newer
58
- - **[@lyeve/cms-client](https://www.npmjs.com/package/@lyeve/cms-client)** `>=0.1.0`
58
+ - **[@lyeve-labs/client](https://www.npmjs.com/package/@lyeve-labs/client)** `>=0.2.1`
59
59
 
60
60
  ## Install
61
61
 
62
62
  ```bash
63
- pnpm add @lyeve/cms-client @lyeve/cms-client-svelte
64
- # or npm install @lyeve/cms-client @lyeve/cms-client-svelte
65
- # or yarn add @lyeve/cms-client @lyeve/cms-client-svelte
63
+ pnpm add @lyeve-labs/client @lyeve-labs/client-svelte
64
+ # or npm install @lyeve-labs/client @lyeve-labs/client-svelte
65
+ # or yarn add @lyeve-labs/client @lyeve-labs/client-svelte
66
66
  ```
67
67
 
68
+ The examples below fetch through the REST helpers, which ship separately. Add
69
+ `@lyeve-labs/client-rest` if you want them, or pass any fetcher of your own.
70
+
68
71
  ## Use
69
72
 
70
73
  ### Data fetching
71
74
 
72
75
  ```svelte
73
76
  <script lang="ts">
74
- import { createCmsClient, createAsyncStore } from '@lyeve/cms-client-svelte';
75
- import { getSchemas } from '@lyeve/cms-client-rest';
77
+ import { createCmsClient, createAsyncStore } from '@lyeve-labs/client-svelte';
78
+ import { getSchemas } from '@lyeve-labs/client-rest';
76
79
 
77
80
  const client = createCmsClient({
78
81
  baseUrl: 'https://cms.example.com',
@@ -101,7 +104,7 @@ pnpm add @lyeve/cms-client @lyeve/cms-client-svelte
101
104
 
102
105
  ```svelte
103
106
  <script lang="ts">
104
- import { createCmsClient, createAuthStore } from '@lyeve/cms-client-svelte';
107
+ import { createCmsClient, createAuthStore } from '@lyeve-labs/client-svelte';
105
108
 
106
109
  const client = createCmsClient({ baseUrl: 'https://cms.example.com' });
107
110
  const auth = createAuthStore(client);
@@ -193,7 +196,7 @@ tests/ # vitest test suite
193
196
 
194
197
  ## Versioning
195
198
 
196
- `@lyeve/cms-client-svelte` follows [SemVer](https://semver.org). While under `1.0`,
199
+ `@lyeve-labs/client-svelte` follows [SemVer](https://semver.org). While under `1.0`,
197
200
  breaking changes bump the **minor** version; additive changes bump the **patch**.
198
201
  Every release is logged in [`CHANGELOG.md`](CHANGELOG.md).
199
202
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@lyeve-labs/client-svelte",
3
- "version": "0.1.3",
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@9.15.0",
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": {
@@ -46,11 +49,11 @@
46
49
  "format:check": "prettier --check ."
47
50
  },
48
51
  "peerDependencies": {
49
- "@lyeve-labs/client": ">=0.1.0",
52
+ "@lyeve-labs/client": ">=0.2.1",
50
53
  "svelte": ">=5.0.0"
51
54
  },
52
55
  "devDependencies": {
53
- "@lyeve-labs/client": "^0.2.0",
56
+ "@lyeve-labs/client": "^0.3.0",
54
57
  "svelte": "^5.0.0",
55
58
  "tsup": "^8.4.0",
56
59
  "typescript": "^5.7.0",
@@ -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
+ }