@ecopages/core 0.2.0-beta.29 → 0.2.0-beta.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -7
- package/package.json +2 -2
- package/src/adapters/README.md +6 -2
- package/src/adapters/abstract/segment-path-matcher.d.ts +6 -0
- package/src/adapters/abstract/segment-path-matcher.js +149 -0
- package/src/adapters/abstract/ws-pattern-matcher.d.ts +2 -50
- package/src/adapters/abstract/ws-pattern-matcher.js +5 -36
- package/src/adapters/{shared → bun}/bun-user-websocket-lifecycle.js +1 -1
- package/src/adapters/bun/create-app.d.ts +2 -2
- package/src/adapters/bun/create-app.js +2 -2
- package/src/adapters/bun/hmr-manager.d.ts +1 -1
- package/src/adapters/bun/hmr-manager.js +1 -1
- package/src/adapters/bun/index.d.ts +1 -1
- package/src/adapters/bun/index.js +25 -2
- package/src/adapters/bun/runtime-host.d.ts +1 -1
- package/src/adapters/bun/runtime-host.js +1 -1
- package/src/adapters/bun/server-adapter.d.ts +8 -25
- package/src/adapters/bun/server-adapter.js +21 -65
- package/src/adapters/bun/static-preview-host.d.ts +1 -1
- package/src/adapters/bun/static-preview-host.js +1 -1
- package/src/adapters/index.d.ts +1 -1
- package/src/adapters/index.js +25 -2
- package/src/adapters/node/create-app.d.ts +2 -2
- package/src/adapters/node/create-app.js +2 -2
- package/src/adapters/node/node-hmr-manager.d.ts +1 -1
- package/src/adapters/node/node-hmr-manager.js +1 -1
- package/src/adapters/node/runtime-host.d.ts +1 -1
- package/src/adapters/node/runtime-host.js +1 -1
- package/src/adapters/node/server-adapter.d.ts +2 -3
- package/src/adapters/node/server-adapter.js +15 -18
- package/src/adapters/node/static-preview-host.d.ts +1 -1
- package/src/adapters/node/static-preview-host.js +1 -1
- package/src/adapters/shared/{hmr-entrypoint-registrar.d.ts → hmr/hmr-entrypoint-registrar.d.ts} +1 -1
- package/src/adapters/shared/{hmr-entrypoint-registrar.js → hmr/hmr-entrypoint-registrar.js} +1 -1
- package/src/adapters/shared/{hmr-html-response.d.ts → hmr/hmr-html-response.d.ts} +1 -1
- package/src/adapters/shared/{shared-hmr-manager.d.ts → hmr/shared-hmr-manager.d.ts} +9 -8
- package/src/adapters/shared/{shared-hmr-manager.js → hmr/shared-hmr-manager.js} +41 -15
- package/src/adapters/shared/http/api-request-pipeline.d.ts +23 -0
- package/src/adapters/shared/http/api-request-pipeline.js +129 -0
- package/src/adapters/shared/{api-response.d.ts → http/api-response.d.ts} +1 -1
- package/src/adapters/shared/{api-response.js → http/api-response.js} +6 -23
- package/src/adapters/shared/http/create-body-response.d.ts +12 -0
- package/src/adapters/shared/http/create-body-response.js +23 -0
- package/src/adapters/shared/http/define-api-handler.d.ts +90 -0
- package/src/adapters/shared/http/define-api-handler.js +63 -0
- package/src/adapters/shared/{explicit-static-render-preparation.d.ts → http/explicit-static-render-preparation.d.ts} +3 -3
- package/src/adapters/shared/{explicit-static-route-matcher.d.ts → http/explicit-static-route-matcher.d.ts} +3 -9
- package/src/adapters/shared/{explicit-static-route-matcher.js → http/explicit-static-route-matcher.js} +3 -38
- package/src/adapters/shared/{file-route-middleware-pipeline.d.ts → http/file-route-middleware-pipeline.d.ts} +2 -2
- package/src/adapters/shared/{file-route-middleware-pipeline.js → http/file-route-middleware-pipeline.js} +10 -22
- package/src/adapters/shared/{fs-server-response-factory.d.ts → http/fs-server-response-factory.d.ts} +2 -2
- package/src/adapters/shared/{fs-server-response-factory.js → http/fs-server-response-factory.js} +2 -2
- package/src/adapters/shared/{fs-server-response-matcher.d.ts → http/fs-server-response-matcher.d.ts} +5 -5
- package/src/adapters/shared/{fs-server-response-matcher.js → http/fs-server-response-matcher.js} +5 -5
- package/src/adapters/shared/http/run-middleware-chain.d.ts +5 -0
- package/src/adapters/shared/http/run-middleware-chain.js +17 -0
- package/src/adapters/shared/{application-adapter.d.ts → runtime/application-adapter.d.ts} +2 -2
- package/src/adapters/shared/{application-adapter.js → runtime/application-adapter.js} +1 -1
- package/src/adapters/shared/{render-context.d.ts → runtime/render-context.d.ts} +2 -2
- package/src/adapters/shared/{render-context.js → runtime/render-context.js} +8 -17
- package/src/adapters/shared/{runtime-app-bootstrap.d.ts → runtime/runtime-app-bootstrap.d.ts} +2 -2
- package/src/adapters/shared/{runtime-app-bootstrap.js → runtime/runtime-app-bootstrap.js} +1 -1
- package/src/adapters/shared/{runtime-server-lifecycle.d.ts → runtime/runtime-server-lifecycle.d.ts} +13 -13
- package/src/adapters/shared/{runtime-server-lifecycle.js → runtime/runtime-server-lifecycle.js} +22 -15
- package/src/adapters/shared/{server-adapter.d.ts → runtime/server-adapter.d.ts} +20 -48
- package/src/adapters/shared/runtime/server-adapter.js +226 -0
- package/src/adapters/shared/{server-route-handler.d.ts → runtime/server-route-handler.d.ts} +6 -36
- package/src/adapters/shared/{server-route-handler.js → runtime/server-route-handler.js} +6 -47
- package/src/adapters/shared/{server-static-builder.d.ts → runtime/server-static-builder.d.ts} +6 -6
- package/src/adapters/shared/{server-static-builder.js → runtime/server-static-builder.js} +8 -8
- package/src/adapters/shared/{static-preview-host.d.ts → runtime/static-preview-host.d.ts} +1 -1
- package/src/adapters/shared/{node-http-websocket-upgrades.d.ts → ws/node-http-websocket-upgrades.d.ts} +1 -1
- package/src/adapters/shared/{node-http-websocket-upgrades.js → ws/node-http-websocket-upgrades.js} +2 -2
- package/src/adapters/shared/{websocket-lifecycle.d.ts → ws/websocket-lifecycle.d.ts} +1 -1
- package/src/adapters/shared/{websocket-lifecycle.js → ws/websocket-lifecycle.js} +1 -1
- package/src/build/app-build-manifest-runtime.d.ts +4 -0
- package/src/build/app-build-manifest-runtime.js +19 -9
- package/src/index.d.ts +1 -1
- package/src/index.js +24 -1
- package/src/static-site-generator/static-site-generator.js +1 -1
- package/src/types/internal-types.d.ts +8 -2
- package/src/types/public-types.d.ts +9 -1
- package/src/adapters/shared/define-api-handler.d.ts +0 -25
- package/src/adapters/shared/define-api-handler.js +0 -15
- package/src/adapters/shared/server-adapter.js +0 -452
- /package/src/adapters/{shared → bun}/bun-user-websocket-lifecycle.d.ts +0 -0
- /package/src/adapters/shared/{hmr-html-response.js → hmr/hmr-html-response.js} +0 -0
- /package/src/adapters/shared/{explicit-static-render-preparation.js → http/explicit-static-render-preparation.js} +0 -0
- /package/src/adapters/shared/{copy-runtime-public-dir.d.ts → runtime/copy-runtime-public-dir.d.ts} +0 -0
- /package/src/adapters/shared/{copy-runtime-public-dir.js → runtime/copy-runtime-public-dir.js} +0 -0
- /package/src/adapters/shared/{port-manager.d.ts → runtime/port-manager.d.ts} +0 -0
- /package/src/adapters/shared/{port-manager.js → runtime/port-manager.js} +0 -0
- /package/src/adapters/shared/{runtime-host.d.ts → runtime/runtime-host.d.ts} +0 -0
- /package/src/adapters/shared/{runtime-host.js → runtime/runtime-host.js} +0 -0
- /package/src/adapters/shared/{static-preview-host.js → runtime/static-preview-host.js} +0 -0
package/README.md
CHANGED
|
@@ -186,31 +186,60 @@ Dependency ownership affects final asset packaging:
|
|
|
186
186
|
|
|
187
187
|
### 5. API Handlers
|
|
188
188
|
|
|
189
|
-
Add server-side routes using `defineApiHandler
|
|
189
|
+
Add server-side routes using `defineApiHandler` or the method helpers (`defineGet`, `definePost`, …). Register them on your `app` instance before starting:
|
|
190
190
|
|
|
191
191
|
```typescript
|
|
192
|
-
import { defineApiHandler } from '@ecopages/core';
|
|
192
|
+
import { defineApiHandler, defineGet, json } from '@ecopages/core';
|
|
193
193
|
|
|
194
|
-
export const helloWorld =
|
|
194
|
+
export const helloWorld = defineGet({
|
|
195
195
|
path: '/api/hello',
|
|
196
|
-
method: 'GET',
|
|
197
196
|
handler: async ({ response }) => {
|
|
198
197
|
return response.json({ message: 'Hello World' });
|
|
199
198
|
},
|
|
200
199
|
});
|
|
200
|
+
|
|
201
|
+
// Equivalent with an explicit method:
|
|
202
|
+
export const helloWorldAlt = defineApiHandler({
|
|
203
|
+
path: '/api/hello',
|
|
204
|
+
method: 'GET',
|
|
205
|
+
handler: async () => json({ message: 'Hello World' }),
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Group related routes with `defineGroupHandler`. Prefer `api.get` / `api.post` helpers inside the `routes` callback:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { defineGroupHandler } from '@ecopages/core';
|
|
213
|
+
|
|
214
|
+
export const adminGroup = defineGroupHandler({
|
|
215
|
+
prefix: '/admin',
|
|
216
|
+
routes: (api) => [
|
|
217
|
+
api.get({
|
|
218
|
+
path: '/',
|
|
219
|
+
handler: async ({ response }) => response.json({ ok: true }),
|
|
220
|
+
}),
|
|
221
|
+
api.post({
|
|
222
|
+
path: '/items',
|
|
223
|
+
handler: async ({ response }) => response.status(201).json({ created: true }),
|
|
224
|
+
}),
|
|
225
|
+
],
|
|
226
|
+
});
|
|
201
227
|
```
|
|
202
228
|
|
|
229
|
+
Standalone response helpers (`json`, `html`, `redirect`) share the same body emission path as `context.response.json()` / `context.response.html()`.
|
|
230
|
+
|
|
231
|
+
Register a prebuilt handler with `app.add()`. Use `app.get(path, handler)` only when defining the route inline in `app.ts`.
|
|
232
|
+
|
|
203
233
|
Attach the handler in your `app.ts` entry:
|
|
204
234
|
|
|
205
235
|
```typescript
|
|
206
|
-
// app.ts
|
|
207
236
|
import { createApp } from '@ecopages/core/create-app';
|
|
208
237
|
import { helloWorld } from './handlers/hello';
|
|
209
238
|
import appConfig from './eco.config';
|
|
210
239
|
|
|
211
240
|
const app = await createApp({ appConfig });
|
|
212
241
|
|
|
213
|
-
app.
|
|
242
|
+
app.add(helloWorld);
|
|
214
243
|
|
|
215
244
|
await app.start();
|
|
216
245
|
```
|
|
@@ -223,7 +252,7 @@ Use the `create-app` subpath for runtime startup and the root package for standa
|
|
|
223
252
|
|
|
224
253
|
```ts
|
|
225
254
|
import { createApp } from '@ecopages/core/create-app';
|
|
226
|
-
import { defineApiHandler, defineGroupHandler, eco } from '@ecopages/core';
|
|
255
|
+
import { defineApiHandler, defineGet, defineGroupHandler, eco } from '@ecopages/core';
|
|
227
256
|
```
|
|
228
257
|
|
|
229
258
|
> [!NOTE]
|
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.30",
|
|
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.30",
|
|
21
21
|
"@ecopages/logger": "^0.2.3",
|
|
22
22
|
"@ecopages/scripts-injector": "^0.1.5",
|
|
23
23
|
"@oxc-project/runtime": "0.134.0",
|
package/src/adapters/README.md
CHANGED
|
@@ -22,9 +22,13 @@ They are not responsible for:
|
|
|
22
22
|
|
|
23
23
|
## Main Areas
|
|
24
24
|
|
|
25
|
-
- `bun/`: Bun server adapter, lifecycle coordination, bridge, and
|
|
25
|
+
- `bun/`: Bun server adapter, lifecycle coordination, bridge, HMR transport, and Bun user WebSocket lifecycle
|
|
26
26
|
- `node/`: internal Node compatibility adapter pieces used by the CLI thin-host path
|
|
27
|
-
- `shared/`: runtime-neutral adapter helpers used by both hosts
|
|
27
|
+
- `shared/`: runtime-neutral adapter helpers used by both hosts, clustered by concern:
|
|
28
|
+
- `shared/http/` — API response builders, request pipeline, middleware runner, define-api helpers, explicit-static and filesystem matchers
|
|
29
|
+
- `shared/hmr/` — HMR HTML inject helpers, shared HMR manager, entrypoint registrar
|
|
30
|
+
- `shared/runtime/` — shared server adapter, route handler, application adapter, static builder, port/bootstrap utilities
|
|
31
|
+
- `shared/ws/` — WinterCG WebSocket lifecycle helpers and Node HTTP upgrade bridge (Bun-specific user WS lifecycle lives under `bun/`)
|
|
28
32
|
|
|
29
33
|
## Ownership Boundary
|
|
30
34
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function normalizeSegmentPath(pathname: string): string;
|
|
2
|
+
export declare function matchApiPathPattern(pattern: string, pathname: string): Record<string, string | string[]> | null;
|
|
3
|
+
export declare function scoreApiPathPattern(pattern: string): number;
|
|
4
|
+
export declare function matchExplicitStaticPathPattern(pattern: string, pathname: string): Record<string, string> | null;
|
|
5
|
+
export declare function matchColonSegmentPath(pattern: string, pathname: string): Record<string, string> | null;
|
|
6
|
+
export declare function scoreColonSegmentPath(pattern: string): number;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
function normalizeSegmentPath(pathname) {
|
|
2
|
+
if (pathname.length > 1 && pathname.endsWith("/")) {
|
|
3
|
+
return pathname.slice(0, -1);
|
|
4
|
+
}
|
|
5
|
+
return pathname;
|
|
6
|
+
}
|
|
7
|
+
function matchApiPathPattern(pattern, pathname) {
|
|
8
|
+
const normalizedPattern = normalizeSegmentPath(pattern);
|
|
9
|
+
const normalizedPathname = normalizeSegmentPath(pathname);
|
|
10
|
+
const patternSegments = normalizedPattern.split("/").filter(Boolean);
|
|
11
|
+
const pathSegments = normalizedPathname.split("/").filter(Boolean);
|
|
12
|
+
const params = {};
|
|
13
|
+
let patternIndex = 0;
|
|
14
|
+
let pathIndex = 0;
|
|
15
|
+
while (patternIndex < patternSegments.length && pathIndex < pathSegments.length) {
|
|
16
|
+
const patternSegment = patternSegments[patternIndex];
|
|
17
|
+
const pathSegment = pathSegments[pathIndex];
|
|
18
|
+
if (patternSegment === "*") {
|
|
19
|
+
return params;
|
|
20
|
+
}
|
|
21
|
+
if (patternSegment.startsWith("[...") && patternSegment.endsWith("]")) {
|
|
22
|
+
const paramName = patternSegment.slice(4, -1);
|
|
23
|
+
params[paramName] = pathSegments.slice(pathIndex);
|
|
24
|
+
return params;
|
|
25
|
+
}
|
|
26
|
+
if (patternSegment.startsWith(":")) {
|
|
27
|
+
params[patternSegment.slice(1)] = pathSegment;
|
|
28
|
+
patternIndex++;
|
|
29
|
+
pathIndex++;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (patternSegment.startsWith("[") && patternSegment.endsWith("]")) {
|
|
33
|
+
params[patternSegment.slice(1, -1)] = pathSegment;
|
|
34
|
+
patternIndex++;
|
|
35
|
+
pathIndex++;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (patternSegment !== pathSegment) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
patternIndex++;
|
|
42
|
+
pathIndex++;
|
|
43
|
+
}
|
|
44
|
+
if (patternIndex < patternSegments.length) {
|
|
45
|
+
const remaining = patternSegments.slice(patternIndex);
|
|
46
|
+
const catchAll = remaining[0];
|
|
47
|
+
if (remaining.length === 1 && (catchAll === "*" || catchAll.startsWith("[...") && catchAll.endsWith("]"))) {
|
|
48
|
+
if (catchAll.startsWith("[...")) {
|
|
49
|
+
const paramName = catchAll.slice(4, -1);
|
|
50
|
+
params[paramName] = [];
|
|
51
|
+
}
|
|
52
|
+
return params;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (pathIndex < pathSegments.length) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return params;
|
|
60
|
+
}
|
|
61
|
+
function scoreApiPathPattern(pattern) {
|
|
62
|
+
const segments = normalizeSegmentPath(pattern).split("/").filter(Boolean);
|
|
63
|
+
let score = 0;
|
|
64
|
+
for (const segment of segments) {
|
|
65
|
+
if (segment === "*" || segment.startsWith("[...") && segment.endsWith("]")) {
|
|
66
|
+
score += 10;
|
|
67
|
+
} else if (segment.startsWith(":") || segment.startsWith("[") && segment.endsWith("]")) {
|
|
68
|
+
score += 50;
|
|
69
|
+
} else {
|
|
70
|
+
score += 100;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return score;
|
|
74
|
+
}
|
|
75
|
+
function matchExplicitStaticPathPattern(pattern, pathname) {
|
|
76
|
+
const patternSegments = normalizeSegmentPath(pattern).split("/").filter(Boolean);
|
|
77
|
+
const pathSegments = normalizeSegmentPath(pathname).split("/").filter(Boolean);
|
|
78
|
+
if (patternSegments.length !== pathSegments.length) {
|
|
79
|
+
const lastPattern = patternSegments[patternSegments.length - 1];
|
|
80
|
+
const isCatchAll = lastPattern?.startsWith("[...") || lastPattern?.startsWith(":...");
|
|
81
|
+
if (!isCatchAll) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const params = {};
|
|
86
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
87
|
+
const patternPart = patternSegments[i];
|
|
88
|
+
const pathPart = pathSegments[i];
|
|
89
|
+
if (patternPart.startsWith(":...") || patternPart.startsWith("[...")) {
|
|
90
|
+
const paramName = patternPart.replace(/^(:\.\.\.|\[\.\.\.)/, "").replace(/\]$/, "");
|
|
91
|
+
params[paramName] = pathSegments.slice(i).join("/");
|
|
92
|
+
return params;
|
|
93
|
+
}
|
|
94
|
+
if (patternPart.startsWith(":")) {
|
|
95
|
+
params[patternPart.slice(1)] = pathPart;
|
|
96
|
+
} else if (patternPart.startsWith("[") && patternPart.endsWith("]")) {
|
|
97
|
+
params[patternPart.slice(1, -1)] = pathPart;
|
|
98
|
+
} else if (patternPart !== pathPart) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return params;
|
|
103
|
+
}
|
|
104
|
+
function matchColonSegmentPath(pattern, pathname) {
|
|
105
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
106
|
+
const pathSegments = pathname.split("/").filter(Boolean);
|
|
107
|
+
if (patternSegments.length !== pathSegments.length) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const params = {};
|
|
111
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
112
|
+
const patternSegment = patternSegments[i];
|
|
113
|
+
const pathSegment = pathSegments[i];
|
|
114
|
+
if (!patternSegment || !pathSegment) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
if (patternSegment.startsWith(":")) {
|
|
118
|
+
const paramName = patternSegment.slice(1);
|
|
119
|
+
if (!paramName) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
params[paramName] = pathSegment;
|
|
123
|
+
} else if (patternSegment !== pathSegment) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return params;
|
|
128
|
+
}
|
|
129
|
+
function scoreColonSegmentPath(pattern) {
|
|
130
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
131
|
+
let literals = 0;
|
|
132
|
+
let dynamics = 0;
|
|
133
|
+
for (const segment of segments) {
|
|
134
|
+
if (segment.startsWith(":")) {
|
|
135
|
+
dynamics += 1;
|
|
136
|
+
} else {
|
|
137
|
+
literals += 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return literals * 100 - dynamics * 10 + segments.length;
|
|
141
|
+
}
|
|
142
|
+
export {
|
|
143
|
+
matchApiPathPattern,
|
|
144
|
+
matchColonSegmentPath,
|
|
145
|
+
matchExplicitStaticPathPattern,
|
|
146
|
+
normalizeSegmentPath,
|
|
147
|
+
scoreApiPathPattern,
|
|
148
|
+
scoreColonSegmentPath
|
|
149
|
+
};
|
|
@@ -1,60 +1,12 @@
|
|
|
1
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
2
|
export type WebSocketRouteMatch = {
|
|
33
3
|
handler: EcopagesWebSocketHandler<any, any>;
|
|
34
4
|
params: Record<string, string>;
|
|
35
5
|
kind: string;
|
|
36
6
|
};
|
|
37
7
|
/**
|
|
38
|
-
*
|
|
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
|
|
8
|
+
* Pattern matcher for WebSocket route paths.
|
|
47
9
|
*/
|
|
10
|
+
export declare function matchWebSocketPath(pattern: string, pathname: string): Record<string, string> | null;
|
|
48
11
|
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
12
|
export declare function findWebSocketRoute(handlers: Map<string, EcopagesWebSocketHandler<any, any>>, pathname: string): WebSocketRouteMatch | null;
|
|
@@ -1,51 +1,20 @@
|
|
|
1
|
+
import { matchColonSegmentPath, scoreColonSegmentPath } from "./segment-path-matcher.js";
|
|
1
2
|
function matchWebSocketPath(pattern, pathname) {
|
|
2
|
-
|
|
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;
|
|
3
|
+
return matchColonSegmentPath(pattern, pathname);
|
|
25
4
|
}
|
|
26
5
|
function scoreWebSocketPattern(pattern) {
|
|
27
|
-
|
|
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;
|
|
6
|
+
return scoreColonSegmentPath(pattern);
|
|
38
7
|
}
|
|
39
8
|
function findWebSocketRoute(handlers, pathname) {
|
|
40
9
|
let best = null;
|
|
41
10
|
let index = 0;
|
|
42
11
|
for (const [pattern, handler] of handlers) {
|
|
43
|
-
const params =
|
|
12
|
+
const params = matchColonSegmentPath(pattern, pathname);
|
|
44
13
|
if (params === null) {
|
|
45
14
|
index += 1;
|
|
46
15
|
continue;
|
|
47
16
|
}
|
|
48
|
-
const score =
|
|
17
|
+
const score = scoreColonSegmentPath(pattern);
|
|
49
18
|
const match = { handler, params, kind: pattern };
|
|
50
19
|
if (best === null || score > best.score || score === best.score && index < best.index) {
|
|
51
20
|
best = { score, match, index };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { appLogger } from "../../global/app-logger.js";
|
|
2
|
-
import { invokeWebSocketHandlerHook, toWebSocketCloseInfo } from "
|
|
2
|
+
import { invokeWebSocketHandlerHook, toWebSocketCloseInfo } from "../shared/ws/websocket-lifecycle.js";
|
|
3
3
|
function toUpgradeRequest(ws, runtimeOrigin) {
|
|
4
4
|
const upgradeUrl = ws.data?.upgradeUrl;
|
|
5
5
|
if (upgradeUrl) {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { Server } from 'bun';
|
|
11
11
|
import type { ApiHandlerContext, RouteGroupBuilder } from '../../types/public-types.js';
|
|
12
|
-
import { SharedApplicationAdapter } from '../shared/application-adapter.js';
|
|
13
|
-
import type { RuntimeHost } from '../shared/runtime-host.js';
|
|
12
|
+
import { SharedApplicationAdapter } from '../shared/runtime/application-adapter.js';
|
|
13
|
+
import type { RuntimeHost } from '../shared/runtime/runtime-host.js';
|
|
14
14
|
import type { EcopagesAppOptions } from '../create-app.js';
|
|
15
15
|
import { type BunServerAdapterResult } from './server-adapter.js';
|
|
16
16
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { appLogger } from "../../global/app-logger.js";
|
|
2
|
-
import { SharedApplicationAdapter } from "../shared/application-adapter.js";
|
|
3
|
-
import { resolveRuntimeBinding, resolveStaticRuntimeMode } from "../shared/runtime-app-bootstrap.js";
|
|
2
|
+
import { SharedApplicationAdapter } from "../shared/runtime/application-adapter.js";
|
|
3
|
+
import { resolveRuntimeBinding, resolveStaticRuntimeMode } from "../shared/runtime/runtime-app-bootstrap.js";
|
|
4
4
|
import { startupTrace } from "../../diagnostics/startup-trace.js";
|
|
5
5
|
import { createBunServerAdapter } from "./server-adapter.js";
|
|
6
6
|
import { BunRuntimeHost } from "./runtime-host.js";
|
|
@@ -2,7 +2,7 @@ import type { WebSocketHandler } from 'bun';
|
|
|
2
2
|
import type { EcoPagesAppConfig } from '../../types/internal-types.js';
|
|
3
3
|
import type { ClientBridge } from './client-bridge.js';
|
|
4
4
|
import { InMemoryEntrypointDependencyGraph, type EntrypointDependencyGraph } from '../../services/runtime-state/entrypoint-dependency-graph.service.js';
|
|
5
|
-
import { SharedHmrManager } from '../shared/shared-hmr-manager.js';
|
|
5
|
+
import { SharedHmrManager } from '../shared/hmr/shared-hmr-manager.js';
|
|
6
6
|
type BunSocketHandler = WebSocketHandler<unknown>;
|
|
7
7
|
export interface HmrManagerParams {
|
|
8
8
|
appConfig: EcoPagesAppConfig;
|
|
@@ -2,7 +2,7 @@ import { appLogger } from "../../global/app-logger.js";
|
|
|
2
2
|
import {
|
|
3
3
|
InMemoryEntrypointDependencyGraph
|
|
4
4
|
} from "../../services/runtime-state/entrypoint-dependency-graph.service.js";
|
|
5
|
-
import { SharedHmrManager } from "../shared/shared-hmr-manager.js";
|
|
5
|
+
import { SharedHmrManager } from "../shared/hmr/shared-hmr-manager.js";
|
|
6
6
|
class HmrManager extends SharedHmrManager {
|
|
7
7
|
wsHandler;
|
|
8
8
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { BunEcopagesApp, createApp } from './create-app.js';
|
|
2
|
-
export { defineApiHandler, defineGroupHandler, type GroupHandler } from '../shared/define-api-handler.js';
|
|
2
|
+
export { defineApiHandler, defineGroupHandler, defineGet, definePost, definePut, defineDelete, definePatch, defineOptions, defineHead, json, html, redirect, type GroupHandler, } from '../shared/http/define-api-handler.js';
|
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
import { BunEcopagesApp, createApp } from "./create-app.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
defineApiHandler,
|
|
4
|
+
defineGroupHandler,
|
|
5
|
+
defineGet,
|
|
6
|
+
definePost,
|
|
7
|
+
definePut,
|
|
8
|
+
defineDelete,
|
|
9
|
+
definePatch,
|
|
10
|
+
defineOptions,
|
|
11
|
+
defineHead,
|
|
12
|
+
json,
|
|
13
|
+
html,
|
|
14
|
+
redirect
|
|
15
|
+
} from "../shared/http/define-api-handler.js";
|
|
3
16
|
export {
|
|
4
17
|
BunEcopagesApp,
|
|
5
18
|
createApp,
|
|
6
19
|
defineApiHandler,
|
|
7
|
-
|
|
20
|
+
defineDelete,
|
|
21
|
+
defineGet,
|
|
22
|
+
defineGroupHandler,
|
|
23
|
+
defineHead,
|
|
24
|
+
defineOptions,
|
|
25
|
+
definePatch,
|
|
26
|
+
definePost,
|
|
27
|
+
definePut,
|
|
28
|
+
html,
|
|
29
|
+
json,
|
|
30
|
+
redirect
|
|
8
31
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Server } from 'bun';
|
|
2
|
-
import type { RuntimeHost, RuntimeHostStartOptions } from '../shared/runtime-host.js';
|
|
2
|
+
import type { RuntimeHost, RuntimeHostStartOptions } from '../shared/runtime/runtime-host.js';
|
|
3
3
|
type BunRuntimeProvider = {
|
|
4
4
|
serve(options: Bun.Serve.Options<unknown>): Server<unknown>;
|
|
5
5
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getBunRuntime } from "../../utils/runtime.js";
|
|
2
|
-
import { resolveServeRuntimeOrigin } from "../shared/runtime-app-bootstrap.js";
|
|
2
|
+
import { resolveServeRuntimeOrigin } from "../shared/runtime/runtime-app-bootstrap.js";
|
|
3
3
|
class BunRuntimeHost {
|
|
4
4
|
/**
|
|
5
5
|
* Creates a Bun runtime host with an injectable runtime lookup.
|
|
@@ -2,9 +2,9 @@ import type { Server as NodeHttpServer } from 'node:http';
|
|
|
2
2
|
import type { Server, WebSocketHandler } from 'bun';
|
|
3
3
|
import type { EcoPagesAppConfig } from '../../types/internal-types.js';
|
|
4
4
|
import type { ApiHandler, ErrorHandler, StaticRoute, EcopagesWebSocketHandler } from '../../types/public-types.js';
|
|
5
|
-
import { SharedServerAdapter } from '../shared/server-adapter.js';
|
|
5
|
+
import { SharedServerAdapter } from '../shared/runtime/server-adapter.js';
|
|
6
6
|
import type { ServerAdapterResult } from '../abstract/server-adapter.js';
|
|
7
|
-
import type { StaticPreviewHost } from '../shared/static-preview-host.js';
|
|
7
|
+
import type { StaticPreviewHost } from '../shared/runtime/static-preview-host.js';
|
|
8
8
|
import { ClientBridge } from './client-bridge.js';
|
|
9
9
|
import { HmrManager } from './hmr-manager.js';
|
|
10
10
|
type BunServerInstance = Server<unknown>;
|
|
@@ -136,15 +136,6 @@ export declare class BunServerAdapter extends SharedServerAdapter<BunServerAdapt
|
|
|
136
136
|
attachUserWebSocketUpgrades(server: NodeHttpServer, options?: {
|
|
137
137
|
passthroughUnmatched?: boolean;
|
|
138
138
|
}): void;
|
|
139
|
-
/**
|
|
140
|
-
* Returns whether adapter-level HTML responses still need HMR runtime injection.
|
|
141
|
-
*
|
|
142
|
-
* @remarks
|
|
143
|
-
* Filesystem-routed pages are wrapped later in the shared route layer. This
|
|
144
|
-
* adapter-level check exists for explicit API handlers that return HTML and
|
|
145
|
-
* would otherwise bypass the route wrapper entirely.
|
|
146
|
-
*/
|
|
147
|
-
private maybeInjectHmrScript;
|
|
148
139
|
/**
|
|
149
140
|
* Initializes the server adapter's core runtime components.
|
|
150
141
|
*/
|
|
@@ -189,18 +180,12 @@ export declare class BunServerAdapter extends SharedServerAdapter<BunServerAdapt
|
|
|
189
180
|
private createHmrAwareWebSocketHandler;
|
|
190
181
|
/**
|
|
191
182
|
* @remarks
|
|
192
|
-
* When `serveHmrEndpoints` is set, `/_hmr`
|
|
193
|
-
*
|
|
183
|
+
* When `serveHmrEndpoints` is set, `/_hmr` is checked before user websocket
|
|
184
|
+
* patterns so HMR upgrades are never captured by app routes. Runtime assets are
|
|
185
|
+
* served by `SharedServerAdapter.handleSharedRequest` via `tryHandleAssetRequest`.
|
|
194
186
|
* Production mode with only user handlers skips the HMR branch entirely.
|
|
195
187
|
*/
|
|
196
188
|
private wrapFetchWithWebSocketUpgrades;
|
|
197
|
-
/**
|
|
198
|
-
* Helper method to retrieve and parse the request body.
|
|
199
|
-
* Handles JSON and plain text content types.
|
|
200
|
-
* For FormData (multipart/form-data, x-www-form-urlencoded), use ctx.request.formData() directly.
|
|
201
|
-
* Returns undefined for unsupported content types.
|
|
202
|
-
*/
|
|
203
|
-
private retrieveBodyFromRequest;
|
|
204
189
|
/**
|
|
205
190
|
* Composes the base Bun server settings that all runtime modes build from.
|
|
206
191
|
*
|
|
@@ -256,11 +241,9 @@ export declare class BunServerAdapter extends SharedServerAdapter<BunServerAdapt
|
|
|
256
241
|
* Handles HTTP requests by passing them securely to the shared core router adapter.
|
|
257
242
|
*
|
|
258
243
|
* @remarks
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* dev HMR runtime — so the HMR script injection happens here, after
|
|
263
|
-
* the shared handler runs.
|
|
244
|
+
* HMR HTML injection for API and page responses is owned by
|
|
245
|
+
* `SharedServerAdapter.handleSharedRequest`. This method only maps the
|
|
246
|
+
* request into the shared pipeline.
|
|
264
247
|
*/
|
|
265
248
|
handleRequest(request: Request): Promise<Response>;
|
|
266
249
|
/**
|