@mokup/server 1.0.1 → 1.0.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.
package/dist/index.cjs CHANGED
@@ -189,6 +189,18 @@ function applyRouteOverrides(response, route) {
189
189
  }
190
190
  return new Response(response.body, { status, headers });
191
191
  }
192
+ function resolveResponse(value, fallback) {
193
+ if (value instanceof Response) {
194
+ return value;
195
+ }
196
+ if (value && typeof value === "object" && "res" in value) {
197
+ const resolved = value.res;
198
+ if (resolved instanceof Response) {
199
+ return resolved;
200
+ }
201
+ }
202
+ return fallback;
203
+ }
192
204
  function normalizeHandlerValue(c, value) {
193
205
  if (value instanceof Response) {
194
206
  return value;
@@ -221,30 +233,41 @@ function createRouteHandler(route) {
221
233
  return normalizeHandlerValue(c, value);
222
234
  };
223
235
  }
224
- function createFinalizeMiddleware(route) {
236
+ function createFinalizeMiddleware(route, onResponse) {
225
237
  return async (c, next) => {
226
238
  const response = await next();
227
- const resolved = response ?? c.res;
239
+ const resolved = resolveResponse(response, c.res);
228
240
  if (route.delay && route.delay > 0) {
229
241
  await delay(route.delay);
230
242
  }
231
- return applyRouteOverrides(resolved, route);
243
+ const overridden = applyRouteOverrides(resolved, route);
244
+ c.res = overridden;
245
+ if (onResponse) {
246
+ try {
247
+ const result = onResponse(route, overridden);
248
+ if (result instanceof Promise) {
249
+ result.catch(() => void 0);
250
+ }
251
+ } catch {
252
+ }
253
+ }
254
+ return overridden;
232
255
  };
233
256
  }
234
257
  function wrapMiddleware(handler) {
235
258
  return async (c, next) => {
236
259
  const response = await handler(c, next);
237
- return response ?? c.res;
260
+ return resolveResponse(response, c.res);
238
261
  };
239
262
  }
240
- function createHonoApp(routes) {
263
+ function createHonoApp(routes, options = {}) {
241
264
  const app = new hono.Hono({ router: new hono.PatternRouter(), strict: false });
242
265
  for (const route of routes) {
243
266
  const middlewares = route.middlewares?.map((entry) => wrapMiddleware(entry.handle)) ?? [];
244
267
  app.on(
245
268
  route.method,
246
269
  toHonoPath(route),
247
- createFinalizeMiddleware(route),
270
+ createFinalizeMiddleware(route, options.onResponse),
248
271
  ...middlewares,
249
272
  createRouteHandler(route)
250
273
  );
@@ -426,7 +449,16 @@ function registerPlaygroundRoutes(params) {
426
449
  return new Response("Playground is not available.", { status: 500 });
427
450
  }
428
451
  };
429
- params.app.get(playgroundPath, (c) => c.redirect(`${playgroundPath}/`));
452
+ params.app.get(playgroundPath, (c) => {
453
+ try {
454
+ const pathname = new URL(c.req.raw.url, "http://localhost").pathname;
455
+ if (pathname.endsWith("/")) {
456
+ return serveIndex();
457
+ }
458
+ } catch {
459
+ }
460
+ return c.redirect(`${playgroundPath}/`);
461
+ });
430
462
  params.app.get(`${playgroundPath}/`, () => serveIndex());
431
463
  params.app.get(`${playgroundPath}/index.html`, () => serveIndex());
432
464
  params.app.get(`${playgroundPath}/routes`, (c) => {
@@ -972,8 +1004,11 @@ function buildApp(params) {
972
1004
  config: params.playground,
973
1005
  root: params.root
974
1006
  });
1007
+ if (params.wsHandler && params.playground.enabled) {
1008
+ app.get(`${params.playground.path}/ws`, params.wsHandler);
1009
+ }
975
1010
  if (params.routes.length > 0) {
976
- const mockApp = createHonoApp(params.routes);
1011
+ const mockApp = createHonoApp(params.routes, { onResponse: params.onResponse });
977
1012
  app.route("/", mockApp);
978
1013
  }
979
1014
  return app;
@@ -1060,13 +1095,81 @@ async function createFetchServer(options = {}) {
1060
1095
  const logger = createLogger(logEnabled);
1061
1096
  const playgroundConfig = resolvePlaygroundOptions(resolvePlaygroundInput(optionList));
1062
1097
  const dirs = resolveAllDirs(optionList, root);
1098
+ const routeCounts = {};
1099
+ const wsClients = /* @__PURE__ */ new Set();
1100
+ let totalCount = 0;
1101
+ let wsHandler;
1102
+ let injectWebSocket;
1103
+ function getRouteKey(route) {
1104
+ return `${route.method} ${route.template}`;
1105
+ }
1106
+ function buildSnapshot() {
1107
+ return {
1108
+ type: "snapshot",
1109
+ total: totalCount,
1110
+ perRoute: { ...routeCounts }
1111
+ };
1112
+ }
1113
+ function broadcast(payload) {
1114
+ if (wsClients.size === 0) {
1115
+ return;
1116
+ }
1117
+ const message = JSON.stringify(payload);
1118
+ for (const client of wsClients) {
1119
+ try {
1120
+ client.send(message);
1121
+ } catch {
1122
+ wsClients.delete(client);
1123
+ }
1124
+ }
1125
+ }
1126
+ function registerWsClient(client) {
1127
+ wsClients.add(client);
1128
+ try {
1129
+ client.send(JSON.stringify(buildSnapshot()));
1130
+ } catch {
1131
+ wsClients.delete(client);
1132
+ }
1133
+ }
1134
+ function handleRouteResponse(route) {
1135
+ const routeKey = getRouteKey(route);
1136
+ routeCounts[routeKey] = (routeCounts[routeKey] ?? 0) + 1;
1137
+ totalCount += 1;
1138
+ broadcast({ type: "increment", routeKey, total: totalCount });
1139
+ }
1140
+ async function setupPlaygroundWebSocket(app2) {
1141
+ if (!playgroundConfig.enabled) {
1142
+ return;
1143
+ }
1144
+ try {
1145
+ const mod = await import('@hono/node-ws');
1146
+ const { createNodeWebSocket } = mod;
1147
+ const { upgradeWebSocket, injectWebSocket: inject } = createNodeWebSocket({ app: app2 });
1148
+ wsHandler = upgradeWebSocket(() => ({
1149
+ onOpen: (_event, ws) => {
1150
+ registerWsClient(ws);
1151
+ },
1152
+ onClose: (_event, ws) => {
1153
+ wsClients.delete(ws);
1154
+ },
1155
+ onMessage: () => {
1156
+ }
1157
+ }));
1158
+ injectWebSocket = (server2) => {
1159
+ inject(server2);
1160
+ };
1161
+ } catch {
1162
+ }
1163
+ }
1063
1164
  let routes = [];
1064
1165
  let app = buildApp({
1065
1166
  routes,
1066
1167
  dirs,
1067
1168
  playground: playgroundConfig,
1068
1169
  root,
1069
- logger
1170
+ logger,
1171
+ onResponse: handleRouteResponse,
1172
+ wsHandler
1070
1173
  });
1071
1174
  const refreshRoutes = async () => {
1072
1175
  try {
@@ -1093,7 +1196,9 @@ async function createFetchServer(options = {}) {
1093
1196
  dirs,
1094
1197
  playground: playgroundConfig,
1095
1198
  root,
1096
- logger
1199
+ logger,
1200
+ onResponse: handleRouteResponse,
1201
+ wsHandler
1097
1202
  });
1098
1203
  logger.info(`Loaded ${routes.length} mock routes.`);
1099
1204
  } catch (error) {
@@ -1101,6 +1206,10 @@ async function createFetchServer(options = {}) {
1101
1206
  }
1102
1207
  };
1103
1208
  await refreshRoutes();
1209
+ await setupPlaygroundWebSocket(app);
1210
+ if (wsHandler && playgroundConfig.enabled) {
1211
+ app.get(`${playgroundConfig.path}/ws`, wsHandler);
1212
+ }
1104
1213
  const scheduleRefresh = createDebouncer(80, () => {
1105
1214
  void refreshRoutes();
1106
1215
  });
@@ -1114,7 +1223,8 @@ async function createFetchServer(options = {}) {
1114
1223
  const server = {
1115
1224
  fetch,
1116
1225
  refresh: refreshRoutes,
1117
- getRoutes: () => routes
1226
+ getRoutes: () => routes,
1227
+ injectWebSocket
1118
1228
  };
1119
1229
  if (watcher) {
1120
1230
  server.close = async () => {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MokupServerOptions, F as FetchHandler, a as MokupWorkerInput } from './shared/server.C0FGI34s.cjs';
2
- export { b as MokupWorkerBundle } from './shared/server.C0FGI34s.cjs';
1
+ import { S as ServerOptions, F as FetchHandler, W as WorkerInput } from './shared/server.DNITwCtQ.cjs';
2
+ export { a as WorkerBundle } from './shared/server.DNITwCtQ.cjs';
3
3
  import { RouteToken } from '@mokup/runtime';
4
4
  export { Manifest, ManifestRoute, ModuleMap, RuntimeOptions } from '@mokup/runtime';
5
5
  import { Context, MiddlewareHandler } from '@mokup/shared/hono';
@@ -21,9 +21,9 @@ interface NodeResponseLike {
21
21
  }
22
22
 
23
23
  type NextFunction = (error?: unknown) => void;
24
- declare function createConnectMiddleware(options: MokupServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: NextFunction) => Promise<void>;
24
+ declare function createConnectMiddleware(options: ServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: NextFunction) => Promise<void>;
25
25
 
26
- declare function createExpressMiddleware(options: MokupServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: (error?: unknown) => void) => Promise<void>;
26
+ declare function createExpressMiddleware(options: ServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: (error?: unknown) => void) => Promise<void>;
27
27
 
28
28
  interface FastifyRequestLike extends NodeRequestLike {
29
29
  raw?: NodeRequestLike;
@@ -36,16 +36,15 @@ interface FastifyReplyLike {
36
36
  interface FastifyInstanceLike {
37
37
  addHook: (name: 'onRequest' | 'preHandler', handler: (request: FastifyRequestLike, reply: FastifyReplyLike) => Promise<void> | void) => void;
38
38
  }
39
- declare function createFastifyPlugin(options: MokupServerOptions): (instance: FastifyInstanceLike) => Promise<void>;
39
+ declare function createFastifyPlugin(options: ServerOptions): (instance: FastifyInstanceLike) => Promise<void>;
40
40
 
41
- declare function createFetchHandler(options: MokupServerOptions): FetchHandler;
41
+ declare function createFetchHandler(options: ServerOptions): FetchHandler;
42
42
 
43
43
  type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
44
- type MockMiddleware = MiddlewareHandler;
45
- type MockResponseHandler = (context: Context) => Response | Promise<Response> | unknown;
46
- type MockResponse = unknown | MockResponseHandler;
44
+ type RequestHandler = (context: Context) => Response | Promise<Response> | unknown;
45
+ type RouteResponse = unknown | RequestHandler;
47
46
  interface ResolvedMiddleware {
48
- handle: MockMiddleware;
47
+ handle: MiddlewareHandler;
49
48
  source: string;
50
49
  index: number;
51
50
  }
@@ -55,7 +54,7 @@ interface ResolvedRoute {
55
54
  method: HttpMethod;
56
55
  tokens: RouteToken[];
57
56
  score: number[];
58
- handler: MockResponse;
57
+ handler: RouteResponse;
59
58
  middlewares?: ResolvedMiddleware[];
60
59
  status?: number;
61
60
  headers?: Record<string, string>;
@@ -66,7 +65,7 @@ type RouteTable = ResolvedRoute[];
66
65
 
67
66
  type DirInput = string | string[] | ((root: string) => string | string[]) | undefined;
68
67
 
69
- interface MokupFetchServerOptions {
68
+ interface FetchServerOptions {
70
69
  dir?: DirInput;
71
70
  prefix?: string;
72
71
  include?: RegExp | RegExp[];
@@ -81,15 +80,19 @@ interface MokupFetchServerOptions {
81
80
  port?: number;
82
81
  root?: string;
83
82
  }
84
- type MokupFetchServerOptionsInput = MokupFetchServerOptions | MokupFetchServerOptions[];
83
+ type FetchServerOptionsInput = FetchServerOptions | FetchServerOptions[];
85
84
 
86
- interface MokupFetchServer {
85
+ interface FetchServer {
87
86
  fetch: (request: Request) => Promise<Response>;
88
87
  refresh: () => Promise<void>;
89
88
  getRoutes: () => RouteTable;
89
+ injectWebSocket?: (server: NodeWebSocketServer) => void;
90
90
  close?: () => Promise<void>;
91
91
  }
92
- declare function createFetchServer(options?: MokupFetchServerOptionsInput): Promise<MokupFetchServer>;
92
+ interface NodeWebSocketServer {
93
+ on: (event: string, listener: (...args: unknown[]) => void) => void;
94
+ }
95
+ declare function createFetchServer(options?: FetchServerOptionsInput): Promise<FetchServer>;
93
96
 
94
97
  interface HonoContextLike {
95
98
  req: {
@@ -104,7 +107,7 @@ interface HonoRouteLike {
104
107
  method: string;
105
108
  handler: HonoMiddleware;
106
109
  }
107
- declare function createHonoMiddleware(options: MokupServerOptions): HonoMiddleware & {
110
+ declare function createHonoMiddleware(options: ServerOptions): HonoMiddleware & {
108
111
  routes: HonoRouteLike[];
109
112
  };
110
113
 
@@ -119,13 +122,13 @@ interface KoaContextLike {
119
122
  set: (header: Record<string, string>) => void;
120
123
  }
121
124
  type KoaNext = () => Promise<unknown>;
122
- declare function createKoaMiddleware(options: MokupServerOptions): (ctx: KoaContextLike, next: KoaNext) => Promise<void>;
125
+ declare function createKoaMiddleware(options: ServerOptions): (ctx: KoaContextLike, next: KoaNext) => Promise<void>;
123
126
 
124
- interface MokupWorker {
127
+ interface FetchWorker {
125
128
  fetch: (request: Request) => Promise<Response>;
126
129
  }
127
- declare function createMokupWorker(input: string): Promise<MokupWorker>;
128
- declare function createMokupWorker(input: Exclude<MokupWorkerInput, string>): MokupWorker;
130
+ declare function createMokupWorker(input: string): Promise<FetchWorker>;
131
+ declare function createMokupWorker(input: Exclude<WorkerInput, string>): FetchWorker;
129
132
 
130
- export { FetchHandler, MokupServerOptions, MokupWorkerInput, createConnectMiddleware, createExpressMiddleware, createFastifyPlugin, createFetchHandler, createFetchServer, createHonoMiddleware, createKoaMiddleware, createMokupWorker };
131
- export type { MokupFetchServer, MokupFetchServerOptions, MokupFetchServerOptionsInput };
133
+ export { FetchHandler, ServerOptions, WorkerInput, createConnectMiddleware, createExpressMiddleware, createFastifyPlugin, createFetchHandler, createFetchServer, createHonoMiddleware, createKoaMiddleware, createMokupWorker };
134
+ export type { FetchServer, FetchServerOptions, FetchServerOptionsInput };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MokupServerOptions, F as FetchHandler, a as MokupWorkerInput } from './shared/server.C0FGI34s.mjs';
2
- export { b as MokupWorkerBundle } from './shared/server.C0FGI34s.mjs';
1
+ import { S as ServerOptions, F as FetchHandler, W as WorkerInput } from './shared/server.DNITwCtQ.mjs';
2
+ export { a as WorkerBundle } from './shared/server.DNITwCtQ.mjs';
3
3
  import { RouteToken } from '@mokup/runtime';
4
4
  export { Manifest, ManifestRoute, ModuleMap, RuntimeOptions } from '@mokup/runtime';
5
5
  import { Context, MiddlewareHandler } from '@mokup/shared/hono';
@@ -21,9 +21,9 @@ interface NodeResponseLike {
21
21
  }
22
22
 
23
23
  type NextFunction = (error?: unknown) => void;
24
- declare function createConnectMiddleware(options: MokupServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: NextFunction) => Promise<void>;
24
+ declare function createConnectMiddleware(options: ServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: NextFunction) => Promise<void>;
25
25
 
26
- declare function createExpressMiddleware(options: MokupServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: (error?: unknown) => void) => Promise<void>;
26
+ declare function createExpressMiddleware(options: ServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: (error?: unknown) => void) => Promise<void>;
27
27
 
28
28
  interface FastifyRequestLike extends NodeRequestLike {
29
29
  raw?: NodeRequestLike;
@@ -36,16 +36,15 @@ interface FastifyReplyLike {
36
36
  interface FastifyInstanceLike {
37
37
  addHook: (name: 'onRequest' | 'preHandler', handler: (request: FastifyRequestLike, reply: FastifyReplyLike) => Promise<void> | void) => void;
38
38
  }
39
- declare function createFastifyPlugin(options: MokupServerOptions): (instance: FastifyInstanceLike) => Promise<void>;
39
+ declare function createFastifyPlugin(options: ServerOptions): (instance: FastifyInstanceLike) => Promise<void>;
40
40
 
41
- declare function createFetchHandler(options: MokupServerOptions): FetchHandler;
41
+ declare function createFetchHandler(options: ServerOptions): FetchHandler;
42
42
 
43
43
  type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
44
- type MockMiddleware = MiddlewareHandler;
45
- type MockResponseHandler = (context: Context) => Response | Promise<Response> | unknown;
46
- type MockResponse = unknown | MockResponseHandler;
44
+ type RequestHandler = (context: Context) => Response | Promise<Response> | unknown;
45
+ type RouteResponse = unknown | RequestHandler;
47
46
  interface ResolvedMiddleware {
48
- handle: MockMiddleware;
47
+ handle: MiddlewareHandler;
49
48
  source: string;
50
49
  index: number;
51
50
  }
@@ -55,7 +54,7 @@ interface ResolvedRoute {
55
54
  method: HttpMethod;
56
55
  tokens: RouteToken[];
57
56
  score: number[];
58
- handler: MockResponse;
57
+ handler: RouteResponse;
59
58
  middlewares?: ResolvedMiddleware[];
60
59
  status?: number;
61
60
  headers?: Record<string, string>;
@@ -66,7 +65,7 @@ type RouteTable = ResolvedRoute[];
66
65
 
67
66
  type DirInput = string | string[] | ((root: string) => string | string[]) | undefined;
68
67
 
69
- interface MokupFetchServerOptions {
68
+ interface FetchServerOptions {
70
69
  dir?: DirInput;
71
70
  prefix?: string;
72
71
  include?: RegExp | RegExp[];
@@ -81,15 +80,19 @@ interface MokupFetchServerOptions {
81
80
  port?: number;
82
81
  root?: string;
83
82
  }
84
- type MokupFetchServerOptionsInput = MokupFetchServerOptions | MokupFetchServerOptions[];
83
+ type FetchServerOptionsInput = FetchServerOptions | FetchServerOptions[];
85
84
 
86
- interface MokupFetchServer {
85
+ interface FetchServer {
87
86
  fetch: (request: Request) => Promise<Response>;
88
87
  refresh: () => Promise<void>;
89
88
  getRoutes: () => RouteTable;
89
+ injectWebSocket?: (server: NodeWebSocketServer) => void;
90
90
  close?: () => Promise<void>;
91
91
  }
92
- declare function createFetchServer(options?: MokupFetchServerOptionsInput): Promise<MokupFetchServer>;
92
+ interface NodeWebSocketServer {
93
+ on: (event: string, listener: (...args: unknown[]) => void) => void;
94
+ }
95
+ declare function createFetchServer(options?: FetchServerOptionsInput): Promise<FetchServer>;
93
96
 
94
97
  interface HonoContextLike {
95
98
  req: {
@@ -104,7 +107,7 @@ interface HonoRouteLike {
104
107
  method: string;
105
108
  handler: HonoMiddleware;
106
109
  }
107
- declare function createHonoMiddleware(options: MokupServerOptions): HonoMiddleware & {
110
+ declare function createHonoMiddleware(options: ServerOptions): HonoMiddleware & {
108
111
  routes: HonoRouteLike[];
109
112
  };
110
113
 
@@ -119,13 +122,13 @@ interface KoaContextLike {
119
122
  set: (header: Record<string, string>) => void;
120
123
  }
121
124
  type KoaNext = () => Promise<unknown>;
122
- declare function createKoaMiddleware(options: MokupServerOptions): (ctx: KoaContextLike, next: KoaNext) => Promise<void>;
125
+ declare function createKoaMiddleware(options: ServerOptions): (ctx: KoaContextLike, next: KoaNext) => Promise<void>;
123
126
 
124
- interface MokupWorker {
127
+ interface FetchWorker {
125
128
  fetch: (request: Request) => Promise<Response>;
126
129
  }
127
- declare function createMokupWorker(input: string): Promise<MokupWorker>;
128
- declare function createMokupWorker(input: Exclude<MokupWorkerInput, string>): MokupWorker;
130
+ declare function createMokupWorker(input: string): Promise<FetchWorker>;
131
+ declare function createMokupWorker(input: Exclude<WorkerInput, string>): FetchWorker;
129
132
 
130
- export { FetchHandler, MokupServerOptions, MokupWorkerInput, createConnectMiddleware, createExpressMiddleware, createFastifyPlugin, createFetchHandler, createFetchServer, createHonoMiddleware, createKoaMiddleware, createMokupWorker };
131
- export type { MokupFetchServer, MokupFetchServerOptions, MokupFetchServerOptionsInput };
133
+ export { FetchHandler, ServerOptions, WorkerInput, createConnectMiddleware, createExpressMiddleware, createFastifyPlugin, createFetchHandler, createFetchServer, createHonoMiddleware, createKoaMiddleware, createMokupWorker };
134
+ export type { FetchServer, FetchServerOptions, FetchServerOptionsInput };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MokupServerOptions, F as FetchHandler, a as MokupWorkerInput } from './shared/server.C0FGI34s.js';
2
- export { b as MokupWorkerBundle } from './shared/server.C0FGI34s.js';
1
+ import { S as ServerOptions, F as FetchHandler, W as WorkerInput } from './shared/server.DNITwCtQ.js';
2
+ export { a as WorkerBundle } from './shared/server.DNITwCtQ.js';
3
3
  import { RouteToken } from '@mokup/runtime';
4
4
  export { Manifest, ManifestRoute, ModuleMap, RuntimeOptions } from '@mokup/runtime';
5
5
  import { Context, MiddlewareHandler } from '@mokup/shared/hono';
@@ -21,9 +21,9 @@ interface NodeResponseLike {
21
21
  }
22
22
 
23
23
  type NextFunction = (error?: unknown) => void;
24
- declare function createConnectMiddleware(options: MokupServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: NextFunction) => Promise<void>;
24
+ declare function createConnectMiddleware(options: ServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: NextFunction) => Promise<void>;
25
25
 
26
- declare function createExpressMiddleware(options: MokupServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: (error?: unknown) => void) => Promise<void>;
26
+ declare function createExpressMiddleware(options: ServerOptions): (req: NodeRequestLike, res: NodeResponseLike, next: (error?: unknown) => void) => Promise<void>;
27
27
 
28
28
  interface FastifyRequestLike extends NodeRequestLike {
29
29
  raw?: NodeRequestLike;
@@ -36,16 +36,15 @@ interface FastifyReplyLike {
36
36
  interface FastifyInstanceLike {
37
37
  addHook: (name: 'onRequest' | 'preHandler', handler: (request: FastifyRequestLike, reply: FastifyReplyLike) => Promise<void> | void) => void;
38
38
  }
39
- declare function createFastifyPlugin(options: MokupServerOptions): (instance: FastifyInstanceLike) => Promise<void>;
39
+ declare function createFastifyPlugin(options: ServerOptions): (instance: FastifyInstanceLike) => Promise<void>;
40
40
 
41
- declare function createFetchHandler(options: MokupServerOptions): FetchHandler;
41
+ declare function createFetchHandler(options: ServerOptions): FetchHandler;
42
42
 
43
43
  type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
44
- type MockMiddleware = MiddlewareHandler;
45
- type MockResponseHandler = (context: Context) => Response | Promise<Response> | unknown;
46
- type MockResponse = unknown | MockResponseHandler;
44
+ type RequestHandler = (context: Context) => Response | Promise<Response> | unknown;
45
+ type RouteResponse = unknown | RequestHandler;
47
46
  interface ResolvedMiddleware {
48
- handle: MockMiddleware;
47
+ handle: MiddlewareHandler;
49
48
  source: string;
50
49
  index: number;
51
50
  }
@@ -55,7 +54,7 @@ interface ResolvedRoute {
55
54
  method: HttpMethod;
56
55
  tokens: RouteToken[];
57
56
  score: number[];
58
- handler: MockResponse;
57
+ handler: RouteResponse;
59
58
  middlewares?: ResolvedMiddleware[];
60
59
  status?: number;
61
60
  headers?: Record<string, string>;
@@ -66,7 +65,7 @@ type RouteTable = ResolvedRoute[];
66
65
 
67
66
  type DirInput = string | string[] | ((root: string) => string | string[]) | undefined;
68
67
 
69
- interface MokupFetchServerOptions {
68
+ interface FetchServerOptions {
70
69
  dir?: DirInput;
71
70
  prefix?: string;
72
71
  include?: RegExp | RegExp[];
@@ -81,15 +80,19 @@ interface MokupFetchServerOptions {
81
80
  port?: number;
82
81
  root?: string;
83
82
  }
84
- type MokupFetchServerOptionsInput = MokupFetchServerOptions | MokupFetchServerOptions[];
83
+ type FetchServerOptionsInput = FetchServerOptions | FetchServerOptions[];
85
84
 
86
- interface MokupFetchServer {
85
+ interface FetchServer {
87
86
  fetch: (request: Request) => Promise<Response>;
88
87
  refresh: () => Promise<void>;
89
88
  getRoutes: () => RouteTable;
89
+ injectWebSocket?: (server: NodeWebSocketServer) => void;
90
90
  close?: () => Promise<void>;
91
91
  }
92
- declare function createFetchServer(options?: MokupFetchServerOptionsInput): Promise<MokupFetchServer>;
92
+ interface NodeWebSocketServer {
93
+ on: (event: string, listener: (...args: unknown[]) => void) => void;
94
+ }
95
+ declare function createFetchServer(options?: FetchServerOptionsInput): Promise<FetchServer>;
93
96
 
94
97
  interface HonoContextLike {
95
98
  req: {
@@ -104,7 +107,7 @@ interface HonoRouteLike {
104
107
  method: string;
105
108
  handler: HonoMiddleware;
106
109
  }
107
- declare function createHonoMiddleware(options: MokupServerOptions): HonoMiddleware & {
110
+ declare function createHonoMiddleware(options: ServerOptions): HonoMiddleware & {
108
111
  routes: HonoRouteLike[];
109
112
  };
110
113
 
@@ -119,13 +122,13 @@ interface KoaContextLike {
119
122
  set: (header: Record<string, string>) => void;
120
123
  }
121
124
  type KoaNext = () => Promise<unknown>;
122
- declare function createKoaMiddleware(options: MokupServerOptions): (ctx: KoaContextLike, next: KoaNext) => Promise<void>;
125
+ declare function createKoaMiddleware(options: ServerOptions): (ctx: KoaContextLike, next: KoaNext) => Promise<void>;
123
126
 
124
- interface MokupWorker {
127
+ interface FetchWorker {
125
128
  fetch: (request: Request) => Promise<Response>;
126
129
  }
127
- declare function createMokupWorker(input: string): Promise<MokupWorker>;
128
- declare function createMokupWorker(input: Exclude<MokupWorkerInput, string>): MokupWorker;
130
+ declare function createMokupWorker(input: string): Promise<FetchWorker>;
131
+ declare function createMokupWorker(input: Exclude<WorkerInput, string>): FetchWorker;
129
132
 
130
- export { FetchHandler, MokupServerOptions, MokupWorkerInput, createConnectMiddleware, createExpressMiddleware, createFastifyPlugin, createFetchHandler, createFetchServer, createHonoMiddleware, createKoaMiddleware, createMokupWorker };
131
- export type { MokupFetchServer, MokupFetchServerOptions, MokupFetchServerOptionsInput };
133
+ export { FetchHandler, ServerOptions, WorkerInput, createConnectMiddleware, createExpressMiddleware, createFastifyPlugin, createFetchHandler, createFetchServer, createHonoMiddleware, createKoaMiddleware, createMokupWorker };
134
+ export type { FetchServer, FetchServerOptions, FetchServerOptionsInput };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRuntime, compareRouteScore, parseRouteTemplate } from '@mokup/runtime';
2
- import { t as toRuntimeOptions, a as toRuntimeRequestFromNode, b as applyRuntimeResultToNode, c as toBinaryBody, d as createFetchHandler } from './shared/server.HVB7OYyI.mjs';
2
+ import { t as toRuntimeOptions, a as toRuntimeRequestFromNode, b as applyRuntimeResultToNode, d as toBinaryBody, c as createFetchHandler } from './shared/server.Dje1y79O.mjs';
3
3
  import { cwd } from 'node:process';
4
4
  import { Hono, PatternRouter } from '@mokup/shared/hono';
5
5
  import { resolve, isAbsolute, join, normalize, extname, dirname, relative, basename } from '@mokup/shared/pathe';
@@ -186,6 +186,18 @@ function applyRouteOverrides(response, route) {
186
186
  }
187
187
  return new Response(response.body, { status, headers });
188
188
  }
189
+ function resolveResponse(value, fallback) {
190
+ if (value instanceof Response) {
191
+ return value;
192
+ }
193
+ if (value && typeof value === "object" && "res" in value) {
194
+ const resolved = value.res;
195
+ if (resolved instanceof Response) {
196
+ return resolved;
197
+ }
198
+ }
199
+ return fallback;
200
+ }
189
201
  function normalizeHandlerValue(c, value) {
190
202
  if (value instanceof Response) {
191
203
  return value;
@@ -218,30 +230,41 @@ function createRouteHandler(route) {
218
230
  return normalizeHandlerValue(c, value);
219
231
  };
220
232
  }
221
- function createFinalizeMiddleware(route) {
233
+ function createFinalizeMiddleware(route, onResponse) {
222
234
  return async (c, next) => {
223
235
  const response = await next();
224
- const resolved = response ?? c.res;
236
+ const resolved = resolveResponse(response, c.res);
225
237
  if (route.delay && route.delay > 0) {
226
238
  await delay(route.delay);
227
239
  }
228
- return applyRouteOverrides(resolved, route);
240
+ const overridden = applyRouteOverrides(resolved, route);
241
+ c.res = overridden;
242
+ if (onResponse) {
243
+ try {
244
+ const result = onResponse(route, overridden);
245
+ if (result instanceof Promise) {
246
+ result.catch(() => void 0);
247
+ }
248
+ } catch {
249
+ }
250
+ }
251
+ return overridden;
229
252
  };
230
253
  }
231
254
  function wrapMiddleware(handler) {
232
255
  return async (c, next) => {
233
256
  const response = await handler(c, next);
234
- return response ?? c.res;
257
+ return resolveResponse(response, c.res);
235
258
  };
236
259
  }
237
- function createHonoApp(routes) {
260
+ function createHonoApp(routes, options = {}) {
238
261
  const app = new Hono({ router: new PatternRouter(), strict: false });
239
262
  for (const route of routes) {
240
263
  const middlewares = route.middlewares?.map((entry) => wrapMiddleware(entry.handle)) ?? [];
241
264
  app.on(
242
265
  route.method,
243
266
  toHonoPath(route),
244
- createFinalizeMiddleware(route),
267
+ createFinalizeMiddleware(route, options.onResponse),
245
268
  ...middlewares,
246
269
  createRouteHandler(route)
247
270
  );
@@ -423,7 +446,16 @@ function registerPlaygroundRoutes(params) {
423
446
  return new Response("Playground is not available.", { status: 500 });
424
447
  }
425
448
  };
426
- params.app.get(playgroundPath, (c) => c.redirect(`${playgroundPath}/`));
449
+ params.app.get(playgroundPath, (c) => {
450
+ try {
451
+ const pathname = new URL(c.req.raw.url, "http://localhost").pathname;
452
+ if (pathname.endsWith("/")) {
453
+ return serveIndex();
454
+ }
455
+ } catch {
456
+ }
457
+ return c.redirect(`${playgroundPath}/`);
458
+ });
427
459
  params.app.get(`${playgroundPath}/`, () => serveIndex());
428
460
  params.app.get(`${playgroundPath}/index.html`, () => serveIndex());
429
461
  params.app.get(`${playgroundPath}/routes`, (c) => {
@@ -969,8 +1001,11 @@ function buildApp(params) {
969
1001
  config: params.playground,
970
1002
  root: params.root
971
1003
  });
1004
+ if (params.wsHandler && params.playground.enabled) {
1005
+ app.get(`${params.playground.path}/ws`, params.wsHandler);
1006
+ }
972
1007
  if (params.routes.length > 0) {
973
- const mockApp = createHonoApp(params.routes);
1008
+ const mockApp = createHonoApp(params.routes, { onResponse: params.onResponse });
974
1009
  app.route("/", mockApp);
975
1010
  }
976
1011
  return app;
@@ -1057,13 +1092,81 @@ async function createFetchServer(options = {}) {
1057
1092
  const logger = createLogger(logEnabled);
1058
1093
  const playgroundConfig = resolvePlaygroundOptions(resolvePlaygroundInput(optionList));
1059
1094
  const dirs = resolveAllDirs(optionList, root);
1095
+ const routeCounts = {};
1096
+ const wsClients = /* @__PURE__ */ new Set();
1097
+ let totalCount = 0;
1098
+ let wsHandler;
1099
+ let injectWebSocket;
1100
+ function getRouteKey(route) {
1101
+ return `${route.method} ${route.template}`;
1102
+ }
1103
+ function buildSnapshot() {
1104
+ return {
1105
+ type: "snapshot",
1106
+ total: totalCount,
1107
+ perRoute: { ...routeCounts }
1108
+ };
1109
+ }
1110
+ function broadcast(payload) {
1111
+ if (wsClients.size === 0) {
1112
+ return;
1113
+ }
1114
+ const message = JSON.stringify(payload);
1115
+ for (const client of wsClients) {
1116
+ try {
1117
+ client.send(message);
1118
+ } catch {
1119
+ wsClients.delete(client);
1120
+ }
1121
+ }
1122
+ }
1123
+ function registerWsClient(client) {
1124
+ wsClients.add(client);
1125
+ try {
1126
+ client.send(JSON.stringify(buildSnapshot()));
1127
+ } catch {
1128
+ wsClients.delete(client);
1129
+ }
1130
+ }
1131
+ function handleRouteResponse(route) {
1132
+ const routeKey = getRouteKey(route);
1133
+ routeCounts[routeKey] = (routeCounts[routeKey] ?? 0) + 1;
1134
+ totalCount += 1;
1135
+ broadcast({ type: "increment", routeKey, total: totalCount });
1136
+ }
1137
+ async function setupPlaygroundWebSocket(app2) {
1138
+ if (!playgroundConfig.enabled) {
1139
+ return;
1140
+ }
1141
+ try {
1142
+ const mod = await import('@hono/node-ws');
1143
+ const { createNodeWebSocket } = mod;
1144
+ const { upgradeWebSocket, injectWebSocket: inject } = createNodeWebSocket({ app: app2 });
1145
+ wsHandler = upgradeWebSocket(() => ({
1146
+ onOpen: (_event, ws) => {
1147
+ registerWsClient(ws);
1148
+ },
1149
+ onClose: (_event, ws) => {
1150
+ wsClients.delete(ws);
1151
+ },
1152
+ onMessage: () => {
1153
+ }
1154
+ }));
1155
+ injectWebSocket = (server2) => {
1156
+ inject(server2);
1157
+ };
1158
+ } catch {
1159
+ }
1160
+ }
1060
1161
  let routes = [];
1061
1162
  let app = buildApp({
1062
1163
  routes,
1063
1164
  dirs,
1064
1165
  playground: playgroundConfig,
1065
1166
  root,
1066
- logger
1167
+ logger,
1168
+ onResponse: handleRouteResponse,
1169
+ wsHandler
1067
1170
  });
1068
1171
  const refreshRoutes = async () => {
1069
1172
  try {
@@ -1090,7 +1193,9 @@ async function createFetchServer(options = {}) {
1090
1193
  dirs,
1091
1194
  playground: playgroundConfig,
1092
1195
  root,
1093
- logger
1196
+ logger,
1197
+ onResponse: handleRouteResponse,
1198
+ wsHandler
1094
1199
  });
1095
1200
  logger.info(`Loaded ${routes.length} mock routes.`);
1096
1201
  } catch (error) {
@@ -1098,6 +1203,10 @@ async function createFetchServer(options = {}) {
1098
1203
  }
1099
1204
  };
1100
1205
  await refreshRoutes();
1206
+ await setupPlaygroundWebSocket(app);
1207
+ if (wsHandler && playgroundConfig.enabled) {
1208
+ app.get(`${playgroundConfig.path}/ws`, wsHandler);
1209
+ }
1101
1210
  const scheduleRefresh = createDebouncer(80, () => {
1102
1211
  void refreshRoutes();
1103
1212
  });
@@ -1111,7 +1220,8 @@ async function createFetchServer(options = {}) {
1111
1220
  const server = {
1112
1221
  fetch,
1113
1222
  refresh: refreshRoutes,
1114
- getRoutes: () => routes
1223
+ getRoutes: () => routes,
1224
+ injectWebSocket
1115
1225
  };
1116
1226
  if (watcher) {
1117
1227
  server.close = async () => {
package/dist/node.cjs ADDED
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const nodeServer = require('@hono/node-server');
4
+
5
+
6
+
7
+ exports.serve = nodeServer.serve;
@@ -0,0 +1 @@
1
+ export { serve } from '@hono/node-server';
@@ -0,0 +1 @@
1
+ export { serve } from '@hono/node-server';
package/dist/node.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { serve } from '@hono/node-server';
package/dist/node.mjs ADDED
@@ -0,0 +1 @@
1
+ export { serve } from '@hono/node-server';
@@ -1,15 +1,15 @@
1
1
  import { RuntimeOptions, Manifest, ModuleMap } from '@mokup/runtime';
2
2
 
3
- interface MokupServerOptions extends RuntimeOptions {
3
+ interface ServerOptions extends RuntimeOptions {
4
4
  onNotFound?: 'next' | 'response';
5
5
  }
6
6
  type FetchHandler = (request: Request) => Promise<Response | null>;
7
- interface MokupWorkerBundle {
7
+ interface WorkerBundle {
8
8
  manifest: Manifest;
9
9
  moduleMap?: ModuleMap;
10
10
  moduleBase?: string | URL;
11
11
  onNotFound?: 'next' | 'response';
12
12
  }
13
- type MokupWorkerInput = string | Manifest | MokupWorkerBundle;
13
+ type WorkerInput = string | Manifest | WorkerBundle;
14
14
 
15
- export type { FetchHandler as F, MokupServerOptions as M, MokupWorkerInput as a, MokupWorkerBundle as b };
15
+ export type { FetchHandler as F, ServerOptions as S, WorkerInput as W, WorkerBundle as a };
@@ -1,15 +1,15 @@
1
1
  import { RuntimeOptions, Manifest, ModuleMap } from '@mokup/runtime';
2
2
 
3
- interface MokupServerOptions extends RuntimeOptions {
3
+ interface ServerOptions extends RuntimeOptions {
4
4
  onNotFound?: 'next' | 'response';
5
5
  }
6
6
  type FetchHandler = (request: Request) => Promise<Response | null>;
7
- interface MokupWorkerBundle {
7
+ interface WorkerBundle {
8
8
  manifest: Manifest;
9
9
  moduleMap?: ModuleMap;
10
10
  moduleBase?: string | URL;
11
11
  onNotFound?: 'next' | 'response';
12
12
  }
13
- type MokupWorkerInput = string | Manifest | MokupWorkerBundle;
13
+ type WorkerInput = string | Manifest | WorkerBundle;
14
14
 
15
- export type { FetchHandler as F, MokupServerOptions as M, MokupWorkerInput as a, MokupWorkerBundle as b };
15
+ export type { FetchHandler as F, ServerOptions as S, WorkerInput as W, WorkerBundle as a };
@@ -1,15 +1,15 @@
1
1
  import { RuntimeOptions, Manifest, ModuleMap } from '@mokup/runtime';
2
2
 
3
- interface MokupServerOptions extends RuntimeOptions {
3
+ interface ServerOptions extends RuntimeOptions {
4
4
  onNotFound?: 'next' | 'response';
5
5
  }
6
6
  type FetchHandler = (request: Request) => Promise<Response | null>;
7
- interface MokupWorkerBundle {
7
+ interface WorkerBundle {
8
8
  manifest: Manifest;
9
9
  moduleMap?: ModuleMap;
10
10
  moduleBase?: string | URL;
11
11
  onNotFound?: 'next' | 'response';
12
12
  }
13
- type MokupWorkerInput = string | Manifest | MokupWorkerBundle;
13
+ type WorkerInput = string | Manifest | WorkerBundle;
14
14
 
15
- export type { FetchHandler as F, MokupServerOptions as M, MokupWorkerInput as a, MokupWorkerBundle as b };
15
+ export type { FetchHandler as F, ServerOptions as S, WorkerInput as W, WorkerBundle as a };
@@ -255,4 +255,4 @@ function createFetchHandler(options) {
255
255
  };
256
256
  }
257
257
 
258
- export { toRuntimeRequestFromNode as a, applyRuntimeResultToNode as b, toBinaryBody as c, createFetchHandler as d, toRuntimeOptions as t };
258
+ export { toRuntimeRequestFromNode as a, applyRuntimeResultToNode as b, createFetchHandler as c, toBinaryBody as d, toRuntimeOptions as t };
package/dist/worker.d.cts CHANGED
@@ -1,10 +1,10 @@
1
- import { a as MokupWorkerInput } from './shared/server.C0FGI34s.cjs';
1
+ import { W as WorkerInput } from './shared/server.DNITwCtQ.cjs';
2
2
  import '@mokup/runtime';
3
3
 
4
- interface MokupWorker {
4
+ interface FetchWorker {
5
5
  fetch: (request: Request) => Promise<Response>;
6
6
  }
7
- declare function createMokupWorker(input: Exclude<MokupWorkerInput, string>): MokupWorker;
7
+ declare function createMokupWorker(input: Exclude<WorkerInput, string>): FetchWorker;
8
8
 
9
9
  export { createMokupWorker };
10
- export type { MokupWorker };
10
+ export type { FetchWorker };
package/dist/worker.d.mts CHANGED
@@ -1,10 +1,10 @@
1
- import { a as MokupWorkerInput } from './shared/server.C0FGI34s.mjs';
1
+ import { W as WorkerInput } from './shared/server.DNITwCtQ.mjs';
2
2
  import '@mokup/runtime';
3
3
 
4
- interface MokupWorker {
4
+ interface FetchWorker {
5
5
  fetch: (request: Request) => Promise<Response>;
6
6
  }
7
- declare function createMokupWorker(input: Exclude<MokupWorkerInput, string>): MokupWorker;
7
+ declare function createMokupWorker(input: Exclude<WorkerInput, string>): FetchWorker;
8
8
 
9
9
  export { createMokupWorker };
10
- export type { MokupWorker };
10
+ export type { FetchWorker };
package/dist/worker.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { a as MokupWorkerInput } from './shared/server.C0FGI34s.js';
1
+ import { W as WorkerInput } from './shared/server.DNITwCtQ.js';
2
2
  import '@mokup/runtime';
3
3
 
4
- interface MokupWorker {
4
+ interface FetchWorker {
5
5
  fetch: (request: Request) => Promise<Response>;
6
6
  }
7
- declare function createMokupWorker(input: Exclude<MokupWorkerInput, string>): MokupWorker;
7
+ declare function createMokupWorker(input: Exclude<WorkerInput, string>): FetchWorker;
8
8
 
9
9
  export { createMokupWorker };
10
- export type { MokupWorker };
10
+ export type { FetchWorker };
package/dist/worker.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { d as createFetchHandler } from './shared/server.HVB7OYyI.mjs';
1
+ import { c as createFetchHandler } from './shared/server.Dje1y79O.mjs';
2
2
  import '@mokup/runtime';
3
3
 
4
4
  function isManifest(value) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mokup/server",
3
3
  "type": "module",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
5
5
  "description": "Server adapters for @mokup/runtime.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://mokup.icebreaker.top",
@@ -16,6 +16,11 @@
16
16
  "import": "./dist/index.mjs",
17
17
  "require": "./dist/index.cjs"
18
18
  },
19
+ "./node": {
20
+ "types": "./dist/node.d.ts",
21
+ "import": "./dist/node.mjs",
22
+ "require": "./dist/node.cjs"
23
+ },
19
24
  "./worker": {
20
25
  "types": "./dist/worker.d.ts",
21
26
  "import": "./dist/worker.mjs",
@@ -32,12 +37,13 @@
32
37
  "dist"
33
38
  ],
34
39
  "dependencies": {
35
- "@mokup/shared": "0.1.0",
36
- "@mokup/runtime": "0.1.1",
37
- "@mokup/playground": "0.0.5"
40
+ "@hono/node-server": "^1.19.9",
41
+ "@hono/node-ws": "^1.1.1",
42
+ "@mokup/runtime": "1.0.0",
43
+ "@mokup/playground": "0.0.7",
44
+ "@mokup/shared": "1.0.0"
38
45
  },
39
46
  "devDependencies": {
40
- "@hono/node-server": "^1.19.9",
41
47
  "@types/node": "^25.0.9",
42
48
  "typescript": "^5.9.3",
43
49
  "unbuild": "^3.6.1"