@insforge/sdk 1.3.0 → 1.3.2-razorpay.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +27 -0
- package/README.md +79 -24
- package/SDK-REFERENCE.md +946 -0
- package/dist/{client-CQfw9UsO.d.mts → client-Co8KxT-n.d.mts} +53 -11
- package/dist/{client-CQfw9UsO.d.ts → client-Co8KxT-n.d.ts} +53 -11
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +159 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +159 -19
- package/dist/index.mjs.map +1 -1
- package/dist/ssr.d.mts +1 -1
- package/dist/ssr.d.ts +1 -1
- package/dist/ssr.js +159 -19
- package/dist/ssr.js.map +1 -1
- package/dist/ssr.mjs +159 -19
- package/dist/ssr.mjs.map +1 -1
- package/package.json +5 -3
package/SDK-REFERENCE.md
ADDED
|
@@ -0,0 +1,946 @@
|
|
|
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 provider-scoped and intended for generated app frontends. They call runtime-safe backend routes using the current user token or anon key. Admin-only key, catalog, sync, transaction, and webhook configuration APIs are intentionally not exposed through this frontend SDK surface.
|
|
386
|
+
|
|
387
|
+
### `stripe.createCheckoutSession()`
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
const { data, error } = await insforge.payments.stripe.createCheckoutSession(
|
|
391
|
+
"test",
|
|
392
|
+
{
|
|
393
|
+
mode: "payment",
|
|
394
|
+
lineItems: [{ priceId: "price_123", quantity: 1 }],
|
|
395
|
+
successUrl: "https://example.com/success",
|
|
396
|
+
cancelUrl: "https://example.com/pricing",
|
|
397
|
+
idempotencyKey: "cart_123", // optional, recommended for retry-safe checkout creation
|
|
398
|
+
},
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
if (!error && data?.checkoutSession.url) {
|
|
402
|
+
window.location.assign(data.checkoutSession.url);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
For one-time payments, `subject` is optional. For subscription checkout, `subject` is required because subscriptions represent ongoing entitlement for an app-defined billing owner.
|
|
407
|
+
|
|
408
|
+
```javascript
|
|
409
|
+
await insforge.payments.stripe.createCheckoutSession("test", {
|
|
410
|
+
mode: "subscription",
|
|
411
|
+
subject: { type: "team", id: "team_123" },
|
|
412
|
+
lineItems: [{ priceId: "price_monthly_123", quantity: 1 }],
|
|
413
|
+
successUrl: "https://example.com/billing/success",
|
|
414
|
+
cancelUrl: "https://example.com/billing",
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### `stripe.createCustomerPortalSession()`
|
|
419
|
+
|
|
420
|
+
```javascript
|
|
421
|
+
const { data, error } =
|
|
422
|
+
await insforge.payments.stripe.createCustomerPortalSession("test", {
|
|
423
|
+
subject: { type: "team", id: "team_123" },
|
|
424
|
+
returnUrl: "https://example.com/billing",
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
if (!error && data?.customerPortalSession.url) {
|
|
428
|
+
window.location.assign(data.customerPortalSession.url);
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Customer portal sessions require an authenticated user and an existing Stripe customer mapping for the billing subject.
|
|
433
|
+
|
|
434
|
+
### `razorpay.createOrder()`
|
|
435
|
+
|
|
436
|
+
Razorpay uses Checkout.js instead of a hosted redirect URL. Create an order through InsForge, pass `checkoutOptions` into Razorpay Checkout.js, then verify the signed payment response.
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
const { data, error } = await insforge.payments.razorpay.createOrder("test", {
|
|
440
|
+
amount: 200000,
|
|
441
|
+
currency: "INR",
|
|
442
|
+
receipt: "cart_123",
|
|
443
|
+
subject: { type: "team", id: "team_123" },
|
|
444
|
+
customerName: "Ada Lovelace",
|
|
445
|
+
customerEmail: "ada@example.com",
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
if (!error && data) {
|
|
449
|
+
const checkout = new Razorpay({
|
|
450
|
+
...data.checkoutOptions,
|
|
451
|
+
handler: async (response) => {
|
|
452
|
+
await insforge.payments.razorpay.verifyOrder("test", {
|
|
453
|
+
orderId: response.razorpay_order_id,
|
|
454
|
+
paymentId: response.razorpay_payment_id,
|
|
455
|
+
signature: response.razorpay_signature,
|
|
456
|
+
});
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
checkout.open();
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### `razorpay.createSubscription()`
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
467
|
+
const { data, error } = await insforge.payments.razorpay.createSubscription(
|
|
468
|
+
"test",
|
|
469
|
+
{
|
|
470
|
+
planId: "plan_123",
|
|
471
|
+
totalCount: 12,
|
|
472
|
+
subject: { type: "team", id: "team_123" },
|
|
473
|
+
customerName: "Ada Lovelace",
|
|
474
|
+
customerEmail: "ada@example.com",
|
|
475
|
+
},
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
if (!error && data) {
|
|
479
|
+
const checkout = new Razorpay({
|
|
480
|
+
...data.checkoutOptions,
|
|
481
|
+
handler: async (response) => {
|
|
482
|
+
await insforge.payments.razorpay.verifySubscription("test", {
|
|
483
|
+
subscriptionId: response.razorpay_subscription_id,
|
|
484
|
+
paymentId: response.razorpay_payment_id,
|
|
485
|
+
signature: response.razorpay_signature,
|
|
486
|
+
});
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
checkout.open();
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### `razorpay.cancelSubscription()`
|
|
495
|
+
|
|
496
|
+
```javascript
|
|
497
|
+
await insforge.payments.razorpay.cancelSubscription("test", "sub_123", {
|
|
498
|
+
cancelAtCycleEnd: false,
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### `razorpay.pauseSubscription()` / `razorpay.resumeSubscription()`
|
|
503
|
+
|
|
504
|
+
```javascript
|
|
505
|
+
await insforge.payments.razorpay.pauseSubscription("test", "sub_123");
|
|
506
|
+
await insforge.payments.razorpay.resumeSubscription("test", "sub_123");
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Razorpay webhook setup is manual in the Razorpay dashboard. Configure keys and copy the webhook URL, secret, and recommended events from the InsForge payments settings UI.
|
|
510
|
+
|
|
511
|
+
## Database Methods
|
|
512
|
+
|
|
513
|
+
**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.
|
|
514
|
+
|
|
515
|
+
### `from()`
|
|
516
|
+
|
|
517
|
+
Create a query builder for a table:
|
|
518
|
+
|
|
519
|
+
```javascript
|
|
520
|
+
const query = insforge.database.from("posts");
|
|
521
|
+
// Returns a PostgREST query builder with all Supabase features
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### SELECT Operations
|
|
525
|
+
|
|
526
|
+
```javascript
|
|
527
|
+
// Basic select
|
|
528
|
+
await insforge.database.from("posts").select(); // Default: '*'
|
|
529
|
+
|
|
530
|
+
// Select specific columns
|
|
531
|
+
await insforge.database.from("posts").select("id, title, created_at");
|
|
532
|
+
|
|
533
|
+
// With filters
|
|
534
|
+
await insforge.database
|
|
535
|
+
.from("posts")
|
|
536
|
+
.select()
|
|
537
|
+
.eq("user_id", "123")
|
|
538
|
+
.order("created_at", { ascending: false })
|
|
539
|
+
.limit(10);
|
|
540
|
+
|
|
541
|
+
// With joins (PostgREST syntax)
|
|
542
|
+
await insforge.database.from("posts").select("*, users!inner(*)"); // Inner join with users table
|
|
543
|
+
|
|
544
|
+
// Join with specific columns
|
|
545
|
+
await insforge.database
|
|
546
|
+
.from("posts")
|
|
547
|
+
.select("id, title, users(nickname, avatar_url)");
|
|
548
|
+
|
|
549
|
+
// Aliased joins
|
|
550
|
+
await insforge.database.from("posts").select("*, author:users(*)"); // Alias users as author
|
|
551
|
+
// Response: { data: [...], error }
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### INSERT Operations
|
|
555
|
+
|
|
556
|
+
```javascript
|
|
557
|
+
// Single record - use .select() to return inserted data
|
|
558
|
+
await insforge.database
|
|
559
|
+
.from("posts")
|
|
560
|
+
.insert({ title: "Hello", content: "World" })
|
|
561
|
+
.select();
|
|
562
|
+
|
|
563
|
+
// Multiple records
|
|
564
|
+
await insforge.database
|
|
565
|
+
.from("posts")
|
|
566
|
+
.insert([
|
|
567
|
+
{ title: "Post 1", content: "Content 1" },
|
|
568
|
+
{ title: "Post 2", content: "Content 2" },
|
|
569
|
+
])
|
|
570
|
+
.select();
|
|
571
|
+
|
|
572
|
+
// Upsert
|
|
573
|
+
await insforge.database
|
|
574
|
+
.from("posts")
|
|
575
|
+
.upsert({ id: "123", title: "Updated or New" })
|
|
576
|
+
.select();
|
|
577
|
+
// Response: { data: [...], error }
|
|
578
|
+
|
|
579
|
+
// Note: Without .select(), mutations return { data: null, error }
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### UPDATE Operations
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
await insforge.database
|
|
586
|
+
.from("posts")
|
|
587
|
+
.update({ title: "Updated Title" })
|
|
588
|
+
.eq("id", "123")
|
|
589
|
+
.select();
|
|
590
|
+
// Response: { data: [...], error }
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### DELETE Operations
|
|
594
|
+
|
|
595
|
+
```javascript
|
|
596
|
+
await insforge.database.from("posts").delete().eq("id", "123").select();
|
|
597
|
+
// Response: { data: [...], error }
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Filter Methods
|
|
601
|
+
|
|
602
|
+
```javascript
|
|
603
|
+
.eq('column', value) // Equals
|
|
604
|
+
.neq('column', value) // Not equals
|
|
605
|
+
.gt('column', value) // Greater than
|
|
606
|
+
.gte('column', value) // Greater than or equal
|
|
607
|
+
.lt('column', value) // Less than
|
|
608
|
+
.lte('column', value) // Less than or equal
|
|
609
|
+
.like('column', '%pattern%') // Pattern match (case-sensitive)
|
|
610
|
+
.ilike('column', '%pattern%') // Pattern match (case-insensitive)
|
|
611
|
+
.is('column', null) // IS NULL / IS boolean
|
|
612
|
+
.in('column', [1, 2, 3]) // IN array
|
|
613
|
+
|
|
614
|
+
// Logical operators (v0.0.22+)
|
|
615
|
+
.or('status.eq.active,status.eq.pending') // OR condition
|
|
616
|
+
.and('price.gte.100,price.lte.500') // Explicit AND
|
|
617
|
+
.not('deleted', 'is.true') // NOT condition
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
#### OR Condition Examples
|
|
621
|
+
|
|
622
|
+
```javascript
|
|
623
|
+
// Simple OR: status = 'active' OR status = 'pending'
|
|
624
|
+
await insforge.database
|
|
625
|
+
.from("posts")
|
|
626
|
+
.select()
|
|
627
|
+
.or("status.eq.active,status.eq.pending");
|
|
628
|
+
|
|
629
|
+
// OR with other filters (implicit AND)
|
|
630
|
+
await insforge.database
|
|
631
|
+
.from("posts")
|
|
632
|
+
.select()
|
|
633
|
+
.eq("user_id", "123") // AND
|
|
634
|
+
.or("status.eq.draft,status.eq.published"); // OR
|
|
635
|
+
|
|
636
|
+
// Complex OR with NOT
|
|
637
|
+
await insforge.database.from("users").select().or("age.lt.18,age.gt.65");
|
|
638
|
+
// age < 18 OR age > 65
|
|
639
|
+
|
|
640
|
+
// Combining AND and OR
|
|
641
|
+
await insforge.database
|
|
642
|
+
.from("products")
|
|
643
|
+
.select()
|
|
644
|
+
.eq("category", "electronics")
|
|
645
|
+
.or("price.lt.100,rating.gte.4.5");
|
|
646
|
+
// category = 'electronics' AND (price < 100 OR rating >= 4.5)
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Modifiers
|
|
650
|
+
|
|
651
|
+
```javascript
|
|
652
|
+
.order('column', { ascending: false }) // Order by
|
|
653
|
+
.limit(10) // Limit results
|
|
654
|
+
.offset(20) // Skip results
|
|
655
|
+
.range(0, 9) // Get specific range
|
|
656
|
+
.single() // Return single object
|
|
657
|
+
.maybeSingle() // Return single object or null
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### Count Options
|
|
661
|
+
|
|
662
|
+
Use with `select()` to get counts:
|
|
663
|
+
|
|
664
|
+
```javascript
|
|
665
|
+
// Get exact count with data
|
|
666
|
+
const { data, count, error } = await insforge.database
|
|
667
|
+
.from("posts")
|
|
668
|
+
.select("*", { count: "exact" });
|
|
669
|
+
|
|
670
|
+
// Get count without data (HEAD request)
|
|
671
|
+
const { count, error } = await insforge.database
|
|
672
|
+
.from("posts")
|
|
673
|
+
.select("*", { count: "exact", head: true });
|
|
674
|
+
|
|
675
|
+
// Count strategies:
|
|
676
|
+
// 'exact' - Accurate but slower for large tables
|
|
677
|
+
// 'planned' - Fast estimate from query planner
|
|
678
|
+
// 'estimated' - Very fast but rough estimate
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### Method Chaining
|
|
682
|
+
|
|
683
|
+
All methods return the query builder for chaining:
|
|
684
|
+
|
|
685
|
+
```javascript
|
|
686
|
+
const { data, error } = await insforge.database
|
|
687
|
+
.from("posts")
|
|
688
|
+
.select("id, title, content")
|
|
689
|
+
.eq("status", "published")
|
|
690
|
+
.gte("likes", 100)
|
|
691
|
+
.order("created_at", { ascending: false })
|
|
692
|
+
.limit(10);
|
|
693
|
+
|
|
694
|
+
// With count (Supabase-style)
|
|
695
|
+
const { data, error, count } = await insforge.database
|
|
696
|
+
.from("posts")
|
|
697
|
+
.select("*", { count: "exact" }) // Request exact count
|
|
698
|
+
.eq("status", "published")
|
|
699
|
+
.range(0, 9); // Get first 10
|
|
700
|
+
// Returns: data (array), error (PostgrestError), count (number)
|
|
701
|
+
|
|
702
|
+
// Count without data (head request)
|
|
703
|
+
const { count, error } = await insforge.database
|
|
704
|
+
.from("posts")
|
|
705
|
+
.select("*", { count: "exact", head: true })
|
|
706
|
+
.eq("status", "published");
|
|
707
|
+
// Returns only count, no data
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
## Storage Methods
|
|
711
|
+
|
|
712
|
+
### `storage.from()`
|
|
713
|
+
|
|
714
|
+
```javascript
|
|
715
|
+
const bucket = insforge.storage.from("avatars");
|
|
716
|
+
// Returns StorageBucket instance for file operations
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### `bucket.upload()`
|
|
720
|
+
|
|
721
|
+
```javascript
|
|
722
|
+
await bucket.upload("path/file.jpg", file);
|
|
723
|
+
// Response: { data: StorageFileSchema, error }
|
|
724
|
+
// data: { bucket, key, size, mimeType, uploadedAt, url }
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### `bucket.uploadAuto()`
|
|
728
|
+
|
|
729
|
+
```javascript
|
|
730
|
+
await bucket.uploadAuto(file);
|
|
731
|
+
// Response: { data: StorageFileSchema, error }
|
|
732
|
+
// Auto-generates unique filename
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### `bucket.download()`
|
|
736
|
+
|
|
737
|
+
```javascript
|
|
738
|
+
await bucket.download("path/file.jpg");
|
|
739
|
+
// Response: { data: Blob, error }
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
### `bucket.list()`
|
|
743
|
+
|
|
744
|
+
```javascript
|
|
745
|
+
await bucket.list({ prefix: "users/", limit: 10 });
|
|
746
|
+
// Response: { data: ListObjectsResponseSchema, error }
|
|
747
|
+
// data: { bucketName, objects[], pagination }
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### `bucket.remove()`
|
|
751
|
+
|
|
752
|
+
```javascript
|
|
753
|
+
await bucket.remove("path/file.jpg");
|
|
754
|
+
// Response: { data: { message }, error }
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### `bucket.getPublicUrl()`
|
|
758
|
+
|
|
759
|
+
```javascript
|
|
760
|
+
bucket.getPublicUrl("path/file.jpg");
|
|
761
|
+
// Returns: string URL (no API call)
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
## AI Methods
|
|
765
|
+
|
|
766
|
+
### `ai.chat.completions.create()`
|
|
767
|
+
|
|
768
|
+
Create AI chat completions with support for both streaming and non-streaming responses.
|
|
769
|
+
|
|
770
|
+
#### Non-Streaming
|
|
771
|
+
|
|
772
|
+
```javascript
|
|
773
|
+
const completion = await insforge.ai.chat.completions.create({
|
|
774
|
+
model: "anthropic/claude-3.5-haiku",
|
|
775
|
+
messages: [
|
|
776
|
+
{ role: "system", content: "You are a helpful assistant" },
|
|
777
|
+
{ role: "user", content: "Hello, how are you?" },
|
|
778
|
+
],
|
|
779
|
+
temperature: 0.7,
|
|
780
|
+
maxTokens: 500,
|
|
781
|
+
});
|
|
782
|
+
// Returns an OpenAI-like completion object directly (not a { data, error } envelope):
|
|
783
|
+
// completion.choices[0].message.content - the complete AI response text
|
|
784
|
+
// completion.usage - token usage information
|
|
785
|
+
// completion.model - the model used for generation
|
|
786
|
+
console.log(completion.choices[0].message.content);
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
#### Streaming
|
|
790
|
+
|
|
791
|
+
```javascript
|
|
792
|
+
// Returns an async iterable of OpenAI-like chunks for real-time streaming
|
|
793
|
+
const stream = await insforge.ai.chat.completions.create({
|
|
794
|
+
model: "anthropic/claude-3.5-haiku",
|
|
795
|
+
messages: [{ role: "user", content: "Tell me a story" }],
|
|
796
|
+
stream: true,
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// Each chunk carries an incremental delta in choices[0].delta.content
|
|
800
|
+
for await (const chunk of stream) {
|
|
801
|
+
const delta = chunk.choices[0]?.delta?.content;
|
|
802
|
+
if (delta) process.stdout.write(delta);
|
|
803
|
+
}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
#### Parameters
|
|
807
|
+
|
|
808
|
+
- `model` (string, required): AI model to use (e.g., 'anthropic/claude-3.5-haiku', 'openai/gpt-4', etc.)
|
|
809
|
+
- `messages` (array): Conversation messages with role ('system', 'user', 'assistant') and content
|
|
810
|
+
- `temperature` (number): Sampling temperature (0-2)
|
|
811
|
+
- `maxTokens` (number): Maximum tokens to generate
|
|
812
|
+
- `topP` (number): Top-p sampling parameter (0-1)
|
|
813
|
+
- `stream` (boolean): Enable streaming mode
|
|
814
|
+
- `thinking` (boolean): Enable chain-of-thought reasoning (supported models)
|
|
815
|
+
- `webSearch`, `fileParser`, `tools`, `toolChoice`, `parallelToolCalls`: Optional plugin/tool-calling options — see the SDK source for their shapes
|
|
816
|
+
|
|
817
|
+
### `ai.images.generate()`
|
|
818
|
+
|
|
819
|
+
Generate images using AI models.
|
|
820
|
+
|
|
821
|
+
```javascript
|
|
822
|
+
// Text-to-image
|
|
823
|
+
const image = await insforge.ai.images.generate({
|
|
824
|
+
model: "google/gemini-3-pro-image-preview",
|
|
825
|
+
prompt: "A serene landscape with mountains at sunset",
|
|
826
|
+
});
|
|
827
|
+
// Returns an OpenAI-like image object directly (not a { data, error } envelope):
|
|
828
|
+
// image.data[i].b64_json - base64-encoded image (no `data:` URI prefix)
|
|
829
|
+
// image.data[i].content - text output, for text-capable image models
|
|
830
|
+
// image.usage - token usage (when reported by the model)
|
|
831
|
+
const base64Png = image.data[0].b64_json;
|
|
832
|
+
|
|
833
|
+
// Image-to-image — pass source images as URLs or base64 data URIs
|
|
834
|
+
const edited = await insforge.ai.images.generate({
|
|
835
|
+
model: "google/gemini-3-pro-image-preview",
|
|
836
|
+
prompt: "Turn this into a watercolor painting",
|
|
837
|
+
images: [{ url: "https://example.com/input.jpg" }],
|
|
838
|
+
});
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
#### Parameters
|
|
842
|
+
|
|
843
|
+
- `model` (string, required): Image generation model (e.g., 'google/gemini-3-pro-image-preview', 'openai/dall-e-3')
|
|
844
|
+
- `prompt` (string, required): Text description of the image to generate
|
|
845
|
+
- `images` (array): Optional source images for image-to-image, each `{ url: string }` (HTTPS URL or `data:` base64 URI)
|
|
846
|
+
|
|
847
|
+
> The SDK normalizes generated images to base64 and exposes them as `image.data[i].b64_json`.
|
|
848
|
+
|
|
849
|
+
### `ai.embeddings.create()`
|
|
850
|
+
|
|
851
|
+
Create embeddings for one or more text inputs.
|
|
852
|
+
|
|
853
|
+
```javascript
|
|
854
|
+
const embeddings = await insforge.ai.embeddings.create({
|
|
855
|
+
model: "openai/text-embedding-3-small",
|
|
856
|
+
input: "Hello world", // or string[] for batch input
|
|
857
|
+
});
|
|
858
|
+
// Returns an OpenAI-like embeddings object directly (not a { data, error } envelope):
|
|
859
|
+
// embeddings.data[i].embedding - the vector (number[]) for input i
|
|
860
|
+
// embeddings.usage - token usage information
|
|
861
|
+
// embeddings.model - the model used
|
|
862
|
+
console.log(embeddings.data[0].embedding);
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
#### Parameters
|
|
866
|
+
|
|
867
|
+
- `model` (string, required): Embedding model (e.g., 'openai/text-embedding-3-small')
|
|
868
|
+
- `input` (string | string[], required): Text(s) to embed
|
|
869
|
+
- `dimensions` (number): Output dimensions, if supported by the model
|
|
870
|
+
- `encoding_format` ('float' | 'base64'): Encoding of the returned vectors
|
|
871
|
+
|
|
872
|
+
### Complete AI Example
|
|
873
|
+
|
|
874
|
+
```javascript
|
|
875
|
+
import { createClient } from "@insforge/sdk";
|
|
876
|
+
|
|
877
|
+
const insforge = createClient({
|
|
878
|
+
baseUrl: "http://localhost:7130",
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
// Chat completion
|
|
882
|
+
const chat = await insforge.ai.chat.completions.create({
|
|
883
|
+
model: "anthropic/claude-3.5-haiku",
|
|
884
|
+
messages: [{ role: "user", content: "What is the capital of France?" }],
|
|
885
|
+
});
|
|
886
|
+
console.log(chat.choices[0].message.content); // "The capital of France is Paris."
|
|
887
|
+
|
|
888
|
+
// Streaming chat
|
|
889
|
+
const stream = await insforge.ai.chat.completions.create({
|
|
890
|
+
model: "anthropic/claude-3.5-haiku",
|
|
891
|
+
messages: [{ role: "user", content: "Write a haiku about coding" }],
|
|
892
|
+
stream: true,
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
let fullResponse = "";
|
|
896
|
+
for await (const chunk of stream) {
|
|
897
|
+
const delta = chunk.choices[0]?.delta?.content;
|
|
898
|
+
if (delta) {
|
|
899
|
+
fullResponse += delta;
|
|
900
|
+
process.stdout.write(delta);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Image generation
|
|
905
|
+
const image = await insforge.ai.images.generate({
|
|
906
|
+
model: "google/gemini-3-pro-image-preview",
|
|
907
|
+
prompt: "A futuristic city with flying cars",
|
|
908
|
+
});
|
|
909
|
+
const base64Png = image.data[0].b64_json; // base64-encoded image
|
|
910
|
+
|
|
911
|
+
// Embeddings
|
|
912
|
+
const embeddings = await insforge.ai.embeddings.create({
|
|
913
|
+
model: "openai/text-embedding-3-small",
|
|
914
|
+
input: "Vectorize this sentence",
|
|
915
|
+
});
|
|
916
|
+
console.log(embeddings.data[0].embedding); // number[]
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
## Types (from @insforge/shared-schemas)
|
|
920
|
+
|
|
921
|
+
```typescript
|
|
922
|
+
import type {
|
|
923
|
+
UserSchema,
|
|
924
|
+
CreateUserRequest,
|
|
925
|
+
CreateSessionRequest,
|
|
926
|
+
GetCurrentSessionResponse,
|
|
927
|
+
StorageFileSchema,
|
|
928
|
+
StorageBucketSchema,
|
|
929
|
+
ListObjectsResponseSchema,
|
|
930
|
+
PublicOAuthProvider,
|
|
931
|
+
GetPublicEmailAuthConfigResponse,
|
|
932
|
+
} from "@insforge/shared-schemas";
|
|
933
|
+
|
|
934
|
+
// Database response type
|
|
935
|
+
interface DatabaseResponse<T> {
|
|
936
|
+
data: T | null;
|
|
937
|
+
error: InsForgeError | null;
|
|
938
|
+
count?: number;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Storage response type
|
|
942
|
+
interface StorageResponse<T> {
|
|
943
|
+
data: T | null;
|
|
944
|
+
error: InsForgeError | null;
|
|
945
|
+
}
|
|
946
|
+
```
|