@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
package/src/index.ts CHANGED
@@ -1,54 +1,7 @@
1
- import { OpenAPIHono, type HonoEnv } from './lib/hono.js';
2
- import { HTTPException } from 'hono/http-exception';
3
- import { initFunctionRegistry } from './_functions-registry.js';
4
- import { corsMiddleware } from './middleware/cors.js';
5
- import { rateLimitMiddleware } from './middleware/rate-limit.js';
6
- import { errorHandlerMiddleware } from './middleware/error-handler.js';
7
- import { internalGuardMiddleware } from './middleware/internal-guard.js';
8
- import { authMiddleware } from './middleware/auth.js';
9
- import { rulesMiddleware } from './middleware/rules.js';
10
-
11
- import { loggerMiddleware } from './middleware/logger.js';
12
- import { EdgeBaseError } from '@edge-base/shared';
13
- import { SERVER_VERSION } from './lib/version.js';
14
- import { healthRoute } from './routes/health.js';
15
- import { tablesRoute } from './routes/tables.js';
16
- import { schemaRoute } from './routes/schema-endpoint.js';
17
- import { authRoute } from './routes/auth.js';
18
- import { adminAuthRoute } from './routes/admin-auth.js';
19
- import { oauthRoute } from './routes/oauth.js';
20
- import { databaseLiveRoute } from './routes/database-live.js';
21
- import { storageRoute } from './routes/storage.js';
22
- import { functionsRoute } from './routes/functions.js';
23
- import { adminRoute } from './routes/admin.js';
24
- import { backupRoute } from './routes/backup.js';
25
- import { sqlRoute } from './routes/sql.js';
26
- import { kvRoute } from './routes/kv.js';
27
- import { d1Route } from './routes/d1.js';
28
- import { vectorizeRoute } from './routes/vectorize.js';
29
- import { configRoute } from './routes/config.js';
30
- import { pushRoute } from './routes/push.js';
31
- import { roomRoute } from './routes/room.js';
32
- import { analyticsApi } from './routes/analytics-api.js';
33
- import { parseConfig, setConfig } from './lib/do-router.js';
34
- import { createAdminAssetRequest } from './lib/admin-assets.js';
35
- import { resolveAdminFaviconTarget, resolveAdminRedirectTarget } from './lib/admin-routing.js';
36
- import { zodDefaultHook } from './lib/schemas.js';
37
- import { executePluginMigrations } from './lib/plugin-migrations.js';
38
- import { getFunctionsByTrigger, buildFunctionContext, getWorkerUrl } from './lib/functions.js';
39
- import { parseCron, matchesCron } from './lib/cron.js';
40
- import { parseDuration } from './lib/jwt.js';
41
- import { resolveStartupConfig } from './lib/startup-config.js';
42
- import * as authService from './lib/auth-d1-service.js';
43
- import { ensureAuthSchema, deleteAnon } from './lib/auth-d1.js';
44
- import { resolveAuthDb } from './lib/auth-db-adapter.js';
45
- import { resolveRootServiceKey } from './lib/service-key.js';
46
- import { normalizeOpenApiDocument, type OpenApiSpec } from './lib/openapi.js';
47
- import generatedConfig from './generated-config.js';
1
+ import type { HonoEnv } from './lib/hono.js';
2
+ import type { OpenApiSpec } from './lib/openapi.js';
48
3
  import type { Env } from './types.js';
49
-
50
- // Compile-time constant — injected by wrangler [define] in wrangler.test.toml
51
- declare const EDGEBASE_TEST_BUILD: boolean | undefined;
4
+ import { ensureServerStartup } from './lib/runtime-startup.js';
52
5
 
53
6
  // ─── DO Re-exports (wrangler needs exports from main entry) ───
54
7
  export { DatabaseDO } from './durable-objects/database-do.js';
@@ -57,275 +10,374 @@ export { AuthDO } from './durable-objects/auth-do.js';
57
10
  export { RoomsDO } from './durable-objects/rooms-do.js';
58
11
  export { LogsDO } from './durable-objects/logs-do.js';
59
12
 
60
- try {
61
- const processEnv = typeof process !== 'undefined' ? process.env : undefined;
62
- // EDGEBASE_TEST_BUILD is a compile-time constant injected by wrangler [define]
63
- // in wrangler.test.toml. typeof is safe for undefined identifiers.
64
- const isTestBuild = typeof EDGEBASE_TEST_BUILD !== 'undefined';
65
- const preferTestConfig = await detectWorkersTestRuntime() || isTestBuild;
66
- const resolvedConfig = await resolveStartupConfig(
67
- generatedConfig,
68
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
- async () => import('../edgebase.test.config.ts' as any),
70
- processEnv,
71
- { preferTestConfig },
72
- );
73
-
74
- if (resolvedConfig) {
75
- setConfig(resolvedConfig);
76
- }
77
- } catch (err) {
78
- console.error('[EdgeBase] Failed to initialize config at startup:', err);
79
- throw err;
80
- }
13
+ let appPromise: Promise<Awaited<ReturnType<typeof buildApp>>> | null = null;
14
+
15
+ async function buildApp() {
16
+ await ensureServerStartup();
17
+
18
+ const [
19
+ honoModule,
20
+ httpExceptionModule,
21
+ corsModule,
22
+ rateLimitModule,
23
+ errorHandlerModule,
24
+ internalGuardModule,
25
+ authMiddlewareModule,
26
+ rulesMiddlewareModule,
27
+ loggerModule,
28
+ sharedModule,
29
+ versionModule,
30
+ healthRouteModule,
31
+ tablesRouteModule,
32
+ schemaRouteModule,
33
+ authRouteModule,
34
+ adminAuthRouteModule,
35
+ oauthRouteModule,
36
+ databaseLiveRouteModule,
37
+ storageRouteModule,
38
+ functionsRouteModule,
39
+ adminRouteModule,
40
+ backupRouteModule,
41
+ sqlRouteModule,
42
+ kvRouteModule,
43
+ d1RouteModule,
44
+ vectorizeRouteModule,
45
+ configRouteModule,
46
+ pushRouteModule,
47
+ roomRouteModule,
48
+ analyticsRouteModule,
49
+ adminAssetsModule,
50
+ adminRoutingModule,
51
+ schemasModule,
52
+ pluginMigrationsModule,
53
+ pluginMigrationRoutingModule,
54
+ functionsModule,
55
+ openApiModule,
56
+ doRouterModule,
57
+ ] = await Promise.all([
58
+ import('./lib/hono.js'),
59
+ import('hono/http-exception'),
60
+ import('./middleware/cors.js'),
61
+ import('./middleware/rate-limit.js'),
62
+ import('./middleware/error-handler.js'),
63
+ import('./middleware/internal-guard.js'),
64
+ import('./middleware/auth.js'),
65
+ import('./middleware/rules.js'),
66
+ import('./middleware/logger.js'),
67
+ import('@edge-base/shared'),
68
+ import('./lib/version.js'),
69
+ import('./routes/health.js'),
70
+ import('./routes/tables.js'),
71
+ import('./routes/schema-endpoint.js'),
72
+ import('./routes/auth.js'),
73
+ import('./routes/admin-auth.js'),
74
+ import('./routes/oauth.js'),
75
+ import('./routes/database-live.js'),
76
+ import('./routes/storage.js'),
77
+ import('./routes/functions.js'),
78
+ import('./routes/admin.js'),
79
+ import('./routes/backup.js'),
80
+ import('./routes/sql.js'),
81
+ import('./routes/kv.js'),
82
+ import('./routes/d1.js'),
83
+ import('./routes/vectorize.js'),
84
+ import('./routes/config.js'),
85
+ import('./routes/push.js'),
86
+ import('./routes/room.js'),
87
+ import('./routes/analytics-api.js'),
88
+ import('./lib/admin-assets.js'),
89
+ import('./lib/admin-routing.js'),
90
+ import('./lib/schemas.js'),
91
+ import('./lib/plugin-migrations.js'),
92
+ import('./lib/plugin-migration-routing.js'),
93
+ import('./lib/functions.js'),
94
+ import('./lib/openapi.js'),
95
+ import('./lib/do-router.js'),
96
+ ]);
97
+
98
+ const { OpenAPIHono } = honoModule;
99
+ const { HTTPException } = httpExceptionModule;
100
+ const { corsMiddleware } = corsModule;
101
+ const { rateLimitMiddleware } = rateLimitModule;
102
+ const { errorHandlerMiddleware } = errorHandlerModule;
103
+ const { internalGuardMiddleware } = internalGuardModule;
104
+ const { authMiddleware } = authMiddlewareModule;
105
+ const { rulesMiddleware } = rulesMiddlewareModule;
106
+ const { loggerMiddleware } = loggerModule;
107
+ const { EdgeBaseError } = sharedModule;
108
+ const { SERVER_VERSION } = versionModule;
109
+ const { createAdminAssetRequest } = adminAssetsModule;
110
+ const { resolveAdminFaviconTarget, resolveAdminRedirectTarget } = adminRoutingModule;
111
+ const { zodDefaultHook } = schemasModule;
112
+ const { executePluginMigrations } = pluginMigrationsModule;
113
+ const { shouldRunPluginMigrationsForRequestPath } = pluginMigrationRoutingModule;
114
+ const { getWorkerUrl } = functionsModule;
115
+ const { normalizeOpenApiDocument } = openApiModule;
116
+
117
+ const app = new OpenAPIHono<HonoEnv>({ defaultHook: zodDefaultHook });
118
+
119
+ app.use('*', errorHandlerMiddleware);
120
+ app.use('*', loggerMiddleware);
121
+ app.use('*', corsMiddleware);
122
+
123
+ app.use('*', async (c, next) => {
124
+ const env = c.env as Env;
125
+ const config = doRouterModule.parseConfig(env);
126
+ const requestPath = new URL(c.req.url).pathname;
127
+ if (config?.plugins?.length && shouldRunPluginMigrationsForRequestPath(requestPath)) {
128
+ await executePluginMigrations(config.plugins, env, config, getWorkerUrl(c.req.url, env));
129
+ }
130
+ return next();
131
+ });
81
132
 
82
- async function detectWorkersTestRuntime(): Promise<boolean> {
83
- try {
84
- await import('cloudflare:test');
85
- return true;
86
- } catch {
87
- return false;
88
- }
89
- }
133
+ app.use('*', rateLimitMiddleware);
134
+ app.use('/api/*', authMiddleware);
135
+ app.use('/api/db/*', rulesMiddleware);
136
+ app.use('/internal/*', internalGuardMiddleware);
137
+
138
+ app.route('/api', healthRouteModule.healthRoute);
139
+ app.route('/api/auth', authRouteModule.authRoute);
140
+ app.route('/api/auth/admin', adminAuthRouteModule.adminAuthRoute);
141
+ app.route('/api/auth/oauth', oauthRouteModule.oauthRoute);
142
+ app.route('/api/db', tablesRouteModule.tablesRoute);
143
+ app.route('/api/db', databaseLiveRouteModule.databaseLiveRoute);
144
+ app.route('/api/schema', schemaRouteModule.schemaRoute);
145
+ app.route('/api/storage', storageRouteModule.storageRoute);
146
+ app.route('/api/functions', functionsRouteModule.functionsRoute);
147
+ app.route('/api/sql', sqlRouteModule.sqlRoute);
148
+ app.route('/api/kv', kvRouteModule.kvRoute);
149
+ app.route('/api/d1', d1RouteModule.d1Route);
150
+ app.route('/api/vectorize', vectorizeRouteModule.vectorizeRoute);
151
+ app.route('/api/config', configRouteModule.configRoute);
152
+ app.route('/api/push', pushRouteModule.pushRoute);
153
+ app.route('/api/room', roomRouteModule.roomRoute);
154
+ app.route('/api/analytics', analyticsRouteModule.analyticsApi);
155
+ app.route('/admin/api', adminRouteModule.adminRoute);
156
+ app.route('/admin/api/backup', backupRouteModule.backupRoute);
157
+
158
+ app.get('/', (c) => {
159
+ const env = c.env as Env;
160
+ const externalAdminUrl = resolveAdminRedirectTarget(c.req.url, env.ADMIN_ORIGIN);
161
+ if (externalAdminUrl) {
162
+ return c.redirect(externalAdminUrl, 302);
163
+ }
164
+ if (env.ASSETS) {
165
+ return c.redirect('/admin', 302);
166
+ }
167
+ return c.json({
168
+ name: 'EdgeBase API',
169
+ docs: '/openapi.json',
170
+ admin: null,
171
+ });
172
+ });
90
173
 
91
- initFunctionRegistry();
174
+ app.get('/favicon.ico', async (c) => {
175
+ const env = c.env as Env;
176
+ const externalFaviconUrl = resolveAdminFaviconTarget(env.ADMIN_ORIGIN);
177
+ if (externalFaviconUrl) {
178
+ return c.redirect(externalFaviconUrl, 302);
179
+ }
92
180
 
93
- const app = new OpenAPIHono<HonoEnv>({ defaultHook: zodDefaultHook });
181
+ if (!env.ASSETS) {
182
+ return c.json({ code: 404, message: 'Admin dashboard not deployed.' }, 404);
183
+ }
94
184
 
95
- // ─── Global Middleware Chain ───
96
- // Order: Error Handler → Logger → CORS → Strip Internal Header → Rate Limit → Auth → Context → Rules
185
+ const url = new URL(c.req.url);
186
+ url.pathname = '/admin/favicon.svg';
187
+ return env.ASSETS.fetch(createAdminAssetRequest(new Request(url.toString(), c.req.raw)));
188
+ });
97
189
 
98
- app.use('*', errorHandlerMiddleware);
99
- app.use('*', loggerMiddleware);
100
- app.use('*', corsMiddleware);
190
+ app.get('/favicon.svg', async (c) => {
191
+ const env = c.env as Env;
192
+ const externalFaviconUrl = resolveAdminFaviconTarget(env.ADMIN_ORIGIN);
193
+ if (externalFaviconUrl) {
194
+ return c.redirect(externalFaviconUrl, 302);
195
+ }
101
196
 
102
- // NOTE: X-EdgeBase-Internal header stripping via c.req.raw.headers.delete() is NOT
103
- // possible in Workers — Request.headers are immutable. Instead, the internal guard
104
- // middleware below rejects any request with this header unless it comes via internal
105
- // stub.fetch (which is allowed by the x-internal whitelist). No stripping needed.
197
+ if (env.ASSETS) {
198
+ return env.ASSETS.fetch(c.req.raw);
199
+ }
106
200
 
107
- // Plugin migration middleware lazy, runs once per cold-start
108
- app.use('*', async (c, next) => {
109
- const config = parseConfig(c.env);
110
- const requestPath = new URL(c.req.url).pathname;
111
- const shouldSkipPluginMigrations =
112
- requestPath.startsWith('/admin/api/backup/') ||
113
- requestPath.startsWith('/admin/api/data/backup/') ||
114
- requestPath.startsWith('/internal/backup/');
115
- if (!shouldSkipPluginMigrations && config?.plugins?.length) {
116
- await executePluginMigrations(config.plugins, c.env, config, getWorkerUrl(c.req.url, c.env));
117
- }
118
- return next();
119
- });
120
-
121
- app.use('*', rateLimitMiddleware);
122
-
123
- // Auth middleware — JWT verification + auth context injection (M3)
124
- app.use('/api/*', authMiddleware);
125
-
126
- // Context middleware removed — DB-level access rules (§4,) handle multi-tenancy.
127
-
128
- // Rules middleware — access rules evaluation (M4,)
129
- app.use('/api/db/*', rulesMiddleware);
130
-
131
- // ─── Internal Guard ───
132
- app.use('/internal/*', internalGuardMiddleware);
133
-
134
- // ─── Routes ───
135
- app.route('/api', healthRoute);
136
- app.route('/api/auth', authRoute);
137
- app.route('/api/auth/admin', adminAuthRoute);
138
- app.route('/api/auth/oauth', oauthRoute);
139
- app.route('/api/db', tablesRoute);
140
- app.route('/api/db', databaseLiveRoute);
141
- app.route('/api/schema', schemaRoute);
142
- app.route('/api/storage', storageRoute);
143
- app.route('/api/functions', functionsRoute);
144
- app.route('/api/sql', sqlRoute);
145
- app.route('/api/kv', kvRoute);
146
- app.route('/api/d1', d1Route);
147
- app.route('/api/vectorize', vectorizeRoute);
148
- app.route('/api/config', configRoute);
149
- app.route('/api/push', pushRoute);
150
- app.route('/api/room', roomRoute);
151
- app.route('/api/analytics', analyticsApi);
152
- // ─── Admin Dashboard (M12,) ───
153
- app.route('/admin/api', adminRoute);
154
- app.route('/admin/api/backup', backupRoute);
155
-
156
- app.get('/', (c) => {
157
- const externalAdminUrl = resolveAdminRedirectTarget(c.req.url, c.env.ADMIN_ORIGIN);
158
- if (externalAdminUrl) {
159
- return c.redirect(externalAdminUrl, 302);
160
- }
161
- if (c.env.ASSETS) {
162
- return c.redirect('/admin', 302);
163
- }
164
- return c.json({
165
- name: 'EdgeBase API',
166
- docs: '/openapi.json',
167
- admin: null,
201
+ return c.json({ code: 404, message: 'Admin dashboard not deployed.' }, 404);
168
202
  });
169
- });
170
203
 
171
- // Admin static assets — SvelteKit SPA served via Workers Static Assets
172
- app.get('/favicon.ico', async (c) => {
173
- const externalFaviconUrl = resolveAdminFaviconTarget(c.env.ADMIN_ORIGIN);
174
- if (externalFaviconUrl) {
175
- return c.redirect(externalFaviconUrl, 302);
176
- }
204
+ app.get('/_app/*', async (c) => {
205
+ const env = c.env as Env;
206
+ if (env.ASSETS) {
207
+ return env.ASSETS.fetch(c.req.raw);
208
+ }
177
209
 
178
- if (!c.env.ASSETS) {
179
210
  return c.json({ code: 404, message: 'Admin dashboard not deployed.' }, 404);
180
- }
211
+ });
181
212
 
182
- const url = new URL(c.req.url);
183
- url.pathname = '/admin/favicon.svg';
184
- return c.env.ASSETS.fetch(createAdminAssetRequest(new Request(url.toString(), c.req.raw)));
185
- });
213
+ app.get('/admin/*', async (c) => {
214
+ const env = c.env as Env;
215
+ const externalAdminUrl = resolveAdminRedirectTarget(c.req.url, env.ADMIN_ORIGIN);
216
+ if (externalAdminUrl) {
217
+ return c.redirect(externalAdminUrl, 302);
218
+ }
219
+ if (env.ASSETS) {
220
+ return env.ASSETS.fetch(createAdminAssetRequest(c.req.raw));
221
+ }
222
+ return c.json({ code: 404, message: 'Admin dashboard not deployed.' }, 404);
223
+ });
186
224
 
187
- app.get('/favicon.svg', async (c) => {
188
- const externalFaviconUrl = resolveAdminFaviconTarget(c.env.ADMIN_ORIGIN);
189
- if (externalFaviconUrl) {
190
- return c.redirect(externalFaviconUrl, 302);
191
- }
225
+ app.get('/admin', async (c) => {
226
+ const env = c.env as Env;
227
+ const externalAdminUrl = resolveAdminRedirectTarget(c.req.url, env.ADMIN_ORIGIN);
228
+ if (externalAdminUrl) {
229
+ return c.redirect(externalAdminUrl, 302);
230
+ }
231
+ if (env.ASSETS) {
232
+ return env.ASSETS.fetch(createAdminAssetRequest(c.req.raw));
233
+ }
234
+ return c.json({ code: 404, message: 'Admin dashboard not deployed.' }, 404);
235
+ });
192
236
 
193
- if (c.env.ASSETS) {
194
- return c.env.ASSETS.fetch(c.req.raw);
195
- }
237
+ app.get('/harness', (c) => {
238
+ return c.redirect('/harness/', 302);
239
+ });
196
240
 
197
- return c.json({ code: 404, message: 'Admin dashboard not deployed.' }, 404);
198
- });
241
+ app.get('/harness/', async (c) => {
242
+ const env = c.env as Env;
243
+ if (env.ASSETS) {
244
+ return env.ASSETS.fetch(c.req.raw);
245
+ }
246
+ return c.json({ code: 404, message: 'Harness assets not deployed.' }, 404);
247
+ });
199
248
 
200
- app.get('/_app/*', async (c) => {
201
- if (c.env.ASSETS) {
202
- return c.env.ASSETS.fetch(c.req.raw);
203
- }
249
+ app.get('/harness/assets/*', async (c) => {
250
+ const env = c.env as Env;
251
+ if (env.ASSETS) {
252
+ return env.ASSETS.fetch(c.req.raw);
253
+ }
254
+ return c.json({ code: 404, message: 'Harness assets not deployed.' }, 404);
255
+ });
204
256
 
205
- return c.json({ code: 404, message: 'Admin dashboard not deployed.' }, 404);
206
- });
257
+ app.get('/harness/*', (c) => {
258
+ return c.redirect('/harness/', 302);
259
+ });
207
260
 
208
- app.get('/admin/*', async (c) => {
209
- const externalAdminUrl = resolveAdminRedirectTarget(c.req.url, c.env.ADMIN_ORIGIN);
210
- if (externalAdminUrl) {
211
- return c.redirect(externalAdminUrl, 302);
212
- }
213
- if (c.env.ASSETS) {
214
- return c.env.ASSETS.fetch(createAdminAssetRequest(c.req.raw));
215
- }
216
- return c.json({ code: 404, message: 'Admin dashboard not deployed.' }, 404);
217
- });
218
- app.get('/admin', async (c) => {
219
- const externalAdminUrl = resolveAdminRedirectTarget(c.req.url, c.env.ADMIN_ORIGIN);
220
- if (externalAdminUrl) {
221
- return c.redirect(externalAdminUrl, 302);
222
- }
223
- if (c.env.ASSETS) {
224
- return c.env.ASSETS.fetch(createAdminAssetRequest(c.req.raw));
225
- }
226
- return c.json({ code: 404, message: 'Admin dashboard not deployed.' }, 404);
227
- });
228
-
229
- app.get('/harness', (c) => {
230
- return c.redirect('/harness/', 302);
231
- });
232
- app.get('/harness/', async (c) => {
233
- if (c.env.ASSETS) {
234
- return c.env.ASSETS.fetch(c.req.raw);
235
- }
236
- return c.json({ code: 404, message: 'Harness assets not deployed.' }, 404);
237
- });
238
- app.get('/harness/assets/*', async (c) => {
239
- if (c.env.ASSETS) {
240
- return c.env.ASSETS.fetch(c.req.raw);
241
- }
242
- return c.json({ code: 404, message: 'Harness assets not deployed.' }, 404);
243
- });
244
- app.get('/harness/*', (c) => {
245
- return c.redirect('/harness/', 302);
246
- });
247
-
248
- // ─── OpenAPI Spec ───
249
- app.get('/openapi.json', (c) => {
250
- const spec = app.getOpenAPI31Document({
251
- openapi: '3.1.0',
252
- info: { title: 'EdgeBase API', version: SERVER_VERSION },
261
+ app.get('/openapi.json', (c) => {
262
+ const spec = app.getOpenAPI31Document({
263
+ openapi: '3.1.0',
264
+ info: { title: 'EdgeBase API', version: SERVER_VERSION },
265
+ });
266
+
267
+ return c.json(normalizeOpenApiDocument(spec as OpenApiSpec, new URL(c.req.url).origin));
253
268
  });
254
269
 
255
- return c.json(normalizeOpenApiDocument(spec as OpenApiSpec, new URL(c.req.url).origin));
256
- });
257
-
258
- // ─── 404 Fallback ───
259
- app.notFound((c) => {
260
- return c.json({ code: 404, message: 'Not found.' }, 404);
261
- });
262
-
263
- // ─── Hono-level onError (safety net for Workers cross-module instanceof issues) ───
264
- app.onError((err, c) => {
265
- if (err instanceof SyntaxError) {
266
- return c.json(
267
- {
268
- code: 400,
269
- message: 'Invalid JSON payload. Please ensure your request body is valid JSON.',
270
- },
271
- 400,
272
- );
273
- }
274
- if (err instanceof EdgeBaseError) {
275
- return c.json(err.toJSON(), err.code as 400);
276
- }
277
- // Hono HTTPException (thrown by @hono/zod-openapi validators on malformed JSON, etc.)
278
- if (err instanceof HTTPException) {
279
- return c.json({ code: err.status, message: err.message }, err.status as 400);
280
- }
281
- // Duck-type fallback
282
- const e = err as unknown as Record<string, unknown>;
283
- if (
284
- typeof e.code === 'number' &&
285
- e.code >= 400 &&
286
- e.code < 600 &&
287
- typeof e.message === 'string'
288
- ) {
289
- const body: { code: number; message: string; data?: unknown } = {
290
- code: e.code as number,
291
- message: e.message as string,
292
- };
293
- if (e.data) body.data = e.data;
294
- return c.json(body, e.code as number as 400);
270
+ app.notFound((c) => {
271
+ return c.json({ code: 404, message: 'Not found.' }, 404);
272
+ });
273
+
274
+ app.onError((err, c) => {
275
+ if (err instanceof SyntaxError) {
276
+ return c.json(
277
+ {
278
+ code: 400,
279
+ message: 'Invalid JSON payload. Please ensure your request body is valid JSON.',
280
+ },
281
+ 400,
282
+ );
283
+ }
284
+ if (err instanceof EdgeBaseError) {
285
+ return c.json(err.toJSON(), err.code as 400);
286
+ }
287
+ if (err instanceof HTTPException) {
288
+ return c.json({ code: err.status, message: err.message }, err.status as 400);
289
+ }
290
+ const e = err as unknown as Record<string, unknown>;
291
+ if (
292
+ typeof e.code === 'number' &&
293
+ e.code >= 400 &&
294
+ e.code < 600 &&
295
+ typeof e.message === 'string'
296
+ ) {
297
+ const body: { code: number; message: string; data?: unknown } = {
298
+ code: e.code as number,
299
+ message: e.message as string,
300
+ };
301
+ if (e.data) body.data = e.data;
302
+ return c.json(body, e.code as number as 400);
303
+ }
304
+ console.error('Unhandled error:', err);
305
+ return c.json({ code: 500, message: 'Internal server error.' }, 500);
306
+ });
307
+
308
+ return app;
309
+ }
310
+
311
+ async function getApp() {
312
+ if (!appPromise) {
313
+ appPromise = buildApp();
295
314
  }
296
- console.error('Unhandled error:', err);
297
- return c.json({ code: 500, message: 'Internal server error.' }, 500);
298
- });
315
+ return appPromise;
316
+ }
299
317
 
300
318
  export default {
301
- fetch: app.fetch,
319
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
320
+ const app = await getApp();
321
+ return app.fetch(request, env, ctx);
322
+ },
302
323
 
303
- /**
304
- * Cloudflare Cron Triggers — replaces db:_system alarm-based scheduling.
305
- * CLI generates [triggers] section in wrangler.toml from config schedule functions.
306
- */
307
324
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
308
- const config = parseConfig(env);
325
+ await ensureServerStartup();
326
+
327
+ const [
328
+ pluginMigrationsModule,
329
+ functionsModule,
330
+ cronModule,
331
+ jwtModule,
332
+ authServiceModule,
333
+ authD1Module,
334
+ authDbAdapterModule,
335
+ serviceKeyModule,
336
+ doRouterModule,
337
+ ] = await Promise.all([
338
+ import('./lib/plugin-migrations.js'),
339
+ import('./lib/functions.js'),
340
+ import('./lib/cron.js'),
341
+ import('./lib/jwt.js'),
342
+ import('./lib/auth-d1-service.js'),
343
+ import('./lib/auth-d1.js'),
344
+ import('./lib/auth-db-adapter.js'),
345
+ import('./lib/service-key.js'),
346
+ import('./lib/do-router.js'),
347
+ ]);
348
+
349
+ const { executePluginMigrations } = pluginMigrationsModule;
350
+ const { getFunctionsByTrigger, buildFunctionContext, getWorkerUrl } = functionsModule;
351
+ const { parseCron, matchesCron } = cronModule;
352
+ const { parseDuration } = jwtModule;
353
+ const { ensureAuthSchema, deleteAnon } = authD1Module;
354
+ const { resolveAuthDb } = authDbAdapterModule;
355
+ const { resolveRootServiceKey } = serviceKeyModule;
356
+
357
+ const config = doRouterModule.parseConfig(env);
358
+ if (config.plugins?.length) {
359
+ await executePluginMigrations(
360
+ config.plugins,
361
+ env,
362
+ config,
363
+ getWorkerUrl('http://internal/scheduled', env),
364
+ );
365
+ }
309
366
  const scheduleFns = getFunctionsByTrigger('schedule');
310
367
 
311
368
  const now = new Date(event.scheduledTime);
312
-
313
- // Schedule function timeout (default: 10s)
314
369
  const timeoutStr = config.functions?.scheduleFunctionTimeout ?? '10s';
315
370
  const timeoutMs = parseDuration(timeoutStr) * 1000;
316
371
 
317
- // ── System cron: session cleanup + anonymous account cleanup ──
318
372
  ctx.waitUntil(
319
373
  (async () => {
320
374
  try {
321
375
  const authDb = resolveAuthDb(env as unknown as Record<string, unknown>);
322
376
  await ensureAuthSchema(authDb);
323
- // Clean expired sessions
324
- await authService.cleanExpiredSessions(authDb);
325
- // Clean stale anonymous accounts
377
+ await authServiceModule.cleanExpiredSessions(authDb);
326
378
  if (config?.auth?.anonymousAuth) {
327
379
  const retentionDays = config.auth.anonymousRetentionDays ?? 30;
328
- const deletedIds = await authService.cleanStaleAnonymousAccounts(authDb, retentionDays);
380
+ const deletedIds = await authServiceModule.cleanStaleAnonymousAccounts(authDb, retentionDays);
329
381
  for (const id of deletedIds) {
330
382
  await deleteAnon(authDb, id).catch(() => {});
331
383
  }
@@ -345,7 +397,7 @@ export default {
345
397
  if (!matchesCron(now, schedule)) continue;
346
398
 
347
399
  const fnCtx = buildFunctionContext({
348
- request: new Request('http://internal/schedule/' + name),
400
+ request: new Request(`http://internal/schedule/${name}`),
349
401
  auth: null,
350
402
  databaseNamespace: env.DATABASE,
351
403
  authNamespace: env.AUTH,