@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.
- package/README.md +63 -7
- package/package.json +4 -47
- package/src/adapters/bun/create-app.ts +54 -2
- package/src/adapters/bun/hmr-manager.test.ts +0 -2
- package/src/adapters/bun/hmr-manager.ts +1 -24
- package/src/adapters/bun/server-adapter.ts +30 -4
- package/src/adapters/node/node-hmr-manager.test.ts +0 -2
- package/src/adapters/node/node-hmr-manager.ts +2 -25
- package/src/adapters/shared/explicit-static-render-preparation.ts +58 -0
- package/src/adapters/shared/explicit-static-route-matcher.test.ts +6 -6
- package/src/adapters/shared/explicit-static-route-matcher.ts +22 -31
- package/src/adapters/shared/file-route-middleware-pipeline.test.ts +5 -10
- package/src/adapters/shared/file-route-middleware-pipeline.ts +8 -17
- package/src/adapters/shared/fs-server-response-factory.test.ts +32 -43
- package/src/adapters/shared/fs-server-response-factory.ts +15 -37
- package/src/adapters/shared/fs-server-response-matcher.test.ts +65 -39
- package/src/adapters/shared/fs-server-response-matcher.ts +94 -43
- package/src/adapters/shared/hmr-manager.contract.test.ts +0 -4
- package/src/adapters/shared/render-context.ts +3 -3
- package/src/adapters/shared/server-adapter.test.ts +53 -0
- package/src/adapters/shared/server-adapter.ts +228 -159
- package/src/adapters/shared/server-route-handler.test.ts +6 -5
- package/src/adapters/shared/server-route-handler.ts +4 -4
- package/src/adapters/shared/server-static-builder.test.ts +4 -4
- package/src/adapters/shared/server-static-builder.ts +4 -4
- package/src/config/README.md +1 -1
- package/src/config/config-builder.test.ts +0 -1
- package/src/config/config-builder.ts +2 -7
- package/src/dev/host-runtime.ts +34 -0
- package/src/eco/eco.browser.test.ts +2 -2
- package/src/eco/eco.browser.ts +2 -2
- package/src/eco/eco.test.ts +6 -6
- package/src/eco/eco.ts +12 -12
- package/src/eco/eco.types.ts +3 -3
- package/src/errors/index.ts +1 -0
- package/src/hmr/client/hmr-runtime.ts +4 -2
- package/src/hmr/strategies/js-hmr-strategy.test.ts +0 -1
- package/src/hmr/strategies/js-hmr-strategy.ts +0 -6
- package/src/integrations/ghtml/ghtml-renderer.test.ts +7 -7
- package/src/integrations/ghtml/ghtml-renderer.ts +1 -11
- package/src/plugins/eco-component-meta-plugin.ts +0 -1
- package/src/plugins/integration-plugin.test.ts +9 -14
- package/src/plugins/integration-plugin.ts +34 -22
- package/src/plugins/processor.ts +17 -0
- package/src/route-renderer/GRAPH.md +81 -289
- package/src/route-renderer/README.md +67 -105
- package/src/route-renderer/orchestration/component-render-context.ts +45 -38
- package/src/route-renderer/orchestration/declared-ownership-graph.ts +62 -0
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.ts +383 -0
- package/src/route-renderer/orchestration/integration-renderer.test.ts +118 -121
- package/src/route-renderer/orchestration/integration-renderer.ts +362 -403
- package/src/route-renderer/orchestration/ownership-planning.service.ts +97 -0
- package/src/route-renderer/orchestration/ownership-validation.service.ts +76 -0
- package/src/route-renderer/orchestration/processed-asset-dedupe.ts +1 -1
- package/src/route-renderer/orchestration/{queued-boundary-runtime.service.test.ts → queued-foreign-subtree-resolution.service.test.ts} +76 -71
- package/src/route-renderer/orchestration/{queued-boundary-runtime.service.ts → queued-foreign-subtree-resolution.service.ts} +68 -63
- package/src/route-renderer/orchestration/render-output.utils.ts +21 -13
- package/src/route-renderer/orchestration/{render-preparation.service.test.ts → route-render-orchestrator.prepare-render-options.test.ts} +160 -85
- package/src/route-renderer/orchestration/route-render-orchestrator.test.ts +265 -0
- package/src/route-renderer/orchestration/{render-preparation.service.ts → route-render-orchestrator.ts} +244 -160
- package/src/route-renderer/page-loading/component-dependency-collection.ts +9 -3
- package/src/route-renderer/page-loading/declared-asset-collection.ts +2 -5
- package/src/route-renderer/page-loading/dependency-resolver.test.ts +107 -11
- package/src/route-renderer/page-loading/dependency-resolver.ts +6 -12
- package/src/route-renderer/page-loading/ecopages-virtual-imports.ts +1 -1
- package/src/route-renderer/page-loading/lazy-entry-collection.ts +1 -1
- package/src/route-renderer/page-loading/lazy-trigger-planning.ts +1 -1
- package/src/route-renderer/page-loading/module-declaration-aggregation.ts +1 -1
- package/src/route-renderer/page-loading/module-declaration-scripts.ts +1 -1
- package/src/route-renderer/page-loading/page-dependency-bundling.ts +105 -66
- package/src/route-renderer/route-renderer.ts +28 -31
- package/src/router/README.md +16 -19
- package/src/router/server/route-registry.test.ts +176 -0
- package/src/router/server/route-registry.ts +382 -0
- package/src/services/README.md +1 -2
- package/src/services/assets/asset-processing-service/asset-dependency-keys.ts +1 -1
- package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +1 -4
- package/src/services/assets/asset-processing-service/asset-processing.service.ts +1 -2
- package/src/services/assets/asset-processing-service/assets.types.ts +3 -0
- package/src/services/assets/asset-processing-service/grouped-content-bundles.ts +1 -1
- package/src/services/assets/asset-processing-service/index.ts +1 -0
- package/src/{route-renderer/orchestration/page-packaging.service.test.ts → services/assets/asset-processing-service/page-package.test.ts} +38 -14
- package/src/services/assets/asset-processing-service/page-package.ts +93 -0
- package/src/services/assets/asset-processing-service/processors/base/base-script-processor.ts +4 -5
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.test.ts +13 -10
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.ts +3 -0
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.ts +6 -0
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.ts +2 -0
- package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +1 -0
- package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +2 -0
- package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.ts +1 -1
- package/src/services/html/html-transformer.service.test.ts +1 -4
- package/src/services/module-loading/app-server-module-transpiler.service.ts +1 -3
- package/src/services/module-loading/node-bootstrap-plugin.ts +17 -3
- package/src/services/module-loading/page-module-import.service.ts +0 -1
- package/src/services/module-loading/source-module-support.ts +1 -1
- package/src/static-site-generator/static-site-generator.test.ts +124 -32
- package/src/static-site-generator/static-site-generator.ts +168 -185
- package/src/types/internal-types.ts +13 -12
- package/src/types/public-types.ts +55 -39
- package/src/watchers/project-watcher.test-helpers.ts +4 -3
- package/src/route-renderer/orchestration/boundary-planning.service.ts +0 -146
- package/src/route-renderer/orchestration/page-packaging.service.ts +0 -85
- package/src/route-renderer/orchestration/render-execution.service.test.ts +0 -196
- package/src/route-renderer/orchestration/render-execution.service.ts +0 -182
- package/src/route-renderer/orchestration/route-shell-composer.service.ts +0 -162
- package/src/router/server/fs-router-scanner.test.ts +0 -83
- package/src/router/server/fs-router-scanner.ts +0 -224
- package/src/router/server/fs-router.test.ts +0 -214
- package/src/router/server/fs-router.ts +0 -122
- package/src/services/runtime-state/runtime-specifier-registry.service.ts +0 -96
|
@@ -2,8 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { AbstractServerAdapter } from '../abstract/server-adapter.ts';
|
|
3
3
|
import type { ServerAdapterOptions, ServerAdapterResult } from '../abstract/server-adapter.ts';
|
|
4
4
|
import { RouteRendererFactory } from '../../route-renderer/route-renderer.ts';
|
|
5
|
-
import {
|
|
6
|
-
import { FSRouterScanner } from '../../router/server/fs-router-scanner.ts';
|
|
5
|
+
import { RouteRegistry } from '../../router/server/route-registry.ts';
|
|
7
6
|
import { MemoryCacheStore } from '../../services/cache/memory-cache-store.ts';
|
|
8
7
|
import { PageCacheService } from '../../services/cache/page-cache-service.ts';
|
|
9
8
|
import { SchemaValidationService } from '../../services/validation/schema-validation-service.ts';
|
|
@@ -19,6 +18,10 @@ import { HttpError } from '../../errors/http-error.ts';
|
|
|
19
18
|
import { ApiResponseBuilder } from './api-response.ts';
|
|
20
19
|
import { appLogger } from '../../global/app-logger.ts';
|
|
21
20
|
import { fileSystem } from '@ecopages/file-system';
|
|
21
|
+
import type { EcoPageFile } from '../../types/public-types.ts';
|
|
22
|
+
import { getAppServerModuleTranspiler } from '../../services/module-loading/app-server-module-transpiler.service.ts';
|
|
23
|
+
import { resolveInternalExecutionDir } from '../../utils/resolve-work-dir.ts';
|
|
24
|
+
import type { RouteRegistryPageModuleAdapter } from '../../router/server/route-registry.ts';
|
|
22
25
|
import type {
|
|
23
26
|
ApiHandler,
|
|
24
27
|
ApiHandlerContext,
|
|
@@ -28,11 +31,24 @@ import type {
|
|
|
28
31
|
StaticRoute,
|
|
29
32
|
} from '../../types/public-types.ts';
|
|
30
33
|
|
|
34
|
+
type SharedResponseHandlerDependencies = {
|
|
35
|
+
cacheService: PageCacheService | null;
|
|
36
|
+
fileSystemResponseMatcher: FileSystemResponseMatcher;
|
|
37
|
+
explicitStaticRouteMatcher?: ExplicitStaticRouteMatcher;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type SharedRequestContext = {
|
|
41
|
+
apiHandlers: ApiHandler[];
|
|
42
|
+
errorHandler?: ErrorHandler;
|
|
43
|
+
serverInstance?: any;
|
|
44
|
+
hmrManager?: any;
|
|
45
|
+
};
|
|
46
|
+
|
|
31
47
|
export abstract class SharedServerAdapter<
|
|
32
48
|
TOptions extends ServerAdapterOptions,
|
|
33
49
|
TResult extends ServerAdapterResult,
|
|
34
50
|
> extends AbstractServerAdapter<TOptions, TResult> {
|
|
35
|
-
protected router!:
|
|
51
|
+
protected router!: RouteRegistry;
|
|
36
52
|
protected fileSystemResponseMatcher!: FileSystemResponseMatcher;
|
|
37
53
|
protected routeRendererFactory!: RouteRendererFactory;
|
|
38
54
|
protected routeHandler!: ServerRouteHandler;
|
|
@@ -76,33 +92,50 @@ export abstract class SharedServerAdapter<
|
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
/**
|
|
79
|
-
* Scans the filesystem and dynamically constructs the
|
|
95
|
+
* Scans the filesystem and dynamically constructs the Route Registry.
|
|
80
96
|
*
|
|
81
97
|
* This process runs identically across both Bun and Node wrappers. It analyzes the configured pages
|
|
82
98
|
* directory, building a map of all available UI routes and API endpoints.
|
|
83
|
-
* The resulting `
|
|
99
|
+
* The resulting `RouteRegistry` instance becomes the central nervous system for mapping WinterCG incoming
|
|
84
100
|
* Web Requests (`Request`) to their corresponding internal execution paths.
|
|
85
101
|
*/
|
|
86
102
|
protected async initSharedRouter(): Promise<void> {
|
|
87
|
-
|
|
88
|
-
|
|
103
|
+
this.router = new RouteRegistry({
|
|
104
|
+
pagesDir: path.join(this.appConfig.rootDir, this.appConfig.srcDir, this.appConfig.pagesDir),
|
|
89
105
|
appConfig: this.appConfig,
|
|
90
106
|
origin: this.runtimeOrigin,
|
|
91
107
|
templatesExt: this.appConfig.templatesExt,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
this.router = new FSRouter({
|
|
98
|
-
origin: this.runtimeOrigin,
|
|
99
|
-
assetPrefix: path.join(this.appConfig.rootDir, this.appConfig.distDir),
|
|
100
|
-
scanner,
|
|
108
|
+
buildMode: !this.options?.watch,
|
|
109
|
+
pageModuleAdapter: this.createRouteRegistryPageModuleAdapter(),
|
|
101
110
|
});
|
|
102
111
|
|
|
103
112
|
await this.router.init();
|
|
104
113
|
}
|
|
105
114
|
|
|
115
|
+
private createRouteRegistryPageModuleAdapter(): RouteRegistryPageModuleAdapter {
|
|
116
|
+
const serverModuleTranspiler = getAppServerModuleTranspiler(this.appConfig);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
loadPageModule: async (filePath) => {
|
|
120
|
+
const module = (await serverModuleTranspiler.importModule({
|
|
121
|
+
filePath,
|
|
122
|
+
outdir: path.join(resolveInternalExecutionDir(this.appConfig), '.server-route-modules'),
|
|
123
|
+
externalPackages: false,
|
|
124
|
+
transpileErrorMessage: (details) => `Error transpiling route module: ${details}`,
|
|
125
|
+
noOutputMessage: (targetFilePath) =>
|
|
126
|
+
`No transpiled output generated for route module: ${targetFilePath}`,
|
|
127
|
+
})) as EcoPageFile;
|
|
128
|
+
|
|
129
|
+
const page = module.default;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
staticPaths: page?.staticPaths ?? module.getStaticPaths,
|
|
133
|
+
staticProps: page?.staticProps ?? module.getStaticProps,
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
106
139
|
/**
|
|
107
140
|
* Sets up the unified rendering pipeline and response matching chain.
|
|
108
141
|
*
|
|
@@ -124,51 +157,63 @@ export abstract class SharedServerAdapter<
|
|
|
124
157
|
runtimeOrigin: this.runtimeOrigin,
|
|
125
158
|
});
|
|
126
159
|
|
|
160
|
+
const { fileSystemResponseMatcher, explicitStaticRouteMatcher } =
|
|
161
|
+
this.createSharedResponseHandlerDependencies(staticRoutes);
|
|
162
|
+
|
|
163
|
+
this.fileSystemResponseMatcher = fileSystemResponseMatcher;
|
|
164
|
+
this.routeHandler = new ServerRouteHandler({
|
|
165
|
+
router: this.router,
|
|
166
|
+
fileSystemResponseMatcher: this.fileSystemResponseMatcher,
|
|
167
|
+
explicitStaticRouteMatcher,
|
|
168
|
+
watch: !!this.options?.watch,
|
|
169
|
+
hmrManager,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private createSharedResponseHandlerDependencies(staticRoutes: StaticRoute[]): SharedResponseHandlerDependencies {
|
|
127
174
|
const fileSystemResponseFactory = new FileSystemServerResponseFactory({
|
|
128
|
-
appConfig: this.appConfig,
|
|
129
|
-
routeRendererFactory: this.routeRendererFactory,
|
|
130
175
|
options: {
|
|
131
176
|
watchMode: !!this.options?.watch,
|
|
132
177
|
},
|
|
133
178
|
});
|
|
134
179
|
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
let cacheService: PageCacheService | null = null;
|
|
138
|
-
|
|
139
|
-
if (isCacheEnabled) {
|
|
140
|
-
const store =
|
|
141
|
-
cacheConfig?.store === 'memory' || !cacheConfig?.store
|
|
142
|
-
? new MemoryCacheStore({ maxEntries: cacheConfig?.maxEntries })
|
|
143
|
-
: cacheConfig.store;
|
|
144
|
-
cacheService = new PageCacheService({ store, enabled: true });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
this.fileSystemResponseMatcher = new FileSystemResponseMatcher({
|
|
180
|
+
const cacheService = this.createSharedPageCacheService();
|
|
181
|
+
const fileSystemResponseMatcher = new FileSystemResponseMatcher({
|
|
148
182
|
appConfig: this.appConfig,
|
|
183
|
+
assetPrefix: path.join(this.appConfig.rootDir, this.appConfig.distDir),
|
|
149
184
|
router: this.router,
|
|
150
185
|
routeRendererFactory: this.routeRendererFactory,
|
|
151
186
|
fileSystemResponseFactory,
|
|
152
187
|
cacheService,
|
|
153
|
-
defaultCacheStrategy:
|
|
188
|
+
defaultCacheStrategy: this.appConfig.cache?.defaultStrategy ?? 'static',
|
|
154
189
|
});
|
|
155
190
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
191
|
+
return {
|
|
192
|
+
cacheService,
|
|
193
|
+
fileSystemResponseMatcher,
|
|
194
|
+
explicitStaticRouteMatcher:
|
|
195
|
+
staticRoutes.length > 0
|
|
196
|
+
? new ExplicitStaticRouteMatcher({
|
|
197
|
+
appConfig: this.appConfig,
|
|
198
|
+
routeRendererFactory: this.routeRendererFactory,
|
|
199
|
+
staticRoutes,
|
|
200
|
+
})
|
|
201
|
+
: undefined,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
164
204
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
205
|
+
private createSharedPageCacheService(): PageCacheService | null {
|
|
206
|
+
const cacheConfig = this.appConfig.cache;
|
|
207
|
+
const isCacheEnabled = cacheConfig?.enabled ?? !this.options?.watch;
|
|
208
|
+
if (!isCacheEnabled) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const store =
|
|
213
|
+
cacheConfig?.store === 'memory' || !cacheConfig?.store
|
|
214
|
+
? new MemoryCacheStore({ maxEntries: cacheConfig?.maxEntries })
|
|
215
|
+
: cacheConfig.store;
|
|
216
|
+
return new PageCacheService({ store, enabled: true });
|
|
172
217
|
}
|
|
173
218
|
|
|
174
219
|
protected getCacheService(): CacheInvalidator | null {
|
|
@@ -209,98 +254,20 @@ export abstract class SharedServerAdapter<
|
|
|
209
254
|
let context: ApiHandlerContext<Request, any> | undefined;
|
|
210
255
|
|
|
211
256
|
try {
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const normalizedParams = Object.fromEntries(
|
|
217
|
-
Object.entries(params).map(([key, value]) => [key, Array.isArray(value) ? value.join('/') : value]),
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
context = {
|
|
221
|
-
request,
|
|
222
|
-
params: normalizedParams,
|
|
223
|
-
response: new ApiResponseBuilder(),
|
|
224
|
-
server: serverInstance,
|
|
225
|
-
locals,
|
|
226
|
-
require: createRequire((): Record<string, unknown> => locals),
|
|
227
|
-
services: {
|
|
228
|
-
cache: this.getCacheService(),
|
|
229
|
-
},
|
|
230
|
-
...this.getRenderContext(),
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
if (schema) {
|
|
234
|
-
const url = new URL(request.url);
|
|
235
|
-
const queryParams = Object.fromEntries(url.searchParams);
|
|
236
|
-
const headers = Object.fromEntries(request.headers);
|
|
237
|
-
|
|
238
|
-
let body: unknown;
|
|
239
|
-
if (schema.body) {
|
|
240
|
-
try {
|
|
241
|
-
const contentType = request.headers.get('Content-Type') || '';
|
|
242
|
-
if (contentType.includes('application/json')) body = await request.clone().json();
|
|
243
|
-
else if (contentType.includes('text/plain')) body = await request.clone().text();
|
|
244
|
-
} catch {
|
|
245
|
-
return context.response.status(400).json({ error: 'Invalid request body' });
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const validationResult = await this.schemaValidator.validateRequest(
|
|
250
|
-
{ body, query: queryParams, headers, params: normalizedParams },
|
|
251
|
-
schema,
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
if (!validationResult.success) {
|
|
255
|
-
return context.response.status(400).json({
|
|
256
|
-
error: 'Validation failed',
|
|
257
|
-
issues: validationResult.errors,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const validated = validationResult.data!;
|
|
262
|
-
if (validated.body !== undefined) context.body = validated.body;
|
|
263
|
-
if (validated.query !== undefined) context.query = validated.query;
|
|
264
|
-
if (validated.headers !== undefined) context.headers = validated.headers;
|
|
265
|
-
if (validated.params !== undefined) context.params = validated.params as Record<string, string>;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (middleware.length === 0) {
|
|
269
|
-
return await routeConfig.handler(context);
|
|
257
|
+
context = this.createApiHandlerContext(request, params, serverInstance);
|
|
258
|
+
const schemaResponse = await this.applyApiRequestSchema(context, routeConfig.schema);
|
|
259
|
+
if (schemaResponse) {
|
|
260
|
+
return schemaResponse;
|
|
270
261
|
}
|
|
271
262
|
|
|
272
|
-
|
|
273
|
-
const executeNext = async (): Promise<Response> => {
|
|
274
|
-
if (index < middleware.length) {
|
|
275
|
-
const currentMiddleware = middleware[index++];
|
|
276
|
-
return await currentMiddleware(context!, executeNext);
|
|
277
|
-
}
|
|
278
|
-
return await routeConfig.handler(context!);
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
return await executeNext();
|
|
263
|
+
return await this.runApiMiddlewareChain(context, routeConfig);
|
|
282
264
|
} catch (error) {
|
|
283
265
|
if (error instanceof Response) return error;
|
|
284
266
|
|
|
285
267
|
if (errorHandler) {
|
|
286
268
|
try {
|
|
287
269
|
if (!context) {
|
|
288
|
-
|
|
289
|
-
context = {
|
|
290
|
-
request,
|
|
291
|
-
params: Object.fromEntries(
|
|
292
|
-
Object.entries(params).map(([key, value]) => [
|
|
293
|
-
key,
|
|
294
|
-
Array.isArray(value) ? value.join('/') : value,
|
|
295
|
-
]),
|
|
296
|
-
),
|
|
297
|
-
response: new ApiResponseBuilder(),
|
|
298
|
-
server: serverInstance,
|
|
299
|
-
locals,
|
|
300
|
-
require: createRequire((): Record<string, unknown> => locals),
|
|
301
|
-
services: { cache: this.getCacheService() },
|
|
302
|
-
...this.getRenderContext(),
|
|
303
|
-
};
|
|
270
|
+
context = this.createApiHandlerContext(request, params, serverInstance);
|
|
304
271
|
}
|
|
305
272
|
return await errorHandler(error, context);
|
|
306
273
|
} catch (handlerError) {
|
|
@@ -314,6 +281,98 @@ export abstract class SharedServerAdapter<
|
|
|
314
281
|
}
|
|
315
282
|
}
|
|
316
283
|
|
|
284
|
+
private createApiHandlerContext(
|
|
285
|
+
request: Request,
|
|
286
|
+
params: Record<string, string | string[]>,
|
|
287
|
+
serverInstance: any,
|
|
288
|
+
): ApiHandlerContext<Request, any> {
|
|
289
|
+
const locals: Record<string, unknown> = {};
|
|
290
|
+
const normalizedParams = this.normalizeApiParams(params);
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
request,
|
|
294
|
+
params: normalizedParams,
|
|
295
|
+
response: new ApiResponseBuilder(),
|
|
296
|
+
server: serverInstance,
|
|
297
|
+
locals,
|
|
298
|
+
require: createRequire((): Record<string, unknown> => locals),
|
|
299
|
+
services: {
|
|
300
|
+
cache: this.getCacheService(),
|
|
301
|
+
},
|
|
302
|
+
...this.getRenderContext(),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private normalizeApiParams(params: Record<string, string | string[]>): Record<string, string> {
|
|
307
|
+
return Object.fromEntries(
|
|
308
|
+
Object.entries(params).map(([key, value]) => [key, Array.isArray(value) ? value.join('/') : value]),
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private async applyApiRequestSchema(
|
|
313
|
+
context: ApiHandlerContext<Request, any>,
|
|
314
|
+
schema: ApiHandler['schema'],
|
|
315
|
+
): Promise<Response | undefined> {
|
|
316
|
+
if (!schema) {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const url = new URL(context.request.url);
|
|
321
|
+
const queryParams = Object.fromEntries(url.searchParams);
|
|
322
|
+
const headers = Object.fromEntries(context.request.headers);
|
|
323
|
+
|
|
324
|
+
let body: unknown;
|
|
325
|
+
if (schema.body) {
|
|
326
|
+
try {
|
|
327
|
+
const contentType = context.request.headers.get('Content-Type') || '';
|
|
328
|
+
if (contentType.includes('application/json')) body = await context.request.clone().json();
|
|
329
|
+
else if (contentType.includes('text/plain')) body = await context.request.clone().text();
|
|
330
|
+
} catch {
|
|
331
|
+
return context.response.status(400).json({ error: 'Invalid request body' });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const validationResult = await this.schemaValidator.validateRequest(
|
|
336
|
+
{ body, query: queryParams, headers, params: context.params },
|
|
337
|
+
schema,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
if (!validationResult.success) {
|
|
341
|
+
return context.response.status(400).json({
|
|
342
|
+
error: 'Validation failed',
|
|
343
|
+
issues: validationResult.errors,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const validated = validationResult.data!;
|
|
348
|
+
if (validated.body !== undefined) context.body = validated.body;
|
|
349
|
+
if (validated.query !== undefined) context.query = validated.query;
|
|
350
|
+
if (validated.headers !== undefined) context.headers = validated.headers;
|
|
351
|
+
if (validated.params !== undefined) context.params = validated.params as Record<string, string>;
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private async runApiMiddlewareChain(
|
|
356
|
+
context: ApiHandlerContext<Request, any>,
|
|
357
|
+
routeConfig: ApiHandler,
|
|
358
|
+
): Promise<Response> {
|
|
359
|
+
const middleware = routeConfig.middleware || [];
|
|
360
|
+
if (middleware.length === 0) {
|
|
361
|
+
return await routeConfig.handler(context);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let index = 0;
|
|
365
|
+
const executeNext = async (): Promise<Response> => {
|
|
366
|
+
if (index < middleware.length) {
|
|
367
|
+
const currentMiddleware = middleware[index++];
|
|
368
|
+
return await currentMiddleware(context, executeNext);
|
|
369
|
+
}
|
|
370
|
+
return await routeConfig.handler(context);
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
return await executeNext();
|
|
374
|
+
}
|
|
375
|
+
|
|
317
376
|
private normalizePath(pathname: string): string {
|
|
318
377
|
if (pathname.length > 1 && pathname.endsWith('/')) {
|
|
319
378
|
return pathname.slice(0, -1);
|
|
@@ -435,26 +494,7 @@ export abstract class SharedServerAdapter<
|
|
|
435
494
|
return null;
|
|
436
495
|
}
|
|
437
496
|
|
|
438
|
-
|
|
439
|
-
* Universally processes an incoming WinterCG Web standard Request.
|
|
440
|
-
*
|
|
441
|
-
* 1. Resolves static Hot Module Replacement runtime blobs if development.
|
|
442
|
-
* 2. Checks if the incoming request matches any parsed API route schemas.
|
|
443
|
-
* - Routes through `executeApiHandler` which performs strict validation.
|
|
444
|
-
* 3. Falls through to standard `ServerRouteHandler` for React/Lit filesystem pages.
|
|
445
|
-
*
|
|
446
|
-
* Both Bun and Node bindings fall back to this exact function once they have mapped their
|
|
447
|
-
* native HTTP objects into Web Standard Requests.
|
|
448
|
-
*/
|
|
449
|
-
public async handleSharedRequest(
|
|
450
|
-
request: Request,
|
|
451
|
-
context: {
|
|
452
|
-
apiHandlers: ApiHandler[];
|
|
453
|
-
errorHandler?: ErrorHandler;
|
|
454
|
-
serverInstance?: any;
|
|
455
|
-
hmrManager?: any;
|
|
456
|
-
},
|
|
457
|
-
): Promise<Response> {
|
|
497
|
+
private tryHandleSharedHmrRequest(request: Request, context: SharedRequestContext): Response | null {
|
|
458
498
|
const url = new URL(request.url);
|
|
459
499
|
|
|
460
500
|
if (url.pathname === '/_hmr_runtime.js' && context.hmrManager) {
|
|
@@ -477,15 +517,44 @@ export abstract class SharedServerAdapter<
|
|
|
477
517
|
}
|
|
478
518
|
}
|
|
479
519
|
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private async tryHandleSharedApiRequest(request: Request, context: SharedRequestContext): Promise<Response | null> {
|
|
480
524
|
const apiMatch = this.matchApiHandler(request, context.apiHandlers);
|
|
481
|
-
if (apiMatch) {
|
|
482
|
-
return
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
525
|
+
if (!apiMatch) {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return await this.executeApiHandler(
|
|
530
|
+
request,
|
|
531
|
+
apiMatch.params,
|
|
532
|
+
apiMatch.routeConfig,
|
|
533
|
+
context.serverInstance,
|
|
534
|
+
context.errorHandler,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Universally processes an incoming WinterCG Web standard Request.
|
|
540
|
+
*
|
|
541
|
+
* 1. Resolves static Hot Module Replacement runtime blobs if development.
|
|
542
|
+
* 2. Checks if the incoming request matches any parsed API route schemas.
|
|
543
|
+
* - Routes through `executeApiHandler` which performs strict validation.
|
|
544
|
+
* 3. Falls through to standard `ServerRouteHandler` for React/Lit filesystem pages.
|
|
545
|
+
*
|
|
546
|
+
* Both Bun and Node bindings fall back to this exact function once they have mapped their
|
|
547
|
+
* native HTTP objects into Web Standard Requests.
|
|
548
|
+
*/
|
|
549
|
+
public async handleSharedRequest(request: Request, context: SharedRequestContext): Promise<Response> {
|
|
550
|
+
const hmrResponse = this.tryHandleSharedHmrRequest(request, context);
|
|
551
|
+
if (hmrResponse) {
|
|
552
|
+
return hmrResponse;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const apiResponse = await this.tryHandleSharedApiRequest(request, context);
|
|
556
|
+
if (apiResponse) {
|
|
557
|
+
return apiResponse;
|
|
489
558
|
}
|
|
490
559
|
|
|
491
560
|
return this.routeHandler.handleResponse(request);
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { ServerRouteHandler } from './server-route-handler';
|
|
3
|
-
import type {
|
|
3
|
+
import type { RouteRegistry } from '../../router/server/route-registry.ts';
|
|
4
4
|
import type { FileSystemResponseMatcher } from './fs-server-response-matcher.ts';
|
|
5
5
|
import type { IHmrManager } from '../../types/public-types.ts';
|
|
6
6
|
|
|
7
7
|
function createMockDependencies() {
|
|
8
8
|
const Router = {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
matchRequest: vi.fn(() => null),
|
|
10
|
+
origin: 'http://localhost:3000',
|
|
11
|
+
} as unknown as RouteRegistry;
|
|
11
12
|
|
|
12
13
|
const FileSystemResponseMatcher = {
|
|
13
14
|
handleMatch: vi.fn(() => Promise.resolve(new Response('Matched Content'))),
|
|
@@ -35,7 +36,7 @@ describe('ServerRouteHandler', () => {
|
|
|
35
36
|
fileSystemResponseMatcher: FileSystemResponseMatcher,
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
Router.
|
|
39
|
+
Router.matchRequest = vi.fn(
|
|
39
40
|
() =>
|
|
40
41
|
({
|
|
41
42
|
/* match */
|
|
@@ -71,7 +72,7 @@ describe('ServerRouteHandler', () => {
|
|
|
71
72
|
hmrManager: HmrManager,
|
|
72
73
|
});
|
|
73
74
|
|
|
74
|
-
Router.
|
|
75
|
+
Router.matchRequest = vi.fn(() => ({}) as any);
|
|
75
76
|
FileSystemResponseMatcher.handleMatch = vi.fn(() =>
|
|
76
77
|
Promise.resolve(
|
|
77
78
|
new Response('<html><body></body></html>', { headers: { 'Content-Type': 'text/html' } }),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IHmrManager } from '../../types/public-types.ts';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RouteRegistry } from '../../router/server/route-registry.ts';
|
|
3
3
|
import type { ExplicitStaticRouteMatcher } from './explicit-static-route-matcher.ts';
|
|
4
4
|
import type { FileSystemResponseMatcher } from './fs-server-response-matcher.ts';
|
|
5
5
|
import { appLogger } from '../../global/app-logger.ts';
|
|
@@ -11,7 +11,7 @@ import { injectHmrRuntimeIntoHtmlResponse, isHtmlResponse, shouldInjectHmrHtmlRe
|
|
|
11
11
|
*/
|
|
12
12
|
export interface ServerRouteHandlerParams {
|
|
13
13
|
/** File system router for matching request URLs to route handlers. */
|
|
14
|
-
router:
|
|
14
|
+
router: RouteRegistry;
|
|
15
15
|
/** Matcher for handling file system route responses. */
|
|
16
16
|
fileSystemResponseMatcher: FileSystemResponseMatcher;
|
|
17
17
|
/** Optional matcher for explicit static routes like processed images or sitemaps. */
|
|
@@ -33,7 +33,7 @@ export interface ServerRouteHandlerParams {
|
|
|
33
33
|
* In development mode, it also injects HMR scripts into HTML responses.
|
|
34
34
|
*/
|
|
35
35
|
export class ServerRouteHandler {
|
|
36
|
-
private readonly router:
|
|
36
|
+
private readonly router: RouteRegistry;
|
|
37
37
|
private readonly fileSystemResponseMatcher: FileSystemResponseMatcher;
|
|
38
38
|
private readonly explicitStaticRouteMatcher?: ExplicitStaticRouteMatcher;
|
|
39
39
|
private readonly watch: boolean;
|
|
@@ -98,7 +98,7 @@ export class ServerRouteHandler {
|
|
|
98
98
|
return this.maybeInjectHmrScript(response);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const fsMatch = !pathname.includes('.') && this.router.
|
|
101
|
+
const fsMatch = !pathname.includes('.') && this.router.matchRequest(request.url);
|
|
102
102
|
|
|
103
103
|
const response = await (fsMatch
|
|
104
104
|
? this.fileSystemResponseMatcher.handleMatch(fsMatch, request)
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
} from './server-static-builder';
|
|
9
9
|
import type { EcoPagesAppConfig } from '../../types/internal-types';
|
|
10
10
|
import type { StaticSiteGenerator } from '../../static-site-generator/static-site-generator';
|
|
11
|
-
import type {
|
|
12
|
-
import type {
|
|
11
|
+
import type { RouteRegistry } from '../../router/server/route-registry';
|
|
12
|
+
import type { StaticGenerationRendererResolver } from '../../route-renderer/route-renderer';
|
|
13
13
|
import fs from 'node:fs';
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import os from 'node:os';
|
|
@@ -68,8 +68,8 @@ function createMockDependencies() {
|
|
|
68
68
|
port: 3000,
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
const Router = {} as
|
|
72
|
-
const RouteRendererFactory = {} as
|
|
71
|
+
const Router = {} as RouteRegistry;
|
|
72
|
+
const RouteRendererFactory = {} as StaticGenerationRendererResolver;
|
|
73
73
|
const logger: ServerStaticBuilderLogger = {
|
|
74
74
|
warn: (message: string, detail?: string) => {
|
|
75
75
|
calls.warn.push([message, detail]);
|
|
@@ -5,9 +5,9 @@ import { StaticContentServer } from '../../dev/sc-server.ts';
|
|
|
5
5
|
import { appLogger } from '../../global/app-logger.ts';
|
|
6
6
|
import type { EcoPagesAppConfig } from '../../types/internal-types.ts';
|
|
7
7
|
import type { ApiHandler, StaticRoute } from '../../types/public-types.ts';
|
|
8
|
-
import type {
|
|
9
|
-
import type { FSRouter } from '../../router/server/fs-router.ts';
|
|
8
|
+
import type { RouteRegistry } from '../../router/server/route-registry.ts';
|
|
10
9
|
import type { StaticSiteGenerator } from '../../static-site-generator/static-site-generator.ts';
|
|
10
|
+
import type { StaticGenerationRendererResolver } from '../../route-renderer/route-renderer.ts';
|
|
11
11
|
|
|
12
12
|
export interface StaticBuildOptions {
|
|
13
13
|
preview?: boolean;
|
|
@@ -128,8 +128,8 @@ export class ServerStaticBuilder {
|
|
|
128
128
|
async build(
|
|
129
129
|
options: StaticBuildOptions | undefined,
|
|
130
130
|
dependencies: {
|
|
131
|
-
router:
|
|
132
|
-
routeRendererFactory:
|
|
131
|
+
router: RouteRegistry;
|
|
132
|
+
routeRendererFactory: StaticGenerationRendererResolver;
|
|
133
133
|
staticRoutes?: StaticRoute[];
|
|
134
134
|
},
|
|
135
135
|
): Promise<void> {
|
package/src/config/README.md
CHANGED
|
@@ -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,
|
|
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
|
|
|
@@ -67,7 +67,6 @@ describe('EcoConfigBuilder', () => {
|
|
|
67
67
|
expect(createVitePluginsFromAppSourceTransforms(config).length).toBeGreaterThan(0);
|
|
68
68
|
expect(config.runtime?.serverInvalidationState).toBeDefined();
|
|
69
69
|
expect(config.runtime?.entrypointDependencyGraph).toBeDefined();
|
|
70
|
-
expect(config.runtime?.runtimeSpecifierRegistry).toBeDefined();
|
|
71
70
|
});
|
|
72
71
|
|
|
73
72
|
test('should allow explicit Vite-host build ownership during config build', async () => {
|
|
@@ -26,7 +26,7 @@ import { ghtmlPlugin } from '../integrations/ghtml/ghtml.plugin.ts';
|
|
|
26
26
|
import type { EcoPagesAppConfig, RobotsPreference } from '../types/internal-types.ts';
|
|
27
27
|
import { createEcoComponentMetaPlugin } from '../plugins/eco-component-meta-plugin.ts';
|
|
28
28
|
import { createEcoComponentMetaTransform } from '../plugins/eco-component-meta-plugin.ts';
|
|
29
|
-
import type {
|
|
29
|
+
import type { AnyIntegrationPlugin } from '../plugins/integration-plugin.ts';
|
|
30
30
|
import type { Processor } from '../plugins/processor.ts';
|
|
31
31
|
import type { EcoSourceTransform } from '../plugins/source-transform.ts';
|
|
32
32
|
import type { RuntimeCapabilityDeclaration, RuntimeCapabilityTag } from '../plugins/runtime-capability.ts';
|
|
@@ -36,10 +36,6 @@ import {
|
|
|
36
36
|
NoopEntrypointDependencyGraph,
|
|
37
37
|
setAppEntrypointDependencyGraph,
|
|
38
38
|
} from '../services/runtime-state/entrypoint-dependency-graph.service.ts';
|
|
39
|
-
import {
|
|
40
|
-
InMemoryRuntimeSpecifierRegistry,
|
|
41
|
-
setAppRuntimeSpecifierRegistry,
|
|
42
|
-
} from '../services/runtime-state/runtime-specifier-registry.service.ts';
|
|
43
39
|
import {
|
|
44
40
|
CounterServerInvalidationState,
|
|
45
41
|
setAppServerInvalidationState,
|
|
@@ -283,7 +279,7 @@ export class ConfigBuilder {
|
|
|
283
279
|
* @param integrations - An array of integration plugins
|
|
284
280
|
* @returns The ConfigBuilder instance for method chaining
|
|
285
281
|
*/
|
|
286
|
-
setIntegrations(integrations:
|
|
282
|
+
setIntegrations(integrations: AnyIntegrationPlugin[]): this {
|
|
287
283
|
this.config.integrations = integrations;
|
|
288
284
|
return this;
|
|
289
285
|
}
|
|
@@ -727,7 +723,6 @@ export class ConfigBuilder {
|
|
|
727
723
|
updateAppBuildManifest(this.config, await collectConfiguredAppBuildManifestContributions(this.config));
|
|
728
724
|
setAppServerInvalidationState(this.config, new CounterServerInvalidationState());
|
|
729
725
|
setAppEntrypointDependencyGraph(this.config, new NoopEntrypointDependencyGraph());
|
|
730
|
-
setAppRuntimeSpecifierRegistry(this.config, new InMemoryRuntimeSpecifierRegistry());
|
|
731
726
|
setAppBuildExecutor(
|
|
732
727
|
this.config,
|
|
733
728
|
createAppBuildExecutor({
|