@ezcoder.dev/sdk 1.3.2 → 1.4.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-DaBP5XUs.d.ts → DatabaseProvider-BtYMJRqh.d.ts} +34 -20
- package/dist/analytics/index.js +4 -4
- package/dist/auth/index.d.ts +28 -1
- package/dist/auth/index.js +21 -4
- package/dist/auth/index.js.map +1 -1
- package/dist/{chunk-CQKYANAW.js → chunk-4WGGFJPE.js} +2 -2
- package/dist/{chunk-UAY64NNA.js → chunk-5QYGP7G7.js} +157 -277
- package/dist/chunk-5QYGP7G7.js.map +1 -0
- package/dist/{chunk-HJ2EIZ4S.js → chunk-DU6N3HVQ.js} +2 -2
- package/dist/{chunk-I2YGB7Z6.js → chunk-KKTY5NCR.js} +2 -2
- package/dist/{chunk-QHB7LGCA.js → chunk-NTA3RMYR.js} +231 -5
- package/dist/chunk-NTA3RMYR.js.map +1 -0
- package/dist/{chunk-LIUE7M7K.js → chunk-X4JP7DCK.js} +11 -2
- package/dist/chunk-X4JP7DCK.js.map +1 -0
- package/dist/cms/index.js +2 -2
- package/dist/config-D5ajnLCe.d.ts +29 -0
- package/dist/cron/index.js +2 -2
- package/dist/database/index.d.ts +1 -1
- package/dist/database/index.js +4 -4
- package/dist/email/index.js +2 -2
- package/dist/index.d.ts +45 -27
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/notifications/index.js +5 -5
- package/dist/payments/index.js +4 -4
- package/dist/roles/index.js +4 -4
- package/dist/storage/index.js +4 -4
- package/package.json +9 -6
- package/dist/chunk-LIUE7M7K.js.map +0 -1
- package/dist/chunk-QHB7LGCA.js.map +0 -1
- package/dist/chunk-UAY64NNA.js.map +0 -1
- /package/dist/{chunk-CQKYANAW.js.map → chunk-4WGGFJPE.js.map} +0 -0
- /package/dist/{chunk-HJ2EIZ4S.js.map → chunk-DU6N3HVQ.js.map} +0 -0
- /package/dist/{chunk-I2YGB7Z6.js.map → chunk-KKTY5NCR.js.map} +0 -0
|
@@ -1,17 +1,56 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ezcoder
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DU6N3HVQ.js";
|
|
4
4
|
import {
|
|
5
5
|
isSupabaseConfigured,
|
|
6
6
|
supabase
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-KKTY5NCR.js";
|
|
8
8
|
import {
|
|
9
9
|
env,
|
|
10
10
|
features
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-X4JP7DCK.js";
|
|
12
12
|
|
|
13
13
|
// src/database/DatabaseProvider.tsx
|
|
14
|
-
import { createContext, useContext, useMemo } from "react";
|
|
14
|
+
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
15
|
+
|
|
16
|
+
// src/core/bootstrap.ts
|
|
17
|
+
var cached = null;
|
|
18
|
+
var inflight = null;
|
|
19
|
+
function apiBase() {
|
|
20
|
+
return (env.EZCODER_API_URL || "https://ezcoder.dev").replace(/\/$/, "");
|
|
21
|
+
}
|
|
22
|
+
async function loadBootstrap(opts = {}) {
|
|
23
|
+
if (cached && !opts.force) return cached;
|
|
24
|
+
if (inflight && !opts.force) return inflight;
|
|
25
|
+
if (typeof fetch === "undefined") return null;
|
|
26
|
+
inflight = (async () => {
|
|
27
|
+
try {
|
|
28
|
+
const headers = {};
|
|
29
|
+
const tok = env.EZC_PROJECT_TOKEN_PUBLIC || env.EZC_PROJECT_TOKEN_LEGACY || "";
|
|
30
|
+
if (tok) headers["X-EzCoder-Public-Token"] = tok;
|
|
31
|
+
const qs = env.EZC_PROJECT_ID ? `?projectId=${encodeURIComponent(env.EZC_PROJECT_ID)}` : "";
|
|
32
|
+
const res = await fetch(`${apiBase()}/api/sdk/bootstrap${qs}`, { method: "GET", headers });
|
|
33
|
+
if (!res.ok) return null;
|
|
34
|
+
const body = await res.json().catch(() => null);
|
|
35
|
+
if (!body?.success || !body.config?.projectId) return null;
|
|
36
|
+
cached = body.config;
|
|
37
|
+
return cached;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
} finally {
|
|
41
|
+
inflight = null;
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
44
|
+
return inflight;
|
|
45
|
+
}
|
|
46
|
+
function getRuntimeConfig() {
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
function configValue(key, bakedFallback) {
|
|
50
|
+
const rc = cached;
|
|
51
|
+
const v = rc ? rc[key] : null;
|
|
52
|
+
return typeof v === "string" && v ? v : bakedFallback;
|
|
53
|
+
}
|
|
15
54
|
|
|
16
55
|
// src/database/errors.ts
|
|
17
56
|
var DatabaseError = class extends Error {
|
|
@@ -56,58 +95,6 @@ function mapRpcError(rpcResult) {
|
|
|
56
95
|
return new QueryError(msg);
|
|
57
96
|
}
|
|
58
97
|
|
|
59
|
-
// src/database/publicReadProxy.ts
|
|
60
|
-
var PROXY_TIMEOUT_MS = 1e4;
|
|
61
|
-
var AUTH_FAILURE_PATTERN = /authentication required|permission denied|project binding|unauthorized|jwt|42501/i;
|
|
62
|
-
function isAuthShapedFailure(message) {
|
|
63
|
-
return typeof message === "string" && AUTH_FAILURE_PATTERN.test(message);
|
|
64
|
-
}
|
|
65
|
-
function publicReadToken() {
|
|
66
|
-
return env.EZC_PROJECT_TOKEN_PUBLIC || env.EZC_PROJECT_TOKEN_LEGACY || "";
|
|
67
|
-
}
|
|
68
|
-
function publicReadAvailable() {
|
|
69
|
-
return Boolean(env.EZCODER_API_URL);
|
|
70
|
-
}
|
|
71
|
-
var stickyPublicMode = false;
|
|
72
|
-
function shouldSkipRpc() {
|
|
73
|
-
return stickyPublicMode && publicReadAvailable();
|
|
74
|
-
}
|
|
75
|
-
async function hasActiveSession(supabase2) {
|
|
76
|
-
try {
|
|
77
|
-
if (!supabase2?.auth?.getSession) return false;
|
|
78
|
-
const { data } = await supabase2.auth.getSession();
|
|
79
|
-
return Boolean(data?.session);
|
|
80
|
-
} catch {
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
async function publicReadQuery(projectId, sql) {
|
|
85
|
-
if (!publicReadAvailable()) {
|
|
86
|
-
return { success: false, error: "Public read proxy not configured (missing public token or API URL)" };
|
|
87
|
-
}
|
|
88
|
-
try {
|
|
89
|
-
const base = env.EZCODER_API_URL.replace(/\/$/, "");
|
|
90
|
-
const token = publicReadToken();
|
|
91
|
-
const headers = { "Content-Type": "application/json" };
|
|
92
|
-
if (token) headers["X-EzCoder-Public-Token"] = token;
|
|
93
|
-
const res = await fetch(`${base}/api/platform/db/${encodeURIComponent(projectId)}/query`, {
|
|
94
|
-
method: "POST",
|
|
95
|
-
headers,
|
|
96
|
-
body: JSON.stringify({ sql }),
|
|
97
|
-
signal: typeof AbortSignal !== "undefined" && AbortSignal.timeout ? AbortSignal.timeout(PROXY_TIMEOUT_MS) : void 0
|
|
98
|
-
});
|
|
99
|
-
const payload = await res.json().catch(() => null);
|
|
100
|
-
if (!res.ok || !payload) {
|
|
101
|
-
return { success: false, error: payload?.error || `Public read proxy HTTP ${res.status}` };
|
|
102
|
-
}
|
|
103
|
-
if (payload.success) stickyPublicMode = true;
|
|
104
|
-
return payload;
|
|
105
|
-
} catch (err) {
|
|
106
|
-
const message = err instanceof Error ? err.message : "Public read proxy request failed";
|
|
107
|
-
return { success: false, error: message };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
98
|
// src/database/QueryBuilder.ts
|
|
112
99
|
function escapeSqlValue(value) {
|
|
113
100
|
if (value === null) return "NULL";
|
|
@@ -124,6 +111,18 @@ function escapeIdentifier(name) {
|
|
|
124
111
|
}
|
|
125
112
|
return `"${name}"`;
|
|
126
113
|
}
|
|
114
|
+
function assertSafeSelectColumns(columns) {
|
|
115
|
+
if (columns === "*") return;
|
|
116
|
+
if (/[;]|--|\/\*|\*\//.test(columns)) {
|
|
117
|
+
throw new ValidationError("select() columns may not contain statement separators or comments");
|
|
118
|
+
}
|
|
119
|
+
if (/\bselect\b/i.test(columns)) {
|
|
120
|
+
throw new ValidationError("select() columns may not contain a sub-SELECT");
|
|
121
|
+
}
|
|
122
|
+
if (!/^[A-Za-z0-9_.,*()\s"']+$/.test(columns)) {
|
|
123
|
+
throw new ValidationError("select() columns contain unsupported characters");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
127
126
|
var QueryBuilder = class {
|
|
128
127
|
constructor(supabase2, projectId, table, serverProxy) {
|
|
129
128
|
this.operation = "select";
|
|
@@ -143,6 +142,7 @@ var QueryBuilder = class {
|
|
|
143
142
|
this.serverProxy = serverProxy;
|
|
144
143
|
}
|
|
145
144
|
select(columns = "*") {
|
|
145
|
+
assertSafeSelectColumns(columns);
|
|
146
146
|
this.operation = "select";
|
|
147
147
|
this.selectColumns = columns;
|
|
148
148
|
return this;
|
|
@@ -330,71 +330,18 @@ var QueryBuilder = class {
|
|
|
330
330
|
default:
|
|
331
331
|
throw new QueryError(`Unknown operation: ${this.operation}`);
|
|
332
332
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (!data2.success) {
|
|
337
|
-
const dbError = mapRpcError(data2);
|
|
338
|
-
if (dbError) throw dbError;
|
|
339
|
-
throw new QueryError(data2.error || "Query failed", sql);
|
|
340
|
-
}
|
|
341
|
-
return {
|
|
342
|
-
success: true,
|
|
343
|
-
data: isRead ? data2.data || [] : [],
|
|
344
|
-
rowCount: data2.rowCount ?? (isRead ? data2.data?.length || 0 : 0),
|
|
345
|
-
schema: data2.schema
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
const publicQuery = this.serverProxy?.publicQuery;
|
|
349
|
-
if (isRead && publicQuery && shouldSkipRpc() && !await hasActiveSession(this.supabase)) {
|
|
350
|
-
return this.runPublicQuery(publicQuery, sql);
|
|
351
|
-
}
|
|
352
|
-
const rpcName = isRead ? "sdk_query_project" : "sdk_exec_project";
|
|
353
|
-
const { data, error } = await this.supabase.rpc(rpcName, {
|
|
354
|
-
p_project_id: this.projectId,
|
|
355
|
-
p_sql: sql
|
|
356
|
-
});
|
|
357
|
-
if (error) {
|
|
358
|
-
if (isRead && publicQuery && isAuthShapedFailure(error.message) && !await hasActiveSession(this.supabase)) {
|
|
359
|
-
return this.runPublicQuery(publicQuery, sql, error.message);
|
|
360
|
-
}
|
|
361
|
-
throw new QueryError(error.message, sql);
|
|
362
|
-
}
|
|
333
|
+
const fn = isRead ? this.serverProxy?.serverQuery : this.serverProxy?.serverExec;
|
|
334
|
+
if (!fn) throw new QueryError("Database client is not configured", sql);
|
|
335
|
+
const data = await fn(sql);
|
|
363
336
|
if (!data.success) {
|
|
364
|
-
if (isRead && publicQuery && isAuthShapedFailure(data.error) && !await hasActiveSession(this.supabase)) {
|
|
365
|
-
return this.runPublicQuery(publicQuery, sql, data.error);
|
|
366
|
-
}
|
|
367
337
|
const dbError = mapRpcError(data);
|
|
368
338
|
if (dbError) throw dbError;
|
|
369
339
|
throw new QueryError(data.error || "Query failed", sql);
|
|
370
340
|
}
|
|
371
|
-
if (isRead) {
|
|
372
|
-
return {
|
|
373
|
-
success: true,
|
|
374
|
-
data: data.data || [],
|
|
375
|
-
rowCount: data.rowCount ?? (data.data?.length || 0),
|
|
376
|
-
schema: data.schema
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
return {
|
|
380
|
-
success: true,
|
|
381
|
-
data: [],
|
|
382
|
-
rowCount: data.rowCount ?? 0,
|
|
383
|
-
schema: data.schema
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
/** Execute a read through the platform public proxy (anonymous-visitor path). */
|
|
387
|
-
async runPublicQuery(publicQuery, sql, rpcError) {
|
|
388
|
-
const data = await publicQuery(sql);
|
|
389
|
-
if (!data.success) {
|
|
390
|
-
const dbError = mapRpcError(data);
|
|
391
|
-
if (dbError) throw dbError;
|
|
392
|
-
throw new QueryError(data.error || rpcError || "Query failed", sql);
|
|
393
|
-
}
|
|
394
341
|
return {
|
|
395
342
|
success: true,
|
|
396
|
-
data: data.data || [],
|
|
397
|
-
rowCount: data.rowCount ?? (data.data?.length || 0),
|
|
343
|
+
data: isRead ? data.data || [] : [],
|
|
344
|
+
rowCount: data.rowCount ?? (isRead ? data.data?.length || 0 : 0),
|
|
398
345
|
schema: data.schema
|
|
399
346
|
};
|
|
400
347
|
}
|
|
@@ -428,29 +375,25 @@ function trackDbEvent(event, props) {
|
|
|
428
375
|
}
|
|
429
376
|
}
|
|
430
377
|
var MAX_RETRIES = 3;
|
|
431
|
-
var SERVER_TIMEOUT_MS =
|
|
378
|
+
var SERVER_TIMEOUT_MS = 8e3;
|
|
432
379
|
var DatabaseClient = class _DatabaseClient {
|
|
433
380
|
constructor(supabase2, projectId) {
|
|
434
381
|
this.serverMode = false;
|
|
435
382
|
this.supabase = supabase2;
|
|
436
383
|
this.projectId = projectId;
|
|
384
|
+
this.platformUrl = env.EZCODER_API_URL || "https://ezcoder.dev";
|
|
437
385
|
}
|
|
438
386
|
static createServerClient(projectId, apiKey, platformUrl) {
|
|
439
387
|
const client = new _DatabaseClient(null, projectId);
|
|
440
388
|
client.apiKey = apiKey;
|
|
441
|
-
client.platformUrl = platformUrl || typeof process !== "undefined" && process.env?.EZCODER_API_URL || "https://ezcoder.
|
|
389
|
+
client.platformUrl = platformUrl || typeof process !== "undefined" && process.env?.EZCODER_API_URL || env.EZCODER_API_URL || "https://ezcoder.dev";
|
|
442
390
|
client.serverMode = true;
|
|
443
391
|
return client;
|
|
444
392
|
}
|
|
445
393
|
from(table) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
serverExec: (sql) => this._serverRequest("/execute", sql)
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
return new QueryBuilder(this.supabase, this.projectId, table, {
|
|
453
|
-
publicQuery: (sql) => publicReadQuery(this.projectId, sql)
|
|
394
|
+
return new QueryBuilder(null, this.projectId, table, {
|
|
395
|
+
serverQuery: (sql) => this._proxy("/query", sql),
|
|
396
|
+
serverExec: (sql) => this._proxy("/execute", sql)
|
|
454
397
|
});
|
|
455
398
|
}
|
|
456
399
|
async sql(query) {
|
|
@@ -458,116 +401,93 @@ var DatabaseClient = class _DatabaseClient {
|
|
|
458
401
|
if (!trimmed.startsWith("select")) {
|
|
459
402
|
throw new QueryError("sql() is for read-only queries. Use execute() for mutations.");
|
|
460
403
|
}
|
|
461
|
-
if (this.serverMode) {
|
|
462
|
-
return this._serverQuery(query);
|
|
463
|
-
}
|
|
464
404
|
const start = Date.now();
|
|
465
|
-
|
|
466
|
-
return this._publicFallback(query, start, null);
|
|
467
|
-
}
|
|
468
|
-
const { data, error } = await this.supabase.rpc("sdk_query_project", {
|
|
469
|
-
p_project_id: this.projectId,
|
|
470
|
-
p_sql: query
|
|
471
|
-
});
|
|
472
|
-
const latencyMs = Date.now() - start;
|
|
473
|
-
if (error) {
|
|
474
|
-
if (publicReadAvailable() && isAuthShapedFailure(error.message) && !await hasActiveSession(this.supabase)) {
|
|
475
|
-
return this._publicFallback(query, start, error.message);
|
|
476
|
-
}
|
|
477
|
-
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: false, error: error.message });
|
|
478
|
-
throw new QueryError(error.message, query);
|
|
479
|
-
}
|
|
480
|
-
if (!data.success) {
|
|
481
|
-
if (publicReadAvailable() && isAuthShapedFailure(data.error) && !await hasActiveSession(this.supabase)) {
|
|
482
|
-
return this._publicFallback(query, start, data.error);
|
|
483
|
-
}
|
|
484
|
-
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: false, error: data.error });
|
|
485
|
-
throw new QueryError(data.error || "Query failed", query);
|
|
486
|
-
}
|
|
487
|
-
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: true, rowCount: data.rowCount ?? 0 });
|
|
488
|
-
return {
|
|
489
|
-
success: true,
|
|
490
|
-
data: data.data || [],
|
|
491
|
-
rowCount: data.rowCount ?? (data.data?.length || 0),
|
|
492
|
-
schema: data.schema
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
/** Read via the platform public proxy (anonymous-visitor path). */
|
|
496
|
-
async _publicFallback(query, start, rpcError) {
|
|
497
|
-
const data = await publicReadQuery(this.projectId, query);
|
|
405
|
+
const result = await this._proxy("/query", query);
|
|
498
406
|
const latencyMs = Date.now() - start;
|
|
499
|
-
if (!
|
|
500
|
-
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: false, error:
|
|
501
|
-
throw new QueryError(
|
|
407
|
+
if (!result.success) {
|
|
408
|
+
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: false, error: result.error });
|
|
409
|
+
throw new QueryError(result.error || "Query failed", query);
|
|
502
410
|
}
|
|
503
|
-
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: true, rowCount:
|
|
411
|
+
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0 });
|
|
504
412
|
return {
|
|
505
413
|
success: true,
|
|
506
|
-
data:
|
|
507
|
-
rowCount:
|
|
508
|
-
schema:
|
|
414
|
+
data: result.data || [],
|
|
415
|
+
rowCount: result.rowCount ?? (result.data?.length || 0),
|
|
416
|
+
schema: result.schema
|
|
509
417
|
};
|
|
510
418
|
}
|
|
511
419
|
async execute(sql) {
|
|
512
|
-
if (this.serverMode) {
|
|
513
|
-
return this._serverExecute(sql);
|
|
514
|
-
}
|
|
515
420
|
const start = Date.now();
|
|
516
|
-
const
|
|
517
|
-
p_project_id: this.projectId,
|
|
518
|
-
p_sql: sql
|
|
519
|
-
});
|
|
421
|
+
const result = await this._proxy("/execute", sql);
|
|
520
422
|
const latencyMs = Date.now() - start;
|
|
521
|
-
if (
|
|
522
|
-
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: false, error: error
|
|
523
|
-
throw new QueryError(error
|
|
524
|
-
}
|
|
525
|
-
if (!data.success) {
|
|
526
|
-
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: false, error: data.error });
|
|
527
|
-
throw new QueryError(data.error || "Execution failed", sql);
|
|
423
|
+
if (!result.success) {
|
|
424
|
+
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: false, error: result.error });
|
|
425
|
+
throw new QueryError(result.error || "Execution failed", sql);
|
|
528
426
|
}
|
|
529
|
-
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: true, rowCount:
|
|
427
|
+
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0 });
|
|
530
428
|
return {
|
|
531
429
|
success: true,
|
|
532
|
-
rowCount:
|
|
533
|
-
schema:
|
|
534
|
-
executed_sql:
|
|
430
|
+
rowCount: result.rowCount ?? 0,
|
|
431
|
+
schema: result.schema,
|
|
432
|
+
executed_sql: result.executed_sql
|
|
535
433
|
};
|
|
536
434
|
}
|
|
537
435
|
async getSchema() {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
const { data, error } = await this.supabase.rpc("sdk_get_schema_info", {
|
|
542
|
-
p_project_id: this.projectId
|
|
543
|
-
});
|
|
544
|
-
if (error) throw new ConnectionError(error.message);
|
|
436
|
+
const result = await this._proxy("/schema", null);
|
|
437
|
+
if (!result.success) throw new ConnectionError(result.error || "Schema fetch failed");
|
|
545
438
|
return {
|
|
546
|
-
exists:
|
|
547
|
-
schema:
|
|
548
|
-
tables:
|
|
549
|
-
tablesCount:
|
|
550
|
-
createdAt:
|
|
551
|
-
lastAccessedAt:
|
|
439
|
+
exists: result.exists ?? false,
|
|
440
|
+
schema: result.schema,
|
|
441
|
+
tables: result.tables || [],
|
|
442
|
+
tablesCount: result.tablesCount ?? 0,
|
|
443
|
+
createdAt: result.createdAt,
|
|
444
|
+
lastAccessedAt: result.lastAccessedAt
|
|
552
445
|
};
|
|
553
446
|
}
|
|
554
|
-
|
|
555
|
-
|
|
447
|
+
/**
|
|
448
|
+
* Resolve the credential for this call. Order mirrors the server's auth tiers:
|
|
449
|
+
* server key → end-user session JWT → public/legacy token → none (origin).
|
|
450
|
+
*/
|
|
451
|
+
async _authHeaders() {
|
|
452
|
+
if (this.serverMode && this.apiKey) {
|
|
453
|
+
return { Authorization: `Bearer ${this.apiKey}` };
|
|
454
|
+
}
|
|
455
|
+
if (this.supabase?.auth?.getSession) {
|
|
456
|
+
let sessionResult;
|
|
457
|
+
try {
|
|
458
|
+
sessionResult = await this.supabase.auth.getSession();
|
|
459
|
+
} catch {
|
|
460
|
+
return {};
|
|
461
|
+
}
|
|
462
|
+
const token = sessionResult?.data?.session?.access_token;
|
|
463
|
+
if (token) return { Authorization: `Bearer ${token}` };
|
|
464
|
+
}
|
|
465
|
+
const pub = getRuntimeConfig()?.publicToken || env.EZC_PROJECT_TOKEN_PUBLIC || env.EZC_PROJECT_TOKEN_LEGACY || "";
|
|
466
|
+
if (pub) return { "X-EzCoder-Public-Token": pub };
|
|
467
|
+
return {};
|
|
468
|
+
}
|
|
469
|
+
/** Platform API base — runtime bootstrap wins over the baked value so an
|
|
470
|
+
* env-less build still reaches the right host. */
|
|
471
|
+
baseUrl() {
|
|
472
|
+
return (getRuntimeConfig()?.apiUrl || this.platformUrl).replace(/\/$/, "");
|
|
473
|
+
}
|
|
474
|
+
async _proxy(endpoint, sql) {
|
|
475
|
+
const url = `${this.baseUrl()}/api/platform/db/${this.projectId}${endpoint}`;
|
|
476
|
+
const method = endpoint === "/schema" ? "GET" : "POST";
|
|
556
477
|
let lastError;
|
|
557
478
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
558
479
|
try {
|
|
480
|
+
const headers = { ...await this._authHeaders() };
|
|
481
|
+
if (method === "POST") headers["Content-Type"] = "application/json";
|
|
559
482
|
const res = await fetch(url, {
|
|
560
|
-
method
|
|
561
|
-
headers
|
|
562
|
-
|
|
563
|
-
"Content-Type": "application/json"
|
|
564
|
-
},
|
|
565
|
-
body: JSON.stringify({ sql }),
|
|
483
|
+
method,
|
|
484
|
+
headers,
|
|
485
|
+
body: method === "POST" ? JSON.stringify({ sql }) : void 0,
|
|
566
486
|
signal: AbortSignal.timeout(SERVER_TIMEOUT_MS)
|
|
567
487
|
});
|
|
568
488
|
if (res.status === 429) {
|
|
569
489
|
const retryAfter = parseInt(res.headers.get("retry-after") || "60", 10);
|
|
570
|
-
await new Promise((r) => setTimeout(r, retryAfter * 1e3));
|
|
490
|
+
await new Promise((r) => setTimeout(r, Math.min(retryAfter, 5) * 1e3));
|
|
571
491
|
continue;
|
|
572
492
|
}
|
|
573
493
|
if (res.status === 503) {
|
|
@@ -577,72 +497,11 @@ var DatabaseClient = class _DatabaseClient {
|
|
|
577
497
|
return await res.json();
|
|
578
498
|
} catch (e) {
|
|
579
499
|
lastError = e instanceof Error ? e : new Error(String(e));
|
|
500
|
+
if (endpoint === "/execute") break;
|
|
580
501
|
}
|
|
581
502
|
}
|
|
582
503
|
return { success: false, error: lastError?.message || "Request failed after retries" };
|
|
583
504
|
}
|
|
584
|
-
async _serverQuery(query) {
|
|
585
|
-
const start = Date.now();
|
|
586
|
-
const result = await this._serverRequest("/query", query);
|
|
587
|
-
const latencyMs = Date.now() - start;
|
|
588
|
-
if (!result.success) {
|
|
589
|
-
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: false, error: result.error, serverMode: true });
|
|
590
|
-
throw new QueryError(result.error || "Query failed", query);
|
|
591
|
-
}
|
|
592
|
-
trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0, serverMode: true });
|
|
593
|
-
return {
|
|
594
|
-
success: true,
|
|
595
|
-
data: result.data || [],
|
|
596
|
-
rowCount: result.rowCount ?? (result.data?.length || 0),
|
|
597
|
-
schema: result.schema
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
async _serverExecute(sql) {
|
|
601
|
-
const start = Date.now();
|
|
602
|
-
const result = await this._serverRequest("/execute", sql);
|
|
603
|
-
const latencyMs = Date.now() - start;
|
|
604
|
-
if (!result.success) {
|
|
605
|
-
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: false, error: result.error, serverMode: true });
|
|
606
|
-
throw new QueryError(result.error || "Execution failed", sql);
|
|
607
|
-
}
|
|
608
|
-
trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0, serverMode: true });
|
|
609
|
-
return {
|
|
610
|
-
success: true,
|
|
611
|
-
rowCount: result.rowCount ?? 0,
|
|
612
|
-
schema: result.schema,
|
|
613
|
-
executed_sql: result.executed_sql
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
async _serverGetSchema() {
|
|
617
|
-
const url = `${this.platformUrl}/api/platform/db/${this.projectId}/schema`;
|
|
618
|
-
let lastError;
|
|
619
|
-
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
620
|
-
try {
|
|
621
|
-
const res = await fetch(url, {
|
|
622
|
-
method: "GET",
|
|
623
|
-
headers: { "Authorization": `Bearer ${this.apiKey}` },
|
|
624
|
-
signal: AbortSignal.timeout(SERVER_TIMEOUT_MS)
|
|
625
|
-
});
|
|
626
|
-
if (res.status === 503) {
|
|
627
|
-
await new Promise((r) => setTimeout(r, (attempt + 1) * 2e3));
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
const data = await res.json();
|
|
631
|
-
if (!data.success) throw new ConnectionError(data.error || "Schema fetch failed");
|
|
632
|
-
return {
|
|
633
|
-
exists: data.exists ?? false,
|
|
634
|
-
schema: data.schema,
|
|
635
|
-
tables: data.tables || [],
|
|
636
|
-
tablesCount: data.tablesCount ?? 0,
|
|
637
|
-
createdAt: data.createdAt,
|
|
638
|
-
lastAccessedAt: data.lastAccessedAt
|
|
639
|
-
};
|
|
640
|
-
} catch (e) {
|
|
641
|
-
lastError = e instanceof Error ? e : new Error(String(e));
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
throw new ConnectionError(lastError?.message || "Schema request failed after retries");
|
|
645
|
-
}
|
|
646
505
|
};
|
|
647
506
|
|
|
648
507
|
// src/database/DatabaseProvider.tsx
|
|
@@ -652,20 +511,38 @@ var DatabaseContext = createContext({
|
|
|
652
511
|
isConfigured: false
|
|
653
512
|
});
|
|
654
513
|
function DatabaseProvider({ children, projectId }) {
|
|
655
|
-
const
|
|
656
|
-
const
|
|
514
|
+
const bakedProjectId = projectId || env.EZC_PROJECT_ID || "";
|
|
515
|
+
const [runtimeProjectId, setRuntimeProjectId] = useState(getRuntimeConfig()?.projectId || "");
|
|
516
|
+
const [bootstrapAttempted, setBootstrapAttempted] = useState(Boolean(getRuntimeConfig()));
|
|
517
|
+
useEffect(() => {
|
|
518
|
+
if (bakedProjectId) return;
|
|
519
|
+
let cancelled = false;
|
|
520
|
+
loadBootstrap().then((rc) => {
|
|
521
|
+
if (cancelled) return;
|
|
522
|
+
if (rc?.projectId) setRuntimeProjectId(rc.projectId);
|
|
523
|
+
setBootstrapAttempted(true);
|
|
524
|
+
});
|
|
525
|
+
return () => {
|
|
526
|
+
cancelled = true;
|
|
527
|
+
};
|
|
528
|
+
}, [bakedProjectId]);
|
|
529
|
+
const resolvedProjectId = bakedProjectId || runtimeProjectId;
|
|
530
|
+
const configured = Boolean(resolvedProjectId);
|
|
657
531
|
const db = useMemo(
|
|
658
532
|
() => configured ? new DatabaseClient(supabase, resolvedProjectId) : null,
|
|
659
533
|
[configured, resolvedProjectId]
|
|
660
534
|
);
|
|
661
535
|
const value = useMemo(() => ({ db, isConfigured: configured }), [db, configured]);
|
|
536
|
+
if (!bakedProjectId && !bootstrapAttempted && !runtimeProjectId) {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
662
539
|
return /* @__PURE__ */ jsx(DatabaseContext.Provider, { value, children });
|
|
663
540
|
}
|
|
664
541
|
function useDatabase() {
|
|
665
542
|
const { db } = useContext(DatabaseContext);
|
|
666
543
|
if (!db) {
|
|
667
544
|
throw new Error(
|
|
668
|
-
"useDatabase() must be used within <DatabaseProvider>. Ensure
|
|
545
|
+
"useDatabase() must be used within <DatabaseProvider>. Ensure the app is wrapped in <DatabaseProvider> and the project has a deployment origin or EZC_PROJECT_ID."
|
|
669
546
|
);
|
|
670
547
|
}
|
|
671
548
|
return db;
|
|
@@ -680,14 +557,14 @@ function useIsDatabaseConfigured() {
|
|
|
680
557
|
}
|
|
681
558
|
|
|
682
559
|
// src/database/useRealtime.ts
|
|
683
|
-
import { useEffect, useState, useRef } from "react";
|
|
560
|
+
import { useEffect as useEffect2, useState as useState2, useRef } from "react";
|
|
684
561
|
function useRealtime(table, options = {}) {
|
|
685
|
-
const [data, setData] =
|
|
686
|
-
const [status, setStatus] =
|
|
562
|
+
const [data, setData] = useState2([]);
|
|
563
|
+
const [status, setStatus] = useState2("connecting");
|
|
687
564
|
const optionsRef = useRef(options);
|
|
688
565
|
optionsRef.current = options;
|
|
689
566
|
const projectId = env.EZC_PROJECT_ID;
|
|
690
|
-
|
|
567
|
+
useEffect2(() => {
|
|
691
568
|
if (!isSupabaseConfigured || !projectId) {
|
|
692
569
|
setStatus("disabled");
|
|
693
570
|
return;
|
|
@@ -730,6 +607,9 @@ function useRealtime(table, options = {}) {
|
|
|
730
607
|
}
|
|
731
608
|
|
|
732
609
|
export {
|
|
610
|
+
loadBootstrap,
|
|
611
|
+
getRuntimeConfig,
|
|
612
|
+
configValue,
|
|
733
613
|
DatabaseError,
|
|
734
614
|
QueryError,
|
|
735
615
|
ValidationError,
|
|
@@ -743,4 +623,4 @@ export {
|
|
|
743
623
|
useIsDatabaseConfigured,
|
|
744
624
|
useRealtime
|
|
745
625
|
};
|
|
746
|
-
//# sourceMappingURL=chunk-
|
|
626
|
+
//# sourceMappingURL=chunk-5QYGP7G7.js.map
|