@vaiftechnologies/vaif-client 0.1.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/dist/index.d.mts +2508 -0
- package/dist/index.d.ts +2508 -0
- package/dist/index.js +2428 -0
- package/dist/index.mjs +2391 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
RealtimeChannel: () => RealtimeChannel,
|
|
24
|
+
VaifAI: () => VaifAI,
|
|
25
|
+
VaifAuth: () => VaifAuth,
|
|
26
|
+
VaifClient: () => VaifClient,
|
|
27
|
+
VaifDatabase: () => VaifDatabase,
|
|
28
|
+
VaifFunctions: () => VaifFunctions,
|
|
29
|
+
VaifRealtime: () => VaifRealtime,
|
|
30
|
+
VaifStorage: () => VaifStorage,
|
|
31
|
+
VaifTypeGen: () => VaifTypeGen,
|
|
32
|
+
createClient: () => createClient,
|
|
33
|
+
createTypeGen: () => createTypeGen
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/lib/database.ts
|
|
38
|
+
var VaifDatabase = class {
|
|
39
|
+
constructor(client) {
|
|
40
|
+
this.client = client;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Start a query on a table
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const users = await vaif.db.from('users').select('*').execute();
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
from(table) {
|
|
51
|
+
return new QueryBuilder(this.client, table);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Execute raw SQL query (admin only)
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const result = await vaif.db.raw('SELECT * FROM users WHERE id = $1', [userId]);
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
async raw(sql, params) {
|
|
62
|
+
const response = await this.client.request("/db/query", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
body: { sql, params }
|
|
65
|
+
});
|
|
66
|
+
if (response.error) {
|
|
67
|
+
return {
|
|
68
|
+
data: [],
|
|
69
|
+
error: {
|
|
70
|
+
message: response.error.message,
|
|
71
|
+
code: response.error.code ?? "QUERY_ERROR"
|
|
72
|
+
},
|
|
73
|
+
status: response.status
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
data: response.data?.rows ?? [],
|
|
78
|
+
error: null,
|
|
79
|
+
status: response.status
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Execute a function (stored procedure)
|
|
84
|
+
*/
|
|
85
|
+
async rpc(functionName, params) {
|
|
86
|
+
const response = await this.client.request(`/db/rpc/${functionName}`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
body: params ?? {}
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
data: response.data,
|
|
92
|
+
error: response.error ? {
|
|
93
|
+
message: response.error.message,
|
|
94
|
+
code: response.error.code ?? "RPC_ERROR"
|
|
95
|
+
} : null,
|
|
96
|
+
status: response.status
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var QueryBuilder = class {
|
|
101
|
+
constructor(client, table) {
|
|
102
|
+
this.queryType = "select";
|
|
103
|
+
this.selectColumns = "*";
|
|
104
|
+
this.insertData = null;
|
|
105
|
+
this.updateData = null;
|
|
106
|
+
this.filters = [];
|
|
107
|
+
this.orderSpecs = [];
|
|
108
|
+
this.limitCount = null;
|
|
109
|
+
this.offsetCount = null;
|
|
110
|
+
this.returning = null;
|
|
111
|
+
this.countOption = null;
|
|
112
|
+
this.negateNext = false;
|
|
113
|
+
this.onConflictColumns = null;
|
|
114
|
+
this.onConflictAction = null;
|
|
115
|
+
this.onConflictUpdate = null;
|
|
116
|
+
this.client = client;
|
|
117
|
+
this.table = table;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Select columns
|
|
121
|
+
*/
|
|
122
|
+
select(columns = "*") {
|
|
123
|
+
this.queryType = "select";
|
|
124
|
+
this.selectColumns = Array.isArray(columns) ? columns.join(", ") : columns;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Insert data
|
|
129
|
+
*/
|
|
130
|
+
insert(data) {
|
|
131
|
+
this.queryType = "insert";
|
|
132
|
+
this.insertData = data;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Update data
|
|
137
|
+
*/
|
|
138
|
+
update(data) {
|
|
139
|
+
this.queryType = "update";
|
|
140
|
+
this.updateData = data;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Delete rows
|
|
145
|
+
*/
|
|
146
|
+
delete() {
|
|
147
|
+
this.queryType = "delete";
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Handle insert conflicts (upsert)
|
|
152
|
+
*/
|
|
153
|
+
onConflict(columns) {
|
|
154
|
+
this.onConflictColumns = Array.isArray(columns) ? columns : [columns];
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Do nothing on conflict
|
|
159
|
+
*/
|
|
160
|
+
doNothing() {
|
|
161
|
+
this.onConflictAction = "nothing";
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Update on conflict
|
|
166
|
+
*/
|
|
167
|
+
doUpdate(data) {
|
|
168
|
+
this.onConflictAction = "update";
|
|
169
|
+
this.onConflictUpdate = data;
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
// Filter methods
|
|
173
|
+
eq(column, value) {
|
|
174
|
+
this.addFilter(column, this.negateNext ? "neq" : "eq", value);
|
|
175
|
+
this.negateNext = false;
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
neq(column, value) {
|
|
179
|
+
this.addFilter(column, this.negateNext ? "eq" : "neq", value);
|
|
180
|
+
this.negateNext = false;
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
gt(column, value) {
|
|
184
|
+
this.addFilter(column, this.negateNext ? "lte" : "gt", value);
|
|
185
|
+
this.negateNext = false;
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
gte(column, value) {
|
|
189
|
+
this.addFilter(column, this.negateNext ? "lt" : "gte", value);
|
|
190
|
+
this.negateNext = false;
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
lt(column, value) {
|
|
194
|
+
this.addFilter(column, this.negateNext ? "gte" : "lt", value);
|
|
195
|
+
this.negateNext = false;
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
lte(column, value) {
|
|
199
|
+
this.addFilter(column, this.negateNext ? "gt" : "lte", value);
|
|
200
|
+
this.negateNext = false;
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
like(column, pattern) {
|
|
204
|
+
this.addFilter(column, this.negateNext ? "nlike" : "like", pattern);
|
|
205
|
+
this.negateNext = false;
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
ilike(column, pattern) {
|
|
209
|
+
this.addFilter(column, this.negateNext ? "nilike" : "ilike", pattern);
|
|
210
|
+
this.negateNext = false;
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
is(column, value) {
|
|
214
|
+
this.addFilter(column, this.negateNext ? "isnot" : "is", value);
|
|
215
|
+
this.negateNext = false;
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
in(column, values) {
|
|
219
|
+
this.addFilter(column, this.negateNext ? "nin" : "in", values);
|
|
220
|
+
this.negateNext = false;
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
contains(column, value) {
|
|
224
|
+
this.addFilter(column, "cs", value);
|
|
225
|
+
this.negateNext = false;
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
containedBy(column, value) {
|
|
229
|
+
this.addFilter(column, "cd", value);
|
|
230
|
+
this.negateNext = false;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
overlaps(column, value) {
|
|
234
|
+
this.addFilter(column, "ov", value);
|
|
235
|
+
this.negateNext = false;
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
textSearch(column, query, options) {
|
|
239
|
+
this.addFilter(column, "fts", query, options?.config);
|
|
240
|
+
this.negateNext = false;
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Negate the next filter
|
|
245
|
+
*/
|
|
246
|
+
get not() {
|
|
247
|
+
this.negateNext = true;
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Combine filters with OR
|
|
252
|
+
*/
|
|
253
|
+
or(filters) {
|
|
254
|
+
this.filters.push({ type: "or", value: filters });
|
|
255
|
+
return this;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Order results
|
|
259
|
+
*/
|
|
260
|
+
order(column, options = {}) {
|
|
261
|
+
this.orderSpecs.push({
|
|
262
|
+
column,
|
|
263
|
+
ascending: options.ascending ?? true,
|
|
264
|
+
nullsFirst: options.nullsFirst
|
|
265
|
+
});
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Limit results
|
|
270
|
+
*/
|
|
271
|
+
limit(count) {
|
|
272
|
+
this.limitCount = count;
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Offset results
|
|
277
|
+
*/
|
|
278
|
+
offset(count) {
|
|
279
|
+
this.offsetCount = count;
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Range of results
|
|
284
|
+
*/
|
|
285
|
+
range(from, to) {
|
|
286
|
+
this.offsetCount = from;
|
|
287
|
+
this.limitCount = to - from + 1;
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Include count
|
|
292
|
+
*/
|
|
293
|
+
count(type = "exact") {
|
|
294
|
+
this.countOption = type;
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Return single result
|
|
299
|
+
*/
|
|
300
|
+
async single() {
|
|
301
|
+
this.limitCount = 1;
|
|
302
|
+
const result = await this.execute();
|
|
303
|
+
if (result.error) {
|
|
304
|
+
return {
|
|
305
|
+
data: null,
|
|
306
|
+
error: result.error,
|
|
307
|
+
status: result.status
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (!result.data || result.data.length === 0) {
|
|
311
|
+
return {
|
|
312
|
+
data: null,
|
|
313
|
+
error: {
|
|
314
|
+
message: "No rows returned",
|
|
315
|
+
code: "PGRST116"
|
|
316
|
+
},
|
|
317
|
+
status: 406
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
data: result.data[0],
|
|
322
|
+
error: null,
|
|
323
|
+
status: result.status
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Return first result or null
|
|
328
|
+
*/
|
|
329
|
+
async maybeSingle() {
|
|
330
|
+
this.limitCount = 1;
|
|
331
|
+
const result = await this.execute();
|
|
332
|
+
if (result.error) {
|
|
333
|
+
return {
|
|
334
|
+
data: null,
|
|
335
|
+
error: result.error,
|
|
336
|
+
status: result.status
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
data: result.data?.[0] ?? null,
|
|
341
|
+
error: null,
|
|
342
|
+
status: result.status
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Execute the query
|
|
347
|
+
*/
|
|
348
|
+
async execute() {
|
|
349
|
+
const body = this.buildQuery();
|
|
350
|
+
const response = await this.client.request(
|
|
351
|
+
`/db/${this.table}`,
|
|
352
|
+
{
|
|
353
|
+
method: this.getHttpMethod(),
|
|
354
|
+
body
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
if (response.error) {
|
|
358
|
+
return {
|
|
359
|
+
data: [],
|
|
360
|
+
error: {
|
|
361
|
+
message: response.error.message,
|
|
362
|
+
code: response.error.code ?? "QUERY_ERROR",
|
|
363
|
+
details: response.error.details?.toString()
|
|
364
|
+
},
|
|
365
|
+
status: response.status
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
data: response.data?.data ?? [],
|
|
370
|
+
error: null,
|
|
371
|
+
status: response.status,
|
|
372
|
+
count: response.data?.count
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
// Private helpers
|
|
376
|
+
addFilter(column, operator, value, extra) {
|
|
377
|
+
this.filters.push({ type: "filter", column, operator, value, extra });
|
|
378
|
+
}
|
|
379
|
+
getHttpMethod() {
|
|
380
|
+
switch (this.queryType) {
|
|
381
|
+
case "insert":
|
|
382
|
+
return "POST";
|
|
383
|
+
case "update":
|
|
384
|
+
return "PATCH";
|
|
385
|
+
case "delete":
|
|
386
|
+
return "DELETE";
|
|
387
|
+
default:
|
|
388
|
+
return "GET";
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
buildQuery() {
|
|
392
|
+
const query = {
|
|
393
|
+
type: this.queryType
|
|
394
|
+
};
|
|
395
|
+
if (this.queryType === "select") {
|
|
396
|
+
query.select = this.selectColumns;
|
|
397
|
+
}
|
|
398
|
+
if (this.queryType === "insert" && this.insertData) {
|
|
399
|
+
query.data = this.insertData;
|
|
400
|
+
if (this.onConflictColumns) {
|
|
401
|
+
query.onConflict = {
|
|
402
|
+
columns: this.onConflictColumns,
|
|
403
|
+
action: this.onConflictAction,
|
|
404
|
+
update: this.onConflictUpdate
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (this.queryType === "update" && this.updateData) {
|
|
409
|
+
query.data = this.updateData;
|
|
410
|
+
}
|
|
411
|
+
if (this.filters.length > 0) {
|
|
412
|
+
query.filters = this.filters;
|
|
413
|
+
}
|
|
414
|
+
if (this.orderSpecs.length > 0) {
|
|
415
|
+
query.order = this.orderSpecs;
|
|
416
|
+
}
|
|
417
|
+
if (this.limitCount !== null) {
|
|
418
|
+
query.limit = this.limitCount;
|
|
419
|
+
}
|
|
420
|
+
if (this.offsetCount !== null) {
|
|
421
|
+
query.offset = this.offsetCount;
|
|
422
|
+
}
|
|
423
|
+
if (this.countOption) {
|
|
424
|
+
query.count = this.countOption;
|
|
425
|
+
}
|
|
426
|
+
if (this.returning) {
|
|
427
|
+
query.returning = this.returning;
|
|
428
|
+
}
|
|
429
|
+
return query;
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
// src/lib/realtime.ts
|
|
434
|
+
var VaifRealtime = class {
|
|
435
|
+
constructor(client) {
|
|
436
|
+
this.socket = null;
|
|
437
|
+
this.channels = /* @__PURE__ */ new Map();
|
|
438
|
+
this.reconnectAttempts = 0;
|
|
439
|
+
this.maxReconnectAttempts = 10;
|
|
440
|
+
this.reconnectInterval = 1e3;
|
|
441
|
+
this.heartbeatInterval = null;
|
|
442
|
+
this.client = client;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Connect to realtime server
|
|
446
|
+
*/
|
|
447
|
+
connect(options = {}) {
|
|
448
|
+
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const config = this.client.getConfig();
|
|
452
|
+
const url = new URL(config.realtimeUrl);
|
|
453
|
+
url.searchParams.set("apikey", config.apiKey);
|
|
454
|
+
url.searchParams.set("project", config.projectId);
|
|
455
|
+
const token = this.client.getAccessToken();
|
|
456
|
+
if (token) {
|
|
457
|
+
url.searchParams.set("token", token);
|
|
458
|
+
}
|
|
459
|
+
this.socket = new WebSocket(url.toString());
|
|
460
|
+
this.socket.onopen = () => {
|
|
461
|
+
this.client.debug("Realtime connected");
|
|
462
|
+
this.reconnectAttempts = 0;
|
|
463
|
+
if (options.heartbeat !== false) {
|
|
464
|
+
this.startHeartbeat(options.heartbeatInterval ?? 3e4);
|
|
465
|
+
}
|
|
466
|
+
this.channels.forEach((channel) => {
|
|
467
|
+
channel.resubscribe();
|
|
468
|
+
});
|
|
469
|
+
};
|
|
470
|
+
this.socket.onmessage = (event) => {
|
|
471
|
+
try {
|
|
472
|
+
const message = JSON.parse(event.data);
|
|
473
|
+
this.handleMessage(message);
|
|
474
|
+
} catch {
|
|
475
|
+
this.client.debug("Failed to parse realtime message");
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
this.socket.onclose = () => {
|
|
479
|
+
this.client.debug("Realtime disconnected");
|
|
480
|
+
this.stopHeartbeat();
|
|
481
|
+
if (options.autoReconnect !== false && this.reconnectAttempts < (options.maxReconnectAttempts ?? this.maxReconnectAttempts)) {
|
|
482
|
+
this.reconnectAttempts++;
|
|
483
|
+
const delay = Math.min(
|
|
484
|
+
(options.reconnectInterval ?? this.reconnectInterval) * this.reconnectAttempts,
|
|
485
|
+
3e4
|
|
486
|
+
);
|
|
487
|
+
setTimeout(() => this.connect(options), delay);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
this.socket.onerror = (error) => {
|
|
491
|
+
this.client.debug("Realtime error", error);
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Disconnect from realtime server
|
|
496
|
+
*/
|
|
497
|
+
disconnect() {
|
|
498
|
+
this.stopHeartbeat();
|
|
499
|
+
this.channels.forEach((channel) => channel.unsubscribe());
|
|
500
|
+
this.channels.clear();
|
|
501
|
+
this.socket?.close();
|
|
502
|
+
this.socket = null;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Create or get a channel
|
|
506
|
+
*/
|
|
507
|
+
channel(name) {
|
|
508
|
+
let channel = this.channels.get(name);
|
|
509
|
+
if (!channel) {
|
|
510
|
+
channel = new RealtimeChannel(this, name);
|
|
511
|
+
this.channels.set(name, channel);
|
|
512
|
+
}
|
|
513
|
+
return channel;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Remove a channel
|
|
517
|
+
*/
|
|
518
|
+
removeChannel(name) {
|
|
519
|
+
const channel = this.channels.get(name);
|
|
520
|
+
if (channel) {
|
|
521
|
+
channel.unsubscribe();
|
|
522
|
+
this.channels.delete(name);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Send message to server
|
|
527
|
+
*/
|
|
528
|
+
send(message) {
|
|
529
|
+
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
530
|
+
this.socket.send(JSON.stringify(message));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Get WebSocket instance
|
|
535
|
+
*/
|
|
536
|
+
getSocket() {
|
|
537
|
+
return this.socket;
|
|
538
|
+
}
|
|
539
|
+
handleMessage(message) {
|
|
540
|
+
const channel = this.channels.get(message.topic);
|
|
541
|
+
if (channel) {
|
|
542
|
+
channel.handleMessage(message);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
startHeartbeat(interval) {
|
|
546
|
+
this.heartbeatInterval = setInterval(() => {
|
|
547
|
+
this.send({ topic: "phoenix", event: "heartbeat", payload: {} });
|
|
548
|
+
}, interval);
|
|
549
|
+
}
|
|
550
|
+
stopHeartbeat() {
|
|
551
|
+
if (this.heartbeatInterval) {
|
|
552
|
+
clearInterval(this.heartbeatInterval);
|
|
553
|
+
this.heartbeatInterval = null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
var RealtimeChannel = class {
|
|
558
|
+
constructor(realtime, name) {
|
|
559
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
560
|
+
this.status = "CLOSED";
|
|
561
|
+
this.presenceState = /* @__PURE__ */ new Map();
|
|
562
|
+
this.realtime = realtime;
|
|
563
|
+
this.name = name;
|
|
564
|
+
}
|
|
565
|
+
on(event, filter, callback) {
|
|
566
|
+
const key = `${event}:${JSON.stringify(filter)}`;
|
|
567
|
+
const callbacks = this.subscriptions.get(key) ?? [];
|
|
568
|
+
callbacks.push(callback);
|
|
569
|
+
this.subscriptions.set(key, callbacks);
|
|
570
|
+
return this;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Subscribe to channel
|
|
574
|
+
*/
|
|
575
|
+
subscribe(callback) {
|
|
576
|
+
this.realtime.send({
|
|
577
|
+
topic: this.name,
|
|
578
|
+
event: "phx_join",
|
|
579
|
+
payload: {
|
|
580
|
+
subscriptions: Array.from(this.subscriptions.keys())
|
|
581
|
+
},
|
|
582
|
+
ref: `${this.name}-join`
|
|
583
|
+
});
|
|
584
|
+
this.status = "SUBSCRIBED";
|
|
585
|
+
callback?.(this.status);
|
|
586
|
+
return this;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Unsubscribe from channel
|
|
590
|
+
*/
|
|
591
|
+
unsubscribe() {
|
|
592
|
+
this.realtime.send({
|
|
593
|
+
topic: this.name,
|
|
594
|
+
event: "phx_leave",
|
|
595
|
+
payload: {},
|
|
596
|
+
ref: `${this.name}-leave`
|
|
597
|
+
});
|
|
598
|
+
this.status = "CLOSED";
|
|
599
|
+
this.subscriptions.clear();
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Broadcast message to channel
|
|
603
|
+
*/
|
|
604
|
+
broadcast(event, payload) {
|
|
605
|
+
this.realtime.send({
|
|
606
|
+
topic: this.name,
|
|
607
|
+
event: "broadcast",
|
|
608
|
+
payload: { event, payload }
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Track presence
|
|
613
|
+
*/
|
|
614
|
+
track(payload) {
|
|
615
|
+
this.realtime.send({
|
|
616
|
+
topic: this.name,
|
|
617
|
+
event: "presence",
|
|
618
|
+
payload: { type: "track", payload }
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Untrack presence
|
|
623
|
+
*/
|
|
624
|
+
untrack() {
|
|
625
|
+
this.realtime.send({
|
|
626
|
+
topic: this.name,
|
|
627
|
+
event: "presence",
|
|
628
|
+
payload: { type: "untrack" }
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Get current presence state
|
|
633
|
+
*/
|
|
634
|
+
getPresenceState() {
|
|
635
|
+
return this.presenceState;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Handle incoming message
|
|
639
|
+
*/
|
|
640
|
+
handleMessage(message) {
|
|
641
|
+
this.subscriptions.forEach((callbacks, key) => {
|
|
642
|
+
const [event] = key.split(":");
|
|
643
|
+
if (message.event === event || message.event === "broadcast") {
|
|
644
|
+
callbacks.forEach((callback) => callback(message.payload));
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Resubscribe after reconnect
|
|
650
|
+
*/
|
|
651
|
+
resubscribe() {
|
|
652
|
+
if (this.subscriptions.size > 0) {
|
|
653
|
+
this.subscribe();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
// src/lib/storage.ts
|
|
659
|
+
var VaifStorage = class {
|
|
660
|
+
constructor(client) {
|
|
661
|
+
this.client = client;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Get a storage bucket
|
|
665
|
+
*/
|
|
666
|
+
from(bucket) {
|
|
667
|
+
return new StorageBucket(this.client, bucket);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* List all buckets
|
|
671
|
+
*/
|
|
672
|
+
async listBuckets() {
|
|
673
|
+
const response = await this.client.request("/storage/buckets");
|
|
674
|
+
return {
|
|
675
|
+
data: response.data,
|
|
676
|
+
error: response.error ? {
|
|
677
|
+
message: response.error.message,
|
|
678
|
+
statusCode: String(response.status),
|
|
679
|
+
error: response.error.code ?? "STORAGE_ERROR"
|
|
680
|
+
} : null
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Create a bucket
|
|
685
|
+
*/
|
|
686
|
+
async createBucket(name, options = {}) {
|
|
687
|
+
const response = await this.client.request("/storage/buckets", {
|
|
688
|
+
method: "POST",
|
|
689
|
+
body: { name, ...options }
|
|
690
|
+
});
|
|
691
|
+
return {
|
|
692
|
+
data: response.data,
|
|
693
|
+
error: response.error ? {
|
|
694
|
+
message: response.error.message,
|
|
695
|
+
statusCode: String(response.status),
|
|
696
|
+
error: response.error.code ?? "STORAGE_ERROR"
|
|
697
|
+
} : null
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Delete a bucket
|
|
702
|
+
*/
|
|
703
|
+
async deleteBucket(name) {
|
|
704
|
+
const response = await this.client.request(`/storage/buckets/${name}`, {
|
|
705
|
+
method: "DELETE"
|
|
706
|
+
});
|
|
707
|
+
return {
|
|
708
|
+
error: response.error ? {
|
|
709
|
+
message: response.error.message,
|
|
710
|
+
statusCode: String(response.status),
|
|
711
|
+
error: response.error.code ?? "STORAGE_ERROR"
|
|
712
|
+
} : null
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
var StorageBucket = class {
|
|
717
|
+
constructor(client, bucket) {
|
|
718
|
+
this.client = client;
|
|
719
|
+
this.bucket = bucket;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Upload a file
|
|
723
|
+
*/
|
|
724
|
+
async upload(path, file, options = {}) {
|
|
725
|
+
const formData = new FormData();
|
|
726
|
+
if (file instanceof File) {
|
|
727
|
+
formData.append("file", file);
|
|
728
|
+
} else if (file instanceof Blob) {
|
|
729
|
+
formData.append("file", file, path.split("/").pop() ?? "file");
|
|
730
|
+
} else if (file instanceof ArrayBuffer) {
|
|
731
|
+
formData.append("file", new Blob([file]), path.split("/").pop() ?? "file");
|
|
732
|
+
} else if (typeof file === "string") {
|
|
733
|
+
const blob = await fetch(file).then((r) => r.blob());
|
|
734
|
+
formData.append("file", blob, path.split("/").pop() ?? "file");
|
|
735
|
+
}
|
|
736
|
+
if (options.contentType) {
|
|
737
|
+
formData.append("contentType", options.contentType);
|
|
738
|
+
}
|
|
739
|
+
if (options.cacheControl) {
|
|
740
|
+
formData.append("cacheControl", options.cacheControl);
|
|
741
|
+
}
|
|
742
|
+
if (options.upsert) {
|
|
743
|
+
formData.append("upsert", "true");
|
|
744
|
+
}
|
|
745
|
+
if (options.metadata) {
|
|
746
|
+
formData.append("metadata", JSON.stringify(options.metadata));
|
|
747
|
+
}
|
|
748
|
+
const response = await this.client.request(
|
|
749
|
+
`/storage/${this.bucket}/${path}`,
|
|
750
|
+
{
|
|
751
|
+
method: "POST",
|
|
752
|
+
headers: {
|
|
753
|
+
// Don't set Content-Type, let browser set it with boundary
|
|
754
|
+
},
|
|
755
|
+
body: formData
|
|
756
|
+
}
|
|
757
|
+
);
|
|
758
|
+
return {
|
|
759
|
+
data: response.data,
|
|
760
|
+
error: response.error ? {
|
|
761
|
+
message: response.error.message,
|
|
762
|
+
statusCode: String(response.status),
|
|
763
|
+
error: response.error.code ?? "UPLOAD_ERROR"
|
|
764
|
+
} : null
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Download a file
|
|
769
|
+
*/
|
|
770
|
+
async download(path, options = {}) {
|
|
771
|
+
const params = {};
|
|
772
|
+
if (options.transform) {
|
|
773
|
+
if (options.transform.width) params.width = String(options.transform.width);
|
|
774
|
+
if (options.transform.height) params.height = String(options.transform.height);
|
|
775
|
+
if (options.transform.resize) params.resize = options.transform.resize;
|
|
776
|
+
if (options.transform.format) params.format = options.transform.format;
|
|
777
|
+
if (options.transform.quality) params.quality = String(options.transform.quality);
|
|
778
|
+
}
|
|
779
|
+
const url = new URL(`/storage/${this.bucket}/${path}`, this.client.getConfig().apiUrl);
|
|
780
|
+
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
781
|
+
try {
|
|
782
|
+
const response = await fetch(url.toString(), {
|
|
783
|
+
headers: {
|
|
784
|
+
apikey: this.client.getConfig().apiKey,
|
|
785
|
+
Authorization: `Bearer ${this.client.getAccessToken() ?? this.client.getConfig().apiKey}`
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
if (!response.ok) {
|
|
789
|
+
return {
|
|
790
|
+
data: null,
|
|
791
|
+
error: {
|
|
792
|
+
message: `Download failed: ${response.statusText}`,
|
|
793
|
+
statusCode: String(response.status),
|
|
794
|
+
error: "DOWNLOAD_ERROR"
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
return { data: await response.blob(), error: null };
|
|
799
|
+
} catch (err) {
|
|
800
|
+
return {
|
|
801
|
+
data: null,
|
|
802
|
+
error: {
|
|
803
|
+
message: err instanceof Error ? err.message : "Download failed",
|
|
804
|
+
statusCode: "0",
|
|
805
|
+
error: "NETWORK_ERROR"
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Get public URL for a file
|
|
812
|
+
*/
|
|
813
|
+
getPublicUrl(path) {
|
|
814
|
+
const config = this.client.getConfig();
|
|
815
|
+
return {
|
|
816
|
+
data: {
|
|
817
|
+
publicUrl: `${config.apiUrl}/storage/${this.bucket}/${path}`
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Create signed URL
|
|
823
|
+
*/
|
|
824
|
+
async createSignedUrl(path, options) {
|
|
825
|
+
const response = await this.client.request(
|
|
826
|
+
`/storage/${this.bucket}/${path}/sign`,
|
|
827
|
+
{
|
|
828
|
+
method: "POST",
|
|
829
|
+
body: {
|
|
830
|
+
expiresIn: options.expiresIn,
|
|
831
|
+
transform: options.transform,
|
|
832
|
+
download: options.download
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
);
|
|
836
|
+
return {
|
|
837
|
+
data: response.data,
|
|
838
|
+
error: response.error ? {
|
|
839
|
+
message: response.error.message,
|
|
840
|
+
statusCode: String(response.status),
|
|
841
|
+
error: response.error.code ?? "SIGN_ERROR"
|
|
842
|
+
} : null
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* List files in a path
|
|
847
|
+
*/
|
|
848
|
+
async list(path = "", options = {}) {
|
|
849
|
+
const response = await this.client.request(
|
|
850
|
+
`/storage/${this.bucket}/list`,
|
|
851
|
+
{
|
|
852
|
+
method: "POST",
|
|
853
|
+
body: {
|
|
854
|
+
prefix: path,
|
|
855
|
+
limit: options.limit,
|
|
856
|
+
offset: options.offset,
|
|
857
|
+
search: options.search,
|
|
858
|
+
sortBy: options.sortBy
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
);
|
|
862
|
+
return {
|
|
863
|
+
data: response.data,
|
|
864
|
+
error: response.error ? {
|
|
865
|
+
message: response.error.message,
|
|
866
|
+
statusCode: String(response.status),
|
|
867
|
+
error: response.error.code ?? "LIST_ERROR"
|
|
868
|
+
} : null
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Move/rename a file
|
|
873
|
+
*/
|
|
874
|
+
async move(fromPath, toPath) {
|
|
875
|
+
const response = await this.client.request(`/storage/${this.bucket}/move`, {
|
|
876
|
+
method: "POST",
|
|
877
|
+
body: { from: fromPath, to: toPath }
|
|
878
|
+
});
|
|
879
|
+
return {
|
|
880
|
+
error: response.error ? {
|
|
881
|
+
message: response.error.message,
|
|
882
|
+
statusCode: String(response.status),
|
|
883
|
+
error: response.error.code ?? "MOVE_ERROR"
|
|
884
|
+
} : null
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Copy a file
|
|
889
|
+
*/
|
|
890
|
+
async copy(fromPath, toPath) {
|
|
891
|
+
const response = await this.client.request(`/storage/${this.bucket}/copy`, {
|
|
892
|
+
method: "POST",
|
|
893
|
+
body: { from: fromPath, to: toPath }
|
|
894
|
+
});
|
|
895
|
+
return {
|
|
896
|
+
error: response.error ? {
|
|
897
|
+
message: response.error.message,
|
|
898
|
+
statusCode: String(response.status),
|
|
899
|
+
error: response.error.code ?? "COPY_ERROR"
|
|
900
|
+
} : null
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Delete a file
|
|
905
|
+
*/
|
|
906
|
+
async remove(paths) {
|
|
907
|
+
const pathArray = Array.isArray(paths) ? paths : [paths];
|
|
908
|
+
const response = await this.client.request(`/storage/${this.bucket}/delete`, {
|
|
909
|
+
method: "DELETE",
|
|
910
|
+
body: { paths: pathArray }
|
|
911
|
+
});
|
|
912
|
+
return {
|
|
913
|
+
error: response.error ? {
|
|
914
|
+
message: response.error.message,
|
|
915
|
+
statusCode: String(response.status),
|
|
916
|
+
error: response.error.code ?? "DELETE_ERROR"
|
|
917
|
+
} : null
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
// src/lib/functions.ts
|
|
923
|
+
var VaifFunctions = class {
|
|
924
|
+
constructor(client) {
|
|
925
|
+
this.client = client;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Invoke an edge function
|
|
929
|
+
*
|
|
930
|
+
* @example
|
|
931
|
+
* ```typescript
|
|
932
|
+
* const { data, error } = await vaif.functions.invoke('send-email', {
|
|
933
|
+
* body: { to: 'user@example.com', subject: 'Hello' }
|
|
934
|
+
* });
|
|
935
|
+
* ```
|
|
936
|
+
*/
|
|
937
|
+
async invoke(functionName, options = {}) {
|
|
938
|
+
const config = this.client.getConfig();
|
|
939
|
+
const url = new URL(
|
|
940
|
+
`/functions/${functionName}`,
|
|
941
|
+
config.apiUrl
|
|
942
|
+
);
|
|
943
|
+
const headers = {
|
|
944
|
+
"X-Project-ID": config.projectId,
|
|
945
|
+
apikey: config.apiKey,
|
|
946
|
+
...options.headers
|
|
947
|
+
};
|
|
948
|
+
const token = this.client.getAccessToken();
|
|
949
|
+
if (token) {
|
|
950
|
+
headers.Authorization = `Bearer ${token}`;
|
|
951
|
+
}
|
|
952
|
+
if (options.body !== void 0) {
|
|
953
|
+
if (options.body instanceof FormData) {
|
|
954
|
+
} else if (options.body instanceof Blob) {
|
|
955
|
+
headers["Content-Type"] = options.body.type || "application/octet-stream";
|
|
956
|
+
} else {
|
|
957
|
+
headers["Content-Type"] = "application/json";
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
const timeoutMs = options.timeout ?? config.timeout;
|
|
961
|
+
const controller = new AbortController();
|
|
962
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
963
|
+
try {
|
|
964
|
+
const response = await fetch(url.toString(), {
|
|
965
|
+
method: options.method ?? "POST",
|
|
966
|
+
headers,
|
|
967
|
+
body: this.serializeBody(options.body),
|
|
968
|
+
signal: controller.signal
|
|
969
|
+
});
|
|
970
|
+
clearTimeout(timeoutId);
|
|
971
|
+
const contentType = response.headers.get("content-type");
|
|
972
|
+
let data = null;
|
|
973
|
+
if (contentType?.includes("application/json")) {
|
|
974
|
+
data = await response.json();
|
|
975
|
+
} else if (contentType?.includes("text/")) {
|
|
976
|
+
data = await response.text();
|
|
977
|
+
} else if (response.body) {
|
|
978
|
+
data = await response.blob();
|
|
979
|
+
}
|
|
980
|
+
if (!response.ok) {
|
|
981
|
+
const error = {
|
|
982
|
+
message: typeof data === "object" && data !== null && "message" in data ? data.message : `Function invocation failed with status ${response.status}`,
|
|
983
|
+
status: response.status,
|
|
984
|
+
name: functionName
|
|
985
|
+
};
|
|
986
|
+
return { data: null, error };
|
|
987
|
+
}
|
|
988
|
+
return { data, error: null };
|
|
989
|
+
} catch (err) {
|
|
990
|
+
clearTimeout(timeoutId);
|
|
991
|
+
const error = {
|
|
992
|
+
message: err instanceof Error ? err.name === "AbortError" ? "Function invocation timed out" : err.message : "Function invocation failed",
|
|
993
|
+
status: 0,
|
|
994
|
+
name: functionName
|
|
995
|
+
};
|
|
996
|
+
return { data: null, error };
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Create a function URL for direct invocation
|
|
1001
|
+
*/
|
|
1002
|
+
createUrl(functionName) {
|
|
1003
|
+
const config = this.client.getConfig();
|
|
1004
|
+
return `${config.apiUrl}/functions/${functionName}`;
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Get list of deployed functions
|
|
1008
|
+
*/
|
|
1009
|
+
async list() {
|
|
1010
|
+
const response = await this.client.request("/functions");
|
|
1011
|
+
return {
|
|
1012
|
+
data: response.data,
|
|
1013
|
+
error: response.error ? {
|
|
1014
|
+
message: response.error.message,
|
|
1015
|
+
status: response.status,
|
|
1016
|
+
name: "list"
|
|
1017
|
+
} : null
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Get function details
|
|
1022
|
+
*/
|
|
1023
|
+
async get(functionName) {
|
|
1024
|
+
const response = await this.client.request(`/functions/${functionName}`);
|
|
1025
|
+
return {
|
|
1026
|
+
data: response.data,
|
|
1027
|
+
error: response.error ? {
|
|
1028
|
+
message: response.error.message,
|
|
1029
|
+
status: response.status,
|
|
1030
|
+
name: functionName
|
|
1031
|
+
} : null
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
serializeBody(body) {
|
|
1035
|
+
if (body === void 0) {
|
|
1036
|
+
return void 0;
|
|
1037
|
+
}
|
|
1038
|
+
if (body instanceof FormData || body instanceof Blob) {
|
|
1039
|
+
return body;
|
|
1040
|
+
}
|
|
1041
|
+
return JSON.stringify(body);
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1045
|
+
// src/lib/auth.ts
|
|
1046
|
+
var VaifAuth = class {
|
|
1047
|
+
constructor(client) {
|
|
1048
|
+
this.currentSession = null;
|
|
1049
|
+
this.currentUser = null;
|
|
1050
|
+
this.stateChangeCallbacks = /* @__PURE__ */ new Set();
|
|
1051
|
+
this.refreshTimeout = null;
|
|
1052
|
+
this.client = client;
|
|
1053
|
+
this.restoreSession();
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Get current session
|
|
1057
|
+
*/
|
|
1058
|
+
async getSession() {
|
|
1059
|
+
return {
|
|
1060
|
+
data: { session: this.currentSession },
|
|
1061
|
+
error: null
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Get current user synchronously from cache
|
|
1066
|
+
*/
|
|
1067
|
+
get user() {
|
|
1068
|
+
return this.currentUser;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Get current user (fetches fresh data)
|
|
1072
|
+
*/
|
|
1073
|
+
async getUser() {
|
|
1074
|
+
if (!this.currentSession) {
|
|
1075
|
+
return { data: { user: this.currentUser }, error: null };
|
|
1076
|
+
}
|
|
1077
|
+
const response = await this.client.request("/auth/user");
|
|
1078
|
+
if (response.error) {
|
|
1079
|
+
return {
|
|
1080
|
+
data: { user: this.currentUser },
|
|
1081
|
+
error: {
|
|
1082
|
+
message: response.error.message,
|
|
1083
|
+
status: response.status
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
this.currentUser = response.data ?? null;
|
|
1088
|
+
return { data: { user: this.currentUser }, error: null };
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Sign up with email and password
|
|
1092
|
+
*/
|
|
1093
|
+
async signUp(options) {
|
|
1094
|
+
const response = await this.client.request("/auth/signup", {
|
|
1095
|
+
method: "POST",
|
|
1096
|
+
body: {
|
|
1097
|
+
email: options.email,
|
|
1098
|
+
password: options.password,
|
|
1099
|
+
phone: options.phone,
|
|
1100
|
+
data: options.data,
|
|
1101
|
+
redirectTo: options.redirectTo
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
if (response.error) {
|
|
1105
|
+
return {
|
|
1106
|
+
data: { session: null, user: null },
|
|
1107
|
+
error: {
|
|
1108
|
+
message: response.error.message,
|
|
1109
|
+
status: response.status
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
const { session, user } = response.data ?? { session: null, user: null };
|
|
1114
|
+
if (session) {
|
|
1115
|
+
await this.setSession(session);
|
|
1116
|
+
this.notifyStateChange("SIGNED_IN", session);
|
|
1117
|
+
}
|
|
1118
|
+
return {
|
|
1119
|
+
data: { session, user },
|
|
1120
|
+
error: null
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Sign in with email and password
|
|
1125
|
+
*/
|
|
1126
|
+
async signInWithPassword(options) {
|
|
1127
|
+
const response = await this.client.request("/auth/token?grant_type=password", {
|
|
1128
|
+
method: "POST",
|
|
1129
|
+
body: {
|
|
1130
|
+
email: options.email,
|
|
1131
|
+
password: options.password,
|
|
1132
|
+
phone: options.phone
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
if (response.error) {
|
|
1136
|
+
return {
|
|
1137
|
+
data: { session: null, user: null },
|
|
1138
|
+
error: {
|
|
1139
|
+
message: response.error.message,
|
|
1140
|
+
status: response.status
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
const { session, user } = response.data ?? { session: null, user: null };
|
|
1145
|
+
if (session) {
|
|
1146
|
+
await this.setSession(session);
|
|
1147
|
+
this.notifyStateChange("SIGNED_IN", session);
|
|
1148
|
+
}
|
|
1149
|
+
return {
|
|
1150
|
+
data: { session, user },
|
|
1151
|
+
error: null
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Sign in with OAuth provider
|
|
1156
|
+
*/
|
|
1157
|
+
async signInWithOAuth(options) {
|
|
1158
|
+
const config = this.client.getConfig();
|
|
1159
|
+
const redirectTo = options.redirectTo ?? (typeof window !== "undefined" ? window.location?.origin : "") ?? "";
|
|
1160
|
+
const params = new URLSearchParams({
|
|
1161
|
+
provider: options.provider,
|
|
1162
|
+
redirect_to: redirectTo
|
|
1163
|
+
});
|
|
1164
|
+
if (options.scopes) {
|
|
1165
|
+
params.set("scopes", options.scopes);
|
|
1166
|
+
}
|
|
1167
|
+
if (options.queryParams) {
|
|
1168
|
+
Object.entries(options.queryParams).forEach(([key, value]) => {
|
|
1169
|
+
params.set(key, value);
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
const url = `${config.apiUrl}/auth/authorize?${params.toString()}`;
|
|
1173
|
+
return {
|
|
1174
|
+
data: {
|
|
1175
|
+
provider: options.provider,
|
|
1176
|
+
url
|
|
1177
|
+
},
|
|
1178
|
+
error: null
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Sign in with magic link (passwordless)
|
|
1183
|
+
*/
|
|
1184
|
+
async signInWithOtp(options) {
|
|
1185
|
+
const response = await this.client.request("/auth/otp", {
|
|
1186
|
+
method: "POST",
|
|
1187
|
+
body: {
|
|
1188
|
+
email: options.email,
|
|
1189
|
+
phone: options.phone,
|
|
1190
|
+
create_user: options.createUser ?? true,
|
|
1191
|
+
redirect_to: options.redirectTo
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
if (response.error) {
|
|
1195
|
+
return {
|
|
1196
|
+
data: {},
|
|
1197
|
+
error: {
|
|
1198
|
+
message: response.error.message,
|
|
1199
|
+
status: response.status
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
return {
|
|
1204
|
+
data: { messageId: response.data?.messageId },
|
|
1205
|
+
error: null
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Verify OTP code
|
|
1210
|
+
*/
|
|
1211
|
+
async verifyOtp(options) {
|
|
1212
|
+
const response = await this.client.request("/auth/verify", {
|
|
1213
|
+
method: "POST",
|
|
1214
|
+
body: {
|
|
1215
|
+
email: options.email,
|
|
1216
|
+
phone: options.phone,
|
|
1217
|
+
token: options.token,
|
|
1218
|
+
type: options.type
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
if (response.error) {
|
|
1222
|
+
return {
|
|
1223
|
+
data: { session: null, user: null },
|
|
1224
|
+
error: {
|
|
1225
|
+
message: response.error.message,
|
|
1226
|
+
status: response.status
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
const { session, user } = response.data ?? { session: null, user: null };
|
|
1231
|
+
if (session) {
|
|
1232
|
+
await this.setSession(session);
|
|
1233
|
+
this.notifyStateChange("SIGNED_IN", session);
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
data: { session, user },
|
|
1237
|
+
error: null
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Sign out
|
|
1242
|
+
*/
|
|
1243
|
+
async signOut(options) {
|
|
1244
|
+
const scope = options?.scope ?? "local";
|
|
1245
|
+
if (scope !== "local") {
|
|
1246
|
+
await this.client.request("/auth/logout", {
|
|
1247
|
+
method: "POST",
|
|
1248
|
+
body: { scope }
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
await this.clearSession();
|
|
1252
|
+
this.notifyStateChange("SIGNED_OUT", null);
|
|
1253
|
+
return { error: null };
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Reset password
|
|
1257
|
+
*/
|
|
1258
|
+
async resetPasswordForEmail(options) {
|
|
1259
|
+
const response = await this.client.request("/auth/recover", {
|
|
1260
|
+
method: "POST",
|
|
1261
|
+
body: {
|
|
1262
|
+
email: options.email,
|
|
1263
|
+
redirect_to: options.redirectTo,
|
|
1264
|
+
captcha_token: options.captchaToken
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
if (response.error) {
|
|
1268
|
+
return {
|
|
1269
|
+
error: {
|
|
1270
|
+
message: response.error.message,
|
|
1271
|
+
status: response.status
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
return { error: null };
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Update user
|
|
1279
|
+
*/
|
|
1280
|
+
async updateUser(options) {
|
|
1281
|
+
const response = await this.client.request("/auth/user", {
|
|
1282
|
+
method: "PUT",
|
|
1283
|
+
body: {
|
|
1284
|
+
email: options.email,
|
|
1285
|
+
password: options.password,
|
|
1286
|
+
phone: options.phone,
|
|
1287
|
+
data: options.data,
|
|
1288
|
+
nonce: options.nonce
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
if (response.error) {
|
|
1292
|
+
return {
|
|
1293
|
+
data: { user: null },
|
|
1294
|
+
error: {
|
|
1295
|
+
message: response.error.message,
|
|
1296
|
+
status: response.status
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
this.currentUser = response.data;
|
|
1301
|
+
this.notifyStateChange("USER_UPDATED", this.currentSession);
|
|
1302
|
+
return { data: { user: response.data }, error: null };
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Refresh session
|
|
1306
|
+
*/
|
|
1307
|
+
async refreshSession() {
|
|
1308
|
+
if (!this.currentSession?.refreshToken) {
|
|
1309
|
+
return {
|
|
1310
|
+
data: { session: null, user: null },
|
|
1311
|
+
error: { message: "No refresh token available", status: 401 }
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
const response = await this.client.request("/auth/token?grant_type=refresh_token", {
|
|
1315
|
+
method: "POST",
|
|
1316
|
+
body: {
|
|
1317
|
+
refresh_token: this.currentSession.refreshToken
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
if (response.error) {
|
|
1321
|
+
await this.clearSession();
|
|
1322
|
+
this.notifyStateChange("TOKEN_REFRESHED", null);
|
|
1323
|
+
return {
|
|
1324
|
+
data: { session: null, user: null },
|
|
1325
|
+
error: {
|
|
1326
|
+
message: response.error.message,
|
|
1327
|
+
status: response.status
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
const { session, user } = response.data ?? { session: null, user: null };
|
|
1332
|
+
if (session) {
|
|
1333
|
+
await this.setSession(session);
|
|
1334
|
+
this.notifyStateChange("TOKEN_REFRESHED", session);
|
|
1335
|
+
}
|
|
1336
|
+
return {
|
|
1337
|
+
data: { session, user },
|
|
1338
|
+
error: null
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Set session from URL (after OAuth redirect)
|
|
1343
|
+
*/
|
|
1344
|
+
async setSessionFromUrl() {
|
|
1345
|
+
if (typeof window === "undefined") {
|
|
1346
|
+
return {
|
|
1347
|
+
data: { session: null, user: null },
|
|
1348
|
+
error: { message: "Not in browser environment", status: 0 }
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
const hashParams = new URLSearchParams(window.location.hash.slice(1));
|
|
1352
|
+
const accessToken = hashParams.get("access_token");
|
|
1353
|
+
const refreshToken = hashParams.get("refresh_token");
|
|
1354
|
+
const expiresIn = hashParams.get("expires_in");
|
|
1355
|
+
const tokenType = hashParams.get("token_type");
|
|
1356
|
+
if (!accessToken) {
|
|
1357
|
+
const error = hashParams.get("error_description") || hashParams.get("error");
|
|
1358
|
+
return {
|
|
1359
|
+
data: { session: null, user: null },
|
|
1360
|
+
error: error ? { message: error, status: 0 } : null
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
const session = {
|
|
1364
|
+
accessToken,
|
|
1365
|
+
refreshToken: refreshToken ?? "",
|
|
1366
|
+
expiresIn: expiresIn ? parseInt(expiresIn, 10) : 3600,
|
|
1367
|
+
expiresAt: Date.now() + (expiresIn ? parseInt(expiresIn, 10) * 1e3 : 36e5),
|
|
1368
|
+
tokenType: tokenType ?? "bearer",
|
|
1369
|
+
user: null
|
|
1370
|
+
};
|
|
1371
|
+
this.client.setAccessToken(accessToken);
|
|
1372
|
+
const userResponse = await this.client.request("/auth/user");
|
|
1373
|
+
if (userResponse.data) {
|
|
1374
|
+
session.user = userResponse.data;
|
|
1375
|
+
}
|
|
1376
|
+
await this.setSession(session);
|
|
1377
|
+
this.notifyStateChange("SIGNED_IN", session);
|
|
1378
|
+
window.history.replaceState(null, "", window.location.pathname);
|
|
1379
|
+
return {
|
|
1380
|
+
data: { session, user: session.user },
|
|
1381
|
+
error: null
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Subscribe to auth state changes
|
|
1386
|
+
*/
|
|
1387
|
+
onAuthStateChange(callback) {
|
|
1388
|
+
this.stateChangeCallbacks.add(callback);
|
|
1389
|
+
if (this.currentSession) {
|
|
1390
|
+
callback("INITIAL_SESSION", this.currentSession);
|
|
1391
|
+
}
|
|
1392
|
+
return {
|
|
1393
|
+
data: {
|
|
1394
|
+
subscription: {
|
|
1395
|
+
unsubscribe: () => {
|
|
1396
|
+
this.stateChangeCallbacks.delete(callback);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Exchange code for session (PKCE flow)
|
|
1404
|
+
*/
|
|
1405
|
+
async exchangeCodeForSession(code) {
|
|
1406
|
+
const response = await this.client.request("/auth/token?grant_type=authorization_code", {
|
|
1407
|
+
method: "POST",
|
|
1408
|
+
body: { code }
|
|
1409
|
+
});
|
|
1410
|
+
if (response.error) {
|
|
1411
|
+
return {
|
|
1412
|
+
data: { session: null, user: null },
|
|
1413
|
+
error: {
|
|
1414
|
+
message: response.error.message,
|
|
1415
|
+
status: response.status
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
const { session, user } = response.data ?? { session: null, user: null };
|
|
1420
|
+
if (session) {
|
|
1421
|
+
await this.setSession(session);
|
|
1422
|
+
this.notifyStateChange("SIGNED_IN", session);
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
data: { session, user },
|
|
1426
|
+
error: null
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Resend confirmation email
|
|
1431
|
+
*/
|
|
1432
|
+
async resend(options) {
|
|
1433
|
+
const response = await this.client.request("/auth/resend", {
|
|
1434
|
+
method: "POST",
|
|
1435
|
+
body: options
|
|
1436
|
+
});
|
|
1437
|
+
if (response.error) {
|
|
1438
|
+
return {
|
|
1439
|
+
error: {
|
|
1440
|
+
message: response.error.message,
|
|
1441
|
+
status: response.status
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
return { error: null };
|
|
1446
|
+
}
|
|
1447
|
+
// Private methods
|
|
1448
|
+
async setSession(session) {
|
|
1449
|
+
this.currentSession = session;
|
|
1450
|
+
this.currentUser = session.user;
|
|
1451
|
+
this.client.setAccessToken(session.accessToken);
|
|
1452
|
+
const config = this.client.getConfig();
|
|
1453
|
+
if (config.persistSession) {
|
|
1454
|
+
await config.storage.setItem(
|
|
1455
|
+
`vaif-session-${config.projectId}`,
|
|
1456
|
+
JSON.stringify({
|
|
1457
|
+
accessToken: session.accessToken,
|
|
1458
|
+
refreshToken: session.refreshToken,
|
|
1459
|
+
expiresAt: session.expiresAt
|
|
1460
|
+
})
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
if (config.autoRefreshToken) {
|
|
1464
|
+
this.scheduleRefresh(session);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
async clearSession() {
|
|
1468
|
+
this.currentSession = null;
|
|
1469
|
+
this.currentUser = null;
|
|
1470
|
+
this.client.setAccessToken(null);
|
|
1471
|
+
if (this.refreshTimeout) {
|
|
1472
|
+
clearTimeout(this.refreshTimeout);
|
|
1473
|
+
this.refreshTimeout = null;
|
|
1474
|
+
}
|
|
1475
|
+
const config = this.client.getConfig();
|
|
1476
|
+
if (config.persistSession) {
|
|
1477
|
+
await config.storage.removeItem(`vaif-session-${config.projectId}`);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
async restoreSession() {
|
|
1481
|
+
const config = this.client.getConfig();
|
|
1482
|
+
if (!config.persistSession) return;
|
|
1483
|
+
try {
|
|
1484
|
+
const sessionStr = await config.storage.getItem(`vaif-session-${config.projectId}`);
|
|
1485
|
+
if (!sessionStr) return;
|
|
1486
|
+
const stored = JSON.parse(sessionStr);
|
|
1487
|
+
if (!stored.accessToken || stored.expiresAt < Date.now()) {
|
|
1488
|
+
await this.clearSession();
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
this.client.setAccessToken(stored.accessToken);
|
|
1492
|
+
const response = await this.client.request("/auth/session");
|
|
1493
|
+
if (response.data?.session) {
|
|
1494
|
+
await this.setSession(response.data.session);
|
|
1495
|
+
this.notifyStateChange("INITIAL_SESSION", response.data.session);
|
|
1496
|
+
} else {
|
|
1497
|
+
await this.clearSession();
|
|
1498
|
+
}
|
|
1499
|
+
} catch {
|
|
1500
|
+
await this.clearSession();
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
scheduleRefresh(session) {
|
|
1504
|
+
if (this.refreshTimeout) {
|
|
1505
|
+
clearTimeout(this.refreshTimeout);
|
|
1506
|
+
}
|
|
1507
|
+
const expiresIn = session.expiresAt - Date.now() - 6e4;
|
|
1508
|
+
if (expiresIn > 0) {
|
|
1509
|
+
this.refreshTimeout = setTimeout(() => {
|
|
1510
|
+
this.refreshSession();
|
|
1511
|
+
}, expiresIn);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
notifyStateChange(event, session) {
|
|
1515
|
+
this.stateChangeCallbacks.forEach((callback) => {
|
|
1516
|
+
try {
|
|
1517
|
+
callback(event, session);
|
|
1518
|
+
} catch {
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
|
|
1524
|
+
// src/lib/ai.ts
|
|
1525
|
+
var VaifAI = class {
|
|
1526
|
+
constructor(client) {
|
|
1527
|
+
this.client = client;
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Generate database schema from natural language
|
|
1531
|
+
*
|
|
1532
|
+
* @example
|
|
1533
|
+
* ```typescript
|
|
1534
|
+
* const { data, error } = await vaif.ai.generateSchema({
|
|
1535
|
+
* prompt: 'Create a blog with posts, authors, and comments',
|
|
1536
|
+
* format: 'drizzle',
|
|
1537
|
+
* includeRelations: true
|
|
1538
|
+
* });
|
|
1539
|
+
* ```
|
|
1540
|
+
*/
|
|
1541
|
+
async generateSchema(request, options = {}) {
|
|
1542
|
+
const response = await this.client.request(
|
|
1543
|
+
"/ai/generate/schema",
|
|
1544
|
+
{
|
|
1545
|
+
method: "POST",
|
|
1546
|
+
body: {
|
|
1547
|
+
...request,
|
|
1548
|
+
model: options.model,
|
|
1549
|
+
temperature: options.temperature,
|
|
1550
|
+
maxTokens: options.maxTokens
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
);
|
|
1554
|
+
if (response.error) {
|
|
1555
|
+
return {
|
|
1556
|
+
data: null,
|
|
1557
|
+
error: {
|
|
1558
|
+
message: response.error.message,
|
|
1559
|
+
code: response.error.code,
|
|
1560
|
+
status: response.status
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
return { data: response.data, error: null };
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Generate edge function from natural language
|
|
1568
|
+
*
|
|
1569
|
+
* @example
|
|
1570
|
+
* ```typescript
|
|
1571
|
+
* const { data, error } = await vaif.ai.generateFunction({
|
|
1572
|
+
* prompt: 'Create a function that sends welcome emails to new users',
|
|
1573
|
+
* name: 'send-welcome-email',
|
|
1574
|
+
* runtime: 'edge'
|
|
1575
|
+
* });
|
|
1576
|
+
* ```
|
|
1577
|
+
*/
|
|
1578
|
+
async generateFunction(request, options = {}) {
|
|
1579
|
+
const response = await this.client.request(
|
|
1580
|
+
"/ai/generate/function",
|
|
1581
|
+
{
|
|
1582
|
+
method: "POST",
|
|
1583
|
+
body: {
|
|
1584
|
+
...request,
|
|
1585
|
+
model: options.model,
|
|
1586
|
+
temperature: options.temperature,
|
|
1587
|
+
maxTokens: options.maxTokens
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
);
|
|
1591
|
+
if (response.error) {
|
|
1592
|
+
return {
|
|
1593
|
+
data: null,
|
|
1594
|
+
error: {
|
|
1595
|
+
message: response.error.message,
|
|
1596
|
+
code: response.error.code,
|
|
1597
|
+
status: response.status
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
return { data: response.data, error: null };
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Generate API endpoint from natural language
|
|
1605
|
+
*
|
|
1606
|
+
* @example
|
|
1607
|
+
* ```typescript
|
|
1608
|
+
* const { data, error } = await vaif.ai.generateEndpoint({
|
|
1609
|
+
* prompt: 'Create an endpoint to get user profile with their recent posts',
|
|
1610
|
+
* method: 'GET',
|
|
1611
|
+
* path: '/users/:id/profile',
|
|
1612
|
+
* requiresAuth: true
|
|
1613
|
+
* });
|
|
1614
|
+
* ```
|
|
1615
|
+
*/
|
|
1616
|
+
async generateEndpoint(request, options = {}) {
|
|
1617
|
+
const response = await this.client.request(
|
|
1618
|
+
"/ai/generate/endpoint",
|
|
1619
|
+
{
|
|
1620
|
+
method: "POST",
|
|
1621
|
+
body: {
|
|
1622
|
+
...request,
|
|
1623
|
+
model: options.model,
|
|
1624
|
+
temperature: options.temperature,
|
|
1625
|
+
maxTokens: options.maxTokens
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
);
|
|
1629
|
+
if (response.error) {
|
|
1630
|
+
return {
|
|
1631
|
+
data: null,
|
|
1632
|
+
error: {
|
|
1633
|
+
message: response.error.message,
|
|
1634
|
+
code: response.error.code,
|
|
1635
|
+
status: response.status
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
return { data: response.data, error: null };
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Chat with AI assistant for project help
|
|
1643
|
+
*
|
|
1644
|
+
* @example
|
|
1645
|
+
* ```typescript
|
|
1646
|
+
* const { data, error } = await vaif.ai.chat({
|
|
1647
|
+
* messages: [
|
|
1648
|
+
* { role: 'user', content: 'How do I add pagination to my posts query?' }
|
|
1649
|
+
* ],
|
|
1650
|
+
* context: {
|
|
1651
|
+
* schema: mySchemaString
|
|
1652
|
+
* }
|
|
1653
|
+
* });
|
|
1654
|
+
* ```
|
|
1655
|
+
*/
|
|
1656
|
+
async chat(request, options = {}) {
|
|
1657
|
+
const response = await this.client.request("/ai/chat", {
|
|
1658
|
+
method: "POST",
|
|
1659
|
+
body: {
|
|
1660
|
+
...request,
|
|
1661
|
+
model: options.model,
|
|
1662
|
+
temperature: options.temperature,
|
|
1663
|
+
maxTokens: options.maxTokens
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
if (response.error) {
|
|
1667
|
+
return {
|
|
1668
|
+
data: null,
|
|
1669
|
+
error: {
|
|
1670
|
+
message: response.error.message,
|
|
1671
|
+
code: response.error.code,
|
|
1672
|
+
status: response.status
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
return { data: response.data, error: null };
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Stream chat response
|
|
1680
|
+
*
|
|
1681
|
+
* @example
|
|
1682
|
+
* ```typescript
|
|
1683
|
+
* for await (const chunk of vaif.ai.chatStream({
|
|
1684
|
+
* messages: [{ role: 'user', content: 'Explain this schema' }]
|
|
1685
|
+
* })) {
|
|
1686
|
+
* process.stdout.write(chunk);
|
|
1687
|
+
* }
|
|
1688
|
+
* ```
|
|
1689
|
+
*/
|
|
1690
|
+
async *chatStream(request, options = {}) {
|
|
1691
|
+
const config = this.client.getConfig();
|
|
1692
|
+
const url = new URL("/ai/chat/stream", config.apiUrl);
|
|
1693
|
+
const headers = {
|
|
1694
|
+
"Content-Type": "application/json",
|
|
1695
|
+
"X-Project-ID": config.projectId,
|
|
1696
|
+
apikey: config.apiKey
|
|
1697
|
+
};
|
|
1698
|
+
const token = this.client.getAccessToken();
|
|
1699
|
+
if (token) {
|
|
1700
|
+
headers.Authorization = `Bearer ${token}`;
|
|
1701
|
+
}
|
|
1702
|
+
const response = await fetch(url.toString(), {
|
|
1703
|
+
method: "POST",
|
|
1704
|
+
headers,
|
|
1705
|
+
body: JSON.stringify({
|
|
1706
|
+
...request,
|
|
1707
|
+
model: options.model,
|
|
1708
|
+
temperature: options.temperature,
|
|
1709
|
+
maxTokens: options.maxTokens,
|
|
1710
|
+
stream: true
|
|
1711
|
+
})
|
|
1712
|
+
});
|
|
1713
|
+
if (!response.ok) {
|
|
1714
|
+
throw new Error(`AI chat failed: ${response.statusText}`);
|
|
1715
|
+
}
|
|
1716
|
+
const reader = response.body?.getReader();
|
|
1717
|
+
if (!reader) {
|
|
1718
|
+
throw new Error("No response body");
|
|
1719
|
+
}
|
|
1720
|
+
const decoder = new TextDecoder();
|
|
1721
|
+
try {
|
|
1722
|
+
while (true) {
|
|
1723
|
+
const { done, value } = await reader.read();
|
|
1724
|
+
if (done) break;
|
|
1725
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
1726
|
+
const lines = chunk.split("\n");
|
|
1727
|
+
for (const line of lines) {
|
|
1728
|
+
if (line.startsWith("data: ")) {
|
|
1729
|
+
const data = line.slice(6);
|
|
1730
|
+
if (data === "[DONE]") {
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
try {
|
|
1734
|
+
const parsed = JSON.parse(data);
|
|
1735
|
+
if (parsed.content) {
|
|
1736
|
+
yield parsed.content;
|
|
1737
|
+
}
|
|
1738
|
+
} catch {
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
} finally {
|
|
1744
|
+
reader.releaseLock();
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Get AI credits information
|
|
1749
|
+
*
|
|
1750
|
+
* @example
|
|
1751
|
+
* ```typescript
|
|
1752
|
+
* const { data, error } = await vaif.ai.getCredits();
|
|
1753
|
+
* console.log(`Balance: ${data.balance} credits`);
|
|
1754
|
+
* ```
|
|
1755
|
+
*/
|
|
1756
|
+
async getCredits() {
|
|
1757
|
+
const response = await this.client.request("/ai/credits");
|
|
1758
|
+
if (response.error) {
|
|
1759
|
+
return {
|
|
1760
|
+
data: null,
|
|
1761
|
+
error: {
|
|
1762
|
+
message: response.error.message,
|
|
1763
|
+
code: response.error.code,
|
|
1764
|
+
status: response.status
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
return { data: response.data, error: null };
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Explain code or schema
|
|
1772
|
+
*/
|
|
1773
|
+
async explain(code, options = {}) {
|
|
1774
|
+
const response = await this.client.request(
|
|
1775
|
+
"/ai/explain",
|
|
1776
|
+
{
|
|
1777
|
+
method: "POST",
|
|
1778
|
+
body: {
|
|
1779
|
+
code,
|
|
1780
|
+
language: options.language,
|
|
1781
|
+
context: options.context,
|
|
1782
|
+
model: options.model,
|
|
1783
|
+
temperature: options.temperature
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
);
|
|
1787
|
+
if (response.error) {
|
|
1788
|
+
return {
|
|
1789
|
+
data: null,
|
|
1790
|
+
error: {
|
|
1791
|
+
message: response.error.message,
|
|
1792
|
+
code: response.error.code,
|
|
1793
|
+
status: response.status
|
|
1794
|
+
}
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
return { data: response.data, error: null };
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Review code for issues and improvements
|
|
1801
|
+
*/
|
|
1802
|
+
async review(code, options = {}) {
|
|
1803
|
+
const response = await this.client.request("/ai/review", {
|
|
1804
|
+
method: "POST",
|
|
1805
|
+
body: {
|
|
1806
|
+
code,
|
|
1807
|
+
language: options.language,
|
|
1808
|
+
focus: options.focus,
|
|
1809
|
+
model: options.model,
|
|
1810
|
+
temperature: options.temperature
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
if (response.error) {
|
|
1814
|
+
return {
|
|
1815
|
+
data: null,
|
|
1816
|
+
error: {
|
|
1817
|
+
message: response.error.message,
|
|
1818
|
+
code: response.error.code,
|
|
1819
|
+
status: response.status
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
return { data: response.data, error: null };
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Generate TypeScript types from schema
|
|
1827
|
+
*/
|
|
1828
|
+
async generateTypes(schema, options = {}) {
|
|
1829
|
+
const response = await this.client.request(
|
|
1830
|
+
"/ai/generate/types",
|
|
1831
|
+
{
|
|
1832
|
+
method: "POST",
|
|
1833
|
+
body: {
|
|
1834
|
+
schema,
|
|
1835
|
+
format: options.format ?? "typescript",
|
|
1836
|
+
model: options.model
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
);
|
|
1840
|
+
if (response.error) {
|
|
1841
|
+
return {
|
|
1842
|
+
data: null,
|
|
1843
|
+
error: {
|
|
1844
|
+
message: response.error.message,
|
|
1845
|
+
code: response.error.code,
|
|
1846
|
+
status: response.status
|
|
1847
|
+
}
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
return { data: response.data, error: null };
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Generate SQL migration from schema diff
|
|
1854
|
+
*/
|
|
1855
|
+
async generateMigration(options) {
|
|
1856
|
+
const response = await this.client.request("/ai/generate/migration", {
|
|
1857
|
+
method: "POST",
|
|
1858
|
+
body: {
|
|
1859
|
+
currentSchema: options.currentSchema,
|
|
1860
|
+
targetSchema: options.targetSchema,
|
|
1861
|
+
database: options.database ?? "postgresql",
|
|
1862
|
+
model: options.model
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
if (response.error) {
|
|
1866
|
+
return {
|
|
1867
|
+
data: null,
|
|
1868
|
+
error: {
|
|
1869
|
+
message: response.error.message,
|
|
1870
|
+
code: response.error.code,
|
|
1871
|
+
status: response.status
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
return { data: response.data, error: null };
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
// src/lib/typegen.ts
|
|
1880
|
+
var PG_TO_TS_TYPES = {
|
|
1881
|
+
// Numeric
|
|
1882
|
+
smallint: "number",
|
|
1883
|
+
integer: "number",
|
|
1884
|
+
bigint: "number",
|
|
1885
|
+
int2: "number",
|
|
1886
|
+
int4: "number",
|
|
1887
|
+
int8: "number",
|
|
1888
|
+
decimal: "number",
|
|
1889
|
+
numeric: "number",
|
|
1890
|
+
real: "number",
|
|
1891
|
+
"double precision": "number",
|
|
1892
|
+
float4: "number",
|
|
1893
|
+
float8: "number",
|
|
1894
|
+
serial: "number",
|
|
1895
|
+
bigserial: "number",
|
|
1896
|
+
smallserial: "number",
|
|
1897
|
+
money: "number",
|
|
1898
|
+
// String
|
|
1899
|
+
character: "string",
|
|
1900
|
+
"character varying": "string",
|
|
1901
|
+
varchar: "string",
|
|
1902
|
+
char: "string",
|
|
1903
|
+
text: "string",
|
|
1904
|
+
name: "string",
|
|
1905
|
+
citext: "string",
|
|
1906
|
+
// Boolean
|
|
1907
|
+
boolean: "boolean",
|
|
1908
|
+
bool: "boolean",
|
|
1909
|
+
// Date/Time
|
|
1910
|
+
date: "string",
|
|
1911
|
+
time: "string",
|
|
1912
|
+
"time with time zone": "string",
|
|
1913
|
+
"time without time zone": "string",
|
|
1914
|
+
timestamp: "string",
|
|
1915
|
+
"timestamp with time zone": "string",
|
|
1916
|
+
"timestamp without time zone": "string",
|
|
1917
|
+
timestamptz: "string",
|
|
1918
|
+
timetz: "string",
|
|
1919
|
+
interval: "string",
|
|
1920
|
+
// Binary
|
|
1921
|
+
bytea: "string",
|
|
1922
|
+
// UUID
|
|
1923
|
+
uuid: "string",
|
|
1924
|
+
// JSON
|
|
1925
|
+
json: "unknown",
|
|
1926
|
+
jsonb: "unknown",
|
|
1927
|
+
// Arrays (handled separately)
|
|
1928
|
+
"ARRAY": "unknown[]",
|
|
1929
|
+
// Network
|
|
1930
|
+
inet: "string",
|
|
1931
|
+
cidr: "string",
|
|
1932
|
+
macaddr: "string",
|
|
1933
|
+
macaddr8: "string",
|
|
1934
|
+
// Geometric
|
|
1935
|
+
point: "{ x: number; y: number }",
|
|
1936
|
+
line: "string",
|
|
1937
|
+
lseg: "string",
|
|
1938
|
+
box: "string",
|
|
1939
|
+
path: "string",
|
|
1940
|
+
polygon: "string",
|
|
1941
|
+
circle: "string",
|
|
1942
|
+
// Text search
|
|
1943
|
+
tsvector: "string",
|
|
1944
|
+
tsquery: "string",
|
|
1945
|
+
// Other
|
|
1946
|
+
xml: "string",
|
|
1947
|
+
void: "void",
|
|
1948
|
+
unknown: "unknown"
|
|
1949
|
+
};
|
|
1950
|
+
var VaifTypeGen = class {
|
|
1951
|
+
constructor(client) {
|
|
1952
|
+
this.client = client;
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Fetch database schema from the API.
|
|
1956
|
+
*/
|
|
1957
|
+
async fetchSchema(schema = "public") {
|
|
1958
|
+
const response = await this.client.request(
|
|
1959
|
+
`/database/schema?schema=${schema}`
|
|
1960
|
+
);
|
|
1961
|
+
if (response.error) {
|
|
1962
|
+
throw new Error(response.error.message);
|
|
1963
|
+
}
|
|
1964
|
+
return response.data;
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Generate TypeScript types from schema.
|
|
1968
|
+
*/
|
|
1969
|
+
async generate(options = {}) {
|
|
1970
|
+
const {
|
|
1971
|
+
includeRow = true,
|
|
1972
|
+
includeInsert = true,
|
|
1973
|
+
includeUpdate = true,
|
|
1974
|
+
includeRelationships = true,
|
|
1975
|
+
schema = "public",
|
|
1976
|
+
tables = [],
|
|
1977
|
+
excludeTables = [],
|
|
1978
|
+
includeComments = true,
|
|
1979
|
+
exportFormat = "named",
|
|
1980
|
+
namespace = "Database"
|
|
1981
|
+
} = options;
|
|
1982
|
+
const dbSchema = await this.fetchSchema(schema);
|
|
1983
|
+
const lines = [];
|
|
1984
|
+
lines.push("/**");
|
|
1985
|
+
lines.push(" * VAIF Studio Database Types");
|
|
1986
|
+
lines.push(` * Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
1987
|
+
lines.push(" * DO NOT EDIT - This file is auto-generated");
|
|
1988
|
+
lines.push(" */");
|
|
1989
|
+
lines.push("");
|
|
1990
|
+
if (dbSchema.enums.length > 0) {
|
|
1991
|
+
lines.push("// Enums");
|
|
1992
|
+
for (const enumDef of dbSchema.enums) {
|
|
1993
|
+
lines.push(...this.generateEnum(enumDef, includeComments));
|
|
1994
|
+
lines.push("");
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
let filteredTables = dbSchema.tables.filter((t) => {
|
|
1998
|
+
if (tables.length > 0 && !tables.includes(t.name)) return false;
|
|
1999
|
+
if (excludeTables.includes(t.name)) return false;
|
|
2000
|
+
return true;
|
|
2001
|
+
});
|
|
2002
|
+
const tableTypes = [];
|
|
2003
|
+
for (const table of filteredTables) {
|
|
2004
|
+
const types = this.generateTableTypes(table, {
|
|
2005
|
+
includeRow,
|
|
2006
|
+
includeInsert,
|
|
2007
|
+
includeUpdate,
|
|
2008
|
+
includeRelationships,
|
|
2009
|
+
includeComments,
|
|
2010
|
+
allTables: dbSchema.tables
|
|
2011
|
+
});
|
|
2012
|
+
tableTypes.push(types);
|
|
2013
|
+
}
|
|
2014
|
+
for (const view of dbSchema.views) {
|
|
2015
|
+
if (tables.length > 0 && !tables.includes(view.name)) continue;
|
|
2016
|
+
if (excludeTables.includes(view.name)) continue;
|
|
2017
|
+
const types = this.generateViewTypes(view, includeComments);
|
|
2018
|
+
tableTypes.push(types);
|
|
2019
|
+
}
|
|
2020
|
+
if (exportFormat === "namespace") {
|
|
2021
|
+
lines.push(`export namespace ${namespace} {`);
|
|
2022
|
+
for (const types of tableTypes) {
|
|
2023
|
+
lines.push(this.indent(types, 2));
|
|
2024
|
+
}
|
|
2025
|
+
lines.push("}");
|
|
2026
|
+
} else {
|
|
2027
|
+
lines.push(...tableTypes);
|
|
2028
|
+
}
|
|
2029
|
+
lines.push("");
|
|
2030
|
+
lines.push("// Database type helper");
|
|
2031
|
+
lines.push("export interface Database {");
|
|
2032
|
+
lines.push(" public: {");
|
|
2033
|
+
lines.push(" Tables: {");
|
|
2034
|
+
for (const table of filteredTables) {
|
|
2035
|
+
const pascalName = this.toPascalCase(table.name);
|
|
2036
|
+
lines.push(` ${table.name}: {`);
|
|
2037
|
+
if (includeRow) lines.push(` Row: ${pascalName}Row;`);
|
|
2038
|
+
if (includeInsert) lines.push(` Insert: ${pascalName}Insert;`);
|
|
2039
|
+
if (includeUpdate) lines.push(` Update: ${pascalName}Update;`);
|
|
2040
|
+
lines.push(" };");
|
|
2041
|
+
}
|
|
2042
|
+
lines.push(" };");
|
|
2043
|
+
lines.push(" Views: {");
|
|
2044
|
+
for (const view of dbSchema.views) {
|
|
2045
|
+
const pascalName = this.toPascalCase(view.name);
|
|
2046
|
+
lines.push(` ${view.name}: {`);
|
|
2047
|
+
lines.push(` Row: ${pascalName}Row;`);
|
|
2048
|
+
lines.push(" };");
|
|
2049
|
+
}
|
|
2050
|
+
lines.push(" };");
|
|
2051
|
+
lines.push(" Enums: {");
|
|
2052
|
+
for (const enumDef of dbSchema.enums) {
|
|
2053
|
+
lines.push(` ${enumDef.name}: ${this.toPascalCase(enumDef.name)};`);
|
|
2054
|
+
}
|
|
2055
|
+
lines.push(" };");
|
|
2056
|
+
lines.push(" };");
|
|
2057
|
+
lines.push("}");
|
|
2058
|
+
return lines.join("\n");
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Generate TypeScript from schema JSON directly.
|
|
2062
|
+
*/
|
|
2063
|
+
generateFromSchema(schema, options = {}) {
|
|
2064
|
+
const {
|
|
2065
|
+
includeRow = true,
|
|
2066
|
+
includeInsert = true,
|
|
2067
|
+
includeUpdate = true,
|
|
2068
|
+
includeRelationships = true,
|
|
2069
|
+
tables = [],
|
|
2070
|
+
excludeTables = [],
|
|
2071
|
+
includeComments = true,
|
|
2072
|
+
exportFormat = "named",
|
|
2073
|
+
namespace = "Database"
|
|
2074
|
+
} = options;
|
|
2075
|
+
const lines = [];
|
|
2076
|
+
lines.push("/**");
|
|
2077
|
+
lines.push(" * VAIF Studio Database Types");
|
|
2078
|
+
lines.push(` * Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
2079
|
+
lines.push(" * DO NOT EDIT - This file is auto-generated");
|
|
2080
|
+
lines.push(" */");
|
|
2081
|
+
lines.push("");
|
|
2082
|
+
if (schema.enums && schema.enums.length > 0) {
|
|
2083
|
+
lines.push("// Enums");
|
|
2084
|
+
for (const enumDef of schema.enums) {
|
|
2085
|
+
lines.push(...this.generateEnum(enumDef, includeComments));
|
|
2086
|
+
lines.push("");
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
let filteredTables = (schema.tables || []).filter((t) => {
|
|
2090
|
+
if (tables.length > 0 && !tables.includes(t.name)) return false;
|
|
2091
|
+
if (excludeTables.includes(t.name)) return false;
|
|
2092
|
+
return true;
|
|
2093
|
+
});
|
|
2094
|
+
const tableTypes = [];
|
|
2095
|
+
for (const table of filteredTables) {
|
|
2096
|
+
const types = this.generateTableTypes(table, {
|
|
2097
|
+
includeRow,
|
|
2098
|
+
includeInsert,
|
|
2099
|
+
includeUpdate,
|
|
2100
|
+
includeRelationships,
|
|
2101
|
+
includeComments,
|
|
2102
|
+
allTables: schema.tables
|
|
2103
|
+
});
|
|
2104
|
+
tableTypes.push(types);
|
|
2105
|
+
}
|
|
2106
|
+
if (exportFormat === "namespace") {
|
|
2107
|
+
lines.push(`export namespace ${namespace} {`);
|
|
2108
|
+
for (const types of tableTypes) {
|
|
2109
|
+
lines.push(this.indent(types, 2));
|
|
2110
|
+
}
|
|
2111
|
+
lines.push("}");
|
|
2112
|
+
} else {
|
|
2113
|
+
lines.push(...tableTypes);
|
|
2114
|
+
}
|
|
2115
|
+
return lines.join("\n");
|
|
2116
|
+
}
|
|
2117
|
+
generateEnum(enumDef, includeComments) {
|
|
2118
|
+
const lines = [];
|
|
2119
|
+
const name = this.toPascalCase(enumDef.name);
|
|
2120
|
+
if (includeComments) {
|
|
2121
|
+
lines.push(`/** Enum: ${enumDef.name} */`);
|
|
2122
|
+
}
|
|
2123
|
+
lines.push(`export type ${name} = ${enumDef.values.map((v) => `'${v}'`).join(" | ")};`);
|
|
2124
|
+
return lines;
|
|
2125
|
+
}
|
|
2126
|
+
generateTableTypes(table, options) {
|
|
2127
|
+
const lines = [];
|
|
2128
|
+
const pascalName = this.toPascalCase(table.name);
|
|
2129
|
+
if (options.includeRow) {
|
|
2130
|
+
if (options.includeComments && table.comment) {
|
|
2131
|
+
lines.push(`/** ${table.comment} */`);
|
|
2132
|
+
}
|
|
2133
|
+
lines.push(`export interface ${pascalName}Row {`);
|
|
2134
|
+
for (const col of table.columns) {
|
|
2135
|
+
const tsType = this.pgTypeToTs(col.type);
|
|
2136
|
+
const nullable = col.nullable ? " | null" : "";
|
|
2137
|
+
if (options.includeComments && col.comment) {
|
|
2138
|
+
lines.push(` /** ${col.comment} */`);
|
|
2139
|
+
}
|
|
2140
|
+
lines.push(` ${col.name}: ${tsType}${nullable};`);
|
|
2141
|
+
}
|
|
2142
|
+
if (options.includeRelationships) {
|
|
2143
|
+
for (const fk of table.foreignKeys) {
|
|
2144
|
+
const relatedTable = options.allTables.find(
|
|
2145
|
+
(t) => t.name === fk.foreignTable
|
|
2146
|
+
);
|
|
2147
|
+
if (relatedTable) {
|
|
2148
|
+
const relatedPascal = this.toPascalCase(fk.foreignTable);
|
|
2149
|
+
lines.push(` ${fk.foreignTable}?: ${relatedPascal}Row;`);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
lines.push("}");
|
|
2154
|
+
lines.push("");
|
|
2155
|
+
}
|
|
2156
|
+
if (options.includeInsert) {
|
|
2157
|
+
lines.push(`export interface ${pascalName}Insert {`);
|
|
2158
|
+
for (const col of table.columns) {
|
|
2159
|
+
const tsType = this.pgTypeToTs(col.type);
|
|
2160
|
+
const optional = col.nullable || col.defaultValue !== void 0 || col.isPrimaryKey ? "?" : "";
|
|
2161
|
+
const nullable = col.nullable ? " | null" : "";
|
|
2162
|
+
lines.push(` ${col.name}${optional}: ${tsType}${nullable};`);
|
|
2163
|
+
}
|
|
2164
|
+
lines.push("}");
|
|
2165
|
+
lines.push("");
|
|
2166
|
+
}
|
|
2167
|
+
if (options.includeUpdate) {
|
|
2168
|
+
lines.push(`export interface ${pascalName}Update {`);
|
|
2169
|
+
for (const col of table.columns) {
|
|
2170
|
+
if (col.isPrimaryKey) continue;
|
|
2171
|
+
const tsType = this.pgTypeToTs(col.type);
|
|
2172
|
+
const nullable = col.nullable ? " | null" : "";
|
|
2173
|
+
lines.push(` ${col.name}?: ${tsType}${nullable};`);
|
|
2174
|
+
}
|
|
2175
|
+
lines.push("}");
|
|
2176
|
+
lines.push("");
|
|
2177
|
+
}
|
|
2178
|
+
return lines.join("\n");
|
|
2179
|
+
}
|
|
2180
|
+
generateViewTypes(view, includeComments) {
|
|
2181
|
+
const lines = [];
|
|
2182
|
+
const pascalName = this.toPascalCase(view.name);
|
|
2183
|
+
if (includeComments) {
|
|
2184
|
+
lines.push(`/** View: ${view.name} */`);
|
|
2185
|
+
}
|
|
2186
|
+
lines.push(`export interface ${pascalName}Row {`);
|
|
2187
|
+
for (const col of view.columns) {
|
|
2188
|
+
const tsType = this.pgTypeToTs(col.type);
|
|
2189
|
+
const nullable = col.nullable ? " | null" : "";
|
|
2190
|
+
lines.push(` ${col.name}: ${tsType}${nullable};`);
|
|
2191
|
+
}
|
|
2192
|
+
lines.push("}");
|
|
2193
|
+
lines.push("");
|
|
2194
|
+
return lines.join("\n");
|
|
2195
|
+
}
|
|
2196
|
+
pgTypeToTs(pgType) {
|
|
2197
|
+
if (pgType.endsWith("[]")) {
|
|
2198
|
+
const baseType = pgType.slice(0, -2);
|
|
2199
|
+
const tsBaseType = PG_TO_TS_TYPES[baseType.toLowerCase()] || "unknown";
|
|
2200
|
+
return `${tsBaseType}[]`;
|
|
2201
|
+
}
|
|
2202
|
+
const arrayMatch = pgType.match(/^(.+)\s+ARRAY$/i);
|
|
2203
|
+
if (arrayMatch) {
|
|
2204
|
+
const baseType = arrayMatch[1];
|
|
2205
|
+
const tsBaseType = PG_TO_TS_TYPES[baseType.toLowerCase()] || "unknown";
|
|
2206
|
+
return `${tsBaseType}[]`;
|
|
2207
|
+
}
|
|
2208
|
+
const precisionMatch = pgType.match(/^(\w+)\(/);
|
|
2209
|
+
if (precisionMatch) {
|
|
2210
|
+
const baseType = precisionMatch[1];
|
|
2211
|
+
return PG_TO_TS_TYPES[baseType.toLowerCase()] || "unknown";
|
|
2212
|
+
}
|
|
2213
|
+
return PG_TO_TS_TYPES[pgType.toLowerCase()] || "unknown";
|
|
2214
|
+
}
|
|
2215
|
+
toPascalCase(str) {
|
|
2216
|
+
return str.split(/[_\s-]+/).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
|
|
2217
|
+
}
|
|
2218
|
+
indent(text, spaces) {
|
|
2219
|
+
const pad = " ".repeat(spaces);
|
|
2220
|
+
return text.split("\n").map((line) => line ? pad + line : line).join("\n");
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2223
|
+
function createTypeGen(client) {
|
|
2224
|
+
return new VaifTypeGen(client);
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
// src/lib/client.ts
|
|
2228
|
+
var DEFAULT_API_URL = "https://api.vaif.studio";
|
|
2229
|
+
var DEFAULT_REALTIME_URL = "wss://realtime.vaif.studio";
|
|
2230
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
2231
|
+
var memoryStorage = {
|
|
2232
|
+
data: /* @__PURE__ */ new Map(),
|
|
2233
|
+
getItem(key) {
|
|
2234
|
+
return this.data.get(key) ?? null;
|
|
2235
|
+
},
|
|
2236
|
+
setItem(key, value) {
|
|
2237
|
+
this.data.set(key, value);
|
|
2238
|
+
},
|
|
2239
|
+
removeItem(key) {
|
|
2240
|
+
this.data.delete(key);
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
var browserStorage = typeof window !== "undefined" && window.localStorage ? {
|
|
2244
|
+
getItem: (key) => window.localStorage.getItem(key),
|
|
2245
|
+
setItem: (key, value) => window.localStorage.setItem(key, value),
|
|
2246
|
+
removeItem: (key) => window.localStorage.removeItem(key)
|
|
2247
|
+
} : null;
|
|
2248
|
+
function createClient(options) {
|
|
2249
|
+
return new VaifClient(options);
|
|
2250
|
+
}
|
|
2251
|
+
var VaifClient = class {
|
|
2252
|
+
constructor(options) {
|
|
2253
|
+
this.accessToken = null;
|
|
2254
|
+
if (!options.projectId) {
|
|
2255
|
+
throw new Error("projectId is required");
|
|
2256
|
+
}
|
|
2257
|
+
if (!options.apiKey) {
|
|
2258
|
+
throw new Error("apiKey is required");
|
|
2259
|
+
}
|
|
2260
|
+
this.config = {
|
|
2261
|
+
projectId: options.projectId,
|
|
2262
|
+
apiKey: options.apiKey,
|
|
2263
|
+
apiUrl: options.apiUrl ?? DEFAULT_API_URL,
|
|
2264
|
+
realtimeUrl: options.realtimeUrl ?? DEFAULT_REALTIME_URL,
|
|
2265
|
+
timeout: options.timeout ?? DEFAULT_TIMEOUT,
|
|
2266
|
+
headers: options.headers ?? {},
|
|
2267
|
+
debug: options.debug ?? false,
|
|
2268
|
+
autoRefreshToken: options.autoRefreshToken ?? true,
|
|
2269
|
+
persistSession: options.persistSession ?? true,
|
|
2270
|
+
storage: options.storage ?? browserStorage ?? memoryStorage
|
|
2271
|
+
};
|
|
2272
|
+
this.db = new VaifDatabase(this);
|
|
2273
|
+
this.realtime = new VaifRealtime(this);
|
|
2274
|
+
this.storage = new VaifStorage(this);
|
|
2275
|
+
this.functions = new VaifFunctions(this);
|
|
2276
|
+
this.auth = new VaifAuth(this);
|
|
2277
|
+
this.ai = new VaifAI(this);
|
|
2278
|
+
this.typegen = new VaifTypeGen(this);
|
|
2279
|
+
this.restoreSession();
|
|
2280
|
+
}
|
|
2281
|
+
/**
|
|
2282
|
+
* Get current configuration
|
|
2283
|
+
*/
|
|
2284
|
+
getConfig() {
|
|
2285
|
+
return this.config;
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Set access token for authenticated requests
|
|
2289
|
+
*/
|
|
2290
|
+
setAccessToken(token) {
|
|
2291
|
+
this.accessToken = token;
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Get current access token
|
|
2295
|
+
*/
|
|
2296
|
+
getAccessToken() {
|
|
2297
|
+
return this.accessToken;
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Make an authenticated API request
|
|
2301
|
+
*/
|
|
2302
|
+
async request(path, options = {}) {
|
|
2303
|
+
const url = new URL(path, this.config.apiUrl);
|
|
2304
|
+
if (options.params) {
|
|
2305
|
+
Object.entries(options.params).forEach(([key, value]) => {
|
|
2306
|
+
if (value !== void 0) {
|
|
2307
|
+
url.searchParams.set(key, String(value));
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
const headers = {
|
|
2312
|
+
"Content-Type": "application/json",
|
|
2313
|
+
"X-Project-ID": this.config.projectId,
|
|
2314
|
+
apikey: this.config.apiKey,
|
|
2315
|
+
...this.config.headers,
|
|
2316
|
+
...options.headers
|
|
2317
|
+
};
|
|
2318
|
+
if (this.accessToken) {
|
|
2319
|
+
headers.Authorization = `Bearer ${this.accessToken}`;
|
|
2320
|
+
}
|
|
2321
|
+
const fetchOptions = {
|
|
2322
|
+
method: options.method ?? "GET",
|
|
2323
|
+
headers,
|
|
2324
|
+
signal: options.signal
|
|
2325
|
+
};
|
|
2326
|
+
if (options.body && options.method !== "GET") {
|
|
2327
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
2328
|
+
}
|
|
2329
|
+
const timeoutMs = options.timeout ?? this.config.timeout;
|
|
2330
|
+
const timeoutController = new AbortController();
|
|
2331
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), timeoutMs);
|
|
2332
|
+
if (options.signal) {
|
|
2333
|
+
options.signal.addEventListener("abort", () => timeoutController.abort());
|
|
2334
|
+
}
|
|
2335
|
+
fetchOptions.signal = timeoutController.signal;
|
|
2336
|
+
try {
|
|
2337
|
+
if (this.config.debug) {
|
|
2338
|
+
console.log(`[VAIF] ${options.method ?? "GET"} ${url.toString()}`);
|
|
2339
|
+
}
|
|
2340
|
+
const response = await fetch(url.toString(), fetchOptions);
|
|
2341
|
+
clearTimeout(timeoutId);
|
|
2342
|
+
const contentType = response.headers.get("content-type");
|
|
2343
|
+
let data = null;
|
|
2344
|
+
let error = null;
|
|
2345
|
+
if (contentType?.includes("application/json")) {
|
|
2346
|
+
const json = await response.json();
|
|
2347
|
+
if (response.ok) {
|
|
2348
|
+
data = json;
|
|
2349
|
+
} else {
|
|
2350
|
+
error = {
|
|
2351
|
+
message: json.message ?? json.error ?? "Request failed",
|
|
2352
|
+
code: json.code,
|
|
2353
|
+
status: response.status,
|
|
2354
|
+
details: json.details,
|
|
2355
|
+
requestId: response.headers.get("x-request-id") ?? void 0
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
} else if (!response.ok) {
|
|
2359
|
+
error = {
|
|
2360
|
+
message: `Request failed with status ${response.status}`,
|
|
2361
|
+
status: response.status
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
const responseHeaders = {};
|
|
2365
|
+
response.headers.forEach((value, key) => {
|
|
2366
|
+
responseHeaders[key] = value;
|
|
2367
|
+
});
|
|
2368
|
+
return {
|
|
2369
|
+
data,
|
|
2370
|
+
error,
|
|
2371
|
+
status: response.status,
|
|
2372
|
+
headers: responseHeaders
|
|
2373
|
+
};
|
|
2374
|
+
} catch (err) {
|
|
2375
|
+
clearTimeout(timeoutId);
|
|
2376
|
+
const error = {
|
|
2377
|
+
message: err instanceof Error ? err.name === "AbortError" ? "Request timed out" : err.message : "Unknown error",
|
|
2378
|
+
code: err instanceof Error && err.name === "AbortError" ? "TIMEOUT" : "NETWORK_ERROR"
|
|
2379
|
+
};
|
|
2380
|
+
return {
|
|
2381
|
+
data: null,
|
|
2382
|
+
error,
|
|
2383
|
+
status: 0,
|
|
2384
|
+
headers: {}
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
/**
|
|
2389
|
+
* Restore session from storage
|
|
2390
|
+
*/
|
|
2391
|
+
async restoreSession() {
|
|
2392
|
+
if (!this.config.persistSession) return;
|
|
2393
|
+
try {
|
|
2394
|
+
const sessionStr = await this.config.storage.getItem(
|
|
2395
|
+
`vaif-session-${this.config.projectId}`
|
|
2396
|
+
);
|
|
2397
|
+
if (sessionStr) {
|
|
2398
|
+
const session = JSON.parse(sessionStr);
|
|
2399
|
+
if (session.accessToken && session.expiresAt > Date.now()) {
|
|
2400
|
+
this.accessToken = session.accessToken;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
} catch {
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Log debug messages
|
|
2408
|
+
*/
|
|
2409
|
+
debug(...args) {
|
|
2410
|
+
if (this.config.debug) {
|
|
2411
|
+
console.log("[VAIF]", ...args);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
};
|
|
2415
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2416
|
+
0 && (module.exports = {
|
|
2417
|
+
RealtimeChannel,
|
|
2418
|
+
VaifAI,
|
|
2419
|
+
VaifAuth,
|
|
2420
|
+
VaifClient,
|
|
2421
|
+
VaifDatabase,
|
|
2422
|
+
VaifFunctions,
|
|
2423
|
+
VaifRealtime,
|
|
2424
|
+
VaifStorage,
|
|
2425
|
+
VaifTypeGen,
|
|
2426
|
+
createClient,
|
|
2427
|
+
createTypeGen
|
|
2428
|
+
});
|