@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.
- package/CHANGELOG.md +25 -0
- package/README.md +63 -7
- package/package.json +8 -94
- package/src/adapters/bun/create-app.d.ts +1 -0
- package/src/adapters/bun/create-app.js +39 -2
- package/src/adapters/bun/hmr-manager.d.ts +1 -13
- package/src/adapters/bun/hmr-manager.js +1 -22
- package/src/adapters/bun/server-adapter.js +23 -4
- package/src/adapters/node/node-hmr-manager.d.ts +2 -14
- package/src/adapters/node/node-hmr-manager.js +2 -23
- package/src/adapters/shared/explicit-static-render-preparation.d.ts +25 -0
- package/src/adapters/shared/explicit-static-render-preparation.js +26 -0
- package/src/adapters/shared/explicit-static-route-matcher.d.ts +5 -2
- package/src/adapters/shared/explicit-static-route-matcher.js +14 -16
- package/src/adapters/shared/file-route-middleware-pipeline.d.ts +7 -10
- package/src/adapters/shared/file-route-middleware-pipeline.js +2 -11
- package/src/adapters/shared/fs-server-response-factory.d.ts +13 -9
- package/src/adapters/shared/fs-server-response-factory.js +10 -26
- package/src/adapters/shared/fs-server-response-matcher.d.ts +14 -6
- package/src/adapters/shared/fs-server-response-matcher.js +67 -28
- package/src/adapters/shared/render-context.d.ts +2 -2
- package/src/adapters/shared/server-adapter.d.ts +21 -10
- package/src/adapters/shared/server-adapter.js +171 -132
- package/src/adapters/shared/server-route-handler.d.ts +2 -2
- package/src/adapters/shared/server-route-handler.js +1 -1
- package/src/adapters/shared/server-static-builder.d.ts +4 -4
- package/src/config/README.md +1 -1
- package/src/config/config-builder.d.ts +2 -2
- package/src/config/config-builder.js +0 -5
- package/src/dev/host-runtime.d.ts +10 -0
- package/src/dev/host-runtime.js +24 -0
- package/src/eco/eco.js +7 -7
- package/src/eco/eco.types.d.ts +3 -3
- package/src/errors/index.d.ts +1 -0
- package/src/errors/index.js +3 -1
- package/src/hmr/strategies/js-hmr-strategy.d.ts +0 -5
- package/src/integrations/ghtml/ghtml-renderer.d.ts +0 -4
- package/src/integrations/ghtml/ghtml-renderer.js +1 -7
- package/src/plugins/eco-component-meta-plugin.js +0 -1
- package/src/plugins/integration-plugin.d.ts +14 -18
- package/src/plugins/integration-plugin.js +14 -21
- package/src/plugins/processor.d.ts +2 -0
- package/src/plugins/processor.js +6 -1
- package/src/route-renderer/GRAPH.md +81 -289
- package/src/route-renderer/README.md +67 -105
- package/src/route-renderer/orchestration/component-render-context.d.ts +24 -18
- package/src/route-renderer/orchestration/component-render-context.js +14 -14
- package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +18 -0
- package/src/route-renderer/orchestration/declared-ownership-graph.js +34 -0
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +108 -0
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +206 -0
- package/src/route-renderer/orchestration/integration-renderer.d.ts +96 -136
- package/src/route-renderer/orchestration/integration-renderer.js +280 -303
- package/src/route-renderer/orchestration/ownership-planning.service.d.ts +24 -0
- package/src/route-renderer/orchestration/ownership-planning.service.js +63 -0
- package/src/route-renderer/orchestration/ownership-validation.service.d.ts +29 -0
- package/src/route-renderer/orchestration/ownership-validation.service.js +53 -0
- package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +90 -0
- package/src/route-renderer/orchestration/{queued-boundary-runtime.service.js → queued-foreign-subtree-resolution.service.js} +28 -25
- package/src/route-renderer/orchestration/render-output.utils.d.ts +3 -3
- package/src/route-renderer/orchestration/render-output.utils.js +6 -6
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +120 -0
- package/src/route-renderer/orchestration/{render-preparation.service.js → route-render-orchestrator.js} +132 -108
- package/src/route-renderer/page-loading/component-dependency-collection.js +8 -1
- package/src/route-renderer/page-loading/dependency-resolver.js +5 -7
- package/src/route-renderer/page-loading/page-dependency-bundling.d.ts +1 -1
- package/src/route-renderer/page-loading/page-dependency-bundling.js +41 -19
- package/src/route-renderer/route-renderer.d.ts +28 -26
- package/src/route-renderer/route-renderer.js +4 -27
- package/src/router/README.md +16 -19
- package/src/router/server/route-registry.d.ts +78 -0
- package/src/router/server/route-registry.js +262 -0
- package/src/services/README.md +1 -2
- package/src/services/assets/asset-processing-service/assets.types.d.ts +3 -0
- package/src/services/assets/asset-processing-service/index.d.ts +1 -0
- package/src/services/assets/asset-processing-service/index.js +1 -0
- package/src/services/assets/asset-processing-service/page-package.d.ts +3 -0
- package/src/services/assets/asset-processing-service/page-package.js +74 -0
- package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +4 -4
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +6 -3
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +9 -3
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +4 -2
- package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +2 -1
- package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +3 -1
- package/src/services/module-loading/node-bootstrap-plugin.js +15 -3
- package/src/static-site-generator/static-site-generator.d.ts +20 -21
- package/src/static-site-generator/static-site-generator.js +107 -140
- package/src/types/internal-types.d.ts +13 -12
- package/src/types/public-types.d.ts +46 -36
- package/src/watchers/project-watcher.test-helpers.js +5 -5
- package/src/route-renderer/orchestration/boundary-planning.service.d.ts +0 -25
- package/src/route-renderer/orchestration/boundary-planning.service.js +0 -97
- package/src/route-renderer/orchestration/page-packaging.service.d.ts +0 -16
- package/src/route-renderer/orchestration/page-packaging.service.js +0 -66
- package/src/route-renderer/orchestration/queued-boundary-runtime.service.d.ts +0 -89
- package/src/route-renderer/orchestration/render-execution.service.d.ts +0 -43
- package/src/route-renderer/orchestration/render-execution.service.js +0 -106
- package/src/route-renderer/orchestration/render-preparation.service.d.ts +0 -120
- package/src/route-renderer/orchestration/route-shell-composer.service.d.ts +0 -50
- package/src/route-renderer/orchestration/route-shell-composer.service.js +0 -81
- package/src/router/server/fs-router-scanner.d.ts +0 -41
- package/src/router/server/fs-router-scanner.js +0 -161
- package/src/router/server/fs-router.d.ts +0 -26
- package/src/router/server/fs-router.js +0 -100
- package/src/services/runtime-state/runtime-specifier-registry.service.d.ts +0 -69
- 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 {
|
|
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:
|
|
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
|
|
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 `
|
|
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 {
|
|
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
|
|
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 `
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
|
105
|
-
const
|
|
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:
|
|
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
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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 {
|
|
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:
|
|
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.
|
|
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 {
|
|
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:
|
|
68
|
-
routeRendererFactory:
|
|
67
|
+
router: RouteRegistry;
|
|
68
|
+
routeRendererFactory: StaticGenerationRendererResolver;
|
|
69
69
|
staticRoutes?: StaticRoute[];
|
|
70
70
|
}): Promise<void>;
|
|
71
71
|
}
|
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
|
|
|
@@ -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 {
|
|
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:
|
|
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
|
-
|
|
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
|
|
11
|
+
const foreignChildRender = interceptForeignChild({
|
|
12
12
|
component: comp,
|
|
13
13
|
props: componentProps,
|
|
14
14
|
targetIntegration: integrationName
|
|
15
15
|
});
|
|
16
|
-
if (isThenable(
|
|
17
|
-
return
|
|
18
|
-
(
|
|
16
|
+
if (isThenable(foreignChildRender)) {
|
|
17
|
+
return foreignChildRender.then(
|
|
18
|
+
(resolvedForeignChildRender) => resolvedForeignChildRender !== void 0 ? resolvedForeignChildRender : renderInline()
|
|
19
19
|
);
|
|
20
20
|
}
|
|
21
|
-
if (
|
|
22
|
-
return
|
|
21
|
+
if (foreignChildRender !== void 0) {
|
|
22
|
+
return foreignChildRender;
|
|
23
23
|
}
|
|
24
24
|
return renderInline();
|
|
25
25
|
});
|
package/src/eco/eco.types.d.ts
CHANGED
|
@@ -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,
|
|
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:
|
|
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?:
|
|
130
|
+
middleware?: FileRouteMiddleware[];
|
|
131
131
|
};
|
|
132
132
|
/**
|
|
133
133
|
* The eco namespace interface
|