@edge-base/server 0.2.3 → 0.2.5

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 (89) hide show
  1. package/admin-build/_app/immutable/chunks/{DpVAayDG.js → 6oMK_164.js} +1 -1
  2. package/admin-build/_app/immutable/chunks/{B5Nwfelm.js → B2TnDKF7.js} +1 -1
  3. package/admin-build/_app/immutable/chunks/{DCvwWZrm.js → B6MschND.js} +1 -1
  4. package/admin-build/_app/immutable/chunks/{Du5vWVa2.js → B94PilAN.js} +1 -1
  5. package/admin-build/_app/immutable/chunks/{Dc1-6Po6.js → BEW7Ez_g.js} +1 -1
  6. package/admin-build/_app/immutable/chunks/{Dlty5069.js → BoOooyH6.js} +1 -1
  7. package/admin-build/_app/immutable/chunks/{CzSAxmuj.js → BqTb6Mxk.js} +1 -1
  8. package/admin-build/_app/immutable/chunks/{DCKcAiQH.js → BvHnF5tV.js} +1 -1
  9. package/admin-build/_app/immutable/chunks/{B-_-hJ9o.js → CaVKAiCe.js} +1 -1
  10. package/admin-build/_app/immutable/chunks/{DRqPU3wD.js → Cdm5zBRA.js} +1 -1
  11. package/admin-build/_app/immutable/chunks/{byv2rTy8.js → CrOZMmdF.js} +1 -1
  12. package/admin-build/_app/immutable/chunks/{DiyBpamp.js → Cw6OYcq-.js} +1 -1
  13. package/admin-build/_app/immutable/chunks/{A_3UuvCe.js → D2j3I1VQ.js} +1 -1
  14. package/admin-build/_app/immutable/chunks/{BxoNtYHK.js → DPdQ7z0T.js} +3 -3
  15. package/admin-build/_app/immutable/chunks/{nZvorU8i.js → J2Gw0SMu.js} +1 -1
  16. package/admin-build/_app/immutable/chunks/{CZ0TVkCa.js → pUxw8jfq.js} +1 -1
  17. package/admin-build/_app/immutable/entry/{app.CfrmEXPD.js → app.D3flihMw.js} +2 -2
  18. package/admin-build/_app/immutable/entry/start.Cl6sLxnz.js +1 -0
  19. package/admin-build/_app/immutable/nodes/{0.Cn2BZ4da.js → 0.CdczqZLK.js} +1 -1
  20. package/admin-build/_app/immutable/nodes/{1.Dv4LX_Co.js → 1.DxcSsEqS.js} +1 -1
  21. package/admin-build/_app/immutable/nodes/{10.DPVv3kat.js → 10.DuAd4aIm.js} +1 -1
  22. package/admin-build/_app/immutable/nodes/{11.CiCb6Ayu.js → 11.0jgHQL92.js} +1 -1
  23. package/admin-build/_app/immutable/nodes/{12.CIPyeekF.js → 12.CKNPqmyy.js} +1 -1
  24. package/admin-build/_app/immutable/nodes/{13.Z15Lt36e.js → 13.B1p2POXS.js} +1 -1
  25. package/admin-build/_app/immutable/nodes/{14.s0l5bAq3.js → 14.Bb-REBND.js} +1 -1
  26. package/admin-build/_app/immutable/nodes/{15.UwSSNO76.js → 15.1uBFCX0X.js} +1 -1
  27. package/admin-build/_app/immutable/nodes/{16.qiD8i883.js → 16.BR7WwQrS.js} +1 -1
  28. package/admin-build/_app/immutable/nodes/{17.Dy3dcSvu.js → 17.Cm57KKXV.js} +1 -1
  29. package/admin-build/_app/immutable/nodes/{18.DeXyPYsO.js → 18.CoiwfAuQ.js} +1 -1
  30. package/admin-build/_app/immutable/nodes/{19.CAbuyS6w.js → 19.B8ZdLlXj.js} +1 -1
  31. package/admin-build/_app/immutable/nodes/{20.Bec0T7un.js → 20.DnHeFlTv.js} +1 -1
  32. package/admin-build/_app/immutable/nodes/21.CJFaf0Ia.js +1 -0
  33. package/admin-build/_app/immutable/nodes/{22.CdVprrv2.js → 22.CItETFzy.js} +1 -1
  34. package/admin-build/_app/immutable/nodes/{23.Y8RzVLoF.js → 23.CWSGMcKJ.js} +1 -1
  35. package/admin-build/_app/immutable/nodes/{24.CWhHYFBx.js → 24.CWbEqNMB.js} +1 -1
  36. package/admin-build/_app/immutable/nodes/{25.wCBplOVt.js → 25.DRkLEhKi.js} +1 -1
  37. package/admin-build/_app/immutable/nodes/{26.Cod_JRFK.js → 26.BRxO8AYH.js} +1 -1
  38. package/admin-build/_app/immutable/nodes/{27.BO2HVMu9.js → 27.BLs-nVHz.js} +1 -1
  39. package/admin-build/_app/immutable/nodes/{28.DxG-FBVQ.js → 28.G79qkdBK.js} +1 -1
  40. package/admin-build/_app/immutable/nodes/{29.CjGqWGvE.js → 29.BOcI6g0N.js} +1 -1
  41. package/admin-build/_app/immutable/nodes/{3.By3_OmdZ.js → 3.B6q-7qr8.js} +1 -1
  42. package/admin-build/_app/immutable/nodes/{30.M_H7Htpq.js → 30.DAIC7dKd.js} +1 -1
  43. package/admin-build/_app/immutable/nodes/{31.DEU18izM.js → 31.pl0XXjXF.js} +1 -1
  44. package/admin-build/_app/immutable/nodes/{4.DeYhKtzJ.js → 4.DOdvVlZj.js} +1 -1
  45. package/admin-build/_app/immutable/nodes/{5.9WLgxhrD.js → 5.BW_zlgye.js} +1 -1
  46. package/admin-build/_app/immutable/nodes/{6.BdT2i_dd.js → 6.Dxy1CAI2.js} +1 -1
  47. package/admin-build/_app/immutable/nodes/{7.CHq0s4K6.js → 7.BG98w_o7.js} +1 -1
  48. package/admin-build/_app/immutable/nodes/{8.DuvRw-XZ.js → 8.DoG5R2rG.js} +1 -1
  49. package/admin-build/_app/immutable/nodes/{9.C2Ub82wn.js → 9.Dmxf6zAC.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__/admin-data-routes.test.ts +29 -0
  54. package/src/__tests__/database-do-route-validation.test.ts +108 -0
  55. package/src/__tests__/database-live-route.test.ts +82 -0
  56. package/src/__tests__/do-router.test.ts +116 -0
  57. package/src/__tests__/functions-context.test.ts +84 -0
  58. package/src/__tests__/functions-d1-proxy.test.ts +54 -0
  59. package/src/__tests__/meta-route-registration.test.ts +20 -15
  60. package/src/__tests__/plugin-migration-routing.test.ts +32 -0
  61. package/src/__tests__/provider-aware-sql.test.ts +9 -3
  62. package/src/__tests__/room-auth-state-loss.test.ts +122 -0
  63. package/src/__tests__/room-handler-context.test.ts +4 -4
  64. package/src/__tests__/room-rate-limit-scopes.test.ts +38 -0
  65. package/src/__tests__/runtime-startup.test.ts +49 -0
  66. package/src/__tests__/scheduled.test.ts +55 -0
  67. package/src/__tests__/service-key-db-proxy.test.ts +122 -1
  68. package/src/__tests__/sql-route.test.ts +66 -0
  69. package/src/__tests__/table-hook-runtime.test.ts +137 -0
  70. package/src/durable-objects/database-do.ts +50 -45
  71. package/src/durable-objects/database-live-do.ts +15 -0
  72. package/src/durable-objects/room-runtime-base.ts +387 -129
  73. package/src/durable-objects/rooms-do.ts +31 -24
  74. package/src/index.ts +334 -282
  75. package/src/lib/d1-handler.ts +10 -21
  76. package/src/lib/do-router.ts +135 -3
  77. package/src/lib/functions.ts +4 -3
  78. package/src/lib/internal-transport.ts +28 -12
  79. package/src/lib/plugin-migration-routing.ts +28 -0
  80. package/src/lib/postgres-handler.ts +12 -20
  81. package/src/lib/provider-aware-sql.ts +19 -15
  82. package/src/lib/runtime-startup.ts +53 -0
  83. package/src/lib/table-hook-runtime.ts +62 -0
  84. package/src/routes/admin.ts +41 -41
  85. package/src/routes/database-live.ts +110 -12
  86. package/src/routes/sql.ts +22 -17
  87. package/src/routes/tables.ts +42 -29
  88. package/admin-build/_app/immutable/entry/start.l1WvHznQ.js +0 -1
  89. package/admin-build/_app/immutable/nodes/21.DuDYelMY.js +0 -1
@@ -0,0 +1,137 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import type { Env } from '../types.js';
3
+
4
+ const {
5
+ ensureAuthSchemaMock,
6
+ resolveAuthDbMock,
7
+ sendToDatabaseLiveDOMock,
8
+ createPushProviderMock,
9
+ getDevicesForUserMock,
10
+ providerSendMock,
11
+ } = vi.hoisted(() => ({
12
+ ensureAuthSchemaMock: vi.fn(),
13
+ resolveAuthDbMock: vi.fn(),
14
+ sendToDatabaseLiveDOMock: vi.fn(),
15
+ createPushProviderMock: vi.fn(),
16
+ getDevicesForUserMock: vi.fn(),
17
+ providerSendMock: vi.fn(),
18
+ }));
19
+
20
+ vi.mock('../lib/auth-d1.js', () => ({
21
+ ensureAuthSchema: ensureAuthSchemaMock,
22
+ }));
23
+
24
+ vi.mock('../lib/auth-db-adapter.js', () => ({
25
+ resolveAuthDb: resolveAuthDbMock,
26
+ }));
27
+
28
+ vi.mock('../lib/database-live-emitter.js', () => ({
29
+ sendToDatabaseLiveDO: sendToDatabaseLiveDOMock,
30
+ }));
31
+
32
+ vi.mock('../lib/push-provider.js', () => ({
33
+ createPushProvider: createPushProviderMock,
34
+ }));
35
+
36
+ vi.mock('../lib/push-token.js', () => ({
37
+ getDevicesForUser: getDevicesForUserMock,
38
+ }));
39
+
40
+ describe('buildTableHookRuntimeServices', () => {
41
+ beforeEach(() => {
42
+ vi.resetModules();
43
+ ensureAuthSchemaMock.mockReset().mockResolvedValue(undefined);
44
+ resolveAuthDbMock.mockReset().mockReturnValue({ kind: 'auth-db' });
45
+ sendToDatabaseLiveDOMock.mockReset().mockResolvedValue(undefined);
46
+ createPushProviderMock.mockReset().mockReturnValue({ send: providerSendMock });
47
+ getDevicesForUserMock.mockReset().mockResolvedValue([]);
48
+ providerSendMock.mockReset().mockResolvedValue({ success: true });
49
+ });
50
+
51
+ it('broadcasts hook events through DatabaseLiveDO', async () => {
52
+ const { buildTableHookRuntimeServices } = await import('../lib/table-hook-runtime.js');
53
+ const env = {
54
+ DATABASE_LIVE: {} as DurableObjectNamespace,
55
+ } as Env;
56
+
57
+ const services = buildTableHookRuntimeServices({} as never, env);
58
+ await services.databaseLive.broadcast('posts', 'created', { id: 'post-1' });
59
+
60
+ expect(sendToDatabaseLiveDOMock).toHaveBeenCalledWith(
61
+ env,
62
+ {
63
+ channel: 'posts',
64
+ event: 'created',
65
+ payload: { id: 'post-1' },
66
+ },
67
+ '/internal/broadcast',
68
+ );
69
+ });
70
+
71
+ it('sends push notifications using auth-backed device lookups when available', async () => {
72
+ const { buildTableHookRuntimeServices } = await import('../lib/table-hook-runtime.js');
73
+ const authDb = { kind: 'auth-db' };
74
+ resolveAuthDbMock.mockReturnValue(authDb);
75
+ getDevicesForUserMock.mockResolvedValue([
76
+ { token: 'token-1', platform: 'ios' },
77
+ { token: 'token-2', platform: 'android' },
78
+ ]);
79
+ const env = {
80
+ KV: {} as KVNamespace,
81
+ AUTH_DB: {} as D1Database,
82
+ } as Env;
83
+
84
+ const pushConfig = {
85
+ fcm: {
86
+ projectId: 'demo-project',
87
+ serviceAccount: '{"client_email":"demo@example.com"}',
88
+ },
89
+ };
90
+ const services = buildTableHookRuntimeServices({ push: pushConfig } as never, env);
91
+ await services.push.send('user-123', { title: 'Hello', body: 'World' });
92
+
93
+ expect(resolveAuthDbMock).toHaveBeenCalledWith(env);
94
+ expect(ensureAuthSchemaMock).toHaveBeenCalledWith(authDb);
95
+ expect(createPushProviderMock).toHaveBeenCalledWith(pushConfig, env);
96
+ expect(getDevicesForUserMock).toHaveBeenCalledWith({ kv: env.KV, authDb }, 'user-123');
97
+ expect(providerSendMock).toHaveBeenCalledTimes(2);
98
+ expect(providerSendMock).toHaveBeenNthCalledWith(1, {
99
+ token: 'token-1',
100
+ platform: 'ios',
101
+ payload: { title: 'Hello', body: 'World' },
102
+ });
103
+ expect(providerSendMock).toHaveBeenNthCalledWith(2, {
104
+ token: 'token-2',
105
+ platform: 'android',
106
+ payload: { title: 'Hello', body: 'World' },
107
+ });
108
+ });
109
+
110
+ it('falls back to KV-only token lookups when auth db resolution fails', async () => {
111
+ const { buildTableHookRuntimeServices } = await import('../lib/table-hook-runtime.js');
112
+ resolveAuthDbMock.mockImplementation(() => {
113
+ throw new Error('missing auth db');
114
+ });
115
+ getDevicesForUserMock.mockResolvedValue([{ token: 'token-1', platform: 'web' }]);
116
+ const env = {
117
+ KV: {} as KVNamespace,
118
+ } as Env;
119
+
120
+ const pushConfig = {
121
+ fcm: {
122
+ projectId: 'demo-project',
123
+ serviceAccount: '{"client_email":"demo@example.com"}',
124
+ },
125
+ };
126
+ const services = buildTableHookRuntimeServices({ push: pushConfig } as never, env);
127
+ await services.push.send('user-123', { body: 'Fallback works' });
128
+
129
+ expect(ensureAuthSchemaMock).not.toHaveBeenCalled();
130
+ expect(getDevicesForUserMock).toHaveBeenCalledWith(env.KV, 'user-123');
131
+ expect(providerSendMock).toHaveBeenCalledWith({
132
+ token: 'token-1',
133
+ platform: 'web',
134
+ payload: { body: 'Fallback works' },
135
+ });
136
+ });
137
+ });
@@ -54,15 +54,13 @@ import {
54
54
  getRegisteredFunctions,
55
55
  buildFunctionContext,
56
56
  } from '../lib/functions.js';
57
- import { parseDbDoName, parseConfig as getGlobalConfig } from '../lib/do-router.js';
57
+ import { parseDbDoName, parseConfig as getGlobalConfig, isDynamicDbBlock } from '../lib/do-router.js';
58
58
  import { parseDuration } from '../lib/jwt.js';
59
- import { createPushProvider } from '../lib/push-provider.js';
60
- import { getDevicesForUser } from '../lib/push-token.js';
61
- import { ensureAuthSchema } from '../lib/auth-d1.js';
62
- import { resolveAuthDb, type AuthDb } from '../lib/auth-db-adapter.js';
63
59
  import { buildDbLiveChannel, DATABASE_LIVE_HUB_DO_NAME } from '../lib/database-live-emitter.js';
64
60
  import { resolveRootServiceKey } from '../lib/service-key.js';
65
61
  import { resolveDbLiveBatchThreshold } from '../lib/database-live-config.js';
62
+ import { buildTableHookRuntimeServices } from '../lib/table-hook-runtime.js';
63
+ import { ensureServerStartup } from '../lib/runtime-startup.js';
66
64
  import type { Env } from '../types.js';
67
65
 
68
66
  // ─── Types ───
@@ -83,6 +81,7 @@ export class DatabaseDO extends DurableObject<DOEnv> {
83
81
  private config: EdgeBaseConfig;
84
82
  private initialized = false;
85
83
  private doName = '';
84
+ private runtimeReadyPromise: Promise<void> | null = null;
86
85
 
87
86
  constructor(ctx: DurableObjectState, env: DOEnv) {
88
87
  super(ctx, env);
@@ -95,6 +94,7 @@ export class DatabaseDO extends DurableObject<DOEnv> {
95
94
  }
96
95
 
97
96
  async fetch(request: Request): Promise<Response> {
97
+ await this.ensureRuntimeReady();
98
98
  // Determine DO name from header or URL
99
99
  const doNameHeader = request.headers.get('X-DO-Name');
100
100
 
@@ -104,8 +104,36 @@ export class DatabaseDO extends DurableObject<DOEnv> {
104
104
  if (!this.initialized) {
105
105
  // §36: Newly created DO must be authorized before initialization.
106
106
  // If X-DO-Create-Authorized header is absent, signal Worker to evaluate canCreate.
107
- // Shared/static DOs (doName === 'shared' or system) skip this gate.
108
- const isStaticDO = !this.doName || this.doName === 'shared' || this.doName.startsWith('_');
107
+ // Single-instance DB blocks and internal/system DOs skip this gate.
108
+ const parsedDoName = this.doName ? parseDbDoName(this.doName) : null;
109
+ const dbBlock = parsedDoName ? this.config.databases?.[parsedDoName.namespace] : undefined;
110
+ const dynamicDbBlock = isDynamicDbBlock(dbBlock);
111
+ if (parsedDoName && dbBlock) {
112
+ if (dynamicDbBlock && !parsedDoName.id) {
113
+ return Response.json(
114
+ {
115
+ code: 400,
116
+ message: `instanceId is required for dynamic namespace '${parsedDoName.namespace}'`,
117
+ error: 'INVALID_DB_INSTANCE_ID',
118
+ },
119
+ { status: 400 },
120
+ );
121
+ }
122
+ if (!dynamicDbBlock && parsedDoName.id) {
123
+ return Response.json(
124
+ {
125
+ code: 400,
126
+ message: `instanceId is not allowed for single-instance namespace '${parsedDoName.namespace}'`,
127
+ error: 'INVALID_DB_INSTANCE_ID',
128
+ },
129
+ { status: 400 },
130
+ );
131
+ }
132
+ }
133
+ const isStaticDO =
134
+ !this.doName
135
+ || this.doName.startsWith('_')
136
+ || (!!dbBlock && !dynamicDbBlock && !parsedDoName?.id);
109
137
  if (!isStaticDO && !request.headers.get('X-DO-Create-Authorized')) {
110
138
  // Check if _meta table already exists (i.e., DO was previously initialized)
111
139
  let alreadyExists = false;
@@ -118,9 +146,8 @@ export class DatabaseDO extends DurableObject<DOEnv> {
118
146
 
119
147
  if (!alreadyExists) {
120
148
  // Signal Worker: this DO needs canCreate evaluation before init
121
- const parsed = this.doName ? parseDbDoName(this.doName) : null;
122
149
  return Response.json(
123
- { needsCreate: true, namespace: parsed?.namespace ?? 'shared', id: parsed?.id },
150
+ { needsCreate: true, namespace: parsedDoName?.namespace ?? 'shared', id: parsedDoName?.id },
124
151
  { status: 201 },
125
152
  );
126
153
  }
@@ -173,6 +200,8 @@ export class DatabaseDO extends DurableObject<DOEnv> {
173
200
  * db.get/list/exists use local SQL; databaseLive.broadcast uses emitDbLiveEvent.
174
201
  */
175
202
  private buildHookCtx(_table: string): HookCtx {
203
+ const runtimeServices = buildTableHookRuntimeServices(this.config, this.env as unknown as Env);
204
+
176
205
  return {
177
206
  db: {
178
207
  get: (tbl: string, id: string) => {
@@ -201,42 +230,7 @@ export class DatabaseDO extends DurableObject<DOEnv> {
201
230
  return Promise.resolve(rows.length > 0);
202
231
  },
203
232
  },
204
- databaseLive: {
205
- broadcast: (channel: string, event: string, data: unknown) => {
206
- return this.sendBroadcastToDatabaseLiveDO(
207
- channel,
208
- { channel, event, payload: data ?? {} },
209
- );
210
- },
211
- },
212
- push: {
213
- // Push from hooks — direct FCM via push-provider + KV device tokens
214
- send: async (userId: string, payload: { title?: string; body: string }) => {
215
- // Fire-and-forget — hooks are non-critical side effects
216
- try {
217
- if (!this.env.KV) return;
218
- const provider = createPushProvider(this.config.push, this.env as unknown as Env);
219
- if (!provider) return;
220
- let tokenStore: KVNamespace | { kv: KVNamespace; authDb?: AuthDb | null } = this.env.KV;
221
- try {
222
- const authDb = resolveAuthDb(this.env as unknown as Record<string, unknown>);
223
- await ensureAuthSchema(authDb);
224
- tokenStore = { kv: this.env.KV, authDb };
225
- } catch {
226
- tokenStore = this.env.KV;
227
- }
228
- const devices = await getDevicesForUser(tokenStore, userId);
229
- if (devices.length === 0) return;
230
- await Promise.allSettled(
231
- devices.map((device) =>
232
- provider.send({ token: device.token, platform: device.platform, payload }),
233
- ),
234
- );
235
- } catch {
236
- /* best-effort */
237
- }
238
- },
239
- },
233
+ ...runtimeServices,
240
234
  waitUntil: (p: Promise<unknown>) => this.ctx.waitUntil(p),
241
235
  };
242
236
  }
@@ -2000,6 +1994,17 @@ export class DatabaseDO extends DurableObject<DOEnv> {
2000
1994
  return getGlobalConfig(env);
2001
1995
  }
2002
1996
 
1997
+ private async ensureRuntimeReady(): Promise<void> {
1998
+ if (!this.runtimeReadyPromise) {
1999
+ this.runtimeReadyPromise = (async () => {
2000
+ await ensureServerStartup();
2001
+ this.config = this.parseConfig(this.env);
2002
+ })();
2003
+ }
2004
+
2005
+ await this.runtimeReadyPromise;
2006
+ }
2007
+
2003
2008
  // ─── Database Live Event Emission ───
2004
2009
 
2005
2010
  /**
@@ -7,6 +7,7 @@ import { verifyAccessToken } from '../lib/jwt.js';
7
7
  import { parseConfig as getGlobalConfig } from '../lib/do-router.js';
8
8
  import { isDbLiveChannel } from '../lib/database-live-emitter.js';
9
9
  import { resolveDbLiveAuthTimeoutMs } from '../lib/database-live-config.js';
10
+ import { ensureServerStartup } from '../lib/runtime-startup.js';
10
11
 
11
12
  interface DOEnv {
12
13
  JWT_USER_SECRET?: string;
@@ -142,6 +143,7 @@ export class DatabaseLiveDO extends DurableObject<DOEnv> {
142
143
  private pendingAuth = new Map<string, ReturnType<typeof setTimeout>>();
143
144
  private metaCache = new Map<WebSocket, WSMeta>();
144
145
  private recentDeliveryIds = new Map<string, number>();
146
+ private runtimeReadyPromise: Promise<void> | null = null;
145
147
 
146
148
  constructor(ctx: DurableObjectState, env: DOEnv) {
147
149
  super(ctx, env);
@@ -149,6 +151,7 @@ export class DatabaseLiveDO extends DurableObject<DOEnv> {
149
151
  }
150
152
 
151
153
  async fetch(request: Request): Promise<Response> {
154
+ await this.ensureRuntimeReady();
152
155
  const url = new URL(request.url);
153
156
 
154
157
  if (url.pathname === '/internal/event') {
@@ -218,6 +221,7 @@ export class DatabaseLiveDO extends DurableObject<DOEnv> {
218
221
  }
219
222
 
220
223
  async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
224
+ await this.ensureRuntimeReady();
221
225
  if (typeof message !== 'string') return;
222
226
 
223
227
  let msg: Record<string, unknown>;
@@ -922,6 +926,17 @@ export class DatabaseLiveDO extends DurableObject<DOEnv> {
922
926
  if (parts.length >= 5) return parts[3];
923
927
  return null;
924
928
  }
929
+
930
+ private async ensureRuntimeReady(): Promise<void> {
931
+ if (!this.runtimeReadyPromise) {
932
+ this.runtimeReadyPromise = (async () => {
933
+ await ensureServerStartup();
934
+ this.config = getGlobalConfig(this.env);
935
+ })();
936
+ }
937
+
938
+ await this.runtimeReadyPromise;
939
+ }
925
940
  }
926
941
 
927
942
  export function evaluateDatabaseLiveFilters(