@ecopages/core 0.2.0-alpha.26 → 0.2.0-alpha.28

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 (106) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +63 -7
  3. package/package.json +8 -94
  4. package/src/adapters/bun/create-app.d.ts +1 -0
  5. package/src/adapters/bun/create-app.js +39 -2
  6. package/src/adapters/bun/hmr-manager.d.ts +1 -13
  7. package/src/adapters/bun/hmr-manager.js +1 -22
  8. package/src/adapters/bun/server-adapter.js +23 -4
  9. package/src/adapters/node/node-hmr-manager.d.ts +2 -14
  10. package/src/adapters/node/node-hmr-manager.js +2 -23
  11. package/src/adapters/shared/explicit-static-render-preparation.d.ts +25 -0
  12. package/src/adapters/shared/explicit-static-render-preparation.js +26 -0
  13. package/src/adapters/shared/explicit-static-route-matcher.d.ts +5 -2
  14. package/src/adapters/shared/explicit-static-route-matcher.js +14 -16
  15. package/src/adapters/shared/file-route-middleware-pipeline.d.ts +7 -10
  16. package/src/adapters/shared/file-route-middleware-pipeline.js +2 -11
  17. package/src/adapters/shared/fs-server-response-factory.d.ts +13 -9
  18. package/src/adapters/shared/fs-server-response-factory.js +10 -26
  19. package/src/adapters/shared/fs-server-response-matcher.d.ts +14 -6
  20. package/src/adapters/shared/fs-server-response-matcher.js +67 -28
  21. package/src/adapters/shared/render-context.d.ts +2 -2
  22. package/src/adapters/shared/server-adapter.d.ts +21 -10
  23. package/src/adapters/shared/server-adapter.js +171 -132
  24. package/src/adapters/shared/server-route-handler.d.ts +2 -2
  25. package/src/adapters/shared/server-route-handler.js +1 -1
  26. package/src/adapters/shared/server-static-builder.d.ts +4 -4
  27. package/src/config/README.md +1 -1
  28. package/src/config/config-builder.d.ts +2 -2
  29. package/src/config/config-builder.js +0 -5
  30. package/src/dev/host-runtime.d.ts +10 -0
  31. package/src/dev/host-runtime.js +24 -0
  32. package/src/eco/eco.js +7 -7
  33. package/src/eco/eco.types.d.ts +3 -3
  34. package/src/errors/index.d.ts +1 -0
  35. package/src/errors/index.js +3 -1
  36. package/src/hmr/strategies/js-hmr-strategy.d.ts +0 -5
  37. package/src/integrations/ghtml/ghtml-renderer.d.ts +0 -4
  38. package/src/integrations/ghtml/ghtml-renderer.js +1 -7
  39. package/src/plugins/eco-component-meta-plugin.js +0 -1
  40. package/src/plugins/integration-plugin.d.ts +14 -18
  41. package/src/plugins/integration-plugin.js +14 -21
  42. package/src/plugins/processor.d.ts +2 -0
  43. package/src/plugins/processor.js +6 -1
  44. package/src/route-renderer/GRAPH.md +81 -289
  45. package/src/route-renderer/README.md +67 -105
  46. package/src/route-renderer/orchestration/component-render-context.d.ts +24 -18
  47. package/src/route-renderer/orchestration/component-render-context.js +14 -14
  48. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +18 -0
  49. package/src/route-renderer/orchestration/declared-ownership-graph.js +34 -0
  50. package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +108 -0
  51. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +206 -0
  52. package/src/route-renderer/orchestration/integration-renderer.d.ts +96 -136
  53. package/src/route-renderer/orchestration/integration-renderer.js +280 -303
  54. package/src/route-renderer/orchestration/ownership-planning.service.d.ts +24 -0
  55. package/src/route-renderer/orchestration/ownership-planning.service.js +63 -0
  56. package/src/route-renderer/orchestration/ownership-validation.service.d.ts +29 -0
  57. package/src/route-renderer/orchestration/ownership-validation.service.js +53 -0
  58. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +90 -0
  59. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.js → queued-foreign-subtree-resolution.service.js} +28 -25
  60. package/src/route-renderer/orchestration/render-output.utils.d.ts +3 -3
  61. package/src/route-renderer/orchestration/render-output.utils.js +6 -6
  62. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +120 -0
  63. package/src/route-renderer/orchestration/{render-preparation.service.js → route-render-orchestrator.js} +132 -108
  64. package/src/route-renderer/page-loading/component-dependency-collection.js +8 -1
  65. package/src/route-renderer/page-loading/dependency-resolver.js +5 -7
  66. package/src/route-renderer/page-loading/page-dependency-bundling.d.ts +1 -1
  67. package/src/route-renderer/page-loading/page-dependency-bundling.js +41 -19
  68. package/src/route-renderer/route-renderer.d.ts +28 -26
  69. package/src/route-renderer/route-renderer.js +4 -27
  70. package/src/router/README.md +16 -19
  71. package/src/router/server/route-registry.d.ts +78 -0
  72. package/src/router/server/route-registry.js +262 -0
  73. package/src/services/README.md +1 -2
  74. package/src/services/assets/asset-processing-service/assets.types.d.ts +3 -0
  75. package/src/services/assets/asset-processing-service/index.d.ts +1 -0
  76. package/src/services/assets/asset-processing-service/index.js +1 -0
  77. package/src/services/assets/asset-processing-service/page-package.d.ts +3 -0
  78. package/src/services/assets/asset-processing-service/page-package.js +74 -0
  79. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +4 -4
  80. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +6 -3
  81. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +9 -3
  82. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +4 -2
  83. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +2 -1
  84. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +3 -1
  85. package/src/services/module-loading/node-bootstrap-plugin.js +15 -3
  86. package/src/static-site-generator/static-site-generator.d.ts +20 -21
  87. package/src/static-site-generator/static-site-generator.js +107 -140
  88. package/src/types/internal-types.d.ts +13 -12
  89. package/src/types/public-types.d.ts +46 -36
  90. package/src/watchers/project-watcher.test-helpers.js +5 -5
  91. package/src/route-renderer/orchestration/boundary-planning.service.d.ts +0 -25
  92. package/src/route-renderer/orchestration/boundary-planning.service.js +0 -97
  93. package/src/route-renderer/orchestration/page-packaging.service.d.ts +0 -16
  94. package/src/route-renderer/orchestration/page-packaging.service.js +0 -66
  95. package/src/route-renderer/orchestration/queued-boundary-runtime.service.d.ts +0 -89
  96. package/src/route-renderer/orchestration/render-execution.service.d.ts +0 -43
  97. package/src/route-renderer/orchestration/render-execution.service.js +0 -106
  98. package/src/route-renderer/orchestration/render-preparation.service.d.ts +0 -120
  99. package/src/route-renderer/orchestration/route-shell-composer.service.d.ts +0 -50
  100. package/src/route-renderer/orchestration/route-shell-composer.service.js +0 -81
  101. package/src/router/server/fs-router-scanner.d.ts +0 -41
  102. package/src/router/server/fs-router-scanner.js +0 -161
  103. package/src/router/server/fs-router.d.ts +0 -26
  104. package/src/router/server/fs-router.js +0 -100
  105. package/src/services/runtime-state/runtime-specifier-registry.service.d.ts +0 -69
  106. package/src/services/runtime-state/runtime-specifier-registry.service.js +0 -37
@@ -1,15 +1,21 @@
1
1
  import { AbstractServerAdapter } from '../abstract/server-adapter.js';
2
2
  import type { ServerAdapterOptions, ServerAdapterResult } from '../abstract/server-adapter.js';
3
3
  import { RouteRendererFactory } from '../../route-renderer/route-renderer.js';
4
- import { FSRouter } from '../../router/server/fs-router.js';
4
+ import { RouteRegistry } from '../../router/server/route-registry.js';
5
5
  import { SchemaValidationService } from '../../services/validation/schema-validation-service.js';
6
6
  import { StaticSiteGenerator } from '../../static-site-generator/static-site-generator.js';
7
7
  import { ServerStaticBuilder } from './server-static-builder.js';
8
8
  import { FileSystemResponseMatcher } from './fs-server-response-matcher.js';
9
9
  import { ServerRouteHandler } from './server-route-handler.js';
10
10
  import type { ApiHandler, CacheInvalidator, ErrorHandler, RenderContext, StaticRoute } from '../../types/public-types.js';
11
+ type SharedRequestContext = {
12
+ apiHandlers: ApiHandler[];
13
+ errorHandler?: ErrorHandler;
14
+ serverInstance?: any;
15
+ hmrManager?: any;
16
+ };
11
17
  export declare abstract class SharedServerAdapter<TOptions extends ServerAdapterOptions, TResult extends ServerAdapterResult> extends AbstractServerAdapter<TOptions, TResult> {
12
- protected router: FSRouter;
18
+ protected router: RouteRegistry;
13
19
  protected fileSystemResponseMatcher: FileSystemResponseMatcher;
14
20
  protected routeRendererFactory: RouteRendererFactory;
15
21
  protected routeHandler: ServerRouteHandler;
@@ -27,14 +33,15 @@ export declare abstract class SharedServerAdapter<TOptions extends ServerAdapter
27
33
  onError?: (error: Error) => Promise<void> | void;
28
34
  }): () => Promise<void>;
29
35
  /**
30
- * Scans the filesystem and dynamically constructs the universal router map.
36
+ * Scans the filesystem and dynamically constructs the Route Registry.
31
37
  *
32
38
  * This process runs identically across both Bun and Node wrappers. It analyzes the configured pages
33
39
  * directory, building a map of all available UI routes and API endpoints.
34
- * The resulting `FSRouter` instance becomes the central nervous system for mapping WinterCG incoming
40
+ * The resulting `RouteRegistry` instance becomes the central nervous system for mapping WinterCG incoming
35
41
  * Web Requests (`Request`) to their corresponding internal execution paths.
36
42
  */
37
43
  protected initSharedRouter(): Promise<void>;
44
+ private createRouteRegistryPageModuleAdapter;
38
45
  /**
39
46
  * Sets up the unified rendering pipeline and response matching chain.
40
47
  *
@@ -50,6 +57,8 @@ export declare abstract class SharedServerAdapter<TOptions extends ServerAdapter
50
57
  * @param hmrManager - The runtime-specific Hot Module Replacement orchestrator (if watching).
51
58
  */
52
59
  protected configureSharedResponseHandlers(staticRoutes: StaticRoute[], hmrManager?: any): void;
60
+ private createSharedResponseHandlerDependencies;
61
+ private createSharedPageCacheService;
53
62
  protected getCacheService(): CacheInvalidator | null;
54
63
  protected getRenderContext(): RenderContext;
55
64
  /**
@@ -70,6 +79,10 @@ export declare abstract class SharedServerAdapter<TOptions extends ServerAdapter
70
79
  * @returns The resulting Web standard `Response` constructed by the user's handler.
71
80
  */
72
81
  protected executeApiHandler(request: Request, params: Record<string, string | string[]>, routeConfig: ApiHandler, serverInstance: any, errorHandler?: ErrorHandler): Promise<Response>;
82
+ private createApiHandlerContext;
83
+ private normalizeApiParams;
84
+ private applyApiRequestSchema;
85
+ private runApiMiddlewareChain;
73
86
  private normalizePath;
74
87
  private matchApiPath;
75
88
  private getApiPathScore;
@@ -77,6 +90,8 @@ export declare abstract class SharedServerAdapter<TOptions extends ServerAdapter
77
90
  routeConfig: ApiHandler;
78
91
  params: Record<string, string | string[]>;
79
92
  } | null;
93
+ private tryHandleSharedHmrRequest;
94
+ private tryHandleSharedApiRequest;
80
95
  /**
81
96
  * Universally processes an incoming WinterCG Web standard Request.
82
97
  *
@@ -88,10 +103,6 @@ export declare abstract class SharedServerAdapter<TOptions extends ServerAdapter
88
103
  * Both Bun and Node bindings fall back to this exact function once they have mapped their
89
104
  * native HTTP objects into Web Standard Requests.
90
105
  */
91
- handleSharedRequest(request: Request, context: {
92
- apiHandlers: ApiHandler[];
93
- errorHandler?: ErrorHandler;
94
- serverInstance?: any;
95
- hmrManager?: any;
96
- }): Promise<Response>;
106
+ handleSharedRequest(request: Request, context: SharedRequestContext): Promise<Response>;
97
107
  }
108
+ export {};
@@ -1,8 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { AbstractServerAdapter } from "../abstract/server-adapter.js";
3
3
  import { RouteRendererFactory } from "../../route-renderer/route-renderer.js";
4
- import { FSRouter } from "../../router/server/fs-router.js";
5
- import { FSRouterScanner } from "../../router/server/fs-router-scanner.js";
4
+ import { RouteRegistry } from "../../router/server/route-registry.js";
6
5
  import { MemoryCacheStore } from "../../services/cache/memory-cache-store.js";
7
6
  import { PageCacheService } from "../../services/cache/page-cache-service.js";
8
7
  import { SchemaValidationService } from "../../services/validation/schema-validation-service.js";
@@ -18,6 +17,8 @@ import { HttpError } from "../../errors/http-error.js";
18
17
  import { ApiResponseBuilder } from "./api-response.js";
19
18
  import { appLogger } from "../../global/app-logger.js";
20
19
  import { fileSystem } from "@ecopages/file-system";
20
+ import { getAppServerModuleTranspiler } from "../../services/module-loading/app-server-module-transpiler.service.js";
21
+ import { resolveInternalExecutionDir } from "../../utils/resolve-work-dir.js";
21
22
  class SharedServerAdapter extends AbstractServerAdapter {
22
23
  router;
23
24
  fileSystemResponseMatcher;
@@ -50,30 +51,43 @@ class SharedServerAdapter extends AbstractServerAdapter {
50
51
  };
51
52
  }
52
53
  /**
53
- * Scans the filesystem and dynamically constructs the universal router map.
54
+ * Scans the filesystem and dynamically constructs the Route Registry.
54
55
  *
55
56
  * This process runs identically across both Bun and Node wrappers. It analyzes the configured pages
56
57
  * directory, building a map of all available UI routes and API endpoints.
57
- * The resulting `FSRouter` instance becomes the central nervous system for mapping WinterCG incoming
58
+ * The resulting `RouteRegistry` instance becomes the central nervous system for mapping WinterCG incoming
58
59
  * Web Requests (`Request`) to their corresponding internal execution paths.
59
60
  */
60
61
  async initSharedRouter() {
61
- const scanner = new FSRouterScanner({
62
- dir: path.join(this.appConfig.rootDir, this.appConfig.srcDir, this.appConfig.pagesDir),
62
+ this.router = new RouteRegistry({
63
+ pagesDir: path.join(this.appConfig.rootDir, this.appConfig.srcDir, this.appConfig.pagesDir),
63
64
  appConfig: this.appConfig,
64
65
  origin: this.runtimeOrigin,
65
66
  templatesExt: this.appConfig.templatesExt,
66
- options: {
67
- buildMode: !this.options?.watch
68
- }
69
- });
70
- this.router = new FSRouter({
71
- origin: this.runtimeOrigin,
72
- assetPrefix: path.join(this.appConfig.rootDir, this.appConfig.distDir),
73
- scanner
67
+ buildMode: !this.options?.watch,
68
+ pageModuleAdapter: this.createRouteRegistryPageModuleAdapter()
74
69
  });
75
70
  await this.router.init();
76
71
  }
72
+ createRouteRegistryPageModuleAdapter() {
73
+ const serverModuleTranspiler = getAppServerModuleTranspiler(this.appConfig);
74
+ return {
75
+ loadPageModule: async (filePath) => {
76
+ const module = await serverModuleTranspiler.importModule({
77
+ filePath,
78
+ outdir: path.join(resolveInternalExecutionDir(this.appConfig), ".server-route-modules"),
79
+ externalPackages: false,
80
+ transpileErrorMessage: (details) => `Error transpiling route module: ${details}`,
81
+ noOutputMessage: (targetFilePath) => `No transpiled output generated for route module: ${targetFilePath}`
82
+ });
83
+ const page = module.default;
84
+ return {
85
+ staticPaths: page?.staticPaths ?? module.getStaticPaths,
86
+ staticProps: page?.staticProps ?? module.getStaticProps
87
+ };
88
+ }
89
+ };
90
+ }
77
91
  /**
78
92
  * Sets up the unified rendering pipeline and response matching chain.
79
93
  *
@@ -94,40 +108,50 @@ class SharedServerAdapter extends AbstractServerAdapter {
94
108
  rendererModules: this.appConfig.runtime?.rendererModuleContext,
95
109
  runtimeOrigin: this.runtimeOrigin
96
110
  });
111
+ const { fileSystemResponseMatcher, explicitStaticRouteMatcher } = this.createSharedResponseHandlerDependencies(staticRoutes);
112
+ this.fileSystemResponseMatcher = fileSystemResponseMatcher;
113
+ this.routeHandler = new ServerRouteHandler({
114
+ router: this.router,
115
+ fileSystemResponseMatcher: this.fileSystemResponseMatcher,
116
+ explicitStaticRouteMatcher,
117
+ watch: !!this.options?.watch,
118
+ hmrManager
119
+ });
120
+ }
121
+ createSharedResponseHandlerDependencies(staticRoutes) {
97
122
  const fileSystemResponseFactory = new FileSystemServerResponseFactory({
98
- appConfig: this.appConfig,
99
- routeRendererFactory: this.routeRendererFactory,
100
123
  options: {
101
124
  watchMode: !!this.options?.watch
102
125
  }
103
126
  });
104
- const cacheConfig = this.appConfig.cache;
105
- const isCacheEnabled = cacheConfig?.enabled ?? !this.options?.watch;
106
- let cacheService = null;
107
- if (isCacheEnabled) {
108
- const store = cacheConfig?.store === "memory" || !cacheConfig?.store ? new MemoryCacheStore({ maxEntries: cacheConfig?.maxEntries }) : cacheConfig.store;
109
- cacheService = new PageCacheService({ store, enabled: true });
110
- }
111
- this.fileSystemResponseMatcher = new FileSystemResponseMatcher({
127
+ const cacheService = this.createSharedPageCacheService();
128
+ const fileSystemResponseMatcher = new FileSystemResponseMatcher({
112
129
  appConfig: this.appConfig,
130
+ assetPrefix: path.join(this.appConfig.rootDir, this.appConfig.distDir),
113
131
  router: this.router,
114
132
  routeRendererFactory: this.routeRendererFactory,
115
133
  fileSystemResponseFactory,
116
134
  cacheService,
117
- defaultCacheStrategy: cacheConfig?.defaultStrategy ?? "static"
118
- });
119
- const explicitStaticRouteMatcher = staticRoutes.length > 0 ? new ExplicitStaticRouteMatcher({
120
- appConfig: this.appConfig,
121
- routeRendererFactory: this.routeRendererFactory,
122
- staticRoutes
123
- }) : void 0;
124
- this.routeHandler = new ServerRouteHandler({
125
- router: this.router,
126
- fileSystemResponseMatcher: this.fileSystemResponseMatcher,
127
- explicitStaticRouteMatcher,
128
- watch: !!this.options?.watch,
129
- hmrManager
135
+ defaultCacheStrategy: this.appConfig.cache?.defaultStrategy ?? "static"
130
136
  });
137
+ return {
138
+ cacheService,
139
+ fileSystemResponseMatcher,
140
+ explicitStaticRouteMatcher: staticRoutes.length > 0 ? new ExplicitStaticRouteMatcher({
141
+ appConfig: this.appConfig,
142
+ routeRendererFactory: this.routeRendererFactory,
143
+ staticRoutes
144
+ }) : void 0
145
+ };
146
+ }
147
+ createSharedPageCacheService() {
148
+ const cacheConfig = this.appConfig.cache;
149
+ const isCacheEnabled = cacheConfig?.enabled ?? !this.options?.watch;
150
+ if (!isCacheEnabled) {
151
+ return null;
152
+ }
153
+ const store = cacheConfig?.store === "memory" || !cacheConfig?.store ? new MemoryCacheStore({ maxEntries: cacheConfig?.maxEntries }) : cacheConfig.store;
154
+ return new PageCacheService({ store, enabled: true });
131
155
  }
132
156
  getCacheService() {
133
157
  return this.fileSystemResponseMatcher?.getCacheService() ?? null;
@@ -158,87 +182,18 @@ class SharedServerAdapter extends AbstractServerAdapter {
158
182
  async executeApiHandler(request, params, routeConfig, serverInstance, errorHandler) {
159
183
  let context;
160
184
  try {
161
- const middleware = routeConfig.middleware || [];
162
- const schema = routeConfig.schema;
163
- const locals = {};
164
- const normalizedParams = Object.fromEntries(
165
- Object.entries(params).map(([key, value]) => [key, Array.isArray(value) ? value.join("/") : value])
166
- );
167
- context = {
168
- request,
169
- params: normalizedParams,
170
- response: new ApiResponseBuilder(),
171
- server: serverInstance,
172
- locals,
173
- require: createRequire(() => locals),
174
- services: {
175
- cache: this.getCacheService()
176
- },
177
- ...this.getRenderContext()
178
- };
179
- if (schema) {
180
- const url = new URL(request.url);
181
- const queryParams = Object.fromEntries(url.searchParams);
182
- const headers = Object.fromEntries(request.headers);
183
- let body;
184
- if (schema.body) {
185
- try {
186
- const contentType = request.headers.get("Content-Type") || "";
187
- if (contentType.includes("application/json")) body = await request.clone().json();
188
- else if (contentType.includes("text/plain")) body = await request.clone().text();
189
- } catch {
190
- return context.response.status(400).json({ error: "Invalid request body" });
191
- }
192
- }
193
- const validationResult = await this.schemaValidator.validateRequest(
194
- { body, query: queryParams, headers, params: normalizedParams },
195
- schema
196
- );
197
- if (!validationResult.success) {
198
- return context.response.status(400).json({
199
- error: "Validation failed",
200
- issues: validationResult.errors
201
- });
202
- }
203
- const validated = validationResult.data;
204
- if (validated.body !== void 0) context.body = validated.body;
205
- if (validated.query !== void 0) context.query = validated.query;
206
- if (validated.headers !== void 0) context.headers = validated.headers;
207
- if (validated.params !== void 0) context.params = validated.params;
185
+ context = this.createApiHandlerContext(request, params, serverInstance);
186
+ const schemaResponse = await this.applyApiRequestSchema(context, routeConfig.schema);
187
+ if (schemaResponse) {
188
+ return schemaResponse;
208
189
  }
209
- if (middleware.length === 0) {
210
- return await routeConfig.handler(context);
211
- }
212
- let index = 0;
213
- const executeNext = async () => {
214
- if (index < middleware.length) {
215
- const currentMiddleware = middleware[index++];
216
- return await currentMiddleware(context, executeNext);
217
- }
218
- return await routeConfig.handler(context);
219
- };
220
- return await executeNext();
190
+ return await this.runApiMiddlewareChain(context, routeConfig);
221
191
  } catch (error) {
222
192
  if (error instanceof Response) return error;
223
193
  if (errorHandler) {
224
194
  try {
225
195
  if (!context) {
226
- const locals = {};
227
- context = {
228
- request,
229
- params: Object.fromEntries(
230
- Object.entries(params).map(([key, value]) => [
231
- key,
232
- Array.isArray(value) ? value.join("/") : value
233
- ])
234
- ),
235
- response: new ApiResponseBuilder(),
236
- server: serverInstance,
237
- locals,
238
- require: createRequire(() => locals),
239
- services: { cache: this.getCacheService() },
240
- ...this.getRenderContext()
241
- };
196
+ context = this.createApiHandlerContext(request, params, serverInstance);
242
197
  }
243
198
  return await errorHandler(error, context);
244
199
  } catch (handlerError) {
@@ -250,6 +205,76 @@ class SharedServerAdapter extends AbstractServerAdapter {
250
205
  return new Response("Internal Server Error", { status: 500 });
251
206
  }
252
207
  }
208
+ createApiHandlerContext(request, params, serverInstance) {
209
+ const locals = {};
210
+ const normalizedParams = this.normalizeApiParams(params);
211
+ return {
212
+ request,
213
+ params: normalizedParams,
214
+ response: new ApiResponseBuilder(),
215
+ server: serverInstance,
216
+ locals,
217
+ require: createRequire(() => locals),
218
+ services: {
219
+ cache: this.getCacheService()
220
+ },
221
+ ...this.getRenderContext()
222
+ };
223
+ }
224
+ normalizeApiParams(params) {
225
+ return Object.fromEntries(
226
+ Object.entries(params).map(([key, value]) => [key, Array.isArray(value) ? value.join("/") : value])
227
+ );
228
+ }
229
+ async applyApiRequestSchema(context, schema) {
230
+ if (!schema) {
231
+ return void 0;
232
+ }
233
+ const url = new URL(context.request.url);
234
+ const queryParams = Object.fromEntries(url.searchParams);
235
+ const headers = Object.fromEntries(context.request.headers);
236
+ let body;
237
+ if (schema.body) {
238
+ try {
239
+ const contentType = context.request.headers.get("Content-Type") || "";
240
+ if (contentType.includes("application/json")) body = await context.request.clone().json();
241
+ else if (contentType.includes("text/plain")) body = await context.request.clone().text();
242
+ } catch {
243
+ return context.response.status(400).json({ error: "Invalid request body" });
244
+ }
245
+ }
246
+ const validationResult = await this.schemaValidator.validateRequest(
247
+ { body, query: queryParams, headers, params: context.params },
248
+ schema
249
+ );
250
+ if (!validationResult.success) {
251
+ return context.response.status(400).json({
252
+ error: "Validation failed",
253
+ issues: validationResult.errors
254
+ });
255
+ }
256
+ const validated = validationResult.data;
257
+ if (validated.body !== void 0) context.body = validated.body;
258
+ if (validated.query !== void 0) context.query = validated.query;
259
+ if (validated.headers !== void 0) context.headers = validated.headers;
260
+ if (validated.params !== void 0) context.params = validated.params;
261
+ return void 0;
262
+ }
263
+ async runApiMiddlewareChain(context, routeConfig) {
264
+ const middleware = routeConfig.middleware || [];
265
+ if (middleware.length === 0) {
266
+ return await routeConfig.handler(context);
267
+ }
268
+ let index = 0;
269
+ const executeNext = async () => {
270
+ if (index < middleware.length) {
271
+ const currentMiddleware = middleware[index++];
272
+ return await currentMiddleware(context, executeNext);
273
+ }
274
+ return await routeConfig.handler(context);
275
+ };
276
+ return await executeNext();
277
+ }
253
278
  normalizePath(pathname) {
254
279
  if (pathname.length > 1 && pathname.endsWith("/")) {
255
280
  return pathname.slice(0, -1);
@@ -342,18 +367,7 @@ class SharedServerAdapter extends AbstractServerAdapter {
342
367
  }
343
368
  return null;
344
369
  }
345
- /**
346
- * Universally processes an incoming WinterCG Web standard Request.
347
- *
348
- * 1. Resolves static Hot Module Replacement runtime blobs if development.
349
- * 2. Checks if the incoming request matches any parsed API route schemas.
350
- * - Routes through `executeApiHandler` which performs strict validation.
351
- * 3. Falls through to standard `ServerRouteHandler` for React/Lit filesystem pages.
352
- *
353
- * Both Bun and Node bindings fall back to this exact function once they have mapped their
354
- * native HTTP objects into Web Standard Requests.
355
- */
356
- async handleSharedRequest(request, context) {
370
+ tryHandleSharedHmrRequest(request, context) {
357
371
  const url = new URL(request.url);
358
372
  if (url.pathname === "/_hmr_runtime.js" && context.hmrManager) {
359
373
  const runtimePath = context.hmrManager.getRuntimePath();
@@ -372,15 +386,40 @@ class SharedServerAdapter extends AbstractServerAdapter {
372
386
  });
373
387
  }
374
388
  }
389
+ return null;
390
+ }
391
+ async tryHandleSharedApiRequest(request, context) {
375
392
  const apiMatch = this.matchApiHandler(request, context.apiHandlers);
376
- if (apiMatch) {
377
- return this.executeApiHandler(
378
- request,
379
- apiMatch.params,
380
- apiMatch.routeConfig,
381
- context.serverInstance,
382
- context.errorHandler
383
- );
393
+ if (!apiMatch) {
394
+ return null;
395
+ }
396
+ return await this.executeApiHandler(
397
+ request,
398
+ apiMatch.params,
399
+ apiMatch.routeConfig,
400
+ context.serverInstance,
401
+ context.errorHandler
402
+ );
403
+ }
404
+ /**
405
+ * Universally processes an incoming WinterCG Web standard Request.
406
+ *
407
+ * 1. Resolves static Hot Module Replacement runtime blobs if development.
408
+ * 2. Checks if the incoming request matches any parsed API route schemas.
409
+ * - Routes through `executeApiHandler` which performs strict validation.
410
+ * 3. Falls through to standard `ServerRouteHandler` for React/Lit filesystem pages.
411
+ *
412
+ * Both Bun and Node bindings fall back to this exact function once they have mapped their
413
+ * native HTTP objects into Web Standard Requests.
414
+ */
415
+ async handleSharedRequest(request, context) {
416
+ const hmrResponse = this.tryHandleSharedHmrRequest(request, context);
417
+ if (hmrResponse) {
418
+ return hmrResponse;
419
+ }
420
+ const apiResponse = await this.tryHandleSharedApiRequest(request, context);
421
+ if (apiResponse) {
422
+ return apiResponse;
384
423
  }
385
424
  return this.routeHandler.handleResponse(request);
386
425
  }
@@ -1,5 +1,5 @@
1
1
  import type { IHmrManager } from '../../types/public-types.js';
2
- import type { FSRouter } from '../../router/server/fs-router.js';
2
+ import type { RouteRegistry } from '../../router/server/route-registry.js';
3
3
  import type { ExplicitStaticRouteMatcher } from './explicit-static-route-matcher.js';
4
4
  import type { FileSystemResponseMatcher } from './fs-server-response-matcher.js';
5
5
  /**
@@ -7,7 +7,7 @@ import type { FileSystemResponseMatcher } from './fs-server-response-matcher.js'
7
7
  */
8
8
  export interface ServerRouteHandlerParams {
9
9
  /** File system router for matching request URLs to route handlers. */
10
- router: FSRouter;
10
+ router: RouteRegistry;
11
11
  /** Matcher for handling file system route responses. */
12
12
  fileSystemResponseMatcher: FileSystemResponseMatcher;
13
13
  /** Optional matcher for explicit static routes like processed images or sitemaps. */
@@ -60,7 +60,7 @@ class ServerRouteHandler {
60
60
  const response2 = await this.explicitStaticRouteMatcher.handleMatch(explicitMatch);
61
61
  return this.maybeInjectHmrScript(response2);
62
62
  }
63
- const fsMatch = !pathname.includes(".") && this.router.match(request.url);
63
+ const fsMatch = !pathname.includes(".") && this.router.matchRequest(request.url);
64
64
  const response = await (fsMatch ? this.fileSystemResponseMatcher.handleMatch(fsMatch, request) : this.handleNoMatch(request));
65
65
  return this.maybeInjectHmrScript(response);
66
66
  }
@@ -1,8 +1,8 @@
1
1
  import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
2
  import type { ApiHandler, StaticRoute } from '../../types/public-types.js';
3
- import type { RouteRendererFactory } from '../../route-renderer/route-renderer.js';
4
- import type { FSRouter } from '../../router/server/fs-router.js';
3
+ import type { RouteRegistry } from '../../router/server/route-registry.js';
5
4
  import type { StaticSiteGenerator } from '../../static-site-generator/static-site-generator.js';
5
+ import type { StaticGenerationRendererResolver } from '../../route-renderer/route-renderer.js';
6
6
  export interface StaticBuildOptions {
7
7
  preview?: boolean;
8
8
  baseUrl?: string;
@@ -64,8 +64,8 @@ export declare class ServerStaticBuilder {
64
64
  * @param dependencies.staticRoutes - Explicit static routes registered via app.static()
65
65
  */
66
66
  build(options: StaticBuildOptions | undefined, dependencies: {
67
- router: FSRouter;
68
- routeRendererFactory: RouteRendererFactory;
67
+ router: RouteRegistry;
68
+ routeRendererFactory: StaticGenerationRendererResolver;
69
69
  staticRoutes?: StaticRoute[];
70
70
  }): Promise<void>;
71
71
  }
@@ -12,7 +12,7 @@ It is responsible for:
12
12
 
13
13
  - validating integration, processor, and loader registration
14
14
  - resolving semantic paths such as `html` and `404` templates
15
- - selecting explicit build ownership and creating app-owned runtime state such as the build adapter, build executor, build manifest, dev graph service, runtime specifier registry, and remaining compatibility-only runtime state
15
+ - selecting explicit build ownership and creating app-owned runtime state such as the build adapter, build executor, build manifest, dev graph service, and remaining compatibility-only runtime state
16
16
  - enforcing runtime capability requirements before startup
17
17
  - carrying host-injected runtime dependencies only through abstract slots such as host module loaders, never through bundler-specific core defaults
18
18
 
@@ -5,7 +5,7 @@
5
5
  import { type BuildOwnership } from '../build/build-adapter.js';
6
6
  import type { EcoBuildPlugin } from '../build/build-types.js';
7
7
  import type { EcoPagesAppConfig, RobotsPreference } from '../types/internal-types.js';
8
- import type { IntegrationPlugin } from '../plugins/integration-plugin.js';
8
+ import type { AnyIntegrationPlugin } from '../plugins/integration-plugin.js';
9
9
  import type { Processor } from '../plugins/processor.js';
10
10
  import type { EcoSourceTransform } from '../plugins/source-transform.js';
11
11
  import type { PageMetadataProps } from '../types/public-types.js';
@@ -136,7 +136,7 @@ export declare class ConfigBuilder {
136
136
  * @param integrations - An array of integration plugins
137
137
  * @returns The ConfigBuilder instance for method chaining
138
138
  */
139
- setIntegrations(integrations: IntegrationPlugin<unknown>[]): this;
139
+ setIntegrations(integrations: AnyIntegrationPlugin[]): this;
140
140
  /**
141
141
  * Sets the output directory for the built application.
142
142
  *
@@ -22,10 +22,6 @@ import {
22
22
  NoopEntrypointDependencyGraph,
23
23
  setAppEntrypointDependencyGraph
24
24
  } from "../services/runtime-state/entrypoint-dependency-graph.service.js";
25
- import {
26
- InMemoryRuntimeSpecifierRegistry,
27
- setAppRuntimeSpecifierRegistry
28
- } from "../services/runtime-state/runtime-specifier-registry.service.js";
29
25
  import {
30
26
  CounterServerInvalidationState,
31
27
  setAppServerInvalidationState
@@ -585,7 +581,6 @@ class ConfigBuilder {
585
581
  updateAppBuildManifest(this.config, await collectConfiguredAppBuildManifestContributions(this.config));
586
582
  setAppServerInvalidationState(this.config, new CounterServerInvalidationState());
587
583
  setAppEntrypointDependencyGraph(this.config, new NoopEntrypointDependencyGraph());
588
- setAppRuntimeSpecifierRegistry(this.config, new InMemoryRuntimeSpecifierRegistry());
589
584
  setAppBuildExecutor(
590
585
  this.config,
591
586
  createAppBuildExecutor({
@@ -0,0 +1,10 @@
1
+ import type { EcoPagesAppConfig } from '../types/public-types.js';
2
+ import { type DevelopmentInvalidationPlan } from '../services/invalidation/development-invalidation.service.js';
3
+ export type HostRuntimeModuleLoader = (id: string) => Promise<unknown>;
4
+ export interface DevelopmentHostRuntime {
5
+ registerHostModuleLoader(loader: HostRuntimeModuleLoader): void;
6
+ planFileChange(filePath: string): DevelopmentInvalidationPlan;
7
+ invalidateServerModules(changedFiles?: string[]): void;
8
+ resetRuntimeState(changedFiles?: string[]): void;
9
+ }
10
+ export declare function createDevelopmentHostRuntime(appConfig: EcoPagesAppConfig): DevelopmentHostRuntime;
@@ -0,0 +1,24 @@
1
+ import {
2
+ DevelopmentInvalidationService
3
+ } from "../services/invalidation/development-invalidation.service.js";
4
+ import { setHostModuleLoader } from "../services/module-loading/host-module-loader-registry.js";
5
+ function createDevelopmentHostRuntime(appConfig) {
6
+ const invalidationService = new DevelopmentInvalidationService(appConfig);
7
+ return {
8
+ registerHostModuleLoader(loader) {
9
+ setHostModuleLoader(loader);
10
+ },
11
+ planFileChange(filePath) {
12
+ return invalidationService.planFileChange(filePath);
13
+ },
14
+ invalidateServerModules(changedFiles) {
15
+ invalidationService.invalidateServerModules(changedFiles);
16
+ },
17
+ resetRuntimeState(changedFiles) {
18
+ invalidationService.resetRuntimeState(changedFiles);
19
+ }
20
+ };
21
+ }
22
+ export {
23
+ createDevelopmentHostRuntime
24
+ };
package/src/eco/eco.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  finalizeComponentRender,
3
- interceptComponentBoundary
3
+ interceptForeignChild
4
4
  } from "../route-renderer/orchestration/component-render-context.js";
5
5
  import { isThenable } from "../route-renderer/orchestration/render-output.utils.js";
6
6
  function createComponentFactory(options) {
@@ -8,18 +8,18 @@ function createComponentFactory(options) {
8
8
  const comp = ((props) => {
9
9
  const componentProps = props ?? {};
10
10
  const renderInline = () => finalizeComponentRender(comp, options.render(props));
11
- const boundaryRender = interceptComponentBoundary({
11
+ const foreignChildRender = interceptForeignChild({
12
12
  component: comp,
13
13
  props: componentProps,
14
14
  targetIntegration: integrationName
15
15
  });
16
- if (isThenable(boundaryRender)) {
17
- return boundaryRender.then(
18
- (resolvedBoundaryRender) => resolvedBoundaryRender !== void 0 ? resolvedBoundaryRender : renderInline()
16
+ if (isThenable(foreignChildRender)) {
17
+ return foreignChildRender.then(
18
+ (resolvedForeignChildRender) => resolvedForeignChildRender !== void 0 ? resolvedForeignChildRender : renderInline()
19
19
  );
20
20
  }
21
- if (boundaryRender !== void 0) {
22
- return boundaryRender;
21
+ if (foreignChildRender !== void 0) {
22
+ return foreignChildRender;
23
23
  }
24
24
  return renderInline();
25
25
  });
@@ -2,7 +2,7 @@
2
2
  * Type definitions for the eco namespace API
3
3
  * @module
4
4
  */
5
- import type { DependencyLazyTrigger, EcoComponent, EcoComponentDependencies, EcoHtmlComponent, EcoInjectedMeta, EcoLayoutComponent, EcoPageLayoutComponent, EcoPagesElement, GetMetadata, GetStaticPaths, GetStaticProps, HtmlTemplateProps, LayoutProps, Middleware, RequestLocals, RequestPageContext } from '../types/public-types.js';
5
+ import type { DependencyLazyTrigger, EcoComponent, EcoComponentDependencies, EcoHtmlComponent, EcoInjectedMeta, EcoLayoutComponent, EcoPageLayoutComponent, EcoPagesElement, FileRouteMiddleware, GetMetadata, GetStaticPaths, GetStaticProps, HtmlTemplateProps, LayoutProps, RequestLocals, RequestPageContext } from '../types/public-types.js';
6
6
  import type { CacheStrategy } from '../services/cache/cache.types.js';
7
7
  type WithRequiredLocals<K extends keyof RequestLocals> = Omit<RequestLocals, K> & {
8
8
  [P in K]-?: Exclude<RequestLocals[P], null | undefined>;
@@ -86,7 +86,7 @@ interface PageOptionsWithMiddleware<T, E = EcoPagesElement> extends PageOptionsB
86
86
  * Request-time middleware for file-based routes.
87
87
  * Runs before rendering and can short-circuit by returning a Response.
88
88
  */
89
- middleware: Middleware[];
89
+ middleware: FileRouteMiddleware[];
90
90
  }
91
91
  /**
92
92
  * Options for creating a page with eco.page()
@@ -127,7 +127,7 @@ export type EcoPageComponent<T> = EcoComponent<PagePropsFor<T> & Partial<Request
127
127
  metadata?: GetMetadata<T>;
128
128
  cache?: CacheStrategy;
129
129
  requires?: PageRequires;
130
- middleware?: Middleware[];
130
+ middleware?: FileRouteMiddleware[];
131
131
  };
132
132
  /**
133
133
  * The eco namespace interface