@edge-base/server 0.2.4 → 0.2.6

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