@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.
Files changed (95) hide show
  1. package/README.md +36 -7
  2. package/package.json +2 -2
  3. package/src/adapters/README.md +6 -2
  4. package/src/adapters/abstract/segment-path-matcher.d.ts +6 -0
  5. package/src/adapters/abstract/segment-path-matcher.js +149 -0
  6. package/src/adapters/abstract/ws-pattern-matcher.d.ts +2 -50
  7. package/src/adapters/abstract/ws-pattern-matcher.js +5 -36
  8. package/src/adapters/{shared → bun}/bun-user-websocket-lifecycle.js +1 -1
  9. package/src/adapters/bun/create-app.d.ts +2 -2
  10. package/src/adapters/bun/create-app.js +2 -2
  11. package/src/adapters/bun/hmr-manager.d.ts +1 -1
  12. package/src/adapters/bun/hmr-manager.js +1 -1
  13. package/src/adapters/bun/index.d.ts +1 -1
  14. package/src/adapters/bun/index.js +25 -2
  15. package/src/adapters/bun/runtime-host.d.ts +1 -1
  16. package/src/adapters/bun/runtime-host.js +1 -1
  17. package/src/adapters/bun/server-adapter.d.ts +8 -25
  18. package/src/adapters/bun/server-adapter.js +21 -65
  19. package/src/adapters/bun/static-preview-host.d.ts +1 -1
  20. package/src/adapters/bun/static-preview-host.js +1 -1
  21. package/src/adapters/index.d.ts +1 -1
  22. package/src/adapters/index.js +25 -2
  23. package/src/adapters/node/create-app.d.ts +2 -2
  24. package/src/adapters/node/create-app.js +2 -2
  25. package/src/adapters/node/node-hmr-manager.d.ts +1 -1
  26. package/src/adapters/node/node-hmr-manager.js +1 -1
  27. package/src/adapters/node/runtime-host.d.ts +1 -1
  28. package/src/adapters/node/runtime-host.js +1 -1
  29. package/src/adapters/node/server-adapter.d.ts +2 -3
  30. package/src/adapters/node/server-adapter.js +15 -18
  31. package/src/adapters/node/static-preview-host.d.ts +1 -1
  32. package/src/adapters/node/static-preview-host.js +1 -1
  33. package/src/adapters/shared/{hmr-entrypoint-registrar.d.ts → hmr/hmr-entrypoint-registrar.d.ts} +1 -1
  34. package/src/adapters/shared/{hmr-entrypoint-registrar.js → hmr/hmr-entrypoint-registrar.js} +1 -1
  35. package/src/adapters/shared/{hmr-html-response.d.ts → hmr/hmr-html-response.d.ts} +1 -1
  36. package/src/adapters/shared/{shared-hmr-manager.d.ts → hmr/shared-hmr-manager.d.ts} +9 -8
  37. package/src/adapters/shared/{shared-hmr-manager.js → hmr/shared-hmr-manager.js} +41 -15
  38. package/src/adapters/shared/http/api-request-pipeline.d.ts +23 -0
  39. package/src/adapters/shared/http/api-request-pipeline.js +129 -0
  40. package/src/adapters/shared/{api-response.d.ts → http/api-response.d.ts} +1 -1
  41. package/src/adapters/shared/{api-response.js → http/api-response.js} +6 -23
  42. package/src/adapters/shared/http/create-body-response.d.ts +12 -0
  43. package/src/adapters/shared/http/create-body-response.js +23 -0
  44. package/src/adapters/shared/http/define-api-handler.d.ts +90 -0
  45. package/src/adapters/shared/http/define-api-handler.js +63 -0
  46. package/src/adapters/shared/{explicit-static-render-preparation.d.ts → http/explicit-static-render-preparation.d.ts} +3 -3
  47. package/src/adapters/shared/{explicit-static-route-matcher.d.ts → http/explicit-static-route-matcher.d.ts} +3 -9
  48. package/src/adapters/shared/{explicit-static-route-matcher.js → http/explicit-static-route-matcher.js} +3 -38
  49. package/src/adapters/shared/{file-route-middleware-pipeline.d.ts → http/file-route-middleware-pipeline.d.ts} +2 -2
  50. package/src/adapters/shared/{file-route-middleware-pipeline.js → http/file-route-middleware-pipeline.js} +10 -22
  51. package/src/adapters/shared/{fs-server-response-factory.d.ts → http/fs-server-response-factory.d.ts} +2 -2
  52. package/src/adapters/shared/{fs-server-response-factory.js → http/fs-server-response-factory.js} +2 -2
  53. package/src/adapters/shared/{fs-server-response-matcher.d.ts → http/fs-server-response-matcher.d.ts} +5 -5
  54. package/src/adapters/shared/{fs-server-response-matcher.js → http/fs-server-response-matcher.js} +5 -5
  55. package/src/adapters/shared/http/run-middleware-chain.d.ts +5 -0
  56. package/src/adapters/shared/http/run-middleware-chain.js +17 -0
  57. package/src/adapters/shared/{application-adapter.d.ts → runtime/application-adapter.d.ts} +2 -2
  58. package/src/adapters/shared/{application-adapter.js → runtime/application-adapter.js} +1 -1
  59. package/src/adapters/shared/{render-context.d.ts → runtime/render-context.d.ts} +2 -2
  60. package/src/adapters/shared/{render-context.js → runtime/render-context.js} +8 -17
  61. package/src/adapters/shared/{runtime-app-bootstrap.d.ts → runtime/runtime-app-bootstrap.d.ts} +2 -2
  62. package/src/adapters/shared/{runtime-app-bootstrap.js → runtime/runtime-app-bootstrap.js} +1 -1
  63. package/src/adapters/shared/{runtime-server-lifecycle.d.ts → runtime/runtime-server-lifecycle.d.ts} +13 -13
  64. package/src/adapters/shared/{runtime-server-lifecycle.js → runtime/runtime-server-lifecycle.js} +22 -15
  65. package/src/adapters/shared/{server-adapter.d.ts → runtime/server-adapter.d.ts} +20 -48
  66. package/src/adapters/shared/runtime/server-adapter.js +226 -0
  67. package/src/adapters/shared/{server-route-handler.d.ts → runtime/server-route-handler.d.ts} +6 -36
  68. package/src/adapters/shared/{server-route-handler.js → runtime/server-route-handler.js} +6 -47
  69. package/src/adapters/shared/{server-static-builder.d.ts → runtime/server-static-builder.d.ts} +6 -6
  70. package/src/adapters/shared/{server-static-builder.js → runtime/server-static-builder.js} +8 -8
  71. package/src/adapters/shared/{static-preview-host.d.ts → runtime/static-preview-host.d.ts} +1 -1
  72. package/src/adapters/shared/{node-http-websocket-upgrades.d.ts → ws/node-http-websocket-upgrades.d.ts} +1 -1
  73. package/src/adapters/shared/{node-http-websocket-upgrades.js → ws/node-http-websocket-upgrades.js} +2 -2
  74. package/src/adapters/shared/{websocket-lifecycle.d.ts → ws/websocket-lifecycle.d.ts} +1 -1
  75. package/src/adapters/shared/{websocket-lifecycle.js → ws/websocket-lifecycle.js} +1 -1
  76. package/src/build/app-build-manifest-runtime.d.ts +4 -0
  77. package/src/build/app-build-manifest-runtime.js +19 -9
  78. package/src/index.d.ts +1 -1
  79. package/src/index.js +24 -1
  80. package/src/static-site-generator/static-site-generator.js +1 -1
  81. package/src/types/internal-types.d.ts +8 -2
  82. package/src/types/public-types.d.ts +9 -1
  83. package/src/adapters/shared/define-api-handler.d.ts +0 -25
  84. package/src/adapters/shared/define-api-handler.js +0 -15
  85. package/src/adapters/shared/server-adapter.js +0 -452
  86. /package/src/adapters/{shared → bun}/bun-user-websocket-lifecycle.d.ts +0 -0
  87. /package/src/adapters/shared/{hmr-html-response.js → hmr/hmr-html-response.js} +0 -0
  88. /package/src/adapters/shared/{explicit-static-render-preparation.js → http/explicit-static-render-preparation.js} +0 -0
  89. /package/src/adapters/shared/{copy-runtime-public-dir.d.ts → runtime/copy-runtime-public-dir.d.ts} +0 -0
  90. /package/src/adapters/shared/{copy-runtime-public-dir.js → runtime/copy-runtime-public-dir.js} +0 -0
  91. /package/src/adapters/shared/{port-manager.d.ts → runtime/port-manager.d.ts} +0 -0
  92. /package/src/adapters/shared/{port-manager.js → runtime/port-manager.js} +0 -0
  93. /package/src/adapters/shared/{runtime-host.d.ts → runtime/runtime-host.d.ts} +0 -0
  94. /package/src/adapters/shared/{runtime-host.js → runtime/runtime-host.js} +0 -0
  95. /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`. Register them on your `app` instance before starting:
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 = defineApiHandler({
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.get(helloWorld); // Register the API handler
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.29",
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.29",
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",
@@ -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 HMR transport
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
- * 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
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
- 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;
3
+ return matchColonSegmentPath(pattern, pathname);
25
4
  }
26
5
  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;
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 = matchWebSocketPath(pattern, pathname);
12
+ const params = matchColonSegmentPath(pattern, pathname);
44
13
  if (params === null) {
45
14
  index += 1;
46
15
  continue;
47
16
  }
48
- const score = scoreWebSocketPattern(pattern);
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 "./websocket-lifecycle.js";
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 { defineApiHandler, defineGroupHandler } from "../shared/define-api-handler.js";
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
- defineGroupHandler
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` and `/_hmr_runtime.js` are checked
193
- * before user websocket patterns so HMR upgrades are never captured by app routes.
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
- * Filesystem page responses are wrapped by `ServerRouteHandler`. This
260
- * adapter-level pass only covers HTML returned by explicit API handlers,
261
- * which bypass that route-layer wrapper and would otherwise miss the
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
  /**