@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.
Files changed (34) hide show
  1. package/dist/{DatabaseProvider-DaBP5XUs.d.ts → DatabaseProvider-BtYMJRqh.d.ts} +34 -20
  2. package/dist/analytics/index.js +4 -4
  3. package/dist/auth/index.d.ts +28 -1
  4. package/dist/auth/index.js +21 -4
  5. package/dist/auth/index.js.map +1 -1
  6. package/dist/{chunk-CQKYANAW.js → chunk-4WGGFJPE.js} +2 -2
  7. package/dist/{chunk-UAY64NNA.js → chunk-5QYGP7G7.js} +157 -277
  8. package/dist/chunk-5QYGP7G7.js.map +1 -0
  9. package/dist/{chunk-HJ2EIZ4S.js → chunk-DU6N3HVQ.js} +2 -2
  10. package/dist/{chunk-I2YGB7Z6.js → chunk-KKTY5NCR.js} +2 -2
  11. package/dist/{chunk-QHB7LGCA.js → chunk-NTA3RMYR.js} +231 -5
  12. package/dist/chunk-NTA3RMYR.js.map +1 -0
  13. package/dist/{chunk-LIUE7M7K.js → chunk-X4JP7DCK.js} +11 -2
  14. package/dist/chunk-X4JP7DCK.js.map +1 -0
  15. package/dist/cms/index.js +2 -2
  16. package/dist/config-D5ajnLCe.d.ts +29 -0
  17. package/dist/cron/index.js +2 -2
  18. package/dist/database/index.d.ts +1 -1
  19. package/dist/database/index.js +4 -4
  20. package/dist/email/index.js +2 -2
  21. package/dist/index.d.ts +45 -27
  22. package/dist/index.js +13 -5
  23. package/dist/index.js.map +1 -1
  24. package/dist/notifications/index.js +5 -5
  25. package/dist/payments/index.js +4 -4
  26. package/dist/roles/index.js +4 -4
  27. package/dist/storage/index.js +4 -4
  28. package/package.json +9 -6
  29. package/dist/chunk-LIUE7M7K.js.map +0 -1
  30. package/dist/chunk-QHB7LGCA.js.map +0 -1
  31. package/dist/chunk-UAY64NNA.js.map +0 -1
  32. /package/dist/{chunk-CQKYANAW.js.map → chunk-4WGGFJPE.js.map} +0 -0
  33. /package/dist/{chunk-HJ2EIZ4S.js.map → chunk-DU6N3HVQ.js.map} +0 -0
  34. /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-HJ2EIZ4S.js";
3
+ } from "./chunk-DU6N3HVQ.js";
4
4
  import {
5
5
  isSupabaseConfigured,
6
6
  supabase
7
- } from "./chunk-I2YGB7Z6.js";
7
+ } from "./chunk-KKTY5NCR.js";
8
8
  import {
9
9
  env,
10
10
  features
11
- } from "./chunk-LIUE7M7K.js";
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
- if (this.serverProxy?.serverQuery && this.serverProxy?.serverExec) {
334
- const fn = isRead ? this.serverProxy.serverQuery : this.serverProxy.serverExec;
335
- const data2 = await fn(sql);
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 = 5e3;
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.app";
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
- if (this.serverMode) {
447
- return new QueryBuilder(null, this.projectId, table, {
448
- serverQuery: (sql) => this._serverRequest("/query", sql),
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
- if (shouldSkipRpc() && !await hasActiveSession(this.supabase)) {
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 (!data.success) {
500
- trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: false, error: data.error, publicProxy: true });
501
- throw new QueryError(data.error || rpcError || "Query failed", query);
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: data.rowCount ?? 0, publicProxy: true });
411
+ trackDbEvent("db_query", { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0 });
504
412
  return {
505
413
  success: true,
506
- data: data.data || [],
507
- rowCount: data.rowCount ?? (data.data?.length || 0),
508
- schema: data.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 { data, error } = await this.supabase.rpc("sdk_exec_project", {
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 (error) {
522
- trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: false, error: error.message });
523
- throw new QueryError(error.message, sql);
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: data.rowCount ?? 0 });
427
+ trackDbEvent("db_mutation", { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0 });
530
428
  return {
531
429
  success: true,
532
- rowCount: data.rowCount ?? 0,
533
- schema: data.schema,
534
- executed_sql: data.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
- if (this.serverMode) {
539
- return this._serverGetSchema();
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: data.exists ?? false,
547
- schema: data.schema,
548
- tables: data.tables || [],
549
- tablesCount: data.tablesCount ?? 0,
550
- createdAt: data.createdAt,
551
- lastAccessedAt: data.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
- async _serverRequest(endpoint, sql) {
555
- const url = `${this.platformUrl}/api/platform/db/${this.projectId}${endpoint}`;
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: "POST",
561
- headers: {
562
- "Authorization": `Bearer ${this.apiKey}`,
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 resolvedProjectId = projectId || env.EZC_PROJECT_ID;
656
- const configured = isSupabaseConfigured && Boolean(resolvedProjectId);
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 EZC_PROJECT_ID and Supabase credentials are configured."
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] = useState([]);
686
- const [status, setStatus] = useState("connecting");
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
- useEffect(() => {
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-UAY64NNA.js.map
626
+ //# sourceMappingURL=chunk-5QYGP7G7.js.map