@archlast/client 0.1.1 → 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 +69 -855
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,713 +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
64
- undefined, // Optional: auth URL (for same-origin proxy)
65
- {
66
- autoConnect: true, // Auto-connect WebSocket (default: true)
67
- isAdmin: false, // Admin mode (default: false)
68
- apiKey: "arch_key", // Optional: Better-Auth API key
69
- betterAuthCookies: {}, // Optional: Better-Auth session cookies
70
- }
32
+ "ws://localhost:4000/ws",
33
+ "http://localhost:4000",
34
+ "web"
71
35
  );
72
- ```
73
-
74
- ### 2. Set Up the Provider
75
-
76
- ```tsx
77
- import { ArchlastProvider } from "@archlast/client/react";
78
36
 
79
37
  function App() {
80
38
  return (
81
39
  <ArchlastProvider client={client}>
82
- <YourApp />
40
+ <TodoList />
83
41
  </ArchlastProvider>
84
42
  );
85
43
  }
86
- ```
87
-
88
- ### 3. Use Hooks in Components
89
-
90
- ```tsx
91
- import { useQuery, useMutation } from "@archlast/client/react";
92
- import { api } from "./_generated/api";
93
44
 
94
45
  function TodoList() {
95
- // Subscribe to live updates
96
46
  const tasks = useQuery(api.tasks.list, {});
97
-
98
- // Mutation with automatic cache invalidation
99
47
  const createTask = useMutation(api.tasks.create);
100
48
 
101
- const handleAdd = async () => {
102
- await createTask({ text: "New task" });
103
- // Cache automatically invalidated, UI updates reactively
104
- };
105
-
106
- if (!tasks) return <div>Loading...</div>;
107
-
108
49
  return (
109
50
  <div>
110
- <ul>
111
- {tasks.map(task => (
112
- <li key={task._id}>{task.text}</li>
113
- ))}
114
- </ul>
115
- <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>
116
55
  </div>
117
56
  );
118
57
  }
119
58
  ```
120
59
 
121
- > **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`.
122
61
 
123
- ---
62
+ ## Client configuration
124
63
 
125
- ## Client Configuration
126
-
127
- ### Constructor Signature
64
+ Constructor signature:
128
65
 
129
66
  ```ts
130
- new ArchlastClient(wsUrl, httpUrl, appId, authUrl?, options?)
67
+ new ArchlastClient(wsUrl, httpUrl?, appId?, authUrl?, options?)
131
68
  ```
132
69
 
133
- ### Parameters
134
-
135
- | Parameter | Type | Description |
136
- |-----------|------|-------------|
137
- | `wsUrl` | `string` | WebSocket endpoint (e.g., `ws://localhost:4000/ws`) |
138
- | `httpUrl` | `string` | HTTP base URL (e.g., `http://localhost:4000`) |
139
- | `appId` | `string` | Application identifier (e.g., `"web"`, `"mobile"`) |
140
- | `authUrl` | `string?` | Optional custom auth URL (defaults to `httpUrl`) |
141
- | `options` | `object?` | Additional configuration options |
142
-
143
- ### Options
144
-
145
- ```ts
146
- interface ClientOptions {
147
- // Auto-connect WebSocket on instantiation (default: true)
148
- autoConnect?: boolean;
149
-
150
- // API key for authentication (arch_ prefix)
151
- apiKey?: string;
152
-
153
- // Enable admin WebSocket subscriptions
154
- isAdmin?: boolean;
155
-
156
- // Better Auth session cookies for SSR
157
- betterAuthCookies?: Record<string, string>;
158
- }
159
- ```
160
-
161
- ### 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
162
75
 
163
76
  ```ts
164
77
  const client = new ArchlastClient(
165
- "wss://api.myapp.com/ws",
166
- "https://api.myapp.com",
78
+ "ws://localhost:4000/ws",
79
+ "http://localhost:4000",
167
80
  "web",
168
- "https://auth.myapp.com", // Custom auth URL
169
- {
170
- autoConnect: true,
171
- apiKey: "arch_your_api_key",
172
- isAdmin: false,
173
- betterAuthCookies: {
174
- "better-auth.session": "session_token_here"
175
- }
176
- }
81
+ undefined,
82
+ { apiKey: "arch_example" }
177
83
  );
178
- ```
179
-
180
- ### Runtime Credential Updates
181
-
182
- ```ts
183
- // Rotate API key
184
- client.setApiKey("arch_new_api_key");
185
-
186
- // Update session cookies (for SSR)
187
- client.setBetterAuthCookies({
188
- "better-auth.session": "new_session_token"
189
- });
190
- ```
191
-
192
- ---
193
-
194
- ## React Hooks
195
-
196
- ### useQuery
197
-
198
- Subscribe to real-time query updates with automatic caching via TanStack Query.
199
-
200
- ```ts
201
- import { useQuery } from "@archlast/client/react";
202
- import { api } from "./_generated/api";
203
84
 
204
- function TaskList() {
205
- // Basic usage
206
- const tasks = useQuery(api.tasks.list, {});
207
-
208
- // With arguments
209
- const task = useQuery(api.tasks.get, { id: "123" });
210
-
211
- // With options
212
- const { data, isLoading, error, refetch } = useQuery(
213
- api.tasks.list,
214
- { completed: false },
215
- {
216
- // TanStack Query options
217
- staleTime: 5000,
218
- refetchOnWindowFocus: true,
219
- }
220
- );
221
-
222
- if (isLoading) return <Spinner />;
223
- if (error) return <Error message={error.message} />;
224
-
225
- return <TaskGrid tasks={data} />;
226
- }
85
+ client.setApiKey("arch_new_key");
86
+ client.setBetterAuthCookies({ "better-auth.session": "..." });
227
87
  ```
228
88
 
229
- ### useMutation
230
-
231
- Execute mutations with automatic cache invalidation.
89
+ ## React hooks
232
90
 
233
- ```ts
234
- import { useMutation } from "@archlast/client/react";
91
+ Available from `@archlast/client/react`:
235
92
 
236
- function CreateTaskForm() {
237
- const createTask = useMutation(api.tasks.create);
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`
238
98
 
239
- const handleSubmit = async (e: FormEvent) => {
240
- e.preventDefault();
241
- const formData = new FormData(e.target as HTMLFormElement);
242
-
243
- try {
244
- const task = await createTask({
245
- text: formData.get("text") as string,
246
- priority: "medium"
247
- });
248
- console.log("Created:", task._id);
249
- } catch (error) {
250
- console.error("Failed:", error);
251
- }
252
- };
253
-
254
- return (
255
- <form onSubmit={handleSubmit}>
256
- <input name="text" required />
257
- <button type="submit">Create</button>
258
- </form>
259
- );
260
- }
261
- ```
262
-
263
- ### usePagination
264
-
265
- Handle cursor-based pagination with infinite scroll support.
99
+ Example:
266
100
 
267
101
  ```ts
268
- import { usePagination } from "@archlast/client/react";
269
-
270
- function InfiniteTaskList() {
271
- const {
272
- items, // Accumulated items across all pages
273
- cursor, // Current cursor for next page
274
- isDone, // Whether pagination is complete
275
- hasMore, // Convenience flag: !isDone && cursor exists
276
- isLoading, // Loading state
277
- loadMore, // Function to load next page
278
- refresh, // Function to reset and reload from first page
279
- page, // Optional: current page number (if server provides)
280
- pageSize, // Optional: page size (if server provides)
281
- total, // Optional: total count (if server provides)
282
- } = usePagination(
283
- api.tasks.listPaginated,
284
- { completed: false }, // Base args (no cursor/limit)
285
- { limit: 20 } // Options
286
- );
287
-
288
- return (
289
- <div>
290
- <ul>
291
- {items.map(task => (
292
- <li key={task._id}>{task.text}</li>
293
- ))}
294
- </ul>
295
-
296
- {hasMore && (
297
- <button
298
- onClick={loadMore}
299
- disabled={isLoading}
300
- >
301
- {isLoading ? "Loading..." : "Load More"}
302
- </button>
303
- )}
304
-
305
- <button onClick={refresh}>Refresh</button>
306
- </div>
307
- );
308
- }
102
+ const items = useQuery(api.items.list, { limit: 10 });
103
+ const createItem = useMutation(api.items.create);
309
104
  ```
310
105
 
311
- **Server-side query must return:**
106
+ ## Direct client usage
312
107
 
313
108
  ```ts
314
- interface PaginatedResponse<T> {
315
- items: T[];
316
- continueCursor: string | null;
317
- isDone: boolean;
318
- page?: number; // Optional
319
- pageSize?: number; // Optional
320
- total?: number; // Optional
321
- }
322
- continueCursor: string | null;
323
- isDone: boolean;
324
- }
109
+ const data = await client.fetch("tasks.get", { id: "123" });
110
+ const updated = await client.mutate("tasks.update", { id: "123", text: "Hi" });
325
111
  ```
326
112
 
327
- ### useUpload
328
-
329
- Upload files with progress tracking.
330
-
331
- ```ts
332
- import { useUpload } from "@archlast/client/react";
333
-
334
- function FileUploader() {
335
- const {
336
- upload,
337
- progress,
338
- isUploading,
339
- storageId,
340
- error
341
- } = useUpload();
342
-
343
- const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
344
- const file = e.target.files?.[0];
345
- if (!file) return;
346
-
347
- const id = await upload(file, "images");
348
- console.log("Uploaded with ID:", id);
349
- };
350
-
351
- return (
352
- <div>
353
- <input type="file" onChange={handleFileChange} disabled={isUploading} />
354
-
355
- {isUploading && (
356
- <div>
357
- <progress value={progress} max={100} />
358
- <span>{progress}%</span>
359
- </div>
360
- )}
361
-
362
- {storageId && <p>Uploaded: {storageId}</p>}
363
- {error && <p className="error">{error.message}</p>}
364
- </div>
365
- );
366
- }
367
- ```
368
-
369
- ### useAuth
370
-
371
- Authentication state and actions via the `ArchlastAuthClient` wrapper.
372
-
373
- ```ts
374
- import { useAuth } from "@archlast/client/react";
375
-
376
- function AuthButton() {
377
- const {
378
- isAuthenticated,
379
- user,
380
- isLoading,
381
- signIn,
382
- signOut
383
- } = useAuth();
384
-
385
- if (isLoading) return <Spinner />;
386
-
387
- if (isAuthenticated) {
388
- return (
389
- <div>
390
- <span>Welcome, {user?.name}</span>
391
- <button onClick={() => signOut()}>
392
- Sign Out
393
- </button>
394
- </div>
395
- );
396
- }
397
-
398
- return (
399
- <button onClick={() => signIn({ email: "user@example.com", password: "..." })}>
400
- Sign In
401
- </button>
402
- );
403
- }
404
- ```
405
-
406
- ### Using Better-Auth React Hooks
407
-
408
- For full-featured auth in React, use Better-Auth's `useSession` hook:
409
-
410
- ```tsx
411
- import { authClient } from "./lib/better-auth"; // Your Better-Auth client
412
-
413
- function AuthButton() {
414
- const { data: session, isPending } = authClient.useSession();
415
-
416
- if (isPending) return <Spinner />;
417
-
418
- if (session?.user) {
419
- return (
420
- <div>
421
- <span>Welcome, {session.user.name}</span>
422
- <button onClick={() => authClient.signOut()}>
423
- Sign Out
424
- </button>
425
- </div>
426
- );
427
- }
428
-
429
- return (
430
- <button onClick={() => authClient.signIn.email({ email, password })}>
431
- Sign In
432
- </button>
433
- );
434
- }
435
- ```
436
-
437
- ---
438
-
439
- ## Direct Client Usage
440
-
441
- Use the client directly without React for Node.js, scripts, or non-React frameworks.
442
-
443
- ### Fetch (Query)
444
-
445
- ```ts
446
- // Fetch a single query
447
- const tasks = await client.fetch("tasks.list", {});
448
-
449
- // With arguments
450
- const task = await client.fetch("tasks.get", { id: "123" });
451
- ```
452
-
453
- ### Mutate
454
-
455
- ```ts
456
- // Execute a mutation
457
- const created = await client.mutate("tasks.create", {
458
- text: "New task",
459
- priority: "high"
460
- });
461
-
462
- // Update
463
- await client.mutate("tasks.update", {
464
- id: "123",
465
- completed: true
466
- });
467
-
468
- // Delete
469
- await client.mutate("tasks.delete", { id: "123" });
470
- ```
471
-
472
- ### Subscribe
473
-
474
- ```ts
475
- // Manual WebSocket subscription
476
- const unsubscribe = client.subscribe("tasks.list", {}, (data) => {
477
- console.log("Tasks updated:", data);
478
- });
479
-
480
- // Later: unsubscribe
481
- unsubscribe();
482
- ```
483
-
484
- ---
485
-
486
113
  ## Authentication
487
114
 
488
- Archlast uses [Better-Auth](https://www.better-auth.com/) for authentication. The server exposes Better-Auth endpoints at `/api/auth/*`.
489
-
490
- ### Using the Auth Client
491
-
492
- The `ArchlastAuthClient` provides a wrapper around Better-Auth endpoints:
493
-
494
- ```ts
495
- import { ArchlastAuthClient } from "@archlast/client/auth";
496
-
497
- const auth = new ArchlastAuthClient({
498
- baseUrl: "http://localhost:4000",
499
- apiKey: "arch_your_api_key" // Optional: for programmatic access
500
- });
501
-
502
- // Sign up with email/password
503
- // POST /api/auth/sign-up/email
504
- await auth.signUp({
505
- email: "user@example.com",
506
- password: "secure_password",
507
- name: "John Doe"
508
- });
509
-
510
- // Sign in with email/password
511
- // POST /api/auth/sign-in/email
512
- await auth.signIn({
513
- email: "user@example.com",
514
- password: "secure_password"
515
- });
516
-
517
- // Sign in with username (if username plugin enabled)
518
- await auth.signIn({
519
- username: "johndoe",
520
- password: "secure_password"
521
- });
522
-
523
- // Get current session
524
- // GET /api/auth/get-session
525
- const state = await auth.getState();
526
- console.log(state.isAuthenticated, state.user);
527
-
528
- // Sign out
529
- await auth.signOut();
530
- ```
531
-
532
- ### Using Better-Auth Client Directly
533
-
534
- For full Better-Auth features (OAuth, organization, admin), use the Better-Auth client directly:
115
+ Auth uses Better Auth endpoints under `/api/auth/*`.
535
116
 
536
117
  ```ts
537
- import { createAuthClient } from "better-auth/react";
538
- import {
539
- adminClient,
540
- usernameClient,
541
- organizationClient,
542
- // Optional plugins (add as needed):
543
- // anonymousClient, // For guest users
544
- // apiKeyClient, // For API key management
545
- } from "better-auth/client/plugins";
546
-
547
- const authClient = createAuthClient({
548
- baseURL: "http://localhost:4000/api/auth",
549
- fetchOptions: { credentials: "include" },
550
- plugins: [
551
- adminClient(),
552
- usernameClient(),
553
- organizationClient(),
554
- ],
555
- });
556
-
557
- // Email sign in (typical for user-facing apps)
558
- await authClient.signIn.email({
559
- email: "user@example.com",
560
- password: "secure_password",
561
- });
562
-
563
- // Username sign in (used by dashboard)
564
- await authClient.signIn.username({
565
- username: "admin",
566
- password: "secure_password",
567
- });
568
-
569
- // Session management
570
- const { data: session } = authClient.useSession();
571
-
572
- // Organization management
573
- await authClient.organization.create({ name: "My Team" });
574
-
575
- // Admin operations (requires admin role)
576
- await authClient.admin.listUsers({ limit: 10 });
577
- ```
578
-
579
- ### Auth State in Components
580
-
581
- ```tsx
582
118
  import { useAuth } from "@archlast/client/react";
583
119
 
584
- function ProtectedRoute({ children }) {
585
- const { isAuthenticated, isLoading } = useAuth();
586
-
587
- if (isLoading) return <LoadingScreen />;
588
- if (!isAuthenticated) return <Navigate to="/login" />;
589
-
590
- return children;
591
- }
592
- ```
593
-
594
- ---
595
-
596
- ## File Storage
597
-
598
- ### Storage Client
599
-
600
- ```ts
601
- // Access via main client
602
- const storage = client.storage;
603
-
604
- // Or import directly
605
- import { StorageClient } from "@archlast/client/storage";
606
- const storage = new StorageClient(httpUrl, getAuthHeaders);
607
- ```
608
-
609
- ### Upload Files
610
-
611
- ```ts
612
- const file = new File(["Hello, World!"], "hello.txt", { type: "text/plain" });
613
-
614
- // Basic upload
615
- const result = await client.storage.upload(file);
616
- console.log("Storage ID:", result.id);
617
- console.log("URL:", result.url);
618
-
619
- // Upload with explicit content type
620
- const blob = new Blob(["data"], { type: "application/octet-stream" });
621
- const binaryResult = await client.storage.upload(blob, "application/octet-stream");
120
+ const { signIn, signOut, isAuthenticated } = useAuth();
622
121
  ```
623
122
 
624
- ### List Files
625
-
626
- ### List Files
123
+ Or use the auth client directly:
627
124
 
628
125
  ```ts
629
- // List files with pagination
630
- const { items } = await client.storage.list(20, 0); // limit, offset
631
-
632
- // Next page
633
- const page2 = await client.storage.list(20, 20);
634
-
635
- // Items structure
636
- items.forEach(file => {
637
- console.log(file.id); // Storage ID
638
- console.log(file.fileName); // Original filename
639
- console.log(file.contentType); // MIME type
640
- console.log(file.size); // Size in bytes
641
- console.log(file.url); // Public URL
642
- });
643
- ```
644
-
645
- ### Get Presigned URLs
646
-
647
- ```ts
648
- // Get temporary signed URL for download
649
- const { url, expiresAt } = await client.storage.presign(storageId);
126
+ import { ArchlastAuthClient } from "@archlast/client/auth";
650
127
 
651
- // Use in img tag
652
- <img src={url} alt="Uploaded file" />
128
+ const auth = new ArchlastAuthClient({ baseUrl: "http://localhost:4000" });
653
129
  ```
654
130
 
655
- ### Get Metadata
131
+ ## Storage
656
132
 
657
133
  ```ts
658
- const metadata = await client.storage.getMetadata(storageId);
659
- console.log(metadata.filename); // "hello.txt"
660
- console.log(metadata.contentType); // "text/plain"
661
- console.log(metadata.size); // 13
662
- 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);
663
138
  ```
664
139
 
665
- ---
666
-
667
140
  ## Admin APIs
668
141
 
669
- Access admin functionality (requires admin privileges).
670
-
671
- ### Admin Auth Client
672
-
673
- The `AdminClient` currently provides authentication management:
674
-
675
142
  ```ts
676
- import { AdminClient } from "@archlast/client/admin";
677
-
678
- const admin = new AdminClient("http://localhost:4000", {
679
- apiKey: "arch_your_api_key" // Optional: Better-Auth API key
680
- });
681
-
682
- // Admin authentication
683
- await admin.auth.signIn(email, password);
684
- const { user } = await admin.auth.getProfile();
685
- await admin.auth.signOut();
686
-
687
- // Session management
688
- const { sessions } = await admin.auth.getSessions();
689
- await admin.auth.revokeSession(sessionId);
690
- await admin.auth.signOutAll();
143
+ const profile = await client.admin.auth.getProfile();
691
144
  ```
692
145
 
693
- **Available Methods:**
694
-
695
- - `admin.auth.signIn(email, password)` - Admin sign in
696
- - `admin.auth.signOut()` - Sign out current session
697
- - `admin.auth.getProfile()` - Get admin profile
698
- - `admin.auth.getSessions()` - List all sessions
699
- - `admin.auth.revokeSession(sessionId)` - Revoke specific session
700
- - `admin.auth.signOutAll()` - Sign out from all sessions
701
-
702
- **Note:** Additional admin clients (users, data, API keys) may be added in future releases.
703
-
704
- ---
705
-
706
- ## tRPC Integration
707
-
708
- For type-safe RPC with full TypeScript inference.
709
-
710
- ### Setup
146
+ ## tRPC integration
711
147
 
712
148
  ```ts
713
149
  import { createArchlastTRPCClient } from "@archlast/client/trpc";
@@ -715,246 +151,24 @@ import type { AppRouter } from "./_generated/rpc";
715
151
 
716
152
  const trpc = createArchlastTRPCClient<AppRouter>({
717
153
  baseUrl: "http://localhost:4000",
718
- apiKey: "arch_your_api_key"
154
+ apiKey: "arch_example"
719
155
  });
720
156
  ```
721
157
 
722
- ### Usage
723
-
724
- ```ts
725
- // Queries
726
- const tasks = await trpc.tasks.list.query({});
727
- const task = await trpc.tasks.get.query({ id: "123" });
728
-
729
- // Mutations
730
- const created = await trpc.tasks.create.mutate({ text: "New task" });
731
- await trpc.tasks.update.mutate({ id: "123", completed: true });
732
- ```
733
-
734
- ### React Query Integration
735
-
736
- ```tsx
737
- import { createTRPCReact } from "@trpc/react-query";
738
- import type { AppRouter } from "./_generated/rpc";
739
-
740
- export const trpc = createTRPCReact<AppRouter>();
741
-
742
- // In component
743
- function TaskList() {
744
- const { data, isLoading } = trpc.tasks.list.useQuery({});
745
- const createTask = trpc.tasks.create.useMutation();
746
-
747
- // ...
748
- }
749
- ```
750
-
751
- ---
752
-
753
- ## Subpath Exports
754
-
755
- Import specific functionality to reduce bundle size:
756
-
757
- | Import | Description |
758
- |--------|-------------|
759
- | `@archlast/client` | Core `ArchlastClient` class |
760
- | `@archlast/client/react` | React hooks and provider |
761
- | `@archlast/client/auth` | Authentication client |
762
- | `@archlast/client/storage` | File storage client |
763
- | `@archlast/client/admin` | Admin API client |
764
- | `@archlast/client/trpc` | tRPC client factory |
765
-
766
- ```ts
767
- // Minimal import for core functionality
768
- import { ArchlastClient } from "@archlast/client";
769
-
770
- // React-specific imports
771
- import {
772
- ArchlastProvider,
773
- useQuery,
774
- useMutation,
775
- usePagination,
776
- useUpload,
777
- useAuth
778
- } from "@archlast/client/react";
779
-
780
- // Auth-only import
781
- import { ArchlastAuthClient } from "@archlast/client/auth";
782
- ```
783
-
784
- ---
785
-
786
- ## TypeScript Support
787
-
788
- The client is fully typed. Types are generated by the CLI based on your schema.
789
-
790
- ### Generated Types
791
-
792
- ```ts
793
- // _generated/api.ts
794
- export const api = {
795
- tasks: {
796
- list: { _path: "tasks.list", _returnType: {} as Task[] },
797
- get: { _path: "tasks.get", _returnType: {} as Task | null },
798
- create: { _path: "tasks.create", _returnType: {} as Task },
799
- // ...
800
- }
801
- };
802
- ```
803
-
804
- ### Usage with Types
805
-
806
- ```ts
807
- import { useQuery } from "@archlast/client/react";
808
- import { api } from "./_generated/api";
809
-
810
- // TypeScript knows `tasks` is Task[]
811
- const tasks = useQuery(api.tasks.list, {});
812
-
813
- // TypeScript knows `task` is Task | null
814
- const task = useQuery(api.tasks.get, { id: "123" });
815
- ```
816
-
817
- ---
818
-
819
- ## Error Handling
820
-
821
- ### Query Errors
822
-
823
- ```tsx
824
- function TaskList() {
825
- const { data, error, isError } = useQuery(api.tasks.list, {});
826
-
827
- if (isError) {
828
- return (
829
- <div className="error">
830
- <h3>Failed to load tasks</h3>
831
- <p>{error.message}</p>
832
- <button onClick={() => refetch()}>Retry</button>
833
- </div>
834
- );
835
- }
836
-
837
- return <TaskGrid tasks={data} />;
838
- }
839
- ```
840
-
841
- ### Mutation Errors
842
-
843
- ```ts
844
- const createTask = useMutation(api.tasks.create);
845
-
846
- try {
847
- await createTask({ text: "" }); // Invalid
848
- } catch (error) {
849
- if (error instanceof ValidationError) {
850
- console.log("Validation failed:", error.issues);
851
- } else if (error instanceof AuthenticationError) {
852
- console.log("Not authenticated");
853
- } else {
854
- console.log("Unknown error:", error);
855
- }
856
- }
857
- ```
858
-
859
- ### Global Error Boundary
860
-
861
- ```tsx
862
- import { QueryErrorResetBoundary } from "@tanstack/react-query";
863
- import { ErrorBoundary } from "react-error-boundary";
864
-
865
- function App() {
866
- return (
867
- <QueryErrorResetBoundary>
868
- {({ reset }) => (
869
- <ErrorBoundary
870
- onReset={reset}
871
- fallbackRender={({ error, resetErrorBoundary }) => (
872
- <div>
873
- <p>Something went wrong: {error.message}</p>
874
- <button onClick={resetErrorBoundary}>Try again</button>
875
- </div>
876
- )}
877
- >
878
- <ArchlastProvider client={client}>
879
- <YourApp />
880
- </ArchlastProvider>
881
- </ErrorBoundary>
882
- )}
883
- </QueryErrorResetBoundary>
884
- );
885
- }
886
- ```
887
-
888
- ---
889
-
890
- ## Advanced Patterns
891
-
892
- ### Optimistic Updates
893
-
894
- ```ts
895
- const queryClient = useQueryClient();
896
-
897
- const updateTask = useMutation(api.tasks.update, {
898
- onMutate: async (newData) => {
899
- // Cancel outgoing refetches
900
- await queryClient.cancelQueries(["tasks.list"]);
901
-
902
- // Snapshot previous value
903
- const previous = queryClient.getQueryData(["tasks.list"]);
904
-
905
- // Optimistically update
906
- queryClient.setQueryData(["tasks.list"], (old: Task[]) =>
907
- old.map(t => t._id === newData.id ? { ...t, ...newData } : t)
908
- );
909
-
910
- return { previous };
911
- },
912
- onError: (err, newData, context) => {
913
- // Rollback on error
914
- queryClient.setQueryData(["tasks.list"], context.previous);
915
- }
916
- });
917
- ```
918
-
919
- ### SSR / Server Components
920
-
921
- ```ts
922
- // For Next.js SSR
923
- const client = new ArchlastClient(
924
- wsUrl,
925
- httpUrl,
926
- "web",
927
- undefined,
928
- {
929
- autoConnect: false, // Don't connect on server
930
- betterAuthCookies: cookies() // Pass request cookies
931
- }
932
- );
933
- ```
934
-
935
- ### Multiple Clients
936
-
937
- ```tsx
938
- const mainClient = new ArchlastClient(mainWsUrl, mainHttpUrl, "main");
939
- const analyticsClient = new ArchlastClient(analyticsWsUrl, analyticsHttpUrl, "analytics");
940
-
941
- function App() {
942
- return (
943
- <ArchlastProvider client={mainClient}>
944
- <ArchlastProvider client={analyticsClient} contextKey="analytics">
945
- <YourApp />
946
- </ArchlastProvider>
947
- </ArchlastProvider>
948
- );
949
- }
950
- ```
158
+ ## Subpath exports
951
159
 
952
- ---
160
+ - `@archlast/client/react`
161
+ - `@archlast/client/trpc`
162
+ - `@archlast/client/auth`
163
+ - `@archlast/client/storage`
164
+ - `@archlast/client/admin`
953
165
 
954
- ## Keywords
166
+ ## Troubleshooting
955
167
 
956
- archlast, client, sdk, react, hooks, real-time, websocket, typescript, api
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.
957
171
 
958
- ## License
172
+ ## Publishing (maintainers)
959
173
 
960
- 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.1",
3
+ "version": "0.1.2",
4
4
  "description": "Archlast client SDK for React and API access",
5
5
  "license": "MIT",
6
6
  "repository": {