@mokup/server 1.1.2 → 1.1.3

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 (73) hide show
  1. package/dist/connect.cjs +5 -5
  2. package/dist/connect.d.cts +13 -2
  3. package/dist/connect.d.mts +13 -2
  4. package/dist/connect.d.ts +13 -2
  5. package/dist/connect.mjs +1 -1
  6. package/dist/express.cjs +1 -1
  7. package/dist/express.d.cts +13 -2
  8. package/dist/express.d.mts +13 -2
  9. package/dist/express.d.ts +13 -2
  10. package/dist/express.mjs +1 -1
  11. package/dist/fastify.cjs +5 -5
  12. package/dist/fastify.d.cts +13 -2
  13. package/dist/fastify.d.mts +13 -2
  14. package/dist/fastify.d.ts +13 -2
  15. package/dist/fastify.mjs +1 -1
  16. package/dist/fetch-server.cjs +599 -447
  17. package/dist/fetch-server.d.cts +81 -27
  18. package/dist/fetch-server.d.mts +81 -27
  19. package/dist/fetch-server.d.ts +81 -27
  20. package/dist/fetch-server.mjs +601 -449
  21. package/dist/fetch.cjs +5 -5
  22. package/dist/fetch.d.cts +12 -1
  23. package/dist/fetch.d.mts +12 -1
  24. package/dist/fetch.d.ts +12 -1
  25. package/dist/fetch.mjs +1 -1
  26. package/dist/hono.cjs +1 -1
  27. package/dist/hono.d.cts +12 -1
  28. package/dist/hono.d.mts +12 -1
  29. package/dist/hono.d.ts +12 -1
  30. package/dist/hono.mjs +1 -1
  31. package/dist/index.cjs +34 -2
  32. package/dist/index.d.cts +40 -1
  33. package/dist/index.d.mts +40 -1
  34. package/dist/index.d.ts +40 -1
  35. package/dist/index.mjs +36 -1
  36. package/dist/koa.cjs +5 -5
  37. package/dist/koa.d.cts +13 -2
  38. package/dist/koa.d.mts +13 -2
  39. package/dist/koa.d.ts +13 -2
  40. package/dist/koa.mjs +1 -1
  41. package/dist/node.cjs +3 -3
  42. package/dist/node.d.cts +3 -2
  43. package/dist/node.d.mts +3 -2
  44. package/dist/node.d.ts +3 -2
  45. package/dist/node.mjs +3 -3
  46. package/dist/shared/server.CyVIKPsp.d.cts +214 -0
  47. package/dist/shared/server.CyVIKPsp.d.mts +214 -0
  48. package/dist/shared/server.CyVIKPsp.d.ts +214 -0
  49. package/dist/shared/server.D0gAciOr.d.cts +46 -0
  50. package/dist/shared/server.D0gAciOr.d.mts +46 -0
  51. package/dist/shared/server.D0gAciOr.d.ts +46 -0
  52. package/dist/shared/server.DkerfsA-.d.cts +73 -0
  53. package/dist/shared/server.DkerfsA-.d.mts +73 -0
  54. package/dist/shared/server.DkerfsA-.d.ts +73 -0
  55. package/dist/shared/{server.tZ4R8aB2.mjs → server.LbftO9Jh.mjs} +57 -54
  56. package/dist/shared/{server.3GcmR3Ev.cjs → server.aaygIV2Q.cjs} +57 -54
  57. package/dist/worker-node.cjs +1 -1
  58. package/dist/worker-node.d.cts +29 -1
  59. package/dist/worker-node.d.mts +29 -1
  60. package/dist/worker-node.d.ts +29 -1
  61. package/dist/worker-node.mjs +1 -1
  62. package/dist/worker.cjs +1 -1
  63. package/dist/worker.d.cts +23 -1
  64. package/dist/worker.d.mts +23 -1
  65. package/dist/worker.d.ts +23 -1
  66. package/dist/worker.mjs +1 -1
  67. package/package.json +4 -4
  68. package/dist/shared/server.B82hrXoo.d.cts +0 -15
  69. package/dist/shared/server.B82hrXoo.d.mts +0 -15
  70. package/dist/shared/server.B82hrXoo.d.ts +0 -15
  71. package/dist/shared/server.Cb2eiCU2.d.cts +0 -17
  72. package/dist/shared/server.Cb2eiCU2.d.mts +0 -17
  73. package/dist/shared/server.Cb2eiCU2.d.ts +0 -17
@@ -1,277 +1,71 @@
1
- import process, { cwd } from 'node:process';
2
- import { Hono, PatternRouter } from '@mokup/shared/hono';
3
- import { resolve, isAbsolute, relative, join, normalize, extname, dirname, basename } from '@mokup/shared/pathe';
1
+ import { join, normalize, extname, dirname, relative, resolve, isAbsolute, basename } from '@mokup/shared/pathe';
4
2
  import { promises } from 'node:fs';
5
3
  import { createRequire } from 'node:module';
4
+ import process, { cwd } from 'node:process';
6
5
  import { compareRouteScore, parseRouteTemplate } from '@mokup/runtime';
7
6
  import { Buffer } from 'node:buffer';
8
7
  import { pathToFileURL } from 'node:url';
9
8
  import { build } from '@mokup/shared/esbuild';
10
9
  import { parse } from '@mokup/shared/jsonc-parser';
10
+ import { Hono, PatternRouter } from '@mokup/shared/hono';
11
11
 
12
- const methodSet = /* @__PURE__ */ new Set([
13
- "GET",
14
- "POST",
15
- "PUT",
16
- "PATCH",
17
- "DELETE",
18
- "OPTIONS",
19
- "HEAD"
20
- ]);
21
- const methodSuffixSet = new Set(
22
- Array.from(methodSet, (method) => method.toLowerCase())
23
- );
24
- const supportedExtensions = /* @__PURE__ */ new Set([
25
- ".json",
26
- ".jsonc",
27
- ".ts",
28
- ".js",
29
- ".mjs",
30
- ".cjs"
31
- ]);
32
- const configExtensions = [".ts", ".js", ".mjs", ".cjs"];
33
-
34
- function normalizePrefix(prefix) {
35
- if (!prefix) {
36
- return "";
37
- }
38
- const normalized = prefix.startsWith("/") ? prefix : `/${prefix}`;
39
- return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
40
- }
41
- function resolveDirs(dir, root) {
42
- const raw = typeof dir === "function" ? dir(root) : dir;
43
- const resolved = Array.isArray(raw) ? raw : raw ? [raw] : ["mock"];
44
- const normalized = resolved.map(
45
- (entry) => isAbsolute(entry) ? entry : resolve(root, entry)
46
- );
47
- return Array.from(new Set(normalized));
48
- }
49
- function createDebouncer(delayMs, fn) {
50
- let timer = null;
51
- return () => {
52
- if (timer) {
53
- clearTimeout(timer);
54
- }
55
- timer = setTimeout(() => {
56
- timer = null;
57
- fn();
58
- }, delayMs);
59
- };
60
- }
61
- function toPosix(value) {
62
- return value.replace(/\\/g, "/");
63
- }
64
- function isInDirs(file, dirs) {
65
- const normalized = toPosix(file);
66
- return dirs.some((dir) => {
67
- const normalizedDir = toPosix(dir).replace(/\/$/, "");
68
- return normalized === normalizedDir || normalized.startsWith(`${normalizedDir}/`);
69
- });
70
- }
71
- function testPatterns(patterns, value) {
72
- const list = Array.isArray(patterns) ? patterns : [patterns];
73
- return list.some((pattern) => pattern.test(value));
74
- }
75
- function matchesFilter(file, include, exclude) {
76
- const normalized = toPosix(file);
77
- if (exclude && testPatterns(exclude, normalized)) {
78
- return false;
79
- }
80
- if (include) {
81
- return testPatterns(include, normalized);
82
- }
83
- return true;
84
- }
85
- function normalizeIgnorePrefix(value, fallback = ["."]) {
86
- const list = typeof value === "undefined" ? fallback : Array.isArray(value) ? value : [value];
87
- return list.filter((entry) => typeof entry === "string" && entry.length > 0);
88
- }
89
- function hasIgnoredPrefix(file, rootDir, prefixes) {
90
- if (prefixes.length === 0) {
91
- return false;
92
- }
93
- const relativePath = toPosix(relative(rootDir, file));
94
- const segments = relativePath.split("/");
95
- return segments.some(
96
- (segment) => prefixes.some((prefix) => segment.startsWith(prefix))
97
- );
98
- }
99
- function delay(ms) {
100
- return new Promise((resolve2) => setTimeout(resolve2, ms));
101
- }
102
-
103
- function toHonoPath(route) {
104
- if (!route.tokens || route.tokens.length === 0) {
105
- return "/";
106
- }
107
- const segments = route.tokens.map((token) => {
108
- if (token.type === "static") {
109
- return token.value;
110
- }
111
- if (token.type === "param") {
112
- return `:${token.name}`;
113
- }
114
- if (token.type === "catchall") {
115
- return `:${token.name}{.+}`;
116
- }
117
- return `:${token.name}{.+}?`;
118
- });
119
- return `/${segments.join("/")}`;
120
- }
121
- function isValidStatus(status) {
122
- return typeof status === "number" && Number.isFinite(status) && status >= 200 && status <= 599;
123
- }
124
- function resolveStatus(routeStatus, responseStatus) {
125
- if (isValidStatus(routeStatus)) {
126
- return routeStatus;
127
- }
128
- if (isValidStatus(responseStatus)) {
129
- return responseStatus;
130
- }
131
- return 200;
132
- }
133
- function applyRouteOverrides(response, route) {
134
- const headers = new Headers(response.headers);
135
- const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
136
- if (route.headers) {
137
- for (const [key, value] of Object.entries(route.headers)) {
138
- headers.set(key, value);
139
- }
140
- }
141
- const status = resolveStatus(route.status, response.status);
142
- if (status === response.status && !hasHeaders) {
143
- return response;
144
- }
145
- return new Response(response.body, { status, headers });
146
- }
147
- function resolveResponse(value, fallback) {
148
- if (value instanceof Response) {
149
- return value;
150
- }
151
- if (value && typeof value === "object" && "res" in value) {
152
- const resolved = value.res;
153
- if (resolved instanceof Response) {
154
- return resolved;
155
- }
156
- }
157
- return fallback;
158
- }
159
- function normalizeHandlerValue(c, value) {
160
- if (value instanceof Response) {
161
- return value;
162
- }
163
- if (typeof value === "undefined") {
164
- const response = c.body(null);
165
- if (response.status === 200) {
166
- return new Response(response.body, {
167
- status: 204,
168
- headers: response.headers
169
- });
170
- }
171
- return response;
172
- }
173
- if (typeof value === "string") {
174
- return c.text(value);
175
- }
176
- if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
177
- if (!c.res.headers.get("content-type")) {
178
- c.header("content-type", "application/octet-stream");
179
- }
180
- const data = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value);
181
- return c.body(data);
182
- }
183
- return c.json(value);
184
- }
185
- function createRouteHandler(route) {
186
- return async (c) => {
187
- const value = typeof route.handler === "function" ? await route.handler(c) : route.handler;
188
- return normalizeHandlerValue(c, value);
189
- };
190
- }
191
- function createFinalizeMiddleware(route, onResponse) {
192
- return async (c, next) => {
193
- const response = await next();
194
- const resolved = resolveResponse(response, c.res);
195
- if (route.delay && route.delay > 0) {
196
- await delay(route.delay);
197
- }
198
- const overridden = applyRouteOverrides(resolved, route);
199
- c.res = overridden;
200
- if (onResponse) {
201
- try {
202
- const result = onResponse(route, overridden);
203
- if (result instanceof Promise) {
204
- result.catch(() => void 0);
205
- }
206
- } catch {
207
- }
208
- }
209
- return overridden;
210
- };
211
- }
212
- function wrapMiddleware(handler) {
213
- return async (c, next) => {
214
- const response = await handler(c, next);
215
- return resolveResponse(response, c.res);
216
- };
217
- }
218
- function createHonoApp(routes, options = {}) {
219
- const app = new Hono({ router: new PatternRouter(), strict: false });
220
- for (const route of routes) {
221
- const middlewares = route.middlewares?.map((entry) => wrapMiddleware(entry.handle)) ?? [];
222
- app.on(
223
- route.method,
224
- toHonoPath(route),
225
- createFinalizeMiddleware(route, options.onResponse),
226
- ...middlewares,
227
- createRouteHandler(route)
228
- );
12
+ const silentLogger$1 = {
13
+ info: () => {
14
+ },
15
+ warn: () => {
16
+ },
17
+ error: () => {
18
+ },
19
+ log: () => {
229
20
  }
230
- return app;
231
- }
232
-
233
- function createLogger(enabled) {
21
+ };
22
+ function createLogger(options = true) {
23
+ const resolvedOptions = typeof options === "boolean" ? { enabled: options } : options;
24
+ const enabled = resolvedOptions?.enabled ?? true;
25
+ if (!enabled) {
26
+ return silentLogger$1;
27
+ }
28
+ const tag = resolvedOptions?.tag ?? "mokup";
29
+ const prefix = tag ? `[${tag}]` : "";
30
+ const withTag = (args) => prefix ? [prefix, ...args] : args;
234
31
  return {
235
32
  info: (...args) => {
236
- if (enabled) {
237
- console.info("[mokup]", ...args);
33
+ if (args.length === 0) {
34
+ return;
238
35
  }
36
+ console.info(...withTag(args));
239
37
  },
240
38
  warn: (...args) => {
241
- if (enabled) {
242
- console.warn("[mokup]", ...args);
39
+ if (args.length === 0) {
40
+ return;
243
41
  }
42
+ console.warn(...withTag(args));
244
43
  },
245
44
  error: (...args) => {
246
- if (enabled) {
247
- console.error("[mokup]", ...args);
45
+ if (args.length === 0) {
46
+ return;
47
+ }
48
+ console.error(...withTag(args));
49
+ },
50
+ log: (...args) => {
51
+ if (args.length === 0) {
52
+ return;
248
53
  }
54
+ console.log(...withTag(args));
249
55
  }
250
56
  };
251
57
  }
252
58
 
253
- const mimeTypes = {
254
- ".html": "text/html; charset=utf-8",
255
- ".css": "text/css; charset=utf-8",
256
- ".js": "text/javascript; charset=utf-8",
257
- ".map": "application/json; charset=utf-8",
258
- ".json": "application/json; charset=utf-8",
259
- ".svg": "image/svg+xml",
260
- ".png": "image/png",
261
- ".jpg": "image/jpeg",
262
- ".jpeg": "image/jpeg",
263
- ".ico": "image/x-icon"
264
- };
265
59
  function normalizePlaygroundPath(value) {
266
60
  if (!value) {
267
- return "/_mokup";
61
+ return "/__mokup";
268
62
  }
269
63
  const normalized = value.startsWith("/") ? value : `/${value}`;
270
64
  return normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
271
65
  }
272
66
  function resolvePlaygroundOptions(playground) {
273
67
  if (playground === false) {
274
- return { enabled: false, path: "/_mokup" };
68
+ return { enabled: false, path: "/__mokup" };
275
69
  }
276
70
  if (playground && typeof playground === "object") {
277
71
  return {
@@ -279,13 +73,43 @@ function resolvePlaygroundOptions(playground) {
279
73
  path: normalizePlaygroundPath(playground.path)
280
74
  };
281
75
  }
282
- return { enabled: true, path: "/_mokup" };
76
+ return { enabled: true, path: "/__mokup" };
283
77
  }
78
+
79
+ const require$1 = createRequire(import.meta.url);
80
+ const mimeTypes = {
81
+ ".html": "text/html; charset=utf-8",
82
+ ".css": "text/css; charset=utf-8",
83
+ ".js": "text/javascript; charset=utf-8",
84
+ ".map": "application/json; charset=utf-8",
85
+ ".json": "application/json; charset=utf-8",
86
+ ".svg": "image/svg+xml",
87
+ ".png": "image/png",
88
+ ".jpg": "image/jpeg",
89
+ ".jpeg": "image/jpeg",
90
+ ".ico": "image/x-icon"
91
+ };
284
92
  function resolvePlaygroundDist() {
285
- const require = createRequire(import.meta.url);
286
- const pkgPath = require.resolve("@mokup/playground/package.json");
93
+ const pkgPath = require$1.resolve("@mokup/playground/package.json");
287
94
  return join(pkgPath, "..", "dist");
288
95
  }
96
+ async function readPlaygroundIndex(indexPath) {
97
+ const html = await promises.readFile(indexPath, "utf8");
98
+ const contentType = mimeTypes[".html"];
99
+ return new Response(html, { headers: { "Content-Type": contentType } });
100
+ }
101
+ async function readPlaygroundAsset(distDir, relPath) {
102
+ if (relPath.includes("..")) {
103
+ return new Response("Invalid path.", { status: 400 });
104
+ }
105
+ const normalizedPath = normalize(relPath);
106
+ const filePath = join(distDir, normalizedPath);
107
+ const content = await promises.readFile(filePath);
108
+ const ext = extname(filePath);
109
+ const contentType = mimeTypes[ext] ?? "application/octet-stream";
110
+ return new Response(content, { headers: { "Content-Type": contentType } });
111
+ }
112
+
289
113
  function toPosixPath(value) {
290
114
  return value.replace(/\\/g, "/");
291
115
  }
@@ -327,41 +151,6 @@ function resolveGroupRoot(dirs, serverRoot) {
327
151
  }
328
152
  return common;
329
153
  }
330
- const disabledReasonSet = /* @__PURE__ */ new Set([
331
- "disabled",
332
- "disabled-dir",
333
- "exclude",
334
- "ignore-prefix",
335
- "include",
336
- "unknown"
337
- ]);
338
- const ignoredReasonSet = /* @__PURE__ */ new Set([
339
- "unsupported",
340
- "invalid-route",
341
- "unknown"
342
- ]);
343
- function normalizeDisabledReason(reason) {
344
- if (reason && disabledReasonSet.has(reason)) {
345
- return reason;
346
- }
347
- return "unknown";
348
- }
349
- function normalizeIgnoredReason(reason) {
350
- if (reason && ignoredReasonSet.has(reason)) {
351
- return reason;
352
- }
353
- return "unknown";
354
- }
355
- function formatRouteFile(file, root) {
356
- if (!root) {
357
- return toPosixPath(file);
358
- }
359
- const rel = toPosixPath(relative(root, file));
360
- if (!rel || rel.startsWith("..")) {
361
- return toPosixPath(file);
362
- }
363
- return rel;
364
- }
365
154
  function resolveGroups(dirs, root) {
366
155
  const groups = [];
367
156
  const seen = /* @__PURE__ */ new Set();
@@ -394,11 +183,54 @@ function resolveRouteGroup(routeFile, groups) {
394
183
  }
395
184
  }
396
185
  }
397
- return matched;
186
+ return matched;
187
+ }
188
+ function formatRouteFile(file, root) {
189
+ if (!root) {
190
+ return toPosixPath(file);
191
+ }
192
+ const rel = toPosixPath(relative(root, file));
193
+ if (!rel || rel.startsWith("..")) {
194
+ return toPosixPath(file);
195
+ }
196
+ return rel;
197
+ }
198
+
199
+ const disabledReasonSet = /* @__PURE__ */ new Set([
200
+ "disabled",
201
+ "disabled-dir",
202
+ "exclude",
203
+ "ignore-prefix",
204
+ "include",
205
+ "unknown"
206
+ ]);
207
+ const ignoredReasonSet = /* @__PURE__ */ new Set([
208
+ "unsupported",
209
+ "invalid-route",
210
+ "unknown"
211
+ ]);
212
+ function normalizeDisabledReason(reason) {
213
+ if (reason && disabledReasonSet.has(reason)) {
214
+ return reason;
215
+ }
216
+ return "unknown";
217
+ }
218
+ function normalizeIgnoredReason(reason) {
219
+ if (reason && ignoredReasonSet.has(reason)) {
220
+ return reason;
221
+ }
222
+ return "unknown";
398
223
  }
399
224
  function toPlaygroundRoute(route, root, groups) {
400
225
  const matchedGroup = resolveRouteGroup(route.file, groups);
401
- const middlewareSources = route.middlewares?.map((entry) => formatRouteFile(entry.source, root));
226
+ const preSources = route.middlewares?.filter((entry) => entry.position === "pre").map((entry) => formatRouteFile(entry.source, root)) ?? [];
227
+ const postSources = route.middlewares?.filter((entry) => entry.position === "post").map((entry) => formatRouteFile(entry.source, root)) ?? [];
228
+ const normalSources = route.middlewares?.filter((entry) => entry.position !== "pre" && entry.position !== "post").map((entry) => formatRouteFile(entry.source, root)) ?? [];
229
+ const combinedSources = [
230
+ ...preSources,
231
+ ...normalSources,
232
+ ...postSources
233
+ ];
402
234
  return {
403
235
  method: route.method,
404
236
  url: route.template,
@@ -406,8 +238,14 @@ function toPlaygroundRoute(route, root, groups) {
406
238
  type: typeof route.handler === "function" ? "handler" : "static",
407
239
  status: route.status,
408
240
  delay: route.delay,
409
- middlewareCount: middlewareSources?.length ?? 0,
410
- middlewares: middlewareSources,
241
+ middlewareCount: combinedSources.length,
242
+ middlewares: combinedSources,
243
+ preMiddlewareCount: preSources.length,
244
+ normalMiddlewareCount: normalSources.length,
245
+ postMiddlewareCount: postSources.length,
246
+ preMiddlewares: preSources,
247
+ normalMiddlewares: normalSources,
248
+ postMiddlewares: postSources,
411
249
  groupKey: matchedGroup?.key,
412
250
  group: matchedGroup?.label
413
251
  };
@@ -453,6 +291,7 @@ function toPlaygroundConfigFile(entry, root, groups) {
453
291
  }
454
292
  return configFile;
455
293
  }
294
+
456
295
  function registerPlaygroundRoutes(params) {
457
296
  if (!params.config.enabled) {
458
297
  return;
@@ -462,9 +301,7 @@ function registerPlaygroundRoutes(params) {
462
301
  const indexPath = join(distDir, "index.html");
463
302
  const serveIndex = async () => {
464
303
  try {
465
- const html = await promises.readFile(indexPath, "utf8");
466
- const contentType = mimeTypes[".html"] ?? "text/html; charset=utf-8";
467
- return new Response(html, { headers: { "Content-Type": contentType } });
304
+ return await readPlaygroundIndex(indexPath);
468
305
  } catch (error) {
469
306
  params.logger.error("Failed to load playground index:", error);
470
307
  return new Response("Playground is not available.", { status: 500 });
@@ -505,22 +342,105 @@ function registerPlaygroundRoutes(params) {
505
342
  if (!relPath || relPath === "/") {
506
343
  return serveIndex();
507
344
  }
508
- if (relPath.includes("..")) {
509
- return new Response("Invalid path.", { status: 400 });
510
- }
511
- const normalizedPath = normalize(relPath);
512
- const filePath = join(distDir, normalizedPath);
513
345
  try {
514
- const content = await promises.readFile(filePath);
515
- const ext = extname(filePath);
516
- const contentType = mimeTypes[ext] ?? "application/octet-stream";
517
- return new Response(content, { headers: { "Content-Type": contentType } });
346
+ return await readPlaygroundAsset(distDir, relPath);
518
347
  } catch {
519
348
  return c.notFound();
520
349
  }
521
350
  });
522
351
  }
523
352
 
353
+ const methodSet = /* @__PURE__ */ new Set([
354
+ "GET",
355
+ "POST",
356
+ "PUT",
357
+ "PATCH",
358
+ "DELETE",
359
+ "OPTIONS",
360
+ "HEAD"
361
+ ]);
362
+ const methodSuffixSet = new Set(
363
+ Array.from(methodSet, (method) => method.toLowerCase())
364
+ );
365
+ const supportedExtensions = /* @__PURE__ */ new Set([
366
+ ".json",
367
+ ".jsonc",
368
+ ".ts",
369
+ ".js",
370
+ ".mjs",
371
+ ".cjs"
372
+ ]);
373
+ const configExtensions = [".ts", ".js", ".mjs", ".cjs"];
374
+
375
+ function normalizePrefix(prefix) {
376
+ if (!prefix) {
377
+ return "";
378
+ }
379
+ const normalized = prefix.startsWith("/") ? prefix : `/${prefix}`;
380
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
381
+ }
382
+ function resolveDirs(dir, root) {
383
+ const raw = typeof dir === "function" ? dir(root) : dir;
384
+ const resolved = Array.isArray(raw) ? raw : raw ? [raw] : ["mock"];
385
+ const normalized = resolved.map(
386
+ (entry) => isAbsolute(entry) ? entry : resolve(root, entry)
387
+ );
388
+ return Array.from(new Set(normalized));
389
+ }
390
+ function createDebouncer(delayMs, fn) {
391
+ let timer = null;
392
+ return () => {
393
+ if (timer) {
394
+ clearTimeout(timer);
395
+ }
396
+ timer = setTimeout(() => {
397
+ timer = null;
398
+ fn();
399
+ }, delayMs);
400
+ };
401
+ }
402
+ function toPosix(value) {
403
+ return value.replace(/\\/g, "/");
404
+ }
405
+ function isInDirs(file, dirs) {
406
+ const normalized = toPosix(file);
407
+ return dirs.some((dir) => {
408
+ const normalizedDir = toPosix(dir).replace(/\/$/, "");
409
+ return normalized === normalizedDir || normalized.startsWith(`${normalizedDir}/`);
410
+ });
411
+ }
412
+ function testPatterns(patterns, value) {
413
+ const list = Array.isArray(patterns) ? patterns : [patterns];
414
+ return list.some((pattern) => pattern.test(value));
415
+ }
416
+ function matchesFilter(file, include, exclude) {
417
+ const normalized = toPosix(file);
418
+ if (exclude && testPatterns(exclude, normalized)) {
419
+ return false;
420
+ }
421
+ if (include) {
422
+ return testPatterns(include, normalized);
423
+ }
424
+ return true;
425
+ }
426
+ function normalizeIgnorePrefix(value, fallback = ["."]) {
427
+ const list = typeof value === "undefined" ? fallback : Array.isArray(value) ? value : [value];
428
+ return list.filter((entry) => typeof entry === "string" && entry.length > 0);
429
+ }
430
+ function hasIgnoredPrefix(file, rootDir, prefixes) {
431
+ if (prefixes.length === 0) {
432
+ return false;
433
+ }
434
+ const relativePath = toPosix(relative(rootDir, file));
435
+ const segments = relativePath.split("/");
436
+ return segments.some(
437
+ (segment) => prefixes.some((prefix) => segment.startsWith(prefix))
438
+ );
439
+ }
440
+ function delay(ms) {
441
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
442
+ }
443
+
524
444
  const jsonExtensions = /* @__PURE__ */ new Set([".json", ".jsonc"]);
525
445
  function resolveTemplate(template, prefix) {
526
446
  const normalized = template.startsWith("/") ? template : `/${template}`;
@@ -673,6 +593,7 @@ async function ensureTsxRegister(logger) {
673
593
  return registerPromise;
674
594
  }
675
595
 
596
+ const middlewareSymbol = Symbol.for("mokup.config.middlewares");
676
597
  function isUnknownFileExtensionError$1(error) {
677
598
  if (!error || typeof error !== "object") {
678
599
  return false;
@@ -759,7 +680,7 @@ async function loadConfig(file, logger) {
759
680
  }
760
681
  return value;
761
682
  }
762
- function normalizeMiddlewares(value, source, logger) {
683
+ function normalizeMiddlewares(value, source, logger, position) {
763
684
  if (!value) {
764
685
  return [];
765
686
  }
@@ -770,10 +691,27 @@ function normalizeMiddlewares(value, source, logger) {
770
691
  logger.warn(`Invalid middleware in ${source}`);
771
692
  continue;
772
693
  }
773
- middlewares.push({ handle: entry, source, index });
694
+ middlewares.push({
695
+ handle: entry,
696
+ source,
697
+ index,
698
+ position
699
+ });
774
700
  }
775
701
  return middlewares;
776
702
  }
703
+ function readMiddlewareMeta(config) {
704
+ const value = config[middlewareSymbol];
705
+ if (!value || typeof value !== "object") {
706
+ return null;
707
+ }
708
+ const meta = value;
709
+ return {
710
+ pre: Array.isArray(meta.pre) ? meta.pre : [],
711
+ normal: Array.isArray(meta.normal) ? meta.normal : [],
712
+ post: Array.isArray(meta.post) ? meta.post : []
713
+ };
714
+ }
777
715
  async function resolveDirectoryConfig(params) {
778
716
  const { file, rootDir, logger, configCache, fileCache } = params;
779
717
  const resolvedRoot = normalize(rootDir);
@@ -792,7 +730,10 @@ async function resolveDirectoryConfig(params) {
792
730
  current = parent;
793
731
  }
794
732
  chain.reverse();
795
- const merged = { middlewares: [] };
733
+ const merged = {};
734
+ const preMiddlewares = [];
735
+ const normalMiddlewares = [];
736
+ const postMiddlewares = [];
796
737
  for (const dir of chain) {
797
738
  const configPath = await findConfigFile(dir, fileCache);
798
739
  if (!configPath) {
@@ -828,12 +769,48 @@ async function resolveDirectoryConfig(params) {
828
769
  if (typeof config.exclude !== "undefined") {
829
770
  merged.exclude = config.exclude;
830
771
  }
831
- const normalized = normalizeMiddlewares(config.middleware, configPath, logger);
832
- if (normalized.length > 0) {
833
- merged.middlewares.push(...normalized);
772
+ const meta = readMiddlewareMeta(config);
773
+ const normalizedPre = normalizeMiddlewares(
774
+ meta?.pre,
775
+ configPath,
776
+ logger,
777
+ "pre"
778
+ );
779
+ const normalizedNormal = normalizeMiddlewares(
780
+ meta?.normal,
781
+ configPath,
782
+ logger,
783
+ "normal"
784
+ );
785
+ const normalizedLegacy = normalizeMiddlewares(
786
+ config.middleware,
787
+ configPath,
788
+ logger,
789
+ "normal"
790
+ );
791
+ const normalizedPost = normalizeMiddlewares(
792
+ meta?.post,
793
+ configPath,
794
+ logger,
795
+ "post"
796
+ );
797
+ if (normalizedPre.length > 0) {
798
+ preMiddlewares.push(...normalizedPre);
799
+ }
800
+ if (normalizedNormal.length > 0) {
801
+ normalMiddlewares.push(...normalizedNormal);
802
+ }
803
+ if (normalizedLegacy.length > 0) {
804
+ normalMiddlewares.push(...normalizedLegacy);
805
+ }
806
+ if (normalizedPost.length > 0) {
807
+ postMiddlewares.push(...normalizedPost);
834
808
  }
835
809
  }
836
- return merged;
810
+ return {
811
+ ...merged,
812
+ middlewares: [...preMiddlewares, ...normalMiddlewares, ...postMiddlewares]
813
+ };
837
814
  }
838
815
 
839
816
  async function walkDir(dir, rootDir, files) {
@@ -1002,6 +979,8 @@ const silentLogger = {
1002
979
  warn: () => {
1003
980
  },
1004
981
  error: () => {
982
+ },
983
+ log: () => {
1005
984
  }
1006
985
  };
1007
986
  function resolveSkipRoute(params) {
@@ -1129,55 +1108,228 @@ async function scanRoutes(params) {
1129
1108
  });
1130
1109
  params.onSkip?.(buildSkipInfo(fileInfo.file, "disabled", resolved2));
1131
1110
  }
1132
- continue;
1133
- }
1134
- const ruleValue = rule;
1135
- const unsupportedKeys = ["response", "url", "method"].filter(
1136
- (key2) => key2 in ruleValue
1137
- );
1138
- if (unsupportedKeys.length > 0) {
1139
- params.logger.warn(
1140
- `Skip mock with unsupported fields (${unsupportedKeys.join(", ")}): ${fileInfo.file}`
1141
- );
1142
- continue;
1143
- }
1144
- if (typeof rule.handler === "undefined") {
1145
- params.logger.warn(`Skip mock without handler: ${fileInfo.file}`);
1146
- continue;
1147
- }
1148
- const resolved = resolveRule({
1149
- rule,
1150
- derivedTemplate: derived.template,
1151
- derivedMethod: derived.method,
1152
- prefix: params.prefix,
1153
- file: fileInfo.file,
1154
- logger: params.logger
1155
- });
1156
- if (!resolved) {
1157
- continue;
1158
- }
1159
- resolved.ruleIndex = index;
1160
- if (config.headers) {
1161
- resolved.headers = { ...config.headers, ...resolved.headers ?? {} };
1162
- }
1163
- if (typeof resolved.status === "undefined" && typeof config.status === "number") {
1164
- resolved.status = config.status;
1165
- }
1166
- if (typeof resolved.delay === "undefined" && typeof config.delay === "number") {
1167
- resolved.delay = config.delay;
1168
- }
1169
- if (config.middlewares.length > 0) {
1170
- resolved.middlewares = config.middlewares;
1171
- }
1172
- const key = `${resolved.method} ${resolved.template}`;
1173
- if (seen.has(key)) {
1174
- params.logger.warn(`Duplicate mock route ${key} from ${fileInfo.file}`);
1111
+ continue;
1112
+ }
1113
+ const ruleValue = rule;
1114
+ const unsupportedKeys = ["response", "url", "method"].filter(
1115
+ (key2) => key2 in ruleValue
1116
+ );
1117
+ if (unsupportedKeys.length > 0) {
1118
+ params.logger.warn(
1119
+ `Skip mock with unsupported fields (${unsupportedKeys.join(", ")}): ${fileInfo.file}`
1120
+ );
1121
+ continue;
1122
+ }
1123
+ if (typeof rule.handler === "undefined") {
1124
+ params.logger.warn(`Skip mock without handler: ${fileInfo.file}`);
1125
+ continue;
1126
+ }
1127
+ const resolved = resolveRule({
1128
+ rule,
1129
+ derivedTemplate: derived.template,
1130
+ derivedMethod: derived.method,
1131
+ prefix: params.prefix,
1132
+ file: fileInfo.file,
1133
+ logger: params.logger
1134
+ });
1135
+ if (!resolved) {
1136
+ continue;
1137
+ }
1138
+ resolved.ruleIndex = index;
1139
+ if (config.headers) {
1140
+ resolved.headers = { ...config.headers, ...resolved.headers ?? {} };
1141
+ }
1142
+ if (typeof resolved.status === "undefined" && typeof config.status === "number") {
1143
+ resolved.status = config.status;
1144
+ }
1145
+ if (typeof resolved.delay === "undefined" && typeof config.delay === "number") {
1146
+ resolved.delay = config.delay;
1147
+ }
1148
+ if (config.middlewares.length > 0) {
1149
+ resolved.middlewares = config.middlewares;
1150
+ }
1151
+ const key = `${resolved.method} ${resolved.template}`;
1152
+ if (seen.has(key)) {
1153
+ params.logger.warn(`Duplicate mock route ${key} from ${fileInfo.file}`);
1154
+ }
1155
+ seen.add(key);
1156
+ routes.push(resolved);
1157
+ }
1158
+ }
1159
+ return sortRoutes(routes);
1160
+ }
1161
+
1162
+ function toHonoPath(route) {
1163
+ if (!route.tokens || route.tokens.length === 0) {
1164
+ return "/";
1165
+ }
1166
+ const segments = route.tokens.map((token) => {
1167
+ if (token.type === "static") {
1168
+ return token.value;
1169
+ }
1170
+ if (token.type === "param") {
1171
+ return `:${token.name}`;
1172
+ }
1173
+ if (token.type === "catchall") {
1174
+ return `:${token.name}{.+}`;
1175
+ }
1176
+ return `:${token.name}{.+}?`;
1177
+ });
1178
+ return `/${segments.join("/")}`;
1179
+ }
1180
+ function isValidStatus(status) {
1181
+ return typeof status === "number" && Number.isFinite(status) && status >= 200 && status <= 599;
1182
+ }
1183
+ function resolveStatus(routeStatus, responseStatus) {
1184
+ if (isValidStatus(routeStatus)) {
1185
+ return routeStatus;
1186
+ }
1187
+ if (isValidStatus(responseStatus)) {
1188
+ return responseStatus;
1189
+ }
1190
+ return 200;
1191
+ }
1192
+ function applyRouteOverrides(response, route) {
1193
+ const headers = new Headers(response.headers);
1194
+ const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
1195
+ if (route.headers) {
1196
+ for (const [key, value] of Object.entries(route.headers)) {
1197
+ headers.set(key, value);
1198
+ }
1199
+ }
1200
+ const status = resolveStatus(route.status, response.status);
1201
+ if (status === response.status && !hasHeaders) {
1202
+ return response;
1203
+ }
1204
+ return new Response(response.body, { status, headers });
1205
+ }
1206
+ function resolveResponse(value, fallback) {
1207
+ if (value instanceof Response) {
1208
+ return value;
1209
+ }
1210
+ if (value && typeof value === "object" && "res" in value) {
1211
+ const resolved = value.res;
1212
+ if (resolved instanceof Response) {
1213
+ return resolved;
1214
+ }
1215
+ }
1216
+ return fallback;
1217
+ }
1218
+ function normalizeHandlerValue(c, value) {
1219
+ if (value instanceof Response) {
1220
+ return value;
1221
+ }
1222
+ if (typeof value === "undefined") {
1223
+ const response = c.body(null);
1224
+ if (response.status === 200) {
1225
+ return new Response(response.body, {
1226
+ status: 204,
1227
+ headers: response.headers
1228
+ });
1229
+ }
1230
+ return response;
1231
+ }
1232
+ if (typeof value === "string") {
1233
+ return c.text(value);
1234
+ }
1235
+ if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
1236
+ if (!c.res.headers.get("content-type")) {
1237
+ c.header("content-type", "application/octet-stream");
1238
+ }
1239
+ const data = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value);
1240
+ return c.body(data);
1241
+ }
1242
+ return c.json(value);
1243
+ }
1244
+ function createRouteHandler(route) {
1245
+ return async (c) => {
1246
+ const value = typeof route.handler === "function" ? await route.handler(c) : route.handler;
1247
+ return normalizeHandlerValue(c, value);
1248
+ };
1249
+ }
1250
+ function createFinalizeMiddleware(route, onResponse) {
1251
+ return async (c, next) => {
1252
+ const response = await next();
1253
+ const resolved = resolveResponse(response, c.res);
1254
+ if (route.delay && route.delay > 0) {
1255
+ await delay(route.delay);
1256
+ }
1257
+ const overridden = applyRouteOverrides(resolved, route);
1258
+ c.res = overridden;
1259
+ if (onResponse) {
1260
+ try {
1261
+ const result = onResponse(route, overridden);
1262
+ if (result instanceof Promise) {
1263
+ result.catch(() => void 0);
1264
+ }
1265
+ } catch {
1175
1266
  }
1176
- seen.add(key);
1177
- routes.push(resolved);
1267
+ }
1268
+ return overridden;
1269
+ };
1270
+ }
1271
+ function wrapMiddleware(handler) {
1272
+ return async (c, next) => {
1273
+ const response = await handler(c, next);
1274
+ return resolveResponse(response, c.res);
1275
+ };
1276
+ }
1277
+ function splitRouteMiddlewares(route) {
1278
+ const before = [];
1279
+ const normal = [];
1280
+ const after = [];
1281
+ for (const entry of route.middlewares ?? []) {
1282
+ const wrapped = wrapMiddleware(entry.handle);
1283
+ if (entry.position === "post") {
1284
+ after.push(wrapped);
1285
+ } else if (entry.position === "pre") {
1286
+ before.push(wrapped);
1287
+ } else {
1288
+ normal.push(wrapped);
1178
1289
  }
1179
1290
  }
1180
- return sortRoutes(routes);
1291
+ return { before, normal, after };
1292
+ }
1293
+ function createHonoApp(routes, options = {}) {
1294
+ const app = new Hono({ router: new PatternRouter(), strict: false });
1295
+ for (const route of routes) {
1296
+ const { before, normal, after } = splitRouteMiddlewares(route);
1297
+ app.on(
1298
+ route.method,
1299
+ toHonoPath(route),
1300
+ createFinalizeMiddleware(route, options.onResponse),
1301
+ ...before,
1302
+ ...normal,
1303
+ ...after,
1304
+ createRouteHandler(route)
1305
+ );
1306
+ }
1307
+ return app;
1308
+ }
1309
+
1310
+ function buildFetchServerApp(params) {
1311
+ const app = new Hono({ strict: false });
1312
+ registerPlaygroundRoutes({
1313
+ app,
1314
+ routes: params.routes,
1315
+ disabledRoutes: params.disabledRoutes,
1316
+ ignoredRoutes: params.ignoredRoutes,
1317
+ configFiles: params.configFiles,
1318
+ disabledConfigFiles: params.disabledConfigFiles,
1319
+ dirs: params.dirs,
1320
+ logger: params.logger,
1321
+ config: params.playground,
1322
+ root: params.root
1323
+ });
1324
+ if (params.wsHandler && params.playground.enabled) {
1325
+ app.get(`${params.playground.path}/ws`, params.wsHandler);
1326
+ }
1327
+ if (params.routes.length > 0) {
1328
+ const mockAppOptions = params.onResponse ? { onResponse: params.onResponse } : {};
1329
+ const mockApp = createHonoApp(params.routes, mockAppOptions);
1330
+ app.route("/", mockApp);
1331
+ }
1332
+ return app;
1181
1333
  }
1182
1334
 
1183
1335
  function normalizeEntries(entries) {
@@ -1224,30 +1376,82 @@ function resolveAllDirs(list, root) {
1224
1376
  }
1225
1377
  return dirs;
1226
1378
  }
1227
- function buildApp(params) {
1228
- const app = new Hono({ strict: false });
1229
- registerPlaygroundRoutes({
1230
- app,
1231
- routes: params.routes,
1232
- disabledRoutes: params.disabledRoutes,
1233
- ignoredRoutes: params.ignoredRoutes,
1234
- configFiles: params.configFiles,
1235
- disabledConfigFiles: params.disabledConfigFiles,
1236
- dirs: params.dirs,
1237
- logger: params.logger,
1238
- config: params.playground,
1239
- root: params.root
1240
- });
1241
- if (params.wsHandler && params.playground.enabled) {
1242
- app.get(`${params.playground.path}/ws`, params.wsHandler);
1379
+
1380
+ function createPlaygroundWs(playground) {
1381
+ const routeCounts = {};
1382
+ const wsClients = /* @__PURE__ */ new Set();
1383
+ let totalCount = 0;
1384
+ let wsHandler;
1385
+ let injectWebSocket;
1386
+ function getRouteKey(route) {
1387
+ return `${route.method} ${route.template}`;
1243
1388
  }
1244
- if (params.routes.length > 0) {
1245
- const mockAppOptions = params.onResponse ? { onResponse: params.onResponse } : {};
1246
- const mockApp = createHonoApp(params.routes, mockAppOptions);
1247
- app.route("/", mockApp);
1389
+ function buildSnapshot() {
1390
+ return {
1391
+ type: "snapshot",
1392
+ total: totalCount,
1393
+ perRoute: { ...routeCounts }
1394
+ };
1248
1395
  }
1249
- return app;
1396
+ function broadcast(payload) {
1397
+ if (wsClients.size === 0) {
1398
+ return;
1399
+ }
1400
+ const message = JSON.stringify(payload);
1401
+ for (const client of wsClients) {
1402
+ try {
1403
+ client.send(message);
1404
+ } catch {
1405
+ wsClients.delete(client);
1406
+ }
1407
+ }
1408
+ }
1409
+ function registerWsClient(client) {
1410
+ wsClients.add(client);
1411
+ try {
1412
+ client.send(JSON.stringify(buildSnapshot()));
1413
+ } catch {
1414
+ wsClients.delete(client);
1415
+ }
1416
+ }
1417
+ function handleRouteResponse(route) {
1418
+ const routeKey = getRouteKey(route);
1419
+ routeCounts[routeKey] = (routeCounts[routeKey] ?? 0) + 1;
1420
+ totalCount += 1;
1421
+ broadcast({ type: "increment", routeKey, total: totalCount });
1422
+ }
1423
+ async function setupPlaygroundWebSocket(app) {
1424
+ if (!playground.enabled) {
1425
+ return;
1426
+ }
1427
+ try {
1428
+ const mod = await import('@hono/node-ws');
1429
+ const { createNodeWebSocket } = mod;
1430
+ const { upgradeWebSocket, injectWebSocket: inject } = createNodeWebSocket({ app });
1431
+ wsHandler = upgradeWebSocket(() => ({
1432
+ onOpen: (_event, ws) => {
1433
+ registerWsClient(ws);
1434
+ },
1435
+ onClose: (_event, ws) => {
1436
+ wsClients.delete(ws);
1437
+ },
1438
+ onMessage: () => {
1439
+ }
1440
+ }));
1441
+ injectWebSocket = (server) => {
1442
+ inject(server);
1443
+ };
1444
+ } catch {
1445
+ }
1446
+ }
1447
+ return {
1448
+ handleRouteResponse,
1449
+ setupPlaygroundWebSocket,
1450
+ getWsHandler: () => wsHandler,
1451
+ getInjectWebSocket: () => injectWebSocket
1452
+ };
1250
1453
  }
1454
+
1251
1455
  async function createDenoWatcher(params) {
1252
1456
  const deno = globalThis.Deno;
1253
1457
  if (!deno?.watchFs) {
@@ -1322,6 +1526,7 @@ async function createWatcher(params) {
1322
1526
  params.logger.warn("Watcher is not available in this runtime; file watching disabled.");
1323
1527
  return null;
1324
1528
  }
1529
+
1325
1530
  async function createFetchServer(options = {}) {
1326
1531
  const normalized = normalizeOptions(options);
1327
1532
  const optionList = normalized.entries;
@@ -1331,78 +1536,13 @@ async function createFetchServer(options = {}) {
1331
1536
  const logger = createLogger(logEnabled);
1332
1537
  const playgroundConfig = resolvePlaygroundOptions(normalized.playground);
1333
1538
  const dirs = resolveAllDirs(optionList, root);
1334
- const routeCounts = {};
1335
- const wsClients = /* @__PURE__ */ new Set();
1336
- let totalCount = 0;
1337
- let wsHandler;
1338
- let injectWebSocket;
1339
- function getRouteKey(route) {
1340
- return `${route.method} ${route.template}`;
1341
- }
1342
- function buildSnapshot() {
1343
- return {
1344
- type: "snapshot",
1345
- total: totalCount,
1346
- perRoute: { ...routeCounts }
1347
- };
1348
- }
1349
- function broadcast(payload) {
1350
- if (wsClients.size === 0) {
1351
- return;
1352
- }
1353
- const message = JSON.stringify(payload);
1354
- for (const client of wsClients) {
1355
- try {
1356
- client.send(message);
1357
- } catch {
1358
- wsClients.delete(client);
1359
- }
1360
- }
1361
- }
1362
- function registerWsClient(client) {
1363
- wsClients.add(client);
1364
- try {
1365
- client.send(JSON.stringify(buildSnapshot()));
1366
- } catch {
1367
- wsClients.delete(client);
1368
- }
1369
- }
1370
- function handleRouteResponse(route) {
1371
- const routeKey = getRouteKey(route);
1372
- routeCounts[routeKey] = (routeCounts[routeKey] ?? 0) + 1;
1373
- totalCount += 1;
1374
- broadcast({ type: "increment", routeKey, total: totalCount });
1375
- }
1376
- async function setupPlaygroundWebSocket(app2) {
1377
- if (!playgroundConfig.enabled) {
1378
- return;
1379
- }
1380
- try {
1381
- const mod = await import('@hono/node-ws');
1382
- const { createNodeWebSocket } = mod;
1383
- const { upgradeWebSocket, injectWebSocket: inject } = createNodeWebSocket({ app: app2 });
1384
- wsHandler = upgradeWebSocket(() => ({
1385
- onOpen: (_event, ws) => {
1386
- registerWsClient(ws);
1387
- },
1388
- onClose: (_event, ws) => {
1389
- wsClients.delete(ws);
1390
- },
1391
- onMessage: () => {
1392
- }
1393
- }));
1394
- injectWebSocket = (server2) => {
1395
- inject(server2);
1396
- };
1397
- } catch {
1398
- }
1399
- }
1539
+ const playgroundWs = createPlaygroundWs(playgroundConfig);
1400
1540
  let routes = [];
1401
1541
  let disabledRoutes = [];
1402
1542
  let ignoredRoutes = [];
1403
1543
  let configFiles = [];
1404
1544
  let disabledConfigFiles = [];
1405
- let app = buildApp({
1545
+ const appParams = {
1406
1546
  routes,
1407
1547
  disabledRoutes,
1408
1548
  ignoredRoutes,
@@ -1412,9 +1552,13 @@ async function createFetchServer(options = {}) {
1412
1552
  playground: playgroundConfig,
1413
1553
  root,
1414
1554
  logger,
1415
- onResponse: handleRouteResponse,
1416
- ...wsHandler ? { wsHandler } : {}
1417
- });
1555
+ onResponse: playgroundWs.handleRouteResponse
1556
+ };
1557
+ const initialWsHandler = playgroundWs.getWsHandler();
1558
+ if (initialWsHandler) {
1559
+ appParams.wsHandler = initialWsHandler;
1560
+ }
1561
+ let app = buildFetchServerApp(appParams);
1418
1562
  const refreshRoutes = async () => {
1419
1563
  try {
1420
1564
  const collected = [];
@@ -1450,7 +1594,7 @@ async function createFetchServer(options = {}) {
1450
1594
  const resolvedConfigs = Array.from(configMap.values());
1451
1595
  configFiles = resolvedConfigs.filter((entry) => entry.enabled);
1452
1596
  disabledConfigFiles = resolvedConfigs.filter((entry) => !entry.enabled);
1453
- app = buildApp({
1597
+ const refreshedParams = {
1454
1598
  routes,
1455
1599
  disabledRoutes,
1456
1600
  ignoredRoutes,
@@ -1460,16 +1604,21 @@ async function createFetchServer(options = {}) {
1460
1604
  playground: playgroundConfig,
1461
1605
  root,
1462
1606
  logger,
1463
- onResponse: handleRouteResponse,
1464
- ...wsHandler ? { wsHandler } : {}
1465
- });
1607
+ onResponse: playgroundWs.handleRouteResponse
1608
+ };
1609
+ const refreshedWsHandler = playgroundWs.getWsHandler();
1610
+ if (refreshedWsHandler) {
1611
+ refreshedParams.wsHandler = refreshedWsHandler;
1612
+ }
1613
+ app = buildFetchServerApp(refreshedParams);
1466
1614
  logger.info(`Loaded ${routes.length} mock routes.`);
1467
1615
  } catch (error) {
1468
1616
  logger.error("Failed to scan mock routes:", error);
1469
1617
  }
1470
1618
  };
1471
1619
  await refreshRoutes();
1472
- await setupPlaygroundWebSocket(app);
1620
+ await playgroundWs.setupPlaygroundWebSocket(app);
1621
+ const wsHandler = playgroundWs.getWsHandler();
1473
1622
  if (wsHandler && playgroundConfig.enabled) {
1474
1623
  app.get(`${playgroundConfig.path}/ws`, wsHandler);
1475
1624
  }
@@ -1486,9 +1635,12 @@ async function createFetchServer(options = {}) {
1486
1635
  const server = {
1487
1636
  fetch,
1488
1637
  refresh: refreshRoutes,
1489
- getRoutes: () => routes,
1490
- ...injectWebSocket ? { injectWebSocket } : {}
1638
+ getRoutes: () => routes
1491
1639
  };
1640
+ const injectWebSocket = playgroundWs.getInjectWebSocket();
1641
+ if (injectWebSocket) {
1642
+ server.injectWebSocket = injectWebSocket;
1643
+ }
1492
1644
  if (watcher) {
1493
1645
  server.close = async () => {
1494
1646
  await watcher.close();