@edge-base/server 0.2.1 → 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 (81) hide show
  1. package/admin-build/_app/immutable/chunks/{DjOEv9M9.js → A_3UuvCe.js} +1 -1
  2. package/admin-build/_app/immutable/chunks/{Dqk2TGNU.js → B-_-hJ9o.js} +1 -1
  3. package/admin-build/_app/immutable/chunks/{BFs_qStz.js → B5Nwfelm.js} +1 -1
  4. package/admin-build/_app/immutable/chunks/{B0QyxC2M.js → BxoNtYHK.js} +3 -3
  5. package/admin-build/_app/immutable/chunks/{BsFiK_FJ.js → CZ0TVkCa.js} +1 -1
  6. package/admin-build/_app/immutable/chunks/{k0CIJkw4.js → CzSAxmuj.js} +1 -1
  7. package/admin-build/_app/immutable/chunks/{D-x55wdW.js → DCKcAiQH.js} +1 -1
  8. package/admin-build/_app/immutable/chunks/{CSGrwS7E.js → DCvwWZrm.js} +1 -1
  9. package/admin-build/_app/immutable/chunks/{BTJcQFEp.js → DRqPU3wD.js} +1 -1
  10. package/admin-build/_app/immutable/chunks/{CqUxCvs_.js → Dc1-6Po6.js} +1 -1
  11. package/admin-build/_app/immutable/chunks/{D755Tqat.js → DiyBpamp.js} +1 -1
  12. package/admin-build/_app/immutable/chunks/{BcIUK2sk.js → Dlty5069.js} +1 -1
  13. package/admin-build/_app/immutable/chunks/{BY07qVPA.js → DpVAayDG.js} +1 -1
  14. package/admin-build/_app/immutable/chunks/{BCKr7yKd.js → Du5vWVa2.js} +1 -1
  15. package/admin-build/_app/immutable/chunks/{m9QZTyVV.js → byv2rTy8.js} +1 -1
  16. package/admin-build/_app/immutable/chunks/{DnLqc9L1.js → nZvorU8i.js} +1 -1
  17. package/admin-build/_app/immutable/entry/{app.BTsq3_xq.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.BZ00WDYH.js → 0.Cn2BZ4da.js} +1 -1
  20. package/admin-build/_app/immutable/nodes/{1.RzSJ3yyr.js → 1.Dv4LX_Co.js} +1 -1
  21. package/admin-build/_app/immutable/nodes/{10.D-rsiquF.js → 10.DPVv3kat.js} +1 -1
  22. package/admin-build/_app/immutable/nodes/{11.l7-bgtFD.js → 11.CiCb6Ayu.js} +1 -1
  23. package/admin-build/_app/immutable/nodes/{12.Dkq0H7B5.js → 12.CIPyeekF.js} +1 -1
  24. package/admin-build/_app/immutable/nodes/{13.DtK_4oRz.js → 13.Z15Lt36e.js} +1 -1
  25. package/admin-build/_app/immutable/nodes/{14.BKo7-AMx.js → 14.s0l5bAq3.js} +1 -1
  26. package/admin-build/_app/immutable/nodes/{15.CQAj_6lq.js → 15.UwSSNO76.js} +1 -1
  27. package/admin-build/_app/immutable/nodes/{16.XVIG-Ffr.js → 16.qiD8i883.js} +1 -1
  28. package/admin-build/_app/immutable/nodes/{17.g6raZLCM.js → 17.Dy3dcSvu.js} +1 -1
  29. package/admin-build/_app/immutable/nodes/{18.IQz6a3T6.js → 18.DeXyPYsO.js} +1 -1
  30. package/admin-build/_app/immutable/nodes/{19.CAAZ8i8h.js → 19.CAbuyS6w.js} +1 -1
  31. package/admin-build/_app/immutable/nodes/{20.BPcX3KPj.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.Br5AG_5Z.js → 22.CdVprrv2.js} +1 -1
  34. package/admin-build/_app/immutable/nodes/{23.KjbrdXoE.js → 23.Y8RzVLoF.js} +1 -1
  35. package/admin-build/_app/immutable/nodes/{24.C3n2-hgw.js → 24.CWhHYFBx.js} +1 -1
  36. package/admin-build/_app/immutable/nodes/{25.SFDSBzHd.js → 25.wCBplOVt.js} +1 -1
  37. package/admin-build/_app/immutable/nodes/{26.D95vui6E.js → 26.Cod_JRFK.js} +1 -1
  38. package/admin-build/_app/immutable/nodes/{27.FgLgdjwB.js → 27.BO2HVMu9.js} +1 -1
  39. package/admin-build/_app/immutable/nodes/{28.B9sYYm1F.js → 28.DxG-FBVQ.js} +1 -1
  40. package/admin-build/_app/immutable/nodes/{29.DyqZ_wbN.js → 29.CjGqWGvE.js} +1 -1
  41. package/admin-build/_app/immutable/nodes/{3.Bzo2yVIO.js → 3.By3_OmdZ.js} +1 -1
  42. package/admin-build/_app/immutable/nodes/{30.c1CiNwiS.js → 30.M_H7Htpq.js} +1 -1
  43. package/admin-build/_app/immutable/nodes/{31.CXty66Vh.js → 31.DEU18izM.js} +1 -1
  44. package/admin-build/_app/immutable/nodes/{4.BgQaXZ27.js → 4.DeYhKtzJ.js} +1 -1
  45. package/admin-build/_app/immutable/nodes/{5.BuJrHvxH.js → 5.9WLgxhrD.js} +1 -1
  46. package/admin-build/_app/immutable/nodes/{6.CkBBC94k.js → 6.BdT2i_dd.js} +1 -1
  47. package/admin-build/_app/immutable/nodes/{7.D2YBvNFM.js → 7.CHq0s4K6.js} +1 -1
  48. package/admin-build/_app/immutable/nodes/{8.D8qQWo_z.js → 8.DuvRw-XZ.js} +1 -1
  49. package/admin-build/_app/immutable/nodes/{9.BLDLX5hV.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 -2
  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 +592 -35
  58. package/src/__tests__/meta-export-coverage.test.ts +1 -0
  59. package/src/__tests__/postgres-field-ops-compat.test.ts +110 -0
  60. package/src/__tests__/provider-aware-sql.test.ts +157 -0
  61. package/src/__tests__/room-auth-state-loss.test.ts +124 -0
  62. package/src/__tests__/runtime-surface-accounting.test.ts +0 -4
  63. package/src/__tests__/sql-route.test.ts +187 -76
  64. package/src/durable-objects/database-live-do.ts +46 -1
  65. package/src/durable-objects/room-runtime-base.ts +26 -2
  66. package/src/durable-objects/rooms-do.ts +1 -1
  67. package/src/lib/admin-db-target.ts +30 -74
  68. package/src/lib/d1-handler.ts +45 -14
  69. package/src/lib/database-live-emitter.ts +57 -16
  70. package/src/lib/functions.ts +332 -454
  71. package/src/lib/internal-transport.ts +316 -0
  72. package/src/lib/plugin-migrations.ts +39 -39
  73. package/src/lib/postgres-handler.ts +39 -11
  74. package/src/lib/provider-aware-sql.ts +827 -0
  75. package/src/routes/admin.ts +7 -1
  76. package/src/routes/auth.ts +11 -12
  77. package/src/routes/sql.ts +51 -76
  78. package/src/routes/storage.ts +11 -12
  79. package/src/types.ts +2 -0
  80. package/admin-build/_app/immutable/entry/start.zXCirpgY.js +0 -1
  81. package/admin-build/_app/immutable/nodes/21.DoPabrY_.js +0 -1
@@ -25,19 +25,22 @@ import type {
25
25
  ScheduleTrigger,
26
26
  HttpTrigger,
27
27
  } from '@edge-base/shared';
28
- import { getDbDoName, getD1BindingName, callDO, 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
- import { handleD1Request } from './d1-handler.js';
32
- import { handlePgRequest } from './postgres-handler.js';
33
- import { buildInternalHandlerContext } from './internal-request.js';
30
+ import { ensureAuthSchema } from './auth-d1.js';
34
31
  import type { Env } from '../types.js';
35
32
  import { createSignedToken } from '../routes/storage.js';
36
33
  import {
34
+ createManagedAdminUser,
37
35
  deleteManagedAdminUser,
38
36
  normalizeAdminUserUpdates,
39
37
  updateManagedAdminUser,
40
38
  } from './admin-user-management.js';
39
+ import { hashPassword } from './password.js';
40
+ import { generateId } from './uuid.js';
41
+ import { DbRef, TableRef, DefaultDbApi, HttpClient, ContextManager } from '@edge-base/core';
42
+ import { InternalHttpTransport } from './internal-transport.js';
43
+ import { executeProviderAwareSql } from './provider-aware-sql.js';
41
44
 
42
45
  // ─── Function Context Types ───
43
46
 
@@ -48,21 +51,6 @@ export interface AuthContext {
48
51
  custom?: Record<string, unknown>;
49
52
  }
50
53
 
51
- export interface TableProxy {
52
- insert(data: Record<string, unknown>): Promise<Record<string, unknown>>;
53
- upsert(
54
- data: Record<string, unknown>,
55
- options?: { conflictTarget?: string },
56
- ): Promise<Record<string, unknown>>;
57
- update(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
58
- delete(id: string): Promise<{ deleted: boolean }>;
59
- get(id: string): Promise<Record<string, unknown>>;
60
- list(options?: {
61
- limit?: number;
62
- filter?: unknown;
63
- }): Promise<{ items: Record<string, unknown>[] }>;
64
- }
65
-
66
54
  export interface AdminAuthContext {
67
55
  getUser(userId: string): Promise<Record<string, unknown>>;
68
56
  listUsers(options?: {
@@ -89,22 +77,41 @@ export interface AdminAuthContext {
89
77
  */
90
78
  export interface FunctionAdminContext {
91
79
  /** Cross-DO table access — rules bypassed, Service Key authenticated. */
92
- table(name: string): TableProxy;
80
+ table(name: string): TableRef;
93
81
  /**
94
82
  * Access a specific DB namespace instance (§5).
95
- * Replaces forTenant() aligned with Database-first architecture (#133).
83
+ * Uses the same TableRef from @edge-base/core as the client SDK,
84
+ * ensuring API parity (getList, getOne, where, orderBy, limit, etc.).
96
85
  *
97
86
  * @example
98
87
  * // Static DB
99
- * context.admin.db('shared').table('posts').list()
88
+ * context.admin.db('shared').table('posts').getList()
100
89
  * // Dynamic DB (tenant/user)
101
- * context.admin.db('workspace', 'ws-456').table('documents').list()
90
+ * context.admin.db('workspace', 'ws-456').table('documents').getList()
91
+ * // With query builder
92
+ * context.admin.db('shared').table('posts').where('status', '==', 'published').limit(10).getList()
102
93
  */
103
- db(namespace: string, id?: string): { table(name: string): TableProxy };
94
+ db(namespace: string, id?: string): DbRef;
104
95
  /** Admin user management. */
105
96
  auth: AdminAuthContext;
106
- /** Raw SQL on a DB namespace DO. */
107
- sql(
97
+ /**
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.
101
+ *
102
+ * @example
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.
113
+ */
114
+ sqlWithDirectD1Access(
108
115
  namespace: string,
109
116
  id: string | undefined,
110
117
  query: string,
@@ -139,9 +146,7 @@ export interface FunctionPushProxy {
139
146
  payload: Record<string, unknown>,
140
147
  ): Promise<{ sent: number; failed: number; removed: number }>;
141
148
  /** Get registered device tokens for a user — token values NOT exposed. */
142
- getTokens(
143
- userId: string,
144
- ): Promise<
149
+ getTokens(userId: string): Promise<
145
150
  Array<{
146
151
  deviceId: string;
147
152
  platform: string;
@@ -181,12 +186,30 @@ export interface FunctionPushProxy {
181
186
 
182
187
  /** Storage proxy for App Functions — wraps R2Bucket with convenience methods. */
183
188
  export interface FunctionStorageProxy {
184
- put(key: string, value: ReadableStream | ArrayBuffer | string, options?: { contentType?: string; customMetadata?: Record<string, string> }): Promise<void>;
185
- 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>;
186
200
  delete(key: string): Promise<void>;
187
201
  getSignedUrl(key: string, options?: { expiresIn?: number }): Promise<string>;
188
- list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<{ keys: Array<{ key: string; size: number; contentType: string }>; cursor?: string; truncated: boolean }>;
189
- 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>;
190
213
  }
191
214
 
192
215
  /** KV proxy for App Functions — routes through Worker HTTP. */
@@ -291,20 +314,21 @@ export interface FunctionContext {
291
314
  *
292
315
  * @example
293
316
  * // Static DB
294
- * await context.db('shared').table('posts').list()
317
+ * await context.db('shared').table('posts').getList()
295
318
  * // Dynamic DB
296
- * await context.db('workspace', 'ws-456').table('documents').list()
319
+ * await context.db('workspace', 'ws-456').table('documents').getList()
320
+ * // With query builder
321
+ * await context.db('shared').table('posts').where('status', '==', 'published').limit(10).getList()
297
322
  */
298
323
  db: FunctionAdminContext['db'];
299
324
  /**
300
- * Server-side EdgeBase admin client (§5,).
325
+ * Server-side EdgeBase admin client (§5).
301
326
  * Use context.admin.db(namespace, id?).table(name) for all DB access.
327
+ * Uses the same TableRef from @edge-base/core as the client SDK.
302
328
  *
303
329
  * @example
304
- * // Static DB
305
- * await context.admin.db('shared').table('posts').list()
306
- * // Dynamic DB
307
- * await context.admin.db('workspace', 'ws-456').table('documents').list()
330
+ * await context.admin.db('shared').table('posts').getList()
331
+ * await context.admin.db('shared').table('posts').where('userId', '==', uid).getList()
308
332
  */
309
333
  admin: FunctionAdminContext;
310
334
  /**
@@ -365,13 +389,14 @@ function getRegistryName(key: string, def: FunctionDefinition): string {
365
389
 
366
390
  export function registerFunction(name: string, def: FunctionDefinition): void {
367
391
  if (!def || typeof def !== 'object' || !def.trigger) {
368
- const received = typeof def === 'function'
369
- ? 'a plain function'
370
- : `${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)})`;
371
396
  throw new Error(
372
397
  `registerFunction('${name}'): expected a FunctionDefinition with a 'trigger' property, but received ${received}. ` +
373
- `Functions must use defineFunction() from '@edge-base/shared' and be exported as named HTTP method exports ` +
374
- `(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.`,
375
400
  );
376
401
  }
377
402
  functionRegistry.set(buildRegistryKey(name, def), def);
@@ -678,7 +703,12 @@ export function wrapMethodExport(
678
703
  } else if (handler && typeof handler === 'object') {
679
704
  fn = (handler.handler ?? handler) as unknown as (ctx: unknown) => Promise<unknown>;
680
705
  captcha = handler.captcha;
681
- 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
+ ) {
682
712
  const triggerPath = handler.trigger.path;
683
713
  path = typeof triggerPath === 'string' ? triggerPath : undefined;
684
714
  }
@@ -697,284 +727,7 @@ export function wrapMethodExport(
697
727
  };
698
728
  }
699
729
 
700
- // ─── Table Proxy (Cross-DO) ───
701
-
702
- /**
703
- * Build a cross-DO table proxy.
704
- * All calls bypass access rules by using Service Key.
705
- */
706
- function buildTableProxy(
707
- tableName: string,
708
- namespace: DurableObjectNamespace,
709
- config: EdgeBaseConfig,
710
- workerUrl?: string,
711
- serviceKey?: string,
712
- env?: Env,
713
- executionCtx?: ExecutionContext,
714
- /** DB routing info (§5). If provided, overrides auto-detect. */
715
- dbTarget?: { namespace: string; id?: string; doName: string },
716
- routingOptions?: { preferDirectDo?: boolean },
717
- ): TableProxy {
718
- // Determine DO name: explicit DB target (§5) or auto-detect from config
719
- let resolvedTarget: { namespace: string; id?: string; doName: string };
720
- if (dbTarget) {
721
- resolvedTarget = dbTarget;
722
- } else {
723
- // Auto-detect namespace for this table from config (§1)
724
- let tableNamespace = 'shared';
725
- for (const [ns, dbBlock] of Object.entries(config.databases ?? {})) {
726
- if (dbBlock.tables?.[tableName]) {
727
- tableNamespace = ns;
728
- break;
729
- }
730
- }
731
- resolvedTarget = {
732
- namespace: tableNamespace,
733
- doName: getDbDoName(tableNamespace),
734
- };
735
- }
736
- const doName = resolvedTarget.doName;
737
- const headers: Record<string, string> = { 'X-DO-Name': doName };
738
- if (serviceKey) {
739
- headers['X-EdgeBase-Service-Key'] = serviceKey;
740
- }
741
- // Add internal header to bypass rules
742
- headers['X-EdgeBase-Internal'] = 'true';
743
-
744
- const buildTablePath = (id?: string, query?: URLSearchParams): string => {
745
- const base = resolvedTarget.id !== undefined
746
- ? `/api/db/${resolvedTarget.namespace}/${resolvedTarget.id}/tables/${tableName}`
747
- : `/api/db/${resolvedTarget.namespace}/tables/${tableName}`;
748
- const withId = id ? `${base}/${id}` : base;
749
- const search = query && Array.from(query.keys()).length > 0 ? `?${query.toString()}` : '';
750
- return `${withId}${search}`;
751
- };
752
- const buildDirectTablePath = (id?: string, query?: URLSearchParams): string => {
753
- const base = `/tables/${tableName}`;
754
- const withId = id ? `${base}/${id}` : base;
755
- const search = query && Array.from(query.keys()).length > 0 ? `?${query.toString()}` : '';
756
- return `${withId}${search}`;
757
- };
758
-
759
- const requestViaWorker = async (
760
- method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
761
- path: string,
762
- body?: Record<string, unknown>,
763
- ): Promise<Response> => {
764
- return fetch(`${workerUrl}${path}`, {
765
- method,
766
- headers: {
767
- 'Content-Type': 'application/json',
768
- ...headers,
769
- },
770
- body: body === undefined || method === 'GET' || method === 'DELETE'
771
- ? undefined
772
- : JSON.stringify(body),
773
- });
774
- };
775
- const requestViaD1Handler = async (
776
- method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
777
- directPath: string,
778
- query?: URLSearchParams,
779
- body?: Record<string, unknown>,
780
- ): Promise<Response> => {
781
- if (!env) {
782
- throw new Error('D1 table proxy requires env.');
783
- }
784
-
785
- const request = new Request(
786
- `http://internal/api/db/${resolvedTarget.namespace}${resolvedTarget.id ? `/${resolvedTarget.id}` : ''}${buildDirectTablePath(undefined, query).replace('/tables/' + tableName, directPath)}`,
787
- {
788
- method,
789
- headers: {
790
- 'Content-Type': 'application/json',
791
- ...headers,
792
- },
793
- body: body === undefined || method === 'GET' || method === 'DELETE'
794
- ? undefined
795
- : JSON.stringify(body),
796
- },
797
- );
798
-
799
- return handleD1Request(
800
- buildInternalHandlerContext({
801
- env,
802
- request,
803
- body,
804
- executionCtx,
805
- }),
806
- resolvedTarget.namespace,
807
- tableName,
808
- directPath,
809
- );
810
- };
811
- const requestViaPgHandler = async (
812
- method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
813
- directPath: string,
814
- query?: URLSearchParams,
815
- body?: Record<string, unknown>,
816
- ): Promise<Response> => {
817
- if (!env) {
818
- throw new Error('PostgreSQL table proxy requires env.');
819
- }
820
-
821
- const request = new Request(
822
- `http://internal/api/db/${resolvedTarget.namespace}${resolvedTarget.id ? `/${resolvedTarget.id}` : ''}${buildDirectTablePath(undefined, query).replace('/tables/' + tableName, directPath)}`,
823
- {
824
- method,
825
- headers: {
826
- 'Content-Type': 'application/json',
827
- ...headers,
828
- },
829
- body: body === undefined || method === 'GET' || method === 'DELETE'
830
- ? undefined
831
- : JSON.stringify(body),
832
- },
833
- );
834
-
835
- return handlePgRequest(
836
- buildInternalHandlerContext({
837
- env,
838
- request,
839
- body,
840
- executionCtx,
841
- }),
842
- resolvedTarget.namespace,
843
- tableName,
844
- directPath,
845
- );
846
- };
847
- const requestViaDirectDo = async (
848
- method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
849
- directPath: string,
850
- body?: Record<string, unknown>,
851
- ): Promise<Response> => {
852
- const res = await callDO(namespace, doName, directPath, {
853
- method,
854
- body,
855
- headers,
856
- });
857
-
858
- if (!resolvedTarget.id || res.status !== 201) {
859
- return res;
860
- }
861
-
862
- const createPayload = await res.clone().json().catch(() => null) as
863
- | { needsCreate?: boolean }
864
- | null;
865
- if (!createPayload?.needsCreate) {
866
- return res;
867
- }
868
-
869
- return callDO(namespace, doName, directPath, {
870
- method,
871
- body,
872
- headers: {
873
- ...headers,
874
- 'X-DO-Create-Authorized': '1',
875
- },
876
- });
877
- };
878
- const requestTable = async (
879
- method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
880
- id?: string,
881
- body?: Record<string, unknown>,
882
- query?: URLSearchParams,
883
- ): Promise<Response> => {
884
- const directPath = buildDirectTablePath(id);
885
- const directPathWithQuery = buildDirectTablePath(id, query);
886
- const provider = config.databases?.[resolvedTarget.namespace]?.provider;
887
-
888
- if (!routingOptions?.preferDirectDo && shouldRouteToD1(resolvedTarget.namespace, config) && env) {
889
- return requestViaD1Handler(method, directPath, query, body);
890
- }
891
- if ((provider === 'neon' || provider === 'postgres') && env) {
892
- return requestViaPgHandler(method, directPath, query, body);
893
- }
894
- if (env) {
895
- return requestViaDirectDo(method, directPathWithQuery, body);
896
- }
897
- if (workerUrl) {
898
- return requestViaWorker(method, buildTablePath(id, query), body);
899
- }
900
- return requestViaDirectDo(method, directPathWithQuery, body);
901
- };
902
-
903
- const insert = async (data: Record<string, unknown>): Promise<Record<string, unknown>> => {
904
- const res = await requestTable('POST', undefined, data);
905
- if (!res.ok) {
906
- const err = (await res.json().catch(() => ({}))) as Record<string, unknown>;
907
- throw new Error(`Cross-DO insert failed: ${err.message || res.status}`);
908
- }
909
- return (await res.json()) as Record<string, unknown>;
910
- };
911
-
912
- const upsert = async (
913
- data: Record<string, unknown>,
914
- options?: { conflictTarget?: string },
915
- ): Promise<Record<string, unknown>> => {
916
- const query = new URLSearchParams();
917
- query.set('upsert', 'true');
918
- if (options?.conflictTarget) {
919
- query.set('conflictTarget', options.conflictTarget);
920
- }
921
-
922
- const res = await requestTable('POST', undefined, data, query);
923
- if (!res.ok) {
924
- const err = (await res.json().catch(() => ({}))) as Record<string, unknown>;
925
- throw new Error(`Cross-DO upsert failed: ${err.message || res.status}`);
926
- }
927
- return (await res.json()) as Record<string, unknown>;
928
- };
929
-
930
- const update = async (
931
- id: string,
932
- data: Record<string, unknown>,
933
- ): Promise<Record<string, unknown>> => {
934
- const res = await requestTable('PATCH', id, data);
935
- if (!res.ok) {
936
- const err = (await res.json().catch(() => ({}))) as Record<string, unknown>;
937
- throw new Error(`Cross-DO update failed: ${err.message || res.status}`);
938
- }
939
- return (await res.json()) as Record<string, unknown>;
940
- };
941
-
942
- const del = async (id: string): Promise<{ deleted: boolean }> => {
943
- const res = await requestTable('DELETE', id);
944
- if (!res.ok) {
945
- const err = (await res.json().catch(() => ({}))) as Record<string, unknown>;
946
- throw new Error(`Cross-DO delete failed: ${err.message || res.status}`);
947
- }
948
- return (await res.json()) as { deleted: boolean };
949
- };
950
-
951
- const get = async (id: string): Promise<Record<string, unknown>> => {
952
- const res = await requestTable('GET', id);
953
- if (!res.ok) {
954
- const err = (await res.json().catch(() => ({}))) as Record<string, unknown>;
955
- throw new Error(`Cross-DO get failed: ${err.message || res.status}`);
956
- }
957
- return (await res.json()) as Record<string, unknown>;
958
- };
959
-
960
- const list = async (listOptions?: {
961
- limit?: number;
962
- filter?: unknown;
963
- }): Promise<{ items: Record<string, unknown>[] }> => {
964
- const query = new URLSearchParams();
965
- if (listOptions?.limit) query.set('limit', String(listOptions.limit));
966
- if (listOptions?.filter) query.set('filter', JSON.stringify(listOptions.filter));
967
-
968
- const res = await requestTable('GET', undefined, undefined, query);
969
- if (!res.ok) {
970
- const err = (await res.json().catch(() => ({}))) as Record<string, unknown>;
971
- throw new Error(`Cross-DO list failed: ${err.message || res.status}`);
972
- }
973
- return (await res.json()) as { items: Record<string, unknown>[] };
974
- };
975
-
976
- return { insert, upsert, update, delete: del, get, list };
977
- }
730
+ // ─── Admin DB Proxy (uses @edge-base/core TableRef via InternalHttpTransport) ───
978
731
 
979
732
  interface BuildAdminDbProxyOptions {
980
733
  databaseNamespace: DurableObjectNamespace;
@@ -986,25 +739,111 @@ interface BuildAdminDbProxyOptions {
986
739
  preferDirectDo?: boolean;
987
740
  }
988
741
 
742
+ // ─── Shared SQL executor — provider-aware direct paths → HTTP fallback ───
743
+
744
+ export interface SqlProviderAwareOptions {
745
+ env?: Env;
746
+ config: EdgeBaseConfig;
747
+ databaseNamespace?: DurableObjectNamespace;
748
+ workerUrl?: string;
749
+ serviceKey?: string;
750
+ }
751
+
752
+ /**
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
758
+ *
759
+ * Shared by buildFunctionContext, auth hooks, storage hooks, and plugin migrations.
760
+ */
761
+ export async function executeSqlProviderAware(
762
+ opts: SqlProviderAwareOptions,
763
+ namespace: string,
764
+ id: string | undefined,
765
+ query: string,
766
+ params?: unknown[],
767
+ ): Promise<unknown[]> {
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 ?? [],
780
+ );
781
+ return result.rows;
782
+ }
783
+
784
+ // Backwards-compatible aliases for existing internal callers and public surfaces.
785
+ export const executeSqlWithDirectBindingAccess = executeSqlProviderAware;
786
+ export const executeSqlWithDirectD1Access = executeSqlProviderAware;
787
+
788
+ /**
789
+ * Build the admin DB proxy that returns real DbRef/TableRef instances
790
+ * from @edge-base/core, routed through InternalHttpTransport for
791
+ * direct D1/PG/DO access (no HTTP round-trip).
792
+ */
989
793
  export function buildAdminDbProxy(options: BuildAdminDbProxyOptions): FunctionAdminContext['db'] {
990
- return (namespace: string, id?: string) => {
991
- const doName = getDbDoName(namespace, id);
992
-
993
- return {
994
- table(tableName: string): TableProxy {
995
- return buildTableProxy(
996
- tableName,
997
- options.databaseNamespace,
998
- options.config,
999
- options.workerUrl,
1000
- options.serviceKey,
1001
- options.env,
1002
- options.executionCtx,
1003
- { namespace, id, doName },
1004
- { preferDirectDo: options.preferDirectDo },
1005
- );
794
+ // Create HttpClient fallback for sql() tagged template support on TableRef.
795
+ // Trusted server contexts also receive a direct SQL executor below.
796
+ let httpClient: HttpClient | undefined;
797
+ if (options.workerUrl) {
798
+ httpClient = new HttpClient({
799
+ baseUrl: options.workerUrl,
800
+ serviceKey: options.serviceKey,
801
+ contextManager: new ContextManager(),
802
+ });
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,
1006
817
  },
1007
- };
818
+ namespace,
819
+ id,
820
+ query,
821
+ params,
822
+ );
823
+
824
+ return (namespace: string, id?: string): DbRef => {
825
+ // Create a per-DbRef transport with explicit dbContext so that
826
+ // path parsing is unambiguous even when instanceId === 'tables'.
827
+ const transport = new InternalHttpTransport({
828
+ databaseNamespace: options.databaseNamespace,
829
+ config: options.config,
830
+ workerUrl: options.workerUrl,
831
+ serviceKey: options.serviceKey,
832
+ env: options.env,
833
+ executionCtx: options.executionCtx,
834
+ preferDirectDo: options.preferDirectDo,
835
+ dbContext: { namespace, instanceId: id },
836
+ });
837
+ const dbApi = new DefaultDbApi(transport);
838
+ return new DbRef(
839
+ dbApi,
840
+ namespace,
841
+ id,
842
+ undefined, // databaseLiveClient — not available server-side
843
+ undefined, // filterMatchFn
844
+ httpClient, // enables table().sql`...` tagged template
845
+ sqlExecutor,
846
+ );
1008
847
  };
1009
848
  }
1010
849
 
@@ -1024,7 +863,7 @@ interface AdminAuthOptions {
1024
863
  /**
1025
864
  * Build admin auth context for App Functions.
1026
865
  * Uses AUTH_DB D1 directly for all operations (D1-first architecture).
1027
- * Cross-shard operations (listUsers, createUser) also available via Worker HTTP relay
866
+ * Cross-shard operations (listUsers) also available via Worker HTTP relay
1028
867
  * when workerUrl is provided.
1029
868
  */
1030
869
  export function buildAdminAuthContext(options: AdminAuthOptions): AdminAuthContext {
@@ -1089,10 +928,51 @@ export function buildAdminAuthContext(options: AdminAuthOptions): AdminAuthConte
1089
928
  displayName?: string;
1090
929
  role?: string;
1091
930
  }): Promise<Record<string, unknown>> {
931
+ // Direct D1 path — works without service key (same as updateUser/deleteUser)
932
+ if (d1Database) {
933
+ // Input validation (mirrors routes/admin-auth.ts guards)
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.');
938
+ const email = data.email.trim().toLowerCase();
939
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
940
+ throw new Error('Invalid email format.');
941
+ }
942
+ if (data.password.length < 8) throw new Error('Password must be at least 8 characters.');
943
+ if (data.password.length > 256) throw new Error('Password must not exceed 256 characters.');
944
+ if (data.displayName && data.displayName.length > 200) {
945
+ throw new Error('Display name must not exceed 200 characters.');
946
+ }
947
+ // Role validation (mirrors normalizeOptionalRole in routes/admin-auth.ts)
948
+ let role = 'user';
949
+ if (data.role !== undefined) {
950
+ if (typeof data.role !== 'string') throw new Error('Role must be a non-empty string.');
951
+ const trimmed = data.role.trim();
952
+ if (!trimmed) throw new Error('Role must be a non-empty string.');
953
+ if (trimmed.length > 100) throw new Error('Role must not exceed 100 characters.');
954
+ role = trimmed;
955
+ }
956
+
957
+ const db = new D1AuthDb(d1Database);
958
+ // Ensure auth tables exist (critical for fresh databases)
959
+ await ensureAuthSchema(db);
960
+ const user = await createManagedAdminUser(
961
+ db,
962
+ {
963
+ userId: generateId(),
964
+ email,
965
+ passwordHash: await hashPassword(data.password),
966
+ displayName: data.displayName,
967
+ role,
968
+ verified: true,
969
+ },
970
+ { kv: kvNamespace },
971
+ );
972
+ return authService.sanitizeUser(user, { includeAppMetadata: true });
973
+ }
1092
974
  if (workerUrl && serviceKey) {
1093
- // HTTP relay: POST /api/auth/admin/users → Worker → D1
1094
- // createUser has complex side effects (email index, _users_public, etc.)
1095
- // so it routes through the admin route which handles the full flow.
975
+ // HTTP relay fallback: POST /api/auth/admin/users → Worker → D1
1096
976
  const res = await fetch(`${workerUrl}/api/auth/admin/users`, {
1097
977
  method: 'POST',
1098
978
  headers: {
@@ -1111,7 +991,7 @@ export function buildAdminAuthContext(options: AdminAuthOptions): AdminAuthConte
1111
991
  return result.user;
1112
992
  }
1113
993
  throw new Error(
1114
- 'admin.auth.createUser() is not available in this context (requires workerUrl). ' +
994
+ 'admin.auth.createUser() is not available in this context (requires D1 or workerUrl). ' +
1115
995
  'Pass workerUrl to buildFunctionContext(), or use the external SDK.',
1116
996
  );
1117
997
  },
@@ -1215,6 +1095,25 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
1215
1095
  executionCtx: options.executionCtx,
1216
1096
  preferDirectDo: options.preferDirectDoDb,
1217
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
+ );
1218
1117
 
1219
1118
  // ─── context.admin — AdminEdgeBase-shaped internal proxy ───
1220
1119
  const admin: FunctionAdminContext = {
@@ -1223,77 +1122,9 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
1223
1122
  // ─── context.admin.db(namespace, id) — DB-first tenant access (§5) ───
1224
1123
  db: adminDb,
1225
1124
  auth: adminAuthContext,
1226
- async sql(
1227
- namespace: string,
1228
- id: string | undefined,
1229
- query: string,
1230
- params?: unknown[],
1231
- ): Promise<unknown[]> {
1232
- if (options.env) {
1233
- const dbBlock = options.config.databases?.[namespace];
1234
- const isDynamicNamespace = !!(dbBlock?.instance || dbBlock?.access?.canCreate || dbBlock?.access?.access);
1235
- if (isDynamicNamespace && !id) {
1236
- throw new Error(`admin.sql() requires an id for dynamic namespace '${namespace}'.`);
1237
- }
1238
-
1239
- if (!id && shouldRouteToD1(namespace, options.config)) {
1240
- const bindingName = getD1BindingName(namespace);
1241
- const d1 = (options.env as unknown as Record<string, unknown>)[bindingName] as D1Database | undefined;
1242
- if (!d1) {
1243
- throw new Error(`D1 binding '${bindingName}' not found.`);
1244
- }
1245
- try {
1246
- const stmt = d1.prepare(query);
1247
- const bound = params && params.length > 0 ? stmt.bind(...params) : stmt;
1248
- const result = await bound.all();
1249
- const rows = (result.results ?? []) as unknown[];
1250
- return rows;
1251
- } catch (error) {
1252
- const message = error instanceof Error ? error.message : 'SQL execution failed';
1253
- throw new Error(message);
1254
- }
1255
- }
1256
-
1257
- return executeDoSql({
1258
- databaseNamespace: options.databaseNamespace,
1259
- namespace,
1260
- id,
1261
- query,
1262
- params: params ?? [],
1263
- internal: true,
1264
- });
1265
- }
1266
-
1267
- if (options.workerUrl && options.serviceKey) {
1268
- // HTTP route: POST /api/sql → Worker → DatabaseDO (§11)
1269
- const res = await fetch(`${options.workerUrl}/api/sql`, {
1270
- method: 'POST',
1271
- headers: {
1272
- 'Content-Type': 'application/json',
1273
- 'X-EdgeBase-Service-Key': options.serviceKey,
1274
- },
1275
- body: JSON.stringify({ namespace, id, sql: query, params: params ?? [] }),
1276
- });
1277
- if (!res.ok) {
1278
- const err = (await res.json().catch(() => ({ message: 'SQL execution failed' }))) as {
1279
- message: string;
1280
- };
1281
- throw new Error(err.message);
1282
- }
1283
- const data = (await res.json()) as {
1284
- rows?: unknown[];
1285
- items?: unknown[];
1286
- results?: unknown[];
1287
- };
1288
- if (Array.isArray(data.rows)) return data.rows;
1289
- if (Array.isArray(data.items)) return data.items;
1290
- if (Array.isArray(data.results)) return data.results;
1291
- return [];
1292
- }
1293
- throw new Error(
1294
- 'admin.sql() requires workerUrl. Pass workerUrl to buildFunctionContext(), or use the external SDK.',
1295
- );
1296
- },
1125
+ // ─── Direct provider-aware SQL — delegates to shared executor ───
1126
+ sqlProviderAware,
1127
+ sqlWithDirectD1Access: sqlProviderAware,
1297
1128
  async broadcast(
1298
1129
  channel: string,
1299
1130
  event: string,
@@ -1302,11 +1133,13 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
1302
1133
  if (options.env?.DATABASE_LIVE) {
1303
1134
  const hubId = options.env.DATABASE_LIVE.idFromName('database-live:hub');
1304
1135
  const stub = options.env.DATABASE_LIVE.get(hubId);
1305
- const response = await stub.fetch(new Request('http://do/internal/broadcast', {
1306
- method: 'POST',
1307
- headers: { 'Content-Type': 'application/json' },
1308
- body: JSON.stringify({ channel, event, payload: payload ?? {} }),
1309
- }));
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
+ );
1310
1143
  if (!response.ok) {
1311
1144
  throw new Error(`client.broadcast() failed: ${response.status}`);
1312
1145
  }
@@ -1354,9 +1187,7 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
1354
1187
  method: 'POST',
1355
1188
  headers: {
1356
1189
  'Content-Type': 'application/json',
1357
- ...(options.serviceKey
1358
- ? { 'X-EdgeBase-Service-Key': options.serviceKey }
1359
- : {}),
1190
+ ...(options.serviceKey ? { 'X-EdgeBase-Service-Key': options.serviceKey } : {}),
1360
1191
  'X-EdgeBase-Call-Depth': String(currentDepth + 1),
1361
1192
  },
1362
1193
  body: JSON.stringify(data ?? {}),
@@ -1414,13 +1245,31 @@ export function buildFunctionContext(options: BuildFunctionContextOptions): Func
1414
1245
 
1415
1246
  // KV / D1 / Vectorize proxies
1416
1247
  kv(namespace: string): FunctionKvProxy {
1417
- 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
+ );
1418
1255
  },
1419
1256
  d1(database: string): FunctionD1Proxy {
1420
- 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
+ );
1421
1264
  },
1422
1265
  vector(index: string): FunctionVectorizeProxy {
1423
- 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
+ );
1424
1273
  },
1425
1274
 
1426
1275
  // Push notification management
@@ -1659,11 +1508,14 @@ export function buildFunctionD1Proxy(
1659
1508
  return {
1660
1509
  async exec<T = Record<string, unknown>>(query: string, params?: unknown[]): Promise<T[]> {
1661
1510
  if (config && env) {
1662
- const bindingName = config.d1?.[database]?.binding
1663
- ?? (database === 'auth' ? 'AUTH_DB' : undefined)
1664
- ?? (database === 'control' ? 'CONTROL_DB' : undefined)
1665
- ?? getD1BindingName(database);
1666
- 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;
1667
1519
  if (!binding) {
1668
1520
  throw new Error(`D1 binding '${bindingName}' not found.`);
1669
1521
  }
@@ -1715,7 +1567,7 @@ export function buildFunctionVectorizeProxy(
1715
1567
  if (values instanceof Float32Array || values instanceof Float64Array) {
1716
1568
  return Array.from(values);
1717
1569
  }
1718
- return Array.isArray(values) ? values as number[] : undefined;
1570
+ return Array.isArray(values) ? (values as number[]) : undefined;
1719
1571
  };
1720
1572
 
1721
1573
  const mapMatches = (
@@ -1726,15 +1578,19 @@ export function buildFunctionVectorizeProxy(
1726
1578
  metadata?: Record<string, unknown>;
1727
1579
  namespace?: string;
1728
1580
  }>,
1729
- ) => matches.map((match) => ({
1730
- id: match.id,
1731
- score: match.score,
1732
- ...(match.values !== undefined ? { values: normalizeValues(match.values) } : {}),
1733
- ...(match.metadata !== undefined ? { metadata: match.metadata } : {}),
1734
- ...(match.namespace ? { namespace: match.namespace } : {}),
1735
- }));
1736
-
1737
- 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[] => {
1738
1594
  if (!namespace) return vectors;
1739
1595
  return vectors.map((vector) => (vector.namespace ? vector : { ...vector, namespace }));
1740
1596
  };
@@ -1752,7 +1608,10 @@ export function buildFunctionVectorizeProxy(
1752
1608
  if (directBinding) {
1753
1609
  switch (body.action) {
1754
1610
  case 'upsert': {
1755
- 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
+ );
1756
1615
  let count = 0;
1757
1616
  let mutationId: string | undefined;
1758
1617
  for (const chunk of chunkArray(vectors, VECTOR_BATCH_LIMIT)) {
@@ -1765,7 +1624,10 @@ export function buildFunctionVectorizeProxy(
1765
1624
  return { count, ...(mutationId ? { mutationId } : {}) };
1766
1625
  }
1767
1626
  case 'insert': {
1768
- 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
+ );
1769
1631
  let count = 0;
1770
1632
  let mutationId: string | undefined;
1771
1633
  for (const chunk of chunkArray(vectors, VECTOR_BATCH_LIMIT)) {
@@ -1788,9 +1650,11 @@ export function buildFunctionVectorizeProxy(
1788
1650
  return { matches: mapMatches(result.matches), count: result.count };
1789
1651
  }
1790
1652
  case 'queryById': {
1791
- const queryById = (directBinding as unknown as {
1792
- queryById?: (id: string, opts?: VectorizeQueryOptions) => Promise<VectorizeMatches>;
1793
- }).queryById;
1653
+ const queryById = (
1654
+ directBinding as unknown as {
1655
+ queryById?: (id: string, opts?: VectorizeQueryOptions) => Promise<VectorizeMatches>;
1656
+ }
1657
+ ).queryById;
1794
1658
  if (typeof queryById !== 'function') {
1795
1659
  throw new Error('queryById is not available on this Vectorize binding');
1796
1660
  }
@@ -1805,7 +1669,11 @@ export function buildFunctionVectorizeProxy(
1805
1669
  }
1806
1670
  case 'getByIds': {
1807
1671
  const vectors = (
1808
- 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
+ )
1809
1677
  ).flat();
1810
1678
  return {
1811
1679
  vectors: vectors.map((vector) => ({
@@ -1833,12 +1701,22 @@ export function buildFunctionVectorizeProxy(
1833
1701
  const details = info as unknown as Record<string, unknown>;
1834
1702
  return {
1835
1703
  vectorCount: details.vectorCount ?? details.vectorsCount ?? 0,
1836
- dimensions: details.dimensions ?? (details.config as Record<string, unknown> | undefined)?.dimensions ?? 0,
1837
- 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',
1838
1712
  ...('id' in details ? { id: details.id } : {}),
1839
1713
  ...('name' in details ? { name: details.name } : {}),
1840
- ...('processedUpToDatetime' in details ? { processedUpToDatetime: details.processedUpToDatetime } : {}),
1841
- ...('processedUpToMutation' in details ? { processedUpToMutation: details.processedUpToMutation } : {}),
1714
+ ...('processedUpToDatetime' in details
1715
+ ? { processedUpToDatetime: details.processedUpToDatetime }
1716
+ : {}),
1717
+ ...('processedUpToMutation' in details
1718
+ ? { processedUpToMutation: details.processedUpToMutation }
1719
+ : {}),
1842
1720
  };
1843
1721
  }
1844
1722
  }