@ezcoder.dev/sdk 1.3.2 → 1.3.3
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/{chunk-UAY64NNA.js → chunk-O3GL4ZVC.js} +154 -274
- package/dist/chunk-O3GL4ZVC.js.map +1 -0
- package/dist/database/index.d.ts +1 -1
- package/dist/database/index.js +1 -1
- package/dist/index.d.ts +45 -2
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-UAY64NNA.js.map +0 -1
|
@@ -83,16 +83,6 @@ interface ServerProxy {
|
|
|
83
83
|
error?: string;
|
|
84
84
|
schema?: string;
|
|
85
85
|
}>;
|
|
86
|
-
/** Browser-mode READ fallback: routes through the platform public proxy
|
|
87
|
-
* (public-class token) when the direct RPC needs an authenticated session
|
|
88
|
-
* the visitor doesn't have. Reads only — writes never fall back. */
|
|
89
|
-
publicQuery?: (sql: string) => Promise<{
|
|
90
|
-
success: boolean;
|
|
91
|
-
data?: unknown[];
|
|
92
|
-
rowCount?: number;
|
|
93
|
-
error?: string;
|
|
94
|
-
schema?: string;
|
|
95
|
-
}>;
|
|
96
86
|
}
|
|
97
87
|
declare class QueryBuilder<T = Record<string, unknown>> {
|
|
98
88
|
private readonly supabase;
|
|
@@ -144,36 +134,60 @@ declare class QueryBuilder<T = Record<string, unknown>> {
|
|
|
144
134
|
private buildUpdateSql;
|
|
145
135
|
private buildDeleteSql;
|
|
146
136
|
private executeQuery;
|
|
147
|
-
/** Execute a read through the platform public proxy (anonymous-visitor path). */
|
|
148
|
-
private runPublicQuery;
|
|
149
137
|
then<TResult1 = DatabaseResult<T> | SingleResult<T>, TResult2 = never>(onfulfilled?: ((value: DatabaseResult<T> | SingleResult<T>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
150
138
|
}
|
|
151
139
|
|
|
140
|
+
/**
|
|
141
|
+
* ONE data path (Parity Doctrine W1). Every database call — preview & deployed,
|
|
142
|
+
* read & write, anonymous & authenticated, browser & server — goes through the
|
|
143
|
+
* platform proxy `/api/platform/db/{projectId}/{query|execute|schema}`. The
|
|
144
|
+
* ONLY thing that varies is the credential attached:
|
|
145
|
+
*
|
|
146
|
+
* - server mode → Authorization: Bearer <server API key>
|
|
147
|
+
* - logged-in user → Authorization: Bearer <tenant session JWT> (RLS applies)
|
|
148
|
+
* - anonymous + token → X-EzCoder-Public-Token: <public/legacy token>
|
|
149
|
+
* - anonymous, no tok → nothing; the server authorizes by deployment origin
|
|
150
|
+
*
|
|
151
|
+
* No environment branching, no RPC-vs-proxy fork, no preview-only fallback —
|
|
152
|
+
* preview and deployed take byte-identical paths, which is the whole point.
|
|
153
|
+
*/
|
|
152
154
|
declare class DatabaseClient {
|
|
153
155
|
private readonly supabase;
|
|
154
156
|
private readonly projectId;
|
|
155
157
|
private apiKey?;
|
|
156
|
-
private platformUrl
|
|
158
|
+
private platformUrl;
|
|
157
159
|
private serverMode;
|
|
158
160
|
constructor(supabase: SupabaseClient | null, projectId: string);
|
|
159
161
|
static createServerClient(projectId: string, apiKey: string, platformUrl?: string): DatabaseClient;
|
|
160
162
|
from<T = Record<string, unknown>>(table: string): QueryBuilder<T>;
|
|
161
163
|
sql<T = Record<string, unknown>>(query: string): Promise<DatabaseResult<T>>;
|
|
162
|
-
/** Read via the platform public proxy (anonymous-visitor path). */
|
|
163
|
-
private _publicFallback;
|
|
164
164
|
execute(sql: string): Promise<MutationResult>;
|
|
165
165
|
getSchema(): Promise<SchemaInfo>;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Resolve the credential for this call. Order mirrors the server's auth tiers:
|
|
168
|
+
* server key → end-user session JWT → public/legacy token → none (origin).
|
|
169
|
+
*/
|
|
170
|
+
private _authHeaders;
|
|
171
|
+
/** Platform API base — runtime bootstrap wins over the baked value so an
|
|
172
|
+
* env-less build still reaches the right host. */
|
|
173
|
+
private baseUrl;
|
|
174
|
+
private _proxy;
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
interface DatabaseProviderProps {
|
|
173
178
|
children: ReactNode;
|
|
174
179
|
projectId?: string;
|
|
175
180
|
}
|
|
176
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Parity Doctrine W2: the database is configured as soon as we know the
|
|
183
|
+
* projectId — from the prop, the build-time env, OR the runtime bootstrap.
|
|
184
|
+
* Crucially, a build that baked NO env (the herdmark "useDatabase must be used
|
|
185
|
+
* within <DatabaseProvider>" boot crash) is no longer broken: we fetch the
|
|
186
|
+
* config live and resolve the projectId at runtime. The DatabaseClient reaches
|
|
187
|
+
* the DB through the platform proxy, so a configured Supabase client is NOT
|
|
188
|
+
* required for data access — anonymous reads authorize by deployment origin.
|
|
189
|
+
*/
|
|
190
|
+
declare function DatabaseProvider({ children, projectId }: DatabaseProviderProps): react_jsx_runtime.JSX.Element | null;
|
|
177
191
|
declare function useDatabase(): DatabaseClient;
|
|
178
192
|
declare function useDatabaseOptional(): DatabaseClient | null;
|
|
179
193
|
declare function useIsDatabaseConfigured(): boolean;
|
|
@@ -11,7 +11,46 @@ import {
|
|
|
11
11
|
} from "./chunk-LIUE7M7K.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-O3GL4ZVC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/database/DatabaseProvider.tsx","../src/core/bootstrap.ts","../src/database/errors.ts","../src/database/QueryBuilder.ts","../src/database/DatabaseClient.ts","../src/database/useRealtime.ts"],"sourcesContent":["import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';\nimport { supabase } from '../core/supabase';\nimport { env } from '../core/config';\nimport { loadBootstrap, getRuntimeConfig } from '../core/bootstrap';\nimport { DatabaseClient } from './DatabaseClient';\n\ninterface DatabaseContextValue {\n db: DatabaseClient | null;\n isConfigured: boolean;\n}\n\nconst DatabaseContext = createContext<DatabaseContextValue>({\n db: null,\n isConfigured: false,\n});\n\ninterface DatabaseProviderProps {\n children: ReactNode;\n projectId?: string;\n}\n\n/**\n * Parity Doctrine W2: the database is configured as soon as we know the\n * projectId — from the prop, the build-time env, OR the runtime bootstrap.\n * Crucially, a build that baked NO env (the herdmark \"useDatabase must be used\n * within <DatabaseProvider>\" boot crash) is no longer broken: we fetch the\n * config live and resolve the projectId at runtime. The DatabaseClient reaches\n * the DB through the platform proxy, so a configured Supabase client is NOT\n * required for data access — anonymous reads authorize by deployment origin.\n */\nexport function DatabaseProvider({ children, projectId }: DatabaseProviderProps) {\n const bakedProjectId = projectId || env.EZC_PROJECT_ID || '';\n const [runtimeProjectId, setRuntimeProjectId] = useState<string>(getRuntimeConfig()?.projectId || '');\n const [bootstrapAttempted, setBootstrapAttempted] = useState<boolean>(Boolean(getRuntimeConfig()));\n\n useEffect(() => {\n if (bakedProjectId) return; // already configured from build-time env/prop\n let cancelled = false;\n loadBootstrap().then((rc) => {\n if (cancelled) return;\n if (rc?.projectId) setRuntimeProjectId(rc.projectId);\n setBootstrapAttempted(true);\n });\n return () => { cancelled = true; };\n }, [bakedProjectId]);\n\n const resolvedProjectId = bakedProjectId || runtimeProjectId;\n const configured = Boolean(resolvedProjectId);\n\n const db = useMemo(\n () => (configured ? new DatabaseClient(supabase, resolvedProjectId) : null),\n [configured, resolvedProjectId],\n );\n\n const value = useMemo(() => ({ db, isConfigured: configured }), [db, configured]);\n\n // Env-less build ONLY: hold render until the runtime bootstrap resolves the\n // projectId, converting the old hard crash into a sub-second gate. A normally\n // configured build (projectId baked) never enters this branch.\n if (!bakedProjectId && !bootstrapAttempted && !runtimeProjectId) {\n return null;\n }\n\n return <DatabaseContext.Provider value={value}>{children}</DatabaseContext.Provider>;\n}\n\nexport function useDatabase(): DatabaseClient {\n const { db } = useContext(DatabaseContext);\n if (!db) {\n throw new Error(\n 'useDatabase() must be used within <DatabaseProvider>. ' +\n 'Ensure the app is wrapped in <DatabaseProvider> and the project has a deployment origin or EZC_PROJECT_ID.',\n );\n }\n return db;\n}\n\nexport function useDatabaseOptional(): DatabaseClient | null {\n const { db } = useContext(DatabaseContext);\n return db;\n}\n\nexport function useIsDatabaseConfigured(): boolean {\n const { isConfigured } = useContext(DatabaseContext);\n return isConfigured;\n}\n","/**\n * Runtime config bootstrap (Parity Doctrine W2).\n *\n * The SDK fetches /api/sdk/bootstrap ONCE at startup and overlays the result on\n * the build-time-baked `env`. This means a bundle that baked NO env (the\n * herdmark \"loads but crashes on boot\" class) still works — it learns its\n * config live — and preview and deployed take the identical path.\n *\n * Resolution order for any config value: runtime bootstrap → baked env. The\n * baked env is the OFFLINE FALLBACK, never the primary source. Secrets never\n * appear here — the endpoint returns public config only.\n */\n\nimport { env } from './config';\n\nexport interface RuntimeConfig {\n projectId: string;\n apiUrl: string;\n authUrl: string | null;\n authAnonKey: string | null;\n supabaseUrl: string | null;\n supabaseAnonKey: string | null;\n stripePublishableKey: string | null;\n storageBucketPublic: string | null;\n storageBucketPrivate: string | null;\n publicToken: string | null;\n manifest: unknown | null;\n}\n\nlet cached: RuntimeConfig | null = null;\nlet inflight: Promise<RuntimeConfig | null> | null = null;\n\n/** The base URL of the platform API. Baked value is the offline fallback. */\nfunction apiBase(): string {\n return (env.EZCODER_API_URL || 'https://ezcoder.dev').replace(/\\/$/, '');\n}\n\n/**\n * Fetch the runtime config once (coalesced). Safe to call from multiple\n * providers — only one network request is made. Never throws: on failure it\n * returns null and the SDK falls back to baked env.\n *\n * @param opts.force re-fetch even if cached\n */\nexport async function loadBootstrap(opts: { force?: boolean } = {}): Promise<RuntimeConfig | null> {\n if (cached && !opts.force) return cached;\n if (inflight && !opts.force) return inflight;\n if (typeof fetch === 'undefined') return null;\n\n inflight = (async () => {\n try {\n const headers: Record<string, string> = {};\n // If a token IS baked, send it so the endpoint resolves the project even\n // off a non-deployment origin (e.g. preview). Otherwise the endpoint\n // resolves by origin — no baked config required.\n const tok = env.EZC_PROJECT_TOKEN_PUBLIC || env.EZC_PROJECT_TOKEN_LEGACY || '';\n if (tok) headers['X-EzCoder-Public-Token'] = tok;\n // Pass the baked projectId as a preview fallback (honored only for trusted origins server-side).\n const qs = env.EZC_PROJECT_ID ? `?projectId=${encodeURIComponent(env.EZC_PROJECT_ID)}` : '';\n const res = await fetch(`${apiBase()}/api/sdk/bootstrap${qs}`, { method: 'GET', headers });\n if (!res.ok) return null;\n const body = await res.json().catch(() => null);\n if (!body?.success || !body.config?.projectId) return null;\n cached = body.config as RuntimeConfig;\n return cached;\n } catch {\n return null;\n } finally {\n inflight = null;\n }\n })();\n return inflight;\n}\n\n/** The loaded runtime config, or null if not yet loaded / unavailable. */\nexport function getRuntimeConfig(): RuntimeConfig | null {\n return cached;\n}\n\n/** Test seam. */\nexport function _resetBootstrap(): void {\n cached = null;\n inflight = null;\n}\n\n/**\n * Resolve a config value preferring runtime bootstrap over baked env. Use this\n * for any SDK-critical value so an env-less build still resolves it at runtime.\n */\nexport function configValue<K extends keyof RuntimeConfig>(key: K, bakedFallback: string): string {\n const rc = cached;\n const v = rc ? rc[key] : null;\n return (typeof v === 'string' && v) ? v : bakedFallback;\n}\n","export class DatabaseError extends Error {\r\n readonly code: string;\r\n readonly statusCode: number;\r\n\r\n constructor(message: string, code = 'DATABASE_ERROR', statusCode = 500) {\r\n super(message);\r\n this.name = 'DatabaseError';\r\n this.code = code;\r\n this.statusCode = statusCode;\r\n }\r\n}\r\n\r\nexport class QueryError extends DatabaseError {\r\n readonly sql?: string;\r\n\r\n constructor(message: string, sql?: string) {\r\n super(message, 'QUERY_ERROR', 400);\r\n this.name = 'QueryError';\r\n this.sql = sql;\r\n }\r\n}\r\n\r\nexport class ValidationError extends DatabaseError {\r\n constructor(message: string) {\r\n super(message, 'VALIDATION_ERROR', 422);\r\n this.name = 'ValidationError';\r\n }\r\n}\r\n\r\nexport class ConnectionError extends DatabaseError {\r\n constructor(message: string) {\r\n super(message, 'CONNECTION_ERROR', 503);\r\n this.name = 'ConnectionError';\r\n }\r\n}\r\n\r\nexport class NotFoundError extends DatabaseError {\r\n constructor(message = 'Record not found') {\r\n super(message, 'NOT_FOUND', 404);\r\n this.name = 'NotFoundError';\r\n }\r\n}\r\n\r\nexport function mapRpcError(rpcResult: { success: boolean; error?: string; code?: string }): DatabaseError | null {\r\n if (rpcResult.success) return null;\r\n const msg = rpcResult.error || 'Unknown database error';\r\n if (msg.includes('No database schema exists')) return new NotFoundError(msg);\r\n if (msg.includes('Authentication required')) return new ConnectionError(msg);\r\n if (msg.includes('blocked') || msg.includes('forbidden')) return new ValidationError(msg);\r\n return new QueryError(msg);\r\n}\r\n","import type { SupabaseClient } from '@supabase/supabase-js';\r\nimport type { DatabaseResult, SingleResult, MutationResult, FilterValue, OrderOption } from './types';\r\nimport { QueryError, ValidationError, mapRpcError } from './errors';\r\n\r\nfunction escapeSqlValue(value: FilterValue): string {\r\n if (value === null) return 'NULL';\r\n if (typeof value === 'boolean') return value ? 'TRUE' : 'FALSE';\r\n if (typeof value === 'number') {\r\n if (!Number.isFinite(value)) throw new ValidationError('Non-finite numbers are not allowed');\r\n return String(value);\r\n }\r\n return `'${String(value).replace(/'/g, \"''\")}'`;\r\n}\r\n\r\nfunction escapeIdentifier(name: string): string {\r\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\r\n throw new ValidationError(`Invalid identifier: ${name}`);\r\n }\r\n return `\"${name}\"`;\r\n}\r\n\r\n/**\r\n * Validate a .select() column list. The list is concatenated into the SELECT\r\n * clause, so it must not be able to break out of the column position. We allow\r\n * the shapes generated apps actually use — column names, dotted refs, double-\r\n * quoted idents, `AS` aliases, commas, `*`, and simple function calls like\r\n * count(*) — but reject statement terminators, comment markers, and nested\r\n * sub-SELECTs that would change the query's meaning (security review finding 3).\r\n */\r\nfunction assertSafeSelectColumns(columns: string): void {\r\n if (columns === '*') return;\r\n if (/[;]|--|\\/\\*|\\*\\//.test(columns)) {\r\n throw new ValidationError('select() columns may not contain statement separators or comments');\r\n }\r\n if (/\\bselect\\b/i.test(columns)) {\r\n throw new ValidationError('select() columns may not contain a sub-SELECT');\r\n }\r\n // Allowed character set: identifiers, dots, commas, whitespace, parens, star,\r\n // quotes (for quoted idents / string-literal aliases). No operators that\r\n // enable injection (e.g. semicolons handled above).\r\n if (!/^[A-Za-z0-9_.,*()\\s\"']+$/.test(columns)) {\r\n throw new ValidationError('select() columns contain unsupported characters');\r\n }\r\n}\r\n\r\ntype Operation = 'select' | 'insert' | 'update' | 'delete' | 'count' | 'upsert';\r\n\r\ninterface Filter {\r\n column: string;\r\n op: string;\r\n value: FilterValue | FilterValue[];\r\n}\r\n\r\nexport interface ServerProxy {\r\n serverQuery?: (sql: string) => Promise<{ success: boolean; data?: unknown[]; rowCount?: number; error?: string; schema?: string }>;\r\n serverExec?: (sql: string) => Promise<{ success: boolean; data?: unknown[]; rowCount?: number; error?: string; schema?: string }>;\r\n}\r\n\r\nexport class QueryBuilder<T = Record<string, unknown>> {\r\n private readonly supabase: SupabaseClient;\r\n private readonly projectId: string;\r\n private readonly table: string;\r\n private readonly serverProxy?: ServerProxy;\r\n\r\n private operation: Operation = 'select';\r\n private selectColumns = '*';\r\n private filters: Filter[] = [];\r\n private orderClauses: string[] = [];\r\n private limitValue: number | null = null;\r\n private offsetValue: number | null = null;\r\n private insertData: Record<string, FilterValue>[] | null = null;\r\n private updateData: Record<string, FilterValue> | null = null;\r\n private upsertConflict: string | null = null;\r\n private singleMode = false;\r\n private maybeSingleMode = false;\r\n\r\n constructor(supabase: SupabaseClient, projectId: string, table: string, serverProxy?: ServerProxy) {\r\n this.supabase = supabase;\r\n this.projectId = projectId;\r\n this.table = table;\r\n this.serverProxy = serverProxy;\r\n }\r\n\r\n select(columns = '*'): this {\r\n assertSafeSelectColumns(columns);\r\n this.operation = 'select';\r\n this.selectColumns = columns;\r\n return this;\r\n }\r\n\r\n // --- Filters ---\r\n\r\n eq(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '=', value });\r\n return this;\r\n }\r\n\r\n neq(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '!=', value });\r\n return this;\r\n }\r\n\r\n gt(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '>', value });\r\n return this;\r\n }\r\n\r\n lt(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '<', value });\r\n return this;\r\n }\r\n\r\n gte(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '>=', value });\r\n return this;\r\n }\r\n\r\n lte(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '<=', value });\r\n return this;\r\n }\r\n\r\n like(column: string, pattern: string): this {\r\n this.filters.push({ column, op: 'LIKE', value: pattern });\r\n return this;\r\n }\r\n\r\n ilike(column: string, pattern: string): this {\r\n this.filters.push({ column, op: 'ILIKE', value: pattern });\r\n return this;\r\n }\r\n\r\n in(column: string, values: FilterValue[]): this {\r\n this.filters.push({ column, op: 'IN', value: values });\r\n return this;\r\n }\r\n\r\n is(column: string, value: null | boolean): this {\r\n this.filters.push({ column, op: 'IS', value });\r\n return this;\r\n }\r\n\r\n // --- Ordering & Pagination ---\r\n\r\n order(column: string, options: OrderOption = {}): this {\r\n const dir = options.ascending === false ? 'DESC' : 'ASC';\r\n const nulls = options.nullsFirst ? 'NULLS FIRST' : 'NULLS LAST';\r\n this.orderClauses.push(`${escapeIdentifier(column)} ${dir} ${nulls}`);\r\n return this;\r\n }\r\n\r\n limit(count: number): this {\r\n this.limitValue = count;\r\n return this;\r\n }\r\n\r\n offset(count: number): this {\r\n this.offsetValue = count;\r\n return this;\r\n }\r\n\r\n // --- Mutations ---\r\n\r\n insert(records: Record<string, FilterValue> | Record<string, FilterValue>[]): this {\r\n this.operation = 'insert';\r\n this.insertData = Array.isArray(records) ? records : [records];\r\n return this;\r\n }\r\n\r\n update(values: Record<string, FilterValue>): this {\r\n this.operation = 'update';\r\n this.updateData = values;\r\n return this;\r\n }\r\n\r\n delete(): this {\r\n this.operation = 'delete';\r\n return this;\r\n }\r\n\r\n upsert(records: Record<string, FilterValue> | Record<string, FilterValue>[], options?: { onConflict?: string }): this {\r\n this.operation = 'upsert';\r\n this.insertData = Array.isArray(records) ? records : [records];\r\n this.upsertConflict = options?.onConflict || null;\r\n return this;\r\n }\r\n\r\n // --- Result Helpers ---\r\n\r\n count(): QueryBuilder<{ count: number }> {\r\n this.operation = 'count';\r\n return this as unknown as QueryBuilder<{ count: number }>;\r\n }\r\n\r\n single(): QueryBuilder<T> {\r\n this.singleMode = true;\r\n this.limitValue = 1;\r\n return this;\r\n }\r\n\r\n maybeSingle(): QueryBuilder<T> {\r\n this.maybeSingleMode = true;\r\n this.limitValue = 1;\r\n return this;\r\n }\r\n\r\n // --- SQL Building ---\r\n\r\n private buildWhereClause(): string {\r\n if (this.filters.length === 0) return '';\r\n const conditions = this.filters.map((f) => {\r\n const col = escapeIdentifier(f.column);\r\n if (f.op === 'IS') {\r\n const val = f.value === null ? 'NULL' : f.value ? 'TRUE' : 'FALSE';\r\n return `${col} IS ${val}`;\r\n }\r\n if (f.op === 'IN') {\r\n const vals = (f.value as FilterValue[]).map(escapeSqlValue).join(', ');\r\n return `${col} IN (${vals})`;\r\n }\r\n return `${col} ${f.op} ${escapeSqlValue(f.value as FilterValue)}`;\r\n });\r\n return ` WHERE ${conditions.join(' AND ')}`;\r\n }\r\n\r\n private buildSelectSql(): string {\r\n let sql = `SELECT ${this.selectColumns} FROM \"${this.table}\"`;\r\n sql += this.buildWhereClause();\r\n if (this.orderClauses.length > 0) sql += ` ORDER BY ${this.orderClauses.join(', ')}`;\r\n if (this.limitValue !== null) sql += ` LIMIT ${this.limitValue}`;\r\n if (this.offsetValue !== null && this.offsetValue > 0) sql += ` OFFSET ${this.offsetValue}`;\r\n return sql;\r\n }\r\n\r\n private buildCountSql(): string {\r\n let sql = `SELECT COUNT(*) as count FROM \"${this.table}\"`;\r\n sql += this.buildWhereClause();\r\n return sql;\r\n }\r\n\r\n private buildInsertSql(): string {\r\n if (!this.insertData || this.insertData.length === 0) {\r\n throw new ValidationError('No data provided for insert');\r\n }\r\n const columns = Object.keys(this.insertData[0]);\r\n const colList = columns.map(escapeIdentifier).join(', ');\r\n const rows = this.insertData.map(\r\n (row) => `(${columns.map((c) => escapeSqlValue(row[c] ?? null)).join(', ')})`\r\n );\r\n return `INSERT INTO \"${this.table}\" (${colList}) VALUES ${rows.join(', ')}`;\r\n }\r\n\r\n private buildUpsertSql(): string {\r\n let sql = this.buildInsertSql();\r\n const conflict = this.upsertConflict || 'id';\r\n const columns = Object.keys(this.insertData![0]);\r\n const updateCols = columns\r\n .filter((c) => c !== conflict)\r\n .map((c) => `${escapeIdentifier(c)} = EXCLUDED.${escapeIdentifier(c)}`)\r\n .join(', ');\r\n sql += ` ON CONFLICT (${escapeIdentifier(conflict)}) DO UPDATE SET ${updateCols}`;\r\n return sql;\r\n }\r\n\r\n private buildUpdateSql(): string {\r\n if (!this.updateData || Object.keys(this.updateData).length === 0) {\r\n throw new ValidationError('No data provided for update');\r\n }\r\n if (this.filters.length === 0) {\r\n throw new ValidationError('UPDATE without filters is not allowed — add .eq() or other filters');\r\n }\r\n const setClauses = Object.entries(this.updateData)\r\n .map(([col, val]) => `${escapeIdentifier(col)} = ${escapeSqlValue(val)}`)\r\n .join(', ');\r\n return `UPDATE \"${this.table}\" SET ${setClauses}${this.buildWhereClause()}`;\r\n }\r\n\r\n private buildDeleteSql(): string {\r\n if (this.filters.length === 0) {\r\n throw new ValidationError('DELETE without filters is not allowed — add .eq() or other filters');\r\n }\r\n return `DELETE FROM \"${this.table}\"${this.buildWhereClause()}`;\r\n }\r\n\r\n // --- Execution ---\r\n\r\n private async executeQuery(): Promise<DatabaseResult<T>> {\r\n const isRead = this.operation === 'select' || this.operation === 'count';\r\n\r\n let sql: string;\r\n switch (this.operation) {\r\n case 'select': sql = this.buildSelectSql(); break;\r\n case 'count': sql = this.buildCountSql(); break;\r\n case 'insert': sql = this.buildInsertSql(); break;\r\n case 'upsert': sql = this.buildUpsertSql(); break;\r\n case 'update': sql = this.buildUpdateSql(); break;\r\n case 'delete': sql = this.buildDeleteSql(); break;\r\n default: throw new QueryError(`Unknown operation: ${this.operation}`);\r\n }\r\n\r\n // ONE path (Parity Doctrine W1): every read/write goes through the platform\r\n // proxy. The DatabaseClient supplies serverQuery/serverExec, which attach the\r\n // right credential (end-user JWT → token → origin) — identical in preview and\r\n // deployed, so there is no environment branching here.\r\n const fn = isRead ? this.serverProxy?.serverQuery : this.serverProxy?.serverExec;\r\n if (!fn) throw new QueryError('Database client is not configured', sql);\r\n const data = await fn(sql);\r\n if (!data.success) {\r\n const dbError = mapRpcError(data);\r\n if (dbError) throw dbError;\r\n throw new QueryError(data.error || 'Query failed', sql);\r\n }\r\n return {\r\n success: true,\r\n data: (isRead ? data.data || [] : []) as T[],\r\n rowCount: data.rowCount ?? (isRead ? (data.data?.length || 0) : 0),\r\n schema: data.schema,\r\n };\r\n }\r\n\r\n then<TResult1 = DatabaseResult<T> | SingleResult<T>, TResult2 = never>(\r\n onfulfilled?: ((value: DatabaseResult<T> | SingleResult<T>) => TResult1 | PromiseLike<TResult1>) | null,\r\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\r\n ): Promise<TResult1 | TResult2> {\r\n const promise = this.executeQuery().then((result) => {\r\n if (this.singleMode) {\r\n if (result.data.length === 0) {\r\n throw new QueryError('Expected exactly one row, got 0');\r\n }\r\n return { success: true, data: result.data[0], error: undefined } as SingleResult<T>;\r\n }\r\n if (this.maybeSingleMode) {\r\n return {\r\n success: true,\r\n data: result.data.length > 0 ? result.data[0] : null,\r\n error: undefined,\r\n } as SingleResult<T>;\r\n }\r\n return result;\r\n });\r\n return promise.then(onfulfilled, onrejected);\r\n }\r\n}\r\n","import type { SupabaseClient } from '@supabase/supabase-js';\nimport type { DatabaseResult, MutationResult, SchemaInfo } from './types';\nimport { QueryBuilder } from './QueryBuilder';\nimport { QueryError, ConnectionError } from './errors';\nimport { ezcoder } from '../core/platform';\nimport { features, env } from '../core/config';\nimport { getRuntimeConfig } from '../core/bootstrap';\n\nfunction trackDbEvent(event: string, props: Record<string, unknown>) {\n if (!features.analytics) return;\n try { ezcoder.analytics.track(event, props); } catch { /* non-critical */ }\n}\n\ninterface ProxyResult {\n success: boolean;\n data?: unknown[];\n rowCount?: number;\n error?: string;\n schema?: string;\n exists?: boolean;\n tables?: unknown[];\n tablesCount?: number;\n createdAt?: string;\n lastAccessedAt?: string;\n executed_sql?: string;\n}\n\nconst MAX_RETRIES = 3;\nconst SERVER_TIMEOUT_MS = 8000;\n\n/**\n * ONE data path (Parity Doctrine W1). Every database call — preview & deployed,\n * read & write, anonymous & authenticated, browser & server — goes through the\n * platform proxy `/api/platform/db/{projectId}/{query|execute|schema}`. The\n * ONLY thing that varies is the credential attached:\n *\n * - server mode → Authorization: Bearer <server API key>\n * - logged-in user → Authorization: Bearer <tenant session JWT> (RLS applies)\n * - anonymous + token → X-EzCoder-Public-Token: <public/legacy token>\n * - anonymous, no tok → nothing; the server authorizes by deployment origin\n *\n * No environment branching, no RPC-vs-proxy fork, no preview-only fallback —\n * preview and deployed take byte-identical paths, which is the whole point.\n */\nexport class DatabaseClient {\n private readonly supabase: SupabaseClient | null;\n private readonly projectId: string;\n private apiKey?: string;\n private platformUrl: string;\n private serverMode: boolean = false;\n\n constructor(supabase: SupabaseClient | null, projectId: string) {\n this.supabase = supabase;\n this.projectId = projectId;\n this.platformUrl = env.EZCODER_API_URL || 'https://ezcoder.dev';\n }\n\n static createServerClient(\n projectId: string,\n apiKey: string,\n platformUrl?: string\n ): DatabaseClient {\n const client = new DatabaseClient(null, projectId);\n client.apiKey = apiKey;\n client.platformUrl = platformUrl\n || (typeof process !== 'undefined' && process.env?.EZCODER_API_URL)\n || env.EZCODER_API_URL\n || 'https://ezcoder.dev';\n client.serverMode = true;\n return client;\n }\n\n from<T = Record<string, unknown>>(table: string): QueryBuilder<T> {\n return new QueryBuilder<T>(null as unknown as SupabaseClient, this.projectId, table, {\n serverQuery: (sql: string) => this._proxy('/query', sql),\n serverExec: (sql: string) => this._proxy('/execute', sql),\n });\n }\n\n async sql<T = Record<string, unknown>>(query: string): Promise<DatabaseResult<T>> {\n const trimmed = query.trim().toLowerCase();\n if (!trimmed.startsWith('select')) {\n throw new QueryError('sql() is for read-only queries. Use execute() for mutations.');\n }\n const start = Date.now();\n const result = await this._proxy('/query', query);\n const latencyMs = Date.now() - start;\n if (!result.success) {\n trackDbEvent('db_query', { projectId: this.projectId, latencyMs, success: false, error: result.error });\n throw new QueryError(result.error || 'Query failed', query);\n }\n trackDbEvent('db_query', { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0 });\n return {\n success: true,\n data: (result.data || []) as T[],\n rowCount: result.rowCount ?? (result.data?.length || 0),\n schema: result.schema,\n };\n }\n\n async execute(sql: string): Promise<MutationResult> {\n const start = Date.now();\n const result = await this._proxy('/execute', sql);\n const latencyMs = Date.now() - start;\n if (!result.success) {\n trackDbEvent('db_mutation', { projectId: this.projectId, latencyMs, success: false, error: result.error });\n throw new QueryError(result.error || 'Execution failed', sql);\n }\n trackDbEvent('db_mutation', { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0 });\n return {\n success: true,\n rowCount: result.rowCount ?? 0,\n schema: result.schema,\n executed_sql: result.executed_sql,\n };\n }\n\n async getSchema(): Promise<SchemaInfo> {\n const result = await this._proxy('/schema', null);\n if (!result.success) throw new ConnectionError(result.error || 'Schema fetch failed');\n return {\n exists: result.exists ?? false,\n schema: result.schema,\n tables: (result.tables || []) as SchemaInfo['tables'],\n tablesCount: result.tablesCount ?? 0,\n createdAt: result.createdAt,\n lastAccessedAt: result.lastAccessedAt,\n };\n }\n\n /**\n * Resolve the credential for this call. Order mirrors the server's auth tiers:\n * server key → end-user session JWT → public/legacy token → none (origin).\n */\n private async _authHeaders(): Promise<Record<string, string>> {\n if (this.serverMode && this.apiKey) {\n return { Authorization: `Bearer ${this.apiKey}` };\n }\n // Browser: prefer the logged-in user's session so the server runs the query\n // under their identity and RLS applies.\n if (this.supabase?.auth?.getSession) {\n let sessionResult: { data?: { session?: { access_token?: string } | null } } | undefined;\n try {\n sessionResult = await this.supabase.auth.getSession();\n } catch {\n // getSession THREW (network/refresh race). Do NOT silently fall through\n // to the public/origin path — that runs without per-user RLS and would\n // leak other users' rows to a logged-in user. Send no credential so the\n // server rejects (401) and the app re-authenticates. A NULL session\n // (logged out) is different and DOES fall through below.\n return {};\n }\n const token = sessionResult?.data?.session?.access_token;\n if (token) return { Authorization: `Bearer ${token}` };\n }\n // Anonymous (no session): send the browser-safe project token. Prefer the\n // runtime-bootstrapped token (works even when the build baked none); fall\n // back to the baked token; otherwise send nothing and let the server\n // authorize by origin.\n const pub = getRuntimeConfig()?.publicToken\n || env.EZC_PROJECT_TOKEN_PUBLIC || env.EZC_PROJECT_TOKEN_LEGACY || '';\n if (pub) return { 'X-EzCoder-Public-Token': pub };\n return {};\n }\n\n /** Platform API base — runtime bootstrap wins over the baked value so an\n * env-less build still reaches the right host. */\n private baseUrl(): string {\n return (getRuntimeConfig()?.apiUrl || this.platformUrl).replace(/\\/$/, '');\n }\n\n private async _proxy(endpoint: '/query' | '/execute' | '/schema', sql: string | null): Promise<ProxyResult> {\n const url = `${this.baseUrl()}/api/platform/db/${this.projectId}${endpoint}`;\n const method = endpoint === '/schema' ? 'GET' : 'POST';\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const headers: Record<string, string> = { ...(await this._authHeaders()) };\n if (method === 'POST') headers['Content-Type'] = 'application/json';\n const res = await fetch(url, {\n method,\n headers,\n body: method === 'POST' ? JSON.stringify({ sql }) : undefined,\n signal: AbortSignal.timeout(SERVER_TIMEOUT_MS),\n });\n\n if (res.status === 429) {\n const retryAfter = parseInt(res.headers.get('retry-after') || '60', 10);\n await new Promise(r => setTimeout(r, Math.min(retryAfter, 5) * 1000));\n continue;\n }\n if (res.status === 503) {\n await new Promise(r => setTimeout(r, (attempt + 1) * 2000));\n continue;\n }\n return await res.json() as ProxyResult;\n } catch (e) {\n lastError = e instanceof Error ? e : new Error(String(e));\n // A thrown error (timeout / connection drop) on /execute may mean the\n // write already committed server-side — retrying could double-insert.\n // Reads and schema are idempotent and safe to retry. (429/503 above are\n // explicit \"not processed\" signals and DO retry for all endpoints.)\n if (endpoint === '/execute') break;\n }\n }\n return { success: false, error: lastError?.message || 'Request failed after retries' };\n }\n}\n","import { useEffect, useState, useRef } from 'react';\r\nimport { supabase, isSupabaseConfigured } from '../core/supabase';\r\nimport { env } from '../core/config';\r\n\r\ntype RealtimeEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';\r\n\r\ninterface RealtimeOptions {\r\n event?: RealtimeEvent;\r\n filter?: string;\r\n}\r\n\r\ninterface UseRealtimeReturn<T> {\r\n data: T[];\r\n status: 'connecting' | 'connected' | 'error' | 'disabled';\r\n setData: React.Dispatch<React.SetStateAction<T[]>>;\r\n}\r\n\r\nexport function useRealtime<T extends Record<string, unknown> = Record<string, unknown>>(\r\n table: string,\r\n options: RealtimeOptions = {},\r\n): UseRealtimeReturn<T> {\r\n const [data, setData] = useState<T[]>([]);\r\n const [status, setStatus] = useState<UseRealtimeReturn<T>['status']>('connecting');\r\n const optionsRef = useRef(options);\r\n optionsRef.current = options;\r\n\r\n const projectId = env.EZC_PROJECT_ID;\r\n\r\n useEffect(() => {\r\n if (!isSupabaseConfigured || !projectId) {\r\n setStatus('disabled');\r\n return;\r\n }\r\n\r\n const schemaName = `proj_${projectId.replace(/-/g, '_')}`;\r\n const channelName = `${schemaName}_${table}_${optionsRef.current.event || 'all'}`;\r\n\r\n const channel = supabase\r\n .channel(channelName)\r\n .on(\r\n 'postgres_changes' as never,\r\n {\r\n event: optionsRef.current.event || '*',\r\n schema: schemaName,\r\n table,\r\n filter: optionsRef.current.filter,\r\n } as never,\r\n (payload: { eventType: string; new: Record<string, unknown>; old: Record<string, unknown> }) => {\r\n if (payload.eventType === 'INSERT') {\r\n setData((prev) => [...prev, payload.new as T]);\r\n } else if (payload.eventType === 'UPDATE') {\r\n setData((prev) =>\r\n prev.map((item) =>\r\n (item as Record<string, unknown>).id === (payload.new as Record<string, unknown>).id\r\n ? (payload.new as T)\r\n : item,\r\n ),\r\n );\r\n } else if (payload.eventType === 'DELETE') {\r\n setData((prev) =>\r\n prev.filter(\r\n (item) =>\r\n (item as Record<string, unknown>).id !== (payload.old as Record<string, unknown>).id,\r\n ),\r\n );\r\n }\r\n },\r\n )\r\n .subscribe((subStatus: string) => {\r\n setStatus(subStatus === 'SUBSCRIBED' ? 'connected' : 'connecting');\r\n });\r\n\r\n return () => {\r\n supabase.removeChannel(channel);\r\n };\r\n }, [projectId, table]);\r\n\r\n return { data, status, setData };\r\n}\r\n"],"mappings":"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","names":["supabase","supabase","useEffect","useState","useState","useEffect"]}
|
package/dist/database/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { C as ColumnInfo, D as DatabaseClient, a as DatabaseProvider, b as DatabaseResult, F as FilterValue, c as ForeignKeyInfo, I as IndexInfo, M as MutationResult, O as OrderOption, P as PolicyInfo, Q as QueryBuilder, S as SchemaInfo, d as SingleResult, T as TableInfo, u as useDatabase, e as useDatabaseOptional, f as useIsDatabaseConfigured } from '../DatabaseProvider-
|
|
1
|
+
export { C as ColumnInfo, D as DatabaseClient, a as DatabaseProvider, b as DatabaseResult, F as FilterValue, c as ForeignKeyInfo, I as IndexInfo, M as MutationResult, O as OrderOption, P as PolicyInfo, Q as QueryBuilder, S as SchemaInfo, d as SingleResult, T as TableInfo, u as useDatabase, e as useDatabaseOptional, f as useIsDatabaseConfigured } from '../DatabaseProvider-BtYMJRqh.js';
|
|
2
2
|
import 'react/jsx-runtime';
|
|
3
3
|
import 'react';
|
|
4
4
|
import '@supabase/supabase-js';
|
package/dist/database/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
2
|
import { E as EzcoderClient, A as AuthIntegration } from './types-1uP3V_pe.js';
|
|
3
3
|
export { a as AnalyticsResult, b as AuthUser, C as CheckoutOptions, S as StorageFile, c as StorageResult, d as SubscriptionStatus, e as SubscriptionTier, U as UserProfile } from './types-1uP3V_pe.js';
|
|
4
|
-
export { D as DatabaseClient, a as DatabaseProvider, u as useDatabase } from './DatabaseProvider-
|
|
4
|
+
export { D as DatabaseClient, a as DatabaseProvider, u as useDatabase } from './DatabaseProvider-BtYMJRqh.js';
|
|
5
5
|
import 'react/jsx-runtime';
|
|
6
6
|
import 'react';
|
|
7
7
|
|
|
@@ -31,6 +31,49 @@ declare const features: {
|
|
|
31
31
|
};
|
|
32
32
|
declare function isFeatureConfigured(feature: keyof typeof features): boolean;
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Runtime config bootstrap (Parity Doctrine W2).
|
|
36
|
+
*
|
|
37
|
+
* The SDK fetches /api/sdk/bootstrap ONCE at startup and overlays the result on
|
|
38
|
+
* the build-time-baked `env`. This means a bundle that baked NO env (the
|
|
39
|
+
* herdmark "loads but crashes on boot" class) still works — it learns its
|
|
40
|
+
* config live — and preview and deployed take the identical path.
|
|
41
|
+
*
|
|
42
|
+
* Resolution order for any config value: runtime bootstrap → baked env. The
|
|
43
|
+
* baked env is the OFFLINE FALLBACK, never the primary source. Secrets never
|
|
44
|
+
* appear here — the endpoint returns public config only.
|
|
45
|
+
*/
|
|
46
|
+
interface RuntimeConfig {
|
|
47
|
+
projectId: string;
|
|
48
|
+
apiUrl: string;
|
|
49
|
+
authUrl: string | null;
|
|
50
|
+
authAnonKey: string | null;
|
|
51
|
+
supabaseUrl: string | null;
|
|
52
|
+
supabaseAnonKey: string | null;
|
|
53
|
+
stripePublishableKey: string | null;
|
|
54
|
+
storageBucketPublic: string | null;
|
|
55
|
+
storageBucketPrivate: string | null;
|
|
56
|
+
publicToken: string | null;
|
|
57
|
+
manifest: unknown | null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Fetch the runtime config once (coalesced). Safe to call from multiple
|
|
61
|
+
* providers — only one network request is made. Never throws: on failure it
|
|
62
|
+
* returns null and the SDK falls back to baked env.
|
|
63
|
+
*
|
|
64
|
+
* @param opts.force re-fetch even if cached
|
|
65
|
+
*/
|
|
66
|
+
declare function loadBootstrap(opts?: {
|
|
67
|
+
force?: boolean;
|
|
68
|
+
}): Promise<RuntimeConfig | null>;
|
|
69
|
+
/** The loaded runtime config, or null if not yet loaded / unavailable. */
|
|
70
|
+
declare function getRuntimeConfig(): RuntimeConfig | null;
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a config value preferring runtime bootstrap over baked env. Use this
|
|
73
|
+
* for any SDK-critical value so an env-less build still resolves it at runtime.
|
|
74
|
+
*/
|
|
75
|
+
declare function configValue<K extends keyof RuntimeConfig>(key: K, bakedFallback: string): string;
|
|
76
|
+
|
|
34
77
|
declare const supabase: SupabaseClient;
|
|
35
78
|
declare const isSupabaseConfigured: boolean;
|
|
36
79
|
|
|
@@ -200,4 +243,4 @@ interface UseEzCoderManifestResult {
|
|
|
200
243
|
*/
|
|
201
244
|
declare function useEzCoderManifest(): UseEzCoderManifestResult;
|
|
202
245
|
|
|
203
|
-
export { AuthIntegration, type EnvRefs, EzCoderManifest, EzcoderClient, type RequiredModules, type SanitizedManifest, type UseEzCoderManifestResult, env, ezcoder, ezcoderAuthIntegration, features, isFeatureConfigured, isSupabaseConfigured, supabase, useEzCoderManifest };
|
|
246
|
+
export { AuthIntegration, type EnvRefs, EzCoderManifest, EzcoderClient, type RequiredModules, type RuntimeConfig, type SanitizedManifest, type UseEzCoderManifestResult, configValue, env, ezcoder, ezcoderAuthIntegration, features, getRuntimeConfig, isFeatureConfigured, isSupabaseConfigured, loadBootstrap, supabase, useEzCoderManifest };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DatabaseClient,
|
|
3
3
|
DatabaseProvider,
|
|
4
|
+
configValue,
|
|
5
|
+
getRuntimeConfig,
|
|
6
|
+
loadBootstrap,
|
|
4
7
|
useDatabase
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-O3GL4ZVC.js";
|
|
6
9
|
import {
|
|
7
10
|
ezcoder,
|
|
8
11
|
ezcoderAuthIntegration
|
|
@@ -272,12 +275,15 @@ export {
|
|
|
272
275
|
DatabaseClient,
|
|
273
276
|
DatabaseProvider,
|
|
274
277
|
EzCoderManifest,
|
|
278
|
+
configValue,
|
|
275
279
|
env,
|
|
276
280
|
ezcoder,
|
|
277
281
|
ezcoderAuthIntegration,
|
|
278
282
|
features,
|
|
283
|
+
getRuntimeConfig,
|
|
279
284
|
isFeatureConfigured,
|
|
280
285
|
isSupabaseConfigured,
|
|
286
|
+
loadBootstrap,
|
|
281
287
|
supabase,
|
|
282
288
|
useDatabase,
|
|
283
289
|
useEzCoderManifest
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/manifest-consumer.ts","../src/useEzCoderManifest.ts","../src/index.ts"],"sourcesContent":["/**\r\n * @module manifest-consumer\r\n * @description Runtime + build-time consumer of the EzCoder Capability Manifest.\r\n *\r\n * Track B (B7) — the user-app SDK reads the project's Capability Manifest so\r\n * it knows which feature modules are actually required (auth, storage,\r\n * payments, email, cron, notifications, database) and which env refs to\r\n * expect. The SDK historically auto-registered everything regardless of\r\n * project needs; this consumer adds *opt-in* awareness without breaking\r\n * existing consumers.\r\n *\r\n * Failure mode is deliberately permissive: any fetch/parse error falls\r\n * through to \"all modules considered required\" so a flaky network blip on\r\n * the user's deployed site never silently disables a feature.\r\n */\r\n\r\nimport { env } from './core/config';\r\n\r\n// ─── Public surface ───────────────────────────────────────────────────────\r\n\r\n/**\r\n * The seven modules the SDK can register. Mirrors the keys of\r\n * `service_requirements` in lib/manifest/schema.ts plus `database` derived\r\n * from the `database` requirement block.\r\n */\r\nexport interface RequiredModules {\r\n auth: boolean;\r\n storage: boolean;\r\n payments: boolean;\r\n email: boolean;\r\n cron: boolean;\r\n database: boolean;\r\n notifications: boolean;\r\n}\r\n\r\n/**\r\n * Public + (publicly-visible names of) secret env refs. The server-side\r\n * endpoint deliberately strips `secret` before returning, but we keep the\r\n * field on the consumer type as an empty array for forward-compat — once a\r\n * privileged consumer path exists it can populate it.\r\n */\r\nexport interface EnvRefs {\r\n public: ReadonlyArray<string>;\r\n secret: ReadonlyArray<string>;\r\n}\r\n\r\n/**\r\n * The shape the SDK actually consumes. A trimmed projection of the full\r\n * Capability Manifest; the server endpoint enforces the projection so the\r\n * SDK can never see secret-ref names even if it asks for them.\r\n */\r\nexport interface SanitizedManifest {\r\n manifest_version: number;\r\n project_id: string;\r\n service_requirements: {\r\n database?: { provider: string; rls_required: boolean; tables: ReadonlyArray<string> };\r\n auth?: { provider: string; flows: ReadonlyArray<string> };\r\n storage?: { buckets: ReadonlyArray<string> };\r\n payments?: { mode: string };\r\n email?: { provider: string };\r\n cron?: { jobs: ReadonlyArray<{ name: string; schedule: string }> };\r\n };\r\n env_refs: { public: ReadonlyArray<string> };\r\n validation_gates: ReadonlyArray<string>;\r\n}\r\n\r\n// ─── Module-level cache (keyed by projectId) ──────────────────────────────\r\n\r\nconst manifestCache: Map<string, EzCoderManifest> = new Map();\r\nconst inflightLoads: Map<string, Promise<EzCoderManifest>> = new Map();\r\n\r\n/**\r\n * Default-on RequiredModules — used as the safe fallback when no manifest\r\n * is available. Preserves the SDK's historical behaviour (everything wired)\r\n * so a missing/failed manifest never silently disables a feature in\r\n * already-deployed user apps.\r\n */\r\nconst PERMISSIVE_DEFAULT_MODULES: Readonly<RequiredModules> = Object.freeze({\r\n auth: true,\r\n storage: true,\r\n payments: true,\r\n email: true,\r\n cron: true,\r\n database: true,\r\n notifications: true,\r\n});\r\n\r\n// ─── EzCoderManifest class ────────────────────────────────────────────────\r\n\r\n/**\r\n * Wraps a sanitized manifest payload and exposes module/env-ref queries.\r\n * Instances are immutable — `manifest` is captured at construction time and\r\n * never mutated. Use the static `load()` / `loadFromBuildOutput()` factories\r\n * rather than calling `new` directly so the page-lifetime cache is honoured.\r\n */\r\nexport class EzCoderManifest {\r\n private readonly manifest: SanitizedManifest | null;\r\n private readonly source: 'runtime' | 'build' | 'fallback';\r\n\r\n constructor(manifest: SanitizedManifest | null, source: 'runtime' | 'build' | 'fallback') {\r\n this.manifest = manifest;\r\n this.source = source;\r\n }\r\n\r\n /**\r\n * Fetch the manifest at runtime from the platform API. Returns a\r\n * cached instance for the lifetime of the page; concurrent callers share\r\n * a single in-flight fetch. Falls back to a permissive (all-modules-on)\r\n * instance on any error.\r\n */\r\n static async load(): Promise<EzCoderManifest> {\r\n const projectId = env.EZC_PROJECT_ID;\r\n const apiUrl = env.EZCODER_API_URL;\r\n const publicToken = readPublicToken();\r\n\r\n if (!projectId || !apiUrl) {\r\n return new EzCoderManifest(null, 'fallback');\r\n }\r\n\r\n const cacheKey = `${projectId}::${apiUrl}`;\r\n const cached = manifestCache.get(cacheKey);\r\n if (cached) return cached;\r\n\r\n const inflight = inflightLoads.get(cacheKey);\r\n if (inflight) return inflight;\r\n\r\n const loadPromise = fetchManifest(apiUrl, publicToken)\r\n .then((m) => {\r\n const instance = new EzCoderManifest(m, 'runtime');\r\n manifestCache.set(cacheKey, instance);\r\n return instance;\r\n })\r\n .catch((err: unknown) => {\r\n const message = err instanceof Error ? err.message : 'unknown';\r\n if (typeof console !== 'undefined') {\r\n console.warn(`[EzCoder SDK] manifest fetch failed (${message}); falling back to permissive mode.`);\r\n }\r\n const fallback = new EzCoderManifest(null, 'fallback');\r\n manifestCache.set(cacheKey, fallback);\r\n return fallback;\r\n })\r\n .finally(() => {\r\n inflightLoads.delete(cacheKey);\r\n });\r\n\r\n inflightLoads.set(cacheKey, loadPromise);\r\n return loadPromise;\r\n }\r\n\r\n /**\r\n * Read a manifest written by the build-time codegen step (see\r\n * scripts/fetch-build-manifest.js). Used by SSG callers that want to\r\n * avoid a runtime network round-trip. Returns null when no build-time\r\n * manifest is present — callers should fall back to `load()`.\r\n *\r\n * Implementation note: we deliberately avoid a static `import` because\r\n * (a) the file may not exist, which would be a hard module-resolution\r\n * error, and (b) browser bundles must not pull in the JSON. We probe\r\n * `process` to detect Node, then read the file from disk via the eval'd\r\n * dynamic require — the eval indirection keeps bundlers from following\r\n * the path at build time.\r\n */\r\n static loadFromBuildOutput(): EzCoderManifest | null {\r\n try {\r\n if (typeof process === 'undefined' || !process.versions?.node) {\r\n return null;\r\n }\r\n // Avoid a static `import('fs')` because (a) the file may not exist,\r\n // (b) bundlers would attempt to resolve it for browser targets, and\r\n // (c) we want a Node-only branch the bundler can drop. Construct the\r\n // require lookup via the Function constructor so the literal \"require\"\r\n // identifier never appears in any statically-analyzable position.\r\n const getRequire = new Function(\r\n 'return typeof require === \"function\" ? require : null;',\r\n ) as () => ((id: string) => unknown) | null;\r\n const dynamicRequire = getRequire();\r\n if (!dynamicRequire) return null;\r\n const fs = dynamicRequire('fs') as { readFileSync: (p: string, enc: string) => string };\r\n const path = dynamicRequire('path') as { resolve: (...segments: string[]) => string };\r\n const cwd = process.cwd();\r\n const full = path.resolve(cwd, 'node_modules', '.ezcoder', 'manifest.json');\r\n const raw = fs.readFileSync(full, 'utf8');\r\n const payload: unknown = JSON.parse(raw);\r\n const parsed = parseManifestPayload(payload);\r\n if (!parsed) return null;\r\n return new EzCoderManifest(parsed, 'build');\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Returns the boolean enablement map for each SDK module. When no\r\n * manifest is loaded, returns the permissive default (everything on) so\r\n * legacy behaviour is preserved.\r\n */\r\n getRequiredModules(): RequiredModules {\r\n if (!this.manifest) {\r\n return { ...PERMISSIVE_DEFAULT_MODULES };\r\n }\r\n const sr = this.manifest.service_requirements;\r\n return {\r\n auth: Boolean(sr.auth),\r\n storage: Boolean(sr.storage),\r\n payments: Boolean(sr.payments),\r\n email: Boolean(sr.email),\r\n cron: Boolean(sr.cron),\r\n database: Boolean(sr.database),\r\n // notifications is not in service_requirements v0; derive from email\r\n // OR auth so the existing notifications module behaves sensibly.\r\n notifications: Boolean(sr.email || sr.auth),\r\n };\r\n }\r\n\r\n /**\r\n * Returns the env refs declared by the manifest. The `secret` array is\r\n * always empty when the manifest came from the public SDK endpoint —\r\n * server-only refs are stripped before transit (see pages/api/sdk/manifest.js).\r\n */\r\n getEnvRefs(): EnvRefs {\r\n if (!this.manifest) {\r\n return { public: [], secret: [] };\r\n }\r\n return {\r\n public: this.manifest.env_refs.public,\r\n secret: [],\r\n };\r\n }\r\n\r\n /**\r\n * Convenience: is a given module required by this app?\r\n */\r\n has(module: keyof RequiredModules): boolean {\r\n return this.getRequiredModules()[module];\r\n }\r\n\r\n /**\r\n * Where the manifest was loaded from. Useful for debugging and for\r\n * surface-level telemetry (`fallback` means we never got the real one).\r\n */\r\n getSource(): 'runtime' | 'build' | 'fallback' {\r\n return this.source;\r\n }\r\n\r\n /**\r\n * Test-only helper: wipe the page-lifetime cache. Never call this in\r\n * production code.\r\n */\r\n static __resetCacheForTests(): void {\r\n manifestCache.clear();\r\n inflightLoads.clear();\r\n }\r\n}\r\n\r\n// ─── Internals ────────────────────────────────────────────────────────────\r\n\r\nfunction readPublicToken(): string {\r\n // The PUBLIC token is intentionally distinct from the server-only secret\r\n // key. We accept both Vite and Next env shapes; both must be safe to\r\n // expose to the browser (i.e. namespaced as PUBLIC).\r\n const fromVite = safeEnv('VITE_EZC_PROJECT_TOKEN_PUBLIC');\r\n if (fromVite) return fromVite;\r\n const fromNext = safeEnv('NEXT_PUBLIC_EZC_PROJECT_TOKEN_PUBLIC');\r\n if (fromNext) return fromNext;\r\n return safeEnv('EZC_PROJECT_TOKEN_PUBLIC');\r\n}\r\n\r\nfunction safeEnv(key: string): string {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const v = (import.meta.env as Record<string, unknown>)[key];\r\n if (typeof v === 'string') return v;\r\n }\r\n } catch {\r\n // import.meta unavailable in this runtime — fall through.\r\n }\r\n try {\r\n if (typeof process !== 'undefined' && process.env) {\r\n return process.env[key] || '';\r\n }\r\n } catch {\r\n // process unavailable — fall through.\r\n }\r\n return '';\r\n}\r\n\r\nasync function fetchManifest(apiUrl: string, publicToken: string): Promise<SanitizedManifest> {\r\n const headers: Record<string, string> = {\r\n 'Accept': 'application/json',\r\n };\r\n if (publicToken) {\r\n headers['X-EzCoder-Public-Token'] = publicToken;\r\n }\r\n const res = await fetch(`${apiUrl}/api/sdk/manifest`, {\r\n method: 'GET',\r\n headers,\r\n // Browsers will fold the 5-minute cache header automatically; this hint\r\n // is here for non-browser runtimes that respect it.\r\n cache: 'default',\r\n });\r\n if (!res.ok) {\r\n throw new Error(`HTTP ${res.status}`);\r\n }\r\n const body: unknown = await res.json();\r\n const parsed = parseManifestPayload(body);\r\n if (!parsed) {\r\n throw new Error('manifest payload failed shape validation');\r\n }\r\n return parsed;\r\n}\r\n\r\n/**\r\n * Permissive boundary parser. We deliberately do NOT pull Zod into the SDK\r\n * bundle — instead we run a lightweight shape check and let unknown fields\r\n * pass through. The server has already validated the manifest with Zod\r\n * before sending; this is just defense-in-depth against a malformed\r\n * response (e.g. an HTML error page).\r\n */\r\nfunction parseManifestPayload(input: unknown): SanitizedManifest | null {\r\n if (!isRecord(input)) return null;\r\n const projectId = input.project_id;\r\n const serviceReqs = input.service_requirements;\r\n const envRefs = input.env_refs;\r\n const gates = input.validation_gates;\r\n if (typeof projectId !== 'string' || projectId.length === 0) return null;\r\n if (!isRecord(serviceReqs)) return null;\r\n if (!isRecord(envRefs) || !Array.isArray(envRefs.public)) return null;\r\n if (!Array.isArray(gates)) return null;\r\n const manifestVersion =\r\n typeof input.manifest_version === 'number' ? input.manifest_version : 0;\r\n return {\r\n manifest_version: manifestVersion,\r\n project_id: projectId,\r\n service_requirements: serviceReqs as SanitizedManifest['service_requirements'],\r\n env_refs: { public: envRefs.public.filter((v): v is string => typeof v === 'string') },\r\n validation_gates: gates.filter((v): v is string => typeof v === 'string'),\r\n };\r\n}\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value);\r\n}\r\n","/**\r\n * @module useEzCoderManifest\r\n * @description React hook that exposes the loaded Capability Manifest to\r\n * components so they can branch on whether a feature module is required by\r\n * the current project.\r\n *\r\n * Importing this file pulls `react` as a peer dep — kept in a dedicated\r\n * file so SSR / non-React entries can consume `manifest-consumer.ts`\r\n * without touching React.\r\n */\r\n\r\nimport { useEffect, useState } from 'react';\r\nimport { EzCoderManifest, type RequiredModules } from './manifest-consumer';\r\n\r\nexport interface UseEzCoderManifestResult {\r\n manifest: EzCoderManifest | null;\r\n modules: RequiredModules | null;\r\n loading: boolean;\r\n source: 'runtime' | 'build' | 'fallback' | null;\r\n}\r\n\r\n/**\r\n * Synchronously prefers a build-time manifest (avoids a render-blocking\r\n * fetch on SSG/SSR); otherwise kicks off the async runtime load. Until the\r\n * load resolves, `modules` is null and callers should treat features as\r\n * unknown (recommended: render the historical default, then re-render once\r\n * `modules` settles).\r\n */\r\nexport function useEzCoderManifest(): UseEzCoderManifestResult {\r\n // Lazy initializer — `loadFromBuildOutput` touches the filesystem on Node\r\n // and must NOT run on every render. Memoized once across the mount.\r\n const [manifest, setManifest] = useState<EzCoderManifest | null>(\r\n () => EzCoderManifest.loadFromBuildOutput(),\r\n );\r\n const [loading, setLoading] = useState<boolean>(manifest === null);\r\n\r\n useEffect(() => {\r\n // If we already have a build-time manifest, no need to fetch.\r\n if (manifest) return;\r\n\r\n let cancelled = false;\r\n EzCoderManifest.load()\r\n .then((m) => {\r\n if (cancelled) return;\r\n setManifest(m);\r\n setLoading(false);\r\n })\r\n .catch(() => {\r\n if (cancelled) return;\r\n // load() already handles errors and returns a fallback, but be\r\n // defensive: ensure we never leave the UI stuck in loading state.\r\n setLoading(false);\r\n });\r\n\r\n return () => {\r\n cancelled = true;\r\n };\r\n // We only want this effect to run on mount; once `manifest` settles,\r\n // re-running would re-fetch unnecessarily.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, []);\r\n\r\n return {\r\n manifest,\r\n modules: manifest ? manifest.getRequiredModules() : null,\r\n loading,\r\n source: manifest ? manifest.getSource() : null,\r\n };\r\n}\r\n","export { env, features, isFeatureConfigured } from './core/config';\r\nexport { supabase, isSupabaseConfigured } from './core/supabase';\r\nexport { ezcoder, ezcoderAuthIntegration } from './core/platform';\r\nexport { DatabaseProvider, useDatabase, DatabaseClient } from './database';\r\nexport type {\r\n AuthUser,\r\n UserProfile,\r\n SubscriptionTier,\r\n SubscriptionStatus,\r\n StorageResult,\r\n StorageFile,\r\n CheckoutOptions,\r\n EzcoderClient,\r\n AnalyticsResult,\r\n AuthIntegration,\r\n} from './core/types';\r\n\r\n// ─── Capability Manifest (B7) ────────────────────────────────────────────\r\n//\r\n// Manifest awareness is OPT-IN. The legacy module auto-registration is\r\n// preserved, so existing consumers see no behavioural change. New code can\r\n// import these to gate features:\r\n//\r\n// import { EzCoderManifest, useEzCoderManifest } from '@ezcoder.dev/sdk';\r\n// const { modules, loading } = useEzCoderManifest();\r\n// if (!loading && modules?.payments) renderCheckout();\r\n//\r\nexport { EzCoderManifest } from './manifest-consumer';\r\nexport type { RequiredModules, EnvRefs, SanitizedManifest } from './manifest-consumer';\r\nexport { useEzCoderManifest } from './useEzCoderManifest';\r\nexport type { UseEzCoderManifestResult } from './useEzCoderManifest';\r\n\r\n// Kick off an opportunistic load on import so the cache is warm by the\r\n// time components call useEzCoderManifest(). Errors are swallowed inside\r\n// load() and produce a fallback instance — never throws.\r\nimport { EzCoderManifest as _EzCoderManifestForBoot } from './manifest-consumer';\r\nif (typeof window !== 'undefined') {\r\n // Browser only. SSR/SSG paths that want the manifest should call\r\n // loadFromBuildOutput() or load() explicitly to control timing.\r\n void _EzCoderManifestForBoot.load();\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/manifest-consumer.ts","../src/useEzCoderManifest.ts","../src/index.ts"],"sourcesContent":["/**\r\n * @module manifest-consumer\r\n * @description Runtime + build-time consumer of the EzCoder Capability Manifest.\r\n *\r\n * Track B (B7) — the user-app SDK reads the project's Capability Manifest so\r\n * it knows which feature modules are actually required (auth, storage,\r\n * payments, email, cron, notifications, database) and which env refs to\r\n * expect. The SDK historically auto-registered everything regardless of\r\n * project needs; this consumer adds *opt-in* awareness without breaking\r\n * existing consumers.\r\n *\r\n * Failure mode is deliberately permissive: any fetch/parse error falls\r\n * through to \"all modules considered required\" so a flaky network blip on\r\n * the user's deployed site never silently disables a feature.\r\n */\r\n\r\nimport { env } from './core/config';\r\n\r\n// ─── Public surface ───────────────────────────────────────────────────────\r\n\r\n/**\r\n * The seven modules the SDK can register. Mirrors the keys of\r\n * `service_requirements` in lib/manifest/schema.ts plus `database` derived\r\n * from the `database` requirement block.\r\n */\r\nexport interface RequiredModules {\r\n auth: boolean;\r\n storage: boolean;\r\n payments: boolean;\r\n email: boolean;\r\n cron: boolean;\r\n database: boolean;\r\n notifications: boolean;\r\n}\r\n\r\n/**\r\n * Public + (publicly-visible names of) secret env refs. The server-side\r\n * endpoint deliberately strips `secret` before returning, but we keep the\r\n * field on the consumer type as an empty array for forward-compat — once a\r\n * privileged consumer path exists it can populate it.\r\n */\r\nexport interface EnvRefs {\r\n public: ReadonlyArray<string>;\r\n secret: ReadonlyArray<string>;\r\n}\r\n\r\n/**\r\n * The shape the SDK actually consumes. A trimmed projection of the full\r\n * Capability Manifest; the server endpoint enforces the projection so the\r\n * SDK can never see secret-ref names even if it asks for them.\r\n */\r\nexport interface SanitizedManifest {\r\n manifest_version: number;\r\n project_id: string;\r\n service_requirements: {\r\n database?: { provider: string; rls_required: boolean; tables: ReadonlyArray<string> };\r\n auth?: { provider: string; flows: ReadonlyArray<string> };\r\n storage?: { buckets: ReadonlyArray<string> };\r\n payments?: { mode: string };\r\n email?: { provider: string };\r\n cron?: { jobs: ReadonlyArray<{ name: string; schedule: string }> };\r\n };\r\n env_refs: { public: ReadonlyArray<string> };\r\n validation_gates: ReadonlyArray<string>;\r\n}\r\n\r\n// ─── Module-level cache (keyed by projectId) ──────────────────────────────\r\n\r\nconst manifestCache: Map<string, EzCoderManifest> = new Map();\r\nconst inflightLoads: Map<string, Promise<EzCoderManifest>> = new Map();\r\n\r\n/**\r\n * Default-on RequiredModules — used as the safe fallback when no manifest\r\n * is available. Preserves the SDK's historical behaviour (everything wired)\r\n * so a missing/failed manifest never silently disables a feature in\r\n * already-deployed user apps.\r\n */\r\nconst PERMISSIVE_DEFAULT_MODULES: Readonly<RequiredModules> = Object.freeze({\r\n auth: true,\r\n storage: true,\r\n payments: true,\r\n email: true,\r\n cron: true,\r\n database: true,\r\n notifications: true,\r\n});\r\n\r\n// ─── EzCoderManifest class ────────────────────────────────────────────────\r\n\r\n/**\r\n * Wraps a sanitized manifest payload and exposes module/env-ref queries.\r\n * Instances are immutable — `manifest` is captured at construction time and\r\n * never mutated. Use the static `load()` / `loadFromBuildOutput()` factories\r\n * rather than calling `new` directly so the page-lifetime cache is honoured.\r\n */\r\nexport class EzCoderManifest {\r\n private readonly manifest: SanitizedManifest | null;\r\n private readonly source: 'runtime' | 'build' | 'fallback';\r\n\r\n constructor(manifest: SanitizedManifest | null, source: 'runtime' | 'build' | 'fallback') {\r\n this.manifest = manifest;\r\n this.source = source;\r\n }\r\n\r\n /**\r\n * Fetch the manifest at runtime from the platform API. Returns a\r\n * cached instance for the lifetime of the page; concurrent callers share\r\n * a single in-flight fetch. Falls back to a permissive (all-modules-on)\r\n * instance on any error.\r\n */\r\n static async load(): Promise<EzCoderManifest> {\r\n const projectId = env.EZC_PROJECT_ID;\r\n const apiUrl = env.EZCODER_API_URL;\r\n const publicToken = readPublicToken();\r\n\r\n if (!projectId || !apiUrl) {\r\n return new EzCoderManifest(null, 'fallback');\r\n }\r\n\r\n const cacheKey = `${projectId}::${apiUrl}`;\r\n const cached = manifestCache.get(cacheKey);\r\n if (cached) return cached;\r\n\r\n const inflight = inflightLoads.get(cacheKey);\r\n if (inflight) return inflight;\r\n\r\n const loadPromise = fetchManifest(apiUrl, publicToken)\r\n .then((m) => {\r\n const instance = new EzCoderManifest(m, 'runtime');\r\n manifestCache.set(cacheKey, instance);\r\n return instance;\r\n })\r\n .catch((err: unknown) => {\r\n const message = err instanceof Error ? err.message : 'unknown';\r\n if (typeof console !== 'undefined') {\r\n console.warn(`[EzCoder SDK] manifest fetch failed (${message}); falling back to permissive mode.`);\r\n }\r\n const fallback = new EzCoderManifest(null, 'fallback');\r\n manifestCache.set(cacheKey, fallback);\r\n return fallback;\r\n })\r\n .finally(() => {\r\n inflightLoads.delete(cacheKey);\r\n });\r\n\r\n inflightLoads.set(cacheKey, loadPromise);\r\n return loadPromise;\r\n }\r\n\r\n /**\r\n * Read a manifest written by the build-time codegen step (see\r\n * scripts/fetch-build-manifest.js). Used by SSG callers that want to\r\n * avoid a runtime network round-trip. Returns null when no build-time\r\n * manifest is present — callers should fall back to `load()`.\r\n *\r\n * Implementation note: we deliberately avoid a static `import` because\r\n * (a) the file may not exist, which would be a hard module-resolution\r\n * error, and (b) browser bundles must not pull in the JSON. We probe\r\n * `process` to detect Node, then read the file from disk via the eval'd\r\n * dynamic require — the eval indirection keeps bundlers from following\r\n * the path at build time.\r\n */\r\n static loadFromBuildOutput(): EzCoderManifest | null {\r\n try {\r\n if (typeof process === 'undefined' || !process.versions?.node) {\r\n return null;\r\n }\r\n // Avoid a static `import('fs')` because (a) the file may not exist,\r\n // (b) bundlers would attempt to resolve it for browser targets, and\r\n // (c) we want a Node-only branch the bundler can drop. Construct the\r\n // require lookup via the Function constructor so the literal \"require\"\r\n // identifier never appears in any statically-analyzable position.\r\n const getRequire = new Function(\r\n 'return typeof require === \"function\" ? require : null;',\r\n ) as () => ((id: string) => unknown) | null;\r\n const dynamicRequire = getRequire();\r\n if (!dynamicRequire) return null;\r\n const fs = dynamicRequire('fs') as { readFileSync: (p: string, enc: string) => string };\r\n const path = dynamicRequire('path') as { resolve: (...segments: string[]) => string };\r\n const cwd = process.cwd();\r\n const full = path.resolve(cwd, 'node_modules', '.ezcoder', 'manifest.json');\r\n const raw = fs.readFileSync(full, 'utf8');\r\n const payload: unknown = JSON.parse(raw);\r\n const parsed = parseManifestPayload(payload);\r\n if (!parsed) return null;\r\n return new EzCoderManifest(parsed, 'build');\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Returns the boolean enablement map for each SDK module. When no\r\n * manifest is loaded, returns the permissive default (everything on) so\r\n * legacy behaviour is preserved.\r\n */\r\n getRequiredModules(): RequiredModules {\r\n if (!this.manifest) {\r\n return { ...PERMISSIVE_DEFAULT_MODULES };\r\n }\r\n const sr = this.manifest.service_requirements;\r\n return {\r\n auth: Boolean(sr.auth),\r\n storage: Boolean(sr.storage),\r\n payments: Boolean(sr.payments),\r\n email: Boolean(sr.email),\r\n cron: Boolean(sr.cron),\r\n database: Boolean(sr.database),\r\n // notifications is not in service_requirements v0; derive from email\r\n // OR auth so the existing notifications module behaves sensibly.\r\n notifications: Boolean(sr.email || sr.auth),\r\n };\r\n }\r\n\r\n /**\r\n * Returns the env refs declared by the manifest. The `secret` array is\r\n * always empty when the manifest came from the public SDK endpoint —\r\n * server-only refs are stripped before transit (see pages/api/sdk/manifest.js).\r\n */\r\n getEnvRefs(): EnvRefs {\r\n if (!this.manifest) {\r\n return { public: [], secret: [] };\r\n }\r\n return {\r\n public: this.manifest.env_refs.public,\r\n secret: [],\r\n };\r\n }\r\n\r\n /**\r\n * Convenience: is a given module required by this app?\r\n */\r\n has(module: keyof RequiredModules): boolean {\r\n return this.getRequiredModules()[module];\r\n }\r\n\r\n /**\r\n * Where the manifest was loaded from. Useful for debugging and for\r\n * surface-level telemetry (`fallback` means we never got the real one).\r\n */\r\n getSource(): 'runtime' | 'build' | 'fallback' {\r\n return this.source;\r\n }\r\n\r\n /**\r\n * Test-only helper: wipe the page-lifetime cache. Never call this in\r\n * production code.\r\n */\r\n static __resetCacheForTests(): void {\r\n manifestCache.clear();\r\n inflightLoads.clear();\r\n }\r\n}\r\n\r\n// ─── Internals ────────────────────────────────────────────────────────────\r\n\r\nfunction readPublicToken(): string {\r\n // The PUBLIC token is intentionally distinct from the server-only secret\r\n // key. We accept both Vite and Next env shapes; both must be safe to\r\n // expose to the browser (i.e. namespaced as PUBLIC).\r\n const fromVite = safeEnv('VITE_EZC_PROJECT_TOKEN_PUBLIC');\r\n if (fromVite) return fromVite;\r\n const fromNext = safeEnv('NEXT_PUBLIC_EZC_PROJECT_TOKEN_PUBLIC');\r\n if (fromNext) return fromNext;\r\n return safeEnv('EZC_PROJECT_TOKEN_PUBLIC');\r\n}\r\n\r\nfunction safeEnv(key: string): string {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const v = (import.meta.env as Record<string, unknown>)[key];\r\n if (typeof v === 'string') return v;\r\n }\r\n } catch {\r\n // import.meta unavailable in this runtime — fall through.\r\n }\r\n try {\r\n if (typeof process !== 'undefined' && process.env) {\r\n return process.env[key] || '';\r\n }\r\n } catch {\r\n // process unavailable — fall through.\r\n }\r\n return '';\r\n}\r\n\r\nasync function fetchManifest(apiUrl: string, publicToken: string): Promise<SanitizedManifest> {\r\n const headers: Record<string, string> = {\r\n 'Accept': 'application/json',\r\n };\r\n if (publicToken) {\r\n headers['X-EzCoder-Public-Token'] = publicToken;\r\n }\r\n const res = await fetch(`${apiUrl}/api/sdk/manifest`, {\r\n method: 'GET',\r\n headers,\r\n // Browsers will fold the 5-minute cache header automatically; this hint\r\n // is here for non-browser runtimes that respect it.\r\n cache: 'default',\r\n });\r\n if (!res.ok) {\r\n throw new Error(`HTTP ${res.status}`);\r\n }\r\n const body: unknown = await res.json();\r\n const parsed = parseManifestPayload(body);\r\n if (!parsed) {\r\n throw new Error('manifest payload failed shape validation');\r\n }\r\n return parsed;\r\n}\r\n\r\n/**\r\n * Permissive boundary parser. We deliberately do NOT pull Zod into the SDK\r\n * bundle — instead we run a lightweight shape check and let unknown fields\r\n * pass through. The server has already validated the manifest with Zod\r\n * before sending; this is just defense-in-depth against a malformed\r\n * response (e.g. an HTML error page).\r\n */\r\nfunction parseManifestPayload(input: unknown): SanitizedManifest | null {\r\n if (!isRecord(input)) return null;\r\n const projectId = input.project_id;\r\n const serviceReqs = input.service_requirements;\r\n const envRefs = input.env_refs;\r\n const gates = input.validation_gates;\r\n if (typeof projectId !== 'string' || projectId.length === 0) return null;\r\n if (!isRecord(serviceReqs)) return null;\r\n if (!isRecord(envRefs) || !Array.isArray(envRefs.public)) return null;\r\n if (!Array.isArray(gates)) return null;\r\n const manifestVersion =\r\n typeof input.manifest_version === 'number' ? input.manifest_version : 0;\r\n return {\r\n manifest_version: manifestVersion,\r\n project_id: projectId,\r\n service_requirements: serviceReqs as SanitizedManifest['service_requirements'],\r\n env_refs: { public: envRefs.public.filter((v): v is string => typeof v === 'string') },\r\n validation_gates: gates.filter((v): v is string => typeof v === 'string'),\r\n };\r\n}\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value);\r\n}\r\n","/**\r\n * @module useEzCoderManifest\r\n * @description React hook that exposes the loaded Capability Manifest to\r\n * components so they can branch on whether a feature module is required by\r\n * the current project.\r\n *\r\n * Importing this file pulls `react` as a peer dep — kept in a dedicated\r\n * file so SSR / non-React entries can consume `manifest-consumer.ts`\r\n * without touching React.\r\n */\r\n\r\nimport { useEffect, useState } from 'react';\r\nimport { EzCoderManifest, type RequiredModules } from './manifest-consumer';\r\n\r\nexport interface UseEzCoderManifestResult {\r\n manifest: EzCoderManifest | null;\r\n modules: RequiredModules | null;\r\n loading: boolean;\r\n source: 'runtime' | 'build' | 'fallback' | null;\r\n}\r\n\r\n/**\r\n * Synchronously prefers a build-time manifest (avoids a render-blocking\r\n * fetch on SSG/SSR); otherwise kicks off the async runtime load. Until the\r\n * load resolves, `modules` is null and callers should treat features as\r\n * unknown (recommended: render the historical default, then re-render once\r\n * `modules` settles).\r\n */\r\nexport function useEzCoderManifest(): UseEzCoderManifestResult {\r\n // Lazy initializer — `loadFromBuildOutput` touches the filesystem on Node\r\n // and must NOT run on every render. Memoized once across the mount.\r\n const [manifest, setManifest] = useState<EzCoderManifest | null>(\r\n () => EzCoderManifest.loadFromBuildOutput(),\r\n );\r\n const [loading, setLoading] = useState<boolean>(manifest === null);\r\n\r\n useEffect(() => {\r\n // If we already have a build-time manifest, no need to fetch.\r\n if (manifest) return;\r\n\r\n let cancelled = false;\r\n EzCoderManifest.load()\r\n .then((m) => {\r\n if (cancelled) return;\r\n setManifest(m);\r\n setLoading(false);\r\n })\r\n .catch(() => {\r\n if (cancelled) return;\r\n // load() already handles errors and returns a fallback, but be\r\n // defensive: ensure we never leave the UI stuck in loading state.\r\n setLoading(false);\r\n });\r\n\r\n return () => {\r\n cancelled = true;\r\n };\r\n // We only want this effect to run on mount; once `manifest` settles,\r\n // re-running would re-fetch unnecessarily.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, []);\r\n\r\n return {\r\n manifest,\r\n modules: manifest ? manifest.getRequiredModules() : null,\r\n loading,\r\n source: manifest ? manifest.getSource() : null,\r\n };\r\n}\r\n","export { env, features, isFeatureConfigured } from './core/config';\r\nexport { loadBootstrap, getRuntimeConfig, configValue } from './core/bootstrap';\r\nexport type { RuntimeConfig } from './core/bootstrap';\r\nexport { supabase, isSupabaseConfigured } from './core/supabase';\r\nexport { ezcoder, ezcoderAuthIntegration } from './core/platform';\r\nexport { DatabaseProvider, useDatabase, DatabaseClient } from './database';\r\nexport type {\r\n AuthUser,\r\n UserProfile,\r\n SubscriptionTier,\r\n SubscriptionStatus,\r\n StorageResult,\r\n StorageFile,\r\n CheckoutOptions,\r\n EzcoderClient,\r\n AnalyticsResult,\r\n AuthIntegration,\r\n} from './core/types';\r\n\r\n// ─── Capability Manifest (B7) ────────────────────────────────────────────\r\n//\r\n// Manifest awareness is OPT-IN. The legacy module auto-registration is\r\n// preserved, so existing consumers see no behavioural change. New code can\r\n// import these to gate features:\r\n//\r\n// import { EzCoderManifest, useEzCoderManifest } from '@ezcoder.dev/sdk';\r\n// const { modules, loading } = useEzCoderManifest();\r\n// if (!loading && modules?.payments) renderCheckout();\r\n//\r\nexport { EzCoderManifest } from './manifest-consumer';\r\nexport type { RequiredModules, EnvRefs, SanitizedManifest } from './manifest-consumer';\r\nexport { useEzCoderManifest } from './useEzCoderManifest';\r\nexport type { UseEzCoderManifestResult } from './useEzCoderManifest';\r\n\r\n// Kick off an opportunistic load on import so the cache is warm by the\r\n// time components call useEzCoderManifest(). Errors are swallowed inside\r\n// load() and produce a fallback instance — never throws.\r\nimport { EzCoderManifest as _EzCoderManifestForBoot } from './manifest-consumer';\r\nif (typeof window !== 'undefined') {\r\n // Browser only. SSR/SSG paths that want the manifest should call\r\n // loadFromBuildOutput() or load() explicitly to control timing.\r\n void _EzCoderManifestForBoot.load();\r\n}\r\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/database/DatabaseProvider.tsx","../src/database/errors.ts","../src/database/publicReadProxy.ts","../src/database/QueryBuilder.ts","../src/database/DatabaseClient.ts","../src/database/useRealtime.ts"],"sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from 'react';\r\nimport { supabase, isSupabaseConfigured } from '../core/supabase';\r\nimport { env } from '../core/config';\r\nimport { DatabaseClient } from './DatabaseClient';\r\n\r\ninterface DatabaseContextValue {\r\n db: DatabaseClient | null;\r\n isConfigured: boolean;\r\n}\r\n\r\nconst DatabaseContext = createContext<DatabaseContextValue>({\r\n db: null,\r\n isConfigured: false,\r\n});\r\n\r\ninterface DatabaseProviderProps {\r\n children: ReactNode;\r\n projectId?: string;\r\n}\r\n\r\nexport function DatabaseProvider({ children, projectId }: DatabaseProviderProps) {\r\n const resolvedProjectId = projectId || env.EZC_PROJECT_ID;\r\n const configured = isSupabaseConfigured && Boolean(resolvedProjectId);\r\n\r\n const db = useMemo(\r\n () => (configured ? new DatabaseClient(supabase, resolvedProjectId) : null),\r\n [configured, resolvedProjectId],\r\n );\r\n\r\n const value = useMemo(() => ({ db, isConfigured: configured }), [db, configured]);\r\n\r\n return <DatabaseContext.Provider value={value}>{children}</DatabaseContext.Provider>;\r\n}\r\n\r\nexport function useDatabase(): DatabaseClient {\r\n const { db } = useContext(DatabaseContext);\r\n if (!db) {\r\n throw new Error(\r\n 'useDatabase() must be used within <DatabaseProvider>. ' +\r\n 'Ensure EZC_PROJECT_ID and Supabase credentials are configured.',\r\n );\r\n }\r\n return db;\r\n}\r\n\r\nexport function useDatabaseOptional(): DatabaseClient | null {\r\n const { db } = useContext(DatabaseContext);\r\n return db;\r\n}\r\n\r\nexport function useIsDatabaseConfigured(): boolean {\r\n const { isConfigured } = useContext(DatabaseContext);\r\n return isConfigured;\r\n}\r\n","export class DatabaseError extends Error {\r\n readonly code: string;\r\n readonly statusCode: number;\r\n\r\n constructor(message: string, code = 'DATABASE_ERROR', statusCode = 500) {\r\n super(message);\r\n this.name = 'DatabaseError';\r\n this.code = code;\r\n this.statusCode = statusCode;\r\n }\r\n}\r\n\r\nexport class QueryError extends DatabaseError {\r\n readonly sql?: string;\r\n\r\n constructor(message: string, sql?: string) {\r\n super(message, 'QUERY_ERROR', 400);\r\n this.name = 'QueryError';\r\n this.sql = sql;\r\n }\r\n}\r\n\r\nexport class ValidationError extends DatabaseError {\r\n constructor(message: string) {\r\n super(message, 'VALIDATION_ERROR', 422);\r\n this.name = 'ValidationError';\r\n }\r\n}\r\n\r\nexport class ConnectionError extends DatabaseError {\r\n constructor(message: string) {\r\n super(message, 'CONNECTION_ERROR', 503);\r\n this.name = 'ConnectionError';\r\n }\r\n}\r\n\r\nexport class NotFoundError extends DatabaseError {\r\n constructor(message = 'Record not found') {\r\n super(message, 'NOT_FOUND', 404);\r\n this.name = 'NotFoundError';\r\n }\r\n}\r\n\r\nexport function mapRpcError(rpcResult: { success: boolean; error?: string; code?: string }): DatabaseError | null {\r\n if (rpcResult.success) return null;\r\n const msg = rpcResult.error || 'Unknown database error';\r\n if (msg.includes('No database schema exists')) return new NotFoundError(msg);\r\n if (msg.includes('Authentication required')) return new ConnectionError(msg);\r\n if (msg.includes('blocked') || msg.includes('forbidden')) return new ValidationError(msg);\r\n return new QueryError(msg);\r\n}\r\n","/**\n * Public read proxy — anonymous-visitor data reads for deployed sites.\n *\n * The direct browser path (supabase.rpc('sdk_query_project')) requires an\n * authenticated, project-bound session by design (tenant migrations 200/201/\n * 227: the RPC executes raw SELECTs, so anon was deliberately revoked). That\n * left every data-driven section of a deployed site empty for visitors.\n *\n * This module routes READ-ONLY queries through the platform's DB proxy\n * (`POST {EZCODER_API_URL}/api/platform/db/{projectId}/query`) authenticated\n * with the browser-safe PUBLIC-class project token (EZC_PROJECT_TOKEN_PUBLIC,\n * baked into the bundle at build time). Server-side that proxy runs the same\n * hardened tenant function (SELECT-only, project-schema-scoped, system/auth\n * schemas blocked, LIMIT-capped, rate-limited) plus a credential-table guard\n * for public-class tokens. Writes never use this path.\n */\n\nimport { env } from '../core/config';\n\nexport interface PublicProxyResult {\n success: boolean;\n data?: unknown[];\n rowCount?: number;\n error?: string;\n schema?: string;\n}\n\nconst PROXY_TIMEOUT_MS = 10000;\n\n/** Failures that mean \"the RPC path needs an authenticated session\" — the\n * signal to fall back to the public proxy rather than surface the error. */\nconst AUTH_FAILURE_PATTERN =\n /authentication required|permission denied|project binding|unauthorized|jwt|42501/i;\n\nexport function isAuthShapedFailure(message: string | null | undefined): boolean {\n return typeof message === 'string' && AUTH_FAILURE_PATTERN.test(message);\n}\n\n/**\n * The browser-safe token to authenticate public reads with. Prefer the\n * bounded-blast-radius PUBLIC-class token (ezc_pub_*); fall back to the legacy\n * single-class token (ezc_*), which is ALSO baked into every deployed bundle\n * and which the platform read proxy accepts. The fallback covers projects\n * whose B1 token-pair provisioning hasn't run or failed (the public-class env\n * var bakes empty) — without it those sites get no data (incident 2026-06-11,\n * herdmark: project_credentials empty → VITE_EZC_PROJECT_TOKEN_PUBLIC empty).\n */\nexport function publicReadToken(): string {\n return env.EZC_PROJECT_TOKEN_PUBLIC || env.EZC_PROJECT_TOKEN_LEGACY || '';\n}\n\n/**\n * The public proxy is usable whenever we know where to send the request. A\n * token is NOT required: the platform proxy can authorize a browser read by\n * the deployment's validated origin (origin→project binding) when no token is\n * baked — which is the resilient path for projects whose token provisioning\n * failed (incident 2026-06-11). If a token IS baked it's sent and preferred.\n */\nexport function publicReadAvailable(): boolean {\n return Boolean(env.EZCODER_API_URL);\n}\n\n// Once a query has fallen back because the visitor has no session, later\n// reads skip the doomed RPC round-trip. Never set unless the proxy SUCCEEDED,\n// so a transient RPC failure can't permanently divert an authenticated user.\nlet stickyPublicMode = false;\n\nexport function shouldSkipRpc(): boolean {\n return stickyPublicMode && publicReadAvailable();\n}\n\n/** Test seam. */\nexport function _resetStickyPublicMode(): void {\n stickyPublicMode = false;\n}\n\n/**\n * True when the supabase client holds an active auth session. The public\n * proxy is for ANONYMOUS visitors only — an authenticated user's reads must\n * always go through the RPC (their session is the authority; security review\n * finding 2026-06-11: an anonymous page load must not divert a subsequently\n * signed-in user to the session-less proxy). Fail-safe: on any error, report\n * a session as PRESENT so the caller refuses the fallback.\n */\nexport async function hasActiveSession(supabase: { auth?: { getSession?: () => Promise<{ data?: { session?: unknown } }> } } | null | undefined): Promise<boolean> {\n try {\n if (!supabase?.auth?.getSession) return false;\n const { data } = await supabase.auth.getSession();\n return Boolean(data?.session);\n } catch {\n return true;\n }\n}\n\nexport async function publicReadQuery(projectId: string, sql: string): Promise<PublicProxyResult> {\n if (!publicReadAvailable()) {\n return { success: false, error: 'Public read proxy not configured (missing public token or API URL)' };\n }\n try {\n const base = env.EZCODER_API_URL.replace(/\\/$/, '');\n const token = publicReadToken();\n /** @type {Record<string,string>} */\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n // Send the token only if one is baked; otherwise the proxy authorizes by\n // the (browser-set, server-validated) Origin → project binding.\n if (token) headers['X-EzCoder-Public-Token'] = token;\n const res = await fetch(`${base}/api/platform/db/${encodeURIComponent(projectId)}/query`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ sql }),\n signal: typeof AbortSignal !== 'undefined' && AbortSignal.timeout\n ? AbortSignal.timeout(PROXY_TIMEOUT_MS)\n : undefined,\n });\n const payload = (await res.json().catch(() => null)) as PublicProxyResult | null;\n if (!res.ok || !payload) {\n return { success: false, error: payload?.error || `Public read proxy HTTP ${res.status}` };\n }\n if (payload.success) stickyPublicMode = true;\n return payload;\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Public read proxy request failed';\n return { success: false, error: message };\n }\n}\n","import type { SupabaseClient } from '@supabase/supabase-js';\r\nimport type { DatabaseResult, SingleResult, MutationResult, FilterValue, OrderOption } from './types';\r\nimport { QueryError, ValidationError, mapRpcError } from './errors';\r\nimport { isAuthShapedFailure, shouldSkipRpc, hasActiveSession } from './publicReadProxy';\r\n\r\nfunction escapeSqlValue(value: FilterValue): string {\r\n if (value === null) return 'NULL';\r\n if (typeof value === 'boolean') return value ? 'TRUE' : 'FALSE';\r\n if (typeof value === 'number') {\r\n if (!Number.isFinite(value)) throw new ValidationError('Non-finite numbers are not allowed');\r\n return String(value);\r\n }\r\n return `'${String(value).replace(/'/g, \"''\")}'`;\r\n}\r\n\r\nfunction escapeIdentifier(name: string): string {\r\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\r\n throw new ValidationError(`Invalid identifier: ${name}`);\r\n }\r\n return `\"${name}\"`;\r\n}\r\n\r\ntype Operation = 'select' | 'insert' | 'update' | 'delete' | 'count' | 'upsert';\r\n\r\ninterface Filter {\r\n column: string;\r\n op: string;\r\n value: FilterValue | FilterValue[];\r\n}\r\n\r\nexport interface ServerProxy {\r\n serverQuery?: (sql: string) => Promise<{ success: boolean; data?: unknown[]; rowCount?: number; error?: string; schema?: string }>;\r\n serverExec?: (sql: string) => Promise<{ success: boolean; data?: unknown[]; rowCount?: number; error?: string; schema?: string }>;\r\n /** Browser-mode READ fallback: routes through the platform public proxy\r\n * (public-class token) when the direct RPC needs an authenticated session\r\n * the visitor doesn't have. Reads only — writes never fall back. */\r\n publicQuery?: (sql: string) => Promise<{ success: boolean; data?: unknown[]; rowCount?: number; error?: string; schema?: string }>;\r\n}\r\n\r\nexport class QueryBuilder<T = Record<string, unknown>> {\r\n private readonly supabase: SupabaseClient;\r\n private readonly projectId: string;\r\n private readonly table: string;\r\n private readonly serverProxy?: ServerProxy;\r\n\r\n private operation: Operation = 'select';\r\n private selectColumns = '*';\r\n private filters: Filter[] = [];\r\n private orderClauses: string[] = [];\r\n private limitValue: number | null = null;\r\n private offsetValue: number | null = null;\r\n private insertData: Record<string, FilterValue>[] | null = null;\r\n private updateData: Record<string, FilterValue> | null = null;\r\n private upsertConflict: string | null = null;\r\n private singleMode = false;\r\n private maybeSingleMode = false;\r\n\r\n constructor(supabase: SupabaseClient, projectId: string, table: string, serverProxy?: ServerProxy) {\r\n this.supabase = supabase;\r\n this.projectId = projectId;\r\n this.table = table;\r\n this.serverProxy = serverProxy;\r\n }\r\n\r\n select(columns = '*'): this {\r\n this.operation = 'select';\r\n this.selectColumns = columns;\r\n return this;\r\n }\r\n\r\n // --- Filters ---\r\n\r\n eq(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '=', value });\r\n return this;\r\n }\r\n\r\n neq(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '!=', value });\r\n return this;\r\n }\r\n\r\n gt(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '>', value });\r\n return this;\r\n }\r\n\r\n lt(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '<', value });\r\n return this;\r\n }\r\n\r\n gte(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '>=', value });\r\n return this;\r\n }\r\n\r\n lte(column: string, value: FilterValue): this {\r\n this.filters.push({ column, op: '<=', value });\r\n return this;\r\n }\r\n\r\n like(column: string, pattern: string): this {\r\n this.filters.push({ column, op: 'LIKE', value: pattern });\r\n return this;\r\n }\r\n\r\n ilike(column: string, pattern: string): this {\r\n this.filters.push({ column, op: 'ILIKE', value: pattern });\r\n return this;\r\n }\r\n\r\n in(column: string, values: FilterValue[]): this {\r\n this.filters.push({ column, op: 'IN', value: values });\r\n return this;\r\n }\r\n\r\n is(column: string, value: null | boolean): this {\r\n this.filters.push({ column, op: 'IS', value });\r\n return this;\r\n }\r\n\r\n // --- Ordering & Pagination ---\r\n\r\n order(column: string, options: OrderOption = {}): this {\r\n const dir = options.ascending === false ? 'DESC' : 'ASC';\r\n const nulls = options.nullsFirst ? 'NULLS FIRST' : 'NULLS LAST';\r\n this.orderClauses.push(`${escapeIdentifier(column)} ${dir} ${nulls}`);\r\n return this;\r\n }\r\n\r\n limit(count: number): this {\r\n this.limitValue = count;\r\n return this;\r\n }\r\n\r\n offset(count: number): this {\r\n this.offsetValue = count;\r\n return this;\r\n }\r\n\r\n // --- Mutations ---\r\n\r\n insert(records: Record<string, FilterValue> | Record<string, FilterValue>[]): this {\r\n this.operation = 'insert';\r\n this.insertData = Array.isArray(records) ? records : [records];\r\n return this;\r\n }\r\n\r\n update(values: Record<string, FilterValue>): this {\r\n this.operation = 'update';\r\n this.updateData = values;\r\n return this;\r\n }\r\n\r\n delete(): this {\r\n this.operation = 'delete';\r\n return this;\r\n }\r\n\r\n upsert(records: Record<string, FilterValue> | Record<string, FilterValue>[], options?: { onConflict?: string }): this {\r\n this.operation = 'upsert';\r\n this.insertData = Array.isArray(records) ? records : [records];\r\n this.upsertConflict = options?.onConflict || null;\r\n return this;\r\n }\r\n\r\n // --- Result Helpers ---\r\n\r\n count(): QueryBuilder<{ count: number }> {\r\n this.operation = 'count';\r\n return this as unknown as QueryBuilder<{ count: number }>;\r\n }\r\n\r\n single(): QueryBuilder<T> {\r\n this.singleMode = true;\r\n this.limitValue = 1;\r\n return this;\r\n }\r\n\r\n maybeSingle(): QueryBuilder<T> {\r\n this.maybeSingleMode = true;\r\n this.limitValue = 1;\r\n return this;\r\n }\r\n\r\n // --- SQL Building ---\r\n\r\n private buildWhereClause(): string {\r\n if (this.filters.length === 0) return '';\r\n const conditions = this.filters.map((f) => {\r\n const col = escapeIdentifier(f.column);\r\n if (f.op === 'IS') {\r\n const val = f.value === null ? 'NULL' : f.value ? 'TRUE' : 'FALSE';\r\n return `${col} IS ${val}`;\r\n }\r\n if (f.op === 'IN') {\r\n const vals = (f.value as FilterValue[]).map(escapeSqlValue).join(', ');\r\n return `${col} IN (${vals})`;\r\n }\r\n return `${col} ${f.op} ${escapeSqlValue(f.value as FilterValue)}`;\r\n });\r\n return ` WHERE ${conditions.join(' AND ')}`;\r\n }\r\n\r\n private buildSelectSql(): string {\r\n let sql = `SELECT ${this.selectColumns} FROM \"${this.table}\"`;\r\n sql += this.buildWhereClause();\r\n if (this.orderClauses.length > 0) sql += ` ORDER BY ${this.orderClauses.join(', ')}`;\r\n if (this.limitValue !== null) sql += ` LIMIT ${this.limitValue}`;\r\n if (this.offsetValue !== null && this.offsetValue > 0) sql += ` OFFSET ${this.offsetValue}`;\r\n return sql;\r\n }\r\n\r\n private buildCountSql(): string {\r\n let sql = `SELECT COUNT(*) as count FROM \"${this.table}\"`;\r\n sql += this.buildWhereClause();\r\n return sql;\r\n }\r\n\r\n private buildInsertSql(): string {\r\n if (!this.insertData || this.insertData.length === 0) {\r\n throw new ValidationError('No data provided for insert');\r\n }\r\n const columns = Object.keys(this.insertData[0]);\r\n const colList = columns.map(escapeIdentifier).join(', ');\r\n const rows = this.insertData.map(\r\n (row) => `(${columns.map((c) => escapeSqlValue(row[c] ?? null)).join(', ')})`\r\n );\r\n return `INSERT INTO \"${this.table}\" (${colList}) VALUES ${rows.join(', ')}`;\r\n }\r\n\r\n private buildUpsertSql(): string {\r\n let sql = this.buildInsertSql();\r\n const conflict = this.upsertConflict || 'id';\r\n const columns = Object.keys(this.insertData![0]);\r\n const updateCols = columns\r\n .filter((c) => c !== conflict)\r\n .map((c) => `${escapeIdentifier(c)} = EXCLUDED.${escapeIdentifier(c)}`)\r\n .join(', ');\r\n sql += ` ON CONFLICT (${escapeIdentifier(conflict)}) DO UPDATE SET ${updateCols}`;\r\n return sql;\r\n }\r\n\r\n private buildUpdateSql(): string {\r\n if (!this.updateData || Object.keys(this.updateData).length === 0) {\r\n throw new ValidationError('No data provided for update');\r\n }\r\n if (this.filters.length === 0) {\r\n throw new ValidationError('UPDATE without filters is not allowed — add .eq() or other filters');\r\n }\r\n const setClauses = Object.entries(this.updateData)\r\n .map(([col, val]) => `${escapeIdentifier(col)} = ${escapeSqlValue(val)}`)\r\n .join(', ');\r\n return `UPDATE \"${this.table}\" SET ${setClauses}${this.buildWhereClause()}`;\r\n }\r\n\r\n private buildDeleteSql(): string {\r\n if (this.filters.length === 0) {\r\n throw new ValidationError('DELETE without filters is not allowed — add .eq() or other filters');\r\n }\r\n return `DELETE FROM \"${this.table}\"${this.buildWhereClause()}`;\r\n }\r\n\r\n // --- Execution ---\r\n\r\n private async executeQuery(): Promise<DatabaseResult<T>> {\r\n const isRead = this.operation === 'select' || this.operation === 'count';\r\n\r\n let sql: string;\r\n switch (this.operation) {\r\n case 'select': sql = this.buildSelectSql(); break;\r\n case 'count': sql = this.buildCountSql(); break;\r\n case 'insert': sql = this.buildInsertSql(); break;\r\n case 'upsert': sql = this.buildUpsertSql(); break;\r\n case 'update': sql = this.buildUpdateSql(); break;\r\n case 'delete': sql = this.buildDeleteSql(); break;\r\n default: throw new QueryError(`Unknown operation: ${this.operation}`);\r\n }\r\n\r\n // Server mode (secret-key proxy): full read/write via the platform.\r\n if (this.serverProxy?.serverQuery && this.serverProxy?.serverExec) {\r\n const fn = isRead ? this.serverProxy.serverQuery : this.serverProxy.serverExec;\r\n const data = await fn(sql);\r\n if (!data.success) {\r\n const dbError = mapRpcError(data);\r\n if (dbError) throw dbError;\r\n throw new QueryError(data.error || 'Query failed', sql);\r\n }\r\n return {\r\n success: true,\r\n data: (isRead ? data.data || [] : []) as T[],\r\n rowCount: data.rowCount ?? (isRead ? (data.data?.length || 0) : 0),\r\n schema: data.schema,\r\n };\r\n }\r\n\r\n // Browser mode, anonymous visitors ONLY: skip the doomed RPC once a read\r\n // has already fallen back to the public proxy. A signed-in user always\r\n // goes through the RPC — their session is the authority.\r\n const publicQuery = this.serverProxy?.publicQuery;\r\n if (isRead && publicQuery && shouldSkipRpc() && !(await hasActiveSession(this.supabase))) {\r\n return this.runPublicQuery(publicQuery, sql);\r\n }\r\n\r\n const rpcName = isRead ? 'sdk_query_project' : 'sdk_exec_project';\r\n const { data, error } = await this.supabase.rpc(rpcName, {\r\n p_project_id: this.projectId,\r\n p_sql: sql,\r\n });\r\n\r\n if (error) {\r\n if (isRead && publicQuery && isAuthShapedFailure(error.message) && !(await hasActiveSession(this.supabase))) {\r\n return this.runPublicQuery(publicQuery, sql, error.message);\r\n }\r\n throw new QueryError(error.message, sql);\r\n }\r\n\r\n if (!data.success) {\r\n if (isRead && publicQuery && isAuthShapedFailure(data.error) && !(await hasActiveSession(this.supabase))) {\r\n return this.runPublicQuery(publicQuery, sql, data.error);\r\n }\r\n const dbError = mapRpcError(data);\r\n if (dbError) throw dbError;\r\n throw new QueryError(data.error || 'Query failed', sql);\r\n }\r\n\r\n if (isRead) {\r\n return {\r\n success: true,\r\n data: data.data || [],\r\n rowCount: data.rowCount ?? (data.data?.length || 0),\r\n schema: data.schema,\r\n };\r\n }\r\n\r\n return {\r\n success: true,\r\n data: [],\r\n rowCount: data.rowCount ?? 0,\r\n schema: data.schema,\r\n };\r\n }\r\n\r\n /** Execute a read through the platform public proxy (anonymous-visitor path). */\r\n private async runPublicQuery(\r\n publicQuery: NonNullable<ServerProxy['publicQuery']>,\r\n sql: string,\r\n rpcError?: string,\r\n ): Promise<DatabaseResult<T>> {\r\n const data = await publicQuery(sql);\r\n if (!data.success) {\r\n const dbError = mapRpcError(data);\r\n if (dbError) throw dbError;\r\n throw new QueryError(data.error || rpcError || 'Query failed', sql);\r\n }\r\n return {\r\n success: true,\r\n data: (data.data || []) as T[],\r\n rowCount: data.rowCount ?? (data.data?.length || 0),\r\n schema: data.schema,\r\n };\r\n }\r\n\r\n then<TResult1 = DatabaseResult<T> | SingleResult<T>, TResult2 = never>(\r\n onfulfilled?: ((value: DatabaseResult<T> | SingleResult<T>) => TResult1 | PromiseLike<TResult1>) | null,\r\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\r\n ): Promise<TResult1 | TResult2> {\r\n const promise = this.executeQuery().then((result) => {\r\n if (this.singleMode) {\r\n if (result.data.length === 0) {\r\n throw new QueryError('Expected exactly one row, got 0');\r\n }\r\n return { success: true, data: result.data[0], error: undefined } as SingleResult<T>;\r\n }\r\n if (this.maybeSingleMode) {\r\n return {\r\n success: true,\r\n data: result.data.length > 0 ? result.data[0] : null,\r\n error: undefined,\r\n } as SingleResult<T>;\r\n }\r\n return result;\r\n });\r\n return promise.then(onfulfilled, onrejected);\r\n }\r\n}\r\n","import type { SupabaseClient } from '@supabase/supabase-js';\nimport type { DatabaseResult, MutationResult, SchemaInfo } from './types';\nimport { QueryBuilder, type ServerProxy } from './QueryBuilder';\nimport { QueryError, ConnectionError } from './errors';\nimport { ezcoder } from '../core/platform';\nimport { features } from '../core/config';\nimport { publicReadQuery, publicReadAvailable, isAuthShapedFailure, shouldSkipRpc, hasActiveSession } from './publicReadProxy';\n\nfunction trackDbEvent(event: string, props: Record<string, unknown>) {\n if (!features.analytics) return;\n try { ezcoder.analytics.track(event, props); } catch { /* non-critical */ }\n}\n\ninterface ServerProxyResult {\n success: boolean;\n data?: unknown[];\n rowCount?: number;\n error?: string;\n schema?: string;\n exists?: boolean;\n tables?: unknown[];\n tablesCount?: number;\n createdAt?: string;\n lastAccessedAt?: string;\n executed_sql?: string;\n}\n\nconst MAX_RETRIES = 3;\nconst SERVER_TIMEOUT_MS = 5000;\n\nexport class DatabaseClient {\n private readonly supabase: SupabaseClient | null;\n private readonly projectId: string;\n private apiKey?: string;\n private platformUrl?: string;\n private serverMode: boolean = false;\n\n constructor(supabase: SupabaseClient | null, projectId: string) {\n this.supabase = supabase;\n this.projectId = projectId;\n }\n\n static createServerClient(\n projectId: string,\n apiKey: string,\n platformUrl?: string\n ): DatabaseClient {\n const client = new DatabaseClient(null, projectId);\n client.apiKey = apiKey;\n client.platformUrl = platformUrl\n || (typeof process !== 'undefined' && process.env?.EZCODER_API_URL)\n || 'https://ezcoder.app';\n client.serverMode = true;\n return client;\n }\n\n from<T = Record<string, unknown>>(table: string): QueryBuilder<T> {\n if (this.serverMode) {\n return new QueryBuilder<T>(null as unknown as SupabaseClient, this.projectId, table, {\n serverQuery: (sql: string) => this._serverRequest('/query', sql),\n serverExec: (sql: string) => this._serverRequest('/execute', sql),\n });\n }\n // Browser mode: reads get the public-proxy fallback so anonymous visitors\n // on deployed sites see data (the direct RPC requires a signed-in,\n // project-bound session). Writes intentionally have no fallback.\n return new QueryBuilder<T>(this.supabase!, this.projectId, table, {\n publicQuery: (sql: string) => publicReadQuery(this.projectId, sql),\n });\n }\n\n async sql<T = Record<string, unknown>>(query: string): Promise<DatabaseResult<T>> {\n const trimmed = query.trim().toLowerCase();\n if (!trimmed.startsWith('select')) {\n throw new QueryError('sql() is for read-only queries. Use execute() for mutations.');\n }\n\n if (this.serverMode) {\n return this._serverQuery<T>(query);\n }\n\n const start = Date.now();\n\n // Anonymous visitors ONLY: once a read has fallen back to the public\n // proxy, skip the doomed RPC round-trip on subsequent reads. A signed-in\n // user always goes through the RPC — their session is the authority.\n if (shouldSkipRpc() && !(await hasActiveSession(this.supabase))) {\n return this._publicFallback<T>(query, start, null);\n }\n\n const { data, error } = await this.supabase!.rpc('sdk_query_project', {\n p_project_id: this.projectId,\n p_sql: query,\n });\n const latencyMs = Date.now() - start;\n\n if (error) {\n if (publicReadAvailable() && isAuthShapedFailure(error.message) && !(await hasActiveSession(this.supabase))) {\n return this._publicFallback<T>(query, start, error.message);\n }\n trackDbEvent('db_query', { projectId: this.projectId, latencyMs, success: false, error: error.message });\n throw new QueryError(error.message, query);\n }\n if (!data.success) {\n if (publicReadAvailable() && isAuthShapedFailure(data.error) && !(await hasActiveSession(this.supabase))) {\n return this._publicFallback<T>(query, start, data.error);\n }\n trackDbEvent('db_query', { projectId: this.projectId, latencyMs, success: false, error: data.error });\n throw new QueryError(data.error || 'Query failed', query);\n }\n\n trackDbEvent('db_query', { projectId: this.projectId, latencyMs, success: true, rowCount: data.rowCount ?? 0 });\n\n return {\n success: true,\n data: data.data || [],\n rowCount: data.rowCount ?? (data.data?.length || 0),\n schema: data.schema,\n };\n }\n\n /** Read via the platform public proxy (anonymous-visitor path). */\n private async _publicFallback<T>(query: string, start: number, rpcError: string | null): Promise<DatabaseResult<T>> {\n const data = await publicReadQuery(this.projectId, query);\n const latencyMs = Date.now() - start;\n if (!data.success) {\n trackDbEvent('db_query', { projectId: this.projectId, latencyMs, success: false, error: data.error, publicProxy: true });\n // Surface the proxy's error (it is the actionable one); note the RPC error for diagnosis.\n throw new QueryError(data.error || rpcError || 'Query failed', query);\n }\n trackDbEvent('db_query', { projectId: this.projectId, latencyMs, success: true, rowCount: data.rowCount ?? 0, publicProxy: true });\n return {\n success: true,\n data: (data.data || []) as T[],\n rowCount: data.rowCount ?? (data.data?.length || 0),\n schema: data.schema,\n };\n }\n\n async execute(sql: string): Promise<MutationResult> {\n if (this.serverMode) {\n return this._serverExecute(sql);\n }\n\n const start = Date.now();\n const { data, error } = await this.supabase!.rpc('sdk_exec_project', {\n p_project_id: this.projectId,\n p_sql: sql,\n });\n const latencyMs = Date.now() - start;\n\n if (error) {\n trackDbEvent('db_mutation', { projectId: this.projectId, latencyMs, success: false, error: error.message });\n throw new QueryError(error.message, sql);\n }\n if (!data.success) {\n trackDbEvent('db_mutation', { projectId: this.projectId, latencyMs, success: false, error: data.error });\n throw new QueryError(data.error || 'Execution failed', sql);\n }\n\n trackDbEvent('db_mutation', { projectId: this.projectId, latencyMs, success: true, rowCount: data.rowCount ?? 0 });\n\n return {\n success: true,\n rowCount: data.rowCount ?? 0,\n schema: data.schema,\n executed_sql: data.executed_sql,\n };\n }\n\n async getSchema(): Promise<SchemaInfo> {\n if (this.serverMode) {\n return this._serverGetSchema();\n }\n\n const { data, error } = await this.supabase!.rpc('sdk_get_schema_info', {\n p_project_id: this.projectId,\n });\n\n if (error) throw new ConnectionError(error.message);\n\n return {\n exists: data.exists ?? false,\n schema: data.schema,\n tables: data.tables || [],\n tablesCount: data.tablesCount ?? 0,\n createdAt: data.createdAt,\n lastAccessedAt: data.lastAccessedAt,\n };\n }\n\n private async _serverRequest(endpoint: string, sql: string): Promise<ServerProxyResult> {\n const url = `${this.platformUrl}/api/platform/db/${this.projectId}${endpoint}`;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ sql }),\n signal: AbortSignal.timeout(SERVER_TIMEOUT_MS),\n });\n\n if (res.status === 429) {\n const retryAfter = parseInt(res.headers.get('retry-after') || '60', 10);\n await new Promise(r => setTimeout(r, retryAfter * 1000));\n continue;\n }\n if (res.status === 503) {\n await new Promise(r => setTimeout(r, (attempt + 1) * 2000));\n continue;\n }\n\n return await res.json() as ServerProxyResult;\n } catch (e) {\n lastError = e instanceof Error ? e : new Error(String(e));\n }\n }\n\n return { success: false, error: lastError?.message || 'Request failed after retries' };\n }\n\n private async _serverQuery<T>(query: string): Promise<DatabaseResult<T>> {\n const start = Date.now();\n const result = await this._serverRequest('/query', query);\n const latencyMs = Date.now() - start;\n\n if (!result.success) {\n trackDbEvent('db_query', { projectId: this.projectId, latencyMs, success: false, error: result.error, serverMode: true });\n throw new QueryError(result.error || 'Query failed', query);\n }\n\n trackDbEvent('db_query', { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0, serverMode: true });\n\n return {\n success: true,\n data: (result.data || []) as T[],\n rowCount: result.rowCount ?? ((result.data as unknown[])?.length || 0),\n schema: result.schema,\n };\n }\n\n private async _serverExecute(sql: string): Promise<MutationResult> {\n const start = Date.now();\n const result = await this._serverRequest('/execute', sql);\n const latencyMs = Date.now() - start;\n\n if (!result.success) {\n trackDbEvent('db_mutation', { projectId: this.projectId, latencyMs, success: false, error: result.error, serverMode: true });\n throw new QueryError(result.error || 'Execution failed', sql);\n }\n\n trackDbEvent('db_mutation', { projectId: this.projectId, latencyMs, success: true, rowCount: result.rowCount ?? 0, serverMode: true });\n\n return {\n success: true,\n rowCount: result.rowCount ?? 0,\n schema: result.schema,\n executed_sql: result.executed_sql,\n };\n }\n\n private async _serverGetSchema(): Promise<SchemaInfo> {\n const url = `${this.platformUrl}/api/platform/db/${this.projectId}/schema`;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const res = await fetch(url, {\n method: 'GET',\n headers: { 'Authorization': `Bearer ${this.apiKey}` },\n signal: AbortSignal.timeout(SERVER_TIMEOUT_MS),\n });\n\n if (res.status === 503) {\n await new Promise(r => setTimeout(r, (attempt + 1) * 2000));\n continue;\n }\n\n const data = await res.json() as ServerProxyResult;\n if (!data.success) throw new ConnectionError(data.error || 'Schema fetch failed');\n\n return {\n exists: data.exists ?? false,\n schema: data.schema,\n tables: (data.tables || []) as SchemaInfo['tables'],\n tablesCount: data.tablesCount ?? 0,\n createdAt: data.createdAt,\n lastAccessedAt: data.lastAccessedAt,\n };\n } catch (e) {\n lastError = e instanceof Error ? e : new Error(String(e));\n }\n }\n\n throw new ConnectionError(lastError?.message || 'Schema request failed after retries');\n }\n}\n","import { useEffect, useState, useRef } from 'react';\r\nimport { supabase, isSupabaseConfigured } from '../core/supabase';\r\nimport { env } from '../core/config';\r\n\r\ntype RealtimeEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';\r\n\r\ninterface RealtimeOptions {\r\n event?: RealtimeEvent;\r\n filter?: string;\r\n}\r\n\r\ninterface UseRealtimeReturn<T> {\r\n data: T[];\r\n status: 'connecting' | 'connected' | 'error' | 'disabled';\r\n setData: React.Dispatch<React.SetStateAction<T[]>>;\r\n}\r\n\r\nexport function useRealtime<T extends Record<string, unknown> = Record<string, unknown>>(\r\n table: string,\r\n options: RealtimeOptions = {},\r\n): UseRealtimeReturn<T> {\r\n const [data, setData] = useState<T[]>([]);\r\n const [status, setStatus] = useState<UseRealtimeReturn<T>['status']>('connecting');\r\n const optionsRef = useRef(options);\r\n optionsRef.current = options;\r\n\r\n const projectId = env.EZC_PROJECT_ID;\r\n\r\n useEffect(() => {\r\n if (!isSupabaseConfigured || !projectId) {\r\n setStatus('disabled');\r\n return;\r\n }\r\n\r\n const schemaName = `proj_${projectId.replace(/-/g, '_')}`;\r\n const channelName = `${schemaName}_${table}_${optionsRef.current.event || 'all'}`;\r\n\r\n const channel = supabase\r\n .channel(channelName)\r\n .on(\r\n 'postgres_changes' as never,\r\n {\r\n event: optionsRef.current.event || '*',\r\n schema: schemaName,\r\n table,\r\n filter: optionsRef.current.filter,\r\n } as never,\r\n (payload: { eventType: string; new: Record<string, unknown>; old: Record<string, unknown> }) => {\r\n if (payload.eventType === 'INSERT') {\r\n setData((prev) => [...prev, payload.new as T]);\r\n } else if (payload.eventType === 'UPDATE') {\r\n setData((prev) =>\r\n prev.map((item) =>\r\n (item as Record<string, unknown>).id === (payload.new as Record<string, unknown>).id\r\n ? (payload.new as T)\r\n : item,\r\n ),\r\n );\r\n } else if (payload.eventType === 'DELETE') {\r\n setData((prev) =>\r\n prev.filter(\r\n (item) =>\r\n (item as Record<string, unknown>).id !== (payload.old as Record<string, unknown>).id,\r\n ),\r\n );\r\n }\r\n },\r\n )\r\n .subscribe((subStatus: string) => {\r\n setStatus(subStatus === 'SUBSCRIBED' ? 'connected' : 'connecting');\r\n });\r\n\r\n return () => {\r\n supabase.removeChannel(channel);\r\n };\r\n }, [projectId, table]);\r\n\r\n return { data, status, setData };\r\n}\r\n"],"mappings":"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","names":["supabase","supabase","data","supabase"]}
|