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