@ezcoder.dev/sdk 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DatabaseProvider-DalP-KHC.d.ts +167 -0
- package/dist/analytics/index.d.ts +22 -1
- package/dist/analytics/index.js +79 -4
- package/dist/analytics/index.js.map +1 -1
- package/dist/animation/index.js.map +1 -1
- package/dist/auth/index.d.ts +2 -1
- package/dist/auth/index.js +25 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/chunk-CQKYANAW.js +44 -0
- package/dist/chunk-CQKYANAW.js.map +1 -0
- package/dist/{chunk-5XIZHBKE.js → chunk-HJ2EIZ4S.js} +23 -6
- package/dist/chunk-HJ2EIZ4S.js.map +1 -0
- package/dist/{chunk-G7XDUN3Z.js → chunk-I2YGB7Z6.js} +24 -44
- package/dist/chunk-I2YGB7Z6.js.map +1 -0
- package/dist/chunk-LIUE7M7K.js +72 -0
- package/dist/chunk-LIUE7M7K.js.map +1 -0
- package/dist/{chunk-YNDCD53D.js → chunk-QHB7LGCA.js} +123 -16
- package/dist/chunk-QHB7LGCA.js.map +1 -0
- package/dist/chunk-R4MDAYFO.js +642 -0
- package/dist/chunk-R4MDAYFO.js.map +1 -0
- package/dist/cms/index.js +4 -2
- package/dist/cms/index.js.map +1 -1
- package/dist/cron/index.d.ts +32 -0
- package/dist/cron/index.js +63 -0
- package/dist/cron/index.js.map +1 -0
- package/dist/database/index.d.ts +37 -0
- package/dist/database/index.js +32 -0
- package/dist/database/index.js.map +1 -0
- package/dist/email/index.d.ts +29 -0
- package/dist/email/index.js +60 -0
- package/dist/email/index.js.map +1 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +176 -3
- package/dist/index.js +270 -6
- package/dist/index.js.map +1 -1
- package/dist/notifications/index.d.ts +35 -1
- package/dist/notifications/index.js +61 -4
- package/dist/notifications/index.js.map +1 -1
- package/dist/payments/index.d.ts +11 -3
- package/dist/payments/index.js +101 -7
- package/dist/payments/index.js.map +1 -1
- package/dist/roles/index.js +6 -4
- package/dist/roles/index.js.map +1 -1
- package/dist/seo/index.js.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js +18 -9
- package/dist/storage/index.js.map +1 -1
- package/dist/{types-DtY5lp3P.d.ts → types-1uP3V_pe.d.ts} +5 -0
- package/package.json +120 -105
- package/dist/chunk-5XIZHBKE.js.map +0 -1
- package/dist/chunk-G7XDUN3Z.js.map +0 -1
- package/dist/chunk-YNDCD53D.js.map +0 -1
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ezcoder
|
|
3
|
+
} from "./chunk-HJ2EIZ4S.js";
|
|
4
|
+
import {
|
|
5
|
+
isSupabaseConfigured,
|
|
6
|
+
supabase
|
|
7
|
+
} from "./chunk-I2YGB7Z6.js";
|
|
8
|
+
import {
|
|
9
|
+
env,
|
|
10
|
+
features
|
|
11
|
+
} from "./chunk-LIUE7M7K.js";
|
|
12
|
+
|
|
13
|
+
// src/database/DatabaseProvider.tsx
|
|
14
|
+
import { createContext, useContext, useMemo } from "react";
|
|
15
|
+
|
|
16
|
+
// src/database/errors.ts
|
|
17
|
+
var DatabaseError = class extends Error {
|
|
18
|
+
constructor(message, code = "DATABASE_ERROR", statusCode = 500) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "DatabaseError";
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.statusCode = statusCode;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var QueryError = class extends DatabaseError {
|
|
26
|
+
constructor(message, sql) {
|
|
27
|
+
super(message, "QUERY_ERROR", 400);
|
|
28
|
+
this.name = "QueryError";
|
|
29
|
+
this.sql = sql;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var ValidationError = class extends DatabaseError {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message, "VALIDATION_ERROR", 422);
|
|
35
|
+
this.name = "ValidationError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var ConnectionError = class extends DatabaseError {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message, "CONNECTION_ERROR", 503);
|
|
41
|
+
this.name = "ConnectionError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var NotFoundError = class extends DatabaseError {
|
|
45
|
+
constructor(message = "Record not found") {
|
|
46
|
+
super(message, "NOT_FOUND", 404);
|
|
47
|
+
this.name = "NotFoundError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function mapRpcError(rpcResult) {
|
|
51
|
+
if (rpcResult.success) return null;
|
|
52
|
+
const msg = rpcResult.error || "Unknown database error";
|
|
53
|
+
if (msg.includes("No database schema exists")) return new NotFoundError(msg);
|
|
54
|
+
if (msg.includes("Authentication required")) return new ConnectionError(msg);
|
|
55
|
+
if (msg.includes("blocked") || msg.includes("forbidden")) return new ValidationError(msg);
|
|
56
|
+
return new QueryError(msg);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/database/QueryBuilder.ts
|
|
60
|
+
function escapeSqlValue(value) {
|
|
61
|
+
if (value === null) return "NULL";
|
|
62
|
+
if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
|
63
|
+
if (typeof value === "number") {
|
|
64
|
+
if (!Number.isFinite(value)) throw new ValidationError("Non-finite numbers are not allowed");
|
|
65
|
+
return String(value);
|
|
66
|
+
}
|
|
67
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
68
|
+
}
|
|
69
|
+
function escapeIdentifier(name) {
|
|
70
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
71
|
+
throw new ValidationError(`Invalid identifier: ${name}`);
|
|
72
|
+
}
|
|
73
|
+
return `"${name}"`;
|
|
74
|
+
}
|
|
75
|
+
var QueryBuilder = class {
|
|
76
|
+
constructor(supabase2, projectId, table, serverProxy) {
|
|
77
|
+
this.operation = "select";
|
|
78
|
+
this.selectColumns = "*";
|
|
79
|
+
this.filters = [];
|
|
80
|
+
this.orderClauses = [];
|
|
81
|
+
this.limitValue = null;
|
|
82
|
+
this.offsetValue = null;
|
|
83
|
+
this.insertData = null;
|
|
84
|
+
this.updateData = null;
|
|
85
|
+
this.upsertConflict = null;
|
|
86
|
+
this.singleMode = false;
|
|
87
|
+
this.maybeSingleMode = false;
|
|
88
|
+
this.supabase = supabase2;
|
|
89
|
+
this.projectId = projectId;
|
|
90
|
+
this.table = table;
|
|
91
|
+
this.serverProxy = serverProxy;
|
|
92
|
+
}
|
|
93
|
+
select(columns = "*") {
|
|
94
|
+
this.operation = "select";
|
|
95
|
+
this.selectColumns = columns;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
// --- Filters ---
|
|
99
|
+
eq(column, value) {
|
|
100
|
+
this.filters.push({ column, op: "=", value });
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
neq(column, value) {
|
|
104
|
+
this.filters.push({ column, op: "!=", value });
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
gt(column, value) {
|
|
108
|
+
this.filters.push({ column, op: ">", value });
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
lt(column, value) {
|
|
112
|
+
this.filters.push({ column, op: "<", value });
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
gte(column, value) {
|
|
116
|
+
this.filters.push({ column, op: ">=", value });
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
lte(column, value) {
|
|
120
|
+
this.filters.push({ column, op: "<=", value });
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
like(column, pattern) {
|
|
124
|
+
this.filters.push({ column, op: "LIKE", value: pattern });
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
ilike(column, pattern) {
|
|
128
|
+
this.filters.push({ column, op: "ILIKE", value: pattern });
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
in(column, values) {
|
|
132
|
+
this.filters.push({ column, op: "IN", value: values });
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
is(column, value) {
|
|
136
|
+
this.filters.push({ column, op: "IS", value });
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
// --- Ordering & Pagination ---
|
|
140
|
+
order(column, options = {}) {
|
|
141
|
+
const dir = options.ascending === false ? "DESC" : "ASC";
|
|
142
|
+
const nulls = options.nullsFirst ? "NULLS FIRST" : "NULLS LAST";
|
|
143
|
+
this.orderClauses.push(`${escapeIdentifier(column)} ${dir} ${nulls}`);
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
limit(count) {
|
|
147
|
+
this.limitValue = count;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
offset(count) {
|
|
151
|
+
this.offsetValue = count;
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
// --- Mutations ---
|
|
155
|
+
insert(records) {
|
|
156
|
+
this.operation = "insert";
|
|
157
|
+
this.insertData = Array.isArray(records) ? records : [records];
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
update(values) {
|
|
161
|
+
this.operation = "update";
|
|
162
|
+
this.updateData = values;
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
delete() {
|
|
166
|
+
this.operation = "delete";
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
upsert(records, options) {
|
|
170
|
+
this.operation = "upsert";
|
|
171
|
+
this.insertData = Array.isArray(records) ? records : [records];
|
|
172
|
+
this.upsertConflict = options?.onConflict || null;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
// --- Result Helpers ---
|
|
176
|
+
count() {
|
|
177
|
+
this.operation = "count";
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
single() {
|
|
181
|
+
this.singleMode = true;
|
|
182
|
+
this.limitValue = 1;
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
maybeSingle() {
|
|
186
|
+
this.maybeSingleMode = true;
|
|
187
|
+
this.limitValue = 1;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
// --- SQL Building ---
|
|
191
|
+
buildWhereClause() {
|
|
192
|
+
if (this.filters.length === 0) return "";
|
|
193
|
+
const conditions = this.filters.map((f) => {
|
|
194
|
+
const col = escapeIdentifier(f.column);
|
|
195
|
+
if (f.op === "IS") {
|
|
196
|
+
const val = f.value === null ? "NULL" : f.value ? "TRUE" : "FALSE";
|
|
197
|
+
return `${col} IS ${val}`;
|
|
198
|
+
}
|
|
199
|
+
if (f.op === "IN") {
|
|
200
|
+
const vals = f.value.map(escapeSqlValue).join(", ");
|
|
201
|
+
return `${col} IN (${vals})`;
|
|
202
|
+
}
|
|
203
|
+
return `${col} ${f.op} ${escapeSqlValue(f.value)}`;
|
|
204
|
+
});
|
|
205
|
+
return ` WHERE ${conditions.join(" AND ")}`;
|
|
206
|
+
}
|
|
207
|
+
buildSelectSql() {
|
|
208
|
+
let sql = `SELECT ${this.selectColumns} FROM "${this.table}"`;
|
|
209
|
+
sql += this.buildWhereClause();
|
|
210
|
+
if (this.orderClauses.length > 0) sql += ` ORDER BY ${this.orderClauses.join(", ")}`;
|
|
211
|
+
if (this.limitValue !== null) sql += ` LIMIT ${this.limitValue}`;
|
|
212
|
+
if (this.offsetValue !== null && this.offsetValue > 0) sql += ` OFFSET ${this.offsetValue}`;
|
|
213
|
+
return sql;
|
|
214
|
+
}
|
|
215
|
+
buildCountSql() {
|
|
216
|
+
let sql = `SELECT COUNT(*) as count FROM "${this.table}"`;
|
|
217
|
+
sql += this.buildWhereClause();
|
|
218
|
+
return sql;
|
|
219
|
+
}
|
|
220
|
+
buildInsertSql() {
|
|
221
|
+
if (!this.insertData || this.insertData.length === 0) {
|
|
222
|
+
throw new ValidationError("No data provided for insert");
|
|
223
|
+
}
|
|
224
|
+
const columns = Object.keys(this.insertData[0]);
|
|
225
|
+
const colList = columns.map(escapeIdentifier).join(", ");
|
|
226
|
+
const rows = this.insertData.map(
|
|
227
|
+
(row) => `(${columns.map((c) => escapeSqlValue(row[c] ?? null)).join(", ")})`
|
|
228
|
+
);
|
|
229
|
+
return `INSERT INTO "${this.table}" (${colList}) VALUES ${rows.join(", ")}`;
|
|
230
|
+
}
|
|
231
|
+
buildUpsertSql() {
|
|
232
|
+
let sql = this.buildInsertSql();
|
|
233
|
+
const conflict = this.upsertConflict || "id";
|
|
234
|
+
const columns = Object.keys(this.insertData[0]);
|
|
235
|
+
const updateCols = columns.filter((c) => c !== conflict).map((c) => `${escapeIdentifier(c)} = EXCLUDED.${escapeIdentifier(c)}`).join(", ");
|
|
236
|
+
sql += ` ON CONFLICT (${escapeIdentifier(conflict)}) DO UPDATE SET ${updateCols}`;
|
|
237
|
+
return sql;
|
|
238
|
+
}
|
|
239
|
+
buildUpdateSql() {
|
|
240
|
+
if (!this.updateData || Object.keys(this.updateData).length === 0) {
|
|
241
|
+
throw new ValidationError("No data provided for update");
|
|
242
|
+
}
|
|
243
|
+
if (this.filters.length === 0) {
|
|
244
|
+
throw new ValidationError("UPDATE without filters is not allowed \u2014 add .eq() or other filters");
|
|
245
|
+
}
|
|
246
|
+
const setClauses = Object.entries(this.updateData).map(([col, val]) => `${escapeIdentifier(col)} = ${escapeSqlValue(val)}`).join(", ");
|
|
247
|
+
return `UPDATE "${this.table}" SET ${setClauses}${this.buildWhereClause()}`;
|
|
248
|
+
}
|
|
249
|
+
buildDeleteSql() {
|
|
250
|
+
if (this.filters.length === 0) {
|
|
251
|
+
throw new ValidationError("DELETE without filters is not allowed \u2014 add .eq() or other filters");
|
|
252
|
+
}
|
|
253
|
+
return `DELETE FROM "${this.table}"${this.buildWhereClause()}`;
|
|
254
|
+
}
|
|
255
|
+
// --- Execution ---
|
|
256
|
+
async executeQuery() {
|
|
257
|
+
const isRead = this.operation === "select" || this.operation === "count";
|
|
258
|
+
let sql;
|
|
259
|
+
switch (this.operation) {
|
|
260
|
+
case "select":
|
|
261
|
+
sql = this.buildSelectSql();
|
|
262
|
+
break;
|
|
263
|
+
case "count":
|
|
264
|
+
sql = this.buildCountSql();
|
|
265
|
+
break;
|
|
266
|
+
case "insert":
|
|
267
|
+
sql = this.buildInsertSql();
|
|
268
|
+
break;
|
|
269
|
+
case "upsert":
|
|
270
|
+
sql = this.buildUpsertSql();
|
|
271
|
+
break;
|
|
272
|
+
case "update":
|
|
273
|
+
sql = this.buildUpdateSql();
|
|
274
|
+
break;
|
|
275
|
+
case "delete":
|
|
276
|
+
sql = this.buildDeleteSql();
|
|
277
|
+
break;
|
|
278
|
+
default:
|
|
279
|
+
throw new QueryError(`Unknown operation: ${this.operation}`);
|
|
280
|
+
}
|
|
281
|
+
if (this.serverProxy) {
|
|
282
|
+
const fn = isRead ? this.serverProxy.serverQuery : this.serverProxy.serverExec;
|
|
283
|
+
const data2 = await fn(sql);
|
|
284
|
+
if (!data2.success) {
|
|
285
|
+
const dbError = mapRpcError(data2);
|
|
286
|
+
if (dbError) throw dbError;
|
|
287
|
+
throw new QueryError(data2.error || "Query failed", sql);
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
success: true,
|
|
291
|
+
data: isRead ? data2.data || [] : [],
|
|
292
|
+
rowCount: data2.rowCount ?? (isRead ? data2.data?.length || 0 : 0),
|
|
293
|
+
schema: data2.schema
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const rpcName = isRead ? "sdk_query_project" : "sdk_exec_project";
|
|
297
|
+
const { data, error } = await this.supabase.rpc(rpcName, {
|
|
298
|
+
p_project_id: this.projectId,
|
|
299
|
+
p_sql: sql
|
|
300
|
+
});
|
|
301
|
+
if (error) {
|
|
302
|
+
throw new QueryError(error.message, sql);
|
|
303
|
+
}
|
|
304
|
+
if (!data.success) {
|
|
305
|
+
const dbError = mapRpcError(data);
|
|
306
|
+
if (dbError) throw dbError;
|
|
307
|
+
throw new QueryError(data.error || "Query failed", sql);
|
|
308
|
+
}
|
|
309
|
+
if (isRead) {
|
|
310
|
+
return {
|
|
311
|
+
success: true,
|
|
312
|
+
data: data.data || [],
|
|
313
|
+
rowCount: data.rowCount ?? (data.data?.length || 0),
|
|
314
|
+
schema: data.schema
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
success: true,
|
|
319
|
+
data: [],
|
|
320
|
+
rowCount: data.rowCount ?? 0,
|
|
321
|
+
schema: data.schema
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
then(onfulfilled, onrejected) {
|
|
325
|
+
const promise = this.executeQuery().then((result) => {
|
|
326
|
+
if (this.singleMode) {
|
|
327
|
+
if (result.data.length === 0) {
|
|
328
|
+
throw new QueryError("Expected exactly one row, got 0");
|
|
329
|
+
}
|
|
330
|
+
return { success: true, data: result.data[0], error: void 0 };
|
|
331
|
+
}
|
|
332
|
+
if (this.maybeSingleMode) {
|
|
333
|
+
return {
|
|
334
|
+
success: true,
|
|
335
|
+
data: result.data.length > 0 ? result.data[0] : null,
|
|
336
|
+
error: void 0
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
});
|
|
341
|
+
return promise.then(onfulfilled, onrejected);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// src/database/DatabaseClient.ts
|
|
346
|
+
function trackDbEvent(event, props) {
|
|
347
|
+
if (!features.analytics) return;
|
|
348
|
+
try {
|
|
349
|
+
ezcoder.analytics.track(event, props);
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
var MAX_RETRIES = 3;
|
|
354
|
+
var SERVER_TIMEOUT_MS = 5e3;
|
|
355
|
+
var DatabaseClient = class _DatabaseClient {
|
|
356
|
+
constructor(supabase2, projectId) {
|
|
357
|
+
this.serverMode = false;
|
|
358
|
+
this.supabase = supabase2;
|
|
359
|
+
this.projectId = projectId;
|
|
360
|
+
}
|
|
361
|
+
static createServerClient(projectId, apiKey, platformUrl) {
|
|
362
|
+
const client = new _DatabaseClient(null, projectId);
|
|
363
|
+
client.apiKey = apiKey;
|
|
364
|
+
client.platformUrl = platformUrl || typeof process !== "undefined" && process.env?.EZCODER_API_URL || "https://ezcoder.app";
|
|
365
|
+
client.serverMode = true;
|
|
366
|
+
return client;
|
|
367
|
+
}
|
|
368
|
+
from(table) {
|
|
369
|
+
if (this.serverMode) {
|
|
370
|
+
return new QueryBuilder(null, this.projectId, table, {
|
|
371
|
+
serverQuery: (sql) => this._serverRequest("/query", sql),
|
|
372
|
+
serverExec: (sql) => this._serverRequest("/execute", sql)
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return new QueryBuilder(this.supabase, this.projectId, table);
|
|
376
|
+
}
|
|
377
|
+
async sql(query) {
|
|
378
|
+
const trimmed = query.trim().toLowerCase();
|
|
379
|
+
if (!trimmed.startsWith("select")) {
|
|
380
|
+
throw new QueryError("sql() is for read-only queries. Use execute() for mutations.");
|
|
381
|
+
}
|
|
382
|
+
if (this.serverMode) {
|
|
383
|
+
return this._serverQuery(query);
|
|
384
|
+
}
|
|
385
|
+
const start = Date.now();
|
|
386
|
+
const { data, error } = await this.supabase.rpc("sdk_query_project", {
|
|
387
|
+
p_project_id: this.projectId,
|
|
388
|
+
p_sql: query
|
|
389
|
+
});
|
|
390
|
+
const latencyMs = Date.now() - start;
|
|
391
|
+
if (error) {
|
|
392
|
+
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: false, error: error.message });
|
|
393
|
+
throw new QueryError(error.message, query);
|
|
394
|
+
}
|
|
395
|
+
if (!data.success) {
|
|
396
|
+
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: false, error: data.error });
|
|
397
|
+
throw new QueryError(data.error || "Query failed", query);
|
|
398
|
+
}
|
|
399
|
+
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: true, rowCount: data.rowCount ?? 0 });
|
|
400
|
+
return {
|
|
401
|
+
success: true,
|
|
402
|
+
data: data.data || [],
|
|
403
|
+
rowCount: data.rowCount ?? (data.data?.length || 0),
|
|
404
|
+
schema: data.schema
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
async execute(sql) {
|
|
408
|
+
if (this.serverMode) {
|
|
409
|
+
return this._serverExecute(sql);
|
|
410
|
+
}
|
|
411
|
+
const start = Date.now();
|
|
412
|
+
const { data, error } = await this.supabase.rpc("sdk_exec_project", {
|
|
413
|
+
p_project_id: this.projectId,
|
|
414
|
+
p_sql: sql
|
|
415
|
+
});
|
|
416
|
+
const latencyMs = Date.now() - start;
|
|
417
|
+
if (error) {
|
|
418
|
+
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: false, error: error.message });
|
|
419
|
+
throw new QueryError(error.message, sql);
|
|
420
|
+
}
|
|
421
|
+
if (!data.success) {
|
|
422
|
+
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: false, error: data.error });
|
|
423
|
+
throw new QueryError(data.error || "Execution failed", sql);
|
|
424
|
+
}
|
|
425
|
+
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: true, rowCount: data.rowCount ?? 0 });
|
|
426
|
+
return {
|
|
427
|
+
success: true,
|
|
428
|
+
rowCount: data.rowCount ?? 0,
|
|
429
|
+
schema: data.schema,
|
|
430
|
+
executed_sql: data.executed_sql
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
async getSchema() {
|
|
434
|
+
if (this.serverMode) {
|
|
435
|
+
return this._serverGetSchema();
|
|
436
|
+
}
|
|
437
|
+
const { data, error } = await this.supabase.rpc("sdk_get_schema_info", {
|
|
438
|
+
p_project_id: this.projectId
|
|
439
|
+
});
|
|
440
|
+
if (error) throw new ConnectionError(error.message);
|
|
441
|
+
return {
|
|
442
|
+
exists: data.exists ?? false,
|
|
443
|
+
schema: data.schema,
|
|
444
|
+
tables: data.tables || [],
|
|
445
|
+
tablesCount: data.tablesCount ?? 0,
|
|
446
|
+
createdAt: data.createdAt,
|
|
447
|
+
lastAccessedAt: data.lastAccessedAt
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
async _serverRequest(endpoint, sql) {
|
|
451
|
+
const url = `${this.platformUrl}/api/platform/db/${this.projectId}${endpoint}`;
|
|
452
|
+
let lastError;
|
|
453
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
454
|
+
try {
|
|
455
|
+
const res = await fetch(url, {
|
|
456
|
+
method: "POST",
|
|
457
|
+
headers: {
|
|
458
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
459
|
+
"Content-Type": "application/json"
|
|
460
|
+
},
|
|
461
|
+
body: JSON.stringify({ sql }),
|
|
462
|
+
signal: AbortSignal.timeout(SERVER_TIMEOUT_MS)
|
|
463
|
+
});
|
|
464
|
+
if (res.status === 429) {
|
|
465
|
+
const retryAfter = parseInt(res.headers.get("retry-after") || "60", 10);
|
|
466
|
+
await new Promise((r) => setTimeout(r, retryAfter * 1e3));
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (res.status === 503) {
|
|
470
|
+
await new Promise((r) => setTimeout(r, (attempt + 1) * 2e3));
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
return await res.json();
|
|
474
|
+
} catch (e) {
|
|
475
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return { success: false, error: lastError?.message || "Request failed after retries" };
|
|
479
|
+
}
|
|
480
|
+
async _serverQuery(query) {
|
|
481
|
+
const start = Date.now();
|
|
482
|
+
const result = await this._serverRequest("/query", query);
|
|
483
|
+
const latencyMs = Date.now() - start;
|
|
484
|
+
if (!result.success) {
|
|
485
|
+
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: false, error: result.error, serverMode: true });
|
|
486
|
+
throw new QueryError(result.error || "Query failed", query);
|
|
487
|
+
}
|
|
488
|
+
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0, serverMode: true });
|
|
489
|
+
return {
|
|
490
|
+
success: true,
|
|
491
|
+
data: result.data || [],
|
|
492
|
+
rowCount: result.rowCount ?? (result.data?.length || 0),
|
|
493
|
+
schema: result.schema
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
async _serverExecute(sql) {
|
|
497
|
+
const start = Date.now();
|
|
498
|
+
const result = await this._serverRequest("/execute", sql);
|
|
499
|
+
const latencyMs = Date.now() - start;
|
|
500
|
+
if (!result.success) {
|
|
501
|
+
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: false, error: result.error, serverMode: true });
|
|
502
|
+
throw new QueryError(result.error || "Execution failed", sql);
|
|
503
|
+
}
|
|
504
|
+
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0, serverMode: true });
|
|
505
|
+
return {
|
|
506
|
+
success: true,
|
|
507
|
+
rowCount: result.rowCount ?? 0,
|
|
508
|
+
schema: result.schema,
|
|
509
|
+
executed_sql: result.executed_sql
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
async _serverGetSchema() {
|
|
513
|
+
const url = `${this.platformUrl}/api/platform/db/${this.projectId}/schema`;
|
|
514
|
+
let lastError;
|
|
515
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
516
|
+
try {
|
|
517
|
+
const res = await fetch(url, {
|
|
518
|
+
method: "GET",
|
|
519
|
+
headers: { "Authorization": `Bearer ${this.apiKey}` },
|
|
520
|
+
signal: AbortSignal.timeout(SERVER_TIMEOUT_MS)
|
|
521
|
+
});
|
|
522
|
+
if (res.status === 503) {
|
|
523
|
+
await new Promise((r) => setTimeout(r, (attempt + 1) * 2e3));
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const data = await res.json();
|
|
527
|
+
if (!data.success) throw new ConnectionError(data.error || "Schema fetch failed");
|
|
528
|
+
return {
|
|
529
|
+
exists: data.exists ?? false,
|
|
530
|
+
schema: data.schema,
|
|
531
|
+
tables: data.tables || [],
|
|
532
|
+
tablesCount: data.tablesCount ?? 0,
|
|
533
|
+
createdAt: data.createdAt,
|
|
534
|
+
lastAccessedAt: data.lastAccessedAt
|
|
535
|
+
};
|
|
536
|
+
} catch (e) {
|
|
537
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
throw new ConnectionError(lastError?.message || "Schema request failed after retries");
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// src/database/DatabaseProvider.tsx
|
|
545
|
+
import { jsx } from "react/jsx-runtime";
|
|
546
|
+
var DatabaseContext = createContext({
|
|
547
|
+
db: null,
|
|
548
|
+
isConfigured: false
|
|
549
|
+
});
|
|
550
|
+
function DatabaseProvider({ children, projectId }) {
|
|
551
|
+
const resolvedProjectId = projectId || env.EZC_PROJECT_ID;
|
|
552
|
+
const configured = isSupabaseConfigured && Boolean(resolvedProjectId);
|
|
553
|
+
const db = useMemo(
|
|
554
|
+
() => configured ? new DatabaseClient(supabase, resolvedProjectId) : null,
|
|
555
|
+
[configured, resolvedProjectId]
|
|
556
|
+
);
|
|
557
|
+
const value = useMemo(() => ({ db, isConfigured: configured }), [db, configured]);
|
|
558
|
+
return /* @__PURE__ */ jsx(DatabaseContext.Provider, { value, children });
|
|
559
|
+
}
|
|
560
|
+
function useDatabase() {
|
|
561
|
+
const { db } = useContext(DatabaseContext);
|
|
562
|
+
if (!db) {
|
|
563
|
+
throw new Error(
|
|
564
|
+
"useDatabase() must be used within <DatabaseProvider>. Ensure EZC_PROJECT_ID and Supabase credentials are configured."
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
return db;
|
|
568
|
+
}
|
|
569
|
+
function useDatabaseOptional() {
|
|
570
|
+
const { db } = useContext(DatabaseContext);
|
|
571
|
+
return db;
|
|
572
|
+
}
|
|
573
|
+
function useIsDatabaseConfigured() {
|
|
574
|
+
const { isConfigured } = useContext(DatabaseContext);
|
|
575
|
+
return isConfigured;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/database/useRealtime.ts
|
|
579
|
+
import { useEffect, useState, useRef } from "react";
|
|
580
|
+
function useRealtime(table, options = {}) {
|
|
581
|
+
const [data, setData] = useState([]);
|
|
582
|
+
const [status, setStatus] = useState("connecting");
|
|
583
|
+
const optionsRef = useRef(options);
|
|
584
|
+
optionsRef.current = options;
|
|
585
|
+
const projectId = env.EZC_PROJECT_ID;
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
if (!isSupabaseConfigured || !projectId) {
|
|
588
|
+
setStatus("disabled");
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const schemaName = `proj_${projectId.replace(/-/g, "_")}`;
|
|
592
|
+
const channelName = `${schemaName}_${table}_${optionsRef.current.event || "all"}`;
|
|
593
|
+
const channel = supabase.channel(channelName).on(
|
|
594
|
+
"postgres_changes",
|
|
595
|
+
{
|
|
596
|
+
event: optionsRef.current.event || "*",
|
|
597
|
+
schema: schemaName,
|
|
598
|
+
table,
|
|
599
|
+
filter: optionsRef.current.filter
|
|
600
|
+
},
|
|
601
|
+
(payload) => {
|
|
602
|
+
if (payload.eventType === "INSERT") {
|
|
603
|
+
setData((prev) => [...prev, payload.new]);
|
|
604
|
+
} else if (payload.eventType === "UPDATE") {
|
|
605
|
+
setData(
|
|
606
|
+
(prev) => prev.map(
|
|
607
|
+
(item) => item.id === payload.new.id ? payload.new : item
|
|
608
|
+
)
|
|
609
|
+
);
|
|
610
|
+
} else if (payload.eventType === "DELETE") {
|
|
611
|
+
setData(
|
|
612
|
+
(prev) => prev.filter(
|
|
613
|
+
(item) => item.id !== payload.old.id
|
|
614
|
+
)
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
).subscribe((subStatus) => {
|
|
619
|
+
setStatus(subStatus === "SUBSCRIBED" ? "connected" : "connecting");
|
|
620
|
+
});
|
|
621
|
+
return () => {
|
|
622
|
+
supabase.removeChannel(channel);
|
|
623
|
+
};
|
|
624
|
+
}, [projectId, table]);
|
|
625
|
+
return { data, status, setData };
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export {
|
|
629
|
+
DatabaseError,
|
|
630
|
+
QueryError,
|
|
631
|
+
ValidationError,
|
|
632
|
+
ConnectionError,
|
|
633
|
+
NotFoundError,
|
|
634
|
+
QueryBuilder,
|
|
635
|
+
DatabaseClient,
|
|
636
|
+
DatabaseProvider,
|
|
637
|
+
useDatabase,
|
|
638
|
+
useDatabaseOptional,
|
|
639
|
+
useIsDatabaseConfigured,
|
|
640
|
+
useRealtime
|
|
641
|
+
};
|
|
642
|
+
//# sourceMappingURL=chunk-R4MDAYFO.js.map
|