@insforge/sdk 1.3.0 → 1.3.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.
@@ -0,0 +1,842 @@
1
+ # InsForge SDK Reference
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm install @insforge/sdk
7
+ ```
8
+
9
+ ## Initialize
10
+
11
+ ```javascript
12
+ import { createClient } from "@insforge/sdk";
13
+
14
+ const insforge = createClient({
15
+ baseUrl: "http://localhost:7130",
16
+ anonKey: "your-anon-key",
17
+ });
18
+ ```
19
+
20
+ `createClient()` is for public and user-scoped clients. Use `createAdminClient()` for project-admin API keys.
21
+
22
+ ## Admin Client
23
+
24
+ ```typescript
25
+ import { createAdminClient } from "@insforge/sdk";
26
+
27
+ const admin = createAdminClient({
28
+ baseUrl: "http://localhost:7130",
29
+ apiKey: process.env.INSFORGE_API_KEY!,
30
+ });
31
+ ```
32
+
33
+ Use this only in trusted server code. The admin client sends `apiKey` as the bearer token for every request.
34
+
35
+ ## SSR Auth Mode
36
+
37
+ Use `@insforge/sdk/ssr` for Next.js/SSR. The helpers keep the refresh token server-owned while still making the short-lived access token available to browser-only SDK surfaces such as Storage and Realtime.
38
+
39
+ Default env resolution:
40
+
41
+ - Browser and server: `NEXT_PUBLIC_INSFORGE_URL`, `NEXT_PUBLIC_INSFORGE_ANON_KEY`
42
+
43
+ Explicit `baseUrl` / `anonKey` values win. Missing SSR config throws a clear error.
44
+
45
+ Default cookies:
46
+
47
+ - `insforge_access_token`: `httpOnly: false`, `sameSite: "lax"`, `path: "/"`, expires at the JWT `exp`
48
+ - `insforge_refresh_token`: `httpOnly: true`, `sameSite: "lax"`, `path: "/"`, expires at the JWT `exp`
49
+
50
+ ### `createBrowserClient()`
51
+
52
+ ```typescript
53
+ import { createBrowserClient } from "@insforge/sdk/ssr";
54
+
55
+ const insforge = createBrowserClient({
56
+ refreshUrl: "/api/auth/refresh", // default
57
+ });
58
+ ```
59
+
60
+ The browser client reads the access-token cookie, uses it for Database, Storage, Functions, and Realtime, and calls the refresh route when the access token is missing or near expiry.
61
+
62
+ ### `createServerClient()`
63
+
64
+ ```typescript
65
+ import { cookies } from "next/headers";
66
+ import { createServerClient } from "@insforge/sdk/ssr";
67
+
68
+ const insforge = createServerClient({
69
+ cookies: await cookies(),
70
+ });
71
+ ```
72
+
73
+ The server client reads only the access-token cookie and passes it as the per-request bearer token.
74
+
75
+ ### `createRefreshAuthRouter()`
76
+
77
+ ```typescript
78
+ // app/api/auth/refresh/route.ts
79
+ import { createRefreshAuthRouter } from "@insforge/sdk/ssr";
80
+
81
+ export const { POST } = createRefreshAuthRouter();
82
+ ```
83
+
84
+ For server-owned refresh cookies, sign-in should also run through a Route Handler or Server Action that can set cookies:
85
+
86
+ ```typescript
87
+ import { NextResponse } from "next/server";
88
+ import { createServerClient, setAuthCookies } from "@insforge/sdk/ssr";
89
+
90
+ export async function POST(request: Request) {
91
+ const client = createServerClient();
92
+ const { data, error } = await client.auth.signInWithPassword(
93
+ await request.json(),
94
+ );
95
+ if (error || !data?.accessToken) {
96
+ return Response.json(error, { status: error?.statusCode ?? 400 });
97
+ }
98
+
99
+ const response = NextResponse.json({
100
+ accessToken: data.accessToken,
101
+ user: data.user,
102
+ });
103
+ setAuthCookies(response.cookies, {
104
+ accessToken: data.accessToken,
105
+ refreshToken: data.refreshToken,
106
+ });
107
+
108
+ return response;
109
+ }
110
+ ```
111
+
112
+ Use `refreshAuth()` directly when the route needs app-specific logic:
113
+
114
+ ```typescript
115
+ import { refreshAuth } from "@insforge/sdk/ssr";
116
+
117
+ export async function POST(request: Request) {
118
+ await beforeRefresh(request);
119
+ const result = await refreshAuth({ request });
120
+ await afterRefresh(result);
121
+ return result.response;
122
+ }
123
+ ```
124
+
125
+ ### `updateSession()`
126
+
127
+ ```typescript
128
+ // proxy.ts on Next.js 16+, middleware.ts on Next.js 15 and earlier
129
+ import { NextResponse, type NextRequest } from "next/server";
130
+ import { updateSession } from "@insforge/sdk/ssr";
131
+
132
+ export async function proxy(request: NextRequest) {
133
+ const response = NextResponse.next({ request });
134
+
135
+ await updateSession({
136
+ requestCookies: request.cookies,
137
+ responseCookies: response.cookies,
138
+ });
139
+
140
+ return response;
141
+ }
142
+ ```
143
+
144
+ ## OAuth Auto-Detection (Browser)
145
+
146
+ The SDK automatically detects and handles OAuth callback parameters when initialized. This feature works seamlessly with the InsForge backend OAuth flow.
147
+
148
+ **How it works:**
149
+
150
+ 1. User calls `signInWithOAuth()` and is redirected to OAuth provider
151
+ 2. After authentication, InsForge redirects back to your app with an `insforge_code` in the URL
152
+ 3. SDK automatically exchanges that code for a session on initialization
153
+ 4. Session is saved and the URL is cleaned - no manual handling needed
154
+
155
+ **Example:**
156
+
157
+ ```javascript
158
+ // Just initialize the client - OAuth is handled automatically
159
+ const insforge = createClient({
160
+ baseUrl: "http://localhost:7130",
161
+ });
162
+
163
+ // If the URL contains OAuth callback parameters like:
164
+ // ?insforge_code=...
165
+ // The SDK will:
166
+ // - Exchange the code for a session
167
+ // - Save the session in memory
168
+ // - Set the auth token for API calls
169
+ // - Clean the URL
170
+
171
+ // You can then immediately use authenticated methods:
172
+ const { data } = await insforge.auth.getCurrentUser();
173
+ ```
174
+
175
+ ## Auth Methods
176
+
177
+ ### `signUp()`
178
+
179
+ ```javascript
180
+ await insforge.auth.signUp({
181
+ email: "user@example.com",
182
+ password: "password123",
183
+ name: "John Doe", // optional
184
+ redirectTo: "http://localhost:3000/sign-in", // optional, recommended for link-based verification
185
+ });
186
+ // Response: { data: { user, accessToken }, error }
187
+ // user: { id, email, name, emailVerified, createdAt, updatedAt }
188
+ // accessToken: JWT token string
189
+ ```
190
+
191
+ If the backend uses link-based email verification, the emailed link opens:
192
+
193
+ ```text
194
+ GET /api/auth/email/verify-link?token=...
195
+ ```
196
+
197
+ InsForge validates the token first, then redirects the browser to your `redirectTo` URL.
198
+ Recommended: use your sign-in page as `redirectTo`, then show a success message and ask the user to sign in with email and password.
199
+
200
+ ### `signInWithPassword()`
201
+
202
+ ```javascript
203
+ await insforge.auth.signInWithPassword({
204
+ email: "user@example.com",
205
+ password: "password123",
206
+ });
207
+ // Response: { data: { user, accessToken }, error }
208
+ // user: { id, email, name, emailVerified, createdAt, updatedAt }
209
+ // accessToken: JWT token string
210
+ ```
211
+
212
+ ### `signInWithOAuth()`
213
+
214
+ ```javascript
215
+ await insforge.auth.signInWithOAuth("google", {
216
+ redirectTo: "http://localhost:3000/dashboard",
217
+ additionalParams: { prompt: "select_account" }, // optional provider-specific OAuth params
218
+ skipBrowserRedirect: true, // optional, returns URL instead of redirecting
219
+ });
220
+ // Response: { data: { url, provider }, error }
221
+ // Auto-redirects in browser unless skipBrowserRedirect: true
222
+ // additionalParams is for provider-specific hints only. Do not pass client_id, scope,
223
+ // redirect_uri, code_challenge, state, or response_type; InsForge sets those server-side
224
+ // and ignores colliding client-provided keys.
225
+
226
+ // AUTOMATIC OAuth Callback Detection (v0.0.14+):
227
+ // When users are redirected back from OAuth provider, the SDK automatically:
228
+ // 1. Detects insforge_code in the URL
229
+ // 2. Exchanges the code for a session
230
+ // 3. Saves the session in memory
231
+ // 4. Cleans the URL
232
+ // No manual handling needed - just initialize the client!
233
+ ```
234
+
235
+ ### `signOut()`
236
+
237
+ ```javascript
238
+ await insforge.auth.signOut();
239
+ // Response: { error }
240
+ // Clears stored tokens
241
+ ```
242
+
243
+ ### `getCurrentUser()`
244
+
245
+ ```javascript
246
+ await insforge.auth.getCurrentUser();
247
+ // Response: { data: { user }, error }
248
+ // user: { id, email, emailVerified, providers, createdAt, updatedAt, profile, metadata }
249
+ // Returns null if not authenticated
250
+ ```
251
+
252
+ For browser apps, call `getCurrentUser()` during startup. The SDK will use the httpOnly refresh cookie automatically when it can refresh the session.
253
+
254
+ For SSR apps, use `@insforge/sdk/ssr`.
255
+
256
+ ### `getProfile()`
257
+
258
+ ```javascript
259
+ await insforge.auth.getProfile(userId);
260
+ // Response: { data: profile, error }
261
+ // profile: { id, nickname, avatar_url, bio, birthday, ... }
262
+ // Gets any user's profile from users table
263
+ ```
264
+
265
+ ### `setProfile()`
266
+
267
+ ```javascript
268
+ await insforge.auth.setProfile({
269
+ nickname: "JohnDoe",
270
+ avatar_url: "https://...",
271
+ bio: "Software developer",
272
+ birthday: "1990-01-01",
273
+ });
274
+ // Response: { data: profile, error }
275
+ // Updates current user's profile in users table
276
+ ```
277
+
278
+ ### `getPublicAuthConfig()`
279
+
280
+ ```javascript
281
+ await insforge.auth.getPublicAuthConfig();
282
+ // Response: { data: GetPublicAuthConfigResponse, error }
283
+ // data: both OAuth providers and email authentication settings in one request
284
+ // This is a public endpoint that doesn't require authentication
285
+ ```
286
+
287
+ ### `resendVerificationEmail()`
288
+
289
+ ```javascript
290
+ await insforge.auth.resendVerificationEmail({
291
+ email: "user@example.com",
292
+ redirectTo: "http://localhost:3000/sign-in", // optional, recommended for link-based verification
293
+ });
294
+ // Response: { data: { success, message }, error }
295
+ ```
296
+
297
+ ### `verifyEmail()`
298
+
299
+ ```javascript
300
+ await insforge.auth.verifyEmail({
301
+ email: "user@example.com",
302
+ otp: "123456",
303
+ });
304
+ // Response: { data: { user, accessToken, csrfToken?, refreshToken? }, error }
305
+ // POST /api/auth/email/verify is code-only
306
+ // Browser link verification uses GET /api/auth/email/verify-link
307
+ // Verification redirect params:
308
+ // - insforge_status=success|error
309
+ // - insforge_type=verify_email
310
+ // - insforge_error (only on error)
311
+ ```
312
+
313
+ ### `sendResetPasswordEmail()`
314
+
315
+ ```javascript
316
+ await insforge.auth.sendResetPasswordEmail({
317
+ email: "user@example.com",
318
+ redirectTo: "http://localhost:3000/reset-password", // optional, recommended for link-based reset
319
+ });
320
+ // Response: { data: { success, message }, error }
321
+ ```
322
+
323
+ ### `exchangeResetPasswordToken()`
324
+
325
+ ```javascript
326
+ await insforge.auth.exchangeResetPasswordToken({
327
+ email: "user@example.com",
328
+ code: "123456",
329
+ });
330
+ // Response: { data: { token, expiresAt }, error }
331
+ ```
332
+
333
+ ### `resetPassword()`
334
+
335
+ ```javascript
336
+ await insforge.auth.resetPassword({
337
+ newPassword: "newSecurePassword123",
338
+ otp: "reset-token",
339
+ });
340
+ // Response: { data: { message }, error }
341
+ // Browser reset links use GET /api/auth/email/reset-password-link first,
342
+ // then your app submits the new password with POST /api/auth/email/reset-password.
343
+ // Reset redirect params:
344
+ // - token (present only when ready)
345
+ // - insforge_status=ready|error
346
+ // - insforge_type=reset_password
347
+ // - insforge_error (only on error)
348
+ ```
349
+
350
+ ## Error Handling
351
+
352
+ ### Auth/Storage/AI Errors (InsForgeError)
353
+
354
+ ```javascript
355
+ {
356
+ error: {
357
+ statusCode: 401,
358
+ error: 'INVALID_CREDENTIALS',
359
+ message: 'Invalid login credentials',
360
+ nextActions: 'Check email and password'
361
+ }
362
+ }
363
+ ```
364
+
365
+ ### Database Errors (PostgrestError)
366
+
367
+ ```javascript
368
+ {
369
+ error: {
370
+ code: 'PGRST116', // PostgreSQL/PostgREST error code
371
+ message: 'JSON object requested, multiple (or no) rows returned',
372
+ details: 'The result contains 5 rows',
373
+ hint: null
374
+ }
375
+ }
376
+ ```
377
+
378
+ ## Auth Session Storage
379
+
380
+ - **Browser**: in-memory (per client instance)
381
+ - **Node.js**: in-memory (per request/client instance)
382
+
383
+ ## Payments Methods
384
+
385
+ Payments methods are intended for generated app frontends. They call runtime-safe backend routes using the current user token or anon key. Admin-only Stripe key, product, price, sync, and webhook configuration APIs are intentionally not exposed through this frontend SDK surface.
386
+
387
+ ### `createCheckoutSession()`
388
+
389
+ ```javascript
390
+ const { data, error } = await insforge.payments.createCheckoutSession("test", {
391
+ mode: "payment",
392
+ lineItems: [{ stripePriceId: "price_123", quantity: 1 }],
393
+ successUrl: "https://example.com/success",
394
+ cancelUrl: "https://example.com/pricing",
395
+ idempotencyKey: "cart_123", // optional, recommended for retry-safe checkout creation
396
+ });
397
+
398
+ if (!error && data?.checkoutSession.url) {
399
+ window.location.assign(data.checkoutSession.url);
400
+ }
401
+ ```
402
+
403
+ For one-time payments, `subject` is optional. For subscription checkout, `subject` is required because subscriptions represent ongoing entitlement for an app-defined billing owner.
404
+
405
+ ```javascript
406
+ await insforge.payments.createCheckoutSession("test", {
407
+ mode: "subscription",
408
+ subject: { type: "team", id: "team_123" },
409
+ lineItems: [{ stripePriceId: "price_monthly_123", quantity: 1 }],
410
+ successUrl: "https://example.com/billing/success",
411
+ cancelUrl: "https://example.com/billing",
412
+ });
413
+ ```
414
+
415
+ ### `createCustomerPortalSession()`
416
+
417
+ ```javascript
418
+ const { data, error } = await insforge.payments.createCustomerPortalSession(
419
+ "test",
420
+ {
421
+ subject: { type: "team", id: "team_123" },
422
+ returnUrl: "https://example.com/billing",
423
+ },
424
+ );
425
+
426
+ if (!error && data?.customerPortalSession.url) {
427
+ window.location.assign(data.customerPortalSession.url);
428
+ }
429
+ ```
430
+
431
+ Customer portal sessions require an authenticated user and an existing Stripe customer mapping for the billing subject.
432
+
433
+ ## Database Methods
434
+
435
+ **Note:** Database operations use [@supabase/postgrest-js](https://github.com/supabase/postgrest-js) under the hood, providing full PostgREST compatibility including advanced features like OR conditions, complex joins, and aggregations.
436
+
437
+ ### `from()`
438
+
439
+ Create a query builder for a table:
440
+
441
+ ```javascript
442
+ const query = insforge.database.from("posts");
443
+ // Returns a PostgREST query builder with all Supabase features
444
+ ```
445
+
446
+ ### SELECT Operations
447
+
448
+ ```javascript
449
+ // Basic select
450
+ await insforge.database.from("posts").select(); // Default: '*'
451
+
452
+ // Select specific columns
453
+ await insforge.database.from("posts").select("id, title, created_at");
454
+
455
+ // With filters
456
+ await insforge.database
457
+ .from("posts")
458
+ .select()
459
+ .eq("user_id", "123")
460
+ .order("created_at", { ascending: false })
461
+ .limit(10);
462
+
463
+ // With joins (PostgREST syntax)
464
+ await insforge.database.from("posts").select("*, users!inner(*)"); // Inner join with users table
465
+
466
+ // Join with specific columns
467
+ await insforge.database
468
+ .from("posts")
469
+ .select("id, title, users(nickname, avatar_url)");
470
+
471
+ // Aliased joins
472
+ await insforge.database.from("posts").select("*, author:users(*)"); // Alias users as author
473
+ // Response: { data: [...], error }
474
+ ```
475
+
476
+ ### INSERT Operations
477
+
478
+ ```javascript
479
+ // Single record - use .select() to return inserted data
480
+ await insforge.database
481
+ .from("posts")
482
+ .insert({ title: "Hello", content: "World" })
483
+ .select();
484
+
485
+ // Multiple records
486
+ await insforge.database
487
+ .from("posts")
488
+ .insert([
489
+ { title: "Post 1", content: "Content 1" },
490
+ { title: "Post 2", content: "Content 2" },
491
+ ])
492
+ .select();
493
+
494
+ // Upsert
495
+ await insforge.database
496
+ .from("posts")
497
+ .upsert({ id: "123", title: "Updated or New" })
498
+ .select();
499
+ // Response: { data: [...], error }
500
+
501
+ // Note: Without .select(), mutations return { data: null, error }
502
+ ```
503
+
504
+ ### UPDATE Operations
505
+
506
+ ```javascript
507
+ await insforge.database
508
+ .from("posts")
509
+ .update({ title: "Updated Title" })
510
+ .eq("id", "123")
511
+ .select();
512
+ // Response: { data: [...], error }
513
+ ```
514
+
515
+ ### DELETE Operations
516
+
517
+ ```javascript
518
+ await insforge.database.from("posts").delete().eq("id", "123").select();
519
+ // Response: { data: [...], error }
520
+ ```
521
+
522
+ ### Filter Methods
523
+
524
+ ```javascript
525
+ .eq('column', value) // Equals
526
+ .neq('column', value) // Not equals
527
+ .gt('column', value) // Greater than
528
+ .gte('column', value) // Greater than or equal
529
+ .lt('column', value) // Less than
530
+ .lte('column', value) // Less than or equal
531
+ .like('column', '%pattern%') // Pattern match (case-sensitive)
532
+ .ilike('column', '%pattern%') // Pattern match (case-insensitive)
533
+ .is('column', null) // IS NULL / IS boolean
534
+ .in('column', [1, 2, 3]) // IN array
535
+
536
+ // Logical operators (v0.0.22+)
537
+ .or('status.eq.active,status.eq.pending') // OR condition
538
+ .and('price.gte.100,price.lte.500') // Explicit AND
539
+ .not('deleted', 'is.true') // NOT condition
540
+ ```
541
+
542
+ #### OR Condition Examples
543
+
544
+ ```javascript
545
+ // Simple OR: status = 'active' OR status = 'pending'
546
+ await insforge.database
547
+ .from("posts")
548
+ .select()
549
+ .or("status.eq.active,status.eq.pending");
550
+
551
+ // OR with other filters (implicit AND)
552
+ await insforge.database
553
+ .from("posts")
554
+ .select()
555
+ .eq("user_id", "123") // AND
556
+ .or("status.eq.draft,status.eq.published"); // OR
557
+
558
+ // Complex OR with NOT
559
+ await insforge.database.from("users").select().or("age.lt.18,age.gt.65");
560
+ // age < 18 OR age > 65
561
+
562
+ // Combining AND and OR
563
+ await insforge.database
564
+ .from("products")
565
+ .select()
566
+ .eq("category", "electronics")
567
+ .or("price.lt.100,rating.gte.4.5");
568
+ // category = 'electronics' AND (price < 100 OR rating >= 4.5)
569
+ ```
570
+
571
+ ### Modifiers
572
+
573
+ ```javascript
574
+ .order('column', { ascending: false }) // Order by
575
+ .limit(10) // Limit results
576
+ .offset(20) // Skip results
577
+ .range(0, 9) // Get specific range
578
+ .single() // Return single object
579
+ .maybeSingle() // Return single object or null
580
+ ```
581
+
582
+ ### Count Options
583
+
584
+ Use with `select()` to get counts:
585
+
586
+ ```javascript
587
+ // Get exact count with data
588
+ const { data, count, error } = await insforge.database
589
+ .from("posts")
590
+ .select("*", { count: "exact" });
591
+
592
+ // Get count without data (HEAD request)
593
+ const { count, error } = await insforge.database
594
+ .from("posts")
595
+ .select("*", { count: "exact", head: true });
596
+
597
+ // Count strategies:
598
+ // 'exact' - Accurate but slower for large tables
599
+ // 'planned' - Fast estimate from query planner
600
+ // 'estimated' - Very fast but rough estimate
601
+ ```
602
+
603
+ ### Method Chaining
604
+
605
+ All methods return the query builder for chaining:
606
+
607
+ ```javascript
608
+ const { data, error } = await insforge.database
609
+ .from("posts")
610
+ .select("id, title, content")
611
+ .eq("status", "published")
612
+ .gte("likes", 100)
613
+ .order("created_at", { ascending: false })
614
+ .limit(10);
615
+
616
+ // With count (Supabase-style)
617
+ const { data, error, count } = await insforge.database
618
+ .from("posts")
619
+ .select("*", { count: "exact" }) // Request exact count
620
+ .eq("status", "published")
621
+ .range(0, 9); // Get first 10
622
+ // Returns: data (array), error (PostgrestError), count (number)
623
+
624
+ // Count without data (head request)
625
+ const { count, error } = await insforge.database
626
+ .from("posts")
627
+ .select("*", { count: "exact", head: true })
628
+ .eq("status", "published");
629
+ // Returns only count, no data
630
+ ```
631
+
632
+ ## Storage Methods
633
+
634
+ ### `storage.from()`
635
+
636
+ ```javascript
637
+ const bucket = insforge.storage.from("avatars");
638
+ // Returns StorageBucket instance for file operations
639
+ ```
640
+
641
+ ### `bucket.upload()`
642
+
643
+ ```javascript
644
+ await bucket.upload("path/file.jpg", file);
645
+ // Response: { data: StorageFileSchema, error }
646
+ // data: { bucket, key, size, mimeType, uploadedAt, url }
647
+ ```
648
+
649
+ ### `bucket.uploadAuto()`
650
+
651
+ ```javascript
652
+ await bucket.uploadAuto(file);
653
+ // Response: { data: StorageFileSchema, error }
654
+ // Auto-generates unique filename
655
+ ```
656
+
657
+ ### `bucket.download()`
658
+
659
+ ```javascript
660
+ await bucket.download("path/file.jpg");
661
+ // Response: { data: Blob, error }
662
+ ```
663
+
664
+ ### `bucket.list()`
665
+
666
+ ```javascript
667
+ await bucket.list({ prefix: "users/", limit: 10 });
668
+ // Response: { data: ListObjectsResponseSchema, error }
669
+ // data: { bucketName, objects[], pagination }
670
+ ```
671
+
672
+ ### `bucket.remove()`
673
+
674
+ ```javascript
675
+ await bucket.remove("path/file.jpg");
676
+ // Response: { data: { message }, error }
677
+ ```
678
+
679
+ ### `bucket.getPublicUrl()`
680
+
681
+ ```javascript
682
+ bucket.getPublicUrl("path/file.jpg");
683
+ // Returns: string URL (no API call)
684
+ ```
685
+
686
+ ## AI Methods
687
+
688
+ ### `ai.chat.completions.create()`
689
+
690
+ Create AI chat completions with support for both streaming and non-streaming responses.
691
+
692
+ #### Non-Streaming
693
+
694
+ ```javascript
695
+ const { data, error } = await insforge.ai.chat.completions.create({
696
+ model: "anthropic/claude-3.5-haiku",
697
+ messages: [
698
+ { role: "system", content: "You are a helpful assistant" },
699
+ { role: "user", content: "Hello, how are you?" },
700
+ ],
701
+ temperature: 0.7,
702
+ maxTokens: 500,
703
+ });
704
+ // Response: { data: { response, usage, model }, error }
705
+ // response: The complete AI response text
706
+ // usage: Token usage information
707
+ // model: The model used for generation
708
+ ```
709
+
710
+ #### Streaming
711
+
712
+ ```javascript
713
+ // Returns async iterable for real-time streaming
714
+ const stream = await insforge.ai.chat.completions.create({
715
+ model: "anthropic/claude-3.5-haiku",
716
+ messages: [{ role: "user", content: "Tell me a story" }],
717
+ stream: true,
718
+ });
719
+
720
+ // Process stream events
721
+ for await (const event of stream) {
722
+ if (event.chunk) {
723
+ // Partial response chunk
724
+ process.stdout.write(event.chunk);
725
+ }
726
+ if (event.done) {
727
+ // Stream complete
728
+ console.log("\nStream finished");
729
+ }
730
+ }
731
+ ```
732
+
733
+ #### Parameters
734
+
735
+ - `model` (string, required): AI model to use (e.g., 'anthropic/claude-3.5-haiku', 'openai/gpt-4', etc.)
736
+ - `messages` (array): Conversation messages with role ('system', 'user', 'assistant') and content
737
+ - `message` (string): Simple message string (alternative to messages array)
738
+ - `systemPrompt` (string): System prompt for the conversation
739
+ - `temperature` (number): Sampling temperature (0-1)
740
+ - `maxTokens` (number): Maximum tokens to generate
741
+ - `topP` (number): Top-p sampling parameter
742
+ - `stream` (boolean): Enable streaming mode
743
+
744
+ ### `ai.images.generate()`
745
+
746
+ Generate images using AI models.
747
+
748
+ ```javascript
749
+ const { data, error } = await insforge.ai.images.generate({
750
+ model: "google/gemini-2.5-flash-image-preview",
751
+ prompt: "A serene landscape with mountains at sunset",
752
+ size: "1024x1024",
753
+ numImages: 1,
754
+ quality: "hd",
755
+ style: "vivid",
756
+ });
757
+ // Response: { data: { images: [{ url, ... }] }, error }
758
+ // images: Array of generated images with URLs
759
+ ```
760
+
761
+ #### Parameters
762
+
763
+ - `model` (string, required): Image generation model (e.g., 'google/gemini-2.5-flash-image-preview', 'openai/dall-e-3', 'stable-diffusion', etc.)
764
+ - `prompt` (string, required): Text description of the image to generate
765
+ - `negativePrompt` (string): What to avoid in the image (some models)
766
+ - `width` (number): Image width in pixels
767
+ - `height` (number): Image height in pixels
768
+ - `size` (string): Predefined size (e.g., '1024x1024', '512x512')
769
+ - `numImages` (number): Number of images to generate
770
+ - `quality` ('standard' | 'hd'): Image quality setting
771
+ - `style` ('vivid' | 'natural'): Image style preference
772
+ - `responseFormat` ('url' | 'b64_json'): Response format for images
773
+
774
+ ### Complete AI Example
775
+
776
+ ```javascript
777
+ import { createClient } from "@insforge/sdk";
778
+
779
+ const insforge = createClient({
780
+ baseUrl: "http://localhost:7130",
781
+ });
782
+
783
+ // Chat completion
784
+ const { data: chat } = await insforge.ai.chat.completions.create({
785
+ model: "anthropic/claude-3.5-haiku",
786
+ messages: [{ role: "user", content: "What is the capital of France?" }],
787
+ });
788
+ console.log(chat.response); // "The capital of France is Paris."
789
+
790
+ // Streaming chat
791
+ const stream = await insforge.ai.chat.completions.create({
792
+ model: "anthropic/claude-3.5-haiku",
793
+ messages: [{ role: "user", content: "Write a haiku about coding" }],
794
+ stream: true,
795
+ });
796
+
797
+ let fullResponse = "";
798
+ for await (const event of stream) {
799
+ if (event.chunk) {
800
+ fullResponse += event.chunk;
801
+ process.stdout.write(event.chunk);
802
+ }
803
+ }
804
+
805
+ // Image generation
806
+ const { data: images } = await insforge.ai.images.generate({
807
+ model: "google/gemini-2.5-flash-image-preview",
808
+ prompt: "A futuristic city with flying cars",
809
+ size: "1024x1024",
810
+ quality: "hd",
811
+ });
812
+ console.log(images.images[0].url); // URL to generated image
813
+ ```
814
+
815
+ ## Types (from @insforge/shared-schemas)
816
+
817
+ ```typescript
818
+ import type {
819
+ UserSchema,
820
+ CreateUserRequest,
821
+ CreateSessionRequest,
822
+ GetCurrentSessionResponse,
823
+ StorageFileSchema,
824
+ StorageBucketSchema,
825
+ ListObjectsResponseSchema,
826
+ PublicOAuthProvider,
827
+ GetPublicEmailAuthConfigResponse,
828
+ } from "@insforge/shared-schemas";
829
+
830
+ // Database response type
831
+ interface DatabaseResponse<T> {
832
+ data: T | null;
833
+ error: InsForgeError | null;
834
+ count?: number;
835
+ }
836
+
837
+ // Storage response type
838
+ interface StorageResponse<T> {
839
+ data: T | null;
840
+ error: InsForgeError | null;
841
+ }
842
+ ```