@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.
- package/admin-build/_app/immutable/chunks/{DpVAayDG.js → 6oMK_164.js} +1 -1
- package/admin-build/_app/immutable/chunks/{B5Nwfelm.js → B2TnDKF7.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DCvwWZrm.js → B6MschND.js} +1 -1
- package/admin-build/_app/immutable/chunks/{Du5vWVa2.js → B94PilAN.js} +1 -1
- package/admin-build/_app/immutable/chunks/{Dc1-6Po6.js → BEW7Ez_g.js} +1 -1
- package/admin-build/_app/immutable/chunks/{Dlty5069.js → BoOooyH6.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CzSAxmuj.js → BqTb6Mxk.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DCKcAiQH.js → BvHnF5tV.js} +1 -1
- package/admin-build/_app/immutable/chunks/{B-_-hJ9o.js → CaVKAiCe.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DRqPU3wD.js → Cdm5zBRA.js} +1 -1
- package/admin-build/_app/immutable/chunks/{byv2rTy8.js → CrOZMmdF.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DiyBpamp.js → Cw6OYcq-.js} +1 -1
- package/admin-build/_app/immutable/chunks/{A_3UuvCe.js → D2j3I1VQ.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BxoNtYHK.js → DPdQ7z0T.js} +3 -3
- package/admin-build/_app/immutable/chunks/{nZvorU8i.js → J2Gw0SMu.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CZ0TVkCa.js → pUxw8jfq.js} +1 -1
- package/admin-build/_app/immutable/entry/{app.CfrmEXPD.js → app.D3flihMw.js} +2 -2
- package/admin-build/_app/immutable/entry/start.Cl6sLxnz.js +1 -0
- package/admin-build/_app/immutable/nodes/{0.Cn2BZ4da.js → 0.CdczqZLK.js} +1 -1
- package/admin-build/_app/immutable/nodes/{1.Dv4LX_Co.js → 1.DxcSsEqS.js} +1 -1
- package/admin-build/_app/immutable/nodes/{10.DPVv3kat.js → 10.DuAd4aIm.js} +1 -1
- package/admin-build/_app/immutable/nodes/{11.CiCb6Ayu.js → 11.0jgHQL92.js} +1 -1
- package/admin-build/_app/immutable/nodes/{12.CIPyeekF.js → 12.CKNPqmyy.js} +1 -1
- package/admin-build/_app/immutable/nodes/{13.Z15Lt36e.js → 13.B1p2POXS.js} +1 -1
- package/admin-build/_app/immutable/nodes/{14.s0l5bAq3.js → 14.Bb-REBND.js} +1 -1
- package/admin-build/_app/immutable/nodes/{15.UwSSNO76.js → 15.1uBFCX0X.js} +1 -1
- package/admin-build/_app/immutable/nodes/{16.qiD8i883.js → 16.BR7WwQrS.js} +1 -1
- package/admin-build/_app/immutable/nodes/{17.Dy3dcSvu.js → 17.Cm57KKXV.js} +1 -1
- package/admin-build/_app/immutable/nodes/{18.DeXyPYsO.js → 18.CoiwfAuQ.js} +1 -1
- package/admin-build/_app/immutable/nodes/{19.CAbuyS6w.js → 19.B8ZdLlXj.js} +1 -1
- package/admin-build/_app/immutable/nodes/{20.Bec0T7un.js → 20.DnHeFlTv.js} +1 -1
- package/admin-build/_app/immutable/nodes/21.CJFaf0Ia.js +1 -0
- package/admin-build/_app/immutable/nodes/{22.CdVprrv2.js → 22.CItETFzy.js} +1 -1
- package/admin-build/_app/immutable/nodes/{23.Y8RzVLoF.js → 23.CWSGMcKJ.js} +1 -1
- package/admin-build/_app/immutable/nodes/{24.CWhHYFBx.js → 24.CWbEqNMB.js} +1 -1
- package/admin-build/_app/immutable/nodes/{25.wCBplOVt.js → 25.DRkLEhKi.js} +1 -1
- package/admin-build/_app/immutable/nodes/{26.Cod_JRFK.js → 26.BRxO8AYH.js} +1 -1
- package/admin-build/_app/immutable/nodes/{27.BO2HVMu9.js → 27.BLs-nVHz.js} +1 -1
- package/admin-build/_app/immutable/nodes/{28.DxG-FBVQ.js → 28.G79qkdBK.js} +1 -1
- package/admin-build/_app/immutable/nodes/{29.CjGqWGvE.js → 29.BOcI6g0N.js} +1 -1
- package/admin-build/_app/immutable/nodes/{3.By3_OmdZ.js → 3.B6q-7qr8.js} +1 -1
- package/admin-build/_app/immutable/nodes/{30.M_H7Htpq.js → 30.DAIC7dKd.js} +1 -1
- package/admin-build/_app/immutable/nodes/{31.DEU18izM.js → 31.pl0XXjXF.js} +1 -1
- package/admin-build/_app/immutable/nodes/{4.DeYhKtzJ.js → 4.DOdvVlZj.js} +1 -1
- package/admin-build/_app/immutable/nodes/{5.9WLgxhrD.js → 5.BW_zlgye.js} +1 -1
- package/admin-build/_app/immutable/nodes/{6.BdT2i_dd.js → 6.Dxy1CAI2.js} +1 -1
- package/admin-build/_app/immutable/nodes/{7.CHq0s4K6.js → 7.BG98w_o7.js} +1 -1
- package/admin-build/_app/immutable/nodes/{8.DuvRw-XZ.js → 8.DoG5R2rG.js} +1 -1
- package/admin-build/_app/immutable/nodes/{9.C2Ub82wn.js → 9.Dmxf6zAC.js} +1 -1
- package/admin-build/_app/version.json +1 -1
- package/admin-build/index.html +7 -7
- package/package.json +3 -3
- package/src/__tests__/admin-data-routes.test.ts +29 -0
- package/src/__tests__/database-do-route-validation.test.ts +108 -0
- package/src/__tests__/database-live-route.test.ts +82 -0
- package/src/__tests__/do-router.test.ts +116 -0
- package/src/__tests__/functions-context.test.ts +84 -0
- package/src/__tests__/functions-d1-proxy.test.ts +54 -0
- package/src/__tests__/meta-route-registration.test.ts +20 -15
- package/src/__tests__/plugin-migration-routing.test.ts +32 -0
- package/src/__tests__/provider-aware-sql.test.ts +9 -3
- package/src/__tests__/room-auth-state-loss.test.ts +122 -0
- package/src/__tests__/room-handler-context.test.ts +4 -4
- package/src/__tests__/room-rate-limit-scopes.test.ts +38 -0
- package/src/__tests__/runtime-startup.test.ts +49 -0
- package/src/__tests__/scheduled.test.ts +55 -0
- package/src/__tests__/service-key-db-proxy.test.ts +122 -1
- package/src/__tests__/sql-route.test.ts +66 -0
- package/src/__tests__/table-hook-runtime.test.ts +137 -0
- package/src/durable-objects/database-do.ts +50 -45
- package/src/durable-objects/database-live-do.ts +15 -0
- package/src/durable-objects/room-runtime-base.ts +387 -129
- package/src/durable-objects/rooms-do.ts +31 -24
- package/src/index.ts +334 -282
- package/src/lib/d1-handler.ts +10 -21
- package/src/lib/do-router.ts +135 -3
- package/src/lib/functions.ts +4 -3
- package/src/lib/internal-transport.ts +28 -12
- package/src/lib/plugin-migration-routing.ts +28 -0
- package/src/lib/postgres-handler.ts +12 -20
- package/src/lib/provider-aware-sql.ts +19 -15
- package/src/lib/runtime-startup.ts +53 -0
- package/src/lib/table-hook-runtime.ts +62 -0
- package/src/routes/admin.ts +41 -41
- package/src/routes/database-live.ts +110 -12
- package/src/routes/sql.ts +22 -17
- package/src/routes/tables.ts +42 -29
- package/admin-build/_app/immutable/entry/start.l1WvHznQ.js +0 -1
- 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
|
-
//
|
|
108
|
-
const
|
|
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:
|
|
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
|
-
|
|
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(
|