@ecopages/core 0.2.0-alpha.25 → 0.2.0-alpha.27

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 (111) hide show
  1. package/README.md +63 -7
  2. package/package.json +4 -47
  3. package/src/adapters/bun/create-app.ts +54 -2
  4. package/src/adapters/bun/hmr-manager.test.ts +0 -2
  5. package/src/adapters/bun/hmr-manager.ts +1 -24
  6. package/src/adapters/bun/server-adapter.ts +30 -4
  7. package/src/adapters/node/node-hmr-manager.test.ts +0 -2
  8. package/src/adapters/node/node-hmr-manager.ts +2 -25
  9. package/src/adapters/shared/explicit-static-render-preparation.ts +58 -0
  10. package/src/adapters/shared/explicit-static-route-matcher.test.ts +6 -6
  11. package/src/adapters/shared/explicit-static-route-matcher.ts +22 -31
  12. package/src/adapters/shared/file-route-middleware-pipeline.test.ts +5 -10
  13. package/src/adapters/shared/file-route-middleware-pipeline.ts +8 -17
  14. package/src/adapters/shared/fs-server-response-factory.test.ts +32 -43
  15. package/src/adapters/shared/fs-server-response-factory.ts +15 -37
  16. package/src/adapters/shared/fs-server-response-matcher.test.ts +65 -39
  17. package/src/adapters/shared/fs-server-response-matcher.ts +94 -43
  18. package/src/adapters/shared/hmr-manager.contract.test.ts +0 -4
  19. package/src/adapters/shared/render-context.ts +3 -3
  20. package/src/adapters/shared/server-adapter.test.ts +53 -0
  21. package/src/adapters/shared/server-adapter.ts +228 -159
  22. package/src/adapters/shared/server-route-handler.test.ts +6 -5
  23. package/src/adapters/shared/server-route-handler.ts +4 -4
  24. package/src/adapters/shared/server-static-builder.test.ts +4 -4
  25. package/src/adapters/shared/server-static-builder.ts +4 -4
  26. package/src/config/README.md +1 -1
  27. package/src/config/config-builder.test.ts +0 -1
  28. package/src/config/config-builder.ts +2 -7
  29. package/src/dev/host-runtime.ts +34 -0
  30. package/src/eco/eco.browser.test.ts +2 -2
  31. package/src/eco/eco.browser.ts +2 -2
  32. package/src/eco/eco.test.ts +6 -6
  33. package/src/eco/eco.ts +12 -12
  34. package/src/eco/eco.types.ts +3 -3
  35. package/src/errors/index.ts +1 -0
  36. package/src/hmr/client/hmr-runtime.ts +4 -2
  37. package/src/hmr/strategies/js-hmr-strategy.test.ts +0 -1
  38. package/src/hmr/strategies/js-hmr-strategy.ts +0 -6
  39. package/src/integrations/ghtml/ghtml-renderer.test.ts +7 -7
  40. package/src/integrations/ghtml/ghtml-renderer.ts +1 -11
  41. package/src/plugins/eco-component-meta-plugin.ts +0 -1
  42. package/src/plugins/integration-plugin.test.ts +9 -14
  43. package/src/plugins/integration-plugin.ts +34 -22
  44. package/src/plugins/processor.ts +17 -0
  45. package/src/route-renderer/GRAPH.md +81 -289
  46. package/src/route-renderer/README.md +67 -105
  47. package/src/route-renderer/orchestration/component-render-context.ts +45 -38
  48. package/src/route-renderer/orchestration/declared-ownership-graph.ts +62 -0
  49. package/src/route-renderer/orchestration/foreign-subtree-execution.service.ts +383 -0
  50. package/src/route-renderer/orchestration/integration-renderer.test.ts +118 -121
  51. package/src/route-renderer/orchestration/integration-renderer.ts +362 -403
  52. package/src/route-renderer/orchestration/ownership-planning.service.ts +97 -0
  53. package/src/route-renderer/orchestration/ownership-validation.service.ts +76 -0
  54. package/src/route-renderer/orchestration/processed-asset-dedupe.ts +1 -1
  55. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.test.ts → queued-foreign-subtree-resolution.service.test.ts} +76 -71
  56. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.ts → queued-foreign-subtree-resolution.service.ts} +68 -63
  57. package/src/route-renderer/orchestration/render-output.utils.ts +21 -13
  58. package/src/route-renderer/orchestration/{render-preparation.service.test.ts → route-render-orchestrator.prepare-render-options.test.ts} +160 -85
  59. package/src/route-renderer/orchestration/route-render-orchestrator.test.ts +265 -0
  60. package/src/route-renderer/orchestration/{render-preparation.service.ts → route-render-orchestrator.ts} +244 -160
  61. package/src/route-renderer/page-loading/component-dependency-collection.ts +9 -3
  62. package/src/route-renderer/page-loading/declared-asset-collection.ts +2 -5
  63. package/src/route-renderer/page-loading/dependency-resolver.test.ts +107 -11
  64. package/src/route-renderer/page-loading/dependency-resolver.ts +6 -12
  65. package/src/route-renderer/page-loading/ecopages-virtual-imports.ts +1 -1
  66. package/src/route-renderer/page-loading/lazy-entry-collection.ts +1 -1
  67. package/src/route-renderer/page-loading/lazy-trigger-planning.ts +1 -1
  68. package/src/route-renderer/page-loading/module-declaration-aggregation.ts +1 -1
  69. package/src/route-renderer/page-loading/module-declaration-scripts.ts +1 -1
  70. package/src/route-renderer/page-loading/page-dependency-bundling.ts +105 -66
  71. package/src/route-renderer/route-renderer.ts +28 -31
  72. package/src/router/README.md +16 -19
  73. package/src/router/server/route-registry.test.ts +176 -0
  74. package/src/router/server/route-registry.ts +382 -0
  75. package/src/services/README.md +1 -2
  76. package/src/services/assets/asset-processing-service/asset-dependency-keys.ts +1 -1
  77. package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +1 -4
  78. package/src/services/assets/asset-processing-service/asset-processing.service.ts +1 -2
  79. package/src/services/assets/asset-processing-service/assets.types.ts +3 -0
  80. package/src/services/assets/asset-processing-service/grouped-content-bundles.ts +1 -1
  81. package/src/services/assets/asset-processing-service/index.ts +1 -0
  82. package/src/{route-renderer/orchestration/page-packaging.service.test.ts → services/assets/asset-processing-service/page-package.test.ts} +38 -14
  83. package/src/services/assets/asset-processing-service/page-package.ts +93 -0
  84. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.ts +4 -5
  85. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.test.ts +13 -10
  86. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.ts +3 -0
  87. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.ts +6 -0
  88. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.ts +2 -0
  89. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +1 -0
  90. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +2 -0
  91. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.ts +1 -1
  92. package/src/services/html/html-transformer.service.test.ts +1 -4
  93. package/src/services/module-loading/app-server-module-transpiler.service.ts +1 -3
  94. package/src/services/module-loading/node-bootstrap-plugin.ts +17 -3
  95. package/src/services/module-loading/page-module-import.service.ts +0 -1
  96. package/src/services/module-loading/source-module-support.ts +1 -1
  97. package/src/static-site-generator/static-site-generator.test.ts +124 -32
  98. package/src/static-site-generator/static-site-generator.ts +168 -185
  99. package/src/types/internal-types.ts +13 -12
  100. package/src/types/public-types.ts +55 -39
  101. package/src/watchers/project-watcher.test-helpers.ts +4 -3
  102. package/src/route-renderer/orchestration/boundary-planning.service.ts +0 -146
  103. package/src/route-renderer/orchestration/page-packaging.service.ts +0 -85
  104. package/src/route-renderer/orchestration/render-execution.service.test.ts +0 -196
  105. package/src/route-renderer/orchestration/render-execution.service.ts +0 -182
  106. package/src/route-renderer/orchestration/route-shell-composer.service.ts +0 -162
  107. package/src/router/server/fs-router-scanner.test.ts +0 -83
  108. package/src/router/server/fs-router-scanner.ts +0 -224
  109. package/src/router/server/fs-router.test.ts +0 -214
  110. package/src/router/server/fs-router.ts +0 -122
  111. package/src/services/runtime-state/runtime-specifier-registry.service.ts +0 -96
@@ -1,22 +1,33 @@
1
1
  import path from 'node:path';
2
2
  import { appLogger } from '../../global/app-logger.ts';
3
3
  import type { EcoPagesAppConfig, MatchResult } from '../../types/internal-types.ts';
4
- import type { RouteRendererFactory } from '../../route-renderer/route-renderer.ts';
5
- import type { FSRouter } from '../../router/server/fs-router.ts';
4
+ import type { PageRendererResolver } from '../../route-renderer/route-renderer.ts';
5
+ import type { RouteRegistry } from '../../router/server/route-registry.ts';
6
6
  import type { PageCacheService } from '../../services/cache/page-cache-service.ts';
7
7
  import type { CacheStrategy, RenderResult } from '../../services/cache/cache.types.ts';
8
8
  import { PageRequestCacheCoordinator } from '../../services/cache/page-request-cache-coordinator.service.ts';
9
9
  import { ServerUtils } from '../../utils/server-utils.module.ts';
10
- import type { Middleware, RequestLocals } from '../../types/public-types.ts';
10
+ import type { FileRouteMiddleware, RequestLocals } from '../../types/public-types.ts';
11
11
  import { FileRouteMiddlewarePipeline } from './file-route-middleware-pipeline.ts';
12
12
  import { LocalsAccessError } from '../../errors/locals-access-error.ts';
13
13
  import { isDevelopmentRuntime } from '../../utils/runtime.ts';
14
14
  import type { FileSystemServerResponseFactory } from './fs-server-response-factory.ts';
15
15
 
16
+ type FileRouteExecutionPlan = {
17
+ cacheKey: string;
18
+ request: Request;
19
+ pageFilePath: string;
20
+ pageMiddleware: FileRouteMiddleware[];
21
+ pageCacheStrategy: CacheStrategy;
22
+ localsStore: RequestLocals;
23
+ localsForRender: RequestLocals | undefined;
24
+ };
25
+
16
26
  export interface FileSystemResponseMatcherOptions {
17
27
  appConfig: EcoPagesAppConfig;
18
- router: FSRouter;
19
- routeRendererFactory: RouteRendererFactory;
28
+ assetPrefix: string;
29
+ router: RouteRegistry;
30
+ routeRendererFactory: PageRendererResolver;
20
31
  fileSystemResponseFactory: FileSystemServerResponseFactory;
21
32
  /** Optional cache service. When null, caching is disabled. */
22
33
  cacheService?: PageCacheService | null;
@@ -27,20 +38,22 @@ export interface FileSystemResponseMatcherOptions {
27
38
  /**
28
39
  * Matches file-system routes to rendered HTML responses.
29
40
  *
30
- * render pipeline. It coordinates page module inspection, request-local policy,
41
+ * This render pipeline coordinates page module inspection, request-local policy,
31
42
  * renderer invocation, middleware execution, cache integration, and fallback
32
43
  * error translation.
33
44
  */
34
45
  export class FileSystemResponseMatcher {
35
46
  private appConfig: EcoPagesAppConfig;
36
- private router: FSRouter;
37
- private routeRendererFactory: RouteRendererFactory;
47
+ private assetPrefix: string;
48
+ private router: RouteRegistry;
49
+ private routeRendererFactory: PageRendererResolver;
38
50
  private fileSystemResponseFactory: FileSystemServerResponseFactory;
39
51
  private pageRequestCacheCoordinator: PageRequestCacheCoordinator;
40
52
  private fileRouteMiddlewarePipeline: FileRouteMiddlewarePipeline;
41
53
 
42
54
  constructor({
43
55
  appConfig,
56
+ assetPrefix,
44
57
  router,
45
58
  routeRendererFactory,
46
59
  fileSystemResponseFactory,
@@ -48,6 +61,7 @@ export class FileSystemResponseMatcher {
48
61
  defaultCacheStrategy = 'static',
49
62
  }: FileSystemResponseMatcherOptions) {
50
63
  this.appConfig = appConfig;
64
+ this.assetPrefix = assetPrefix;
51
65
  this.router = router;
52
66
  this.routeRendererFactory = routeRendererFactory;
53
67
  this.fileSystemResponseFactory = fileSystemResponseFactory;
@@ -65,14 +79,15 @@ export class FileSystemResponseMatcher {
65
79
  const isStaticFileRequest = ServerUtils.hasKnownExtension(requestUrl);
66
80
 
67
81
  if (!isStaticFileRequest) {
68
- return this.fileSystemResponseFactory.createCustomNotFoundResponse();
82
+ return this.renderCustomNotFoundResponse();
69
83
  }
70
84
 
71
85
  const relativeUrl = requestUrl.startsWith('/') ? requestUrl.slice(1) : requestUrl;
72
- const filePath = path.join(this.router.assetPrefix, relativeUrl);
86
+ const filePath = path.join(this.assetPrefix, relativeUrl);
73
87
  const contentType = ServerUtils.getContentType(filePath);
74
88
 
75
- return this.fileSystemResponseFactory.createFileResponse(filePath, contentType);
89
+ const response = await this.fileSystemResponseFactory.createFileResponse(filePath, contentType);
90
+ return response ?? this.renderCustomNotFoundResponse();
76
91
  }
77
92
 
78
93
  /**
@@ -87,44 +102,28 @@ export class FileSystemResponseMatcher {
87
102
  * @returns Final response for the matched route.
88
103
  */
89
104
  async handleMatch(match: MatchResult, request?: Request): Promise<Response> {
90
- const cacheKey = this.pageRequestCacheCoordinator.buildCacheKey(match);
91
-
92
105
  try {
93
- const resolvedRequest =
94
- request ??
95
- new Request(new URL(cacheKey, this.router.origin).toString(), {
96
- method: 'GET',
97
- });
98
-
99
- const localsStore: RequestLocals = {};
100
- const pageModule = await this.importPageModule(match.filePath);
101
- const Page = (pageModule as any)?.default;
102
- const pageMiddleware = (Page?.middleware ?? []) as Middleware[];
103
- const pageCacheStrategy =
104
- (Page?.cache as CacheStrategy | undefined) ??
105
- this.pageRequestCacheCoordinator.getDefaultCacheStrategy();
106
- const localsForRender: RequestLocals | undefined =
107
- pageCacheStrategy === 'dynamic' ? localsStore : undefined;
106
+ const executionPlan = await this.createExecutionPlan(match, request);
108
107
 
109
108
  this.fileRouteMiddlewarePipeline.assertValidConfiguration({
110
- middleware: pageMiddleware,
111
- pageCacheStrategy,
112
- filePath: match.filePath,
109
+ middleware: executionPlan.pageMiddleware,
110
+ pageCacheStrategy: executionPlan.pageCacheStrategy,
111
+ filePath: executionPlan.pageFilePath,
113
112
  });
114
113
 
115
- const routeRenderer = this.routeRendererFactory.createRenderer(match.filePath);
114
+ const routeRenderer = this.routeRendererFactory.getPageRenderer(executionPlan.pageFilePath);
116
115
  const middlewareContext = this.fileRouteMiddlewarePipeline.createContext({
117
- request: resolvedRequest,
116
+ request: executionPlan.request,
118
117
  params: match.params as Record<string, string>,
119
- locals: localsStore,
118
+ locals: executionPlan.localsStore,
120
119
  });
121
120
 
122
121
  const renderFn = async (): Promise<RenderResult> => {
123
- const result = await routeRenderer.createRoute({
124
- file: match.filePath,
122
+ const result = await routeRenderer.execute({
123
+ file: executionPlan.pageFilePath,
125
124
  params: match.params,
126
125
  query: match.query,
127
- locals: localsForRender,
126
+ locals: executionPlan.localsForRender,
128
127
  });
129
128
  const html = await this.pageRequestCacheCoordinator.bodyToString(result.body);
130
129
  const strategy = result.cacheStrategy ?? this.pageRequestCacheCoordinator.getDefaultCacheStrategy();
@@ -132,14 +131,14 @@ export class FileSystemResponseMatcher {
132
131
  };
133
132
  const renderResponse = async (): Promise<Response> => {
134
133
  return this.pageRequestCacheCoordinator.render({
135
- cacheKey,
136
- pageCacheStrategy,
134
+ cacheKey: executionPlan.cacheKey,
135
+ pageCacheStrategy: executionPlan.pageCacheStrategy,
137
136
  renderFn,
138
137
  });
139
138
  };
140
139
 
141
140
  return await this.fileRouteMiddlewarePipeline.run({
142
- middleware: pageMiddleware,
141
+ middleware: executionPlan.pageMiddleware,
143
142
  context: middlewareContext,
144
143
  renderResponse,
145
144
  });
@@ -155,13 +154,65 @@ export class FileSystemResponseMatcher {
155
154
  }
156
155
  if (error instanceof Error) {
157
156
  if (isDevelopmentRuntime() || appLogger.isDebugEnabled()) {
158
- appLogger.error(`[FileSystemResponseMatcher] ${error.message} at ${match.pathname}`);
157
+ appLogger.error(`[FileSystemResponseMatcher] ${error.message} at ${match.requestedPathname}`);
159
158
  }
160
159
  }
161
- return this.fileSystemResponseFactory.createCustomNotFoundResponse();
160
+ return this.renderCustomNotFoundResponse();
162
161
  }
163
162
  }
164
163
 
164
+ /**
165
+ * Renders the app-owned custom 404 page, falling back to the default text 404
166
+ * when the page template cannot be resolved.
167
+ */
168
+ private async renderCustomNotFoundResponse(): Promise<Response> {
169
+ const error404TemplatePath = this.appConfig.absolutePaths.error404TemplatePath;
170
+
171
+ try {
172
+ const routeRenderer = this.routeRendererFactory.getPageRenderer(error404TemplatePath);
173
+ const result = await routeRenderer.execute({
174
+ file: error404TemplatePath,
175
+ });
176
+
177
+ return this.fileSystemResponseFactory.createHtmlNotFoundResponse(result.body);
178
+ } catch {
179
+ appLogger.debug(
180
+ 'Custom 404 template not found, falling back to default 404 response',
181
+ error404TemplatePath,
182
+ );
183
+ return this.fileSystemResponseFactory.createDefaultNotFoundResponse();
184
+ }
185
+ }
186
+
187
+ private async createExecutionPlan(match: MatchResult, request?: Request): Promise<FileRouteExecutionPlan> {
188
+ const cacheKey = this.pageRequestCacheCoordinator.buildCacheKey({
189
+ pathname: match.requestedPathname,
190
+ query: match.query,
191
+ });
192
+ const resolvedRequest =
193
+ request ??
194
+ new Request(new URL(cacheKey, this.router.origin).toString(), {
195
+ method: 'GET',
196
+ });
197
+ const localsStore: RequestLocals = {};
198
+ const pageFilePath = match.templateRoute.filePath;
199
+ const pageModule = await this.importPageModule(pageFilePath);
200
+ const Page = (pageModule as any)?.default;
201
+ const pageMiddleware = (Page?.middleware ?? []) as FileRouteMiddleware[];
202
+ const pageCacheStrategy =
203
+ (Page?.cache as CacheStrategy | undefined) ?? this.pageRequestCacheCoordinator.getDefaultCacheStrategy();
204
+
205
+ return {
206
+ cacheKey,
207
+ request: resolvedRequest,
208
+ pageFilePath,
209
+ pageMiddleware,
210
+ pageCacheStrategy,
211
+ localsStore,
212
+ localsForRender: pageCacheStrategy === 'dynamic' ? localsStore : undefined,
213
+ };
214
+ }
215
+
165
216
  /**
166
217
  * Loads the matched page module for request-time inspection.
167
218
  *
@@ -174,7 +225,7 @@ export class FileSystemResponseMatcher {
174
225
  * @returns Imported page module.
175
226
  */
176
227
  private async importPageModule(filePath: string): Promise<unknown> {
177
- const routeRenderer = this.routeRendererFactory.createRenderer(filePath);
228
+ const routeRenderer = this.routeRendererFactory.getPageRenderer(filePath);
178
229
  return routeRenderer.loadPageModule(filePath, {
179
230
  cacheScope: 'request-metadata',
180
231
  });
@@ -12,9 +12,7 @@ type SharedHmrManager = {
12
12
  handleFileChange(filePath: string, options?: { broadcast?: boolean }): Promise<void>;
13
13
  registerEntrypoint(entrypointPath: string): Promise<string>;
14
14
  registerScriptEntrypoint(entrypointPath: string): Promise<string>;
15
- registerSpecifierMap(map: Record<string, string>): void;
16
15
  getWatchedFiles(): Map<string, string>;
17
- getSpecifierMap(): Map<string, string>;
18
16
  stop(): void;
19
17
  appConfig: Awaited<ReturnType<ConfigBuilder['build']>>;
20
18
  };
@@ -221,12 +219,10 @@ describe.each(runtimes)('shared HMR manager contract: $name', ({ create }) => {
221
219
  fs.writeFileSync(outputPath, 'export default 1;', 'utf8');
222
220
  });
223
221
 
224
- manager.registerSpecifierMap({ react: '/assets/vendors/react.js' });
225
222
  await manager.registerEntrypoint(entrypointPath);
226
223
 
227
224
  manager.stop();
228
225
 
229
226
  assert.equal(manager.getWatchedFiles().size, 0);
230
- assert.equal(manager.getSpecifierMap().size, 0);
231
227
  });
232
228
  });
@@ -3,11 +3,11 @@ import type {
3
3
  IntegrationRenderer,
4
4
  RenderToResponseContext,
5
5
  } from '../../route-renderer/orchestration/integration-renderer.ts';
6
- import type { IntegrationPlugin } from '../../plugins/integration-plugin.ts';
6
+ import type { AnyIntegrationPlugin } from '../../plugins/integration-plugin.ts';
7
7
  import { invariant } from '../../utils/invariant.ts';
8
8
 
9
9
  export interface CreateRenderContextOptions {
10
- integrations: IntegrationPlugin[];
10
+ integrations: AnyIntegrationPlugin[];
11
11
  rendererModules?: unknown;
12
12
  }
13
13
 
@@ -54,7 +54,7 @@ export function createRenderContext(options: CreateRenderContextOptions): Render
54
54
 
55
55
  return integration.initializeRenderer({
56
56
  rendererModules: options.rendererModules,
57
- });
57
+ }) as IntegrationRenderer;
58
58
  };
59
59
 
60
60
  const renderContext: RenderContext = {
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { afterEach, test } from 'vitest';
6
6
  import { ConfigBuilder } from '../../config/config-builder.ts';
7
7
  import type { ServerAdapterResult } from '../abstract/server-adapter.ts';
8
+ import type { ApiHandler } from '../../types/public-types.ts';
8
9
  import { SharedServerAdapter } from './server-adapter.ts';
9
10
 
10
11
  const tempRoots: string[] = [];
@@ -43,6 +44,18 @@ class TestSharedServerAdapter extends SharedServerAdapter<any, ServerAdapterResu
43
44
  });
44
45
  }
45
46
 
47
+ public async handleSharedRequestForTest(request: Request, apiHandlers: ApiHandler[]): Promise<Response> {
48
+ return await this.handleSharedRequest(request, {
49
+ apiHandlers,
50
+ });
51
+ }
52
+
53
+ public setRouteHandlerForTest(handleResponse: (request: Request) => Promise<Response>): void {
54
+ this.routeHandler = {
55
+ handleResponse,
56
+ } as any;
57
+ }
58
+
46
59
  constructor(
47
60
  private readonly hmrDir: string,
48
61
  rootDir: string,
@@ -75,3 +88,43 @@ test('SharedServerAdapter serves /assets/_hmr files from the HMR manager directo
75
88
  false,
76
89
  );
77
90
  });
91
+
92
+ test('SharedServerAdapter dispatches matching API handlers before filesystem routes', async () => {
93
+ const rootDir = createTempRoot('ecopages-shared-server-api-dispatch');
94
+ const adapter = new TestSharedServerAdapter('', rootDir);
95
+ adapter.setRouteHandlerForTest(async () => new Response('filesystem'));
96
+
97
+ const response = await adapter.handleSharedRequestForTest(
98
+ new Request('http://localhost/api/posts/123', { method: 'GET' }),
99
+ [
100
+ {
101
+ path: '/api/posts/[id]',
102
+ method: 'GET',
103
+ handler: ({ params }) => new Response(`api:${params.id}`),
104
+ },
105
+ ],
106
+ );
107
+
108
+ assert.equal(response.status, 200);
109
+ assert.equal(await response.text(), 'api:123');
110
+ });
111
+
112
+ test('SharedServerAdapter falls back to the route handler when no API handler matches', async () => {
113
+ const rootDir = createTempRoot('ecopages-shared-server-route-fallback');
114
+ const adapter = new TestSharedServerAdapter('', rootDir);
115
+ adapter.setRouteHandlerForTest(async () => new Response('filesystem'));
116
+
117
+ const response = await adapter.handleSharedRequestForTest(
118
+ new Request('http://localhost/blog/post-1', { method: 'GET' }),
119
+ [
120
+ {
121
+ path: '/api/posts/[id]',
122
+ method: 'GET',
123
+ handler: () => new Response('api'),
124
+ },
125
+ ],
126
+ );
127
+
128
+ assert.equal(response.status, 200);
129
+ assert.equal(await response.text(), 'filesystem');
130
+ });