@insforge/sdk 1.0.3-dev.2 → 1.0.3-refresh.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -10
- package/dist/browser.mjs +3059 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/index.d.mts +32 -54
- package/dist/index.d.ts +32 -54
- package/dist/index.js +136 -135
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +136 -134
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/browser.mjs
ADDED
|
@@ -0,0 +1,3059 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __esm = (fn, res) => function __init() {
|
|
8
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
|
+
};
|
|
10
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
11
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
12
|
+
};
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
17
|
+
var __copyProps = (to, from, except, desc) => {
|
|
18
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
+
for (let key of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
21
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
26
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
27
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
28
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
29
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
30
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
31
|
+
mod
|
|
32
|
+
));
|
|
33
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
34
|
+
|
|
35
|
+
// node_modules/@supabase/node-fetch/browser.js
|
|
36
|
+
var browser_exports = {};
|
|
37
|
+
__export(browser_exports, {
|
|
38
|
+
Headers: () => Headers2,
|
|
39
|
+
Request: () => Request,
|
|
40
|
+
Response: () => Response,
|
|
41
|
+
default: () => browser_default,
|
|
42
|
+
fetch: () => fetch2
|
|
43
|
+
});
|
|
44
|
+
var getGlobal, globalObject, fetch2, browser_default, Headers2, Request, Response;
|
|
45
|
+
var init_browser = __esm({
|
|
46
|
+
"node_modules/@supabase/node-fetch/browser.js"() {
|
|
47
|
+
"use strict";
|
|
48
|
+
getGlobal = function() {
|
|
49
|
+
if (typeof self !== "undefined") {
|
|
50
|
+
return self;
|
|
51
|
+
}
|
|
52
|
+
if (typeof window !== "undefined") {
|
|
53
|
+
return window;
|
|
54
|
+
}
|
|
55
|
+
if (typeof global !== "undefined") {
|
|
56
|
+
return global;
|
|
57
|
+
}
|
|
58
|
+
throw new Error("unable to locate global object");
|
|
59
|
+
};
|
|
60
|
+
globalObject = getGlobal();
|
|
61
|
+
fetch2 = globalObject.fetch;
|
|
62
|
+
browser_default = globalObject.fetch.bind(globalObject);
|
|
63
|
+
Headers2 = globalObject.Headers;
|
|
64
|
+
Request = globalObject.Request;
|
|
65
|
+
Response = globalObject.Response;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// node_modules/@supabase/postgrest-js/dist/cjs/PostgrestError.js
|
|
70
|
+
var require_PostgrestError = __commonJS({
|
|
71
|
+
"node_modules/@supabase/postgrest-js/dist/cjs/PostgrestError.js"(exports) {
|
|
72
|
+
"use strict";
|
|
73
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
74
|
+
var PostgrestError2 = class extends Error {
|
|
75
|
+
constructor(context) {
|
|
76
|
+
super(context.message);
|
|
77
|
+
this.name = "PostgrestError";
|
|
78
|
+
this.details = context.details;
|
|
79
|
+
this.hint = context.hint;
|
|
80
|
+
this.code = context.code;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
exports.default = PostgrestError2;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// node_modules/@supabase/postgrest-js/dist/cjs/PostgrestBuilder.js
|
|
88
|
+
var require_PostgrestBuilder = __commonJS({
|
|
89
|
+
"node_modules/@supabase/postgrest-js/dist/cjs/PostgrestBuilder.js"(exports) {
|
|
90
|
+
"use strict";
|
|
91
|
+
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
92
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
93
|
+
};
|
|
94
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
95
|
+
var node_fetch_1 = __importDefault((init_browser(), __toCommonJS(browser_exports)));
|
|
96
|
+
var PostgrestError_1 = __importDefault(require_PostgrestError());
|
|
97
|
+
var PostgrestBuilder2 = class {
|
|
98
|
+
constructor(builder) {
|
|
99
|
+
var _a, _b;
|
|
100
|
+
this.shouldThrowOnError = false;
|
|
101
|
+
this.method = builder.method;
|
|
102
|
+
this.url = builder.url;
|
|
103
|
+
this.headers = new Headers(builder.headers);
|
|
104
|
+
this.schema = builder.schema;
|
|
105
|
+
this.body = builder.body;
|
|
106
|
+
this.shouldThrowOnError = (_a = builder.shouldThrowOnError) !== null && _a !== void 0 ? _a : false;
|
|
107
|
+
this.signal = builder.signal;
|
|
108
|
+
this.isMaybeSingle = (_b = builder.isMaybeSingle) !== null && _b !== void 0 ? _b : false;
|
|
109
|
+
if (builder.fetch) {
|
|
110
|
+
this.fetch = builder.fetch;
|
|
111
|
+
} else if (typeof fetch === "undefined") {
|
|
112
|
+
this.fetch = node_fetch_1.default;
|
|
113
|
+
} else {
|
|
114
|
+
this.fetch = fetch;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* If there's an error with the query, throwOnError will reject the promise by
|
|
119
|
+
* throwing the error instead of returning it as part of a successful response.
|
|
120
|
+
*
|
|
121
|
+
* {@link https://github.com/supabase/supabase-js/issues/92}
|
|
122
|
+
*/
|
|
123
|
+
throwOnError() {
|
|
124
|
+
this.shouldThrowOnError = true;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Set an HTTP header for the request.
|
|
129
|
+
*/
|
|
130
|
+
setHeader(name, value) {
|
|
131
|
+
this.headers = new Headers(this.headers);
|
|
132
|
+
this.headers.set(name, value);
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
then(onfulfilled, onrejected) {
|
|
136
|
+
if (this.schema === void 0) {
|
|
137
|
+
} else if (["GET", "HEAD"].includes(this.method)) {
|
|
138
|
+
this.headers.set("Accept-Profile", this.schema);
|
|
139
|
+
} else {
|
|
140
|
+
this.headers.set("Content-Profile", this.schema);
|
|
141
|
+
}
|
|
142
|
+
if (this.method !== "GET" && this.method !== "HEAD") {
|
|
143
|
+
this.headers.set("Content-Type", "application/json");
|
|
144
|
+
}
|
|
145
|
+
const _fetch = this.fetch;
|
|
146
|
+
let res = _fetch(this.url.toString(), {
|
|
147
|
+
method: this.method,
|
|
148
|
+
headers: this.headers,
|
|
149
|
+
body: JSON.stringify(this.body),
|
|
150
|
+
signal: this.signal
|
|
151
|
+
}).then(async (res2) => {
|
|
152
|
+
var _a, _b, _c, _d;
|
|
153
|
+
let error = null;
|
|
154
|
+
let data = null;
|
|
155
|
+
let count = null;
|
|
156
|
+
let status = res2.status;
|
|
157
|
+
let statusText = res2.statusText;
|
|
158
|
+
if (res2.ok) {
|
|
159
|
+
if (this.method !== "HEAD") {
|
|
160
|
+
const body = await res2.text();
|
|
161
|
+
if (body === "") {
|
|
162
|
+
} else if (this.headers.get("Accept") === "text/csv") {
|
|
163
|
+
data = body;
|
|
164
|
+
} else if (this.headers.get("Accept") && ((_a = this.headers.get("Accept")) === null || _a === void 0 ? void 0 : _a.includes("application/vnd.pgrst.plan+text"))) {
|
|
165
|
+
data = body;
|
|
166
|
+
} else {
|
|
167
|
+
data = JSON.parse(body);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const countHeader = (_b = this.headers.get("Prefer")) === null || _b === void 0 ? void 0 : _b.match(/count=(exact|planned|estimated)/);
|
|
171
|
+
const contentRange = (_c = res2.headers.get("content-range")) === null || _c === void 0 ? void 0 : _c.split("/");
|
|
172
|
+
if (countHeader && contentRange && contentRange.length > 1) {
|
|
173
|
+
count = parseInt(contentRange[1]);
|
|
174
|
+
}
|
|
175
|
+
if (this.isMaybeSingle && this.method === "GET" && Array.isArray(data)) {
|
|
176
|
+
if (data.length > 1) {
|
|
177
|
+
error = {
|
|
178
|
+
// https://github.com/PostgREST/postgrest/blob/a867d79c42419af16c18c3fb019eba8df992626f/src/PostgREST/Error.hs#L553
|
|
179
|
+
code: "PGRST116",
|
|
180
|
+
details: `Results contain ${data.length} rows, application/vnd.pgrst.object+json requires 1 row`,
|
|
181
|
+
hint: null,
|
|
182
|
+
message: "JSON object requested, multiple (or no) rows returned"
|
|
183
|
+
};
|
|
184
|
+
data = null;
|
|
185
|
+
count = null;
|
|
186
|
+
status = 406;
|
|
187
|
+
statusText = "Not Acceptable";
|
|
188
|
+
} else if (data.length === 1) {
|
|
189
|
+
data = data[0];
|
|
190
|
+
} else {
|
|
191
|
+
data = null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
const body = await res2.text();
|
|
196
|
+
try {
|
|
197
|
+
error = JSON.parse(body);
|
|
198
|
+
if (Array.isArray(error) && res2.status === 404) {
|
|
199
|
+
data = [];
|
|
200
|
+
error = null;
|
|
201
|
+
status = 200;
|
|
202
|
+
statusText = "OK";
|
|
203
|
+
}
|
|
204
|
+
} catch (_e) {
|
|
205
|
+
if (res2.status === 404 && body === "") {
|
|
206
|
+
status = 204;
|
|
207
|
+
statusText = "No Content";
|
|
208
|
+
} else {
|
|
209
|
+
error = {
|
|
210
|
+
message: body
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (error && this.isMaybeSingle && ((_d = error === null || error === void 0 ? void 0 : error.details) === null || _d === void 0 ? void 0 : _d.includes("0 rows"))) {
|
|
215
|
+
error = null;
|
|
216
|
+
status = 200;
|
|
217
|
+
statusText = "OK";
|
|
218
|
+
}
|
|
219
|
+
if (error && this.shouldThrowOnError) {
|
|
220
|
+
throw new PostgrestError_1.default(error);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const postgrestResponse = {
|
|
224
|
+
error,
|
|
225
|
+
data,
|
|
226
|
+
count,
|
|
227
|
+
status,
|
|
228
|
+
statusText
|
|
229
|
+
};
|
|
230
|
+
return postgrestResponse;
|
|
231
|
+
});
|
|
232
|
+
if (!this.shouldThrowOnError) {
|
|
233
|
+
res = res.catch((fetchError) => {
|
|
234
|
+
var _a, _b, _c;
|
|
235
|
+
return {
|
|
236
|
+
error: {
|
|
237
|
+
message: `${(_a = fetchError === null || fetchError === void 0 ? void 0 : fetchError.name) !== null && _a !== void 0 ? _a : "FetchError"}: ${fetchError === null || fetchError === void 0 ? void 0 : fetchError.message}`,
|
|
238
|
+
details: `${(_b = fetchError === null || fetchError === void 0 ? void 0 : fetchError.stack) !== null && _b !== void 0 ? _b : ""}`,
|
|
239
|
+
hint: "",
|
|
240
|
+
code: `${(_c = fetchError === null || fetchError === void 0 ? void 0 : fetchError.code) !== null && _c !== void 0 ? _c : ""}`
|
|
241
|
+
},
|
|
242
|
+
data: null,
|
|
243
|
+
count: null,
|
|
244
|
+
status: 0,
|
|
245
|
+
statusText: ""
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return res.then(onfulfilled, onrejected);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Override the type of the returned `data`.
|
|
253
|
+
*
|
|
254
|
+
* @typeParam NewResult - The new result type to override with
|
|
255
|
+
* @deprecated Use overrideTypes<yourType, { merge: false }>() method at the end of your call chain instead
|
|
256
|
+
*/
|
|
257
|
+
returns() {
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Override the type of the returned `data` field in the response.
|
|
262
|
+
*
|
|
263
|
+
* @typeParam NewResult - The new type to cast the response data to
|
|
264
|
+
* @typeParam Options - Optional type configuration (defaults to { merge: true })
|
|
265
|
+
* @typeParam Options.merge - When true, merges the new type with existing return type. When false, replaces the existing types entirely (defaults to true)
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* // Merge with existing types (default behavior)
|
|
269
|
+
* const query = supabase
|
|
270
|
+
* .from('users')
|
|
271
|
+
* .select()
|
|
272
|
+
* .overrideTypes<{ custom_field: string }>()
|
|
273
|
+
*
|
|
274
|
+
* // Replace existing types completely
|
|
275
|
+
* const replaceQuery = supabase
|
|
276
|
+
* .from('users')
|
|
277
|
+
* .select()
|
|
278
|
+
* .overrideTypes<{ id: number; name: string }, { merge: false }>()
|
|
279
|
+
* ```
|
|
280
|
+
* @returns A PostgrestBuilder instance with the new type
|
|
281
|
+
*/
|
|
282
|
+
overrideTypes() {
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
exports.default = PostgrestBuilder2;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// node_modules/@supabase/postgrest-js/dist/cjs/PostgrestTransformBuilder.js
|
|
291
|
+
var require_PostgrestTransformBuilder = __commonJS({
|
|
292
|
+
"node_modules/@supabase/postgrest-js/dist/cjs/PostgrestTransformBuilder.js"(exports) {
|
|
293
|
+
"use strict";
|
|
294
|
+
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
295
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
296
|
+
};
|
|
297
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
298
|
+
var PostgrestBuilder_1 = __importDefault(require_PostgrestBuilder());
|
|
299
|
+
var PostgrestTransformBuilder2 = class extends PostgrestBuilder_1.default {
|
|
300
|
+
/**
|
|
301
|
+
* Perform a SELECT on the query result.
|
|
302
|
+
*
|
|
303
|
+
* By default, `.insert()`, `.update()`, `.upsert()`, and `.delete()` do not
|
|
304
|
+
* return modified rows. By calling this method, modified rows are returned in
|
|
305
|
+
* `data`.
|
|
306
|
+
*
|
|
307
|
+
* @param columns - The columns to retrieve, separated by commas
|
|
308
|
+
*/
|
|
309
|
+
select(columns) {
|
|
310
|
+
let quoted = false;
|
|
311
|
+
const cleanedColumns = (columns !== null && columns !== void 0 ? columns : "*").split("").map((c) => {
|
|
312
|
+
if (/\s/.test(c) && !quoted) {
|
|
313
|
+
return "";
|
|
314
|
+
}
|
|
315
|
+
if (c === '"') {
|
|
316
|
+
quoted = !quoted;
|
|
317
|
+
}
|
|
318
|
+
return c;
|
|
319
|
+
}).join("");
|
|
320
|
+
this.url.searchParams.set("select", cleanedColumns);
|
|
321
|
+
this.headers.append("Prefer", "return=representation");
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Order the query result by `column`.
|
|
326
|
+
*
|
|
327
|
+
* You can call this method multiple times to order by multiple columns.
|
|
328
|
+
*
|
|
329
|
+
* You can order referenced tables, but it only affects the ordering of the
|
|
330
|
+
* parent table if you use `!inner` in the query.
|
|
331
|
+
*
|
|
332
|
+
* @param column - The column to order by
|
|
333
|
+
* @param options - Named parameters
|
|
334
|
+
* @param options.ascending - If `true`, the result will be in ascending order
|
|
335
|
+
* @param options.nullsFirst - If `true`, `null`s appear first. If `false`,
|
|
336
|
+
* `null`s appear last.
|
|
337
|
+
* @param options.referencedTable - Set this to order a referenced table by
|
|
338
|
+
* its columns
|
|
339
|
+
* @param options.foreignTable - Deprecated, use `options.referencedTable`
|
|
340
|
+
* instead
|
|
341
|
+
*/
|
|
342
|
+
order(column, { ascending = true, nullsFirst, foreignTable, referencedTable = foreignTable } = {}) {
|
|
343
|
+
const key = referencedTable ? `${referencedTable}.order` : "order";
|
|
344
|
+
const existingOrder = this.url.searchParams.get(key);
|
|
345
|
+
this.url.searchParams.set(key, `${existingOrder ? `${existingOrder},` : ""}${column}.${ascending ? "asc" : "desc"}${nullsFirst === void 0 ? "" : nullsFirst ? ".nullsfirst" : ".nullslast"}`);
|
|
346
|
+
return this;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Limit the query result by `count`.
|
|
350
|
+
*
|
|
351
|
+
* @param count - The maximum number of rows to return
|
|
352
|
+
* @param options - Named parameters
|
|
353
|
+
* @param options.referencedTable - Set this to limit rows of referenced
|
|
354
|
+
* tables instead of the parent table
|
|
355
|
+
* @param options.foreignTable - Deprecated, use `options.referencedTable`
|
|
356
|
+
* instead
|
|
357
|
+
*/
|
|
358
|
+
limit(count, { foreignTable, referencedTable = foreignTable } = {}) {
|
|
359
|
+
const key = typeof referencedTable === "undefined" ? "limit" : `${referencedTable}.limit`;
|
|
360
|
+
this.url.searchParams.set(key, `${count}`);
|
|
361
|
+
return this;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Limit the query result by starting at an offset `from` and ending at the offset `to`.
|
|
365
|
+
* Only records within this range are returned.
|
|
366
|
+
* This respects the query order and if there is no order clause the range could behave unexpectedly.
|
|
367
|
+
* The `from` and `to` values are 0-based and inclusive: `range(1, 3)` will include the second, third
|
|
368
|
+
* and fourth rows of the query.
|
|
369
|
+
*
|
|
370
|
+
* @param from - The starting index from which to limit the result
|
|
371
|
+
* @param to - The last index to which to limit the result
|
|
372
|
+
* @param options - Named parameters
|
|
373
|
+
* @param options.referencedTable - Set this to limit rows of referenced
|
|
374
|
+
* tables instead of the parent table
|
|
375
|
+
* @param options.foreignTable - Deprecated, use `options.referencedTable`
|
|
376
|
+
* instead
|
|
377
|
+
*/
|
|
378
|
+
range(from, to, { foreignTable, referencedTable = foreignTable } = {}) {
|
|
379
|
+
const keyOffset = typeof referencedTable === "undefined" ? "offset" : `${referencedTable}.offset`;
|
|
380
|
+
const keyLimit = typeof referencedTable === "undefined" ? "limit" : `${referencedTable}.limit`;
|
|
381
|
+
this.url.searchParams.set(keyOffset, `${from}`);
|
|
382
|
+
this.url.searchParams.set(keyLimit, `${to - from + 1}`);
|
|
383
|
+
return this;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Set the AbortSignal for the fetch request.
|
|
387
|
+
*
|
|
388
|
+
* @param signal - The AbortSignal to use for the fetch request
|
|
389
|
+
*/
|
|
390
|
+
abortSignal(signal) {
|
|
391
|
+
this.signal = signal;
|
|
392
|
+
return this;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Return `data` as a single object instead of an array of objects.
|
|
396
|
+
*
|
|
397
|
+
* Query result must be one row (e.g. using `.limit(1)`), otherwise this
|
|
398
|
+
* returns an error.
|
|
399
|
+
*/
|
|
400
|
+
single() {
|
|
401
|
+
this.headers.set("Accept", "application/vnd.pgrst.object+json");
|
|
402
|
+
return this;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Return `data` as a single object instead of an array of objects.
|
|
406
|
+
*
|
|
407
|
+
* Query result must be zero or one row (e.g. using `.limit(1)`), otherwise
|
|
408
|
+
* this returns an error.
|
|
409
|
+
*/
|
|
410
|
+
maybeSingle() {
|
|
411
|
+
if (this.method === "GET") {
|
|
412
|
+
this.headers.set("Accept", "application/json");
|
|
413
|
+
} else {
|
|
414
|
+
this.headers.set("Accept", "application/vnd.pgrst.object+json");
|
|
415
|
+
}
|
|
416
|
+
this.isMaybeSingle = true;
|
|
417
|
+
return this;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Return `data` as a string in CSV format.
|
|
421
|
+
*/
|
|
422
|
+
csv() {
|
|
423
|
+
this.headers.set("Accept", "text/csv");
|
|
424
|
+
return this;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Return `data` as an object in [GeoJSON](https://geojson.org) format.
|
|
428
|
+
*/
|
|
429
|
+
geojson() {
|
|
430
|
+
this.headers.set("Accept", "application/geo+json");
|
|
431
|
+
return this;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Return `data` as the EXPLAIN plan for the query.
|
|
435
|
+
*
|
|
436
|
+
* You need to enable the
|
|
437
|
+
* [db_plan_enabled](https://supabase.com/docs/guides/database/debugging-performance#enabling-explain)
|
|
438
|
+
* setting before using this method.
|
|
439
|
+
*
|
|
440
|
+
* @param options - Named parameters
|
|
441
|
+
*
|
|
442
|
+
* @param options.analyze - If `true`, the query will be executed and the
|
|
443
|
+
* actual run time will be returned
|
|
444
|
+
*
|
|
445
|
+
* @param options.verbose - If `true`, the query identifier will be returned
|
|
446
|
+
* and `data` will include the output columns of the query
|
|
447
|
+
*
|
|
448
|
+
* @param options.settings - If `true`, include information on configuration
|
|
449
|
+
* parameters that affect query planning
|
|
450
|
+
*
|
|
451
|
+
* @param options.buffers - If `true`, include information on buffer usage
|
|
452
|
+
*
|
|
453
|
+
* @param options.wal - If `true`, include information on WAL record generation
|
|
454
|
+
*
|
|
455
|
+
* @param options.format - The format of the output, can be `"text"` (default)
|
|
456
|
+
* or `"json"`
|
|
457
|
+
*/
|
|
458
|
+
explain({ analyze = false, verbose = false, settings = false, buffers = false, wal = false, format = "text" } = {}) {
|
|
459
|
+
var _a;
|
|
460
|
+
const options = [
|
|
461
|
+
analyze ? "analyze" : null,
|
|
462
|
+
verbose ? "verbose" : null,
|
|
463
|
+
settings ? "settings" : null,
|
|
464
|
+
buffers ? "buffers" : null,
|
|
465
|
+
wal ? "wal" : null
|
|
466
|
+
].filter(Boolean).join("|");
|
|
467
|
+
const forMediatype = (_a = this.headers.get("Accept")) !== null && _a !== void 0 ? _a : "application/json";
|
|
468
|
+
this.headers.set("Accept", `application/vnd.pgrst.plan+${format}; for="${forMediatype}"; options=${options};`);
|
|
469
|
+
if (format === "json") {
|
|
470
|
+
return this;
|
|
471
|
+
} else {
|
|
472
|
+
return this;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Rollback the query.
|
|
477
|
+
*
|
|
478
|
+
* `data` will still be returned, but the query is not committed.
|
|
479
|
+
*/
|
|
480
|
+
rollback() {
|
|
481
|
+
this.headers.append("Prefer", "tx=rollback");
|
|
482
|
+
return this;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Override the type of the returned `data`.
|
|
486
|
+
*
|
|
487
|
+
* @typeParam NewResult - The new result type to override with
|
|
488
|
+
* @deprecated Use overrideTypes<yourType, { merge: false }>() method at the end of your call chain instead
|
|
489
|
+
*/
|
|
490
|
+
returns() {
|
|
491
|
+
return this;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Set the maximum number of rows that can be affected by the query.
|
|
495
|
+
* Only available in PostgREST v13+ and only works with PATCH and DELETE methods.
|
|
496
|
+
*
|
|
497
|
+
* @param value - The maximum number of rows that can be affected
|
|
498
|
+
*/
|
|
499
|
+
maxAffected(value) {
|
|
500
|
+
this.headers.append("Prefer", "handling=strict");
|
|
501
|
+
this.headers.append("Prefer", `max-affected=${value}`);
|
|
502
|
+
return this;
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
exports.default = PostgrestTransformBuilder2;
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// node_modules/@supabase/postgrest-js/dist/cjs/PostgrestFilterBuilder.js
|
|
510
|
+
var require_PostgrestFilterBuilder = __commonJS({
|
|
511
|
+
"node_modules/@supabase/postgrest-js/dist/cjs/PostgrestFilterBuilder.js"(exports) {
|
|
512
|
+
"use strict";
|
|
513
|
+
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
514
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
515
|
+
};
|
|
516
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
517
|
+
var PostgrestTransformBuilder_1 = __importDefault(require_PostgrestTransformBuilder());
|
|
518
|
+
var PostgrestFilterBuilder2 = class extends PostgrestTransformBuilder_1.default {
|
|
519
|
+
/**
|
|
520
|
+
* Match only rows where `column` is equal to `value`.
|
|
521
|
+
*
|
|
522
|
+
* To check if the value of `column` is NULL, you should use `.is()` instead.
|
|
523
|
+
*
|
|
524
|
+
* @param column - The column to filter on
|
|
525
|
+
* @param value - The value to filter with
|
|
526
|
+
*/
|
|
527
|
+
eq(column, value) {
|
|
528
|
+
this.url.searchParams.append(column, `eq.${value}`);
|
|
529
|
+
return this;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Match only rows where `column` is not equal to `value`.
|
|
533
|
+
*
|
|
534
|
+
* @param column - The column to filter on
|
|
535
|
+
* @param value - The value to filter with
|
|
536
|
+
*/
|
|
537
|
+
neq(column, value) {
|
|
538
|
+
this.url.searchParams.append(column, `neq.${value}`);
|
|
539
|
+
return this;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Match only rows where `column` is greater than `value`.
|
|
543
|
+
*
|
|
544
|
+
* @param column - The column to filter on
|
|
545
|
+
* @param value - The value to filter with
|
|
546
|
+
*/
|
|
547
|
+
gt(column, value) {
|
|
548
|
+
this.url.searchParams.append(column, `gt.${value}`);
|
|
549
|
+
return this;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Match only rows where `column` is greater than or equal to `value`.
|
|
553
|
+
*
|
|
554
|
+
* @param column - The column to filter on
|
|
555
|
+
* @param value - The value to filter with
|
|
556
|
+
*/
|
|
557
|
+
gte(column, value) {
|
|
558
|
+
this.url.searchParams.append(column, `gte.${value}`);
|
|
559
|
+
return this;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Match only rows where `column` is less than `value`.
|
|
563
|
+
*
|
|
564
|
+
* @param column - The column to filter on
|
|
565
|
+
* @param value - The value to filter with
|
|
566
|
+
*/
|
|
567
|
+
lt(column, value) {
|
|
568
|
+
this.url.searchParams.append(column, `lt.${value}`);
|
|
569
|
+
return this;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Match only rows where `column` is less than or equal to `value`.
|
|
573
|
+
*
|
|
574
|
+
* @param column - The column to filter on
|
|
575
|
+
* @param value - The value to filter with
|
|
576
|
+
*/
|
|
577
|
+
lte(column, value) {
|
|
578
|
+
this.url.searchParams.append(column, `lte.${value}`);
|
|
579
|
+
return this;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Match only rows where `column` matches `pattern` case-sensitively.
|
|
583
|
+
*
|
|
584
|
+
* @param column - The column to filter on
|
|
585
|
+
* @param pattern - The pattern to match with
|
|
586
|
+
*/
|
|
587
|
+
like(column, pattern) {
|
|
588
|
+
this.url.searchParams.append(column, `like.${pattern}`);
|
|
589
|
+
return this;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Match only rows where `column` matches all of `patterns` case-sensitively.
|
|
593
|
+
*
|
|
594
|
+
* @param column - The column to filter on
|
|
595
|
+
* @param patterns - The patterns to match with
|
|
596
|
+
*/
|
|
597
|
+
likeAllOf(column, patterns) {
|
|
598
|
+
this.url.searchParams.append(column, `like(all).{${patterns.join(",")}}`);
|
|
599
|
+
return this;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Match only rows where `column` matches any of `patterns` case-sensitively.
|
|
603
|
+
*
|
|
604
|
+
* @param column - The column to filter on
|
|
605
|
+
* @param patterns - The patterns to match with
|
|
606
|
+
*/
|
|
607
|
+
likeAnyOf(column, patterns) {
|
|
608
|
+
this.url.searchParams.append(column, `like(any).{${patterns.join(",")}}`);
|
|
609
|
+
return this;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Match only rows where `column` matches `pattern` case-insensitively.
|
|
613
|
+
*
|
|
614
|
+
* @param column - The column to filter on
|
|
615
|
+
* @param pattern - The pattern to match with
|
|
616
|
+
*/
|
|
617
|
+
ilike(column, pattern) {
|
|
618
|
+
this.url.searchParams.append(column, `ilike.${pattern}`);
|
|
619
|
+
return this;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Match only rows where `column` matches all of `patterns` case-insensitively.
|
|
623
|
+
*
|
|
624
|
+
* @param column - The column to filter on
|
|
625
|
+
* @param patterns - The patterns to match with
|
|
626
|
+
*/
|
|
627
|
+
ilikeAllOf(column, patterns) {
|
|
628
|
+
this.url.searchParams.append(column, `ilike(all).{${patterns.join(",")}}`);
|
|
629
|
+
return this;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Match only rows where `column` matches any of `patterns` case-insensitively.
|
|
633
|
+
*
|
|
634
|
+
* @param column - The column to filter on
|
|
635
|
+
* @param patterns - The patterns to match with
|
|
636
|
+
*/
|
|
637
|
+
ilikeAnyOf(column, patterns) {
|
|
638
|
+
this.url.searchParams.append(column, `ilike(any).{${patterns.join(",")}}`);
|
|
639
|
+
return this;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Match only rows where `column` IS `value`.
|
|
643
|
+
*
|
|
644
|
+
* For non-boolean columns, this is only relevant for checking if the value of
|
|
645
|
+
* `column` is NULL by setting `value` to `null`.
|
|
646
|
+
*
|
|
647
|
+
* For boolean columns, you can also set `value` to `true` or `false` and it
|
|
648
|
+
* will behave the same way as `.eq()`.
|
|
649
|
+
*
|
|
650
|
+
* @param column - The column to filter on
|
|
651
|
+
* @param value - The value to filter with
|
|
652
|
+
*/
|
|
653
|
+
is(column, value) {
|
|
654
|
+
this.url.searchParams.append(column, `is.${value}`);
|
|
655
|
+
return this;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Match only rows where `column` is included in the `values` array.
|
|
659
|
+
*
|
|
660
|
+
* @param column - The column to filter on
|
|
661
|
+
* @param values - The values array to filter with
|
|
662
|
+
*/
|
|
663
|
+
in(column, values) {
|
|
664
|
+
const cleanedValues = Array.from(new Set(values)).map((s) => {
|
|
665
|
+
if (typeof s === "string" && new RegExp("[,()]").test(s))
|
|
666
|
+
return `"${s}"`;
|
|
667
|
+
else
|
|
668
|
+
return `${s}`;
|
|
669
|
+
}).join(",");
|
|
670
|
+
this.url.searchParams.append(column, `in.(${cleanedValues})`);
|
|
671
|
+
return this;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Only relevant for jsonb, array, and range columns. Match only rows where
|
|
675
|
+
* `column` contains every element appearing in `value`.
|
|
676
|
+
*
|
|
677
|
+
* @param column - The jsonb, array, or range column to filter on
|
|
678
|
+
* @param value - The jsonb, array, or range value to filter with
|
|
679
|
+
*/
|
|
680
|
+
contains(column, value) {
|
|
681
|
+
if (typeof value === "string") {
|
|
682
|
+
this.url.searchParams.append(column, `cs.${value}`);
|
|
683
|
+
} else if (Array.isArray(value)) {
|
|
684
|
+
this.url.searchParams.append(column, `cs.{${value.join(",")}}`);
|
|
685
|
+
} else {
|
|
686
|
+
this.url.searchParams.append(column, `cs.${JSON.stringify(value)}`);
|
|
687
|
+
}
|
|
688
|
+
return this;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Only relevant for jsonb, array, and range columns. Match only rows where
|
|
692
|
+
* every element appearing in `column` is contained by `value`.
|
|
693
|
+
*
|
|
694
|
+
* @param column - The jsonb, array, or range column to filter on
|
|
695
|
+
* @param value - The jsonb, array, or range value to filter with
|
|
696
|
+
*/
|
|
697
|
+
containedBy(column, value) {
|
|
698
|
+
if (typeof value === "string") {
|
|
699
|
+
this.url.searchParams.append(column, `cd.${value}`);
|
|
700
|
+
} else if (Array.isArray(value)) {
|
|
701
|
+
this.url.searchParams.append(column, `cd.{${value.join(",")}}`);
|
|
702
|
+
} else {
|
|
703
|
+
this.url.searchParams.append(column, `cd.${JSON.stringify(value)}`);
|
|
704
|
+
}
|
|
705
|
+
return this;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Only relevant for range columns. Match only rows where every element in
|
|
709
|
+
* `column` is greater than any element in `range`.
|
|
710
|
+
*
|
|
711
|
+
* @param column - The range column to filter on
|
|
712
|
+
* @param range - The range to filter with
|
|
713
|
+
*/
|
|
714
|
+
rangeGt(column, range) {
|
|
715
|
+
this.url.searchParams.append(column, `sr.${range}`);
|
|
716
|
+
return this;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Only relevant for range columns. Match only rows where every element in
|
|
720
|
+
* `column` is either contained in `range` or greater than any element in
|
|
721
|
+
* `range`.
|
|
722
|
+
*
|
|
723
|
+
* @param column - The range column to filter on
|
|
724
|
+
* @param range - The range to filter with
|
|
725
|
+
*/
|
|
726
|
+
rangeGte(column, range) {
|
|
727
|
+
this.url.searchParams.append(column, `nxl.${range}`);
|
|
728
|
+
return this;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Only relevant for range columns. Match only rows where every element in
|
|
732
|
+
* `column` is less than any element in `range`.
|
|
733
|
+
*
|
|
734
|
+
* @param column - The range column to filter on
|
|
735
|
+
* @param range - The range to filter with
|
|
736
|
+
*/
|
|
737
|
+
rangeLt(column, range) {
|
|
738
|
+
this.url.searchParams.append(column, `sl.${range}`);
|
|
739
|
+
return this;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Only relevant for range columns. Match only rows where every element in
|
|
743
|
+
* `column` is either contained in `range` or less than any element in
|
|
744
|
+
* `range`.
|
|
745
|
+
*
|
|
746
|
+
* @param column - The range column to filter on
|
|
747
|
+
* @param range - The range to filter with
|
|
748
|
+
*/
|
|
749
|
+
rangeLte(column, range) {
|
|
750
|
+
this.url.searchParams.append(column, `nxr.${range}`);
|
|
751
|
+
return this;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Only relevant for range columns. Match only rows where `column` is
|
|
755
|
+
* mutually exclusive to `range` and there can be no element between the two
|
|
756
|
+
* ranges.
|
|
757
|
+
*
|
|
758
|
+
* @param column - The range column to filter on
|
|
759
|
+
* @param range - The range to filter with
|
|
760
|
+
*/
|
|
761
|
+
rangeAdjacent(column, range) {
|
|
762
|
+
this.url.searchParams.append(column, `adj.${range}`);
|
|
763
|
+
return this;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Only relevant for array and range columns. Match only rows where
|
|
767
|
+
* `column` and `value` have an element in common.
|
|
768
|
+
*
|
|
769
|
+
* @param column - The array or range column to filter on
|
|
770
|
+
* @param value - The array or range value to filter with
|
|
771
|
+
*/
|
|
772
|
+
overlaps(column, value) {
|
|
773
|
+
if (typeof value === "string") {
|
|
774
|
+
this.url.searchParams.append(column, `ov.${value}`);
|
|
775
|
+
} else {
|
|
776
|
+
this.url.searchParams.append(column, `ov.{${value.join(",")}}`);
|
|
777
|
+
}
|
|
778
|
+
return this;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Only relevant for text and tsvector columns. Match only rows where
|
|
782
|
+
* `column` matches the query string in `query`.
|
|
783
|
+
*
|
|
784
|
+
* @param column - The text or tsvector column to filter on
|
|
785
|
+
* @param query - The query text to match with
|
|
786
|
+
* @param options - Named parameters
|
|
787
|
+
* @param options.config - The text search configuration to use
|
|
788
|
+
* @param options.type - Change how the `query` text is interpreted
|
|
789
|
+
*/
|
|
790
|
+
textSearch(column, query, { config, type } = {}) {
|
|
791
|
+
let typePart = "";
|
|
792
|
+
if (type === "plain") {
|
|
793
|
+
typePart = "pl";
|
|
794
|
+
} else if (type === "phrase") {
|
|
795
|
+
typePart = "ph";
|
|
796
|
+
} else if (type === "websearch") {
|
|
797
|
+
typePart = "w";
|
|
798
|
+
}
|
|
799
|
+
const configPart = config === void 0 ? "" : `(${config})`;
|
|
800
|
+
this.url.searchParams.append(column, `${typePart}fts${configPart}.${query}`);
|
|
801
|
+
return this;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Match only rows where each column in `query` keys is equal to its
|
|
805
|
+
* associated value. Shorthand for multiple `.eq()`s.
|
|
806
|
+
*
|
|
807
|
+
* @param query - The object to filter with, with column names as keys mapped
|
|
808
|
+
* to their filter values
|
|
809
|
+
*/
|
|
810
|
+
match(query) {
|
|
811
|
+
Object.entries(query).forEach(([column, value]) => {
|
|
812
|
+
this.url.searchParams.append(column, `eq.${value}`);
|
|
813
|
+
});
|
|
814
|
+
return this;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Match only rows which doesn't satisfy the filter.
|
|
818
|
+
*
|
|
819
|
+
* Unlike most filters, `opearator` and `value` are used as-is and need to
|
|
820
|
+
* follow [PostgREST
|
|
821
|
+
* syntax](https://postgrest.org/en/stable/api.html#operators). You also need
|
|
822
|
+
* to make sure they are properly sanitized.
|
|
823
|
+
*
|
|
824
|
+
* @param column - The column to filter on
|
|
825
|
+
* @param operator - The operator to be negated to filter with, following
|
|
826
|
+
* PostgREST syntax
|
|
827
|
+
* @param value - The value to filter with, following PostgREST syntax
|
|
828
|
+
*/
|
|
829
|
+
not(column, operator, value) {
|
|
830
|
+
this.url.searchParams.append(column, `not.${operator}.${value}`);
|
|
831
|
+
return this;
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Match only rows which satisfy at least one of the filters.
|
|
835
|
+
*
|
|
836
|
+
* Unlike most filters, `filters` is used as-is and needs to follow [PostgREST
|
|
837
|
+
* syntax](https://postgrest.org/en/stable/api.html#operators). You also need
|
|
838
|
+
* to make sure it's properly sanitized.
|
|
839
|
+
*
|
|
840
|
+
* It's currently not possible to do an `.or()` filter across multiple tables.
|
|
841
|
+
*
|
|
842
|
+
* @param filters - The filters to use, following PostgREST syntax
|
|
843
|
+
* @param options - Named parameters
|
|
844
|
+
* @param options.referencedTable - Set this to filter on referenced tables
|
|
845
|
+
* instead of the parent table
|
|
846
|
+
* @param options.foreignTable - Deprecated, use `referencedTable` instead
|
|
847
|
+
*/
|
|
848
|
+
or(filters, { foreignTable, referencedTable = foreignTable } = {}) {
|
|
849
|
+
const key = referencedTable ? `${referencedTable}.or` : "or";
|
|
850
|
+
this.url.searchParams.append(key, `(${filters})`);
|
|
851
|
+
return this;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Match only rows which satisfy the filter. This is an escape hatch - you
|
|
855
|
+
* should use the specific filter methods wherever possible.
|
|
856
|
+
*
|
|
857
|
+
* Unlike most filters, `opearator` and `value` are used as-is and need to
|
|
858
|
+
* follow [PostgREST
|
|
859
|
+
* syntax](https://postgrest.org/en/stable/api.html#operators). You also need
|
|
860
|
+
* to make sure they are properly sanitized.
|
|
861
|
+
*
|
|
862
|
+
* @param column - The column to filter on
|
|
863
|
+
* @param operator - The operator to filter with, following PostgREST syntax
|
|
864
|
+
* @param value - The value to filter with, following PostgREST syntax
|
|
865
|
+
*/
|
|
866
|
+
filter(column, operator, value) {
|
|
867
|
+
this.url.searchParams.append(column, `${operator}.${value}`);
|
|
868
|
+
return this;
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
exports.default = PostgrestFilterBuilder2;
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
// node_modules/@supabase/postgrest-js/dist/cjs/PostgrestQueryBuilder.js
|
|
876
|
+
var require_PostgrestQueryBuilder = __commonJS({
|
|
877
|
+
"node_modules/@supabase/postgrest-js/dist/cjs/PostgrestQueryBuilder.js"(exports) {
|
|
878
|
+
"use strict";
|
|
879
|
+
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
880
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
881
|
+
};
|
|
882
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
883
|
+
var PostgrestFilterBuilder_1 = __importDefault(require_PostgrestFilterBuilder());
|
|
884
|
+
var PostgrestQueryBuilder2 = class {
|
|
885
|
+
constructor(url, { headers = {}, schema, fetch: fetch3 }) {
|
|
886
|
+
this.url = url;
|
|
887
|
+
this.headers = new Headers(headers);
|
|
888
|
+
this.schema = schema;
|
|
889
|
+
this.fetch = fetch3;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Perform a SELECT query on the table or view.
|
|
893
|
+
*
|
|
894
|
+
* @param columns - The columns to retrieve, separated by commas. Columns can be renamed when returned with `customName:columnName`
|
|
895
|
+
*
|
|
896
|
+
* @param options - Named parameters
|
|
897
|
+
*
|
|
898
|
+
* @param options.head - When set to `true`, `data` will not be returned.
|
|
899
|
+
* Useful if you only need the count.
|
|
900
|
+
*
|
|
901
|
+
* @param options.count - Count algorithm to use to count rows in the table or view.
|
|
902
|
+
*
|
|
903
|
+
* `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
|
|
904
|
+
* hood.
|
|
905
|
+
*
|
|
906
|
+
* `"planned"`: Approximated but fast count algorithm. Uses the Postgres
|
|
907
|
+
* statistics under the hood.
|
|
908
|
+
*
|
|
909
|
+
* `"estimated"`: Uses exact count for low numbers and planned count for high
|
|
910
|
+
* numbers.
|
|
911
|
+
*/
|
|
912
|
+
select(columns, { head = false, count } = {}) {
|
|
913
|
+
const method = head ? "HEAD" : "GET";
|
|
914
|
+
let quoted = false;
|
|
915
|
+
const cleanedColumns = (columns !== null && columns !== void 0 ? columns : "*").split("").map((c) => {
|
|
916
|
+
if (/\s/.test(c) && !quoted) {
|
|
917
|
+
return "";
|
|
918
|
+
}
|
|
919
|
+
if (c === '"') {
|
|
920
|
+
quoted = !quoted;
|
|
921
|
+
}
|
|
922
|
+
return c;
|
|
923
|
+
}).join("");
|
|
924
|
+
this.url.searchParams.set("select", cleanedColumns);
|
|
925
|
+
if (count) {
|
|
926
|
+
this.headers.append("Prefer", `count=${count}`);
|
|
927
|
+
}
|
|
928
|
+
return new PostgrestFilterBuilder_1.default({
|
|
929
|
+
method,
|
|
930
|
+
url: this.url,
|
|
931
|
+
headers: this.headers,
|
|
932
|
+
schema: this.schema,
|
|
933
|
+
fetch: this.fetch
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Perform an INSERT into the table or view.
|
|
938
|
+
*
|
|
939
|
+
* By default, inserted rows are not returned. To return it, chain the call
|
|
940
|
+
* with `.select()`.
|
|
941
|
+
*
|
|
942
|
+
* @param values - The values to insert. Pass an object to insert a single row
|
|
943
|
+
* or an array to insert multiple rows.
|
|
944
|
+
*
|
|
945
|
+
* @param options - Named parameters
|
|
946
|
+
*
|
|
947
|
+
* @param options.count - Count algorithm to use to count inserted rows.
|
|
948
|
+
*
|
|
949
|
+
* `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
|
|
950
|
+
* hood.
|
|
951
|
+
*
|
|
952
|
+
* `"planned"`: Approximated but fast count algorithm. Uses the Postgres
|
|
953
|
+
* statistics under the hood.
|
|
954
|
+
*
|
|
955
|
+
* `"estimated"`: Uses exact count for low numbers and planned count for high
|
|
956
|
+
* numbers.
|
|
957
|
+
*
|
|
958
|
+
* @param options.defaultToNull - Make missing fields default to `null`.
|
|
959
|
+
* Otherwise, use the default value for the column. Only applies for bulk
|
|
960
|
+
* inserts.
|
|
961
|
+
*/
|
|
962
|
+
insert(values, { count, defaultToNull = true } = {}) {
|
|
963
|
+
var _a;
|
|
964
|
+
const method = "POST";
|
|
965
|
+
if (count) {
|
|
966
|
+
this.headers.append("Prefer", `count=${count}`);
|
|
967
|
+
}
|
|
968
|
+
if (!defaultToNull) {
|
|
969
|
+
this.headers.append("Prefer", `missing=default`);
|
|
970
|
+
}
|
|
971
|
+
if (Array.isArray(values)) {
|
|
972
|
+
const columns = values.reduce((acc, x) => acc.concat(Object.keys(x)), []);
|
|
973
|
+
if (columns.length > 0) {
|
|
974
|
+
const uniqueColumns = [...new Set(columns)].map((column) => `"${column}"`);
|
|
975
|
+
this.url.searchParams.set("columns", uniqueColumns.join(","));
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return new PostgrestFilterBuilder_1.default({
|
|
979
|
+
method,
|
|
980
|
+
url: this.url,
|
|
981
|
+
headers: this.headers,
|
|
982
|
+
schema: this.schema,
|
|
983
|
+
body: values,
|
|
984
|
+
fetch: (_a = this.fetch) !== null && _a !== void 0 ? _a : fetch
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Perform an UPSERT on the table or view. Depending on the column(s) passed
|
|
989
|
+
* to `onConflict`, `.upsert()` allows you to perform the equivalent of
|
|
990
|
+
* `.insert()` if a row with the corresponding `onConflict` columns doesn't
|
|
991
|
+
* exist, or if it does exist, perform an alternative action depending on
|
|
992
|
+
* `ignoreDuplicates`.
|
|
993
|
+
*
|
|
994
|
+
* By default, upserted rows are not returned. To return it, chain the call
|
|
995
|
+
* with `.select()`.
|
|
996
|
+
*
|
|
997
|
+
* @param values - The values to upsert with. Pass an object to upsert a
|
|
998
|
+
* single row or an array to upsert multiple rows.
|
|
999
|
+
*
|
|
1000
|
+
* @param options - Named parameters
|
|
1001
|
+
*
|
|
1002
|
+
* @param options.onConflict - Comma-separated UNIQUE column(s) to specify how
|
|
1003
|
+
* duplicate rows are determined. Two rows are duplicates if all the
|
|
1004
|
+
* `onConflict` columns are equal.
|
|
1005
|
+
*
|
|
1006
|
+
* @param options.ignoreDuplicates - If `true`, duplicate rows are ignored. If
|
|
1007
|
+
* `false`, duplicate rows are merged with existing rows.
|
|
1008
|
+
*
|
|
1009
|
+
* @param options.count - Count algorithm to use to count upserted rows.
|
|
1010
|
+
*
|
|
1011
|
+
* `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
|
|
1012
|
+
* hood.
|
|
1013
|
+
*
|
|
1014
|
+
* `"planned"`: Approximated but fast count algorithm. Uses the Postgres
|
|
1015
|
+
* statistics under the hood.
|
|
1016
|
+
*
|
|
1017
|
+
* `"estimated"`: Uses exact count for low numbers and planned count for high
|
|
1018
|
+
* numbers.
|
|
1019
|
+
*
|
|
1020
|
+
* @param options.defaultToNull - Make missing fields default to `null`.
|
|
1021
|
+
* Otherwise, use the default value for the column. This only applies when
|
|
1022
|
+
* inserting new rows, not when merging with existing rows under
|
|
1023
|
+
* `ignoreDuplicates: false`. This also only applies when doing bulk upserts.
|
|
1024
|
+
*/
|
|
1025
|
+
upsert(values, { onConflict, ignoreDuplicates = false, count, defaultToNull = true } = {}) {
|
|
1026
|
+
var _a;
|
|
1027
|
+
const method = "POST";
|
|
1028
|
+
this.headers.append("Prefer", `resolution=${ignoreDuplicates ? "ignore" : "merge"}-duplicates`);
|
|
1029
|
+
if (onConflict !== void 0)
|
|
1030
|
+
this.url.searchParams.set("on_conflict", onConflict);
|
|
1031
|
+
if (count) {
|
|
1032
|
+
this.headers.append("Prefer", `count=${count}`);
|
|
1033
|
+
}
|
|
1034
|
+
if (!defaultToNull) {
|
|
1035
|
+
this.headers.append("Prefer", "missing=default");
|
|
1036
|
+
}
|
|
1037
|
+
if (Array.isArray(values)) {
|
|
1038
|
+
const columns = values.reduce((acc, x) => acc.concat(Object.keys(x)), []);
|
|
1039
|
+
if (columns.length > 0) {
|
|
1040
|
+
const uniqueColumns = [...new Set(columns)].map((column) => `"${column}"`);
|
|
1041
|
+
this.url.searchParams.set("columns", uniqueColumns.join(","));
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return new PostgrestFilterBuilder_1.default({
|
|
1045
|
+
method,
|
|
1046
|
+
url: this.url,
|
|
1047
|
+
headers: this.headers,
|
|
1048
|
+
schema: this.schema,
|
|
1049
|
+
body: values,
|
|
1050
|
+
fetch: (_a = this.fetch) !== null && _a !== void 0 ? _a : fetch
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Perform an UPDATE on the table or view.
|
|
1055
|
+
*
|
|
1056
|
+
* By default, updated rows are not returned. To return it, chain the call
|
|
1057
|
+
* with `.select()` after filters.
|
|
1058
|
+
*
|
|
1059
|
+
* @param values - The values to update with
|
|
1060
|
+
*
|
|
1061
|
+
* @param options - Named parameters
|
|
1062
|
+
*
|
|
1063
|
+
* @param options.count - Count algorithm to use to count updated rows.
|
|
1064
|
+
*
|
|
1065
|
+
* `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
|
|
1066
|
+
* hood.
|
|
1067
|
+
*
|
|
1068
|
+
* `"planned"`: Approximated but fast count algorithm. Uses the Postgres
|
|
1069
|
+
* statistics under the hood.
|
|
1070
|
+
*
|
|
1071
|
+
* `"estimated"`: Uses exact count for low numbers and planned count for high
|
|
1072
|
+
* numbers.
|
|
1073
|
+
*/
|
|
1074
|
+
update(values, { count } = {}) {
|
|
1075
|
+
var _a;
|
|
1076
|
+
const method = "PATCH";
|
|
1077
|
+
if (count) {
|
|
1078
|
+
this.headers.append("Prefer", `count=${count}`);
|
|
1079
|
+
}
|
|
1080
|
+
return new PostgrestFilterBuilder_1.default({
|
|
1081
|
+
method,
|
|
1082
|
+
url: this.url,
|
|
1083
|
+
headers: this.headers,
|
|
1084
|
+
schema: this.schema,
|
|
1085
|
+
body: values,
|
|
1086
|
+
fetch: (_a = this.fetch) !== null && _a !== void 0 ? _a : fetch
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Perform a DELETE on the table or view.
|
|
1091
|
+
*
|
|
1092
|
+
* By default, deleted rows are not returned. To return it, chain the call
|
|
1093
|
+
* with `.select()` after filters.
|
|
1094
|
+
*
|
|
1095
|
+
* @param options - Named parameters
|
|
1096
|
+
*
|
|
1097
|
+
* @param options.count - Count algorithm to use to count deleted rows.
|
|
1098
|
+
*
|
|
1099
|
+
* `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
|
|
1100
|
+
* hood.
|
|
1101
|
+
*
|
|
1102
|
+
* `"planned"`: Approximated but fast count algorithm. Uses the Postgres
|
|
1103
|
+
* statistics under the hood.
|
|
1104
|
+
*
|
|
1105
|
+
* `"estimated"`: Uses exact count for low numbers and planned count for high
|
|
1106
|
+
* numbers.
|
|
1107
|
+
*/
|
|
1108
|
+
delete({ count } = {}) {
|
|
1109
|
+
var _a;
|
|
1110
|
+
const method = "DELETE";
|
|
1111
|
+
if (count) {
|
|
1112
|
+
this.headers.append("Prefer", `count=${count}`);
|
|
1113
|
+
}
|
|
1114
|
+
return new PostgrestFilterBuilder_1.default({
|
|
1115
|
+
method,
|
|
1116
|
+
url: this.url,
|
|
1117
|
+
headers: this.headers,
|
|
1118
|
+
schema: this.schema,
|
|
1119
|
+
fetch: (_a = this.fetch) !== null && _a !== void 0 ? _a : fetch
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
exports.default = PostgrestQueryBuilder2;
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
// node_modules/@supabase/postgrest-js/dist/cjs/PostgrestClient.js
|
|
1128
|
+
var require_PostgrestClient = __commonJS({
|
|
1129
|
+
"node_modules/@supabase/postgrest-js/dist/cjs/PostgrestClient.js"(exports) {
|
|
1130
|
+
"use strict";
|
|
1131
|
+
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
1132
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
1133
|
+
};
|
|
1134
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1135
|
+
var PostgrestQueryBuilder_1 = __importDefault(require_PostgrestQueryBuilder());
|
|
1136
|
+
var PostgrestFilterBuilder_1 = __importDefault(require_PostgrestFilterBuilder());
|
|
1137
|
+
var PostgrestClient2 = class _PostgrestClient {
|
|
1138
|
+
// TODO: Add back shouldThrowOnError once we figure out the typings
|
|
1139
|
+
/**
|
|
1140
|
+
* Creates a PostgREST client.
|
|
1141
|
+
*
|
|
1142
|
+
* @param url - URL of the PostgREST endpoint
|
|
1143
|
+
* @param options - Named parameters
|
|
1144
|
+
* @param options.headers - Custom headers
|
|
1145
|
+
* @param options.schema - Postgres schema to switch to
|
|
1146
|
+
* @param options.fetch - Custom fetch
|
|
1147
|
+
*/
|
|
1148
|
+
constructor(url, { headers = {}, schema, fetch: fetch3 } = {}) {
|
|
1149
|
+
this.url = url;
|
|
1150
|
+
this.headers = new Headers(headers);
|
|
1151
|
+
this.schemaName = schema;
|
|
1152
|
+
this.fetch = fetch3;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Perform a query on a table or a view.
|
|
1156
|
+
*
|
|
1157
|
+
* @param relation - The table or view name to query
|
|
1158
|
+
*/
|
|
1159
|
+
from(relation) {
|
|
1160
|
+
const url = new URL(`${this.url}/${relation}`);
|
|
1161
|
+
return new PostgrestQueryBuilder_1.default(url, {
|
|
1162
|
+
headers: new Headers(this.headers),
|
|
1163
|
+
schema: this.schemaName,
|
|
1164
|
+
fetch: this.fetch
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Select a schema to query or perform an function (rpc) call.
|
|
1169
|
+
*
|
|
1170
|
+
* The schema needs to be on the list of exposed schemas inside Supabase.
|
|
1171
|
+
*
|
|
1172
|
+
* @param schema - The schema to query
|
|
1173
|
+
*/
|
|
1174
|
+
schema(schema) {
|
|
1175
|
+
return new _PostgrestClient(this.url, {
|
|
1176
|
+
headers: this.headers,
|
|
1177
|
+
schema,
|
|
1178
|
+
fetch: this.fetch
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Perform a function call.
|
|
1183
|
+
*
|
|
1184
|
+
* @param fn - The function name to call
|
|
1185
|
+
* @param args - The arguments to pass to the function call
|
|
1186
|
+
* @param options - Named parameters
|
|
1187
|
+
* @param options.head - When set to `true`, `data` will not be returned.
|
|
1188
|
+
* Useful if you only need the count.
|
|
1189
|
+
* @param options.get - When set to `true`, the function will be called with
|
|
1190
|
+
* read-only access mode.
|
|
1191
|
+
* @param options.count - Count algorithm to use to count rows returned by the
|
|
1192
|
+
* function. Only applicable for [set-returning
|
|
1193
|
+
* functions](https://www.postgresql.org/docs/current/functions-srf.html).
|
|
1194
|
+
*
|
|
1195
|
+
* `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
|
|
1196
|
+
* hood.
|
|
1197
|
+
*
|
|
1198
|
+
* `"planned"`: Approximated but fast count algorithm. Uses the Postgres
|
|
1199
|
+
* statistics under the hood.
|
|
1200
|
+
*
|
|
1201
|
+
* `"estimated"`: Uses exact count for low numbers and planned count for high
|
|
1202
|
+
* numbers.
|
|
1203
|
+
*/
|
|
1204
|
+
rpc(fn, args = {}, { head = false, get = false, count } = {}) {
|
|
1205
|
+
var _a;
|
|
1206
|
+
let method;
|
|
1207
|
+
const url = new URL(`${this.url}/rpc/${fn}`);
|
|
1208
|
+
let body;
|
|
1209
|
+
if (head || get) {
|
|
1210
|
+
method = head ? "HEAD" : "GET";
|
|
1211
|
+
Object.entries(args).filter(([_, value]) => value !== void 0).map(([name, value]) => [name, Array.isArray(value) ? `{${value.join(",")}}` : `${value}`]).forEach(([name, value]) => {
|
|
1212
|
+
url.searchParams.append(name, value);
|
|
1213
|
+
});
|
|
1214
|
+
} else {
|
|
1215
|
+
method = "POST";
|
|
1216
|
+
body = args;
|
|
1217
|
+
}
|
|
1218
|
+
const headers = new Headers(this.headers);
|
|
1219
|
+
if (count) {
|
|
1220
|
+
headers.set("Prefer", `count=${count}`);
|
|
1221
|
+
}
|
|
1222
|
+
return new PostgrestFilterBuilder_1.default({
|
|
1223
|
+
method,
|
|
1224
|
+
url,
|
|
1225
|
+
headers,
|
|
1226
|
+
schema: this.schemaName,
|
|
1227
|
+
body,
|
|
1228
|
+
fetch: (_a = this.fetch) !== null && _a !== void 0 ? _a : fetch
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
exports.default = PostgrestClient2;
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
// node_modules/@supabase/postgrest-js/dist/cjs/index.js
|
|
1237
|
+
var require_cjs = __commonJS({
|
|
1238
|
+
"node_modules/@supabase/postgrest-js/dist/cjs/index.js"(exports) {
|
|
1239
|
+
"use strict";
|
|
1240
|
+
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
1241
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
1242
|
+
};
|
|
1243
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1244
|
+
exports.PostgrestError = exports.PostgrestBuilder = exports.PostgrestTransformBuilder = exports.PostgrestFilterBuilder = exports.PostgrestQueryBuilder = exports.PostgrestClient = void 0;
|
|
1245
|
+
var PostgrestClient_1 = __importDefault(require_PostgrestClient());
|
|
1246
|
+
exports.PostgrestClient = PostgrestClient_1.default;
|
|
1247
|
+
var PostgrestQueryBuilder_1 = __importDefault(require_PostgrestQueryBuilder());
|
|
1248
|
+
exports.PostgrestQueryBuilder = PostgrestQueryBuilder_1.default;
|
|
1249
|
+
var PostgrestFilterBuilder_1 = __importDefault(require_PostgrestFilterBuilder());
|
|
1250
|
+
exports.PostgrestFilterBuilder = PostgrestFilterBuilder_1.default;
|
|
1251
|
+
var PostgrestTransformBuilder_1 = __importDefault(require_PostgrestTransformBuilder());
|
|
1252
|
+
exports.PostgrestTransformBuilder = PostgrestTransformBuilder_1.default;
|
|
1253
|
+
var PostgrestBuilder_1 = __importDefault(require_PostgrestBuilder());
|
|
1254
|
+
exports.PostgrestBuilder = PostgrestBuilder_1.default;
|
|
1255
|
+
var PostgrestError_1 = __importDefault(require_PostgrestError());
|
|
1256
|
+
exports.PostgrestError = PostgrestError_1.default;
|
|
1257
|
+
exports.default = {
|
|
1258
|
+
PostgrestClient: PostgrestClient_1.default,
|
|
1259
|
+
PostgrestQueryBuilder: PostgrestQueryBuilder_1.default,
|
|
1260
|
+
PostgrestFilterBuilder: PostgrestFilterBuilder_1.default,
|
|
1261
|
+
PostgrestTransformBuilder: PostgrestTransformBuilder_1.default,
|
|
1262
|
+
PostgrestBuilder: PostgrestBuilder_1.default,
|
|
1263
|
+
PostgrestError: PostgrestError_1.default
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
// src/types.ts
|
|
1269
|
+
var InsForgeError = class _InsForgeError extends Error {
|
|
1270
|
+
constructor(message, statusCode, error, nextActions) {
|
|
1271
|
+
super(message);
|
|
1272
|
+
this.name = "InsForgeError";
|
|
1273
|
+
this.statusCode = statusCode;
|
|
1274
|
+
this.error = error;
|
|
1275
|
+
this.nextActions = nextActions;
|
|
1276
|
+
}
|
|
1277
|
+
static fromApiError(apiError) {
|
|
1278
|
+
return new _InsForgeError(
|
|
1279
|
+
apiError.message,
|
|
1280
|
+
apiError.statusCode,
|
|
1281
|
+
apiError.error,
|
|
1282
|
+
apiError.nextActions
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
// src/lib/http-client.ts
|
|
1288
|
+
var HttpClient = class {
|
|
1289
|
+
constructor(config) {
|
|
1290
|
+
this.userToken = null;
|
|
1291
|
+
this.baseUrl = config.baseUrl || "http://localhost:7130";
|
|
1292
|
+
this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
1293
|
+
this.anonKey = config.anonKey;
|
|
1294
|
+
this.defaultHeaders = {
|
|
1295
|
+
...config.headers
|
|
1296
|
+
};
|
|
1297
|
+
if (!this.fetch) {
|
|
1298
|
+
throw new Error(
|
|
1299
|
+
"Fetch is not available. Please provide a fetch implementation in the config."
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
buildUrl(path, params) {
|
|
1304
|
+
const url = new URL(path, this.baseUrl);
|
|
1305
|
+
if (params) {
|
|
1306
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
1307
|
+
if (key === "select") {
|
|
1308
|
+
let normalizedValue = value.replace(/\s+/g, " ").trim();
|
|
1309
|
+
normalizedValue = normalizedValue.replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\(\s+/g, "(").replace(/\s+\)/g, ")").replace(/,\s+(?=[^()]*\))/g, ",");
|
|
1310
|
+
url.searchParams.append(key, normalizedValue);
|
|
1311
|
+
} else {
|
|
1312
|
+
url.searchParams.append(key, value);
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
return url.toString();
|
|
1317
|
+
}
|
|
1318
|
+
async request(method, path, options = {}) {
|
|
1319
|
+
const { params, headers = {}, body, ...fetchOptions } = options;
|
|
1320
|
+
const url = this.buildUrl(path, params);
|
|
1321
|
+
const requestHeaders = {
|
|
1322
|
+
...this.defaultHeaders
|
|
1323
|
+
};
|
|
1324
|
+
const authToken = this.userToken || this.anonKey;
|
|
1325
|
+
if (authToken) {
|
|
1326
|
+
requestHeaders["Authorization"] = `Bearer ${authToken}`;
|
|
1327
|
+
}
|
|
1328
|
+
let processedBody;
|
|
1329
|
+
if (body !== void 0) {
|
|
1330
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
1331
|
+
processedBody = body;
|
|
1332
|
+
} else {
|
|
1333
|
+
if (method !== "GET") {
|
|
1334
|
+
requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
|
|
1335
|
+
}
|
|
1336
|
+
processedBody = JSON.stringify(body);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
Object.assign(requestHeaders, headers);
|
|
1340
|
+
const response = await this.fetch(url, {
|
|
1341
|
+
method,
|
|
1342
|
+
headers: requestHeaders,
|
|
1343
|
+
body: processedBody,
|
|
1344
|
+
...fetchOptions
|
|
1345
|
+
});
|
|
1346
|
+
if (response.status === 204) {
|
|
1347
|
+
return void 0;
|
|
1348
|
+
}
|
|
1349
|
+
let data;
|
|
1350
|
+
const contentType = response.headers.get("content-type");
|
|
1351
|
+
if (contentType?.includes("json")) {
|
|
1352
|
+
data = await response.json();
|
|
1353
|
+
} else {
|
|
1354
|
+
data = await response.text();
|
|
1355
|
+
}
|
|
1356
|
+
if (!response.ok) {
|
|
1357
|
+
if (data && typeof data === "object" && "error" in data) {
|
|
1358
|
+
if (!data.statusCode && !data.status) {
|
|
1359
|
+
data.statusCode = response.status;
|
|
1360
|
+
}
|
|
1361
|
+
const error = InsForgeError.fromApiError(data);
|
|
1362
|
+
Object.keys(data).forEach((key) => {
|
|
1363
|
+
if (key !== "error" && key !== "message" && key !== "statusCode") {
|
|
1364
|
+
error[key] = data[key];
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
throw error;
|
|
1368
|
+
}
|
|
1369
|
+
throw new InsForgeError(
|
|
1370
|
+
`Request failed: ${response.statusText}`,
|
|
1371
|
+
response.status,
|
|
1372
|
+
"REQUEST_FAILED"
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
return data;
|
|
1376
|
+
}
|
|
1377
|
+
get(path, options) {
|
|
1378
|
+
return this.request("GET", path, options);
|
|
1379
|
+
}
|
|
1380
|
+
post(path, body, options) {
|
|
1381
|
+
return this.request("POST", path, { ...options, body });
|
|
1382
|
+
}
|
|
1383
|
+
put(path, body, options) {
|
|
1384
|
+
return this.request("PUT", path, { ...options, body });
|
|
1385
|
+
}
|
|
1386
|
+
patch(path, body, options) {
|
|
1387
|
+
return this.request("PATCH", path, { ...options, body });
|
|
1388
|
+
}
|
|
1389
|
+
delete(path, options) {
|
|
1390
|
+
return this.request("DELETE", path, options);
|
|
1391
|
+
}
|
|
1392
|
+
setAuthToken(token) {
|
|
1393
|
+
this.userToken = token;
|
|
1394
|
+
}
|
|
1395
|
+
getHeaders() {
|
|
1396
|
+
const headers = { ...this.defaultHeaders };
|
|
1397
|
+
const authToken = this.userToken || this.anonKey;
|
|
1398
|
+
if (authToken) {
|
|
1399
|
+
headers["Authorization"] = `Bearer ${authToken}`;
|
|
1400
|
+
}
|
|
1401
|
+
return headers;
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
// src/lib/token-manager.ts
|
|
1406
|
+
var TOKEN_KEY = "insforge-auth-token";
|
|
1407
|
+
var USER_KEY = "insforge-auth-user";
|
|
1408
|
+
var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
|
|
1409
|
+
function getCsrfToken() {
|
|
1410
|
+
if (typeof document === "undefined") return null;
|
|
1411
|
+
const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
|
|
1412
|
+
if (!match) return null;
|
|
1413
|
+
return match.split("=")[1] || null;
|
|
1414
|
+
}
|
|
1415
|
+
function setCsrfToken(token) {
|
|
1416
|
+
if (typeof document === "undefined") return;
|
|
1417
|
+
const maxAge = 7 * 24 * 60 * 60;
|
|
1418
|
+
const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
|
|
1419
|
+
document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
|
|
1420
|
+
}
|
|
1421
|
+
function clearCsrfToken() {
|
|
1422
|
+
if (typeof document === "undefined") return;
|
|
1423
|
+
const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
|
|
1424
|
+
document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
|
|
1425
|
+
}
|
|
1426
|
+
var TokenManager = class {
|
|
1427
|
+
constructor(storage) {
|
|
1428
|
+
// In-memory storage
|
|
1429
|
+
this.accessToken = null;
|
|
1430
|
+
this.user = null;
|
|
1431
|
+
// Mode: 'memory' (new backend) or 'storage' (legacy backend, default)
|
|
1432
|
+
this._mode = "storage";
|
|
1433
|
+
if (storage) {
|
|
1434
|
+
this.storage = storage;
|
|
1435
|
+
} else if (typeof window !== "undefined" && window.localStorage) {
|
|
1436
|
+
this.storage = window.localStorage;
|
|
1437
|
+
} else {
|
|
1438
|
+
const store = /* @__PURE__ */ new Map();
|
|
1439
|
+
this.storage = {
|
|
1440
|
+
getItem: (key) => store.get(key) || null,
|
|
1441
|
+
setItem: (key, value) => {
|
|
1442
|
+
store.set(key, value);
|
|
1443
|
+
},
|
|
1444
|
+
removeItem: (key) => {
|
|
1445
|
+
store.delete(key);
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Get current mode
|
|
1452
|
+
*/
|
|
1453
|
+
get mode() {
|
|
1454
|
+
return this._mode;
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Set mode to memory (new backend with cookies + memory)
|
|
1458
|
+
*/
|
|
1459
|
+
setMemoryMode() {
|
|
1460
|
+
if (this._mode === "storage") {
|
|
1461
|
+
this.storage.removeItem(TOKEN_KEY);
|
|
1462
|
+
this.storage.removeItem(USER_KEY);
|
|
1463
|
+
}
|
|
1464
|
+
this._mode = "memory";
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Set mode to storage (legacy backend with localStorage)
|
|
1468
|
+
* Also loads existing session from localStorage
|
|
1469
|
+
*/
|
|
1470
|
+
setStorageMode() {
|
|
1471
|
+
this._mode = "storage";
|
|
1472
|
+
this.loadFromStorage();
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Load session from localStorage
|
|
1476
|
+
*/
|
|
1477
|
+
loadFromStorage() {
|
|
1478
|
+
const token = this.storage.getItem(TOKEN_KEY);
|
|
1479
|
+
const userStr = this.storage.getItem(USER_KEY);
|
|
1480
|
+
if (token && userStr) {
|
|
1481
|
+
try {
|
|
1482
|
+
this.accessToken = token;
|
|
1483
|
+
this.user = JSON.parse(userStr);
|
|
1484
|
+
} catch {
|
|
1485
|
+
this.clearSession();
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Save session (memory always, localStorage only in storage mode)
|
|
1491
|
+
*/
|
|
1492
|
+
saveSession(session) {
|
|
1493
|
+
this.accessToken = session.accessToken;
|
|
1494
|
+
this.user = session.user;
|
|
1495
|
+
if (this._mode === "storage") {
|
|
1496
|
+
this.storage.setItem(TOKEN_KEY, session.accessToken);
|
|
1497
|
+
this.storage.setItem(USER_KEY, JSON.stringify(session.user));
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Get current session
|
|
1502
|
+
*/
|
|
1503
|
+
getSession() {
|
|
1504
|
+
this.loadFromStorage();
|
|
1505
|
+
if (!this.accessToken || !this.user) return null;
|
|
1506
|
+
return {
|
|
1507
|
+
accessToken: this.accessToken,
|
|
1508
|
+
user: this.user
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Get access token
|
|
1513
|
+
*/
|
|
1514
|
+
getAccessToken() {
|
|
1515
|
+
this.loadFromStorage();
|
|
1516
|
+
return this.accessToken;
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Set access token
|
|
1520
|
+
*/
|
|
1521
|
+
setAccessToken(token) {
|
|
1522
|
+
this.accessToken = token;
|
|
1523
|
+
if (this._mode === "storage") {
|
|
1524
|
+
this.storage.setItem(TOKEN_KEY, token);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Get user
|
|
1529
|
+
*/
|
|
1530
|
+
getUser() {
|
|
1531
|
+
return this.user;
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Set user
|
|
1535
|
+
*/
|
|
1536
|
+
setUser(user) {
|
|
1537
|
+
this.user = user;
|
|
1538
|
+
if (this._mode === "storage") {
|
|
1539
|
+
this.storage.setItem(USER_KEY, JSON.stringify(user));
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Clear session (both memory and localStorage)
|
|
1544
|
+
*/
|
|
1545
|
+
clearSession() {
|
|
1546
|
+
this.accessToken = null;
|
|
1547
|
+
this.user = null;
|
|
1548
|
+
this.storage.removeItem(TOKEN_KEY);
|
|
1549
|
+
this.storage.removeItem(USER_KEY);
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Check if there's a session in localStorage (for legacy detection)
|
|
1553
|
+
*/
|
|
1554
|
+
hasStoredSession() {
|
|
1555
|
+
const token = this.storage.getItem(TOKEN_KEY);
|
|
1556
|
+
return !!token;
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
|
|
1560
|
+
// node_modules/@supabase/postgrest-js/dist/esm/wrapper.mjs
|
|
1561
|
+
var import_cjs = __toESM(require_cjs(), 1);
|
|
1562
|
+
var {
|
|
1563
|
+
PostgrestClient,
|
|
1564
|
+
PostgrestQueryBuilder,
|
|
1565
|
+
PostgrestFilterBuilder,
|
|
1566
|
+
PostgrestTransformBuilder,
|
|
1567
|
+
PostgrestBuilder,
|
|
1568
|
+
PostgrestError
|
|
1569
|
+
} = import_cjs.default;
|
|
1570
|
+
|
|
1571
|
+
// src/modules/database-postgrest.ts
|
|
1572
|
+
function createInsForgePostgrestFetch(httpClient, tokenManager) {
|
|
1573
|
+
return async (input, init) => {
|
|
1574
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
1575
|
+
const urlObj = new URL(url);
|
|
1576
|
+
const tableName = urlObj.pathname.slice(1);
|
|
1577
|
+
const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
|
|
1578
|
+
const token = tokenManager.getAccessToken();
|
|
1579
|
+
const httpHeaders = httpClient.getHeaders();
|
|
1580
|
+
const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
|
|
1581
|
+
const headers = new Headers(init?.headers);
|
|
1582
|
+
if (authToken && !headers.has("Authorization")) {
|
|
1583
|
+
headers.set("Authorization", `Bearer ${authToken}`);
|
|
1584
|
+
}
|
|
1585
|
+
const response = await fetch(insforgeUrl, {
|
|
1586
|
+
...init,
|
|
1587
|
+
headers
|
|
1588
|
+
});
|
|
1589
|
+
return response;
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
var Database = class {
|
|
1593
|
+
constructor(httpClient, tokenManager) {
|
|
1594
|
+
this.postgrest = new PostgrestClient("http://dummy", {
|
|
1595
|
+
fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
|
|
1596
|
+
headers: {}
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Create a query builder for a table
|
|
1601
|
+
*
|
|
1602
|
+
* @example
|
|
1603
|
+
* // Basic query
|
|
1604
|
+
* const { data, error } = await client.database
|
|
1605
|
+
* .from('posts')
|
|
1606
|
+
* .select('*')
|
|
1607
|
+
* .eq('user_id', userId);
|
|
1608
|
+
*
|
|
1609
|
+
* // With count (Supabase style!)
|
|
1610
|
+
* const { data, error, count } = await client.database
|
|
1611
|
+
* .from('posts')
|
|
1612
|
+
* .select('*', { count: 'exact' })
|
|
1613
|
+
* .range(0, 9);
|
|
1614
|
+
*
|
|
1615
|
+
* // Just get count, no data
|
|
1616
|
+
* const { count } = await client.database
|
|
1617
|
+
* .from('posts')
|
|
1618
|
+
* .select('*', { count: 'exact', head: true });
|
|
1619
|
+
*
|
|
1620
|
+
* // Complex queries with OR
|
|
1621
|
+
* const { data } = await client.database
|
|
1622
|
+
* .from('posts')
|
|
1623
|
+
* .select('*, users!inner(*)')
|
|
1624
|
+
* .or('status.eq.active,status.eq.pending');
|
|
1625
|
+
*
|
|
1626
|
+
* // All features work:
|
|
1627
|
+
* - Nested selects
|
|
1628
|
+
* - Foreign key expansion
|
|
1629
|
+
* - OR/AND/NOT conditions
|
|
1630
|
+
* - Count with head
|
|
1631
|
+
* - Range pagination
|
|
1632
|
+
* - Upserts
|
|
1633
|
+
*/
|
|
1634
|
+
from(table) {
|
|
1635
|
+
return this.postgrest.from(table);
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
|
|
1639
|
+
// src/modules/auth.ts
|
|
1640
|
+
function convertDbProfileToCamelCase(dbProfile) {
|
|
1641
|
+
const result = {
|
|
1642
|
+
id: dbProfile.id
|
|
1643
|
+
};
|
|
1644
|
+
Object.keys(dbProfile).forEach((key) => {
|
|
1645
|
+
result[key] = dbProfile[key];
|
|
1646
|
+
if (key.includes("_")) {
|
|
1647
|
+
const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1648
|
+
result[camelKey] = dbProfile[key];
|
|
1649
|
+
}
|
|
1650
|
+
});
|
|
1651
|
+
return result;
|
|
1652
|
+
}
|
|
1653
|
+
function convertCamelCaseToDbProfile(profile) {
|
|
1654
|
+
const dbProfile = {};
|
|
1655
|
+
Object.keys(profile).forEach((key) => {
|
|
1656
|
+
if (profile[key] === void 0) return;
|
|
1657
|
+
const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
1658
|
+
dbProfile[snakeKey] = profile[key];
|
|
1659
|
+
});
|
|
1660
|
+
return dbProfile;
|
|
1661
|
+
}
|
|
1662
|
+
function isHostedAuthEnvironment() {
|
|
1663
|
+
if (typeof window === "undefined") {
|
|
1664
|
+
return false;
|
|
1665
|
+
}
|
|
1666
|
+
const { hostname, port, protocol } = window.location;
|
|
1667
|
+
if (hostname === "localhost" && port === "7130") {
|
|
1668
|
+
return true;
|
|
1669
|
+
}
|
|
1670
|
+
if (protocol === "https:" && hostname.endsWith(".insforge.app")) {
|
|
1671
|
+
return true;
|
|
1672
|
+
}
|
|
1673
|
+
return false;
|
|
1674
|
+
}
|
|
1675
|
+
var Auth = class {
|
|
1676
|
+
constructor(http, tokenManager) {
|
|
1677
|
+
this.http = http;
|
|
1678
|
+
this.tokenManager = tokenManager;
|
|
1679
|
+
this.database = new Database(http, tokenManager);
|
|
1680
|
+
this.detectAuthCallback();
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Automatically detect and handle OAuth callback parameters in the URL
|
|
1684
|
+
* This runs on initialization to seamlessly complete the OAuth flow
|
|
1685
|
+
* Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
|
|
1686
|
+
*/
|
|
1687
|
+
detectAuthCallback() {
|
|
1688
|
+
if (typeof window === "undefined") return;
|
|
1689
|
+
try {
|
|
1690
|
+
const params = new URLSearchParams(window.location.search);
|
|
1691
|
+
const accessToken = params.get("access_token");
|
|
1692
|
+
const userId = params.get("user_id");
|
|
1693
|
+
const email = params.get("email");
|
|
1694
|
+
const name = params.get("name");
|
|
1695
|
+
const csrfToken = params.get("csrf_token");
|
|
1696
|
+
if (accessToken && userId && email) {
|
|
1697
|
+
if (csrfToken) {
|
|
1698
|
+
this.tokenManager.setMemoryMode();
|
|
1699
|
+
setCsrfToken(csrfToken);
|
|
1700
|
+
}
|
|
1701
|
+
const session = {
|
|
1702
|
+
accessToken,
|
|
1703
|
+
user: {
|
|
1704
|
+
id: userId,
|
|
1705
|
+
email,
|
|
1706
|
+
name: name || "",
|
|
1707
|
+
// These fields are not provided by backend OAuth callback
|
|
1708
|
+
// They'll be populated when calling getCurrentUser()
|
|
1709
|
+
emailVerified: false,
|
|
1710
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1711
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
this.tokenManager.saveSession(session);
|
|
1715
|
+
this.http.setAuthToken(accessToken);
|
|
1716
|
+
const url = new URL(window.location.href);
|
|
1717
|
+
url.searchParams.delete("access_token");
|
|
1718
|
+
url.searchParams.delete("user_id");
|
|
1719
|
+
url.searchParams.delete("email");
|
|
1720
|
+
url.searchParams.delete("name");
|
|
1721
|
+
url.searchParams.delete("csrf_token");
|
|
1722
|
+
if (params.has("error")) {
|
|
1723
|
+
url.searchParams.delete("error");
|
|
1724
|
+
}
|
|
1725
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
1726
|
+
}
|
|
1727
|
+
} catch (error) {
|
|
1728
|
+
console.debug("OAuth callback detection skipped:", error);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Sign up a new user
|
|
1733
|
+
*/
|
|
1734
|
+
async signUp(request) {
|
|
1735
|
+
try {
|
|
1736
|
+
const response = await this.http.post("/api/auth/users", request);
|
|
1737
|
+
if (response.accessToken && response.user && !isHostedAuthEnvironment()) {
|
|
1738
|
+
const session = {
|
|
1739
|
+
accessToken: response.accessToken,
|
|
1740
|
+
user: response.user
|
|
1741
|
+
};
|
|
1742
|
+
this.tokenManager.saveSession(session);
|
|
1743
|
+
this.http.setAuthToken(response.accessToken);
|
|
1744
|
+
if (response.csrfToken) {
|
|
1745
|
+
setCsrfToken(response.csrfToken);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
return {
|
|
1749
|
+
data: response,
|
|
1750
|
+
error: null
|
|
1751
|
+
};
|
|
1752
|
+
} catch (error) {
|
|
1753
|
+
if (error instanceof InsForgeError) {
|
|
1754
|
+
return { data: null, error };
|
|
1755
|
+
}
|
|
1756
|
+
return {
|
|
1757
|
+
data: null,
|
|
1758
|
+
error: new InsForgeError(
|
|
1759
|
+
error instanceof Error ? error.message : "An unexpected error occurred during sign up",
|
|
1760
|
+
500,
|
|
1761
|
+
"UNEXPECTED_ERROR"
|
|
1762
|
+
)
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Sign in with email and password
|
|
1768
|
+
*/
|
|
1769
|
+
async signInWithPassword(request) {
|
|
1770
|
+
try {
|
|
1771
|
+
const response = await this.http.post("/api/auth/sessions", request);
|
|
1772
|
+
if (!isHostedAuthEnvironment()) {
|
|
1773
|
+
const session = {
|
|
1774
|
+
accessToken: response.accessToken,
|
|
1775
|
+
user: response.user
|
|
1776
|
+
};
|
|
1777
|
+
this.tokenManager.saveSession(session);
|
|
1778
|
+
this.http.setAuthToken(response.accessToken);
|
|
1779
|
+
if (response.csrfToken) {
|
|
1780
|
+
setCsrfToken(response.csrfToken);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
return {
|
|
1784
|
+
data: response,
|
|
1785
|
+
error: null
|
|
1786
|
+
};
|
|
1787
|
+
} catch (error) {
|
|
1788
|
+
if (error instanceof InsForgeError) {
|
|
1789
|
+
return { data: null, error };
|
|
1790
|
+
}
|
|
1791
|
+
return {
|
|
1792
|
+
data: null,
|
|
1793
|
+
error: new InsForgeError(
|
|
1794
|
+
"An unexpected error occurred during sign in",
|
|
1795
|
+
500,
|
|
1796
|
+
"UNEXPECTED_ERROR"
|
|
1797
|
+
)
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Sign in with OAuth provider
|
|
1803
|
+
*/
|
|
1804
|
+
async signInWithOAuth(options) {
|
|
1805
|
+
try {
|
|
1806
|
+
const { provider, redirectTo, skipBrowserRedirect } = options;
|
|
1807
|
+
const params = redirectTo ? { redirect_uri: redirectTo } : void 0;
|
|
1808
|
+
const endpoint = `/api/auth/oauth/${provider}`;
|
|
1809
|
+
const response = await this.http.get(endpoint, { params });
|
|
1810
|
+
if (typeof window !== "undefined" && !skipBrowserRedirect) {
|
|
1811
|
+
window.location.href = response.authUrl;
|
|
1812
|
+
return { data: {}, error: null };
|
|
1813
|
+
}
|
|
1814
|
+
return {
|
|
1815
|
+
data: {
|
|
1816
|
+
url: response.authUrl,
|
|
1817
|
+
provider
|
|
1818
|
+
},
|
|
1819
|
+
error: null
|
|
1820
|
+
};
|
|
1821
|
+
} catch (error) {
|
|
1822
|
+
if (error instanceof InsForgeError) {
|
|
1823
|
+
return { data: {}, error };
|
|
1824
|
+
}
|
|
1825
|
+
return {
|
|
1826
|
+
data: {},
|
|
1827
|
+
error: new InsForgeError(
|
|
1828
|
+
"An unexpected error occurred during OAuth initialization",
|
|
1829
|
+
500,
|
|
1830
|
+
"UNEXPECTED_ERROR"
|
|
1831
|
+
)
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Sign out the current user
|
|
1837
|
+
*/
|
|
1838
|
+
async signOut() {
|
|
1839
|
+
try {
|
|
1840
|
+
try {
|
|
1841
|
+
await this.http.post("/api/auth/logout", void 0, { credentials: "include" });
|
|
1842
|
+
} catch {
|
|
1843
|
+
}
|
|
1844
|
+
this.tokenManager.clearSession();
|
|
1845
|
+
this.http.setAuthToken(null);
|
|
1846
|
+
clearCsrfToken();
|
|
1847
|
+
return { error: null };
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
return {
|
|
1850
|
+
error: new InsForgeError(
|
|
1851
|
+
"Failed to sign out",
|
|
1852
|
+
500,
|
|
1853
|
+
"SIGNOUT_ERROR"
|
|
1854
|
+
)
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Get all public authentication configuration (OAuth + Email)
|
|
1860
|
+
* Returns both OAuth providers and email authentication settings in one request
|
|
1861
|
+
* This is a public endpoint that doesn't require authentication
|
|
1862
|
+
*
|
|
1863
|
+
* @returns Complete public authentication configuration including OAuth providers and email auth settings
|
|
1864
|
+
*
|
|
1865
|
+
* @example
|
|
1866
|
+
* ```ts
|
|
1867
|
+
* const { data, error } = await insforge.auth.getPublicAuthConfig();
|
|
1868
|
+
* if (data) {
|
|
1869
|
+
* console.log(`OAuth providers: ${data.oauth.data.length}`);
|
|
1870
|
+
* console.log(`Password min length: ${data.email.passwordMinLength}`);
|
|
1871
|
+
* }
|
|
1872
|
+
* ```
|
|
1873
|
+
*/
|
|
1874
|
+
async getPublicAuthConfig() {
|
|
1875
|
+
try {
|
|
1876
|
+
const response = await this.http.get("/api/auth/public-config");
|
|
1877
|
+
return {
|
|
1878
|
+
data: response,
|
|
1879
|
+
error: null
|
|
1880
|
+
};
|
|
1881
|
+
} catch (error) {
|
|
1882
|
+
if (error instanceof InsForgeError) {
|
|
1883
|
+
return { data: null, error };
|
|
1884
|
+
}
|
|
1885
|
+
return {
|
|
1886
|
+
data: null,
|
|
1887
|
+
error: new InsForgeError(
|
|
1888
|
+
"An unexpected error occurred while fetching public authentication configuration",
|
|
1889
|
+
500,
|
|
1890
|
+
"UNEXPECTED_ERROR"
|
|
1891
|
+
)
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Get the current user with full profile information
|
|
1897
|
+
* Returns both auth info (id, email, role) and profile data (dynamic fields from users table)
|
|
1898
|
+
*/
|
|
1899
|
+
async getCurrentUser() {
|
|
1900
|
+
try {
|
|
1901
|
+
const session = this.tokenManager.getSession();
|
|
1902
|
+
if (!session?.accessToken) {
|
|
1903
|
+
return { data: null, error: null };
|
|
1904
|
+
}
|
|
1905
|
+
this.http.setAuthToken(session.accessToken);
|
|
1906
|
+
const authResponse = await this.http.get("/api/auth/sessions/current");
|
|
1907
|
+
const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
|
|
1908
|
+
if (profileError && profileError.code !== "PGRST116") {
|
|
1909
|
+
return { data: null, error: profileError };
|
|
1910
|
+
}
|
|
1911
|
+
return {
|
|
1912
|
+
data: {
|
|
1913
|
+
user: authResponse.user,
|
|
1914
|
+
profile: profile ? convertDbProfileToCamelCase(profile) : null
|
|
1915
|
+
},
|
|
1916
|
+
error: null
|
|
1917
|
+
};
|
|
1918
|
+
} catch (error) {
|
|
1919
|
+
if (error instanceof InsForgeError && error.statusCode === 401) {
|
|
1920
|
+
await this.signOut();
|
|
1921
|
+
return { data: null, error: null };
|
|
1922
|
+
}
|
|
1923
|
+
if (error instanceof InsForgeError) {
|
|
1924
|
+
return { data: null, error };
|
|
1925
|
+
}
|
|
1926
|
+
return {
|
|
1927
|
+
data: null,
|
|
1928
|
+
error: new InsForgeError(
|
|
1929
|
+
"An unexpected error occurred while fetching user",
|
|
1930
|
+
500,
|
|
1931
|
+
"UNEXPECTED_ERROR"
|
|
1932
|
+
)
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Get any user's profile by ID
|
|
1938
|
+
* Returns profile information from the users table (dynamic fields)
|
|
1939
|
+
*/
|
|
1940
|
+
async getProfile(userId) {
|
|
1941
|
+
const { data, error } = await this.database.from("users").select("*").eq("id", userId).single();
|
|
1942
|
+
if (error && error.code === "PGRST116") {
|
|
1943
|
+
return { data: null, error: null };
|
|
1944
|
+
}
|
|
1945
|
+
if (data) {
|
|
1946
|
+
return { data: convertDbProfileToCamelCase(data), error: null };
|
|
1947
|
+
}
|
|
1948
|
+
return { data: null, error };
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Get the current session (only session data, no API call)
|
|
1952
|
+
* Returns the stored JWT token and basic user info from local storage
|
|
1953
|
+
*/
|
|
1954
|
+
async getCurrentSession() {
|
|
1955
|
+
try {
|
|
1956
|
+
const session = this.tokenManager.getSession();
|
|
1957
|
+
if (session) {
|
|
1958
|
+
this.http.setAuthToken(session.accessToken);
|
|
1959
|
+
return { data: { session }, error: null };
|
|
1960
|
+
}
|
|
1961
|
+
if (typeof window !== "undefined") {
|
|
1962
|
+
try {
|
|
1963
|
+
const csrfToken = getCsrfToken();
|
|
1964
|
+
const response = await this.http.post(
|
|
1965
|
+
"/api/auth/refresh",
|
|
1966
|
+
void 0,
|
|
1967
|
+
{
|
|
1968
|
+
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
1969
|
+
credentials: "include"
|
|
1970
|
+
}
|
|
1971
|
+
);
|
|
1972
|
+
if (response.accessToken) {
|
|
1973
|
+
this.tokenManager.setMemoryMode();
|
|
1974
|
+
this.tokenManager.setAccessToken(response.accessToken);
|
|
1975
|
+
this.http.setAuthToken(response.accessToken);
|
|
1976
|
+
if (response.user) {
|
|
1977
|
+
this.tokenManager.setUser(response.user);
|
|
1978
|
+
}
|
|
1979
|
+
if (response.csrfToken) {
|
|
1980
|
+
setCsrfToken(response.csrfToken);
|
|
1981
|
+
}
|
|
1982
|
+
return {
|
|
1983
|
+
data: { session: this.tokenManager.getSession() },
|
|
1984
|
+
error: null
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
} catch (error) {
|
|
1988
|
+
if (error instanceof InsForgeError) {
|
|
1989
|
+
if (error.statusCode === 404) {
|
|
1990
|
+
this.tokenManager.setStorageMode();
|
|
1991
|
+
const session2 = this.tokenManager.getSession();
|
|
1992
|
+
if (session2) {
|
|
1993
|
+
return { data: { session: session2 }, error: null };
|
|
1994
|
+
}
|
|
1995
|
+
return { data: { session: null }, error: null };
|
|
1996
|
+
}
|
|
1997
|
+
return { data: { session: null }, error };
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
return { data: { session: null }, error: null };
|
|
2002
|
+
} catch (error) {
|
|
2003
|
+
if (error instanceof InsForgeError) {
|
|
2004
|
+
return { data: { session: null }, error };
|
|
2005
|
+
}
|
|
2006
|
+
return {
|
|
2007
|
+
data: { session: null },
|
|
2008
|
+
error: new InsForgeError(
|
|
2009
|
+
"An unexpected error occurred while getting session",
|
|
2010
|
+
500,
|
|
2011
|
+
"UNEXPECTED_ERROR"
|
|
2012
|
+
)
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Set/Update the current user's profile
|
|
2018
|
+
* Updates profile information in the users table (supports any dynamic fields)
|
|
2019
|
+
*/
|
|
2020
|
+
async setProfile(profile) {
|
|
2021
|
+
const session = this.tokenManager.getSession();
|
|
2022
|
+
if (!session?.accessToken) {
|
|
2023
|
+
return {
|
|
2024
|
+
data: null,
|
|
2025
|
+
error: new InsForgeError(
|
|
2026
|
+
"No authenticated user found",
|
|
2027
|
+
401,
|
|
2028
|
+
"UNAUTHENTICATED"
|
|
2029
|
+
)
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
if (!session.user?.id) {
|
|
2033
|
+
const { data: data2, error: error2 } = await this.getCurrentUser();
|
|
2034
|
+
if (error2) {
|
|
2035
|
+
return { data: null, error: error2 };
|
|
2036
|
+
}
|
|
2037
|
+
if (data2?.user) {
|
|
2038
|
+
session.user = {
|
|
2039
|
+
id: data2.user.id,
|
|
2040
|
+
email: data2.user.email,
|
|
2041
|
+
name: "",
|
|
2042
|
+
emailVerified: false,
|
|
2043
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2044
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2045
|
+
};
|
|
2046
|
+
this.tokenManager.saveSession(session);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
const dbProfile = convertCamelCaseToDbProfile(profile);
|
|
2050
|
+
const { data, error } = await this.database.from("users").update(dbProfile).eq("id", session.user.id).select().single();
|
|
2051
|
+
if (data) {
|
|
2052
|
+
return { data: convertDbProfileToCamelCase(data), error: null };
|
|
2053
|
+
}
|
|
2054
|
+
return { data: null, error };
|
|
2055
|
+
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Send email verification (code or link based on config)
|
|
2058
|
+
*
|
|
2059
|
+
* Send email verification using the method configured in auth settings (verifyEmailMethod).
|
|
2060
|
+
* When method is 'code', sends a 6-digit numeric code. When method is 'link', sends a magic link.
|
|
2061
|
+
* Prevents user enumeration by returning success even if email doesn't exist.
|
|
2062
|
+
*/
|
|
2063
|
+
async sendVerificationEmail(request) {
|
|
2064
|
+
try {
|
|
2065
|
+
const response = await this.http.post(
|
|
2066
|
+
"/api/auth/email/send-verification",
|
|
2067
|
+
request
|
|
2068
|
+
);
|
|
2069
|
+
return {
|
|
2070
|
+
data: response,
|
|
2071
|
+
error: null
|
|
2072
|
+
};
|
|
2073
|
+
} catch (error) {
|
|
2074
|
+
if (error instanceof InsForgeError) {
|
|
2075
|
+
return { data: null, error };
|
|
2076
|
+
}
|
|
2077
|
+
return {
|
|
2078
|
+
data: null,
|
|
2079
|
+
error: new InsForgeError(
|
|
2080
|
+
"An unexpected error occurred while sending verification code",
|
|
2081
|
+
500,
|
|
2082
|
+
"UNEXPECTED_ERROR"
|
|
2083
|
+
)
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Send password reset (code or link based on config)
|
|
2089
|
+
*
|
|
2090
|
+
* Send password reset email using the method configured in auth settings (resetPasswordMethod).
|
|
2091
|
+
* When method is 'code', sends a 6-digit numeric code for two-step flow.
|
|
2092
|
+
* When method is 'link', sends a magic link.
|
|
2093
|
+
* Prevents user enumeration by returning success even if email doesn't exist.
|
|
2094
|
+
*/
|
|
2095
|
+
async sendResetPasswordEmail(request) {
|
|
2096
|
+
try {
|
|
2097
|
+
const response = await this.http.post(
|
|
2098
|
+
"/api/auth/email/send-reset-password",
|
|
2099
|
+
request
|
|
2100
|
+
);
|
|
2101
|
+
return {
|
|
2102
|
+
data: response,
|
|
2103
|
+
error: null
|
|
2104
|
+
};
|
|
2105
|
+
} catch (error) {
|
|
2106
|
+
if (error instanceof InsForgeError) {
|
|
2107
|
+
return { data: null, error };
|
|
2108
|
+
}
|
|
2109
|
+
return {
|
|
2110
|
+
data: null,
|
|
2111
|
+
error: new InsForgeError(
|
|
2112
|
+
"An unexpected error occurred while sending password reset code",
|
|
2113
|
+
500,
|
|
2114
|
+
"UNEXPECTED_ERROR"
|
|
2115
|
+
)
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Exchange reset password code for reset token
|
|
2121
|
+
*
|
|
2122
|
+
* Step 1 of two-step password reset flow (only used when resetPasswordMethod is 'code'):
|
|
2123
|
+
* 1. Verify the 6-digit code sent to user's email
|
|
2124
|
+
* 2. Return a reset token that can be used to actually reset the password
|
|
2125
|
+
*
|
|
2126
|
+
* This endpoint is not used when resetPasswordMethod is 'link' (magic link flow is direct).
|
|
2127
|
+
*/
|
|
2128
|
+
async exchangeResetPasswordToken(request) {
|
|
2129
|
+
try {
|
|
2130
|
+
const response = await this.http.post(
|
|
2131
|
+
"/api/auth/email/exchange-reset-password-token",
|
|
2132
|
+
request
|
|
2133
|
+
);
|
|
2134
|
+
return {
|
|
2135
|
+
data: response,
|
|
2136
|
+
error: null
|
|
2137
|
+
};
|
|
2138
|
+
} catch (error) {
|
|
2139
|
+
if (error instanceof InsForgeError) {
|
|
2140
|
+
return { data: null, error };
|
|
2141
|
+
}
|
|
2142
|
+
return {
|
|
2143
|
+
data: null,
|
|
2144
|
+
error: new InsForgeError(
|
|
2145
|
+
"An unexpected error occurred while verifying reset code",
|
|
2146
|
+
500,
|
|
2147
|
+
"UNEXPECTED_ERROR"
|
|
2148
|
+
)
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Reset password with token
|
|
2154
|
+
*
|
|
2155
|
+
* Reset user password with a token. The token can be:
|
|
2156
|
+
* - Magic link token (64-character hex token from send-reset-password when method is 'link')
|
|
2157
|
+
* - Reset token (from exchange-reset-password-token after code verification when method is 'code')
|
|
2158
|
+
*
|
|
2159
|
+
* Both token types use RESET_PASSWORD purpose and are verified the same way.
|
|
2160
|
+
*
|
|
2161
|
+
* Flow summary:
|
|
2162
|
+
* - Code method: send-reset-password → exchange-reset-password-token → reset-password (with resetToken)
|
|
2163
|
+
* - Link method: send-reset-password → reset-password (with link token directly)
|
|
2164
|
+
*/
|
|
2165
|
+
async resetPassword(request) {
|
|
2166
|
+
try {
|
|
2167
|
+
const response = await this.http.post(
|
|
2168
|
+
"/api/auth/email/reset-password",
|
|
2169
|
+
request
|
|
2170
|
+
);
|
|
2171
|
+
return {
|
|
2172
|
+
data: response,
|
|
2173
|
+
error: null
|
|
2174
|
+
};
|
|
2175
|
+
} catch (error) {
|
|
2176
|
+
if (error instanceof InsForgeError) {
|
|
2177
|
+
return { data: null, error };
|
|
2178
|
+
}
|
|
2179
|
+
return {
|
|
2180
|
+
data: null,
|
|
2181
|
+
error: new InsForgeError(
|
|
2182
|
+
"An unexpected error occurred while resetting password",
|
|
2183
|
+
500,
|
|
2184
|
+
"UNEXPECTED_ERROR"
|
|
2185
|
+
)
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Verify email with code or link
|
|
2191
|
+
*
|
|
2192
|
+
* Verify email address using the method configured in auth settings (verifyEmailMethod):
|
|
2193
|
+
* - Code verification: Provide both `email` and `otp` (6-digit numeric code)
|
|
2194
|
+
* - Link verification: Provide only `otp` (64-character hex token from magic link)
|
|
2195
|
+
*
|
|
2196
|
+
* Successfully verified users will receive a session token.
|
|
2197
|
+
*
|
|
2198
|
+
* The email verification link sent to users always points to the backend API endpoint.
|
|
2199
|
+
* If `verifyEmailRedirectTo` is configured, the backend will redirect to that URL after successful verification.
|
|
2200
|
+
* Otherwise, a default success page is displayed.
|
|
2201
|
+
*/
|
|
2202
|
+
async verifyEmail(request) {
|
|
2203
|
+
try {
|
|
2204
|
+
const response = await this.http.post(
|
|
2205
|
+
"/api/auth/email/verify",
|
|
2206
|
+
request
|
|
2207
|
+
);
|
|
2208
|
+
if (!isHostedAuthEnvironment()) {
|
|
2209
|
+
const session = {
|
|
2210
|
+
accessToken: response.accessToken,
|
|
2211
|
+
user: response.user
|
|
2212
|
+
};
|
|
2213
|
+
this.tokenManager.saveSession(session);
|
|
2214
|
+
this.http.setAuthToken(response.accessToken);
|
|
2215
|
+
if (response.csrfToken) {
|
|
2216
|
+
setCsrfToken(response.csrfToken);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
return {
|
|
2220
|
+
data: response,
|
|
2221
|
+
error: null
|
|
2222
|
+
};
|
|
2223
|
+
} catch (error) {
|
|
2224
|
+
if (error instanceof InsForgeError) {
|
|
2225
|
+
return { data: null, error };
|
|
2226
|
+
}
|
|
2227
|
+
return {
|
|
2228
|
+
data: null,
|
|
2229
|
+
error: new InsForgeError(
|
|
2230
|
+
"An unexpected error occurred while verifying email",
|
|
2231
|
+
500,
|
|
2232
|
+
"UNEXPECTED_ERROR"
|
|
2233
|
+
)
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2238
|
+
|
|
2239
|
+
// src/modules/storage.ts
|
|
2240
|
+
var StorageBucket = class {
|
|
2241
|
+
constructor(bucketName, http) {
|
|
2242
|
+
this.bucketName = bucketName;
|
|
2243
|
+
this.http = http;
|
|
2244
|
+
}
|
|
2245
|
+
/**
|
|
2246
|
+
* Upload a file with a specific key
|
|
2247
|
+
* Uses the upload strategy from backend (direct or presigned)
|
|
2248
|
+
* @param path - The object key/path
|
|
2249
|
+
* @param file - File or Blob to upload
|
|
2250
|
+
*/
|
|
2251
|
+
async upload(path, file) {
|
|
2252
|
+
try {
|
|
2253
|
+
const strategyResponse = await this.http.post(
|
|
2254
|
+
`/api/storage/buckets/${this.bucketName}/upload-strategy`,
|
|
2255
|
+
{
|
|
2256
|
+
filename: path,
|
|
2257
|
+
contentType: file.type || "application/octet-stream",
|
|
2258
|
+
size: file.size
|
|
2259
|
+
}
|
|
2260
|
+
);
|
|
2261
|
+
if (strategyResponse.method === "presigned") {
|
|
2262
|
+
return await this.uploadWithPresignedUrl(strategyResponse, file);
|
|
2263
|
+
}
|
|
2264
|
+
if (strategyResponse.method === "direct") {
|
|
2265
|
+
const formData = new FormData();
|
|
2266
|
+
formData.append("file", file);
|
|
2267
|
+
const response = await this.http.request(
|
|
2268
|
+
"PUT",
|
|
2269
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`,
|
|
2270
|
+
{
|
|
2271
|
+
body: formData,
|
|
2272
|
+
headers: {
|
|
2273
|
+
// Don't set Content-Type, let browser set multipart boundary
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
);
|
|
2277
|
+
return { data: response, error: null };
|
|
2278
|
+
}
|
|
2279
|
+
throw new InsForgeError(
|
|
2280
|
+
`Unsupported upload method: ${strategyResponse.method}`,
|
|
2281
|
+
500,
|
|
2282
|
+
"STORAGE_ERROR"
|
|
2283
|
+
);
|
|
2284
|
+
} catch (error) {
|
|
2285
|
+
return {
|
|
2286
|
+
data: null,
|
|
2287
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
2288
|
+
"Upload failed",
|
|
2289
|
+
500,
|
|
2290
|
+
"STORAGE_ERROR"
|
|
2291
|
+
)
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
/**
|
|
2296
|
+
* Upload a file with auto-generated key
|
|
2297
|
+
* Uses the upload strategy from backend (direct or presigned)
|
|
2298
|
+
* @param file - File or Blob to upload
|
|
2299
|
+
*/
|
|
2300
|
+
async uploadAuto(file) {
|
|
2301
|
+
try {
|
|
2302
|
+
const filename = file instanceof File ? file.name : "file";
|
|
2303
|
+
const strategyResponse = await this.http.post(
|
|
2304
|
+
`/api/storage/buckets/${this.bucketName}/upload-strategy`,
|
|
2305
|
+
{
|
|
2306
|
+
filename,
|
|
2307
|
+
contentType: file.type || "application/octet-stream",
|
|
2308
|
+
size: file.size
|
|
2309
|
+
}
|
|
2310
|
+
);
|
|
2311
|
+
if (strategyResponse.method === "presigned") {
|
|
2312
|
+
return await this.uploadWithPresignedUrl(strategyResponse, file);
|
|
2313
|
+
}
|
|
2314
|
+
if (strategyResponse.method === "direct") {
|
|
2315
|
+
const formData = new FormData();
|
|
2316
|
+
formData.append("file", file);
|
|
2317
|
+
const response = await this.http.request(
|
|
2318
|
+
"POST",
|
|
2319
|
+
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
2320
|
+
{
|
|
2321
|
+
body: formData,
|
|
2322
|
+
headers: {
|
|
2323
|
+
// Don't set Content-Type, let browser set multipart boundary
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
);
|
|
2327
|
+
return { data: response, error: null };
|
|
2328
|
+
}
|
|
2329
|
+
throw new InsForgeError(
|
|
2330
|
+
`Unsupported upload method: ${strategyResponse.method}`,
|
|
2331
|
+
500,
|
|
2332
|
+
"STORAGE_ERROR"
|
|
2333
|
+
);
|
|
2334
|
+
} catch (error) {
|
|
2335
|
+
return {
|
|
2336
|
+
data: null,
|
|
2337
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
2338
|
+
"Upload failed",
|
|
2339
|
+
500,
|
|
2340
|
+
"STORAGE_ERROR"
|
|
2341
|
+
)
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Internal method to handle presigned URL uploads
|
|
2347
|
+
*/
|
|
2348
|
+
async uploadWithPresignedUrl(strategy, file) {
|
|
2349
|
+
try {
|
|
2350
|
+
const formData = new FormData();
|
|
2351
|
+
if (strategy.fields) {
|
|
2352
|
+
Object.entries(strategy.fields).forEach(([key, value]) => {
|
|
2353
|
+
formData.append(key, value);
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
formData.append("file", file);
|
|
2357
|
+
const uploadResponse = await fetch(strategy.uploadUrl, {
|
|
2358
|
+
method: "POST",
|
|
2359
|
+
body: formData
|
|
2360
|
+
});
|
|
2361
|
+
if (!uploadResponse.ok) {
|
|
2362
|
+
throw new InsForgeError(
|
|
2363
|
+
`Upload to storage failed: ${uploadResponse.statusText}`,
|
|
2364
|
+
uploadResponse.status,
|
|
2365
|
+
"STORAGE_ERROR"
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
if (strategy.confirmRequired && strategy.confirmUrl) {
|
|
2369
|
+
const confirmResponse = await this.http.post(
|
|
2370
|
+
strategy.confirmUrl,
|
|
2371
|
+
{
|
|
2372
|
+
size: file.size,
|
|
2373
|
+
contentType: file.type || "application/octet-stream"
|
|
2374
|
+
}
|
|
2375
|
+
);
|
|
2376
|
+
return { data: confirmResponse, error: null };
|
|
2377
|
+
}
|
|
2378
|
+
return {
|
|
2379
|
+
data: {
|
|
2380
|
+
key: strategy.key,
|
|
2381
|
+
bucket: this.bucketName,
|
|
2382
|
+
size: file.size,
|
|
2383
|
+
mimeType: file.type || "application/octet-stream",
|
|
2384
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2385
|
+
url: this.getPublicUrl(strategy.key)
|
|
2386
|
+
},
|
|
2387
|
+
error: null
|
|
2388
|
+
};
|
|
2389
|
+
} catch (error) {
|
|
2390
|
+
throw error instanceof InsForgeError ? error : new InsForgeError(
|
|
2391
|
+
"Presigned upload failed",
|
|
2392
|
+
500,
|
|
2393
|
+
"STORAGE_ERROR"
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
/**
|
|
2398
|
+
* Download a file
|
|
2399
|
+
* Uses the download strategy from backend (direct or presigned)
|
|
2400
|
+
* @param path - The object key/path
|
|
2401
|
+
* Returns the file as a Blob
|
|
2402
|
+
*/
|
|
2403
|
+
async download(path) {
|
|
2404
|
+
try {
|
|
2405
|
+
const strategyResponse = await this.http.post(
|
|
2406
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}/download-strategy`,
|
|
2407
|
+
{ expiresIn: 3600 }
|
|
2408
|
+
);
|
|
2409
|
+
const downloadUrl = strategyResponse.url;
|
|
2410
|
+
const headers = {};
|
|
2411
|
+
if (strategyResponse.method === "direct") {
|
|
2412
|
+
Object.assign(headers, this.http.getHeaders());
|
|
2413
|
+
}
|
|
2414
|
+
const response = await fetch(downloadUrl, {
|
|
2415
|
+
method: "GET",
|
|
2416
|
+
headers
|
|
2417
|
+
});
|
|
2418
|
+
if (!response.ok) {
|
|
2419
|
+
try {
|
|
2420
|
+
const error = await response.json();
|
|
2421
|
+
throw InsForgeError.fromApiError(error);
|
|
2422
|
+
} catch {
|
|
2423
|
+
throw new InsForgeError(
|
|
2424
|
+
`Download failed: ${response.statusText}`,
|
|
2425
|
+
response.status,
|
|
2426
|
+
"STORAGE_ERROR"
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
const blob = await response.blob();
|
|
2431
|
+
return { data: blob, error: null };
|
|
2432
|
+
} catch (error) {
|
|
2433
|
+
return {
|
|
2434
|
+
data: null,
|
|
2435
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
2436
|
+
"Download failed",
|
|
2437
|
+
500,
|
|
2438
|
+
"STORAGE_ERROR"
|
|
2439
|
+
)
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Get public URL for a file
|
|
2445
|
+
* @param path - The object key/path
|
|
2446
|
+
*/
|
|
2447
|
+
getPublicUrl(path) {
|
|
2448
|
+
return `${this.http.baseUrl}/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`;
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
* List objects in the bucket
|
|
2452
|
+
* @param prefix - Filter by key prefix
|
|
2453
|
+
* @param search - Search in file names
|
|
2454
|
+
* @param limit - Maximum number of results (default: 100, max: 1000)
|
|
2455
|
+
* @param offset - Number of results to skip
|
|
2456
|
+
*/
|
|
2457
|
+
async list(options) {
|
|
2458
|
+
try {
|
|
2459
|
+
const params = {};
|
|
2460
|
+
if (options?.prefix) params.prefix = options.prefix;
|
|
2461
|
+
if (options?.search) params.search = options.search;
|
|
2462
|
+
if (options?.limit) params.limit = options.limit.toString();
|
|
2463
|
+
if (options?.offset) params.offset = options.offset.toString();
|
|
2464
|
+
const response = await this.http.get(
|
|
2465
|
+
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
2466
|
+
{ params }
|
|
2467
|
+
);
|
|
2468
|
+
return { data: response, error: null };
|
|
2469
|
+
} catch (error) {
|
|
2470
|
+
return {
|
|
2471
|
+
data: null,
|
|
2472
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
2473
|
+
"List failed",
|
|
2474
|
+
500,
|
|
2475
|
+
"STORAGE_ERROR"
|
|
2476
|
+
)
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Delete a file
|
|
2482
|
+
* @param path - The object key/path
|
|
2483
|
+
*/
|
|
2484
|
+
async remove(path) {
|
|
2485
|
+
try {
|
|
2486
|
+
const response = await this.http.delete(
|
|
2487
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`
|
|
2488
|
+
);
|
|
2489
|
+
return { data: response, error: null };
|
|
2490
|
+
} catch (error) {
|
|
2491
|
+
return {
|
|
2492
|
+
data: null,
|
|
2493
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
2494
|
+
"Delete failed",
|
|
2495
|
+
500,
|
|
2496
|
+
"STORAGE_ERROR"
|
|
2497
|
+
)
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2502
|
+
var Storage = class {
|
|
2503
|
+
constructor(http) {
|
|
2504
|
+
this.http = http;
|
|
2505
|
+
}
|
|
2506
|
+
/**
|
|
2507
|
+
* Get a bucket instance for operations
|
|
2508
|
+
* @param bucketName - Name of the bucket
|
|
2509
|
+
*/
|
|
2510
|
+
from(bucketName) {
|
|
2511
|
+
return new StorageBucket(bucketName, this.http);
|
|
2512
|
+
}
|
|
2513
|
+
};
|
|
2514
|
+
|
|
2515
|
+
// src/modules/ai.ts
|
|
2516
|
+
var AI = class {
|
|
2517
|
+
constructor(http) {
|
|
2518
|
+
this.http = http;
|
|
2519
|
+
this.chat = new Chat(http);
|
|
2520
|
+
this.images = new Images(http);
|
|
2521
|
+
}
|
|
2522
|
+
};
|
|
2523
|
+
var Chat = class {
|
|
2524
|
+
constructor(http) {
|
|
2525
|
+
this.completions = new ChatCompletions(http);
|
|
2526
|
+
}
|
|
2527
|
+
};
|
|
2528
|
+
var ChatCompletions = class {
|
|
2529
|
+
constructor(http) {
|
|
2530
|
+
this.http = http;
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Create a chat completion - OpenAI-like response format
|
|
2534
|
+
*
|
|
2535
|
+
* @example
|
|
2536
|
+
* ```typescript
|
|
2537
|
+
* // Non-streaming
|
|
2538
|
+
* const completion = await client.ai.chat.completions.create({
|
|
2539
|
+
* model: 'gpt-4',
|
|
2540
|
+
* messages: [{ role: 'user', content: 'Hello!' }]
|
|
2541
|
+
* });
|
|
2542
|
+
* console.log(completion.choices[0].message.content);
|
|
2543
|
+
*
|
|
2544
|
+
* // With images
|
|
2545
|
+
* const response = await client.ai.chat.completions.create({
|
|
2546
|
+
* model: 'gpt-4-vision',
|
|
2547
|
+
* messages: [{
|
|
2548
|
+
* role: 'user',
|
|
2549
|
+
* content: 'What is in this image?',
|
|
2550
|
+
* images: [{ url: 'https://example.com/image.jpg' }]
|
|
2551
|
+
* }]
|
|
2552
|
+
* });
|
|
2553
|
+
*
|
|
2554
|
+
* // Streaming - returns async iterable
|
|
2555
|
+
* const stream = await client.ai.chat.completions.create({
|
|
2556
|
+
* model: 'gpt-4',
|
|
2557
|
+
* messages: [{ role: 'user', content: 'Tell me a story' }],
|
|
2558
|
+
* stream: true
|
|
2559
|
+
* });
|
|
2560
|
+
*
|
|
2561
|
+
* for await (const chunk of stream) {
|
|
2562
|
+
* if (chunk.choices[0]?.delta?.content) {
|
|
2563
|
+
* process.stdout.write(chunk.choices[0].delta.content);
|
|
2564
|
+
* }
|
|
2565
|
+
* }
|
|
2566
|
+
* ```
|
|
2567
|
+
*/
|
|
2568
|
+
async create(params) {
|
|
2569
|
+
const backendParams = {
|
|
2570
|
+
model: params.model,
|
|
2571
|
+
messages: params.messages,
|
|
2572
|
+
temperature: params.temperature,
|
|
2573
|
+
maxTokens: params.maxTokens,
|
|
2574
|
+
topP: params.topP,
|
|
2575
|
+
stream: params.stream
|
|
2576
|
+
};
|
|
2577
|
+
if (params.stream) {
|
|
2578
|
+
const headers = this.http.getHeaders();
|
|
2579
|
+
headers["Content-Type"] = "application/json";
|
|
2580
|
+
const response2 = await this.http.fetch(
|
|
2581
|
+
`${this.http.baseUrl}/api/ai/chat/completion`,
|
|
2582
|
+
{
|
|
2583
|
+
method: "POST",
|
|
2584
|
+
headers,
|
|
2585
|
+
body: JSON.stringify(backendParams)
|
|
2586
|
+
}
|
|
2587
|
+
);
|
|
2588
|
+
if (!response2.ok) {
|
|
2589
|
+
const error = await response2.json();
|
|
2590
|
+
throw new Error(error.error || "Stream request failed");
|
|
2591
|
+
}
|
|
2592
|
+
return this.parseSSEStream(response2, params.model);
|
|
2593
|
+
}
|
|
2594
|
+
const response = await this.http.post(
|
|
2595
|
+
"/api/ai/chat/completion",
|
|
2596
|
+
backendParams
|
|
2597
|
+
);
|
|
2598
|
+
const content = response.text || "";
|
|
2599
|
+
return {
|
|
2600
|
+
id: `chatcmpl-${Date.now()}`,
|
|
2601
|
+
object: "chat.completion",
|
|
2602
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2603
|
+
model: response.metadata?.model,
|
|
2604
|
+
choices: [
|
|
2605
|
+
{
|
|
2606
|
+
index: 0,
|
|
2607
|
+
message: {
|
|
2608
|
+
role: "assistant",
|
|
2609
|
+
content
|
|
2610
|
+
},
|
|
2611
|
+
finish_reason: "stop"
|
|
2612
|
+
}
|
|
2613
|
+
],
|
|
2614
|
+
usage: response.metadata?.usage || {
|
|
2615
|
+
prompt_tokens: 0,
|
|
2616
|
+
completion_tokens: 0,
|
|
2617
|
+
total_tokens: 0
|
|
2618
|
+
}
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Parse SSE stream into async iterable of OpenAI-like chunks
|
|
2623
|
+
*/
|
|
2624
|
+
async *parseSSEStream(response, model) {
|
|
2625
|
+
const reader = response.body.getReader();
|
|
2626
|
+
const decoder = new TextDecoder();
|
|
2627
|
+
let buffer = "";
|
|
2628
|
+
try {
|
|
2629
|
+
while (true) {
|
|
2630
|
+
const { done, value } = await reader.read();
|
|
2631
|
+
if (done) break;
|
|
2632
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2633
|
+
const lines = buffer.split("\n");
|
|
2634
|
+
buffer = lines.pop() || "";
|
|
2635
|
+
for (const line of lines) {
|
|
2636
|
+
if (line.startsWith("data: ")) {
|
|
2637
|
+
const dataStr = line.slice(6).trim();
|
|
2638
|
+
if (dataStr) {
|
|
2639
|
+
try {
|
|
2640
|
+
const data = JSON.parse(dataStr);
|
|
2641
|
+
if (data.chunk || data.content) {
|
|
2642
|
+
yield {
|
|
2643
|
+
id: `chatcmpl-${Date.now()}`,
|
|
2644
|
+
object: "chat.completion.chunk",
|
|
2645
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2646
|
+
model,
|
|
2647
|
+
choices: [
|
|
2648
|
+
{
|
|
2649
|
+
index: 0,
|
|
2650
|
+
delta: {
|
|
2651
|
+
content: data.chunk || data.content
|
|
2652
|
+
},
|
|
2653
|
+
finish_reason: data.done ? "stop" : null
|
|
2654
|
+
}
|
|
2655
|
+
]
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
if (data.done) {
|
|
2659
|
+
reader.releaseLock();
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
} catch (e) {
|
|
2663
|
+
console.warn("Failed to parse SSE data:", dataStr);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
} finally {
|
|
2670
|
+
reader.releaseLock();
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
};
|
|
2674
|
+
var Images = class {
|
|
2675
|
+
constructor(http) {
|
|
2676
|
+
this.http = http;
|
|
2677
|
+
}
|
|
2678
|
+
/**
|
|
2679
|
+
* Generate images - OpenAI-like response format
|
|
2680
|
+
*
|
|
2681
|
+
* @example
|
|
2682
|
+
* ```typescript
|
|
2683
|
+
* // Text-to-image
|
|
2684
|
+
* const response = await client.ai.images.generate({
|
|
2685
|
+
* model: 'dall-e-3',
|
|
2686
|
+
* prompt: 'A sunset over mountains',
|
|
2687
|
+
* });
|
|
2688
|
+
* console.log(response.images[0].url);
|
|
2689
|
+
*
|
|
2690
|
+
* // Image-to-image (with input images)
|
|
2691
|
+
* const response = await client.ai.images.generate({
|
|
2692
|
+
* model: 'stable-diffusion-xl',
|
|
2693
|
+
* prompt: 'Transform this into a watercolor painting',
|
|
2694
|
+
* images: [
|
|
2695
|
+
* { url: 'https://example.com/input.jpg' },
|
|
2696
|
+
* // or base64-encoded Data URI:
|
|
2697
|
+
* { url: 'data:image/jpeg;base64,/9j/4AAQ...' }
|
|
2698
|
+
* ]
|
|
2699
|
+
* });
|
|
2700
|
+
* ```
|
|
2701
|
+
*/
|
|
2702
|
+
async generate(params) {
|
|
2703
|
+
const response = await this.http.post(
|
|
2704
|
+
"/api/ai/image/generation",
|
|
2705
|
+
params
|
|
2706
|
+
);
|
|
2707
|
+
let data = [];
|
|
2708
|
+
if (response.images && response.images.length > 0) {
|
|
2709
|
+
data = response.images.map((img) => ({
|
|
2710
|
+
b64_json: img.imageUrl.replace(/^data:image\/\w+;base64,/, ""),
|
|
2711
|
+
content: response.text
|
|
2712
|
+
}));
|
|
2713
|
+
} else if (response.text) {
|
|
2714
|
+
data = [{ content: response.text }];
|
|
2715
|
+
}
|
|
2716
|
+
return {
|
|
2717
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2718
|
+
data,
|
|
2719
|
+
...response.metadata?.usage && {
|
|
2720
|
+
usage: {
|
|
2721
|
+
total_tokens: response.metadata.usage.totalTokens || 0,
|
|
2722
|
+
input_tokens: response.metadata.usage.promptTokens || 0,
|
|
2723
|
+
output_tokens: response.metadata.usage.completionTokens || 0
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
|
|
2730
|
+
// src/modules/functions.ts
|
|
2731
|
+
var Functions = class {
|
|
2732
|
+
constructor(http) {
|
|
2733
|
+
this.http = http;
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* Invokes an Edge Function
|
|
2737
|
+
* @param slug The function slug to invoke
|
|
2738
|
+
* @param options Request options
|
|
2739
|
+
*/
|
|
2740
|
+
async invoke(slug, options = {}) {
|
|
2741
|
+
try {
|
|
2742
|
+
const { method = "POST", body, headers = {} } = options;
|
|
2743
|
+
const path = `/functions/${slug}`;
|
|
2744
|
+
const data = await this.http.request(
|
|
2745
|
+
method,
|
|
2746
|
+
path,
|
|
2747
|
+
{ body, headers }
|
|
2748
|
+
);
|
|
2749
|
+
return { data, error: null };
|
|
2750
|
+
} catch (error) {
|
|
2751
|
+
return {
|
|
2752
|
+
data: null,
|
|
2753
|
+
error
|
|
2754
|
+
// Pass through the full error object with all properties
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
};
|
|
2759
|
+
|
|
2760
|
+
// src/modules/realtime.ts
|
|
2761
|
+
import { io } from "socket.io-client";
|
|
2762
|
+
var CONNECT_TIMEOUT = 1e4;
|
|
2763
|
+
var Realtime = class {
|
|
2764
|
+
constructor(baseUrl, tokenManager) {
|
|
2765
|
+
this.socket = null;
|
|
2766
|
+
this.connectPromise = null;
|
|
2767
|
+
this.subscribedChannels = /* @__PURE__ */ new Set();
|
|
2768
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
2769
|
+
this.baseUrl = baseUrl;
|
|
2770
|
+
this.tokenManager = tokenManager;
|
|
2771
|
+
}
|
|
2772
|
+
notifyListeners(event, payload) {
|
|
2773
|
+
const listeners = this.eventListeners.get(event);
|
|
2774
|
+
if (!listeners) return;
|
|
2775
|
+
for (const cb of listeners) {
|
|
2776
|
+
try {
|
|
2777
|
+
cb(payload);
|
|
2778
|
+
} catch (err) {
|
|
2779
|
+
console.error(`Error in ${event} callback:`, err);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
/**
|
|
2784
|
+
* Connect to the realtime server
|
|
2785
|
+
* @returns Promise that resolves when connected
|
|
2786
|
+
*/
|
|
2787
|
+
connect() {
|
|
2788
|
+
if (this.socket?.connected) {
|
|
2789
|
+
return Promise.resolve();
|
|
2790
|
+
}
|
|
2791
|
+
if (this.connectPromise) {
|
|
2792
|
+
return this.connectPromise;
|
|
2793
|
+
}
|
|
2794
|
+
this.connectPromise = new Promise((resolve, reject) => {
|
|
2795
|
+
const session = this.tokenManager.getSession();
|
|
2796
|
+
const token = session?.accessToken;
|
|
2797
|
+
this.socket = io(this.baseUrl, {
|
|
2798
|
+
transports: ["websocket"],
|
|
2799
|
+
auth: token ? { token } : void 0
|
|
2800
|
+
});
|
|
2801
|
+
let initialConnection = true;
|
|
2802
|
+
let timeoutId = null;
|
|
2803
|
+
const cleanup = () => {
|
|
2804
|
+
if (timeoutId) {
|
|
2805
|
+
clearTimeout(timeoutId);
|
|
2806
|
+
timeoutId = null;
|
|
2807
|
+
}
|
|
2808
|
+
};
|
|
2809
|
+
timeoutId = setTimeout(() => {
|
|
2810
|
+
if (initialConnection) {
|
|
2811
|
+
initialConnection = false;
|
|
2812
|
+
this.connectPromise = null;
|
|
2813
|
+
this.socket?.disconnect();
|
|
2814
|
+
this.socket = null;
|
|
2815
|
+
reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
|
|
2816
|
+
}
|
|
2817
|
+
}, CONNECT_TIMEOUT);
|
|
2818
|
+
this.socket.on("connect", () => {
|
|
2819
|
+
cleanup();
|
|
2820
|
+
for (const channel of this.subscribedChannels) {
|
|
2821
|
+
this.socket.emit("realtime:subscribe", { channel });
|
|
2822
|
+
}
|
|
2823
|
+
this.notifyListeners("connect");
|
|
2824
|
+
if (initialConnection) {
|
|
2825
|
+
initialConnection = false;
|
|
2826
|
+
this.connectPromise = null;
|
|
2827
|
+
resolve();
|
|
2828
|
+
}
|
|
2829
|
+
});
|
|
2830
|
+
this.socket.on("connect_error", (error) => {
|
|
2831
|
+
cleanup();
|
|
2832
|
+
this.notifyListeners("connect_error", error);
|
|
2833
|
+
if (initialConnection) {
|
|
2834
|
+
initialConnection = false;
|
|
2835
|
+
this.connectPromise = null;
|
|
2836
|
+
reject(error);
|
|
2837
|
+
}
|
|
2838
|
+
});
|
|
2839
|
+
this.socket.on("disconnect", (reason) => {
|
|
2840
|
+
this.notifyListeners("disconnect", reason);
|
|
2841
|
+
});
|
|
2842
|
+
this.socket.on("realtime:error", (error) => {
|
|
2843
|
+
this.notifyListeners("error", error);
|
|
2844
|
+
});
|
|
2845
|
+
this.socket.onAny((event, message) => {
|
|
2846
|
+
if (event === "realtime:error") return;
|
|
2847
|
+
this.notifyListeners(event, message);
|
|
2848
|
+
});
|
|
2849
|
+
});
|
|
2850
|
+
return this.connectPromise;
|
|
2851
|
+
}
|
|
2852
|
+
/**
|
|
2853
|
+
* Disconnect from the realtime server
|
|
2854
|
+
*/
|
|
2855
|
+
disconnect() {
|
|
2856
|
+
if (this.socket) {
|
|
2857
|
+
this.socket.disconnect();
|
|
2858
|
+
this.socket = null;
|
|
2859
|
+
}
|
|
2860
|
+
this.subscribedChannels.clear();
|
|
2861
|
+
}
|
|
2862
|
+
/**
|
|
2863
|
+
* Check if connected to the realtime server
|
|
2864
|
+
*/
|
|
2865
|
+
get isConnected() {
|
|
2866
|
+
return this.socket?.connected ?? false;
|
|
2867
|
+
}
|
|
2868
|
+
/**
|
|
2869
|
+
* Get the current connection state
|
|
2870
|
+
*/
|
|
2871
|
+
get connectionState() {
|
|
2872
|
+
if (!this.socket) return "disconnected";
|
|
2873
|
+
if (this.socket.connected) return "connected";
|
|
2874
|
+
return "connecting";
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Get the socket ID (if connected)
|
|
2878
|
+
*/
|
|
2879
|
+
get socketId() {
|
|
2880
|
+
return this.socket?.id;
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Subscribe to a channel
|
|
2884
|
+
*
|
|
2885
|
+
* Automatically connects if not already connected.
|
|
2886
|
+
*
|
|
2887
|
+
* @param channel - Channel name (e.g., 'orders:123', 'broadcast')
|
|
2888
|
+
* @returns Promise with the subscription response
|
|
2889
|
+
*/
|
|
2890
|
+
async subscribe(channel) {
|
|
2891
|
+
if (this.subscribedChannels.has(channel)) {
|
|
2892
|
+
return { ok: true, channel };
|
|
2893
|
+
}
|
|
2894
|
+
if (!this.socket?.connected) {
|
|
2895
|
+
try {
|
|
2896
|
+
await this.connect();
|
|
2897
|
+
} catch (error) {
|
|
2898
|
+
const message = error instanceof Error ? error.message : "Connection failed";
|
|
2899
|
+
return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
return new Promise((resolve) => {
|
|
2903
|
+
this.socket.emit("realtime:subscribe", { channel }, (response) => {
|
|
2904
|
+
if (response.ok) {
|
|
2905
|
+
this.subscribedChannels.add(channel);
|
|
2906
|
+
}
|
|
2907
|
+
resolve(response);
|
|
2908
|
+
});
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
2911
|
+
/**
|
|
2912
|
+
* Unsubscribe from a channel (fire-and-forget)
|
|
2913
|
+
*
|
|
2914
|
+
* @param channel - Channel name to unsubscribe from
|
|
2915
|
+
*/
|
|
2916
|
+
unsubscribe(channel) {
|
|
2917
|
+
this.subscribedChannels.delete(channel);
|
|
2918
|
+
if (this.socket?.connected) {
|
|
2919
|
+
this.socket.emit("realtime:unsubscribe", { channel });
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
/**
|
|
2923
|
+
* Publish a message to a channel
|
|
2924
|
+
*
|
|
2925
|
+
* @param channel - Channel name
|
|
2926
|
+
* @param event - Event name
|
|
2927
|
+
* @param payload - Message payload
|
|
2928
|
+
*/
|
|
2929
|
+
async publish(channel, event, payload) {
|
|
2930
|
+
if (!this.socket?.connected) {
|
|
2931
|
+
throw new Error("Not connected to realtime server. Call connect() first.");
|
|
2932
|
+
}
|
|
2933
|
+
this.socket.emit("realtime:publish", { channel, event, payload });
|
|
2934
|
+
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Listen for events
|
|
2937
|
+
*
|
|
2938
|
+
* Reserved event names:
|
|
2939
|
+
* - 'connect' - Fired when connected to the server
|
|
2940
|
+
* - 'connect_error' - Fired when connection fails (payload: Error)
|
|
2941
|
+
* - 'disconnect' - Fired when disconnected (payload: reason string)
|
|
2942
|
+
* - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
|
|
2943
|
+
*
|
|
2944
|
+
* All other events receive a `SocketMessage` payload with metadata.
|
|
2945
|
+
*
|
|
2946
|
+
* @param event - Event name to listen for
|
|
2947
|
+
* @param callback - Callback function when event is received
|
|
2948
|
+
*/
|
|
2949
|
+
on(event, callback) {
|
|
2950
|
+
if (!this.eventListeners.has(event)) {
|
|
2951
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
2952
|
+
}
|
|
2953
|
+
this.eventListeners.get(event).add(callback);
|
|
2954
|
+
}
|
|
2955
|
+
/**
|
|
2956
|
+
* Remove a listener for a specific event
|
|
2957
|
+
*
|
|
2958
|
+
* @param event - Event name
|
|
2959
|
+
* @param callback - The callback function to remove
|
|
2960
|
+
*/
|
|
2961
|
+
off(event, callback) {
|
|
2962
|
+
const listeners = this.eventListeners.get(event);
|
|
2963
|
+
if (listeners) {
|
|
2964
|
+
listeners.delete(callback);
|
|
2965
|
+
if (listeners.size === 0) {
|
|
2966
|
+
this.eventListeners.delete(event);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Listen for an event only once, then automatically remove the listener
|
|
2972
|
+
*
|
|
2973
|
+
* @param event - Event name to listen for
|
|
2974
|
+
* @param callback - Callback function when event is received
|
|
2975
|
+
*/
|
|
2976
|
+
once(event, callback) {
|
|
2977
|
+
const wrapper = (payload) => {
|
|
2978
|
+
this.off(event, wrapper);
|
|
2979
|
+
callback(payload);
|
|
2980
|
+
};
|
|
2981
|
+
this.on(event, wrapper);
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* Get all currently subscribed channels
|
|
2985
|
+
*
|
|
2986
|
+
* @returns Array of channel names
|
|
2987
|
+
*/
|
|
2988
|
+
getSubscribedChannels() {
|
|
2989
|
+
return Array.from(this.subscribedChannels);
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
|
|
2993
|
+
// src/client.ts
|
|
2994
|
+
var InsForgeClient = class {
|
|
2995
|
+
constructor(config = {}) {
|
|
2996
|
+
this.http = new HttpClient(config);
|
|
2997
|
+
this.tokenManager = new TokenManager(config.storage);
|
|
2998
|
+
if (config.edgeFunctionToken) {
|
|
2999
|
+
this.http.setAuthToken(config.edgeFunctionToken);
|
|
3000
|
+
this.tokenManager.saveSession({
|
|
3001
|
+
accessToken: config.edgeFunctionToken,
|
|
3002
|
+
user: {}
|
|
3003
|
+
// Will be populated by getCurrentUser()
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
const existingSession = this.tokenManager.getSession();
|
|
3007
|
+
if (existingSession?.accessToken) {
|
|
3008
|
+
this.http.setAuthToken(existingSession.accessToken);
|
|
3009
|
+
}
|
|
3010
|
+
this.auth = new Auth(this.http, this.tokenManager);
|
|
3011
|
+
this.database = new Database(this.http, this.tokenManager);
|
|
3012
|
+
this.storage = new Storage(this.http);
|
|
3013
|
+
this.ai = new AI(this.http);
|
|
3014
|
+
this.functions = new Functions(this.http);
|
|
3015
|
+
this.realtime = new Realtime(this.http.baseUrl, this.tokenManager);
|
|
3016
|
+
}
|
|
3017
|
+
/**
|
|
3018
|
+
* Get the underlying HTTP client for custom requests
|
|
3019
|
+
*
|
|
3020
|
+
* @example
|
|
3021
|
+
* ```typescript
|
|
3022
|
+
* const httpClient = client.getHttpClient();
|
|
3023
|
+
* const customData = await httpClient.get('/api/custom-endpoint');
|
|
3024
|
+
* ```
|
|
3025
|
+
*/
|
|
3026
|
+
getHttpClient() {
|
|
3027
|
+
return this.http;
|
|
3028
|
+
}
|
|
3029
|
+
/**
|
|
3030
|
+
* Future modules will be added here:
|
|
3031
|
+
* - database: Database operations
|
|
3032
|
+
* - storage: File storage operations
|
|
3033
|
+
* - functions: Serverless functions
|
|
3034
|
+
* - tables: Table management
|
|
3035
|
+
* - metadata: Backend metadata
|
|
3036
|
+
*/
|
|
3037
|
+
};
|
|
3038
|
+
|
|
3039
|
+
// src/index.ts
|
|
3040
|
+
function createClient(config) {
|
|
3041
|
+
return new InsForgeClient(config);
|
|
3042
|
+
}
|
|
3043
|
+
var src_default = InsForgeClient;
|
|
3044
|
+
export {
|
|
3045
|
+
AI,
|
|
3046
|
+
Auth,
|
|
3047
|
+
Database,
|
|
3048
|
+
Functions,
|
|
3049
|
+
HttpClient,
|
|
3050
|
+
InsForgeClient,
|
|
3051
|
+
InsForgeError,
|
|
3052
|
+
Realtime,
|
|
3053
|
+
Storage,
|
|
3054
|
+
StorageBucket,
|
|
3055
|
+
TokenManager,
|
|
3056
|
+
createClient,
|
|
3057
|
+
src_default as default
|
|
3058
|
+
};
|
|
3059
|
+
//# sourceMappingURL=browser.mjs.map
|