@archlast/client 0.1.0 → 0.1.2

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.
Files changed (2) hide show
  1. package/README.md +68 -720
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,579 +1,149 @@
1
1
  # @archlast/client
2
2
 
3
- The Archlast client SDK provides a complete solution for building reactive frontend applications with Archlast backends. It includes WebSocket-based real-time queries, React hooks with automatic cache management, authentication helpers, file storage utilities, admin APIs, and optional tRPC integration.
4
-
5
- ## Table of Contents
6
-
7
- - [Installation](#installation)
8
- - [Quick Start](#quick-start)
9
- - [Client Configuration](#client-configuration)
10
- - [React Hooks](#react-hooks)
11
- - [useQuery](#usequery)
12
- - [useMutation](#usemutation)
13
- - [usePagination](#usepagination)
14
- - [useUpload](#useupload)
15
- - [useAuth](#useauth)
16
- - [Direct Client Usage](#direct-client-usage)
17
- - [Authentication](#authentication)
18
- - [File Storage](#file-storage)
19
- - [Admin APIs](#admin-apis)
20
- - [tRPC Integration](#trpc-integration)
21
- - [Subpath Exports](#subpath-exports)
22
- - [TypeScript Support](#typescript-support)
23
- - [Error Handling](#error-handling)
24
- - [Advanced Patterns](#advanced-patterns)
3
+ The Archlast client SDK provides WebSocket-based queries and mutations, React
4
+ hooks with cache integration, authentication helpers, storage utilities, admin
5
+ APIs, and optional tRPC access.
25
6
 
26
7
  ## Installation
27
8
 
28
9
  ```bash
29
10
  npm install @archlast/client
30
11
 
31
- # Or with other package managers
12
+ # Other package managers
32
13
  pnpm add @archlast/client
33
14
  yarn add @archlast/client
34
15
  bun add @archlast/client
35
16
  ```
36
17
 
37
- ### Peer Dependencies
38
-
39
- Install required peer dependencies in your application:
18
+ Peer dependencies:
40
19
 
41
20
  ```bash
42
21
  npm install react react-dom @tanstack/react-query
43
22
  ```
44
23
 
45
- ### Optional Dependencies
46
-
47
- For tRPC support:
48
-
49
- ```bash
50
- npm install @trpc/client @trpc/react-query
51
- ```
52
-
53
- ## Quick Start
54
-
55
- ### 1. Create the Client
24
+ ## Quick start
56
25
 
57
26
  ```ts
58
27
  import { ArchlastClient } from "@archlast/client";
28
+ import { ArchlastProvider, useQuery, useMutation } from "@archlast/client/react";
29
+ import { api } from "./_generated/api";
59
30
 
60
31
  const client = new ArchlastClient(
61
- "ws://localhost:4000/ws", // WebSocket URL
62
- "http://localhost:4000", // HTTP URL
63
- "web" // App ID
32
+ "ws://localhost:4000/ws",
33
+ "http://localhost:4000",
34
+ "web"
64
35
  );
65
- ```
66
-
67
- ### 2. Set Up the Provider
68
-
69
- ```tsx
70
- import { ArchlastProvider } from "@archlast/client/react";
71
36
 
72
37
  function App() {
73
38
  return (
74
39
  <ArchlastProvider client={client}>
75
- <YourApp />
40
+ <TodoList />
76
41
  </ArchlastProvider>
77
42
  );
78
43
  }
79
- ```
80
-
81
- ### 3. Use Hooks in Components
82
-
83
- ```tsx
84
- import { useQuery, useMutation } from "@archlast/client/react";
85
- import { api } from "./_generated/api";
86
44
 
87
45
  function TodoList() {
88
- // Subscribe to live updates
89
46
  const tasks = useQuery(api.tasks.list, {});
90
-
91
- // Mutation with automatic cache invalidation
92
47
  const createTask = useMutation(api.tasks.create);
93
48
 
94
- const handleAdd = async () => {
95
- await createTask({ text: "New task" });
96
- // Cache automatically invalidated, UI updates reactively
97
- };
98
-
99
- if (!tasks) return <div>Loading...</div>;
100
-
101
49
  return (
102
50
  <div>
103
- <ul>
104
- {tasks.map(task => (
105
- <li key={task._id}>{task.text}</li>
106
- ))}
107
- </ul>
108
- <button onClick={handleAdd}>Add Task</button>
51
+ <pre>{JSON.stringify(tasks, null, 2)}</pre>
52
+ <button onClick={() => createTask({ text: "New task" })}>
53
+ Add
54
+ </button>
109
55
  </div>
110
56
  );
111
57
  }
112
58
  ```
113
59
 
114
- > **Note:** The `api` object is auto-generated by the CLI in `_generated/api.ts`. Run `archlast dev` or `archlast build` to generate it.
60
+ The `api` object is generated by the CLI in `_generated/api.ts`.
115
61
 
116
- ---
62
+ ## Client configuration
117
63
 
118
- ## Client Configuration
119
-
120
- ### Constructor Signature
64
+ Constructor signature:
121
65
 
122
66
  ```ts
123
- new ArchlastClient(wsUrl, httpUrl, appId, authUrl?, options?)
67
+ new ArchlastClient(wsUrl, httpUrl?, appId?, authUrl?, options?)
124
68
  ```
125
69
 
126
- ### Parameters
127
-
128
- | Parameter | Type | Description |
129
- |-----------|------|-------------|
130
- | `wsUrl` | `string` | WebSocket endpoint (e.g., `ws://localhost:4000/ws`) |
131
- | `httpUrl` | `string` | HTTP base URL (e.g., `http://localhost:4000`) |
132
- | `appId` | `string` | Application identifier (e.g., `"web"`, `"mobile"`) |
133
- | `authUrl` | `string?` | Optional custom auth URL (defaults to `httpUrl`) |
134
- | `options` | `object?` | Additional configuration options |
135
-
136
- ### Options
137
-
138
- ```ts
139
- interface ClientOptions {
140
- // Auto-connect WebSocket on instantiation (default: true)
141
- autoConnect?: boolean;
142
-
143
- // API key for authentication (arch_ prefix)
144
- apiKey?: string;
145
-
146
- // Enable admin WebSocket subscriptions
147
- isAdmin?: boolean;
148
-
149
- // Better Auth session cookies for SSR
150
- betterAuthCookies?: Record<string, string>;
151
- }
152
- ```
153
-
154
- ### Full Example
70
+ Options:
71
+ - `autoConnect` (default true)
72
+ - `apiKey` Better Auth API key (`arch_` prefix)
73
+ - `isAdmin` subscribe to admin streams
74
+ - `betterAuthCookies` for session cookies
155
75
 
156
76
  ```ts
157
77
  const client = new ArchlastClient(
158
- "wss://api.myapp.com/ws",
159
- "https://api.myapp.com",
78
+ "ws://localhost:4000/ws",
79
+ "http://localhost:4000",
160
80
  "web",
161
- "https://auth.myapp.com", // Custom auth URL
162
- {
163
- autoConnect: true,
164
- apiKey: "arch_your_api_key",
165
- isAdmin: false,
166
- betterAuthCookies: {
167
- "better-auth.session": "session_token_here"
168
- }
169
- }
81
+ undefined,
82
+ { apiKey: "arch_example" }
170
83
  );
171
- ```
172
84
 
173
- ### Runtime Credential Updates
174
-
175
- ```ts
176
- // Rotate API key
177
- client.setApiKey("arch_new_api_key");
178
-
179
- // Update session cookies (for SSR)
180
- client.setBetterAuthCookies({
181
- "better-auth.session": "new_session_token"
182
- });
85
+ client.setApiKey("arch_new_key");
86
+ client.setBetterAuthCookies({ "better-auth.session": "..." });
183
87
  ```
184
88
 
185
- ---
89
+ ## React hooks
186
90
 
187
- ## React Hooks
91
+ Available from `@archlast/client/react`:
188
92
 
189
- ### useQuery
93
+ - `useQuery` / `useMutation` for live queries and mutations
94
+ - `usePagination` for cursor based queries
95
+ - `useAuth` for Better Auth session state
96
+ - `useUpload`, `useDownload` for storage
97
+ - `useStorageList`, `useStorageMetadata`, `useStorageDeleteFile`
190
98
 
191
- Subscribe to real-time query updates with automatic caching via TanStack Query.
99
+ Example:
192
100
 
193
101
  ```ts
194
- import { useQuery } from "@archlast/client/react";
195
- import { api } from "./_generated/api";
196
-
197
- function TaskList() {
198
- // Basic usage
199
- const tasks = useQuery(api.tasks.list, {});
200
-
201
- // With arguments
202
- const task = useQuery(api.tasks.get, { id: "123" });
203
-
204
- // With options
205
- const { data, isLoading, error, refetch } = useQuery(
206
- api.tasks.list,
207
- { completed: false },
208
- {
209
- // TanStack Query options
210
- staleTime: 5000,
211
- refetchOnWindowFocus: true,
212
- }
213
- );
214
-
215
- if (isLoading) return <Spinner />;
216
- if (error) return <Error message={error.message} />;
217
-
218
- return <TaskGrid tasks={data} />;
219
- }
102
+ const items = useQuery(api.items.list, { limit: 10 });
103
+ const createItem = useMutation(api.items.create);
220
104
  ```
221
105
 
222
- ### useMutation
223
-
224
- Execute mutations with automatic cache invalidation.
106
+ ## Direct client usage
225
107
 
226
108
  ```ts
227
- import { useMutation } from "@archlast/client/react";
228
-
229
- function CreateTaskForm() {
230
- const createTask = useMutation(api.tasks.create);
231
-
232
- const handleSubmit = async (e: FormEvent) => {
233
- e.preventDefault();
234
- const formData = new FormData(e.target as HTMLFormElement);
235
-
236
- try {
237
- const task = await createTask({
238
- text: formData.get("text") as string,
239
- priority: "medium"
240
- });
241
- console.log("Created:", task._id);
242
- } catch (error) {
243
- console.error("Failed:", error);
244
- }
245
- };
246
-
247
- return (
248
- <form onSubmit={handleSubmit}>
249
- <input name="text" required />
250
- <button type="submit">Create</button>
251
- </form>
252
- );
253
- }
109
+ const data = await client.fetch("tasks.get", { id: "123" });
110
+ const updated = await client.mutate("tasks.update", { id: "123", text: "Hi" });
254
111
  ```
255
112
 
256
- ### usePagination
257
-
258
- Handle cursor-based pagination with infinite scroll support.
259
-
260
- ```ts
261
- import { usePagination } from "@archlast/client/react";
262
-
263
- function InfiniteTaskList() {
264
- const {
265
- items,
266
- isLoading,
267
- hasMore,
268
- loadMore,
269
- error
270
- } = usePagination(api.tasks.listPaginated, {
271
- limit: 20,
272
- filter: { completed: false }
273
- });
274
-
275
- return (
276
- <div>
277
- <ul>
278
- {items.map(task => (
279
- <li key={task._id}>{task.text}</li>
280
- ))}
281
- </ul>
282
-
283
- {hasMore && (
284
- <button
285
- onClick={loadMore}
286
- disabled={isLoading}
287
- >
288
- {isLoading ? "Loading..." : "Load More"}
289
- </button>
290
- )}
291
- </div>
292
- );
293
- }
294
- ```
295
-
296
- **Server-side query must return:**
297
-
298
- ```ts
299
- interface PaginatedResult<T> {
300
- items: T[];
301
- continueCursor: string | null;
302
- isDone: boolean;
303
- }
304
- ```
305
-
306
- ### useUpload
307
-
308
- Upload files with progress tracking.
309
-
310
- ```ts
311
- import { useUpload } from "@archlast/client/react";
312
-
313
- function FileUploader() {
314
- const {
315
- upload,
316
- progress,
317
- isUploading,
318
- storageId,
319
- error
320
- } = useUpload();
321
-
322
- const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
323
- const file = e.target.files?.[0];
324
- if (!file) return;
325
-
326
- const id = await upload(file, "images");
327
- console.log("Uploaded with ID:", id);
328
- };
329
-
330
- return (
331
- <div>
332
- <input type="file" onChange={handleFileChange} disabled={isUploading} />
333
-
334
- {isUploading && (
335
- <div>
336
- <progress value={progress} max={100} />
337
- <span>{progress}%</span>
338
- </div>
339
- )}
340
-
341
- {storageId && <p>Uploaded: {storageId}</p>}
342
- {error && <p className="error">{error.message}</p>}
343
- </div>
344
- );
345
- }
346
- ```
347
-
348
- ### useAuth
349
-
350
- Authentication state and actions.
351
-
352
- ```ts
353
- import { useAuth } from "@archlast/client/react";
354
-
355
- function AuthButton() {
356
- const {
357
- signIn,
358
- signOut,
359
- isAuthenticated,
360
- user,
361
- isLoading
362
- } = useAuth();
363
-
364
- if (isLoading) return <Spinner />;
365
-
366
- if (isAuthenticated) {
367
- return (
368
- <div>
369
- <span>Welcome, {user?.name}</span>
370
- <button onClick={signOut}>Sign Out</button>
371
- </div>
372
- );
373
- }
374
-
375
- return (
376
- <button onClick={() => signIn({ provider: "google" })}>
377
- Sign in with Google
378
- </button>
379
- );
380
- }
381
- ```
382
-
383
- ---
384
-
385
- ## Direct Client Usage
386
-
387
- Use the client directly without React for Node.js, scripts, or non-React frameworks.
388
-
389
- ### Fetch (Query)
390
-
391
- ```ts
392
- // Fetch a single query
393
- const tasks = await client.fetch("tasks.list", {});
394
-
395
- // With arguments
396
- const task = await client.fetch("tasks.get", { id: "123" });
397
- ```
398
-
399
- ### Mutate
400
-
401
- ```ts
402
- // Execute a mutation
403
- const created = await client.mutate("tasks.create", {
404
- text: "New task",
405
- priority: "high"
406
- });
407
-
408
- // Update
409
- await client.mutate("tasks.update", {
410
- id: "123",
411
- completed: true
412
- });
413
-
414
- // Delete
415
- await client.mutate("tasks.delete", { id: "123" });
416
- ```
417
-
418
- ### Subscribe
419
-
420
- ```ts
421
- // Manual WebSocket subscription
422
- const unsubscribe = client.subscribe("tasks.list", {}, (data) => {
423
- console.log("Tasks updated:", data);
424
- });
425
-
426
- // Later: unsubscribe
427
- unsubscribe();
428
- ```
429
-
430
- ---
431
-
432
113
  ## Authentication
433
114
 
434
- ### Using the Auth Client
115
+ Auth uses Better Auth endpoints under `/api/auth/*`.
435
116
 
436
117
  ```ts
437
- import { ArchlastAuthClient } from "@archlast/client/auth";
438
-
439
- const auth = new ArchlastAuthClient({
440
- baseUrl: "http://localhost:4000"
441
- });
442
-
443
- // Email/password sign up
444
- await auth.signUp({
445
- email: "user@example.com",
446
- password: "secure_password",
447
- name: "John Doe"
448
- });
449
-
450
- // Email/password sign in
451
- await auth.signIn({
452
- email: "user@example.com",
453
- password: "secure_password"
454
- });
455
-
456
- // OAuth sign in
457
- await auth.signIn({ provider: "google" });
458
- await auth.signIn({ provider: "github" });
459
-
460
- // Get current session
461
- const session = await auth.getSession();
462
-
463
- // Sign out
464
- await auth.signOut();
465
- ```
466
-
467
- ### Auth State in Components
468
-
469
- ```tsx
470
118
  import { useAuth } from "@archlast/client/react";
471
119
 
472
- function ProtectedRoute({ children }) {
473
- const { isAuthenticated, isLoading } = useAuth();
474
-
475
- if (isLoading) return <LoadingScreen />;
476
- if (!isAuthenticated) return <Navigate to="/login" />;
477
-
478
- return children;
479
- }
120
+ const { signIn, signOut, isAuthenticated } = useAuth();
480
121
  ```
481
122
 
482
- ---
483
-
484
- ## File Storage
485
-
486
- ### Storage Client
123
+ Or use the auth client directly:
487
124
 
488
125
  ```ts
489
- // Access via main client
490
- const storage = client.storage;
491
-
492
- // Or import directly
493
- import { StorageClient } from "@archlast/client/storage";
494
- const storage = new StorageClient(httpUrl, getAuthHeaders);
495
- ```
496
-
497
- ### Upload Files
498
-
499
- ```ts
500
- const file = new File(["Hello, World!"], "hello.txt", { type: "text/plain" });
501
-
502
- // Basic upload
503
- const result = await client.storage.upload(file);
504
- console.log("Storage ID:", result.id);
505
- console.log("URL:", result.url);
506
-
507
- // Upload to specific bucket
508
- const imageResult = await client.storage.upload(imageFile, "images");
509
- ```
510
-
511
- ### List Files
512
-
513
- ```ts
514
- // List all files
515
- const files = await client.storage.list();
516
-
517
- // List files in bucket
518
- const images = await client.storage.list("images");
519
-
520
- // With pagination
521
- const page1 = await client.storage.list("images", { limit: 20 });
522
- const page2 = await client.storage.list("images", {
523
- limit: 20,
524
- cursor: page1.nextCursor
525
- });
526
- ```
527
-
528
- ### Get Presigned URLs
529
-
530
- ```ts
531
- // Get temporary signed URL for download
532
- const { url, expiresAt } = await client.storage.presign(storageId);
126
+ import { ArchlastAuthClient } from "@archlast/client/auth";
533
127
 
534
- // Use in img tag
535
- <img src={url} alt="Uploaded file" />
128
+ const auth = new ArchlastAuthClient({ baseUrl: "http://localhost:4000" });
536
129
  ```
537
130
 
538
- ### Get Metadata
131
+ ## Storage
539
132
 
540
133
  ```ts
541
- const metadata = await client.storage.getMetadata(storageId);
542
- console.log(metadata.filename); // "hello.txt"
543
- console.log(metadata.contentType); // "text/plain"
544
- console.log(metadata.size); // 13
545
- console.log(metadata.createdAt); // Date
134
+ const file = new File(["hello"], "hello.txt", { type: "text/plain" });
135
+ const uploaded = await client.storage.upload(file);
136
+ const list = await client.storage.list();
137
+ const { url } = await client.storage.presign(uploaded.id);
546
138
  ```
547
139
 
548
- ---
549
-
550
140
  ## Admin APIs
551
141
 
552
- Access admin functionality (requires admin privileges).
553
-
554
142
  ```ts
555
- // Auth operations
556
143
  const profile = await client.admin.auth.getProfile();
557
- const users = await client.admin.auth.listUsers();
558
- await client.admin.auth.updateUser(userId, { role: "admin" });
559
-
560
- // API key management
561
- const keys = await client.admin.apiKeys.list();
562
- const newKey = await client.admin.apiKeys.create({ name: "CI/CD" });
563
- await client.admin.apiKeys.revoke(keyId);
564
-
565
- // Data operations
566
- const collections = await client.admin.data.listCollections();
567
- const docs = await client.admin.data.query("tasks", { limit: 100 });
568
144
  ```
569
145
 
570
- ---
571
-
572
- ## tRPC Integration
573
-
574
- For type-safe RPC with full TypeScript inference.
575
-
576
- ### Setup
146
+ ## tRPC integration
577
147
 
578
148
  ```ts
579
149
  import { createArchlastTRPCClient } from "@archlast/client/trpc";
@@ -581,246 +151,24 @@ import type { AppRouter } from "./_generated/rpc";
581
151
 
582
152
  const trpc = createArchlastTRPCClient<AppRouter>({
583
153
  baseUrl: "http://localhost:4000",
584
- apiKey: "arch_your_api_key"
154
+ apiKey: "arch_example"
585
155
  });
586
156
  ```
587
157
 
588
- ### Usage
589
-
590
- ```ts
591
- // Queries
592
- const tasks = await trpc.tasks.list.query({});
593
- const task = await trpc.tasks.get.query({ id: "123" });
594
-
595
- // Mutations
596
- const created = await trpc.tasks.create.mutate({ text: "New task" });
597
- await trpc.tasks.update.mutate({ id: "123", completed: true });
598
- ```
599
-
600
- ### React Query Integration
601
-
602
- ```tsx
603
- import { createTRPCReact } from "@trpc/react-query";
604
- import type { AppRouter } from "./_generated/rpc";
605
-
606
- export const trpc = createTRPCReact<AppRouter>();
607
-
608
- // In component
609
- function TaskList() {
610
- const { data, isLoading } = trpc.tasks.list.useQuery({});
611
- const createTask = trpc.tasks.create.useMutation();
612
-
613
- // ...
614
- }
615
- ```
616
-
617
- ---
618
-
619
- ## Subpath Exports
620
-
621
- Import specific functionality to reduce bundle size:
622
-
623
- | Import | Description |
624
- |--------|-------------|
625
- | `@archlast/client` | Core `ArchlastClient` class |
626
- | `@archlast/client/react` | React hooks and provider |
627
- | `@archlast/client/auth` | Authentication client |
628
- | `@archlast/client/storage` | File storage client |
629
- | `@archlast/client/admin` | Admin API client |
630
- | `@archlast/client/trpc` | tRPC client factory |
631
-
632
- ```ts
633
- // Minimal import for core functionality
634
- import { ArchlastClient } from "@archlast/client";
635
-
636
- // React-specific imports
637
- import {
638
- ArchlastProvider,
639
- useQuery,
640
- useMutation,
641
- usePagination,
642
- useUpload,
643
- useAuth
644
- } from "@archlast/client/react";
645
-
646
- // Auth-only import
647
- import { ArchlastAuthClient } from "@archlast/client/auth";
648
- ```
649
-
650
- ---
651
-
652
- ## TypeScript Support
653
-
654
- The client is fully typed. Types are generated by the CLI based on your schema.
655
-
656
- ### Generated Types
657
-
658
- ```ts
659
- // _generated/api.ts
660
- export const api = {
661
- tasks: {
662
- list: { _path: "tasks.list", _returnType: {} as Task[] },
663
- get: { _path: "tasks.get", _returnType: {} as Task | null },
664
- create: { _path: "tasks.create", _returnType: {} as Task },
665
- // ...
666
- }
667
- };
668
- ```
669
-
670
- ### Usage with Types
671
-
672
- ```ts
673
- import { useQuery } from "@archlast/client/react";
674
- import { api } from "./_generated/api";
675
-
676
- // TypeScript knows `tasks` is Task[]
677
- const tasks = useQuery(api.tasks.list, {});
678
-
679
- // TypeScript knows `task` is Task | null
680
- const task = useQuery(api.tasks.get, { id: "123" });
681
- ```
682
-
683
- ---
684
-
685
- ## Error Handling
686
-
687
- ### Query Errors
688
-
689
- ```tsx
690
- function TaskList() {
691
- const { data, error, isError } = useQuery(api.tasks.list, {});
692
-
693
- if (isError) {
694
- return (
695
- <div className="error">
696
- <h3>Failed to load tasks</h3>
697
- <p>{error.message}</p>
698
- <button onClick={() => refetch()}>Retry</button>
699
- </div>
700
- );
701
- }
702
-
703
- return <TaskGrid tasks={data} />;
704
- }
705
- ```
706
-
707
- ### Mutation Errors
708
-
709
- ```ts
710
- const createTask = useMutation(api.tasks.create);
711
-
712
- try {
713
- await createTask({ text: "" }); // Invalid
714
- } catch (error) {
715
- if (error instanceof ValidationError) {
716
- console.log("Validation failed:", error.issues);
717
- } else if (error instanceof AuthenticationError) {
718
- console.log("Not authenticated");
719
- } else {
720
- console.log("Unknown error:", error);
721
- }
722
- }
723
- ```
724
-
725
- ### Global Error Boundary
726
-
727
- ```tsx
728
- import { QueryErrorResetBoundary } from "@tanstack/react-query";
729
- import { ErrorBoundary } from "react-error-boundary";
730
-
731
- function App() {
732
- return (
733
- <QueryErrorResetBoundary>
734
- {({ reset }) => (
735
- <ErrorBoundary
736
- onReset={reset}
737
- fallbackRender={({ error, resetErrorBoundary }) => (
738
- <div>
739
- <p>Something went wrong: {error.message}</p>
740
- <button onClick={resetErrorBoundary}>Try again</button>
741
- </div>
742
- )}
743
- >
744
- <ArchlastProvider client={client}>
745
- <YourApp />
746
- </ArchlastProvider>
747
- </ErrorBoundary>
748
- )}
749
- </QueryErrorResetBoundary>
750
- );
751
- }
752
- ```
753
-
754
- ---
755
-
756
- ## Advanced Patterns
757
-
758
- ### Optimistic Updates
759
-
760
- ```ts
761
- const queryClient = useQueryClient();
762
-
763
- const updateTask = useMutation(api.tasks.update, {
764
- onMutate: async (newData) => {
765
- // Cancel outgoing refetches
766
- await queryClient.cancelQueries(["tasks.list"]);
767
-
768
- // Snapshot previous value
769
- const previous = queryClient.getQueryData(["tasks.list"]);
770
-
771
- // Optimistically update
772
- queryClient.setQueryData(["tasks.list"], (old: Task[]) =>
773
- old.map(t => t._id === newData.id ? { ...t, ...newData } : t)
774
- );
775
-
776
- return { previous };
777
- },
778
- onError: (err, newData, context) => {
779
- // Rollback on error
780
- queryClient.setQueryData(["tasks.list"], context.previous);
781
- }
782
- });
783
- ```
784
-
785
- ### SSR / Server Components
786
-
787
- ```ts
788
- // For Next.js SSR
789
- const client = new ArchlastClient(
790
- wsUrl,
791
- httpUrl,
792
- "web",
793
- undefined,
794
- {
795
- autoConnect: false, // Don't connect on server
796
- betterAuthCookies: cookies() // Pass request cookies
797
- }
798
- );
799
- ```
800
-
801
- ### Multiple Clients
802
-
803
- ```tsx
804
- const mainClient = new ArchlastClient(mainWsUrl, mainHttpUrl, "main");
805
- const analyticsClient = new ArchlastClient(analyticsWsUrl, analyticsHttpUrl, "analytics");
806
-
807
- function App() {
808
- return (
809
- <ArchlastProvider client={mainClient}>
810
- <ArchlastProvider client={analyticsClient} contextKey="analytics">
811
- <YourApp />
812
- </ArchlastProvider>
813
- </ArchlastProvider>
814
- );
815
- }
816
- ```
158
+ ## Subpath exports
817
159
 
818
- ---
160
+ - `@archlast/client/react`
161
+ - `@archlast/client/trpc`
162
+ - `@archlast/client/auth`
163
+ - `@archlast/client/storage`
164
+ - `@archlast/client/admin`
819
165
 
820
- ## Publishing (Maintainers)
166
+ ## Troubleshooting
821
167
 
822
- See `docs/npm-publishing.md` for release and publish steps.
168
+ - WebSocket errors: use `ws://<host>/ws` and confirm CORS settings.
169
+ - Auth errors: ensure cookies or API key are configured.
170
+ - Storage uploads: use `File` or `Blob` for browser uploads.
823
171
 
824
- ## License
172
+ ## Publishing (maintainers)
825
173
 
826
- MIT
174
+ See `docs/npm-publishing.md` for release and publish steps.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archlast/client",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Archlast client SDK for React and API access",
5
5
  "license": "MIT",
6
6
  "repository": {