@insforge/sdk 0.0.25 → 0.0.26
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/dist/index.d.mts +491 -0
- package/dist/index.d.ts +491 -0
- package/dist/index.js +971 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +935 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +1 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var InsForgeError = class _InsForgeError extends Error {
|
|
3
|
+
constructor(message, statusCode, error, nextActions) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "InsForgeError";
|
|
6
|
+
this.statusCode = statusCode;
|
|
7
|
+
this.error = error;
|
|
8
|
+
this.nextActions = nextActions;
|
|
9
|
+
}
|
|
10
|
+
static fromApiError(apiError) {
|
|
11
|
+
return new _InsForgeError(
|
|
12
|
+
apiError.message,
|
|
13
|
+
apiError.statusCode,
|
|
14
|
+
apiError.error,
|
|
15
|
+
apiError.nextActions
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/lib/http-client.ts
|
|
21
|
+
var HttpClient = class {
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.baseUrl = config.baseUrl || "http://localhost:7130";
|
|
24
|
+
this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
25
|
+
this.defaultHeaders = {
|
|
26
|
+
...config.headers
|
|
27
|
+
};
|
|
28
|
+
if (!this.fetch) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
"Fetch is not available. Please provide a fetch implementation in the config."
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
buildUrl(path, params) {
|
|
35
|
+
const url = new URL(path, this.baseUrl);
|
|
36
|
+
if (params) {
|
|
37
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
38
|
+
if (key === "select") {
|
|
39
|
+
let normalizedValue = value.replace(/\s+/g, " ").trim();
|
|
40
|
+
normalizedValue = normalizedValue.replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\(\s+/g, "(").replace(/\s+\)/g, ")").replace(/,\s+(?=[^()]*\))/g, ",");
|
|
41
|
+
url.searchParams.append(key, normalizedValue);
|
|
42
|
+
} else {
|
|
43
|
+
url.searchParams.append(key, value);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return url.toString();
|
|
48
|
+
}
|
|
49
|
+
async request(method, path, options = {}) {
|
|
50
|
+
const { params, headers = {}, body, ...fetchOptions } = options;
|
|
51
|
+
const url = this.buildUrl(path, params);
|
|
52
|
+
const requestHeaders = {
|
|
53
|
+
...this.defaultHeaders
|
|
54
|
+
};
|
|
55
|
+
let processedBody;
|
|
56
|
+
if (body !== void 0) {
|
|
57
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
58
|
+
processedBody = body;
|
|
59
|
+
} else {
|
|
60
|
+
if (method !== "GET") {
|
|
61
|
+
requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
|
|
62
|
+
}
|
|
63
|
+
processedBody = JSON.stringify(body);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
Object.assign(requestHeaders, headers);
|
|
67
|
+
const response = await this.fetch(url, {
|
|
68
|
+
method,
|
|
69
|
+
headers: requestHeaders,
|
|
70
|
+
body: processedBody,
|
|
71
|
+
...fetchOptions
|
|
72
|
+
});
|
|
73
|
+
if (response.status === 204) {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
let data;
|
|
77
|
+
const contentType = response.headers.get("content-type");
|
|
78
|
+
if (contentType?.includes("json")) {
|
|
79
|
+
data = await response.json();
|
|
80
|
+
} else {
|
|
81
|
+
data = await response.text();
|
|
82
|
+
}
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
if (data && typeof data === "object" && "error" in data) {
|
|
85
|
+
throw InsForgeError.fromApiError(data);
|
|
86
|
+
}
|
|
87
|
+
throw new InsForgeError(
|
|
88
|
+
`Request failed: ${response.statusText}`,
|
|
89
|
+
response.status,
|
|
90
|
+
"REQUEST_FAILED"
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return data;
|
|
94
|
+
}
|
|
95
|
+
get(path, options) {
|
|
96
|
+
return this.request("GET", path, options);
|
|
97
|
+
}
|
|
98
|
+
post(path, body, options) {
|
|
99
|
+
return this.request("POST", path, { ...options, body });
|
|
100
|
+
}
|
|
101
|
+
put(path, body, options) {
|
|
102
|
+
return this.request("PUT", path, { ...options, body });
|
|
103
|
+
}
|
|
104
|
+
patch(path, body, options) {
|
|
105
|
+
return this.request("PATCH", path, { ...options, body });
|
|
106
|
+
}
|
|
107
|
+
delete(path, options) {
|
|
108
|
+
return this.request("DELETE", path, options);
|
|
109
|
+
}
|
|
110
|
+
setAuthToken(token) {
|
|
111
|
+
if (token) {
|
|
112
|
+
this.defaultHeaders["Authorization"] = `Bearer ${token}`;
|
|
113
|
+
} else {
|
|
114
|
+
delete this.defaultHeaders["Authorization"];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
getHeaders() {
|
|
118
|
+
return { ...this.defaultHeaders };
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/lib/token-manager.ts
|
|
123
|
+
var TOKEN_KEY = "insforge-auth-token";
|
|
124
|
+
var USER_KEY = "insforge-auth-user";
|
|
125
|
+
var TokenManager = class {
|
|
126
|
+
constructor(storage) {
|
|
127
|
+
if (storage) {
|
|
128
|
+
this.storage = storage;
|
|
129
|
+
} else if (typeof window !== "undefined" && window.localStorage) {
|
|
130
|
+
this.storage = window.localStorage;
|
|
131
|
+
} else {
|
|
132
|
+
const store = /* @__PURE__ */ new Map();
|
|
133
|
+
this.storage = {
|
|
134
|
+
getItem: (key) => store.get(key) || null,
|
|
135
|
+
setItem: (key, value) => {
|
|
136
|
+
store.set(key, value);
|
|
137
|
+
},
|
|
138
|
+
removeItem: (key) => {
|
|
139
|
+
store.delete(key);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
saveSession(session) {
|
|
145
|
+
this.storage.setItem(TOKEN_KEY, session.accessToken);
|
|
146
|
+
this.storage.setItem(USER_KEY, JSON.stringify(session.user));
|
|
147
|
+
}
|
|
148
|
+
getSession() {
|
|
149
|
+
const token = this.storage.getItem(TOKEN_KEY);
|
|
150
|
+
const userStr = this.storage.getItem(USER_KEY);
|
|
151
|
+
if (!token || !userStr) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const user = JSON.parse(userStr);
|
|
156
|
+
return { accessToken: token, user };
|
|
157
|
+
} catch {
|
|
158
|
+
this.clearSession();
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
getAccessToken() {
|
|
163
|
+
const token = this.storage.getItem(TOKEN_KEY);
|
|
164
|
+
return typeof token === "string" ? token : null;
|
|
165
|
+
}
|
|
166
|
+
clearSession() {
|
|
167
|
+
this.storage.removeItem(TOKEN_KEY);
|
|
168
|
+
this.storage.removeItem(USER_KEY);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// src/modules/database-postgrest.ts
|
|
173
|
+
import { PostgrestClient } from "@supabase/postgrest-js";
|
|
174
|
+
function createInsForgePostgrestFetch(httpClient, tokenManager) {
|
|
175
|
+
return async (input, init) => {
|
|
176
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
177
|
+
const urlObj = new URL(url);
|
|
178
|
+
const tableName = urlObj.pathname.slice(1);
|
|
179
|
+
const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
|
|
180
|
+
const token = tokenManager.getAccessToken();
|
|
181
|
+
const headers = new Headers(init?.headers);
|
|
182
|
+
if (token && !headers.has("Authorization")) {
|
|
183
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
184
|
+
}
|
|
185
|
+
const response = await fetch(insforgeUrl, {
|
|
186
|
+
...init,
|
|
187
|
+
headers
|
|
188
|
+
});
|
|
189
|
+
return response;
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
var Database = class {
|
|
193
|
+
constructor(httpClient, tokenManager) {
|
|
194
|
+
this.postgrest = new PostgrestClient("http://dummy", {
|
|
195
|
+
fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
|
|
196
|
+
headers: {}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Create a query builder for a table
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* // Basic query
|
|
204
|
+
* const { data, error } = await client.database
|
|
205
|
+
* .from('posts')
|
|
206
|
+
* .select('*')
|
|
207
|
+
* .eq('user_id', userId);
|
|
208
|
+
*
|
|
209
|
+
* // With count (Supabase style!)
|
|
210
|
+
* const { data, error, count } = await client.database
|
|
211
|
+
* .from('posts')
|
|
212
|
+
* .select('*', { count: 'exact' })
|
|
213
|
+
* .range(0, 9);
|
|
214
|
+
*
|
|
215
|
+
* // Just get count, no data
|
|
216
|
+
* const { count } = await client.database
|
|
217
|
+
* .from('posts')
|
|
218
|
+
* .select('*', { count: 'exact', head: true });
|
|
219
|
+
*
|
|
220
|
+
* // Complex queries with OR
|
|
221
|
+
* const { data } = await client.database
|
|
222
|
+
* .from('posts')
|
|
223
|
+
* .select('*, users!inner(*)')
|
|
224
|
+
* .or('status.eq.active,status.eq.pending');
|
|
225
|
+
*
|
|
226
|
+
* // All features work:
|
|
227
|
+
* - Nested selects
|
|
228
|
+
* - Foreign key expansion
|
|
229
|
+
* - OR/AND/NOT conditions
|
|
230
|
+
* - Count with head
|
|
231
|
+
* - Range pagination
|
|
232
|
+
* - Upserts
|
|
233
|
+
*/
|
|
234
|
+
from(table) {
|
|
235
|
+
return this.postgrest.from(table);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// src/modules/auth.ts
|
|
240
|
+
var Auth = class {
|
|
241
|
+
constructor(http, tokenManager) {
|
|
242
|
+
this.http = http;
|
|
243
|
+
this.tokenManager = tokenManager;
|
|
244
|
+
this.database = new Database(http, tokenManager);
|
|
245
|
+
this.detectOAuthCallback();
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Automatically detect and handle OAuth callback parameters in the URL
|
|
249
|
+
* This runs on initialization to seamlessly complete the OAuth flow
|
|
250
|
+
* Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
|
|
251
|
+
*/
|
|
252
|
+
detectOAuthCallback() {
|
|
253
|
+
if (typeof window === "undefined") return;
|
|
254
|
+
try {
|
|
255
|
+
const params = new URLSearchParams(window.location.search);
|
|
256
|
+
const accessToken = params.get("access_token");
|
|
257
|
+
const userId = params.get("user_id");
|
|
258
|
+
const email = params.get("email");
|
|
259
|
+
const name = params.get("name");
|
|
260
|
+
if (accessToken && userId && email) {
|
|
261
|
+
const session = {
|
|
262
|
+
accessToken,
|
|
263
|
+
user: {
|
|
264
|
+
id: userId,
|
|
265
|
+
email,
|
|
266
|
+
name: name || "",
|
|
267
|
+
// These fields are not provided by backend OAuth callback
|
|
268
|
+
// They'll be populated when calling getCurrentUser()
|
|
269
|
+
emailVerified: false,
|
|
270
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
271
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
this.tokenManager.saveSession(session);
|
|
275
|
+
this.http.setAuthToken(accessToken);
|
|
276
|
+
const url = new URL(window.location.href);
|
|
277
|
+
url.searchParams.delete("access_token");
|
|
278
|
+
url.searchParams.delete("user_id");
|
|
279
|
+
url.searchParams.delete("email");
|
|
280
|
+
url.searchParams.delete("name");
|
|
281
|
+
if (params.has("error")) {
|
|
282
|
+
url.searchParams.delete("error");
|
|
283
|
+
}
|
|
284
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
285
|
+
}
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.debug("OAuth callback detection skipped:", error);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Sign up a new user
|
|
292
|
+
*/
|
|
293
|
+
async signUp(request) {
|
|
294
|
+
try {
|
|
295
|
+
const response = await this.http.post("/api/auth/users", request);
|
|
296
|
+
const session = {
|
|
297
|
+
accessToken: response.accessToken,
|
|
298
|
+
user: response.user
|
|
299
|
+
};
|
|
300
|
+
this.tokenManager.saveSession(session);
|
|
301
|
+
this.http.setAuthToken(response.accessToken);
|
|
302
|
+
return {
|
|
303
|
+
data: response,
|
|
304
|
+
error: null
|
|
305
|
+
};
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (error instanceof InsForgeError) {
|
|
308
|
+
return { data: null, error };
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
data: null,
|
|
312
|
+
error: new InsForgeError(
|
|
313
|
+
error instanceof Error ? error.message : "An unexpected error occurred during sign up",
|
|
314
|
+
500,
|
|
315
|
+
"UNEXPECTED_ERROR"
|
|
316
|
+
)
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Sign in with email and password
|
|
322
|
+
*/
|
|
323
|
+
async signInWithPassword(request) {
|
|
324
|
+
try {
|
|
325
|
+
const response = await this.http.post("/api/auth/sessions", request);
|
|
326
|
+
const session = {
|
|
327
|
+
accessToken: response.accessToken,
|
|
328
|
+
user: response.user
|
|
329
|
+
};
|
|
330
|
+
this.tokenManager.saveSession(session);
|
|
331
|
+
this.http.setAuthToken(response.accessToken);
|
|
332
|
+
return {
|
|
333
|
+
data: response,
|
|
334
|
+
error: null
|
|
335
|
+
};
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (error instanceof InsForgeError) {
|
|
338
|
+
return { data: null, error };
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
data: null,
|
|
342
|
+
error: new InsForgeError(
|
|
343
|
+
"An unexpected error occurred during sign in",
|
|
344
|
+
500,
|
|
345
|
+
"UNEXPECTED_ERROR"
|
|
346
|
+
)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Sign in with OAuth provider
|
|
352
|
+
*/
|
|
353
|
+
async signInWithOAuth(options) {
|
|
354
|
+
try {
|
|
355
|
+
const { provider, redirectTo, skipBrowserRedirect } = options;
|
|
356
|
+
const params = redirectTo ? { redirect_uri: redirectTo } : void 0;
|
|
357
|
+
const endpoint = `/api/auth/oauth/${provider}`;
|
|
358
|
+
const response = await this.http.get(endpoint, { params });
|
|
359
|
+
if (typeof window !== "undefined" && !skipBrowserRedirect) {
|
|
360
|
+
window.location.href = response.authUrl;
|
|
361
|
+
return { data: {}, error: null };
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
data: {
|
|
365
|
+
url: response.authUrl,
|
|
366
|
+
provider
|
|
367
|
+
},
|
|
368
|
+
error: null
|
|
369
|
+
};
|
|
370
|
+
} catch (error) {
|
|
371
|
+
if (error instanceof InsForgeError) {
|
|
372
|
+
return { data: {}, error };
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
data: {},
|
|
376
|
+
error: new InsForgeError(
|
|
377
|
+
"An unexpected error occurred during OAuth initialization",
|
|
378
|
+
500,
|
|
379
|
+
"UNEXPECTED_ERROR"
|
|
380
|
+
)
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Sign out the current user
|
|
386
|
+
*/
|
|
387
|
+
async signOut() {
|
|
388
|
+
try {
|
|
389
|
+
this.tokenManager.clearSession();
|
|
390
|
+
this.http.setAuthToken(null);
|
|
391
|
+
return { error: null };
|
|
392
|
+
} catch (error) {
|
|
393
|
+
return {
|
|
394
|
+
error: new InsForgeError(
|
|
395
|
+
"Failed to sign out",
|
|
396
|
+
500,
|
|
397
|
+
"SIGNOUT_ERROR"
|
|
398
|
+
)
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Get the current user with full profile information
|
|
404
|
+
* Returns both auth info (id, email, role) and profile data (nickname, avatar_url, bio, etc.)
|
|
405
|
+
*/
|
|
406
|
+
async getCurrentUser() {
|
|
407
|
+
try {
|
|
408
|
+
const session = this.tokenManager.getSession();
|
|
409
|
+
if (!session?.accessToken) {
|
|
410
|
+
return { data: null, error: null };
|
|
411
|
+
}
|
|
412
|
+
this.http.setAuthToken(session.accessToken);
|
|
413
|
+
const authResponse = await this.http.get("/api/auth/sessions/current");
|
|
414
|
+
const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
|
|
415
|
+
if (profileError && profileError.code !== "PGRST116") {
|
|
416
|
+
return { data: null, error: profileError };
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
data: {
|
|
420
|
+
user: authResponse.user,
|
|
421
|
+
profile
|
|
422
|
+
},
|
|
423
|
+
error: null
|
|
424
|
+
};
|
|
425
|
+
} catch (error) {
|
|
426
|
+
if (error instanceof InsForgeError && error.statusCode === 401) {
|
|
427
|
+
await this.signOut();
|
|
428
|
+
return { data: null, error: null };
|
|
429
|
+
}
|
|
430
|
+
if (error instanceof InsForgeError) {
|
|
431
|
+
return { data: null, error };
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
data: null,
|
|
435
|
+
error: new InsForgeError(
|
|
436
|
+
"An unexpected error occurred while fetching user",
|
|
437
|
+
500,
|
|
438
|
+
"UNEXPECTED_ERROR"
|
|
439
|
+
)
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get any user's profile by ID
|
|
445
|
+
* Returns profile information from the users table (nickname, avatar_url, bio, etc.)
|
|
446
|
+
*/
|
|
447
|
+
async getProfile(userId) {
|
|
448
|
+
const { data, error } = await this.database.from("users").select("*").eq("id", userId).single();
|
|
449
|
+
if (error && error.code === "PGRST116") {
|
|
450
|
+
return { data: null, error: null };
|
|
451
|
+
}
|
|
452
|
+
return { data, error };
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Get the current session (only session data, no API call)
|
|
456
|
+
* Returns the stored JWT token and basic user info from local storage
|
|
457
|
+
*/
|
|
458
|
+
getCurrentSession() {
|
|
459
|
+
try {
|
|
460
|
+
const session = this.tokenManager.getSession();
|
|
461
|
+
if (session?.accessToken) {
|
|
462
|
+
this.http.setAuthToken(session.accessToken);
|
|
463
|
+
return { data: { session }, error: null };
|
|
464
|
+
}
|
|
465
|
+
return { data: { session: null }, error: null };
|
|
466
|
+
} catch (error) {
|
|
467
|
+
if (error instanceof InsForgeError) {
|
|
468
|
+
return { data: { session: null }, error };
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
data: { session: null },
|
|
472
|
+
error: new InsForgeError(
|
|
473
|
+
"An unexpected error occurred while getting session",
|
|
474
|
+
500,
|
|
475
|
+
"UNEXPECTED_ERROR"
|
|
476
|
+
)
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Set/Update the current user's profile
|
|
482
|
+
* Updates profile information in the users table (nickname, avatar_url, bio, etc.)
|
|
483
|
+
*/
|
|
484
|
+
async setProfile(profile) {
|
|
485
|
+
const session = this.tokenManager.getSession();
|
|
486
|
+
if (!session?.user?.id) {
|
|
487
|
+
return {
|
|
488
|
+
data: null,
|
|
489
|
+
error: new InsForgeError(
|
|
490
|
+
"No authenticated user found",
|
|
491
|
+
401,
|
|
492
|
+
"UNAUTHENTICATED"
|
|
493
|
+
)
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const { data, error } = await this.database.from("users").update(profile).eq("id", session.user.id).select().single();
|
|
497
|
+
return { data, error };
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// src/modules/storage.ts
|
|
502
|
+
var StorageBucket = class {
|
|
503
|
+
constructor(bucketName, http) {
|
|
504
|
+
this.bucketName = bucketName;
|
|
505
|
+
this.http = http;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Upload a file with a specific key
|
|
509
|
+
* @param path - The object key/path
|
|
510
|
+
* @param file - File or Blob to upload
|
|
511
|
+
*/
|
|
512
|
+
async upload(path, file) {
|
|
513
|
+
try {
|
|
514
|
+
const formData = new FormData();
|
|
515
|
+
formData.append("file", file);
|
|
516
|
+
const response = await this.http.request(
|
|
517
|
+
"PUT",
|
|
518
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`,
|
|
519
|
+
{
|
|
520
|
+
body: formData,
|
|
521
|
+
headers: {
|
|
522
|
+
// Don't set Content-Type, let browser set multipart boundary
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
);
|
|
526
|
+
return { data: response, error: null };
|
|
527
|
+
} catch (error) {
|
|
528
|
+
return {
|
|
529
|
+
data: null,
|
|
530
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
531
|
+
"Upload failed",
|
|
532
|
+
500,
|
|
533
|
+
"STORAGE_ERROR"
|
|
534
|
+
)
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Upload a file with auto-generated key
|
|
540
|
+
* @param file - File or Blob to upload
|
|
541
|
+
*/
|
|
542
|
+
async uploadAuto(file) {
|
|
543
|
+
try {
|
|
544
|
+
const formData = new FormData();
|
|
545
|
+
formData.append("file", file);
|
|
546
|
+
const response = await this.http.request(
|
|
547
|
+
"POST",
|
|
548
|
+
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
549
|
+
{
|
|
550
|
+
body: formData,
|
|
551
|
+
headers: {
|
|
552
|
+
// Don't set Content-Type, let browser set multipart boundary
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
);
|
|
556
|
+
return { data: response, error: null };
|
|
557
|
+
} catch (error) {
|
|
558
|
+
return {
|
|
559
|
+
data: null,
|
|
560
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
561
|
+
"Upload failed",
|
|
562
|
+
500,
|
|
563
|
+
"STORAGE_ERROR"
|
|
564
|
+
)
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Download a file
|
|
570
|
+
* @param path - The object key/path
|
|
571
|
+
* Returns the file as a Blob
|
|
572
|
+
*/
|
|
573
|
+
async download(path) {
|
|
574
|
+
try {
|
|
575
|
+
const url = `${this.http.baseUrl}/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`;
|
|
576
|
+
const response = await this.http.fetch(url, {
|
|
577
|
+
method: "GET",
|
|
578
|
+
headers: this.http.getHeaders()
|
|
579
|
+
});
|
|
580
|
+
if (!response.ok) {
|
|
581
|
+
try {
|
|
582
|
+
const error = await response.json();
|
|
583
|
+
throw InsForgeError.fromApiError(error);
|
|
584
|
+
} catch {
|
|
585
|
+
throw new InsForgeError(
|
|
586
|
+
`Download failed: ${response.statusText}`,
|
|
587
|
+
response.status,
|
|
588
|
+
"STORAGE_ERROR"
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const blob = await response.blob();
|
|
593
|
+
return { data: blob, error: null };
|
|
594
|
+
} catch (error) {
|
|
595
|
+
return {
|
|
596
|
+
data: null,
|
|
597
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
598
|
+
"Download failed",
|
|
599
|
+
500,
|
|
600
|
+
"STORAGE_ERROR"
|
|
601
|
+
)
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Get public URL for a file
|
|
607
|
+
* @param path - The object key/path
|
|
608
|
+
*/
|
|
609
|
+
getPublicUrl(path) {
|
|
610
|
+
return `${this.http.baseUrl}/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* List objects in the bucket
|
|
614
|
+
* @param prefix - Filter by key prefix
|
|
615
|
+
* @param search - Search in file names
|
|
616
|
+
* @param limit - Maximum number of results (default: 100, max: 1000)
|
|
617
|
+
* @param offset - Number of results to skip
|
|
618
|
+
*/
|
|
619
|
+
async list(options) {
|
|
620
|
+
try {
|
|
621
|
+
const params = {};
|
|
622
|
+
if (options?.prefix) params.prefix = options.prefix;
|
|
623
|
+
if (options?.search) params.search = options.search;
|
|
624
|
+
if (options?.limit) params.limit = options.limit.toString();
|
|
625
|
+
if (options?.offset) params.offset = options.offset.toString();
|
|
626
|
+
const response = await this.http.get(
|
|
627
|
+
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
628
|
+
{ params }
|
|
629
|
+
);
|
|
630
|
+
return { data: response, error: null };
|
|
631
|
+
} catch (error) {
|
|
632
|
+
return {
|
|
633
|
+
data: null,
|
|
634
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
635
|
+
"List failed",
|
|
636
|
+
500,
|
|
637
|
+
"STORAGE_ERROR"
|
|
638
|
+
)
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Delete a file
|
|
644
|
+
* @param path - The object key/path
|
|
645
|
+
*/
|
|
646
|
+
async remove(path) {
|
|
647
|
+
try {
|
|
648
|
+
const response = await this.http.delete(
|
|
649
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`
|
|
650
|
+
);
|
|
651
|
+
return { data: response, error: null };
|
|
652
|
+
} catch (error) {
|
|
653
|
+
return {
|
|
654
|
+
data: null,
|
|
655
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
656
|
+
"Delete failed",
|
|
657
|
+
500,
|
|
658
|
+
"STORAGE_ERROR"
|
|
659
|
+
)
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
var Storage = class {
|
|
665
|
+
constructor(http) {
|
|
666
|
+
this.http = http;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Get a bucket instance for operations
|
|
670
|
+
* @param bucketName - Name of the bucket
|
|
671
|
+
*/
|
|
672
|
+
from(bucketName) {
|
|
673
|
+
return new StorageBucket(bucketName, this.http);
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
// src/modules/ai.ts
|
|
678
|
+
var AI = class {
|
|
679
|
+
constructor(http) {
|
|
680
|
+
this.http = http;
|
|
681
|
+
this.chat = new Chat(http);
|
|
682
|
+
this.images = new Images(http);
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
var Chat = class {
|
|
686
|
+
constructor(http) {
|
|
687
|
+
this.completions = new ChatCompletions(http);
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
var ChatCompletions = class {
|
|
691
|
+
constructor(http) {
|
|
692
|
+
this.http = http;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Create a chat completion - OpenAI-like response format
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* ```typescript
|
|
699
|
+
* // Non-streaming
|
|
700
|
+
* const completion = await client.ai.chat.completions.create({
|
|
701
|
+
* model: 'gpt-4',
|
|
702
|
+
* messages: [{ role: 'user', content: 'Hello!' }]
|
|
703
|
+
* });
|
|
704
|
+
* console.log(completion.choices[0].message.content);
|
|
705
|
+
*
|
|
706
|
+
* // With images
|
|
707
|
+
* const response = await client.ai.chat.completions.create({
|
|
708
|
+
* model: 'gpt-4-vision',
|
|
709
|
+
* messages: [{
|
|
710
|
+
* role: 'user',
|
|
711
|
+
* content: 'What is in this image?',
|
|
712
|
+
* images: [{ url: 'https://example.com/image.jpg' }]
|
|
713
|
+
* }]
|
|
714
|
+
* });
|
|
715
|
+
*
|
|
716
|
+
* // Streaming - returns async iterable
|
|
717
|
+
* const stream = await client.ai.chat.completions.create({
|
|
718
|
+
* model: 'gpt-4',
|
|
719
|
+
* messages: [{ role: 'user', content: 'Tell me a story' }],
|
|
720
|
+
* stream: true
|
|
721
|
+
* });
|
|
722
|
+
*
|
|
723
|
+
* for await (const chunk of stream) {
|
|
724
|
+
* if (chunk.choices[0]?.delta?.content) {
|
|
725
|
+
* process.stdout.write(chunk.choices[0].delta.content);
|
|
726
|
+
* }
|
|
727
|
+
* }
|
|
728
|
+
* ```
|
|
729
|
+
*/
|
|
730
|
+
async create(params) {
|
|
731
|
+
const backendParams = {
|
|
732
|
+
model: params.model,
|
|
733
|
+
messages: params.messages,
|
|
734
|
+
temperature: params.temperature,
|
|
735
|
+
maxTokens: params.maxTokens,
|
|
736
|
+
topP: params.topP,
|
|
737
|
+
stream: params.stream
|
|
738
|
+
};
|
|
739
|
+
if (params.stream) {
|
|
740
|
+
const headers = this.http.getHeaders();
|
|
741
|
+
headers["Content-Type"] = "application/json";
|
|
742
|
+
const response2 = await this.http.fetch(
|
|
743
|
+
`${this.http.baseUrl}/api/ai/chat/completion`,
|
|
744
|
+
{
|
|
745
|
+
method: "POST",
|
|
746
|
+
headers,
|
|
747
|
+
body: JSON.stringify(backendParams)
|
|
748
|
+
}
|
|
749
|
+
);
|
|
750
|
+
if (!response2.ok) {
|
|
751
|
+
const error = await response2.json();
|
|
752
|
+
throw new Error(error.error || "Stream request failed");
|
|
753
|
+
}
|
|
754
|
+
return this.parseSSEStream(response2, params.model);
|
|
755
|
+
}
|
|
756
|
+
const response = await this.http.post("/api/ai/chat/completion", backendParams);
|
|
757
|
+
const content = response.content || response.response || response.message || response.text || "";
|
|
758
|
+
return {
|
|
759
|
+
id: response.id || `chatcmpl-${Date.now()}`,
|
|
760
|
+
object: "chat.completion",
|
|
761
|
+
created: Math.floor(Date.now() / 1e3),
|
|
762
|
+
model: params.model,
|
|
763
|
+
choices: [{
|
|
764
|
+
index: 0,
|
|
765
|
+
message: {
|
|
766
|
+
role: "assistant",
|
|
767
|
+
content
|
|
768
|
+
},
|
|
769
|
+
finish_reason: "stop"
|
|
770
|
+
}],
|
|
771
|
+
usage: response.usage || {
|
|
772
|
+
prompt_tokens: 0,
|
|
773
|
+
completion_tokens: 0,
|
|
774
|
+
total_tokens: 0
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Parse SSE stream into async iterable of OpenAI-like chunks
|
|
780
|
+
*/
|
|
781
|
+
async *parseSSEStream(response, model) {
|
|
782
|
+
const reader = response.body.getReader();
|
|
783
|
+
const decoder = new TextDecoder();
|
|
784
|
+
let buffer = "";
|
|
785
|
+
try {
|
|
786
|
+
while (true) {
|
|
787
|
+
const { done, value } = await reader.read();
|
|
788
|
+
if (done) break;
|
|
789
|
+
buffer += decoder.decode(value, { stream: true });
|
|
790
|
+
const lines = buffer.split("\n");
|
|
791
|
+
buffer = lines.pop() || "";
|
|
792
|
+
for (const line of lines) {
|
|
793
|
+
if (line.startsWith("data: ")) {
|
|
794
|
+
const dataStr = line.slice(6).trim();
|
|
795
|
+
if (dataStr) {
|
|
796
|
+
try {
|
|
797
|
+
const data = JSON.parse(dataStr);
|
|
798
|
+
if (data.chunk || data.content) {
|
|
799
|
+
yield {
|
|
800
|
+
id: `chatcmpl-${Date.now()}`,
|
|
801
|
+
object: "chat.completion.chunk",
|
|
802
|
+
created: Math.floor(Date.now() / 1e3),
|
|
803
|
+
model,
|
|
804
|
+
choices: [{
|
|
805
|
+
index: 0,
|
|
806
|
+
delta: {
|
|
807
|
+
content: data.chunk || data.content
|
|
808
|
+
},
|
|
809
|
+
finish_reason: data.done ? "stop" : null
|
|
810
|
+
}]
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
if (data.done) {
|
|
814
|
+
reader.releaseLock();
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
} catch (e) {
|
|
818
|
+
console.warn("Failed to parse SSE data:", dataStr);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
} finally {
|
|
825
|
+
reader.releaseLock();
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
var Images = class {
|
|
830
|
+
constructor(http) {
|
|
831
|
+
this.http = http;
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Generate images - OpenAI-like response format
|
|
835
|
+
*
|
|
836
|
+
* @example
|
|
837
|
+
* ```typescript
|
|
838
|
+
* // Text-to-image
|
|
839
|
+
* const response = await client.ai.images.generate({
|
|
840
|
+
* model: 'dall-e-3',
|
|
841
|
+
* prompt: 'A sunset over mountains',
|
|
842
|
+
* n: 1,
|
|
843
|
+
* size: '1024x1024'
|
|
844
|
+
* });
|
|
845
|
+
* console.log(response.images[0].url);
|
|
846
|
+
*
|
|
847
|
+
* // Image-to-image (with input images)
|
|
848
|
+
* const response = await client.ai.images.generate({
|
|
849
|
+
* model: 'stable-diffusion-xl',
|
|
850
|
+
* prompt: 'Transform this into a watercolor painting',
|
|
851
|
+
* images: [
|
|
852
|
+
* { url: 'https://example.com/input.jpg' },
|
|
853
|
+
* // or base64-encoded Data URI:
|
|
854
|
+
* { url: 'data:image/jpeg;base64,/9j/4AAQ...' }
|
|
855
|
+
* ]
|
|
856
|
+
* });
|
|
857
|
+
* ```
|
|
858
|
+
*/
|
|
859
|
+
async generate(params) {
|
|
860
|
+
const backendParams = {
|
|
861
|
+
model: params.model,
|
|
862
|
+
prompt: params.prompt,
|
|
863
|
+
numImages: params.n || 1,
|
|
864
|
+
size: params.size,
|
|
865
|
+
quality: params.quality,
|
|
866
|
+
style: params.style,
|
|
867
|
+
responseFormat: params.response_format
|
|
868
|
+
};
|
|
869
|
+
const response = await this.http.post("/api/ai/image/generation", backendParams);
|
|
870
|
+
const images = response.images || response.data || [];
|
|
871
|
+
return {
|
|
872
|
+
created: Math.floor(Date.now() / 1e3),
|
|
873
|
+
data: images.map((img) => ({
|
|
874
|
+
url: img.url || img,
|
|
875
|
+
b64_json: img.b64_json,
|
|
876
|
+
revised_prompt: img.revised_prompt
|
|
877
|
+
}))
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// src/client.ts
|
|
883
|
+
var InsForgeClient = class {
|
|
884
|
+
constructor(config = {}) {
|
|
885
|
+
this.http = new HttpClient(config);
|
|
886
|
+
this.tokenManager = new TokenManager(config.storage);
|
|
887
|
+
this.auth = new Auth(
|
|
888
|
+
this.http,
|
|
889
|
+
this.tokenManager
|
|
890
|
+
);
|
|
891
|
+
this.database = new Database(this.http, this.tokenManager);
|
|
892
|
+
this.storage = new Storage(this.http);
|
|
893
|
+
this.ai = new AI(this.http);
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Get the underlying HTTP client for custom requests
|
|
897
|
+
*
|
|
898
|
+
* @example
|
|
899
|
+
* ```typescript
|
|
900
|
+
* const httpClient = client.getHttpClient();
|
|
901
|
+
* const customData = await httpClient.get('/api/custom-endpoint');
|
|
902
|
+
* ```
|
|
903
|
+
*/
|
|
904
|
+
getHttpClient() {
|
|
905
|
+
return this.http;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Future modules will be added here:
|
|
909
|
+
* - database: Database operations
|
|
910
|
+
* - storage: File storage operations
|
|
911
|
+
* - functions: Serverless functions
|
|
912
|
+
* - tables: Table management
|
|
913
|
+
* - metadata: Backend metadata
|
|
914
|
+
*/
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
// src/index.ts
|
|
918
|
+
function createClient(config) {
|
|
919
|
+
return new InsForgeClient(config);
|
|
920
|
+
}
|
|
921
|
+
var index_default = InsForgeClient;
|
|
922
|
+
export {
|
|
923
|
+
AI,
|
|
924
|
+
Auth,
|
|
925
|
+
Database,
|
|
926
|
+
HttpClient,
|
|
927
|
+
InsForgeClient,
|
|
928
|
+
InsForgeError,
|
|
929
|
+
Storage,
|
|
930
|
+
StorageBucket,
|
|
931
|
+
TokenManager,
|
|
932
|
+
createClient,
|
|
933
|
+
index_default as default
|
|
934
|
+
};
|
|
935
|
+
//# sourceMappingURL=index.mjs.map
|