@ecopages/core 0.2.0-alpha.39 → 0.2.0-alpha.40
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/package.json +2 -2
- package/src/adapters/bun/create-app.d.ts +8 -1
- package/src/adapters/bun/create-app.js +52 -65
- package/src/adapters/bun/hmr-manager.d.ts +19 -103
- package/src/adapters/bun/hmr-manager.js +26 -280
- package/src/adapters/bun/runtime-host.d.ts +52 -0
- package/src/adapters/bun/runtime-host.js +56 -0
- package/src/adapters/bun/server-adapter.d.ts +89 -28
- package/src/adapters/bun/server-adapter.js +113 -61
- package/src/adapters/bun/static-preview-host.d.ts +28 -0
- package/src/adapters/bun/static-preview-host.js +45 -0
- package/src/adapters/node/create-app.d.ts +9 -3
- package/src/adapters/node/create-app.js +24 -81
- package/src/adapters/node/http-request-bridge.d.ts +57 -0
- package/src/adapters/node/http-request-bridge.js +118 -0
- package/src/adapters/node/node-hmr-manager.d.ts +22 -91
- package/src/adapters/node/node-hmr-manager.js +26 -272
- package/src/adapters/node/runtime-host.d.ts +57 -0
- package/src/adapters/node/runtime-host.js +92 -0
- package/src/adapters/node/server-adapter-dependencies.d.ts +19 -0
- package/src/adapters/node/server-adapter-dependencies.js +18 -0
- package/src/adapters/node/server-adapter.d.ts +10 -37
- package/src/adapters/node/server-adapter.js +55 -125
- package/src/adapters/node/static-preview-host.d.ts +55 -0
- package/src/adapters/node/static-preview-host.js +68 -0
- package/src/adapters/shared/runtime-app-bootstrap.d.ts +26 -0
- package/src/adapters/shared/runtime-app-bootstrap.js +46 -0
- package/src/adapters/shared/runtime-host.d.ts +12 -0
- package/src/adapters/shared/runtime-host.js +0 -0
- package/src/adapters/shared/shared-hmr-manager.d.ts +59 -0
- package/src/adapters/shared/shared-hmr-manager.js +239 -0
- package/src/adapters/shared/static-preview-host.d.ts +10 -0
- package/src/adapters/shared/static-preview-host.js +0 -0
- package/src/build/build-adapter.js +12 -1
- package/src/build/esbuild-build-adapter.d.ts +1 -0
- package/src/build/esbuild-build-adapter.js +13 -0
- package/src/hmr/strategies/js-hmr-strategy.js +0 -4
- package/src/plugins/integration-plugin.d.ts +6 -1
- package/src/route-renderer/orchestration/integration-renderer.d.ts +32 -14
- package/src/route-renderer/orchestration/integration-renderer.js +80 -14
- package/src/route-renderer/orchestration/processed-asset-dedupe.d.ts +1 -0
- package/src/route-renderer/orchestration/processed-asset-dedupe.js +15 -11
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +22 -8
- package/src/route-renderer/orchestration/route-render-orchestrator.js +59 -10
- package/src/services/assets/asset-processing-service/page-package.d.ts +4 -1
- package/src/services/assets/asset-processing-service/page-package.js +11 -5
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +3 -1
- package/src/services/html/html-rewriter-provider.service.d.ts +3 -0
- package/src/services/html/html-transformer.service.d.ts +10 -1
- package/src/services/html/html-transformer.service.js +80 -9
- package/src/services/module-loading/page-module-import.service.js +2 -2
- package/src/types/public-types.d.ts +24 -7
- package/src/adapters/bun/server-lifecycle.d.ts +0 -63
- package/src/adapters/bun/server-lifecycle.js +0 -92
- package/src/adapters/shared/runtime-bootstrap.d.ts +0 -38
- package/src/adapters/shared/runtime-bootstrap.js +0 -43
|
@@ -1,36 +1,44 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { DEFAULT_ECOPAGES_HOSTNAME, DEFAULT_ECOPAGES_PORT } from "../../config/constants.js";
|
|
3
|
+
import { RESOLVED_ASSETS_DIR } from "../../config/constants.js";
|
|
2
4
|
import { appLogger } from "../../global/app-logger.js";
|
|
3
5
|
import { HttpError } from "../../errors/http-error.js";
|
|
4
6
|
import { createRequire } from "../../utils/locals-utils.js";
|
|
5
7
|
import { fileSystem } from "@ecopages/file-system";
|
|
8
|
+
import { getAppBrowserBuildPlugins, setupAppRuntimePlugins } from "../../build/build-adapter.js";
|
|
9
|
+
import { installAppRuntimeBuildExecutor } from "../../build/runtime-build-executor.js";
|
|
10
|
+
import { StaticSiteGenerator } from "../../static-site-generator/static-site-generator.js";
|
|
11
|
+
import { ProjectWatcher } from "../../watchers/project-watcher.js";
|
|
6
12
|
import { SharedServerAdapter } from "../shared/server-adapter.js";
|
|
7
13
|
import { ApiResponseBuilder } from "../shared/api-response.js";
|
|
8
|
-
import { installSharedRuntimeBuildExecutor } from "../shared/runtime-bootstrap.js";
|
|
9
|
-
import { StaticContentServer } from "../../dev/sc-server.js";
|
|
10
|
-
import { ServerRouteHandler } from "../shared/server-route-handler.js";
|
|
11
14
|
import { ServerStaticBuilder } from "../shared/server-static-builder.js";
|
|
12
15
|
import {
|
|
13
16
|
injectHmrRuntimeIntoHtmlResponse,
|
|
14
17
|
isHtmlResponse,
|
|
15
18
|
shouldInjectHmrHtmlResponse
|
|
16
19
|
} from "../shared/hmr-html-response.js";
|
|
20
|
+
import { resolveServeRuntimeOrigin } from "../shared/runtime-app-bootstrap.js";
|
|
17
21
|
import { ClientBridge } from "./client-bridge.js";
|
|
18
22
|
import { HmrManager } from "./hmr-manager.js";
|
|
19
|
-
import {
|
|
23
|
+
import { BunStaticPreviewHost } from "./static-preview-host.js";
|
|
20
24
|
class BunServerAdapter extends SharedServerAdapter {
|
|
21
25
|
apiHandlers;
|
|
22
26
|
staticRoutes;
|
|
23
27
|
errorHandler;
|
|
24
28
|
bridge;
|
|
25
|
-
lifecycle;
|
|
26
29
|
hmrManager;
|
|
27
30
|
initializationPromise = null;
|
|
28
31
|
fullyInitialized = false;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
previewHost;
|
|
33
|
+
/**
|
|
34
|
+
* Creates a Bun server adapter with already-resolved runtime collaborators.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* The public params interface keeps `hmrManager`, `bridge`, and `previewHost`
|
|
38
|
+
* optional so factory callers can omit them. By the time the concrete adapter
|
|
39
|
+
* is constructed, those collaborators are mandatory because the adapter cannot
|
|
40
|
+
* initialize Bun HMR or preview flows without them.
|
|
41
|
+
*/
|
|
34
42
|
constructor({
|
|
35
43
|
appConfig,
|
|
36
44
|
runtimeOrigin,
|
|
@@ -39,31 +47,32 @@ class BunServerAdapter extends SharedServerAdapter {
|
|
|
39
47
|
staticRoutes,
|
|
40
48
|
errorHandler,
|
|
41
49
|
options,
|
|
42
|
-
lifecycle,
|
|
43
|
-
staticBuilderFactory,
|
|
44
|
-
routeHandlerFactory,
|
|
45
50
|
hmrManager,
|
|
46
|
-
bridge
|
|
51
|
+
bridge,
|
|
52
|
+
previewHost
|
|
47
53
|
}) {
|
|
48
54
|
super({ appConfig, runtimeOrigin, serveOptions, options });
|
|
49
55
|
this.apiHandlers = apiHandlers || [];
|
|
50
56
|
this.staticRoutes = staticRoutes || [];
|
|
51
57
|
this.errorHandler = errorHandler;
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
54
|
-
this.
|
|
55
|
-
this.hmrManagerFactory = hmrManager;
|
|
56
|
-
this.bridgeFactory = bridge;
|
|
58
|
+
this.bridge = bridge;
|
|
59
|
+
this.hmrManager = hmrManager;
|
|
60
|
+
this.previewHost = previewHost;
|
|
57
61
|
}
|
|
58
62
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
63
|
+
* Returns whether adapter-level HTML responses still need HMR runtime injection.
|
|
64
|
+
*
|
|
65
|
+
* @remarks
|
|
66
|
+
* Filesystem-routed pages are wrapped later in the shared route layer. This
|
|
67
|
+
* adapter-level check exists for explicit API handlers that return HTML and
|
|
68
|
+
* would otherwise bypass the route wrapper entirely.
|
|
61
69
|
*/
|
|
62
70
|
shouldInjectHmrScript() {
|
|
63
71
|
return shouldInjectHmrHtmlResponse(this.options?.watch === true, this.hmrManager);
|
|
64
72
|
}
|
|
65
73
|
/**
|
|
66
|
-
*
|
|
74
|
+
* Delegates the HTML-response test to the shared response helper used by both
|
|
75
|
+
* adapters.
|
|
67
76
|
*/
|
|
68
77
|
isHtmlResponse(response) {
|
|
69
78
|
return isHtmlResponse(response);
|
|
@@ -79,33 +88,68 @@ class BunServerAdapter extends SharedServerAdapter {
|
|
|
79
88
|
return response;
|
|
80
89
|
}
|
|
81
90
|
/**
|
|
82
|
-
* Initializes the server adapter's core components.
|
|
83
|
-
* Delegates to ServerLifecycle for setup.
|
|
91
|
+
* Initializes the server adapter's core runtime components.
|
|
84
92
|
*/
|
|
85
93
|
async initialize() {
|
|
86
|
-
|
|
94
|
+
installAppRuntimeBuildExecutor(this.appConfig, {
|
|
87
95
|
development: this.options?.watch === true
|
|
88
96
|
});
|
|
89
|
-
this.
|
|
90
|
-
this.hmrManager
|
|
91
|
-
this.
|
|
92
|
-
appConfig: this.appConfig,
|
|
93
|
-
runtimeOrigin: this.runtimeOrigin,
|
|
94
|
-
hmrManager: this.hmrManager,
|
|
95
|
-
bridge: this.bridge
|
|
96
|
-
});
|
|
97
|
-
this.staticSiteGenerator = await this.lifecycle.initialize();
|
|
97
|
+
this.staticSiteGenerator = new StaticSiteGenerator({ appConfig: this.appConfig });
|
|
98
|
+
await this.hmrManager.buildRuntime();
|
|
99
|
+
this.prepareRuntimePublicDir();
|
|
98
100
|
const staticBuilderOptions = {
|
|
99
101
|
appConfig: this.appConfig,
|
|
100
102
|
staticSiteGenerator: this.staticSiteGenerator,
|
|
101
103
|
serveOptions: this.serveOptions,
|
|
102
104
|
apiHandlers: this.apiHandlers
|
|
103
105
|
};
|
|
104
|
-
this.staticBuilder =
|
|
105
|
-
await this.
|
|
106
|
+
this.staticBuilder = new ServerStaticBuilder(staticBuilderOptions);
|
|
107
|
+
await this.initializeRuntimePlugins({ watch: this.options?.watch });
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Copies the source `public` directory into the runtime output and ensures the
|
|
111
|
+
* HMR assets directory exists before any runtime bundles are emitted.
|
|
112
|
+
*/
|
|
113
|
+
prepareRuntimePublicDir() {
|
|
114
|
+
const srcPublicDir = path.join(this.appConfig.rootDir, this.appConfig.srcDir, this.appConfig.publicDir);
|
|
115
|
+
if (fileSystem.exists(srcPublicDir)) {
|
|
116
|
+
fileSystem.copyDir(srcPublicDir, path.join(this.appConfig.rootDir, this.appConfig.distDir));
|
|
117
|
+
}
|
|
118
|
+
fileSystem.ensureDir(path.join(this.appConfig.absolutePaths.distDir, RESOLVED_ASSETS_DIR));
|
|
106
119
|
}
|
|
107
120
|
/**
|
|
108
|
-
*
|
|
121
|
+
* Registers runtime plugins and propagates the final HMR manager into each
|
|
122
|
+
* integration.
|
|
123
|
+
*
|
|
124
|
+
* @remarks
|
|
125
|
+
* This is where Bun's runtime-plugin registration path meets the integration
|
|
126
|
+
* lifecycle. A failure here leaves the runtime partially bootstrapped, so the
|
|
127
|
+
* method logs the underlying error and rethrows instead of trying to limp on.
|
|
128
|
+
*/
|
|
129
|
+
async initializeRuntimePlugins(options) {
|
|
130
|
+
try {
|
|
131
|
+
this.hmrManager.setEnabled(Boolean(options?.watch));
|
|
132
|
+
await setupAppRuntimePlugins({
|
|
133
|
+
appConfig: this.appConfig,
|
|
134
|
+
runtimeOrigin: this.runtimeOrigin,
|
|
135
|
+
hmrManager: this.hmrManager,
|
|
136
|
+
onRuntimePlugin: (plugin) => {
|
|
137
|
+
Bun.plugin(plugin);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
const browserBuildPlugins = getAppBrowserBuildPlugins(this.appConfig);
|
|
141
|
+
this.hmrManager.setPlugins(browserBuildPlugins);
|
|
142
|
+
for (const integration of this.appConfig.integrations) {
|
|
143
|
+
integration.setHmrManager(this.hmrManager);
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
appLogger.error(`Failed to initialize plugins: ${error instanceof Error ? error.message : String(error)}`);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Rebuilds the shared routing state and hot-reloads the live Bun server when a
|
|
152
|
+
* watched route file changes.
|
|
109
153
|
*/
|
|
110
154
|
async refreshRouterRoutes() {
|
|
111
155
|
if (!this.serverInstance || typeof this.serverInstance.reload !== "function") {
|
|
@@ -127,15 +171,21 @@ class BunServerAdapter extends SharedServerAdapter {
|
|
|
127
171
|
})();
|
|
128
172
|
}
|
|
129
173
|
async watch() {
|
|
130
|
-
|
|
131
|
-
|
|
174
|
+
const watcher = new ProjectWatcher({
|
|
175
|
+
config: this.appConfig,
|
|
176
|
+
refreshRouterRoutesCallback: this.refreshRouterRoutes.bind(this),
|
|
177
|
+
hmrManager: this.hmrManager,
|
|
178
|
+
bridge: this.bridge
|
|
132
179
|
});
|
|
180
|
+
await watcher.createWatcherSubscription();
|
|
133
181
|
}
|
|
134
182
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
183
|
+
* Builds the `Bun.serve()` options for the current adapter state.
|
|
184
|
+
*
|
|
185
|
+
* @remarks
|
|
186
|
+
* The HMR-enabled variant wraps the base fetch handler so one Bun server can
|
|
187
|
+
* serve normal requests, accept HMR websocket upgrades, and expose the HMR
|
|
188
|
+
* runtime asset without splitting responsibility across separate listeners.
|
|
139
189
|
*/
|
|
140
190
|
getServerOptions({ enableHmr = false } = {}) {
|
|
141
191
|
appLogger.debug(`[BunServerAdapter] getServerOptions called with enableHmr: ${enableHmr}`);
|
|
@@ -191,8 +241,12 @@ class BunServerAdapter extends SharedServerAdapter {
|
|
|
191
241
|
return void 0;
|
|
192
242
|
}
|
|
193
243
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
244
|
+
* Composes the base Bun server settings that all runtime modes build from.
|
|
245
|
+
*
|
|
246
|
+
* @remarks
|
|
247
|
+
* This method centralizes the Bun-specific error boundary. It preserves the
|
|
248
|
+
* shared route pipeline while still allowing adapter-level custom error-handler
|
|
249
|
+
* execution and `HttpError` passthrough.
|
|
196
250
|
*/
|
|
197
251
|
buildServerSettings() {
|
|
198
252
|
const serverOptions = { ...this.serveOptions };
|
|
@@ -268,15 +322,15 @@ class BunServerAdapter extends SharedServerAdapter {
|
|
|
268
322
|
}
|
|
269
323
|
const previewHostname = this.serveOptions.hostname || DEFAULT_ECOPAGES_HOSTNAME;
|
|
270
324
|
const previewPort = Number(this.serveOptions.port || DEFAULT_ECOPAGES_PORT);
|
|
271
|
-
const
|
|
325
|
+
const activePreviewPort = await this.previewHost.start({
|
|
272
326
|
appConfig: this.appConfig,
|
|
273
|
-
|
|
327
|
+
hostname: String(previewHostname),
|
|
328
|
+
port: previewPort
|
|
274
329
|
});
|
|
275
|
-
if (
|
|
276
|
-
appLogger.info(`Preview running at http://${previewHostname}:${
|
|
330
|
+
if (activePreviewPort) {
|
|
331
|
+
appLogger.info(`Preview running at http://${previewHostname}:${activePreviewPort}`);
|
|
277
332
|
return;
|
|
278
333
|
}
|
|
279
|
-
appLogger.error("Failed to start preview server");
|
|
280
334
|
}
|
|
281
335
|
/**
|
|
282
336
|
* Initializes the server with dynamic routes after server creation.
|
|
@@ -296,7 +350,12 @@ class BunServerAdapter extends SharedServerAdapter {
|
|
|
296
350
|
return this.initializationPromise;
|
|
297
351
|
}
|
|
298
352
|
/**
|
|
299
|
-
* Performs
|
|
353
|
+
* Performs the one-time post-bind initialization path for Bun servers.
|
|
354
|
+
*
|
|
355
|
+
* @remarks
|
|
356
|
+
* This is intentionally split from `initialize()` because shared route handling
|
|
357
|
+
* and file watching need the live server instance to exist before Bun can
|
|
358
|
+
* reload updated route handlers in place.
|
|
300
359
|
*/
|
|
301
360
|
async _performInitialization(server) {
|
|
302
361
|
this.serverInstance = server;
|
|
@@ -367,23 +426,16 @@ class BunServerAdapter extends SharedServerAdapter {
|
|
|
367
426
|
}
|
|
368
427
|
}
|
|
369
428
|
async function createBunServerAdapter(params) {
|
|
370
|
-
const runtimeOrigin = params.runtimeOrigin ??
|
|
429
|
+
const runtimeOrigin = params.runtimeOrigin ?? resolveServeRuntimeOrigin(params.serveOptions);
|
|
371
430
|
const bridge = params.bridge ?? new ClientBridge();
|
|
372
431
|
const hmrManager = params.hmrManager ?? new HmrManager({ appConfig: params.appConfig, bridge });
|
|
373
|
-
const
|
|
374
|
-
appConfig: params.appConfig,
|
|
375
|
-
runtimeOrigin,
|
|
376
|
-
hmrManager,
|
|
377
|
-
bridge
|
|
378
|
-
});
|
|
432
|
+
const previewHost = params.previewHost ?? new BunStaticPreviewHost();
|
|
379
433
|
const adapter = new BunServerAdapter({
|
|
380
434
|
...params,
|
|
381
435
|
runtimeOrigin,
|
|
382
436
|
bridge,
|
|
383
437
|
hmrManager,
|
|
384
|
-
|
|
385
|
-
staticBuilderFactory: params.staticBuilderFactory ?? ((opts) => new ServerStaticBuilder(opts)),
|
|
386
|
-
routeHandlerFactory: params.routeHandlerFactory ?? ((p) => new ServerRouteHandler(p))
|
|
438
|
+
previewHost
|
|
387
439
|
});
|
|
388
440
|
return adapter.createAdapter();
|
|
389
441
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EcoPagesAppConfig } from '../../types/internal-types.js';
|
|
2
|
+
import type { StaticPreviewHost, StaticPreviewHostStartOptions } from '../shared/static-preview-host.js';
|
|
3
|
+
type BunStaticPreviewServer = {
|
|
4
|
+
server: {
|
|
5
|
+
port?: number;
|
|
6
|
+
} | null;
|
|
7
|
+
stop(): void;
|
|
8
|
+
};
|
|
9
|
+
type BunStaticPreviewServerFactory = {
|
|
10
|
+
createServer(args: {
|
|
11
|
+
appConfig: EcoPagesAppConfig;
|
|
12
|
+
options: {
|
|
13
|
+
port: number;
|
|
14
|
+
};
|
|
15
|
+
}): BunStaticPreviewServer;
|
|
16
|
+
};
|
|
17
|
+
type BunStaticPreviewLogger = {
|
|
18
|
+
error(message: string): unknown;
|
|
19
|
+
};
|
|
20
|
+
export declare class BunStaticPreviewHost implements StaticPreviewHost {
|
|
21
|
+
private readonly previewServerFactory;
|
|
22
|
+
private readonly logger;
|
|
23
|
+
private previewServer;
|
|
24
|
+
constructor(previewServerFactory?: BunStaticPreviewServerFactory, logger?: BunStaticPreviewLogger);
|
|
25
|
+
start(options: StaticPreviewHostStartOptions): Promise<number | null>;
|
|
26
|
+
stop(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { StaticContentServer } from "../../dev/sc-server.js";
|
|
2
|
+
import { appLogger } from "../../global/app-logger.js";
|
|
3
|
+
class BunStaticPreviewHost {
|
|
4
|
+
constructor(previewServerFactory = StaticContentServer, logger = appLogger) {
|
|
5
|
+
this.previewServerFactory = previewServerFactory;
|
|
6
|
+
this.logger = logger;
|
|
7
|
+
}
|
|
8
|
+
previewServerFactory;
|
|
9
|
+
logger;
|
|
10
|
+
previewServer = null;
|
|
11
|
+
async start(options) {
|
|
12
|
+
await this.stop();
|
|
13
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
14
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
15
|
+
try {
|
|
16
|
+
this.previewServer = this.previewServerFactory.createServer({
|
|
17
|
+
appConfig: options.appConfig,
|
|
18
|
+
options: { port: options.port }
|
|
19
|
+
});
|
|
20
|
+
const previewPort = this.previewServer.server?.port;
|
|
21
|
+
if (previewPort) {
|
|
22
|
+
return previewPort;
|
|
23
|
+
}
|
|
24
|
+
break;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
27
|
+
const errorCode = typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
|
|
28
|
+
const isPortReleaseRace = errorCode === "EADDRINUSE" || errorMessage.includes("EADDRINUSE");
|
|
29
|
+
if (!isPortReleaseRace || attempt === 19) {
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
this.logger.error("Failed to start preview server");
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
async stop() {
|
|
39
|
+
this.previewServer?.stop();
|
|
40
|
+
this.previewServer = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
BunStaticPreviewHost
|
|
45
|
+
};
|
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
import { type Server as NodeServerInstance } from 'node:http';
|
|
2
1
|
import { SharedApplicationAdapter } from '../shared/application-adapter.js';
|
|
2
|
+
import type { RuntimeHost } from '../shared/runtime-host.js';
|
|
3
3
|
import type { EcopagesAppOptions } from '../create-app.js';
|
|
4
4
|
import { type NodeServerAdapterResult, createNodeServerAdapter } from './server-adapter.js';
|
|
5
|
+
import type { NodeServerInstance } from './server-adapter.js';
|
|
5
6
|
export declare class NodeEcopagesApp extends SharedApplicationAdapter<EcopagesAppOptions, NodeServerInstance, Request> {
|
|
6
7
|
serverAdapter: NodeServerAdapterResult | undefined;
|
|
7
8
|
private server;
|
|
8
9
|
private runtimeOrigin;
|
|
10
|
+
private readonly runtimeHost;
|
|
11
|
+
constructor(options: EcopagesAppOptions, dependencies: {
|
|
12
|
+
runtimeHost: RuntimeHost<NodeServerInstance, {
|
|
13
|
+
port?: number;
|
|
14
|
+
hostname?: string;
|
|
15
|
+
}>;
|
|
16
|
+
});
|
|
9
17
|
protected createServerAdapter(params: Parameters<typeof createNodeServerAdapter>[0]): Promise<NodeServerAdapterResult>;
|
|
10
18
|
stop(force?: boolean): Promise<void>;
|
|
11
19
|
protected initializeServerAdapter(): Promise<NodeServerAdapterResult>;
|
|
12
20
|
start(): Promise<NodeServerInstance | void>;
|
|
13
|
-
private createWebRequest;
|
|
14
|
-
private sendNodeResponse;
|
|
15
21
|
fetch(request: Request): Promise<Response>;
|
|
16
22
|
}
|
|
17
23
|
export declare function createNodeApp(options: EcopagesAppOptions): Promise<NodeEcopagesApp>;
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
|
-
import { Readable } from "node:stream";
|
|
3
|
-
import { DEFAULT_ECOPAGES_HOSTNAME, DEFAULT_ECOPAGES_PORT } from "../../config/constants.js";
|
|
4
1
|
import { appLogger } from "../../global/app-logger.js";
|
|
5
2
|
import { SharedApplicationAdapter } from "../shared/application-adapter.js";
|
|
3
|
+
import { resolveRuntimeBinding } from "../shared/runtime-app-bootstrap.js";
|
|
6
4
|
import { createNodeServerAdapter } from "./server-adapter.js";
|
|
5
|
+
import { NodeHttpRequestBridge } from "./http-request-bridge.js";
|
|
6
|
+
import { NodeRuntimeHost } from "./runtime-host.js";
|
|
7
7
|
class NodeEcopagesApp extends SharedApplicationAdapter {
|
|
8
8
|
serverAdapter;
|
|
9
9
|
server = null;
|
|
10
10
|
runtimeOrigin = "";
|
|
11
|
+
runtimeHost;
|
|
12
|
+
constructor(options, dependencies) {
|
|
13
|
+
super(options);
|
|
14
|
+
this.runtimeHost = dependencies.runtimeHost;
|
|
15
|
+
}
|
|
11
16
|
createServerAdapter(params) {
|
|
12
17
|
return createNodeServerAdapter(params);
|
|
13
18
|
}
|
|
@@ -17,39 +22,22 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
|
|
|
17
22
|
}
|
|
18
23
|
const activeServer = this.server;
|
|
19
24
|
this.server = null;
|
|
20
|
-
await
|
|
21
|
-
activeServer.close((error) => {
|
|
22
|
-
if (error) {
|
|
23
|
-
reject(error);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
resolve();
|
|
27
|
-
});
|
|
28
|
-
if (force) {
|
|
29
|
-
activeServer.closeAllConnections();
|
|
30
|
-
}
|
|
31
|
-
});
|
|
25
|
+
await this.runtimeHost.stop(activeServer, { force });
|
|
32
26
|
}
|
|
33
27
|
async initializeServerAdapter() {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const preferredHostname = cliHostname ?? envHostname ?? DEFAULT_ECOPAGES_HOSTNAME;
|
|
40
|
-
this.runtimeOrigin = `http://${preferredHostname}:${preferredPort}`;
|
|
28
|
+
const binding = resolveRuntimeBinding({
|
|
29
|
+
cliArgs: this.cliArgs,
|
|
30
|
+
serverOptions: this.serverOptions
|
|
31
|
+
});
|
|
32
|
+
this.runtimeOrigin = binding.runtimeOrigin;
|
|
41
33
|
return this.createServerAdapter({
|
|
42
34
|
runtimeOrigin: this.runtimeOrigin,
|
|
43
35
|
appConfig: this.appConfig,
|
|
44
36
|
apiHandlers: this.apiHandlers,
|
|
45
37
|
staticRoutes: this.staticRoutes,
|
|
46
38
|
errorHandler: this.errorHandler,
|
|
47
|
-
options: { watch:
|
|
48
|
-
serveOptions:
|
|
49
|
-
port: preferredPort,
|
|
50
|
-
hostname: preferredHostname,
|
|
51
|
-
...this.serverOptions
|
|
52
|
-
}
|
|
39
|
+
options: { watch: binding.watch },
|
|
40
|
+
serveOptions: binding.serveOptions
|
|
53
41
|
});
|
|
54
42
|
}
|
|
55
43
|
async start() {
|
|
@@ -71,64 +59,17 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
|
|
|
71
59
|
return;
|
|
72
60
|
}
|
|
73
61
|
const serveOptions = this.serverAdapter.getServerOptions();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const webRequest = this.createWebRequest(req);
|
|
80
|
-
const response = await this.serverAdapter.handleRequest(webRequest);
|
|
81
|
-
await this.sendNodeResponse(res, response);
|
|
82
|
-
} catch (error) {
|
|
83
|
-
appLogger.error("Node server adapter request failed", error);
|
|
84
|
-
res.statusCode = 500;
|
|
85
|
-
res.end("Internal Server Error");
|
|
62
|
+
this.server = await this.runtimeHost.start({
|
|
63
|
+
serveOptions,
|
|
64
|
+
handleRequest: async (request) => await this.serverAdapter.handleRequest(request),
|
|
65
|
+
onError: async () => {
|
|
86
66
|
}
|
|
87
67
|
});
|
|
88
|
-
|
|
89
|
-
this.server.listen(port, hostname, () => resolve());
|
|
90
|
-
});
|
|
68
|
+
this.runtimeOrigin = this.runtimeHost.getOrigin(this.server, serveOptions);
|
|
91
69
|
await this.serverAdapter.completeInitialization(this.server);
|
|
92
70
|
appLogger.info(`Node server running at ${this.runtimeOrigin}`);
|
|
93
71
|
return this.server;
|
|
94
72
|
}
|
|
95
|
-
createWebRequest(req) {
|
|
96
|
-
const url = new URL(req.url ?? "/", this.runtimeOrigin);
|
|
97
|
-
const headers = new Headers();
|
|
98
|
-
for (const [key, value] of Object.entries(req.headers)) {
|
|
99
|
-
if (Array.isArray(value)) {
|
|
100
|
-
for (const item of value) {
|
|
101
|
-
headers.append(key, item);
|
|
102
|
-
}
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (value !== void 0) {
|
|
106
|
-
headers.set(key, value);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
const method = (req.method ?? "GET").toUpperCase();
|
|
110
|
-
const requestInit = {
|
|
111
|
-
method,
|
|
112
|
-
headers
|
|
113
|
-
};
|
|
114
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
115
|
-
requestInit.body = Readable.toWeb(req);
|
|
116
|
-
requestInit.duplex = "half";
|
|
117
|
-
}
|
|
118
|
-
return new Request(url, requestInit);
|
|
119
|
-
}
|
|
120
|
-
async sendNodeResponse(res, response) {
|
|
121
|
-
res.statusCode = response.status;
|
|
122
|
-
response.headers.forEach((value, key) => {
|
|
123
|
-
res.setHeader(key, value);
|
|
124
|
-
});
|
|
125
|
-
if (!response.body) {
|
|
126
|
-
res.end();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const body = Buffer.from(await response.arrayBuffer());
|
|
130
|
-
res.end(body);
|
|
131
|
-
}
|
|
132
73
|
async fetch(request) {
|
|
133
74
|
if (!this.serverAdapter) {
|
|
134
75
|
this.serverAdapter = await this.initializeServerAdapter();
|
|
@@ -137,7 +78,9 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
|
|
|
137
78
|
}
|
|
138
79
|
}
|
|
139
80
|
async function createNodeApp(options) {
|
|
140
|
-
return new NodeEcopagesApp(options
|
|
81
|
+
return new NodeEcopagesApp(options, {
|
|
82
|
+
runtimeHost: new NodeRuntimeHost(new NodeHttpRequestBridge())
|
|
83
|
+
});
|
|
141
84
|
}
|
|
142
85
|
async function createApp(options) {
|
|
143
86
|
return createNodeApp(options);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* Signals that the remote client closed the HTTP exchange before the Node
|
|
4
|
+
* adapter finished reading or writing the body.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* This error represents a transport-level disconnect, not an application
|
|
8
|
+
* failure. Higher layers may safely suppress logging for this error when the
|
|
9
|
+
* client socket is already gone and no meaningful response can still be sent.
|
|
10
|
+
*/
|
|
11
|
+
export declare class NodeClientAbortError extends Error {
|
|
12
|
+
/**
|
|
13
|
+
* Creates the canonical Node transport abort error used across request-body
|
|
14
|
+
* reads and response-body writes.
|
|
15
|
+
*/
|
|
16
|
+
constructor();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Type guard for the canonical client-abort error used by the Node adapter.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Runtime hosts use this guard before logging so only explicitly classified
|
|
23
|
+
* client disconnects are muted. All other failures continue through the normal
|
|
24
|
+
* error-reporting path.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isNodeClientAbortError(error: unknown): error is NodeClientAbortError;
|
|
27
|
+
/**
|
|
28
|
+
* Bridges Node's `IncomingMessage` / `ServerResponse` pair to the Web
|
|
29
|
+
* `Request` / `Response` contract used by the shared routing pipeline.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* This class is the Node transport boundary. It owns the translation of request
|
|
33
|
+
* headers and bodies into Web primitives and the reverse translation of Web
|
|
34
|
+
* responses back onto Node streams, including client-abort normalization.
|
|
35
|
+
*/
|
|
36
|
+
export declare class NodeHttpRequestBridge {
|
|
37
|
+
/**
|
|
38
|
+
* Converts one incoming Node request into the Web `Request` shape consumed by
|
|
39
|
+
* the shared server adapter.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* Non-GET/HEAD requests retain streaming semantics via a `ReadableStream` so
|
|
43
|
+
* large request bodies do not need to be buffered before route handling begins.
|
|
44
|
+
*/
|
|
45
|
+
createWebRequest(req: IncomingMessage, runtimeOrigin: string): Request;
|
|
46
|
+
/**
|
|
47
|
+
* Sends a Web `Response` through Node's `ServerResponse` without buffering the
|
|
48
|
+
* full body in memory first.
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* Streaming responses matter for large payloads and long-lived transports such
|
|
52
|
+
* as server-sent events. The bridge therefore forwards the `ReadableStream`
|
|
53
|
+
* directly into the Node writable response instead of materializing an
|
|
54
|
+
* intermediate `ArrayBuffer`.
|
|
55
|
+
*/
|
|
56
|
+
sendNodeResponse(res: ServerResponse, response: Response): Promise<void>;
|
|
57
|
+
}
|