@edge-base/server 0.2.2 → 0.2.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/admin-build/_app/immutable/chunks/{BWyDPAjM.js → A_3UuvCe.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BKXmgPq4.js → B-_-hJ9o.js} +1 -1
- package/admin-build/_app/immutable/chunks/{B8DT4fss.js → B5Nwfelm.js} +1 -1
- package/admin-build/_app/immutable/chunks/{C-DsDCNG.js → BxoNtYHK.js} +3 -3
- package/admin-build/_app/immutable/chunks/{CPdXvRUb.js → CZ0TVkCa.js} +1 -1
- package/admin-build/_app/immutable/chunks/{C85dMlzL.js → CzSAxmuj.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DzXaj-Ja.js → DCKcAiQH.js} +1 -1
- package/admin-build/_app/immutable/chunks/{kiJ6KthZ.js → DCvwWZrm.js} +1 -1
- package/admin-build/_app/immutable/chunks/{c5iKSdWY.js → DRqPU3wD.js} +1 -1
- package/admin-build/_app/immutable/chunks/{g3ZZdY-r.js → Dc1-6Po6.js} +1 -1
- package/admin-build/_app/immutable/chunks/{5PDcRlfX.js → DiyBpamp.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BaCHY17I.js → Dlty5069.js} +1 -1
- package/admin-build/_app/immutable/chunks/{4vlsb8ej.js → DpVAayDG.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CTngeX8H.js → Du5vWVa2.js} +1 -1
- package/admin-build/_app/immutable/chunks/{qiZXAKh-.js → byv2rTy8.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BEYYl662.js → nZvorU8i.js} +1 -1
- package/admin-build/_app/immutable/entry/{app.BZxfavhF.js → app.CfrmEXPD.js} +2 -2
- package/admin-build/_app/immutable/entry/start.l1WvHznQ.js +1 -0
- package/admin-build/_app/immutable/nodes/{0.DlsaydXO.js → 0.Cn2BZ4da.js} +1 -1
- package/admin-build/_app/immutable/nodes/{1.D2NWN5eG.js → 1.Dv4LX_Co.js} +1 -1
- package/admin-build/_app/immutable/nodes/{10.EMDaN3nw.js → 10.DPVv3kat.js} +1 -1
- package/admin-build/_app/immutable/nodes/{11.BasqQ_o9.js → 11.CiCb6Ayu.js} +1 -1
- package/admin-build/_app/immutable/nodes/{12.DO31Ljs7.js → 12.CIPyeekF.js} +1 -1
- package/admin-build/_app/immutable/nodes/{13.DhyAy-GZ.js → 13.Z15Lt36e.js} +1 -1
- package/admin-build/_app/immutable/nodes/{14.CLecGWc4.js → 14.s0l5bAq3.js} +1 -1
- package/admin-build/_app/immutable/nodes/{15.B9kp3W4e.js → 15.UwSSNO76.js} +1 -1
- package/admin-build/_app/immutable/nodes/{16.Pu_8T3RI.js → 16.qiD8i883.js} +1 -1
- package/admin-build/_app/immutable/nodes/{17.DX4z43t6.js → 17.Dy3dcSvu.js} +1 -1
- package/admin-build/_app/immutable/nodes/{18.BKsSaxrr.js → 18.DeXyPYsO.js} +1 -1
- package/admin-build/_app/immutable/nodes/{19.DXNF1htN.js → 19.CAbuyS6w.js} +1 -1
- package/admin-build/_app/immutable/nodes/{20.VRVb0wee.js → 20.Bec0T7un.js} +1 -1
- package/admin-build/_app/immutable/nodes/21.DuDYelMY.js +1 -0
- package/admin-build/_app/immutable/nodes/{22.DqZf4CtH.js → 22.CdVprrv2.js} +1 -1
- package/admin-build/_app/immutable/nodes/{23.DtyxMiQG.js → 23.Y8RzVLoF.js} +1 -1
- package/admin-build/_app/immutable/nodes/{24.CloWNmTd.js → 24.CWhHYFBx.js} +1 -1
- package/admin-build/_app/immutable/nodes/{25.CnZWMq7_.js → 25.wCBplOVt.js} +1 -1
- package/admin-build/_app/immutable/nodes/{26.DrV7XOmf.js → 26.Cod_JRFK.js} +1 -1
- package/admin-build/_app/immutable/nodes/{27.DV8L32OF.js → 27.BO2HVMu9.js} +1 -1
- package/admin-build/_app/immutable/nodes/{28.Stil2D4u.js → 28.DxG-FBVQ.js} +1 -1
- package/admin-build/_app/immutable/nodes/{29.Zsm1e5Dc.js → 29.CjGqWGvE.js} +1 -1
- package/admin-build/_app/immutable/nodes/{3.CKoj2vNz.js → 3.By3_OmdZ.js} +1 -1
- package/admin-build/_app/immutable/nodes/{30.Ni0k5bER.js → 30.M_H7Htpq.js} +1 -1
- package/admin-build/_app/immutable/nodes/{31.mnqj9EbV.js → 31.DEU18izM.js} +1 -1
- package/admin-build/_app/immutable/nodes/{4.B_-z9AzT.js → 4.DeYhKtzJ.js} +1 -1
- package/admin-build/_app/immutable/nodes/{5.yiZ72j4k.js → 5.9WLgxhrD.js} +1 -1
- package/admin-build/_app/immutable/nodes/{6.BqykybBG.js → 6.BdT2i_dd.js} +1 -1
- package/admin-build/_app/immutable/nodes/{7.BDAHlhsF.js → 7.CHq0s4K6.js} +1 -1
- package/admin-build/_app/immutable/nodes/{8.D8Xvy0lH.js → 8.DuvRw-XZ.js} +1 -1
- package/admin-build/_app/immutable/nodes/{9.Dddmd7_F.js → 9.C2Ub82wn.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__/d1-live-broadcast-verification.test.ts +271 -0
- package/src/__tests__/database-live-do.test.ts +50 -0
- package/src/__tests__/database-live-emitter.test.ts +116 -1
- package/src/__tests__/error-format.test.ts +63 -0
- package/src/__tests__/functions-context.test.ts +590 -33
- package/src/__tests__/postgres-field-ops-compat.test.ts +110 -0
- package/src/__tests__/provider-aware-sql.test.ts +157 -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__/sql-route.test.ts +187 -76
- 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/lib/admin-db-target.ts +30 -74
- package/src/lib/d1-handler.ts +45 -14
- package/src/lib/database-live-emitter.ts +57 -16
- package/src/lib/functions.ts +212 -141
- package/src/lib/plugin-migrations.ts +38 -38
- package/src/lib/postgres-handler.ts +39 -11
- package/src/lib/provider-aware-sql.ts +827 -0
- package/src/routes/auth.ts +7 -2
- package/src/routes/sql.ts +51 -76
- package/src/routes/storage.ts +7 -2
- 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 } 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,6 +801,25 @@ 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 => {
|
|
814
825
|
// Create a per-DbRef transport with explicit dbContext so that
|
|
@@ -828,9 +839,10 @@ export function buildAdminDbProxy(options: BuildAdminDbProxyOptions): FunctionAd
|
|
|
828
839
|
dbApi,
|
|
829
840
|
namespace,
|
|
830
841
|
id,
|
|
831
|
-
undefined,
|
|
832
|
-
undefined,
|
|
833
|
-
httpClient,
|
|
842
|
+
undefined, // databaseLiveClient — not available server-side
|
|
843
|
+
undefined, // filterMatchFn
|
|
844
|
+
httpClient, // enables table().sql`...` tagged template
|
|
845
|
+
sqlExecutor,
|
|
834
846
|
);
|
|
835
847
|
};
|
|
836
848
|
}
|
|
@@ -919,8 +931,10 @@ export function buildAdminAuthContext(options: AdminAuthOptions): AdminAuthConte
|
|
|
919
931
|
// Direct D1 path — works without service key (same as updateUser/deleteUser)
|
|
920
932
|
if (d1Database) {
|
|
921
933
|
// Input validation (mirrors routes/admin-auth.ts guards)
|
|
922
|
-
if (!data.email || typeof data.email !== 'string')
|
|
923
|
-
|
|
934
|
+
if (!data.email || typeof data.email !== 'string')
|
|
935
|
+
throw new Error('Email and password are required.');
|
|
936
|
+
if (!data.password || typeof data.password !== 'string')
|
|
937
|
+
throw new Error('Email and password are required.');
|
|
924
938
|
const email = data.email.trim().toLowerCase();
|
|
925
939
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
926
940
|
throw new Error('Invalid email format.');
|
|
@@ -1081,6 +1095,25 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
|
|
|
1081
1095
|
executionCtx: options.executionCtx,
|
|
1082
1096
|
preferDirectDo: options.preferDirectDoDb,
|
|
1083
1097
|
});
|
|
1098
|
+
const sqlProviderAware = (
|
|
1099
|
+
namespace: string,
|
|
1100
|
+
id: string | undefined,
|
|
1101
|
+
query: string,
|
|
1102
|
+
params?: unknown[],
|
|
1103
|
+
) =>
|
|
1104
|
+
executeSqlProviderAware(
|
|
1105
|
+
{
|
|
1106
|
+
env: options.env,
|
|
1107
|
+
config: options.config,
|
|
1108
|
+
databaseNamespace: options.databaseNamespace,
|
|
1109
|
+
workerUrl: options.workerUrl,
|
|
1110
|
+
serviceKey: options.serviceKey,
|
|
1111
|
+
},
|
|
1112
|
+
namespace,
|
|
1113
|
+
id,
|
|
1114
|
+
query,
|
|
1115
|
+
params,
|
|
1116
|
+
);
|
|
1084
1117
|
|
|
1085
1118
|
// ─── context.admin — AdminEdgeBase-shaped internal proxy ───
|
|
1086
1119
|
const admin: FunctionAdminContext = {
|
|
@@ -1089,18 +1122,9 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
|
|
|
1089
1122
|
// ─── context.admin.db(namespace, id) — DB-first tenant access (§5) ───
|
|
1090
1123
|
db: adminDb,
|
|
1091
1124
|
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
|
-
),
|
|
1125
|
+
// ─── Direct provider-aware SQL — delegates to shared executor ───
|
|
1126
|
+
sqlProviderAware,
|
|
1127
|
+
sqlWithDirectD1Access: sqlProviderAware,
|
|
1104
1128
|
async broadcast(
|
|
1105
1129
|
channel: string,
|
|
1106
1130
|
event: string,
|
|
@@ -1109,11 +1133,13 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
|
|
|
1109
1133
|
if (options.env?.DATABASE_LIVE) {
|
|
1110
1134
|
const hubId = options.env.DATABASE_LIVE.idFromName('database-live:hub');
|
|
1111
1135
|
const stub = options.env.DATABASE_LIVE.get(hubId);
|
|
1112
|
-
const response = await stub.fetch(
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1136
|
+
const response = await stub.fetch(
|
|
1137
|
+
new Request('http://do/internal/broadcast', {
|
|
1138
|
+
method: 'POST',
|
|
1139
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1140
|
+
body: JSON.stringify({ channel, event, payload: payload ?? {} }),
|
|
1141
|
+
}),
|
|
1142
|
+
);
|
|
1117
1143
|
if (!response.ok) {
|
|
1118
1144
|
throw new Error(`client.broadcast() failed: ${response.status}`);
|
|
1119
1145
|
}
|
|
@@ -1161,9 +1187,7 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
|
|
|
1161
1187
|
method: 'POST',
|
|
1162
1188
|
headers: {
|
|
1163
1189
|
'Content-Type': 'application/json',
|
|
1164
|
-
...(options.serviceKey
|
|
1165
|
-
? { 'X-EdgeBase-Service-Key': options.serviceKey }
|
|
1166
|
-
: {}),
|
|
1190
|
+
...(options.serviceKey ? { 'X-EdgeBase-Service-Key': options.serviceKey } : {}),
|
|
1167
1191
|
'X-EdgeBase-Call-Depth': String(currentDepth + 1),
|
|
1168
1192
|
},
|
|
1169
1193
|
body: JSON.stringify(data ?? {}),
|
|
@@ -1221,13 +1245,31 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
|
|
|
1221
1245
|
|
|
1222
1246
|
// KV / D1 / Vectorize proxies
|
|
1223
1247
|
kv(namespace: string): FunctionKvProxy {
|
|
1224
|
-
return buildFunctionKvProxy(
|
|
1248
|
+
return buildFunctionKvProxy(
|
|
1249
|
+
namespace,
|
|
1250
|
+
options.config,
|
|
1251
|
+
options.env,
|
|
1252
|
+
options.workerUrl,
|
|
1253
|
+
options.serviceKey,
|
|
1254
|
+
);
|
|
1225
1255
|
},
|
|
1226
1256
|
d1(database: string): FunctionD1Proxy {
|
|
1227
|
-
return buildFunctionD1Proxy(
|
|
1257
|
+
return buildFunctionD1Proxy(
|
|
1258
|
+
database,
|
|
1259
|
+
options.config,
|
|
1260
|
+
options.env,
|
|
1261
|
+
options.workerUrl,
|
|
1262
|
+
options.serviceKey,
|
|
1263
|
+
);
|
|
1228
1264
|
},
|
|
1229
1265
|
vector(index: string): FunctionVectorizeProxy {
|
|
1230
|
-
return buildFunctionVectorizeProxy(
|
|
1266
|
+
return buildFunctionVectorizeProxy(
|
|
1267
|
+
index,
|
|
1268
|
+
options.config,
|
|
1269
|
+
options.env,
|
|
1270
|
+
options.workerUrl,
|
|
1271
|
+
options.serviceKey,
|
|
1272
|
+
);
|
|
1231
1273
|
},
|
|
1232
1274
|
|
|
1233
1275
|
// Push notification management
|
|
@@ -1466,11 +1508,14 @@ export function buildFunctionD1Proxy(
|
|
|
1466
1508
|
return {
|
|
1467
1509
|
async exec<T = Record<string, unknown>>(query: string, params?: unknown[]): Promise<T[]> {
|
|
1468
1510
|
if (config && env) {
|
|
1469
|
-
const bindingName =
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1511
|
+
const bindingName =
|
|
1512
|
+
config.d1?.[database]?.binding ??
|
|
1513
|
+
(database === 'auth' ? 'AUTH_DB' : undefined) ??
|
|
1514
|
+
(database === 'control' ? 'CONTROL_DB' : undefined) ??
|
|
1515
|
+
getD1BindingName(database);
|
|
1516
|
+
const binding = (env as unknown as Record<string, unknown>)[bindingName] as
|
|
1517
|
+
| D1Database
|
|
1518
|
+
| undefined;
|
|
1474
1519
|
if (!binding) {
|
|
1475
1520
|
throw new Error(`D1 binding '${bindingName}' not found.`);
|
|
1476
1521
|
}
|
|
@@ -1522,7 +1567,7 @@ export function buildFunctionVectorizeProxy(
|
|
|
1522
1567
|
if (values instanceof Float32Array || values instanceof Float64Array) {
|
|
1523
1568
|
return Array.from(values);
|
|
1524
1569
|
}
|
|
1525
|
-
return Array.isArray(values) ? values as number[] : undefined;
|
|
1570
|
+
return Array.isArray(values) ? (values as number[]) : undefined;
|
|
1526
1571
|
};
|
|
1527
1572
|
|
|
1528
1573
|
const mapMatches = (
|
|
@@ -1533,15 +1578,19 @@ export function buildFunctionVectorizeProxy(
|
|
|
1533
1578
|
metadata?: Record<string, unknown>;
|
|
1534
1579
|
namespace?: string;
|
|
1535
1580
|
}>,
|
|
1536
|
-
) =>
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1581
|
+
) =>
|
|
1582
|
+
matches.map((match) => ({
|
|
1583
|
+
id: match.id,
|
|
1584
|
+
score: match.score,
|
|
1585
|
+
...(match.values !== undefined ? { values: normalizeValues(match.values) } : {}),
|
|
1586
|
+
...(match.metadata !== undefined ? { metadata: match.metadata } : {}),
|
|
1587
|
+
...(match.namespace ? { namespace: match.namespace } : {}),
|
|
1588
|
+
}));
|
|
1589
|
+
|
|
1590
|
+
const withNamespace = <T extends { namespace?: string }>(
|
|
1591
|
+
vectors: T[],
|
|
1592
|
+
namespace?: string,
|
|
1593
|
+
): T[] => {
|
|
1545
1594
|
if (!namespace) return vectors;
|
|
1546
1595
|
return vectors.map((vector) => (vector.namespace ? vector : { ...vector, namespace }));
|
|
1547
1596
|
};
|
|
@@ -1559,7 +1608,10 @@ export function buildFunctionVectorizeProxy(
|
|
|
1559
1608
|
if (directBinding) {
|
|
1560
1609
|
switch (body.action) {
|
|
1561
1610
|
case 'upsert': {
|
|
1562
|
-
const vectors = withNamespace(
|
|
1611
|
+
const vectors = withNamespace(
|
|
1612
|
+
body.vectors as VectorizeVector[],
|
|
1613
|
+
body.namespace as string | undefined,
|
|
1614
|
+
);
|
|
1563
1615
|
let count = 0;
|
|
1564
1616
|
let mutationId: string | undefined;
|
|
1565
1617
|
for (const chunk of chunkArray(vectors, VECTOR_BATCH_LIMIT)) {
|
|
@@ -1572,7 +1624,10 @@ export function buildFunctionVectorizeProxy(
|
|
|
1572
1624
|
return { count, ...(mutationId ? { mutationId } : {}) };
|
|
1573
1625
|
}
|
|
1574
1626
|
case 'insert': {
|
|
1575
|
-
const vectors = withNamespace(
|
|
1627
|
+
const vectors = withNamespace(
|
|
1628
|
+
body.vectors as VectorizeVector[],
|
|
1629
|
+
body.namespace as string | undefined,
|
|
1630
|
+
);
|
|
1576
1631
|
let count = 0;
|
|
1577
1632
|
let mutationId: string | undefined;
|
|
1578
1633
|
for (const chunk of chunkArray(vectors, VECTOR_BATCH_LIMIT)) {
|
|
@@ -1595,9 +1650,11 @@ export function buildFunctionVectorizeProxy(
|
|
|
1595
1650
|
return { matches: mapMatches(result.matches), count: result.count };
|
|
1596
1651
|
}
|
|
1597
1652
|
case 'queryById': {
|
|
1598
|
-
const queryById = (
|
|
1599
|
-
|
|
1600
|
-
|
|
1653
|
+
const queryById = (
|
|
1654
|
+
directBinding as unknown as {
|
|
1655
|
+
queryById?: (id: string, opts?: VectorizeQueryOptions) => Promise<VectorizeMatches>;
|
|
1656
|
+
}
|
|
1657
|
+
).queryById;
|
|
1601
1658
|
if (typeof queryById !== 'function') {
|
|
1602
1659
|
throw new Error('queryById is not available on this Vectorize binding');
|
|
1603
1660
|
}
|
|
@@ -1612,7 +1669,11 @@ export function buildFunctionVectorizeProxy(
|
|
|
1612
1669
|
}
|
|
1613
1670
|
case 'getByIds': {
|
|
1614
1671
|
const vectors = (
|
|
1615
|
-
await Promise.all(
|
|
1672
|
+
await Promise.all(
|
|
1673
|
+
chunkArray(body.ids as string[], VECTOR_BATCH_LIMIT).map((chunk) =>
|
|
1674
|
+
directBinding.getByIds(chunk),
|
|
1675
|
+
),
|
|
1676
|
+
)
|
|
1616
1677
|
).flat();
|
|
1617
1678
|
return {
|
|
1618
1679
|
vectors: vectors.map((vector) => ({
|
|
@@ -1640,12 +1701,22 @@ export function buildFunctionVectorizeProxy(
|
|
|
1640
1701
|
const details = info as unknown as Record<string, unknown>;
|
|
1641
1702
|
return {
|
|
1642
1703
|
vectorCount: details.vectorCount ?? details.vectorsCount ?? 0,
|
|
1643
|
-
dimensions:
|
|
1644
|
-
|
|
1704
|
+
dimensions:
|
|
1705
|
+
details.dimensions ??
|
|
1706
|
+
(details.config as Record<string, unknown> | undefined)?.dimensions ??
|
|
1707
|
+
0,
|
|
1708
|
+
metric:
|
|
1709
|
+
details.metric ??
|
|
1710
|
+
(details.config as Record<string, unknown> | undefined)?.metric ??
|
|
1711
|
+
'cosine',
|
|
1645
1712
|
...('id' in details ? { id: details.id } : {}),
|
|
1646
1713
|
...('name' in details ? { name: details.name } : {}),
|
|
1647
|
-
...('processedUpToDatetime' in details
|
|
1648
|
-
|
|
1714
|
+
...('processedUpToDatetime' in details
|
|
1715
|
+
? { processedUpToDatetime: details.processedUpToDatetime }
|
|
1716
|
+
: {}),
|
|
1717
|
+
...('processedUpToMutation' in details
|
|
1718
|
+
? { processedUpToMutation: details.processedUpToMutation }
|
|
1719
|
+
: {}),
|
|
1649
1720
|
};
|
|
1650
1721
|
}
|
|
1651
1722
|
}
|