@archlast/client 0.0.1 → 0.1.1

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 +918 -18
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,50 +1,713 @@
1
1
  # @archlast/client
2
2
 
3
- Archlast client SDK for React hooks, auth helpers, storage utilities, and tRPC client access.
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
4
 
5
- ## Install
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)
25
+
26
+ ## Installation
6
27
 
7
28
  ```bash
8
29
  npm install @archlast/client
30
+
31
+ # Or with other package managers
32
+ pnpm add @archlast/client
33
+ yarn add @archlast/client
34
+ bun add @archlast/client
9
35
  ```
10
36
 
11
- Peer dependencies (install in your app):
37
+ ### Peer Dependencies
38
+
39
+ Install required peer dependencies in your application:
12
40
 
13
41
  ```bash
14
42
  npm install react react-dom @tanstack/react-query
15
43
  ```
16
44
 
17
- ## Usage
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
18
56
 
19
57
  ```ts
20
- import { ArchlastClient, ArchlastProvider, useQuery } from "@archlast/client";
21
- import { api } from "./_generated/api";
58
+ import { ArchlastClient } from "@archlast/client";
22
59
 
23
- const client = new ArchlastClient({ baseUrl: "http://localhost:4000" });
60
+ 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
+ }
71
+ );
72
+ ```
73
+
74
+ ### 2. Set Up the Provider
75
+
76
+ ```tsx
77
+ import { ArchlastProvider } from "@archlast/client/react";
24
78
 
25
79
  function App() {
26
80
  return (
27
81
  <ArchlastProvider client={client}>
28
- <TodoList />
82
+ <YourApp />
29
83
  </ArchlastProvider>
30
84
  );
31
85
  }
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";
32
93
 
33
94
  function TodoList() {
95
+ // Subscribe to live updates
96
+ const tasks = useQuery(api.tasks.list, {});
97
+
98
+ // Mutation with automatic cache invalidation
99
+ const createTask = useMutation(api.tasks.create);
100
+
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
+ return (
109
+ <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>
116
+ </div>
117
+ );
118
+ }
119
+ ```
120
+
121
+ > **Note:** The `api` object is auto-generated by the CLI in `_generated/api.ts`. Run `archlast dev` or `archlast build` to generate it.
122
+
123
+ ---
124
+
125
+ ## Client Configuration
126
+
127
+ ### Constructor Signature
128
+
129
+ ```ts
130
+ new ArchlastClient(wsUrl, httpUrl, appId, authUrl?, options?)
131
+ ```
132
+
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
162
+
163
+ ```ts
164
+ const client = new ArchlastClient(
165
+ "wss://api.myapp.com/ws",
166
+ "https://api.myapp.com",
167
+ "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
+ }
177
+ );
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
+
204
+ function TaskList() {
205
+ // Basic usage
34
206
  const tasks = useQuery(api.tasks.list, {});
35
- return <pre>{JSON.stringify(tasks, null, 2)}</pre>;
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
+ }
227
+ ```
228
+
229
+ ### useMutation
230
+
231
+ Execute mutations with automatic cache invalidation.
232
+
233
+ ```ts
234
+ import { useMutation } from "@archlast/client/react";
235
+
236
+ function CreateTaskForm() {
237
+ const createTask = useMutation(api.tasks.create);
238
+
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.
266
+
267
+ ```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
+ }
309
+ ```
310
+
311
+ **Server-side query must return:**
312
+
313
+ ```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
+ }
325
+ ```
326
+
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
+ ## Authentication
487
+
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:
535
+
536
+ ```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
+ import { useAuth } from "@archlast/client/react";
583
+
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;
36
591
  }
37
592
  ```
38
593
 
39
- ## Subpath exports
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");
622
+ ```
623
+
624
+ ### List Files
625
+
626
+ ### List Files
40
627
 
41
- - `@archlast/client/react`
42
- - `@archlast/client/trpc`
43
- - `@archlast/client/auth`
44
- - `@archlast/client/storage`
45
- - `@archlast/client/admin`
628
+ ```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
+ ```
46
644
 
47
- ## tRPC client
645
+ ### Get Presigned URLs
646
+
647
+ ```ts
648
+ // Get temporary signed URL for download
649
+ const { url, expiresAt } = await client.storage.presign(storageId);
650
+
651
+ // Use in img tag
652
+ <img src={url} alt="Uploaded file" />
653
+ ```
654
+
655
+ ### Get Metadata
656
+
657
+ ```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
663
+ ```
664
+
665
+ ---
666
+
667
+ ## Admin APIs
668
+
669
+ Access admin functionality (requires admin privileges).
670
+
671
+ ### Admin Auth Client
672
+
673
+ The `AdminClient` currently provides authentication management:
674
+
675
+ ```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();
691
+ ```
692
+
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
48
711
 
49
712
  ```ts
50
713
  import { createArchlastTRPCClient } from "@archlast/client/trpc";
@@ -52,9 +715,246 @@ import type { AppRouter } from "./_generated/rpc";
52
715
 
53
716
  const trpc = createArchlastTRPCClient<AppRouter>({
54
717
  baseUrl: "http://localhost:4000",
718
+ apiKey: "arch_your_api_key"
719
+ });
720
+ ```
721
+
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
+ }
55
916
  });
56
917
  ```
57
918
 
58
- ## Publishing (maintainers)
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
+ ```
951
+
952
+ ---
953
+
954
+ ## Keywords
955
+
956
+ archlast, client, sdk, react, hooks, real-time, websocket, typescript, api
957
+
958
+ ## License
59
959
 
60
- See `docs/npm-publishing.md` for release and publish steps.
960
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archlast/client",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "description": "Archlast client SDK for React and API access",
5
5
  "license": "MIT",
6
6
  "repository": {