@ecopages/core 0.2.0-beta.4 → 0.2.0-beta.6
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/abstract/application-adapter.d.ts +46 -0
- package/src/adapters/abstract/application-adapter.js +70 -0
- package/src/adapters/abstract/ws-pattern-matcher.d.ts +60 -0
- package/src/adapters/abstract/ws-pattern-matcher.js +61 -0
- package/src/adapters/bun/create-app.d.ts +3 -0
- package/src/adapters/bun/create-app.js +8 -1
- package/src/adapters/bun/hmr-manager.d.ts +2 -1
- package/src/adapters/bun/hmr-manager.js +2 -2
- package/src/adapters/bun/server-adapter.d.ts +61 -5
- package/src/adapters/bun/server-adapter.js +201 -6
- package/src/adapters/node/create-app.d.ts +3 -0
- package/src/adapters/node/create-app.js +7 -0
- package/src/adapters/node/node-hmr-manager.d.ts +2 -1
- package/src/adapters/node/node-hmr-manager.js +2 -2
- package/src/adapters/node/server-adapter.d.ts +25 -2
- package/src/adapters/node/server-adapter.js +60 -13
- package/src/adapters/shared/bun-user-websocket-lifecycle.d.ts +22 -0
- package/src/adapters/shared/bun-user-websocket-lifecycle.js +102 -0
- package/src/adapters/shared/node-http-websocket-upgrades.d.ts +21 -0
- package/src/adapters/shared/node-http-websocket-upgrades.js +125 -0
- package/src/adapters/shared/shared-hmr-manager.d.ts +1 -0
- package/src/adapters/shared/shared-hmr-manager.js +22 -3
- package/src/adapters/shared/websocket-lifecycle.d.ts +3 -0
- package/src/adapters/shared/websocket-lifecycle.js +19 -0
- package/src/build/runtime-build-output-normalizer.js +9 -1
- package/src/types/public-types.d.ts +76 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/core",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.6",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"directory": "packages/core"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ecopages/file-system": "0.2.0-beta.
|
|
20
|
+
"@ecopages/file-system": "0.2.0-beta.6",
|
|
21
21
|
"@ecopages/logger": "^0.2.3",
|
|
22
22
|
"@ecopages/scripts-injector": "^0.1.5",
|
|
23
23
|
"@oxc-project/runtime": "0.134.0",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { SourceModuleLoader } from '../../services/module-loading/module-loading-types.js';
|
|
10
10
|
import type { EcoPagesAppConfig } from '../../types/internal-types.js';
|
|
11
11
|
import type { ApiHandler, ApiHandlerContext, ErrorHandler, Middleware, RouteOptions, StaticRoute, ViewLoader } from '../../types/public-types.js';
|
|
12
|
+
import type { EcopagesWebSocketHandler } from '../../types/public-types.js';
|
|
12
13
|
import { type ReturnParseCliArgs } from '../../utils/parse-cli-args.js';
|
|
13
14
|
/**
|
|
14
15
|
* Runtime bootstrap options layered on top of the app config.
|
|
@@ -72,6 +73,11 @@ export declare abstract class AbstractApplicationAdapter<TOptions extends Applic
|
|
|
72
73
|
protected apiHandlers: ApiHandler[];
|
|
73
74
|
protected staticRoutes: StaticRoute[];
|
|
74
75
|
protected errorHandler?: ErrorHandler;
|
|
76
|
+
/**
|
|
77
|
+
* App-level WebSocket handlers keyed by URL path pattern (e.g. '/ws/chat/:id').
|
|
78
|
+
* Both Bun and Node adapters read this map to register upgrade routes.
|
|
79
|
+
*/
|
|
80
|
+
protected websocketHandlers: Map<string, EcopagesWebSocketHandler<any, any>>;
|
|
75
81
|
constructor(options: TOptions);
|
|
76
82
|
private clearDistFolder;
|
|
77
83
|
/**
|
|
@@ -160,6 +166,46 @@ export declare abstract class AbstractApplicationAdapter<TOptions extends Applic
|
|
|
160
166
|
* Get all registered static routes
|
|
161
167
|
*/
|
|
162
168
|
getStaticRoutes(): StaticRoute[];
|
|
169
|
+
/**
|
|
170
|
+
* Register a WebSocket handler for the given path pattern.
|
|
171
|
+
*
|
|
172
|
+
* The runtime adapter handles the HTTP→WebSocket upgrade for this path
|
|
173
|
+
* and routes lifecycle events to `handler`.
|
|
174
|
+
*
|
|
175
|
+
* Supports dynamic segments via `:param` syntax. The handler receives
|
|
176
|
+
* typed `params` and `search` fields, and a typed `context` produced by
|
|
177
|
+
* the optional `context()` factory.
|
|
178
|
+
*
|
|
179
|
+
* One pattern registration matches infinite path variations. For example,
|
|
180
|
+
* `app.websocket('/ws/chat/:roomId', handler)` matches `/ws/chat/abc`,
|
|
181
|
+
* `/ws/chat/xyz`, etc. Each connection receives its own `params.roomId`.
|
|
182
|
+
*
|
|
183
|
+
* Works across both Bun and Node runtimes — no runtime-specific imports needed.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* app.websocket<ChatContext, { roomId: string }>('/ws/chat/:roomId', {
|
|
188
|
+
* async context({ params, search }) {
|
|
189
|
+
* return { username: search.username ?? 'anonymous', roomId: params.roomId };
|
|
190
|
+
* },
|
|
191
|
+
* onConnect(socket) {
|
|
192
|
+
* socket.send(`Welcome to room ${socket.context.roomId}`);
|
|
193
|
+
* },
|
|
194
|
+
* onMessage(socket, message) {
|
|
195
|
+
* if (message.kind === 'text') {
|
|
196
|
+
* socket.send(message.text);
|
|
197
|
+
* }
|
|
198
|
+
* },
|
|
199
|
+
* });
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
websocket<TContext = unknown, TParams extends Record<string, string> = Record<string, string>>(path: string, handler: EcopagesWebSocketHandler<TContext, TParams>): this;
|
|
203
|
+
/**
|
|
204
|
+
* Get the registered WebSocket handlers map.
|
|
205
|
+
*
|
|
206
|
+
* @returns The map of WebSocket route patterns to handlers
|
|
207
|
+
*/
|
|
208
|
+
getWebsocketHandlers(): Map<string, EcopagesWebSocketHandler<any, any>>;
|
|
163
209
|
/**
|
|
164
210
|
* Register a global error handler for all routes.
|
|
165
211
|
* Useful for logging, monitoring integration, and custom error formatting.
|
|
@@ -15,6 +15,11 @@ class AbstractApplicationAdapter {
|
|
|
15
15
|
apiHandlers = [];
|
|
16
16
|
staticRoutes = [];
|
|
17
17
|
errorHandler;
|
|
18
|
+
/**
|
|
19
|
+
* App-level WebSocket handlers keyed by URL path pattern (e.g. '/ws/chat/:id').
|
|
20
|
+
* Both Bun and Node adapters read this map to register upgrade routes.
|
|
21
|
+
*/
|
|
22
|
+
websocketHandlers = /* @__PURE__ */ new Map();
|
|
18
23
|
constructor(options) {
|
|
19
24
|
this.appConfig = options.appConfig;
|
|
20
25
|
this.serverOptions = options.serverOptions || {};
|
|
@@ -93,6 +98,71 @@ class AbstractApplicationAdapter {
|
|
|
93
98
|
getStaticRoutes() {
|
|
94
99
|
return this.staticRoutes;
|
|
95
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Register a WebSocket handler for the given path pattern.
|
|
103
|
+
*
|
|
104
|
+
* The runtime adapter handles the HTTP→WebSocket upgrade for this path
|
|
105
|
+
* and routes lifecycle events to `handler`.
|
|
106
|
+
*
|
|
107
|
+
* Supports dynamic segments via `:param` syntax. The handler receives
|
|
108
|
+
* typed `params` and `search` fields, and a typed `context` produced by
|
|
109
|
+
* the optional `context()` factory.
|
|
110
|
+
*
|
|
111
|
+
* One pattern registration matches infinite path variations. For example,
|
|
112
|
+
* `app.websocket('/ws/chat/:roomId', handler)` matches `/ws/chat/abc`,
|
|
113
|
+
* `/ws/chat/xyz`, etc. Each connection receives its own `params.roomId`.
|
|
114
|
+
*
|
|
115
|
+
* Works across both Bun and Node runtimes — no runtime-specific imports needed.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* app.websocket<ChatContext, { roomId: string }>('/ws/chat/:roomId', {
|
|
120
|
+
* async context({ params, search }) {
|
|
121
|
+
* return { username: search.username ?? 'anonymous', roomId: params.roomId };
|
|
122
|
+
* },
|
|
123
|
+
* onConnect(socket) {
|
|
124
|
+
* socket.send(`Welcome to room ${socket.context.roomId}`);
|
|
125
|
+
* },
|
|
126
|
+
* onMessage(socket, message) {
|
|
127
|
+
* if (message.kind === 'text') {
|
|
128
|
+
* socket.send(message.text);
|
|
129
|
+
* }
|
|
130
|
+
* },
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
websocket(path, handler) {
|
|
135
|
+
invariant(
|
|
136
|
+
typeof path === "string" && path.startsWith("/"),
|
|
137
|
+
`app.websocket(): path must be a string starting with "/", got "${path}".`
|
|
138
|
+
);
|
|
139
|
+
const segments = path.split("/").filter(Boolean);
|
|
140
|
+
const paramNames = /* @__PURE__ */ new Set();
|
|
141
|
+
for (const segment of segments) {
|
|
142
|
+
if (segment.startsWith(":")) {
|
|
143
|
+
const paramName = segment.slice(1);
|
|
144
|
+
invariant(
|
|
145
|
+
paramName.length > 0,
|
|
146
|
+
`app.websocket(): invalid pattern "${path}" \u2014 empty param name in segment ":${paramName}".`
|
|
147
|
+
);
|
|
148
|
+
invariant(
|
|
149
|
+
!paramNames.has(paramName),
|
|
150
|
+
`app.websocket(): invalid pattern "${path}" \u2014 duplicate param name ":${paramName}".`
|
|
151
|
+
);
|
|
152
|
+
paramNames.add(paramName);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
this.websocketHandlers.set(path, handler);
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get the registered WebSocket handlers map.
|
|
160
|
+
*
|
|
161
|
+
* @returns The map of WebSocket route patterns to handlers
|
|
162
|
+
*/
|
|
163
|
+
getWebsocketHandlers() {
|
|
164
|
+
return this.websocketHandlers;
|
|
165
|
+
}
|
|
96
166
|
/**
|
|
97
167
|
* Register a global error handler for all routes.
|
|
98
168
|
* Useful for logging, monitoring integration, and custom error formatting.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { EcopagesWebSocketHandler } from '../../types/public-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pattern matcher for WebSocket route paths.
|
|
4
|
+
*
|
|
5
|
+
* Supports dynamic segments via `:param` syntax. Matches are exact on literal
|
|
6
|
+
* segments and capture dynamic segments into a params object.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* matchWebSocketPath('/ws/chat/:id', '/ws/chat/abc123')
|
|
11
|
+
* // Returns: { id: 'abc123' }
|
|
12
|
+
*
|
|
13
|
+
* matchWebSocketPath('/ws/chat/:id', '/ws/chat/abc123/messages')
|
|
14
|
+
* // Returns: null (length mismatch)
|
|
15
|
+
*
|
|
16
|
+
* matchWebSocketPath('/ws/chat', '/ws/chat')
|
|
17
|
+
* // Returns: {} (no params)
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* This is a minimal segment-based matcher. It does not support wildcards,
|
|
22
|
+
* optional segments, or catch-all patterns. Those are future concerns.
|
|
23
|
+
*
|
|
24
|
+
* @param pattern - The registered route pattern (e.g. '/ws/chat/:id')
|
|
25
|
+
* @param pathname - The actual request pathname (e.g. '/ws/chat/abc123')
|
|
26
|
+
* @returns A params object if the pattern matches, or null if it does not
|
|
27
|
+
*/
|
|
28
|
+
export declare function matchWebSocketPath(pattern: string, pathname: string): Record<string, string> | null;
|
|
29
|
+
/**
|
|
30
|
+
* Represents a matched WebSocket route.
|
|
31
|
+
*/
|
|
32
|
+
export type WebSocketRouteMatch = {
|
|
33
|
+
handler: EcopagesWebSocketHandler<any, any>;
|
|
34
|
+
params: Record<string, string>;
|
|
35
|
+
kind: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Scores a pattern for specificity ordering.
|
|
39
|
+
*
|
|
40
|
+
* Higher scores win. Rules, in order:
|
|
41
|
+
* 1. More literal segments beats more dynamic segments.
|
|
42
|
+
* 2. Fewer dynamic segments beats more dynamic segments.
|
|
43
|
+
* 3. Longer route (more segments) beats shorter route.
|
|
44
|
+
*
|
|
45
|
+
* @param pattern - The registered route pattern
|
|
46
|
+
* @returns A numeric specificity score
|
|
47
|
+
*/
|
|
48
|
+
export declare function scoreWebSocketPattern(pattern: string): number;
|
|
49
|
+
/**
|
|
50
|
+
* Find the registered WebSocket handler that best matches a pathname.
|
|
51
|
+
*
|
|
52
|
+
* Iterates through the provided handlers map, computes each pattern's
|
|
53
|
+
* specificity score, and returns the highest-scoring match. Ties resolve
|
|
54
|
+
* to the earliest-registered pattern.
|
|
55
|
+
*
|
|
56
|
+
* @param handlers - The map of registered WebSocket handlers
|
|
57
|
+
* @param pathname - The actual request pathname
|
|
58
|
+
* @returns The best match, or null if no pattern matches
|
|
59
|
+
*/
|
|
60
|
+
export declare function findWebSocketRoute(handlers: Map<string, EcopagesWebSocketHandler<any, any>>, pathname: string): WebSocketRouteMatch | null;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
function matchWebSocketPath(pattern, pathname) {
|
|
2
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
3
|
+
const pathSegments = pathname.split("/").filter(Boolean);
|
|
4
|
+
if (patternSegments.length !== pathSegments.length) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const params = {};
|
|
8
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
9
|
+
const patternSegment = patternSegments[i];
|
|
10
|
+
const pathSegment = pathSegments[i];
|
|
11
|
+
if (!patternSegment || !pathSegment) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
if (patternSegment.startsWith(":")) {
|
|
15
|
+
const paramName = patternSegment.slice(1);
|
|
16
|
+
if (!paramName) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
params[paramName] = pathSegment;
|
|
20
|
+
} else if (patternSegment !== pathSegment) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return params;
|
|
25
|
+
}
|
|
26
|
+
function scoreWebSocketPattern(pattern) {
|
|
27
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
28
|
+
let literals = 0;
|
|
29
|
+
let dynamics = 0;
|
|
30
|
+
for (const segment of segments) {
|
|
31
|
+
if (segment.startsWith(":")) {
|
|
32
|
+
dynamics += 1;
|
|
33
|
+
} else {
|
|
34
|
+
literals += 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return literals * 100 - dynamics * 10 + segments.length;
|
|
38
|
+
}
|
|
39
|
+
function findWebSocketRoute(handlers, pathname) {
|
|
40
|
+
let best = null;
|
|
41
|
+
let index = 0;
|
|
42
|
+
for (const [pattern, handler] of handlers) {
|
|
43
|
+
const params = matchWebSocketPath(pattern, pathname);
|
|
44
|
+
if (params === null) {
|
|
45
|
+
index += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const score = scoreWebSocketPattern(pattern);
|
|
49
|
+
const match = { handler, params, kind: pattern };
|
|
50
|
+
if (best === null || score > best.score || score === best.score && index < best.index) {
|
|
51
|
+
best = { score, match, index };
|
|
52
|
+
}
|
|
53
|
+
index += 1;
|
|
54
|
+
}
|
|
55
|
+
return best?.match ?? null;
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
findWebSocketRoute,
|
|
59
|
+
matchWebSocketPath,
|
|
60
|
+
scoreWebSocketPattern
|
|
61
|
+
};
|
|
@@ -38,6 +38,9 @@ export declare class BunEcopagesApp<WebSocketData = undefined> extends SharedApp
|
|
|
38
38
|
previewHost: StaticPreviewHost;
|
|
39
39
|
});
|
|
40
40
|
fetch(request: Request): Promise<Response>;
|
|
41
|
+
attachWebSocketUpgrades(httpServer: import('node:http').Server, options?: {
|
|
42
|
+
passthroughUnmatched?: boolean;
|
|
43
|
+
}): Promise<void>;
|
|
41
44
|
/**
|
|
42
45
|
* Complete the initialization of the server adapter by processing dynamic routes
|
|
43
46
|
* @param server The Bun server instance
|
|
@@ -22,6 +22,12 @@ class BunEcopagesApp extends SharedApplicationAdapter {
|
|
|
22
22
|
await this.serverAdapter.completeInitialization(this.server);
|
|
23
23
|
return this.serverAdapter.handleRequest(request);
|
|
24
24
|
}
|
|
25
|
+
async attachWebSocketUpgrades(httpServer, options) {
|
|
26
|
+
if (!this.serverAdapter) {
|
|
27
|
+
this.serverAdapter = await this.initializeServerAdapter();
|
|
28
|
+
}
|
|
29
|
+
this.serverAdapter.attachUserWebSocketUpgrades(httpServer, options);
|
|
30
|
+
}
|
|
25
31
|
/**
|
|
26
32
|
* Complete the initialization of the server adapter by processing dynamic routes
|
|
27
33
|
* @param server The Bun server instance
|
|
@@ -50,12 +56,13 @@ class BunEcopagesApp extends SharedApplicationAdapter {
|
|
|
50
56
|
preferredHostname: binding.preferredHostname,
|
|
51
57
|
composedUrl: binding.runtimeOrigin
|
|
52
58
|
});
|
|
53
|
-
return
|
|
59
|
+
return createBunServerAdapter({
|
|
54
60
|
runtimeOrigin: binding.runtimeOrigin,
|
|
55
61
|
appConfig: this.appConfig,
|
|
56
62
|
apiHandlers: this.apiHandlers,
|
|
57
63
|
staticRoutes: this.staticRoutes,
|
|
58
64
|
errorHandler: this.errorHandler,
|
|
65
|
+
websocketHandlers: this.websocketHandlers.size > 0 ? this.websocketHandlers : void 0,
|
|
59
66
|
options: { watch: binding.watch },
|
|
60
67
|
serveOptions: binding.serveOptions
|
|
61
68
|
});
|
|
@@ -7,6 +7,7 @@ type BunSocketHandler = WebSocketHandler<unknown>;
|
|
|
7
7
|
export interface HmrManagerParams {
|
|
8
8
|
appConfig: EcoPagesAppConfig;
|
|
9
9
|
bridge: ClientBridge;
|
|
10
|
+
registrationTimeoutMs?: number;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* Bun development HMR manager.
|
|
@@ -26,7 +27,7 @@ export declare class HmrManager extends SharedHmrManager {
|
|
|
26
27
|
* generation to `SharedHmrManager`. The Bun subclass only supplies the
|
|
27
28
|
* transport-specific dependency graph policy and websocket hook surface.
|
|
28
29
|
*/
|
|
29
|
-
constructor({ appConfig, bridge }: HmrManagerParams);
|
|
30
|
+
constructor({ appConfig, bridge, registrationTimeoutMs }: HmrManagerParams);
|
|
30
31
|
/**
|
|
31
32
|
* Reuses the shared in-memory dependency graph when possible and otherwise
|
|
32
33
|
* creates the Bun-compatible default graph implementation.
|
|
@@ -13,8 +13,8 @@ class HmrManager extends SharedHmrManager {
|
|
|
13
13
|
* generation to `SharedHmrManager`. The Bun subclass only supplies the
|
|
14
14
|
* transport-specific dependency graph policy and websocket hook surface.
|
|
15
15
|
*/
|
|
16
|
-
constructor({ appConfig, bridge }) {
|
|
17
|
-
super({ appConfig, bridge });
|
|
16
|
+
constructor({ appConfig, bridge, registrationTimeoutMs }) {
|
|
17
|
+
super({ appConfig, bridge, registrationTimeoutMs });
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Reuses the shared in-memory dependency graph when possible and otherwise
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { Server as NodeHttpServer } from 'node:http';
|
|
1
2
|
import type { Server, WebSocketHandler } from 'bun';
|
|
2
3
|
import type { EcoPagesAppConfig } from '../../types/internal-types.js';
|
|
3
|
-
import type { ApiHandler, ErrorHandler, StaticRoute } from '../../types/public-types.js';
|
|
4
|
+
import type { ApiHandler, ErrorHandler, StaticRoute, EcopagesWebSocketHandler } from '../../types/public-types.js';
|
|
4
5
|
import { SharedServerAdapter } from '../shared/server-adapter.js';
|
|
5
6
|
import type { ServerAdapterResult } from '../abstract/server-adapter.js';
|
|
6
7
|
import type { StaticPreviewHost } from '../shared/static-preview-host.js';
|
|
@@ -32,6 +33,7 @@ export interface BunServerAdapterParams {
|
|
|
32
33
|
apiHandlers?: ApiHandler<string, Request, BunServerInstance>[];
|
|
33
34
|
staticRoutes?: StaticRoute[];
|
|
34
35
|
errorHandler?: ErrorHandler;
|
|
36
|
+
websocketHandlers?: Map<string, EcopagesWebSocketHandler<any, any>>;
|
|
35
37
|
options?: {
|
|
36
38
|
watch?: boolean;
|
|
37
39
|
};
|
|
@@ -48,6 +50,9 @@ export interface BunServerAdapterResult extends ServerAdapterResult {
|
|
|
48
50
|
}) => Promise<void>;
|
|
49
51
|
completeInitialization: (server?: BunServerInstance | null) => Promise<void>;
|
|
50
52
|
handleRequest: (request: Request) => Promise<Response>;
|
|
53
|
+
attachUserWebSocketUpgrades: (server: NodeHttpServer, options?: {
|
|
54
|
+
passthroughUnmatched?: boolean;
|
|
55
|
+
}) => void;
|
|
51
56
|
}
|
|
52
57
|
/**
|
|
53
58
|
* Bun transport adapter that wires shared Ecopages request handling onto a live
|
|
@@ -72,6 +77,47 @@ export declare class BunServerAdapter extends SharedServerAdapter<BunServerAdapt
|
|
|
72
77
|
private fullyInitialized;
|
|
73
78
|
serverInstance: BunServerInstance | null;
|
|
74
79
|
private readonly previewHost;
|
|
80
|
+
/**
|
|
81
|
+
* Reference to the application-level WebSocket handlers map.
|
|
82
|
+
*
|
|
83
|
+
* @remarks
|
|
84
|
+
* This is a reference to the map owned by `AbstractApplicationAdapter`,
|
|
85
|
+
* passed in via the constructor. The Bun adapter reads from it to wire
|
|
86
|
+
* WebSocket upgrades for user-registered patterns.
|
|
87
|
+
*/
|
|
88
|
+
protected websocketHandlers: Map<string, EcopagesWebSocketHandler<any, any>>;
|
|
89
|
+
/**
|
|
90
|
+
* Adapts a Bun ServerWebSocket to the public EcopagesSocket interface.
|
|
91
|
+
*
|
|
92
|
+
* @remarks
|
|
93
|
+
* This is the single source of truth for the Bun→public type adaptation.
|
|
94
|
+
* Both the HMR and production branches use this helper to ensure consistency.
|
|
95
|
+
*
|
|
96
|
+
* @param ws - The raw Bun ServerWebSocket
|
|
97
|
+
* @param kind - The registered route pattern
|
|
98
|
+
* @param params - Dynamic path parameters
|
|
99
|
+
* @param search - Query string parameters
|
|
100
|
+
* @param context - The resolved per-connection context
|
|
101
|
+
* @returns An EcopagesSocket view
|
|
102
|
+
*/
|
|
103
|
+
private adaptBunWebSocket;
|
|
104
|
+
/**
|
|
105
|
+
* Resolves the per-connection context for a Bun user connection.
|
|
106
|
+
*
|
|
107
|
+
* @remarks
|
|
108
|
+
* `context()` is invoked exactly once per accepted connection. Its
|
|
109
|
+
* resolved value is shared by all subsequent lifecycle hooks. If
|
|
110
|
+
* `context()` throws, the connection is closed immediately with code
|
|
111
|
+
* 1011 (server error) and the error is logged.
|
|
112
|
+
*
|
|
113
|
+
* @param request - The original upgrade request (best-effort reconstructed)
|
|
114
|
+
* @param handler - The registered handler
|
|
115
|
+
* @param kind - The registered route pattern
|
|
116
|
+
* @param params - Dynamic path parameters
|
|
117
|
+
* @param search - Query string parameters
|
|
118
|
+
* @returns The resolved per-connection context
|
|
119
|
+
*/
|
|
120
|
+
private resolveBunContext;
|
|
75
121
|
/**
|
|
76
122
|
* Creates a Bun server adapter with already-resolved runtime collaborators.
|
|
77
123
|
*
|
|
@@ -81,11 +127,17 @@ export declare class BunServerAdapter extends SharedServerAdapter<BunServerAdapt
|
|
|
81
127
|
* is constructed, those collaborators are mandatory because the adapter cannot
|
|
82
128
|
* initialize Bun HMR or preview flows without them.
|
|
83
129
|
*/
|
|
84
|
-
constructor({ appConfig, runtimeOrigin, serveOptions, apiHandlers, staticRoutes, errorHandler, options, hmrManager, bridge, previewHost, }: BunServerAdapterParams & {
|
|
130
|
+
constructor({ appConfig, runtimeOrigin, serveOptions, apiHandlers, staticRoutes, errorHandler, websocketHandlers, options, hmrManager, bridge, previewHost, }: BunServerAdapterParams & {
|
|
85
131
|
hmrManager: HmrManager;
|
|
86
132
|
bridge: ClientBridge;
|
|
87
133
|
previewHost: StaticPreviewHost;
|
|
88
134
|
});
|
|
135
|
+
/**
|
|
136
|
+
* Wires user WebSocket routes onto a Node HTTP server used by host integrations.
|
|
137
|
+
*/
|
|
138
|
+
attachUserWebSocketUpgrades(server: NodeHttpServer, options?: {
|
|
139
|
+
passthroughUnmatched?: boolean;
|
|
140
|
+
}): void;
|
|
89
141
|
/**
|
|
90
142
|
* Returns whether adapter-level HTML responses still need HMR runtime injection.
|
|
91
143
|
*
|
|
@@ -134,9 +186,13 @@ export declare class BunServerAdapter extends SharedServerAdapter<BunServerAdapt
|
|
|
134
186
|
* Builds the `Bun.serve()` options for the current adapter state.
|
|
135
187
|
*
|
|
136
188
|
* @remarks
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
189
|
+
* When HMR is enabled the websocket dispatcher merges HMR and user handlers,
|
|
190
|
+
* routing by the `kind` field embedded in socket data at upgrade time.
|
|
191
|
+
* This prevents user-registered websocket handlers from being silently overwritten
|
|
192
|
+
* by the HMR handler in development mode.
|
|
193
|
+
*
|
|
194
|
+
* User WebSocket paths are intercepted in the fetch handler via the pattern matcher.
|
|
195
|
+
* The upgrade happens implicitly — no manual GET route registration needed.
|
|
140
196
|
*/
|
|
141
197
|
getServerOptions({ enableHmr }?: {
|
|
142
198
|
enableHmr?: boolean | undefined;
|