@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.
Files changed (77) hide show
  1. package/admin-build/_app/immutable/chunks/{BWyDPAjM.js → A_3UuvCe.js} +1 -1
  2. package/admin-build/_app/immutable/chunks/{BKXmgPq4.js → B-_-hJ9o.js} +1 -1
  3. package/admin-build/_app/immutable/chunks/{B8DT4fss.js → B5Nwfelm.js} +1 -1
  4. package/admin-build/_app/immutable/chunks/{C-DsDCNG.js → BxoNtYHK.js} +3 -3
  5. package/admin-build/_app/immutable/chunks/{CPdXvRUb.js → CZ0TVkCa.js} +1 -1
  6. package/admin-build/_app/immutable/chunks/{C85dMlzL.js → CzSAxmuj.js} +1 -1
  7. package/admin-build/_app/immutable/chunks/{DzXaj-Ja.js → DCKcAiQH.js} +1 -1
  8. package/admin-build/_app/immutable/chunks/{kiJ6KthZ.js → DCvwWZrm.js} +1 -1
  9. package/admin-build/_app/immutable/chunks/{c5iKSdWY.js → DRqPU3wD.js} +1 -1
  10. package/admin-build/_app/immutable/chunks/{g3ZZdY-r.js → Dc1-6Po6.js} +1 -1
  11. package/admin-build/_app/immutable/chunks/{5PDcRlfX.js → DiyBpamp.js} +1 -1
  12. package/admin-build/_app/immutable/chunks/{BaCHY17I.js → Dlty5069.js} +1 -1
  13. package/admin-build/_app/immutable/chunks/{4vlsb8ej.js → DpVAayDG.js} +1 -1
  14. package/admin-build/_app/immutable/chunks/{CTngeX8H.js → Du5vWVa2.js} +1 -1
  15. package/admin-build/_app/immutable/chunks/{qiZXAKh-.js → byv2rTy8.js} +1 -1
  16. package/admin-build/_app/immutable/chunks/{BEYYl662.js → nZvorU8i.js} +1 -1
  17. package/admin-build/_app/immutable/entry/{app.BZxfavhF.js → app.CfrmEXPD.js} +2 -2
  18. package/admin-build/_app/immutable/entry/start.l1WvHznQ.js +1 -0
  19. package/admin-build/_app/immutable/nodes/{0.DlsaydXO.js → 0.Cn2BZ4da.js} +1 -1
  20. package/admin-build/_app/immutable/nodes/{1.D2NWN5eG.js → 1.Dv4LX_Co.js} +1 -1
  21. package/admin-build/_app/immutable/nodes/{10.EMDaN3nw.js → 10.DPVv3kat.js} +1 -1
  22. package/admin-build/_app/immutable/nodes/{11.BasqQ_o9.js → 11.CiCb6Ayu.js} +1 -1
  23. package/admin-build/_app/immutable/nodes/{12.DO31Ljs7.js → 12.CIPyeekF.js} +1 -1
  24. package/admin-build/_app/immutable/nodes/{13.DhyAy-GZ.js → 13.Z15Lt36e.js} +1 -1
  25. package/admin-build/_app/immutable/nodes/{14.CLecGWc4.js → 14.s0l5bAq3.js} +1 -1
  26. package/admin-build/_app/immutable/nodes/{15.B9kp3W4e.js → 15.UwSSNO76.js} +1 -1
  27. package/admin-build/_app/immutable/nodes/{16.Pu_8T3RI.js → 16.qiD8i883.js} +1 -1
  28. package/admin-build/_app/immutable/nodes/{17.DX4z43t6.js → 17.Dy3dcSvu.js} +1 -1
  29. package/admin-build/_app/immutable/nodes/{18.BKsSaxrr.js → 18.DeXyPYsO.js} +1 -1
  30. package/admin-build/_app/immutable/nodes/{19.DXNF1htN.js → 19.CAbuyS6w.js} +1 -1
  31. package/admin-build/_app/immutable/nodes/{20.VRVb0wee.js → 20.Bec0T7un.js} +1 -1
  32. package/admin-build/_app/immutable/nodes/21.DuDYelMY.js +1 -0
  33. package/admin-build/_app/immutable/nodes/{22.DqZf4CtH.js → 22.CdVprrv2.js} +1 -1
  34. package/admin-build/_app/immutable/nodes/{23.DtyxMiQG.js → 23.Y8RzVLoF.js} +1 -1
  35. package/admin-build/_app/immutable/nodes/{24.CloWNmTd.js → 24.CWhHYFBx.js} +1 -1
  36. package/admin-build/_app/immutable/nodes/{25.CnZWMq7_.js → 25.wCBplOVt.js} +1 -1
  37. package/admin-build/_app/immutable/nodes/{26.DrV7XOmf.js → 26.Cod_JRFK.js} +1 -1
  38. package/admin-build/_app/immutable/nodes/{27.DV8L32OF.js → 27.BO2HVMu9.js} +1 -1
  39. package/admin-build/_app/immutable/nodes/{28.Stil2D4u.js → 28.DxG-FBVQ.js} +1 -1
  40. package/admin-build/_app/immutable/nodes/{29.Zsm1e5Dc.js → 29.CjGqWGvE.js} +1 -1
  41. package/admin-build/_app/immutable/nodes/{3.CKoj2vNz.js → 3.By3_OmdZ.js} +1 -1
  42. package/admin-build/_app/immutable/nodes/{30.Ni0k5bER.js → 30.M_H7Htpq.js} +1 -1
  43. package/admin-build/_app/immutable/nodes/{31.mnqj9EbV.js → 31.DEU18izM.js} +1 -1
  44. package/admin-build/_app/immutable/nodes/{4.B_-z9AzT.js → 4.DeYhKtzJ.js} +1 -1
  45. package/admin-build/_app/immutable/nodes/{5.yiZ72j4k.js → 5.9WLgxhrD.js} +1 -1
  46. package/admin-build/_app/immutable/nodes/{6.BqykybBG.js → 6.BdT2i_dd.js} +1 -1
  47. package/admin-build/_app/immutable/nodes/{7.BDAHlhsF.js → 7.CHq0s4K6.js} +1 -1
  48. package/admin-build/_app/immutable/nodes/{8.D8Xvy0lH.js → 8.DuvRw-XZ.js} +1 -1
  49. package/admin-build/_app/immutable/nodes/{9.Dddmd7_F.js → 9.C2Ub82wn.js} +1 -1
  50. package/admin-build/_app/version.json +1 -1
  51. package/admin-build/index.html +7 -7
  52. package/package.json +3 -3
  53. package/src/__tests__/d1-live-broadcast-verification.test.ts +271 -0
  54. package/src/__tests__/database-live-do.test.ts +50 -0
  55. package/src/__tests__/database-live-emitter.test.ts +116 -1
  56. package/src/__tests__/error-format.test.ts +63 -0
  57. package/src/__tests__/functions-context.test.ts +590 -33
  58. package/src/__tests__/postgres-field-ops-compat.test.ts +110 -0
  59. package/src/__tests__/provider-aware-sql.test.ts +157 -0
  60. package/src/__tests__/room-auth-state-loss.test.ts +124 -0
  61. package/src/__tests__/runtime-surface-accounting.test.ts +0 -4
  62. package/src/__tests__/sql-route.test.ts +187 -76
  63. package/src/durable-objects/database-live-do.ts +46 -1
  64. package/src/durable-objects/room-runtime-base.ts +26 -2
  65. package/src/durable-objects/rooms-do.ts +1 -1
  66. package/src/lib/admin-db-target.ts +30 -74
  67. package/src/lib/d1-handler.ts +45 -14
  68. package/src/lib/database-live-emitter.ts +57 -16
  69. package/src/lib/functions.ts +212 -141
  70. package/src/lib/plugin-migrations.ts +38 -38
  71. package/src/lib/postgres-handler.ts +39 -11
  72. package/src/lib/provider-aware-sql.ts +827 -0
  73. package/src/routes/auth.ts +7 -2
  74. package/src/routes/sql.ts +51 -76
  75. package/src/routes/storage.ts +7 -2
  76. package/admin-build/_app/immutable/entry/start.Mr9mmopc.js +0 -1
  77. package/admin-build/_app/immutable/nodes/21.Ck3_0D2f.js +0 -1
@@ -25,8 +25,7 @@ import type {
25
25
  ScheduleTrigger,
26
26
  HttpTrigger,
27
27
  } from '@edge-base/shared';
28
- import { getD1BindingName, shouldRouteToD1 } from './do-router.js';
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 with direct D1/DO binding access — no HTTP round-trip.
99
- * Routes directly to D1 binding or Durable Object without network overhead.
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.sqlWithDirectD1Access('shared', undefined, 'SELECT * FROM posts WHERE status = ?', ['published']);
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(key: string, value: ReadableStream | ArrayBuffer | string, options?: { contentType?: string; customMetadata?: Record<string, string> }): Promise<void>;
182
- get(key: string): Promise<{ body: ReadableStream; contentType: string; size: number; customMetadata: Record<string, string> } | null>;
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<{ keys: Array<{ key: string; size: number; contentType: string }>; cursor?: string; truncated: boolean }>;
186
- head(key: string): Promise<{ key: string; size: number; contentType: string; customMetadata: Record<string, string> } | null>;
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 = typeof def === 'function'
367
- ? 'a plain function'
368
- : `${typeof def} (${JSON.stringify(def)?.slice(0, 100)})`;
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
- `Functions must use defineFunction() from '@edge-base/shared' and be exported as named HTTP method exports ` +
372
- `(e.g. export const GET = defineFunction(...)). See https://docs.edgebase.dev/functions for details.`,
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 ('trigger' in handler && handler.trigger && typeof handler.trigger === 'object' && 'path' in handler.trigger) {
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 — D1 direct DO direct → HTTP fallback ───
742
+ // ─── Shared SQL executor — provider-aware direct paths → HTTP fallback ───
711
743
 
712
- export interface SqlWithDirectD1AccessOptions {
713
- env?: unknown;
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 available path:
722
- * 1. D1 direct binding (no network hop)
723
- * 2. Durable Object direct call
724
- * 3. HTTP fallback via workerUrl
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 storage hooks.
759
+ * Shared by buildFunctionContext, auth hooks, storage hooks, and plugin migrations.
727
760
  */
728
- export async function executeSqlWithDirectD1Access(
729
- opts: SqlWithDirectD1AccessOptions,
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
- if (opts.env) {
736
- const dbBlock = opts.config.databases?.[namespace];
737
- const isDynamicNamespace = !!(dbBlock?.instance || dbBlock?.access?.canCreate || dbBlock?.access?.access);
738
- if (isDynamicNamespace && !id) {
739
- throw new Error(`admin.sqlWithDirectD1Access() requires an id for dynamic namespace '${namespace}'.`);
740
- }
741
-
742
- if (!id && shouldRouteToD1(namespace, opts.config)) {
743
- const bindingName = getD1BindingName(namespace);
744
- const d1 = (opts.env as Record<string, unknown>)[bindingName] as D1Database | undefined;
745
- if (!d1) {
746
- throw new Error(`D1 binding '${bindingName}' not found.`);
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
- // Only available when workerUrl is set (routes through /api/sql endpoint).
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, // databaseLiveClient — not available server-side
832
- undefined, // filterMatchFn
833
- httpClient, // enables table().sql`...` tagged template
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') throw new Error('Email and password are required.');
923
- if (!data.password || typeof data.password !== 'string') throw new Error('Email and password are required.');
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 D1/DO SQL — delegates to shared executor ───
1093
- sqlWithDirectD1Access: (namespace: string, id: string | undefined, query: string, params?: unknown[]) =>
1094
- executeSqlWithDirectD1Access(
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(new Request('http://do/internal/broadcast', {
1113
- method: 'POST',
1114
- headers: { 'Content-Type': 'application/json' },
1115
- body: JSON.stringify({ channel, event, payload: payload ?? {} }),
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(namespace, options.config, options.env, options.workerUrl, options.serviceKey);
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(database, options.config, options.env, options.workerUrl, options.serviceKey);
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(index, options.config, options.env, options.workerUrl, options.serviceKey);
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 = config.d1?.[database]?.binding
1470
- ?? (database === 'auth' ? 'AUTH_DB' : undefined)
1471
- ?? (database === 'control' ? 'CONTROL_DB' : undefined)
1472
- ?? getD1BindingName(database);
1473
- const binding = (env as unknown as Record<string, unknown>)[bindingName] as D1Database | undefined;
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
- ) => matches.map((match) => ({
1537
- id: match.id,
1538
- score: match.score,
1539
- ...(match.values !== undefined ? { values: normalizeValues(match.values) } : {}),
1540
- ...(match.metadata !== undefined ? { metadata: match.metadata } : {}),
1541
- ...(match.namespace ? { namespace: match.namespace } : {}),
1542
- }));
1543
-
1544
- const withNamespace = <T extends { namespace?: string }>(vectors: T[], namespace?: string): T[] => {
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(body.vectors as VectorizeVector[], body.namespace as string | undefined);
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(body.vectors as VectorizeVector[], body.namespace as string | undefined);
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 = (directBinding as unknown as {
1599
- queryById?: (id: string, opts?: VectorizeQueryOptions) => Promise<VectorizeMatches>;
1600
- }).queryById;
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(chunkArray(body.ids as string[], VECTOR_BATCH_LIMIT).map((chunk) => directBinding.getByIds(chunk)))
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: details.dimensions ?? (details.config as Record<string, unknown> | undefined)?.dimensions ?? 0,
1644
- metric: details.metric ?? (details.config as Record<string, unknown> | undefined)?.metric ?? 'cosine',
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 ? { processedUpToDatetime: details.processedUpToDatetime } : {}),
1648
- ...('processedUpToMutation' in details ? { processedUpToMutation: details.processedUpToMutation } : {}),
1714
+ ...('processedUpToDatetime' in details
1715
+ ? { processedUpToDatetime: details.processedUpToDatetime }
1716
+ : {}),
1717
+ ...('processedUpToMutation' in details
1718
+ ? { processedUpToMutation: details.processedUpToMutation }
1719
+ : {}),
1649
1720
  };
1650
1721
  }
1651
1722
  }