@edge-base/server 0.2.2 → 0.2.4
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/{C85dMlzL.js → 5RQRbp5q.js} +1 -1
- package/admin-build/_app/immutable/chunks/{B8DT4fss.js → BME_U9TJ.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BaCHY17I.js → BYI6CUvd.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BWyDPAjM.js → BgDzp0i0.js} +1 -1
- package/admin-build/_app/immutable/chunks/{c5iKSdWY.js → BjWZuf8W.js} +1 -1
- package/admin-build/_app/immutable/chunks/{g3ZZdY-r.js → C6lpZLE2.js} +1 -1
- package/admin-build/_app/immutable/chunks/{C-DsDCNG.js → D5GswVnI.js} +3 -3
- package/admin-build/_app/immutable/chunks/DBsVqhuh.js +1 -0
- package/admin-build/_app/immutable/chunks/{BEYYl662.js → DYaCRWMA.js} +1 -1
- package/admin-build/_app/immutable/chunks/D__dwMuW.js +1 -0
- package/admin-build/_app/immutable/chunks/{4vlsb8ej.js → Dj-E9-FO.js} +1 -1
- package/admin-build/_app/immutable/chunks/{kiJ6KthZ.js → Dj0QUuOf.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BKXmgPq4.js → XQM1k9PM.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CTngeX8H.js → fYEKMQ-Z.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CPdXvRUb.js → g_-Kpxu3.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DzXaj-Ja.js → wCNueVYy.js} +1 -1
- package/admin-build/_app/immutable/entry/{app.BZxfavhF.js → app.C8ylfBe6.js} +2 -2
- package/admin-build/_app/immutable/entry/start.CtsqDyfj.js +1 -0
- package/admin-build/_app/immutable/nodes/{0.DlsaydXO.js → 0.CJJ6HZbp.js} +1 -1
- package/admin-build/_app/immutable/nodes/{1.D2NWN5eG.js → 1.B4sI5cB4.js} +1 -1
- package/admin-build/_app/immutable/nodes/{10.EMDaN3nw.js → 10.D6hvCer6.js} +1 -1
- package/admin-build/_app/immutable/nodes/{11.BasqQ_o9.js → 11.Dx7b8aQ5.js} +1 -1
- package/admin-build/_app/immutable/nodes/{12.DO31Ljs7.js → 12.Bqmy5KIF.js} +1 -1
- package/admin-build/_app/immutable/nodes/{13.DhyAy-GZ.js → 13.CC6KpXgS.js} +1 -1
- package/admin-build/_app/immutable/nodes/{14.CLecGWc4.js → 14.yCo1Ix8E.js} +1 -1
- package/admin-build/_app/immutable/nodes/{15.B9kp3W4e.js → 15.co0UfPlh.js} +1 -1
- package/admin-build/_app/immutable/nodes/{16.Pu_8T3RI.js → 16.D0xkPUBW.js} +1 -1
- package/admin-build/_app/immutable/nodes/{17.DX4z43t6.js → 17.CebNqPeh.js} +1 -1
- package/admin-build/_app/immutable/nodes/{18.BKsSaxrr.js → 18.JUoLOZxh.js} +1 -1
- package/admin-build/_app/immutable/nodes/{19.DXNF1htN.js → 19.ND8kmQJe.js} +1 -1
- package/admin-build/_app/immutable/nodes/{20.VRVb0wee.js → 20.DYb-q3W8.js} +1 -1
- package/admin-build/_app/immutable/nodes/21.cz3IN9Cc.js +1 -0
- package/admin-build/_app/immutable/nodes/{22.DqZf4CtH.js → 22.UOzm8WYV.js} +1 -1
- package/admin-build/_app/immutable/nodes/{23.DtyxMiQG.js → 23.BLgq21om.js} +1 -1
- package/admin-build/_app/immutable/nodes/{24.CloWNmTd.js → 24.DN9usmUs.js} +1 -1
- package/admin-build/_app/immutable/nodes/{25.CnZWMq7_.js → 25.BddRfAyE.js} +1 -1
- package/admin-build/_app/immutable/nodes/{26.DrV7XOmf.js → 26.Dl6XHIeT.js} +1 -1
- package/admin-build/_app/immutable/nodes/{27.DV8L32OF.js → 27.D0iNwALG.js} +1 -1
- package/admin-build/_app/immutable/nodes/{28.Stil2D4u.js → 28.9dKQmdGi.js} +1 -1
- package/admin-build/_app/immutable/nodes/{29.Zsm1e5Dc.js → 29.wXzfJUXp.js} +1 -1
- package/admin-build/_app/immutable/nodes/{3.CKoj2vNz.js → 3.z8ut3jS-.js} +1 -1
- package/admin-build/_app/immutable/nodes/{30.Ni0k5bER.js → 30.BtZETNsL.js} +1 -1
- package/admin-build/_app/immutable/nodes/{31.mnqj9EbV.js → 31.CYonj2Jh.js} +1 -1
- package/admin-build/_app/immutable/nodes/{4.B_-z9AzT.js → 4.COtDPQ9b.js} +1 -1
- package/admin-build/_app/immutable/nodes/{5.yiZ72j4k.js → 5.CTRCeIhp.js} +1 -1
- package/admin-build/_app/immutable/nodes/{6.BqykybBG.js → 6.ChHi3QkR.js} +1 -1
- package/admin-build/_app/immutable/nodes/{7.BDAHlhsF.js → 7.CCMtr6Ac.js} +1 -1
- package/admin-build/_app/immutable/nodes/{8.D8Xvy0lH.js → 8.DpWJ-X_-.js} +1 -1
- package/admin-build/_app/immutable/nodes/{9.Dddmd7_F.js → 9.DOkvfmir.js} +1 -1
- package/admin-build/_app/version.json +1 -1
- package/admin-build/index.html +7 -7
- package/package.json +3 -3
- package/src/__tests__/admin-data-routes.test.ts +29 -0
- package/src/__tests__/d1-live-broadcast-verification.test.ts +271 -0
- package/src/__tests__/database-do-route-validation.test.ts +105 -0
- package/src/__tests__/database-live-do.test.ts +50 -0
- package/src/__tests__/database-live-emitter.test.ts +116 -1
- package/src/__tests__/database-live-route.test.ts +82 -0
- package/src/__tests__/do-router.test.ts +116 -0
- package/src/__tests__/error-format.test.ts +63 -0
- package/src/__tests__/functions-context.test.ts +674 -33
- package/src/__tests__/functions-d1-proxy.test.ts +54 -0
- package/src/__tests__/plugin-migration-routing.test.ts +32 -0
- package/src/__tests__/postgres-field-ops-compat.test.ts +110 -0
- package/src/__tests__/provider-aware-sql.test.ts +163 -0
- package/src/__tests__/room-auth-state-loss.test.ts +124 -0
- package/src/__tests__/runtime-surface-accounting.test.ts +0 -4
- package/src/__tests__/scheduled.test.ts +55 -0
- package/src/__tests__/service-key-db-proxy.test.ts +122 -1
- package/src/__tests__/sql-route.test.ts +252 -75
- package/src/__tests__/table-hook-runtime.test.ts +137 -0
- package/src/durable-objects/database-do.ts +36 -45
- package/src/durable-objects/database-live-do.ts +46 -1
- package/src/durable-objects/room-runtime-base.ts +26 -2
- package/src/durable-objects/rooms-do.ts +1 -1
- package/src/index.ts +12 -6
- package/src/lib/admin-db-target.ts +30 -74
- package/src/lib/d1-handler.ts +55 -35
- package/src/lib/database-live-emitter.ts +57 -16
- package/src/lib/do-router.ts +135 -3
- package/src/lib/functions.ts +215 -143
- package/src/lib/internal-transport.ts +28 -12
- package/src/lib/plugin-migration-routing.ts +28 -0
- package/src/lib/plugin-migrations.ts +38 -38
- package/src/lib/postgres-handler.ts +51 -31
- package/src/lib/provider-aware-sql.ts +831 -0
- package/src/lib/table-hook-runtime.ts +62 -0
- package/src/routes/admin.ts +41 -41
- package/src/routes/auth.ts +7 -2
- package/src/routes/database-live.ts +110 -12
- package/src/routes/sql.ts +64 -84
- package/src/routes/storage.ts +7 -2
- package/src/routes/tables.ts +42 -29
- package/admin-build/_app/immutable/chunks/5PDcRlfX.js +0 -1
- package/admin-build/_app/immutable/chunks/qiZXAKh-.js +0 -1
- package/admin-build/_app/immutable/entry/start.Mr9mmopc.js +0 -1
- package/admin-build/_app/immutable/nodes/21.Ck3_0D2f.js +0 -1
package/src/lib/functions.ts
CHANGED
|
@@ -25,8 +25,7 @@ import type {
|
|
|
25
25
|
ScheduleTrigger,
|
|
26
26
|
HttpTrigger,
|
|
27
27
|
} from '@edge-base/shared';
|
|
28
|
-
import { getD1BindingName,
|
|
29
|
-
import { executeDoSql } from './do-sql.js';
|
|
28
|
+
import { getD1BindingName, normalizeDbInstanceId } from './do-router.js';
|
|
30
29
|
import { D1AuthDb, type AuthDb } from './auth-db-adapter.js';
|
|
31
30
|
import { ensureAuthSchema } from './auth-d1.js';
|
|
32
31
|
import type { Env } from '../types.js';
|
|
@@ -41,6 +40,7 @@ import { hashPassword } from './password.js';
|
|
|
41
40
|
import { generateId } from './uuid.js';
|
|
42
41
|
import { DbRef, TableRef, DefaultDbApi, HttpClient, ContextManager } from '@edge-base/core';
|
|
43
42
|
import { InternalHttpTransport } from './internal-transport.js';
|
|
43
|
+
import { executeProviderAwareSql } from './provider-aware-sql.js';
|
|
44
44
|
|
|
45
45
|
// ─── Function Context Types ───
|
|
46
46
|
|
|
@@ -95,11 +95,21 @@ export interface FunctionAdminContext {
|
|
|
95
95
|
/** Admin user management. */
|
|
96
96
|
auth: AdminAuthContext;
|
|
97
97
|
/**
|
|
98
|
-
* Execute raw SQL
|
|
99
|
-
*
|
|
98
|
+
* Execute raw SQL through the fastest provider-aware path available.
|
|
99
|
+
* Uses direct PostgreSQL / Neon, D1, or DO execution when possible,
|
|
100
|
+
* then falls back to the internal HTTP SQL route when needed.
|
|
100
101
|
*
|
|
101
102
|
* @example
|
|
102
|
-
* const rows = await ctx.admin.
|
|
103
|
+
* const rows = await ctx.admin.sqlProviderAware('shared', undefined, 'SELECT * FROM posts WHERE status = ?', ['published']);
|
|
104
|
+
*/
|
|
105
|
+
sqlProviderAware(
|
|
106
|
+
namespace: string,
|
|
107
|
+
id: string | undefined,
|
|
108
|
+
query: string,
|
|
109
|
+
params?: unknown[],
|
|
110
|
+
): Promise<unknown[]>;
|
|
111
|
+
/**
|
|
112
|
+
* @deprecated Use `sqlProviderAware()` instead.
|
|
103
113
|
*/
|
|
104
114
|
sqlWithDirectD1Access(
|
|
105
115
|
namespace: string,
|
|
@@ -136,9 +146,7 @@ export interface FunctionPushProxy {
|
|
|
136
146
|
payload: Record<string, unknown>,
|
|
137
147
|
): Promise<{ sent: number; failed: number; removed: number }>;
|
|
138
148
|
/** Get registered device tokens for a user — token values NOT exposed. */
|
|
139
|
-
getTokens(
|
|
140
|
-
userId: string,
|
|
141
|
-
): Promise<
|
|
149
|
+
getTokens(userId: string): Promise<
|
|
142
150
|
Array<{
|
|
143
151
|
deviceId: string;
|
|
144
152
|
platform: string;
|
|
@@ -178,12 +186,30 @@ export interface FunctionPushProxy {
|
|
|
178
186
|
|
|
179
187
|
/** Storage proxy for App Functions — wraps R2Bucket with convenience methods. */
|
|
180
188
|
export interface FunctionStorageProxy {
|
|
181
|
-
put(
|
|
182
|
-
|
|
189
|
+
put(
|
|
190
|
+
key: string,
|
|
191
|
+
value: ReadableStream | ArrayBuffer | string,
|
|
192
|
+
options?: { contentType?: string; customMetadata?: Record<string, string> },
|
|
193
|
+
): Promise<void>;
|
|
194
|
+
get(key: string): Promise<{
|
|
195
|
+
body: ReadableStream;
|
|
196
|
+
contentType: string;
|
|
197
|
+
size: number;
|
|
198
|
+
customMetadata: Record<string, string>;
|
|
199
|
+
} | null>;
|
|
183
200
|
delete(key: string): Promise<void>;
|
|
184
201
|
getSignedUrl(key: string, options?: { expiresIn?: number }): Promise<string>;
|
|
185
|
-
list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<{
|
|
186
|
-
|
|
202
|
+
list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<{
|
|
203
|
+
keys: Array<{ key: string; size: number; contentType: string }>;
|
|
204
|
+
cursor?: string;
|
|
205
|
+
truncated: boolean;
|
|
206
|
+
}>;
|
|
207
|
+
head(key: string): Promise<{
|
|
208
|
+
key: string;
|
|
209
|
+
size: number;
|
|
210
|
+
contentType: string;
|
|
211
|
+
customMetadata: Record<string, string>;
|
|
212
|
+
} | null>;
|
|
187
213
|
}
|
|
188
214
|
|
|
189
215
|
/** KV proxy for App Functions — routes through Worker HTTP. */
|
|
@@ -363,13 +389,14 @@ function getRegistryName(key: string, def: FunctionDefinition): string {
|
|
|
363
389
|
|
|
364
390
|
export function registerFunction(name: string, def: FunctionDefinition): void {
|
|
365
391
|
if (!def || typeof def !== 'object' || !def.trigger) {
|
|
366
|
-
const received =
|
|
367
|
-
|
|
368
|
-
|
|
392
|
+
const received =
|
|
393
|
+
typeof def === 'function'
|
|
394
|
+
? 'a plain function'
|
|
395
|
+
: `${typeof def} (${JSON.stringify(def)?.slice(0, 100)})`;
|
|
369
396
|
throw new Error(
|
|
370
397
|
`registerFunction('${name}'): expected a FunctionDefinition with a 'trigger' property, but received ${received}. ` +
|
|
371
|
-
|
|
372
|
-
|
|
398
|
+
`Functions must use defineFunction() from '@edge-base/shared' and be exported as named HTTP method exports ` +
|
|
399
|
+
`(e.g. export const GET = defineFunction(...)). See https://docs.edgebase.dev/functions for details.`,
|
|
373
400
|
);
|
|
374
401
|
}
|
|
375
402
|
functionRegistry.set(buildRegistryKey(name, def), def);
|
|
@@ -676,7 +703,12 @@ export function wrapMethodExport(
|
|
|
676
703
|
} else if (handler && typeof handler === 'object') {
|
|
677
704
|
fn = (handler.handler ?? handler) as unknown as (ctx: unknown) => Promise<unknown>;
|
|
678
705
|
captcha = handler.captcha;
|
|
679
|
-
if (
|
|
706
|
+
if (
|
|
707
|
+
'trigger' in handler &&
|
|
708
|
+
handler.trigger &&
|
|
709
|
+
typeof handler.trigger === 'object' &&
|
|
710
|
+
'path' in handler.trigger
|
|
711
|
+
) {
|
|
680
712
|
const triggerPath = handler.trigger.path;
|
|
681
713
|
path = typeof triggerPath === 'string' ? triggerPath : undefined;
|
|
682
714
|
}
|
|
@@ -707,10 +739,10 @@ interface BuildAdminDbProxyOptions {
|
|
|
707
739
|
preferDirectDo?: boolean;
|
|
708
740
|
}
|
|
709
741
|
|
|
710
|
-
// ─── Shared SQL executor —
|
|
742
|
+
// ─── Shared SQL executor — provider-aware direct paths → HTTP fallback ───
|
|
711
743
|
|
|
712
|
-
export interface
|
|
713
|
-
env?:
|
|
744
|
+
export interface SqlProviderAwareOptions {
|
|
745
|
+
env?: Env;
|
|
714
746
|
config: EdgeBaseConfig;
|
|
715
747
|
databaseNamespace?: DurableObjectNamespace;
|
|
716
748
|
workerUrl?: string;
|
|
@@ -718,89 +750,49 @@ export interface SqlWithDirectD1AccessOptions {
|
|
|
718
750
|
}
|
|
719
751
|
|
|
720
752
|
/**
|
|
721
|
-
* Execute raw SQL with the fastest
|
|
722
|
-
* 1.
|
|
723
|
-
* 2.
|
|
724
|
-
* 3.
|
|
753
|
+
* Execute raw SQL with the fastest provider-aware path:
|
|
754
|
+
* 1. PostgreSQL / Neon direct query path
|
|
755
|
+
* 2. D1 direct binding
|
|
756
|
+
* 3. Durable Object direct call
|
|
757
|
+
* 4. HTTP fallback via workerUrl
|
|
725
758
|
*
|
|
726
|
-
* Shared by buildFunctionContext, auth hooks, and
|
|
759
|
+
* Shared by buildFunctionContext, auth hooks, storage hooks, and plugin migrations.
|
|
727
760
|
*/
|
|
728
|
-
export async function
|
|
729
|
-
opts:
|
|
761
|
+
export async function executeSqlProviderAware(
|
|
762
|
+
opts: SqlProviderAwareOptions,
|
|
730
763
|
namespace: string,
|
|
731
764
|
id: string | undefined,
|
|
732
765
|
query: string,
|
|
733
766
|
params?: unknown[],
|
|
734
767
|
): Promise<unknown[]> {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
try {
|
|
749
|
-
const stmt = d1.prepare(query);
|
|
750
|
-
const bound = params && params.length > 0 ? stmt.bind(...params) : stmt;
|
|
751
|
-
const result = await bound.all();
|
|
752
|
-
return (result.results ?? []) as unknown[];
|
|
753
|
-
} catch (error) {
|
|
754
|
-
const message = error instanceof Error ? error.message : 'SQL execution failed';
|
|
755
|
-
throw new Error(message);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
if (opts.databaseNamespace) {
|
|
760
|
-
return executeDoSql({
|
|
761
|
-
databaseNamespace: opts.databaseNamespace,
|
|
762
|
-
namespace,
|
|
763
|
-
id,
|
|
764
|
-
query,
|
|
765
|
-
params: params ?? [],
|
|
766
|
-
internal: true,
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
if (opts.workerUrl && opts.serviceKey) {
|
|
772
|
-
const res = await fetch(`${opts.workerUrl}/api/sql`, {
|
|
773
|
-
method: 'POST',
|
|
774
|
-
headers: {
|
|
775
|
-
'Content-Type': 'application/json',
|
|
776
|
-
'X-EdgeBase-Service-Key': opts.serviceKey,
|
|
777
|
-
},
|
|
778
|
-
body: JSON.stringify({ namespace, id, sql: query, params: params ?? [] }),
|
|
779
|
-
});
|
|
780
|
-
if (!res.ok) {
|
|
781
|
-
const err = (await res.json().catch(() => ({ message: 'SQL execution failed' }))) as { message: string };
|
|
782
|
-
throw new Error(err.message);
|
|
783
|
-
}
|
|
784
|
-
const data = (await res.json()) as { rows?: unknown[]; items?: unknown[]; results?: unknown[] };
|
|
785
|
-
if (Array.isArray(data.rows)) return data.rows;
|
|
786
|
-
if (Array.isArray(data.items)) return data.items;
|
|
787
|
-
if (Array.isArray(data.results)) return data.results;
|
|
788
|
-
return [];
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
throw new Error(
|
|
792
|
-
'admin.sqlWithDirectD1Access() requires env or workerUrl.',
|
|
768
|
+
const result = await executeProviderAwareSql(
|
|
769
|
+
{
|
|
770
|
+
env: opts.env,
|
|
771
|
+
config: opts.config,
|
|
772
|
+
databaseNamespace: opts.databaseNamespace,
|
|
773
|
+
workerUrl: opts.workerUrl,
|
|
774
|
+
serviceKey: opts.serviceKey,
|
|
775
|
+
},
|
|
776
|
+
namespace,
|
|
777
|
+
id,
|
|
778
|
+
query,
|
|
779
|
+
params ?? [],
|
|
793
780
|
);
|
|
781
|
+
return result.rows;
|
|
794
782
|
}
|
|
795
783
|
|
|
784
|
+
// Backwards-compatible aliases for existing internal callers and public surfaces.
|
|
785
|
+
export const executeSqlWithDirectBindingAccess = executeSqlProviderAware;
|
|
786
|
+
export const executeSqlWithDirectD1Access = executeSqlProviderAware;
|
|
787
|
+
|
|
796
788
|
/**
|
|
797
789
|
* Build the admin DB proxy that returns real DbRef/TableRef instances
|
|
798
790
|
* from @edge-base/core, routed through InternalHttpTransport for
|
|
799
791
|
* direct D1/PG/DO access (no HTTP round-trip).
|
|
800
792
|
*/
|
|
801
793
|
export function buildAdminDbProxy(options: BuildAdminDbProxyOptions): FunctionAdminContext['db'] {
|
|
802
|
-
// Create HttpClient for sql() tagged template support on TableRef.
|
|
803
|
-
//
|
|
794
|
+
// Create HttpClient fallback for sql() tagged template support on TableRef.
|
|
795
|
+
// Trusted server contexts also receive a direct SQL executor below.
|
|
804
796
|
let httpClient: HttpClient | undefined;
|
|
805
797
|
if (options.workerUrl) {
|
|
806
798
|
httpClient = new HttpClient({
|
|
@@ -809,8 +801,28 @@ export function buildAdminDbProxy(options: BuildAdminDbProxyOptions): FunctionAd
|
|
|
809
801
|
contextManager: new ContextManager(),
|
|
810
802
|
});
|
|
811
803
|
}
|
|
804
|
+
const sqlExecutor = (
|
|
805
|
+
namespace: string,
|
|
806
|
+
id: string | undefined,
|
|
807
|
+
query: string,
|
|
808
|
+
params?: unknown[],
|
|
809
|
+
) =>
|
|
810
|
+
executeSqlProviderAware(
|
|
811
|
+
{
|
|
812
|
+
env: options.env,
|
|
813
|
+
config: options.config,
|
|
814
|
+
databaseNamespace: options.databaseNamespace,
|
|
815
|
+
workerUrl: options.workerUrl,
|
|
816
|
+
serviceKey: options.serviceKey,
|
|
817
|
+
},
|
|
818
|
+
namespace,
|
|
819
|
+
id,
|
|
820
|
+
query,
|
|
821
|
+
params,
|
|
822
|
+
);
|
|
812
823
|
|
|
813
824
|
return (namespace: string, id?: string): DbRef => {
|
|
825
|
+
const normalizedId = normalizeDbInstanceId(id);
|
|
814
826
|
// Create a per-DbRef transport with explicit dbContext so that
|
|
815
827
|
// path parsing is unambiguous even when instanceId === 'tables'.
|
|
816
828
|
const transport = new InternalHttpTransport({
|
|
@@ -821,16 +833,17 @@ export function buildAdminDbProxy(options: BuildAdminDbProxyOptions): FunctionAd
|
|
|
821
833
|
env: options.env,
|
|
822
834
|
executionCtx: options.executionCtx,
|
|
823
835
|
preferDirectDo: options.preferDirectDo,
|
|
824
|
-
dbContext: { namespace, instanceId:
|
|
836
|
+
dbContext: { namespace, instanceId: normalizedId },
|
|
825
837
|
});
|
|
826
838
|
const dbApi = new DefaultDbApi(transport);
|
|
827
839
|
return new DbRef(
|
|
828
840
|
dbApi,
|
|
829
841
|
namespace,
|
|
830
|
-
|
|
831
|
-
undefined,
|
|
832
|
-
undefined,
|
|
833
|
-
httpClient,
|
|
842
|
+
normalizedId,
|
|
843
|
+
undefined, // databaseLiveClient — not available server-side
|
|
844
|
+
undefined, // filterMatchFn
|
|
845
|
+
httpClient, // enables table().sql`...` tagged template
|
|
846
|
+
sqlExecutor,
|
|
834
847
|
);
|
|
835
848
|
};
|
|
836
849
|
}
|
|
@@ -919,8 +932,10 @@ export function buildAdminAuthContext(options: AdminAuthOptions): AdminAuthConte
|
|
|
919
932
|
// Direct D1 path — works without service key (same as updateUser/deleteUser)
|
|
920
933
|
if (d1Database) {
|
|
921
934
|
// Input validation (mirrors routes/admin-auth.ts guards)
|
|
922
|
-
if (!data.email || typeof data.email !== 'string')
|
|
923
|
-
|
|
935
|
+
if (!data.email || typeof data.email !== 'string')
|
|
936
|
+
throw new Error('Email and password are required.');
|
|
937
|
+
if (!data.password || typeof data.password !== 'string')
|
|
938
|
+
throw new Error('Email and password are required.');
|
|
924
939
|
const email = data.email.trim().toLowerCase();
|
|
925
940
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
926
941
|
throw new Error('Invalid email format.');
|
|
@@ -1081,6 +1096,25 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
|
|
|
1081
1096
|
executionCtx: options.executionCtx,
|
|
1082
1097
|
preferDirectDo: options.preferDirectDoDb,
|
|
1083
1098
|
});
|
|
1099
|
+
const sqlProviderAware = (
|
|
1100
|
+
namespace: string,
|
|
1101
|
+
id: string | undefined,
|
|
1102
|
+
query: string,
|
|
1103
|
+
params?: unknown[],
|
|
1104
|
+
) =>
|
|
1105
|
+
executeSqlProviderAware(
|
|
1106
|
+
{
|
|
1107
|
+
env: options.env,
|
|
1108
|
+
config: options.config,
|
|
1109
|
+
databaseNamespace: options.databaseNamespace,
|
|
1110
|
+
workerUrl: options.workerUrl,
|
|
1111
|
+
serviceKey: options.serviceKey,
|
|
1112
|
+
},
|
|
1113
|
+
namespace,
|
|
1114
|
+
id,
|
|
1115
|
+
query,
|
|
1116
|
+
params,
|
|
1117
|
+
);
|
|
1084
1118
|
|
|
1085
1119
|
// ─── context.admin — AdminEdgeBase-shaped internal proxy ───
|
|
1086
1120
|
const admin: FunctionAdminContext = {
|
|
@@ -1089,18 +1123,9 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
|
|
|
1089
1123
|
// ─── context.admin.db(namespace, id) — DB-first tenant access (§5) ───
|
|
1090
1124
|
db: adminDb,
|
|
1091
1125
|
auth: adminAuthContext,
|
|
1092
|
-
// ─── Direct
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
{
|
|
1096
|
-
env: options.env,
|
|
1097
|
-
config: options.config,
|
|
1098
|
-
databaseNamespace: options.databaseNamespace,
|
|
1099
|
-
workerUrl: options.workerUrl,
|
|
1100
|
-
serviceKey: options.serviceKey,
|
|
1101
|
-
},
|
|
1102
|
-
namespace, id, query, params,
|
|
1103
|
-
),
|
|
1126
|
+
// ─── Direct provider-aware SQL — delegates to shared executor ───
|
|
1127
|
+
sqlProviderAware,
|
|
1128
|
+
sqlWithDirectD1Access: sqlProviderAware,
|
|
1104
1129
|
async broadcast(
|
|
1105
1130
|
channel: string,
|
|
1106
1131
|
event: string,
|
|
@@ -1109,11 +1134,13 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
|
|
|
1109
1134
|
if (options.env?.DATABASE_LIVE) {
|
|
1110
1135
|
const hubId = options.env.DATABASE_LIVE.idFromName('database-live:hub');
|
|
1111
1136
|
const stub = options.env.DATABASE_LIVE.get(hubId);
|
|
1112
|
-
const response = await stub.fetch(
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1137
|
+
const response = await stub.fetch(
|
|
1138
|
+
new Request('http://do/internal/broadcast', {
|
|
1139
|
+
method: 'POST',
|
|
1140
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1141
|
+
body: JSON.stringify({ channel, event, payload: payload ?? {} }),
|
|
1142
|
+
}),
|
|
1143
|
+
);
|
|
1117
1144
|
if (!response.ok) {
|
|
1118
1145
|
throw new Error(`client.broadcast() failed: ${response.status}`);
|
|
1119
1146
|
}
|
|
@@ -1161,9 +1188,7 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
|
|
|
1161
1188
|
method: 'POST',
|
|
1162
1189
|
headers: {
|
|
1163
1190
|
'Content-Type': 'application/json',
|
|
1164
|
-
...(options.serviceKey
|
|
1165
|
-
? { 'X-EdgeBase-Service-Key': options.serviceKey }
|
|
1166
|
-
: {}),
|
|
1191
|
+
...(options.serviceKey ? { 'X-EdgeBase-Service-Key': options.serviceKey } : {}),
|
|
1167
1192
|
'X-EdgeBase-Call-Depth': String(currentDepth + 1),
|
|
1168
1193
|
},
|
|
1169
1194
|
body: JSON.stringify(data ?? {}),
|
|
@@ -1221,13 +1246,31 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
|
|
|
1221
1246
|
|
|
1222
1247
|
// KV / D1 / Vectorize proxies
|
|
1223
1248
|
kv(namespace: string): FunctionKvProxy {
|
|
1224
|
-
return buildFunctionKvProxy(
|
|
1249
|
+
return buildFunctionKvProxy(
|
|
1250
|
+
namespace,
|
|
1251
|
+
options.config,
|
|
1252
|
+
options.env,
|
|
1253
|
+
options.workerUrl,
|
|
1254
|
+
options.serviceKey,
|
|
1255
|
+
);
|
|
1225
1256
|
},
|
|
1226
1257
|
d1(database: string): FunctionD1Proxy {
|
|
1227
|
-
return buildFunctionD1Proxy(
|
|
1258
|
+
return buildFunctionD1Proxy(
|
|
1259
|
+
database,
|
|
1260
|
+
options.config,
|
|
1261
|
+
options.env,
|
|
1262
|
+
options.workerUrl,
|
|
1263
|
+
options.serviceKey,
|
|
1264
|
+
);
|
|
1228
1265
|
},
|
|
1229
1266
|
vector(index: string): FunctionVectorizeProxy {
|
|
1230
|
-
return buildFunctionVectorizeProxy(
|
|
1267
|
+
return buildFunctionVectorizeProxy(
|
|
1268
|
+
index,
|
|
1269
|
+
options.config,
|
|
1270
|
+
options.env,
|
|
1271
|
+
options.workerUrl,
|
|
1272
|
+
options.serviceKey,
|
|
1273
|
+
);
|
|
1231
1274
|
},
|
|
1232
1275
|
|
|
1233
1276
|
// Push notification management
|
|
@@ -1466,11 +1509,14 @@ export function buildFunctionD1Proxy(
|
|
|
1466
1509
|
return {
|
|
1467
1510
|
async exec<T = Record<string, unknown>>(query: string, params?: unknown[]): Promise<T[]> {
|
|
1468
1511
|
if (config && env) {
|
|
1469
|
-
const bindingName =
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1512
|
+
const bindingName =
|
|
1513
|
+
config.d1?.[database]?.binding ??
|
|
1514
|
+
(database === 'auth' ? 'AUTH_DB' : undefined) ??
|
|
1515
|
+
(database === 'control' ? 'CONTROL_DB' : undefined) ??
|
|
1516
|
+
getD1BindingName(database);
|
|
1517
|
+
const binding = (env as unknown as Record<string, unknown>)[bindingName] as
|
|
1518
|
+
| D1Database
|
|
1519
|
+
| undefined;
|
|
1474
1520
|
if (!binding) {
|
|
1475
1521
|
throw new Error(`D1 binding '${bindingName}' not found.`);
|
|
1476
1522
|
}
|
|
@@ -1522,7 +1568,7 @@ export function buildFunctionVectorizeProxy(
|
|
|
1522
1568
|
if (values instanceof Float32Array || values instanceof Float64Array) {
|
|
1523
1569
|
return Array.from(values);
|
|
1524
1570
|
}
|
|
1525
|
-
return Array.isArray(values) ? values as number[] : undefined;
|
|
1571
|
+
return Array.isArray(values) ? (values as number[]) : undefined;
|
|
1526
1572
|
};
|
|
1527
1573
|
|
|
1528
1574
|
const mapMatches = (
|
|
@@ -1533,15 +1579,19 @@ export function buildFunctionVectorizeProxy(
|
|
|
1533
1579
|
metadata?: Record<string, unknown>;
|
|
1534
1580
|
namespace?: string;
|
|
1535
1581
|
}>,
|
|
1536
|
-
) =>
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1582
|
+
) =>
|
|
1583
|
+
matches.map((match) => ({
|
|
1584
|
+
id: match.id,
|
|
1585
|
+
score: match.score,
|
|
1586
|
+
...(match.values !== undefined ? { values: normalizeValues(match.values) } : {}),
|
|
1587
|
+
...(match.metadata !== undefined ? { metadata: match.metadata } : {}),
|
|
1588
|
+
...(match.namespace ? { namespace: match.namespace } : {}),
|
|
1589
|
+
}));
|
|
1590
|
+
|
|
1591
|
+
const withNamespace = <T extends { namespace?: string }>(
|
|
1592
|
+
vectors: T[],
|
|
1593
|
+
namespace?: string,
|
|
1594
|
+
): T[] => {
|
|
1545
1595
|
if (!namespace) return vectors;
|
|
1546
1596
|
return vectors.map((vector) => (vector.namespace ? vector : { ...vector, namespace }));
|
|
1547
1597
|
};
|
|
@@ -1559,7 +1609,10 @@ export function buildFunctionVectorizeProxy(
|
|
|
1559
1609
|
if (directBinding) {
|
|
1560
1610
|
switch (body.action) {
|
|
1561
1611
|
case 'upsert': {
|
|
1562
|
-
const vectors = withNamespace(
|
|
1612
|
+
const vectors = withNamespace(
|
|
1613
|
+
body.vectors as VectorizeVector[],
|
|
1614
|
+
body.namespace as string | undefined,
|
|
1615
|
+
);
|
|
1563
1616
|
let count = 0;
|
|
1564
1617
|
let mutationId: string | undefined;
|
|
1565
1618
|
for (const chunk of chunkArray(vectors, VECTOR_BATCH_LIMIT)) {
|
|
@@ -1572,7 +1625,10 @@ export function buildFunctionVectorizeProxy(
|
|
|
1572
1625
|
return { count, ...(mutationId ? { mutationId } : {}) };
|
|
1573
1626
|
}
|
|
1574
1627
|
case 'insert': {
|
|
1575
|
-
const vectors = withNamespace(
|
|
1628
|
+
const vectors = withNamespace(
|
|
1629
|
+
body.vectors as VectorizeVector[],
|
|
1630
|
+
body.namespace as string | undefined,
|
|
1631
|
+
);
|
|
1576
1632
|
let count = 0;
|
|
1577
1633
|
let mutationId: string | undefined;
|
|
1578
1634
|
for (const chunk of chunkArray(vectors, VECTOR_BATCH_LIMIT)) {
|
|
@@ -1595,9 +1651,11 @@ export function buildFunctionVectorizeProxy(
|
|
|
1595
1651
|
return { matches: mapMatches(result.matches), count: result.count };
|
|
1596
1652
|
}
|
|
1597
1653
|
case 'queryById': {
|
|
1598
|
-
const queryById = (
|
|
1599
|
-
|
|
1600
|
-
|
|
1654
|
+
const queryById = (
|
|
1655
|
+
directBinding as unknown as {
|
|
1656
|
+
queryById?: (id: string, opts?: VectorizeQueryOptions) => Promise<VectorizeMatches>;
|
|
1657
|
+
}
|
|
1658
|
+
).queryById;
|
|
1601
1659
|
if (typeof queryById !== 'function') {
|
|
1602
1660
|
throw new Error('queryById is not available on this Vectorize binding');
|
|
1603
1661
|
}
|
|
@@ -1612,7 +1670,11 @@ export function buildFunctionVectorizeProxy(
|
|
|
1612
1670
|
}
|
|
1613
1671
|
case 'getByIds': {
|
|
1614
1672
|
const vectors = (
|
|
1615
|
-
await Promise.all(
|
|
1673
|
+
await Promise.all(
|
|
1674
|
+
chunkArray(body.ids as string[], VECTOR_BATCH_LIMIT).map((chunk) =>
|
|
1675
|
+
directBinding.getByIds(chunk),
|
|
1676
|
+
),
|
|
1677
|
+
)
|
|
1616
1678
|
).flat();
|
|
1617
1679
|
return {
|
|
1618
1680
|
vectors: vectors.map((vector) => ({
|
|
@@ -1640,12 +1702,22 @@ export function buildFunctionVectorizeProxy(
|
|
|
1640
1702
|
const details = info as unknown as Record<string, unknown>;
|
|
1641
1703
|
return {
|
|
1642
1704
|
vectorCount: details.vectorCount ?? details.vectorsCount ?? 0,
|
|
1643
|
-
dimensions:
|
|
1644
|
-
|
|
1705
|
+
dimensions:
|
|
1706
|
+
details.dimensions ??
|
|
1707
|
+
(details.config as Record<string, unknown> | undefined)?.dimensions ??
|
|
1708
|
+
0,
|
|
1709
|
+
metric:
|
|
1710
|
+
details.metric ??
|
|
1711
|
+
(details.config as Record<string, unknown> | undefined)?.metric ??
|
|
1712
|
+
'cosine',
|
|
1645
1713
|
...('id' in details ? { id: details.id } : {}),
|
|
1646
1714
|
...('name' in details ? { name: details.name } : {}),
|
|
1647
|
-
...('processedUpToDatetime' in details
|
|
1648
|
-
|
|
1715
|
+
...('processedUpToDatetime' in details
|
|
1716
|
+
? { processedUpToDatetime: details.processedUpToDatetime }
|
|
1717
|
+
: {}),
|
|
1718
|
+
...('processedUpToMutation' in details
|
|
1719
|
+
? { processedUpToMutation: details.processedUpToMutation }
|
|
1720
|
+
: {}),
|
|
1649
1721
|
};
|
|
1650
1722
|
}
|
|
1651
1723
|
}
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { HttpTransport } from '@edge-base/core';
|
|
10
10
|
import type { EdgeBaseConfig } from '@edge-base/shared';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
callDO,
|
|
13
|
+
formatDbTargetValidationIssue,
|
|
14
|
+
getDbDoName,
|
|
15
|
+
resolveDbTarget,
|
|
16
|
+
shouldRouteToD1,
|
|
17
|
+
} from './do-router.js';
|
|
12
18
|
import { handleD1Request } from './d1-handler.js';
|
|
13
19
|
import { handlePgRequest } from './postgres-handler.js';
|
|
14
20
|
import { buildInternalHandlerContext } from './internal-request.js';
|
|
@@ -115,7 +121,12 @@ export class InternalHttpTransport implements HttpTransport {
|
|
|
115
121
|
options?: { query?: Record<string, string>; body?: unknown },
|
|
116
122
|
): Promise<T> {
|
|
117
123
|
const { namespace, instanceId, tableName, directPath } = parsePath(path, this.dbContext);
|
|
118
|
-
const
|
|
124
|
+
const target = resolveDbTarget(this.config, namespace, instanceId);
|
|
125
|
+
if (!target.ok) {
|
|
126
|
+
throw new Error(formatDbTargetValidationIssue(target.issue, namespace));
|
|
127
|
+
}
|
|
128
|
+
const { instanceId: normalizedInstanceId } = target.value;
|
|
129
|
+
const doName = getDbDoName(namespace, normalizedInstanceId);
|
|
119
130
|
|
|
120
131
|
// Build internal headers
|
|
121
132
|
const headers: Record<string, string> = {
|
|
@@ -141,7 +152,7 @@ export class InternalHttpTransport implements HttpTransport {
|
|
|
141
152
|
const res = await this.routeRequest(
|
|
142
153
|
method as 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT',
|
|
143
154
|
namespace,
|
|
144
|
-
|
|
155
|
+
normalizedInstanceId,
|
|
145
156
|
tableName,
|
|
146
157
|
directPath,
|
|
147
158
|
doName,
|
|
@@ -174,36 +185,41 @@ export class InternalHttpTransport implements HttpTransport {
|
|
|
174
185
|
query: URLSearchParams,
|
|
175
186
|
body?: Record<string, unknown>,
|
|
176
187
|
): Promise<Response> {
|
|
188
|
+
const target = resolveDbTarget(this.config, namespace, instanceId);
|
|
189
|
+
if (!target.ok) {
|
|
190
|
+
throw new Error(formatDbTargetValidationIssue(target.issue, namespace));
|
|
191
|
+
}
|
|
192
|
+
const { dbBlock, dynamic, instanceId: normalizedInstanceId } = target.value;
|
|
177
193
|
const queryString = Array.from(query.keys()).length > 0 ? `?${query.toString()}` : '';
|
|
178
194
|
const directPathWithQuery = `${directPath}${queryString}`;
|
|
179
|
-
const provider =
|
|
195
|
+
const provider = dbBlock.provider;
|
|
180
196
|
const httpMethod = method === 'PUT' ? 'PATCH' : method; // normalize PUT → PATCH
|
|
181
197
|
|
|
182
198
|
// 1. D1 route
|
|
183
|
-
if (!this.preferDirectDo && shouldRouteToD1(namespace, this.config) && this.env) {
|
|
184
|
-
return this.requestViaD1Handler(httpMethod, namespace,
|
|
199
|
+
if (!this.preferDirectDo && !dynamic && shouldRouteToD1(namespace, this.config) && this.env) {
|
|
200
|
+
return this.requestViaD1Handler(httpMethod, namespace, normalizedInstanceId, tableName, directPath, headers, query, body);
|
|
185
201
|
}
|
|
186
202
|
|
|
187
203
|
// 2. PostgreSQL route
|
|
188
|
-
if ((provider === 'neon' || provider === 'postgres') && this.env) {
|
|
189
|
-
return this.requestViaPgHandler(httpMethod, namespace,
|
|
204
|
+
if (!dynamic && (provider === 'neon' || provider === 'postgres') && this.env) {
|
|
205
|
+
return this.requestViaPgHandler(httpMethod, namespace, normalizedInstanceId, tableName, directPath, headers, query, body);
|
|
190
206
|
}
|
|
191
207
|
|
|
192
208
|
// 3. Direct DO route
|
|
193
209
|
if (this.env) {
|
|
194
|
-
return this.requestViaDirectDo(httpMethod, doName, directPathWithQuery, headers, body,
|
|
210
|
+
return this.requestViaDirectDo(httpMethod, doName, directPathWithQuery, headers, body, dynamic);
|
|
195
211
|
}
|
|
196
212
|
|
|
197
213
|
// 4. Worker HTTP fallback
|
|
198
214
|
if (this.workerUrl) {
|
|
199
|
-
const apiPath =
|
|
200
|
-
? `/api/db/${namespace}/${
|
|
215
|
+
const apiPath = normalizedInstanceId
|
|
216
|
+
? `/api/db/${namespace}/${normalizedInstanceId}${directPathWithQuery}`
|
|
201
217
|
: `/api/db/${namespace}${directPathWithQuery}`;
|
|
202
218
|
return this.requestViaWorker(httpMethod, apiPath, headers, body);
|
|
203
219
|
}
|
|
204
220
|
|
|
205
221
|
// 5. Fallback: direct DO
|
|
206
|
-
return this.requestViaDirectDo(httpMethod, doName, directPathWithQuery, headers, body,
|
|
222
|
+
return this.requestViaDirectDo(httpMethod, doName, directPathWithQuery, headers, body, dynamic);
|
|
207
223
|
}
|
|
208
224
|
|
|
209
225
|
private async requestViaWorker(
|