@edge-base/server 0.2.1 → 0.2.2
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/admin-build/_app/immutable/chunks/{BY07qVPA.js → 4vlsb8ej.js} +1 -1
- package/admin-build/_app/immutable/chunks/{D755Tqat.js → 5PDcRlfX.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BFs_qStz.js → B8DT4fss.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DnLqc9L1.js → BEYYl662.js} +1 -1
- package/admin-build/_app/immutable/chunks/{Dqk2TGNU.js → BKXmgPq4.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DjOEv9M9.js → BWyDPAjM.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BcIUK2sk.js → BaCHY17I.js} +1 -1
- package/admin-build/_app/immutable/chunks/{B0QyxC2M.js → C-DsDCNG.js} +3 -3
- package/admin-build/_app/immutable/chunks/{k0CIJkw4.js → C85dMlzL.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BsFiK_FJ.js → CPdXvRUb.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BCKr7yKd.js → CTngeX8H.js} +1 -1
- package/admin-build/_app/immutable/chunks/{D-x55wdW.js → DzXaj-Ja.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BTJcQFEp.js → c5iKSdWY.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CqUxCvs_.js → g3ZZdY-r.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CSGrwS7E.js → kiJ6KthZ.js} +1 -1
- package/admin-build/_app/immutable/chunks/{m9QZTyVV.js → qiZXAKh-.js} +1 -1
- package/admin-build/_app/immutable/entry/{app.BTsq3_xq.js → app.BZxfavhF.js} +2 -2
- package/admin-build/_app/immutable/entry/start.Mr9mmopc.js +1 -0
- package/admin-build/_app/immutable/nodes/{0.BZ00WDYH.js → 0.DlsaydXO.js} +1 -1
- package/admin-build/_app/immutable/nodes/{1.RzSJ3yyr.js → 1.D2NWN5eG.js} +1 -1
- package/admin-build/_app/immutable/nodes/{10.D-rsiquF.js → 10.EMDaN3nw.js} +1 -1
- package/admin-build/_app/immutable/nodes/{11.l7-bgtFD.js → 11.BasqQ_o9.js} +1 -1
- package/admin-build/_app/immutable/nodes/{12.Dkq0H7B5.js → 12.DO31Ljs7.js} +1 -1
- package/admin-build/_app/immutable/nodes/{13.DtK_4oRz.js → 13.DhyAy-GZ.js} +1 -1
- package/admin-build/_app/immutable/nodes/{14.BKo7-AMx.js → 14.CLecGWc4.js} +1 -1
- package/admin-build/_app/immutable/nodes/{15.CQAj_6lq.js → 15.B9kp3W4e.js} +1 -1
- package/admin-build/_app/immutable/nodes/{16.XVIG-Ffr.js → 16.Pu_8T3RI.js} +1 -1
- package/admin-build/_app/immutable/nodes/{17.g6raZLCM.js → 17.DX4z43t6.js} +1 -1
- package/admin-build/_app/immutable/nodes/{18.IQz6a3T6.js → 18.BKsSaxrr.js} +1 -1
- package/admin-build/_app/immutable/nodes/{19.CAAZ8i8h.js → 19.DXNF1htN.js} +1 -1
- package/admin-build/_app/immutable/nodes/{20.BPcX3KPj.js → 20.VRVb0wee.js} +1 -1
- package/admin-build/_app/immutable/nodes/21.Ck3_0D2f.js +1 -0
- package/admin-build/_app/immutable/nodes/{22.Br5AG_5Z.js → 22.DqZf4CtH.js} +1 -1
- package/admin-build/_app/immutable/nodes/{23.KjbrdXoE.js → 23.DtyxMiQG.js} +1 -1
- package/admin-build/_app/immutable/nodes/{24.C3n2-hgw.js → 24.CloWNmTd.js} +1 -1
- package/admin-build/_app/immutable/nodes/{25.SFDSBzHd.js → 25.CnZWMq7_.js} +1 -1
- package/admin-build/_app/immutable/nodes/{26.D95vui6E.js → 26.DrV7XOmf.js} +1 -1
- package/admin-build/_app/immutable/nodes/{27.FgLgdjwB.js → 27.DV8L32OF.js} +1 -1
- package/admin-build/_app/immutable/nodes/{28.B9sYYm1F.js → 28.Stil2D4u.js} +1 -1
- package/admin-build/_app/immutable/nodes/{29.DyqZ_wbN.js → 29.Zsm1e5Dc.js} +1 -1
- package/admin-build/_app/immutable/nodes/{3.Bzo2yVIO.js → 3.CKoj2vNz.js} +1 -1
- package/admin-build/_app/immutable/nodes/{30.c1CiNwiS.js → 30.Ni0k5bER.js} +1 -1
- package/admin-build/_app/immutable/nodes/{31.CXty66Vh.js → 31.mnqj9EbV.js} +1 -1
- package/admin-build/_app/immutable/nodes/{4.BgQaXZ27.js → 4.B_-z9AzT.js} +1 -1
- package/admin-build/_app/immutable/nodes/{5.BuJrHvxH.js → 5.yiZ72j4k.js} +1 -1
- package/admin-build/_app/immutable/nodes/{6.CkBBC94k.js → 6.BqykybBG.js} +1 -1
- package/admin-build/_app/immutable/nodes/{7.D2YBvNFM.js → 7.BDAHlhsF.js} +1 -1
- package/admin-build/_app/immutable/nodes/{8.D8qQWo_z.js → 8.D8Xvy0lH.js} +1 -1
- package/admin-build/_app/immutable/nodes/{9.BLDLX5hV.js → 9.Dddmd7_F.js} +1 -1
- package/admin-build/_app/version.json +1 -1
- package/admin-build/index.html +7 -7
- package/package.json +3 -2
- package/src/__tests__/functions-context.test.ts +5 -5
- package/src/__tests__/meta-export-coverage.test.ts +1 -0
- package/src/lib/functions.ts +204 -397
- package/src/lib/internal-transport.ts +316 -0
- package/src/lib/plugin-migrations.ts +2 -2
- package/src/routes/admin.ts +7 -1
- package/src/routes/auth.ts +6 -12
- package/src/routes/storage.ts +6 -12
- package/src/types.ts +2 -0
- package/admin-build/_app/immutable/entry/start.zXCirpgY.js +0 -1
- package/admin-build/_app/immutable/nodes/21.DoPabrY_.js +0 -1
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal HttpTransport implementation for server-side function context.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the old TableProxy by implementing the same HttpTransport interface
|
|
5
|
+
* that @edge-base/core's DefaultDbApi expects. This lets us use the real
|
|
6
|
+
* TableRef/DbRef classes from the core SDK while routing requests directly
|
|
7
|
+
* to D1, PostgreSQL, or DurableObject handlers — no HTTP round-trip.
|
|
8
|
+
*/
|
|
9
|
+
import type { HttpTransport } from '@edge-base/core';
|
|
10
|
+
import type { EdgeBaseConfig } from '@edge-base/shared';
|
|
11
|
+
import { getDbDoName, callDO, shouldRouteToD1 } from './do-router.js';
|
|
12
|
+
import { handleD1Request } from './d1-handler.js';
|
|
13
|
+
import { handlePgRequest } from './postgres-handler.js';
|
|
14
|
+
import { buildInternalHandlerContext } from './internal-request.js';
|
|
15
|
+
import type { Env } from '../types.js';
|
|
16
|
+
|
|
17
|
+
export interface InternalTransportOptions {
|
|
18
|
+
databaseNamespace: DurableObjectNamespace;
|
|
19
|
+
config: EdgeBaseConfig;
|
|
20
|
+
workerUrl?: string;
|
|
21
|
+
serviceKey?: string;
|
|
22
|
+
env?: Env;
|
|
23
|
+
executionCtx?: ExecutionContext;
|
|
24
|
+
preferDirectDo?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* When set, the transport knows this DbRef targets a specific
|
|
27
|
+
* namespace + optional instanceId. This avoids ambiguous path parsing
|
|
28
|
+
* when instanceId happens to be "tables".
|
|
29
|
+
*/
|
|
30
|
+
dbContext?: { namespace: string; instanceId?: string };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse a DefaultDbApi path into routing components.
|
|
35
|
+
*
|
|
36
|
+
* Paths follow two patterns:
|
|
37
|
+
* /api/db/{namespace}/tables/{table}[/rest] → static DB
|
|
38
|
+
* /api/db/{namespace}/{instanceId}/tables/{table}[/rest] → dynamic DB
|
|
39
|
+
*
|
|
40
|
+
* Returns namespace, optional instanceId, tableName, and directPath
|
|
41
|
+
* (everything from /tables/... onward, which D1/PG handlers expect).
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* Parse a DefaultDbApi path, optionally guided by known dbContext.
|
|
45
|
+
*
|
|
46
|
+
* When dbContext is provided (recommended), we know whether this is a
|
|
47
|
+
* static or dynamic DB, so we can unambiguously find the 'tables' keyword
|
|
48
|
+
* even when instanceId === 'tables'.
|
|
49
|
+
*
|
|
50
|
+
* Without dbContext, falls back to heuristic: first 'tables' at index 1
|
|
51
|
+
* means static, at index 2 means dynamic.
|
|
52
|
+
*/
|
|
53
|
+
function parsePath(
|
|
54
|
+
path: string,
|
|
55
|
+
dbContext?: { namespace: string; instanceId?: string },
|
|
56
|
+
): {
|
|
57
|
+
namespace: string;
|
|
58
|
+
instanceId?: string;
|
|
59
|
+
tableName: string;
|
|
60
|
+
directPath: string;
|
|
61
|
+
} {
|
|
62
|
+
// Strip leading /api/db/
|
|
63
|
+
const stripped = path.replace(/^\/api\/db\//, '');
|
|
64
|
+
const segments = stripped.split('/');
|
|
65
|
+
|
|
66
|
+
let tablesIdx: number;
|
|
67
|
+
if (dbContext) {
|
|
68
|
+
// We know the shape: static has 'tables' at index 1, dynamic at index 2
|
|
69
|
+
tablesIdx = dbContext.instanceId ? 2 : 1;
|
|
70
|
+
} else {
|
|
71
|
+
// Heuristic fallback: find first 'tables' keyword
|
|
72
|
+
tablesIdx = segments.indexOf('tables', 1);
|
|
73
|
+
}
|
|
74
|
+
if (tablesIdx < 0 || segments[tablesIdx] !== 'tables') {
|
|
75
|
+
throw new Error(`Invalid DB path: missing 'tables' segment in ${path}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const namespace = segments[0];
|
|
79
|
+
const instanceId = tablesIdx === 2 ? segments[1] : undefined;
|
|
80
|
+
const rawTableName = segments[tablesIdx + 1];
|
|
81
|
+
// Decode URL-encoded table names (e.g. 'plugin-a%2Fevents' → 'plugin-a/events')
|
|
82
|
+
// Use decoded name consistently in both tableName and directPath so that
|
|
83
|
+
// handler suffix parsing (doPath.replace(`/tables/${tableName}`, '')) matches.
|
|
84
|
+
const tableName = decodeURIComponent(rawTableName);
|
|
85
|
+
const rest = segments.slice(tablesIdx + 2);
|
|
86
|
+
const directPath = `/tables/${tableName}${rest.length ? '/' + rest.join('/') : ''}`;
|
|
87
|
+
|
|
88
|
+
return { namespace, instanceId, tableName, directPath };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class InternalHttpTransport implements HttpTransport {
|
|
92
|
+
private readonly databaseNamespace: DurableObjectNamespace;
|
|
93
|
+
private readonly config: EdgeBaseConfig;
|
|
94
|
+
private readonly workerUrl?: string;
|
|
95
|
+
private readonly serviceKey?: string;
|
|
96
|
+
private readonly env?: Env;
|
|
97
|
+
private readonly executionCtx?: ExecutionContext;
|
|
98
|
+
private readonly preferDirectDo: boolean;
|
|
99
|
+
private readonly dbContext?: { namespace: string; instanceId?: string };
|
|
100
|
+
|
|
101
|
+
constructor(options: InternalTransportOptions) {
|
|
102
|
+
this.databaseNamespace = options.databaseNamespace;
|
|
103
|
+
this.config = options.config;
|
|
104
|
+
this.workerUrl = options.workerUrl;
|
|
105
|
+
this.serviceKey = options.serviceKey;
|
|
106
|
+
this.env = options.env;
|
|
107
|
+
this.executionCtx = options.executionCtx;
|
|
108
|
+
this.preferDirectDo = options.preferDirectDo ?? false;
|
|
109
|
+
this.dbContext = options.dbContext;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async request<T>(
|
|
113
|
+
method: string,
|
|
114
|
+
path: string,
|
|
115
|
+
options?: { query?: Record<string, string>; body?: unknown },
|
|
116
|
+
): Promise<T> {
|
|
117
|
+
const { namespace, instanceId, tableName, directPath } = parsePath(path, this.dbContext);
|
|
118
|
+
const doName = getDbDoName(namespace, instanceId);
|
|
119
|
+
|
|
120
|
+
// Build internal headers
|
|
121
|
+
const headers: Record<string, string> = {
|
|
122
|
+
'Content-Type': 'application/json',
|
|
123
|
+
'X-DO-Name': doName,
|
|
124
|
+
'X-EdgeBase-Internal': 'true',
|
|
125
|
+
};
|
|
126
|
+
if (this.serviceKey) {
|
|
127
|
+
headers['X-EdgeBase-Service-Key'] = this.serviceKey;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Convert query Record to URLSearchParams
|
|
131
|
+
const query = new URLSearchParams();
|
|
132
|
+
if (options?.query) {
|
|
133
|
+
for (const [k, v] of Object.entries(options.query)) {
|
|
134
|
+
if (v !== undefined && v !== '') query.set(k, v);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const body = options?.body as Record<string, unknown> | undefined;
|
|
139
|
+
|
|
140
|
+
// Route to the appropriate handler
|
|
141
|
+
const res = await this.routeRequest(
|
|
142
|
+
method as 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT',
|
|
143
|
+
namespace,
|
|
144
|
+
instanceId,
|
|
145
|
+
tableName,
|
|
146
|
+
directPath,
|
|
147
|
+
doName,
|
|
148
|
+
headers,
|
|
149
|
+
query,
|
|
150
|
+
body,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (!res.ok) {
|
|
154
|
+
const err = (await res.json().catch(() => ({}))) as Record<string, unknown>;
|
|
155
|
+
throw new Error(String(err.message || `Internal request failed: ${res.status}`));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (await res.json()) as T;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async head(_path: string): Promise<boolean> {
|
|
162
|
+
// HEAD is only used by StorageClient.checkFileExists — not relevant for DB ops
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private async routeRequest(
|
|
167
|
+
method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT',
|
|
168
|
+
namespace: string,
|
|
169
|
+
instanceId: string | undefined,
|
|
170
|
+
tableName: string,
|
|
171
|
+
directPath: string,
|
|
172
|
+
doName: string,
|
|
173
|
+
headers: Record<string, string>,
|
|
174
|
+
query: URLSearchParams,
|
|
175
|
+
body?: Record<string, unknown>,
|
|
176
|
+
): Promise<Response> {
|
|
177
|
+
const queryString = Array.from(query.keys()).length > 0 ? `?${query.toString()}` : '';
|
|
178
|
+
const directPathWithQuery = `${directPath}${queryString}`;
|
|
179
|
+
const provider = this.config.databases?.[namespace]?.provider;
|
|
180
|
+
const httpMethod = method === 'PUT' ? 'PATCH' : method; // normalize PUT → PATCH
|
|
181
|
+
|
|
182
|
+
// 1. D1 route
|
|
183
|
+
if (!this.preferDirectDo && shouldRouteToD1(namespace, this.config) && this.env) {
|
|
184
|
+
return this.requestViaD1Handler(httpMethod, namespace, instanceId, tableName, directPath, headers, query, body);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 2. PostgreSQL route
|
|
188
|
+
if ((provider === 'neon' || provider === 'postgres') && this.env) {
|
|
189
|
+
return this.requestViaPgHandler(httpMethod, namespace, instanceId, tableName, directPath, headers, query, body);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 3. Direct DO route
|
|
193
|
+
if (this.env) {
|
|
194
|
+
return this.requestViaDirectDo(httpMethod, doName, directPathWithQuery, headers, body, !!instanceId);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 4. Worker HTTP fallback
|
|
198
|
+
if (this.workerUrl) {
|
|
199
|
+
const apiPath = instanceId
|
|
200
|
+
? `/api/db/${namespace}/${instanceId}${directPathWithQuery}`
|
|
201
|
+
: `/api/db/${namespace}${directPathWithQuery}`;
|
|
202
|
+
return this.requestViaWorker(httpMethod, apiPath, headers, body);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 5. Fallback: direct DO
|
|
206
|
+
return this.requestViaDirectDo(httpMethod, doName, directPathWithQuery, headers, body, !!instanceId);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async requestViaWorker(
|
|
210
|
+
method: string,
|
|
211
|
+
path: string,
|
|
212
|
+
headers: Record<string, string>,
|
|
213
|
+
body?: Record<string, unknown>,
|
|
214
|
+
): Promise<Response> {
|
|
215
|
+
return fetch(`${this.workerUrl}${path}`, {
|
|
216
|
+
method,
|
|
217
|
+
headers,
|
|
218
|
+
body: body === undefined || method === 'GET' || method === 'DELETE'
|
|
219
|
+
? undefined
|
|
220
|
+
: JSON.stringify(body),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private async requestViaD1Handler(
|
|
225
|
+
method: string,
|
|
226
|
+
namespace: string,
|
|
227
|
+
instanceId: string | undefined,
|
|
228
|
+
tableName: string,
|
|
229
|
+
directPath: string,
|
|
230
|
+
headers: Record<string, string>,
|
|
231
|
+
query: URLSearchParams,
|
|
232
|
+
body?: Record<string, unknown>,
|
|
233
|
+
): Promise<Response> {
|
|
234
|
+
if (!this.env) throw new Error('D1 table proxy requires env.');
|
|
235
|
+
|
|
236
|
+
const queryString = Array.from(query.keys()).length > 0 ? `?${query.toString()}` : '';
|
|
237
|
+
const url = `http://internal/api/db/${namespace}${instanceId ? `/${instanceId}` : ''}${directPath}${queryString}`;
|
|
238
|
+
|
|
239
|
+
const request = new Request(url, {
|
|
240
|
+
method,
|
|
241
|
+
headers,
|
|
242
|
+
body: body === undefined || method === 'GET' || method === 'DELETE'
|
|
243
|
+
? undefined
|
|
244
|
+
: JSON.stringify(body),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return handleD1Request(
|
|
248
|
+
buildInternalHandlerContext({ env: this.env, request, body, executionCtx: this.executionCtx }),
|
|
249
|
+
namespace,
|
|
250
|
+
tableName,
|
|
251
|
+
directPath,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private async requestViaPgHandler(
|
|
256
|
+
method: string,
|
|
257
|
+
namespace: string,
|
|
258
|
+
instanceId: string | undefined,
|
|
259
|
+
tableName: string,
|
|
260
|
+
directPath: string,
|
|
261
|
+
headers: Record<string, string>,
|
|
262
|
+
query: URLSearchParams,
|
|
263
|
+
body?: Record<string, unknown>,
|
|
264
|
+
): Promise<Response> {
|
|
265
|
+
if (!this.env) throw new Error('PostgreSQL table proxy requires env.');
|
|
266
|
+
|
|
267
|
+
const queryString = Array.from(query.keys()).length > 0 ? `?${query.toString()}` : '';
|
|
268
|
+
const url = `http://internal/api/db/${namespace}${instanceId ? `/${instanceId}` : ''}${directPath}${queryString}`;
|
|
269
|
+
|
|
270
|
+
const request = new Request(url, {
|
|
271
|
+
method,
|
|
272
|
+
headers,
|
|
273
|
+
body: body === undefined || method === 'GET' || method === 'DELETE'
|
|
274
|
+
? undefined
|
|
275
|
+
: JSON.stringify(body),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return handlePgRequest(
|
|
279
|
+
buildInternalHandlerContext({ env: this.env, request, body, executionCtx: this.executionCtx }),
|
|
280
|
+
namespace,
|
|
281
|
+
tableName,
|
|
282
|
+
directPath,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private async requestViaDirectDo(
|
|
287
|
+
method: string,
|
|
288
|
+
doName: string,
|
|
289
|
+
directPathWithQuery: string,
|
|
290
|
+
headers: Record<string, string>,
|
|
291
|
+
body?: Record<string, unknown>,
|
|
292
|
+
isDynamic?: boolean,
|
|
293
|
+
): Promise<Response> {
|
|
294
|
+
const res = await callDO(this.databaseNamespace, doName, directPathWithQuery, {
|
|
295
|
+
method,
|
|
296
|
+
body,
|
|
297
|
+
headers,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Handle dynamic DO creation
|
|
301
|
+
if (isDynamic && res.status === 201) {
|
|
302
|
+
const createPayload = await res.clone().json().catch(() => null) as
|
|
303
|
+
| { needsCreate?: boolean }
|
|
304
|
+
| null;
|
|
305
|
+
if (createPayload?.needsCreate) {
|
|
306
|
+
return callDO(this.databaseNamespace, doName, directPathWithQuery, {
|
|
307
|
+
method,
|
|
308
|
+
body,
|
|
309
|
+
headers: { ...headers, 'X-DO-Create-Authorized': '1' },
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return res;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -350,7 +350,7 @@ function buildMigrationAdminContext(
|
|
|
350
350
|
return doAdminDb(namespace, id);
|
|
351
351
|
},
|
|
352
352
|
|
|
353
|
-
async
|
|
353
|
+
async sqlWithDirectD1Access(
|
|
354
354
|
namespace: string,
|
|
355
355
|
id: string | undefined,
|
|
356
356
|
query: string,
|
|
@@ -359,7 +359,7 @@ function buildMigrationAdminContext(
|
|
|
359
359
|
const dbBlock = config.databases?.[namespace];
|
|
360
360
|
const isDynamicNamespace = !!(dbBlock?.instance || dbBlock?.access?.canCreate || dbBlock?.access?.access);
|
|
361
361
|
if (isDynamicNamespace && !id) {
|
|
362
|
-
throw new Error(`admin.
|
|
362
|
+
throw new Error(`admin.sqlWithDirectD1Access() requires an id for dynamic namespace '${namespace}'.`);
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
const pgConnStr = resolvePgConnString(namespace);
|
package/src/routes/admin.ts
CHANGED
|
@@ -119,10 +119,16 @@ function quoteSqlIdentifier(identifier: string): string {
|
|
|
119
119
|
return `"${identifier}"`;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// Browser bootstrap is opt-in and only enabled by the local dev workflow.
|
|
123
|
+
function isPublicAdminSetupEnabled(env: Env): boolean {
|
|
124
|
+
return env.EDGEBASE_ALLOW_PUBLIC_ADMIN_SETUP === '1'
|
|
125
|
+
|| env.EDGEBASE_ALLOW_PUBLIC_ADMIN_SETUP === 'true';
|
|
126
|
+
}
|
|
127
|
+
|
|
122
128
|
function isPublicAdminSetupAllowed(env: Env): boolean {
|
|
123
129
|
try {
|
|
124
130
|
const config = parseConfig(env);
|
|
125
|
-
return config.release !== true;
|
|
131
|
+
return config.release !== true && isPublicAdminSetupEnabled(env);
|
|
126
132
|
} catch {
|
|
127
133
|
return false;
|
|
128
134
|
}
|
package/src/routes/auth.ts
CHANGED
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
buildFunctionPushProxy,
|
|
55
55
|
buildAdminAuthContext,
|
|
56
56
|
buildAdminDbProxy,
|
|
57
|
+
executeSqlWithDirectD1Access,
|
|
57
58
|
getWorkerUrl,
|
|
58
59
|
} from '../lib/functions.js';
|
|
59
60
|
import * as authService from '../lib/auth-d1-service.js';
|
|
@@ -659,18 +660,11 @@ export async function executeAuthHook(
|
|
|
659
660
|
db: adminDb,
|
|
660
661
|
table: (name: string) => adminDb('shared').table(name),
|
|
661
662
|
auth: authAdmin,
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
body: JSON.stringify({ namespace, id, sql: query, params: params ?? [] }),
|
|
668
|
-
});
|
|
669
|
-
if (!res.ok) throw new Error(`admin.sql() failed: ${res.status}`);
|
|
670
|
-
return res.json();
|
|
671
|
-
}
|
|
672
|
-
throw new Error('admin.sql() requires workerUrl in auth hook context.');
|
|
673
|
-
},
|
|
663
|
+
sqlWithDirectD1Access: (namespace: string, id: string | undefined, query: string, params?: unknown[]) =>
|
|
664
|
+
executeSqlWithDirectD1Access(
|
|
665
|
+
{ env, config, databaseNamespace: env.DATABASE, workerUrl: options.workerUrl, serviceKey },
|
|
666
|
+
namespace, id, query, params,
|
|
667
|
+
),
|
|
674
668
|
async broadcast(channel: string, event: string, payload?: Record<string, unknown>) {
|
|
675
669
|
if (options.workerUrl && serviceKey) {
|
|
676
670
|
await fetch(`${options.workerUrl}/api/db/broadcast`, {
|
package/src/routes/storage.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
buildFunctionPushProxy,
|
|
45
45
|
buildAdminAuthContext,
|
|
46
46
|
buildAdminDbProxy,
|
|
47
|
+
executeSqlWithDirectD1Access,
|
|
47
48
|
getWorkerUrl,
|
|
48
49
|
} from '../lib/functions.js';
|
|
49
50
|
|
|
@@ -165,18 +166,11 @@ function buildStorageHookAdminContext(
|
|
|
165
166
|
db: adminDb,
|
|
166
167
|
table: (name: string) => adminDb('shared').table(name),
|
|
167
168
|
auth: buildAdminAuthContext({ d1Database: env.AUTH_DB, serviceKey, workerUrl }),
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
body: JSON.stringify({ namespace, id, sql: query, params: params ?? [] }),
|
|
174
|
-
});
|
|
175
|
-
if (!res.ok) throw new Error(`admin.sql() failed: ${res.status}`);
|
|
176
|
-
return res.json();
|
|
177
|
-
}
|
|
178
|
-
throw new Error('admin.sql() requires workerUrl in storage hook context.');
|
|
179
|
-
},
|
|
169
|
+
sqlWithDirectD1Access: (namespace: string, id: string | undefined, query: string, params?: unknown[]) =>
|
|
170
|
+
executeSqlWithDirectD1Access(
|
|
171
|
+
{ env, config, databaseNamespace: env.DATABASE, workerUrl, serviceKey },
|
|
172
|
+
namespace, id, query, params,
|
|
173
|
+
),
|
|
180
174
|
async broadcast(channel: string, event: string, payload?: Record<string, unknown>) {
|
|
181
175
|
if (workerUrl && serviceKey) {
|
|
182
176
|
await fetch(`${workerUrl}/api/db/broadcast`, {
|
package/src/types.ts
CHANGED
|
@@ -100,6 +100,8 @@ export interface Env {
|
|
|
100
100
|
EDGEBASE_USE_TEST_CONFIG?: string;
|
|
101
101
|
|
|
102
102
|
// ─── Dev Mode ───
|
|
103
|
+
/** Enables browser-based first-admin setup for the local dev server. */
|
|
104
|
+
EDGEBASE_ALLOW_PUBLIC_ADMIN_SETUP?: string;
|
|
103
105
|
/** Schema Editor sidecar port — set by CLI dev command via --var */
|
|
104
106
|
EDGEBASE_DEV_SIDECAR_PORT?: string;
|
|
105
107
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as r}from"../chunks/m9QZTyVV.js";import{w as t}from"../chunks/D755Tqat.js";export{t as load_css,r as start};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{_ as m}from"../chunks/B0QyxC2M.js";export{m as component};
|