@mokup/runtime 0.1.0 → 0.1.1

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
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const hono = require('hono');
4
- const patternRouter = require('hono/router/pattern-router');
3
+ const hono = require('@mokup/shared/hono');
5
4
 
6
5
  const paramNamePattern = /^[\w-]+$/;
7
6
  const paramPattern = /^\[([^\]/]+)\]$/;
@@ -265,7 +264,10 @@ async function loadModuleExport(modulePath, exportName, moduleBase, moduleMap) {
265
264
  const resolvedUrl = directMapValue ? void 0 : resolveModuleUrl(modulePath, moduleBase);
266
265
  const resolvedMapValue = resolvedUrl ? moduleMap?.[resolvedUrl] : void 0;
267
266
  const moduleValue = directMapValue ?? resolvedMapValue;
268
- const module = moduleValue ?? await import(resolvedUrl ?? modulePath);
267
+ const module = moduleValue ?? await import(
268
+ /* @vite-ignore */
269
+ resolvedUrl ?? modulePath
270
+ );
269
271
  return module[exportName] ?? module.default ?? module;
270
272
  }
271
273
  function resolveModuleCacheKey(modulePath, exportName, moduleBase, moduleMap) {
@@ -405,7 +407,19 @@ function compileRoutes(manifest) {
405
407
  }
406
408
  function shouldTreatAsText(contentType) {
407
409
  const normalized = contentType.toLowerCase();
408
- return normalized.startsWith("text/") || normalized.includes("json") || normalized.includes("xml") || normalized.includes("javascript");
410
+ if (!normalized) {
411
+ return true;
412
+ }
413
+ if (normalized.startsWith("text/") || normalized.includes("json") || normalized.includes("xml") || normalized.includes("javascript")) {
414
+ return true;
415
+ }
416
+ if (normalized.startsWith("image/") || normalized.startsWith("audio/") || normalized.startsWith("video/") || normalized.includes("octet-stream")) {
417
+ return false;
418
+ }
419
+ if (normalized.startsWith("application/") && (normalized.includes("pdf") || normalized.includes("zip") || normalized.includes("gzip"))) {
420
+ return false;
421
+ }
422
+ return true;
409
423
  }
410
424
  async function toRuntimeResult(response) {
411
425
  const headers = {};
@@ -434,6 +448,18 @@ async function toRuntimeResult(response) {
434
448
  body: buffer
435
449
  };
436
450
  }
451
+ function isValidStatus(status) {
452
+ return typeof status === "number" && Number.isFinite(status) && status >= 200 && status <= 599;
453
+ }
454
+ function resolveStatus(routeStatus, responseStatus) {
455
+ if (isValidStatus(routeStatus)) {
456
+ return routeStatus;
457
+ }
458
+ if (isValidStatus(responseStatus)) {
459
+ return responseStatus;
460
+ }
461
+ return 200;
462
+ }
437
463
  function applyRouteOverrides(response, route) {
438
464
  const headers = new Headers(response.headers);
439
465
  const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
@@ -442,7 +468,7 @@ function applyRouteOverrides(response, route) {
442
468
  headers.set(key, value);
443
469
  }
444
470
  }
445
- const status = route.status ?? response.status;
471
+ const status = resolveStatus(route.status, response.status);
446
472
  if (status === response.status && !hasHeaders) {
447
473
  return response;
448
474
  }
@@ -513,7 +539,7 @@ function createFinalizeMiddleware(route) {
513
539
  }
514
540
  async function buildApp(params) {
515
541
  const { manifest, moduleCache, middlewareCache, moduleBase, moduleMap } = params;
516
- const app = new hono.Hono({ router: new patternRouter.PatternRouter(), strict: false });
542
+ const app = new hono.Hono({ router: new hono.PatternRouter(), strict: false });
517
543
  const compiled = compileRoutes(manifest);
518
544
  for (const entry of compiled) {
519
545
  const middlewares = [];
@@ -544,6 +570,18 @@ async function buildApp(params) {
544
570
  }
545
571
  return app;
546
572
  }
573
+ async function createRuntimeApp(options) {
574
+ const manifest = typeof options.manifest === "function" ? await options.manifest() : options.manifest;
575
+ const moduleCache = /* @__PURE__ */ new Map();
576
+ const middlewareCache = /* @__PURE__ */ new Map();
577
+ return await buildApp({
578
+ manifest,
579
+ moduleCache,
580
+ middlewareCache,
581
+ ...typeof options.moduleBase !== "undefined" ? { moduleBase: options.moduleBase } : {},
582
+ ...typeof options.moduleMap !== "undefined" ? { moduleMap: options.moduleMap } : {}
583
+ });
584
+ }
547
585
  function appendQueryParams(url, query) {
548
586
  for (const [key, value] of Object.entries(query)) {
549
587
  if (Array.isArray(value)) {
@@ -579,7 +617,7 @@ function resolveRequestBody(req, contentType) {
579
617
  return String(body);
580
618
  }
581
619
  function toFetchRequest(req) {
582
- const url = new URL(req.path, "http://mokup.local");
620
+ const url = new URL(normalizePathname(req.path), "http://mokup.local");
583
621
  appendQueryParams(url, req.query);
584
622
  const headers = new Headers();
585
623
  for (const [key, value] of Object.entries(req.headers)) {
@@ -594,9 +632,35 @@ function toFetchRequest(req) {
594
632
  }
595
633
  return new Request(url.toString(), init);
596
634
  }
635
+ function isRelativeModulePath(modulePath) {
636
+ return !/^(?:data|http|https|file):/.test(modulePath);
637
+ }
638
+ function requiresModuleBase(modulePath, moduleMap) {
639
+ if (!isRelativeModulePath(modulePath)) {
640
+ return false;
641
+ }
642
+ if (moduleMap && Object.prototype.hasOwnProperty.call(moduleMap, modulePath)) {
643
+ return false;
644
+ }
645
+ return true;
646
+ }
647
+ function routeNeedsModuleBase(route, moduleMap) {
648
+ if (route.response.type === "module" && requiresModuleBase(route.response.module, moduleMap)) {
649
+ return true;
650
+ }
651
+ if (route.middleware) {
652
+ for (const middleware of route.middleware) {
653
+ if (requiresModuleBase(middleware.module, moduleMap)) {
654
+ return true;
655
+ }
656
+ }
657
+ }
658
+ return false;
659
+ }
597
660
  function createRuntime(options) {
598
661
  let manifestCache = null;
599
662
  let appPromise = null;
663
+ let compiledCache = null;
600
664
  const moduleCache = /* @__PURE__ */ new Map();
601
665
  const middlewareCache = /* @__PURE__ */ new Map();
602
666
  const getManifest = async () => {
@@ -620,24 +684,47 @@ function createRuntime(options) {
620
684
  }
621
685
  return appPromise;
622
686
  };
687
+ const getCompiled = async () => {
688
+ if (!compiledCache) {
689
+ compiledCache = compileRoutes(await getManifest());
690
+ }
691
+ return compiledCache;
692
+ };
623
693
  const handle = async (req) => {
624
- const app = await getApp();
625
694
  const method = normalizeMethod(req.method) ?? "GET";
626
695
  const matchMethod = method === "HEAD" ? "GET" : method;
627
- const match = app.router.match(matchMethod, req.path);
628
- if (!match || match[0].length === 0) {
696
+ const pathname = normalizePathname(req.path);
697
+ const compiled = await getCompiled();
698
+ let matchedRoute = null;
699
+ for (const entry of compiled) {
700
+ if (entry.method !== matchMethod) {
701
+ continue;
702
+ }
703
+ if (matchRouteTokens(entry.tokens, pathname)) {
704
+ matchedRoute = entry.route;
705
+ break;
706
+ }
707
+ }
708
+ if (!matchedRoute) {
629
709
  return null;
630
710
  }
711
+ if (typeof options.moduleBase === "undefined" && routeNeedsModuleBase(matchedRoute, options.moduleMap)) {
712
+ throw new Error("moduleBase is required for relative module paths.");
713
+ }
714
+ const app = await getApp();
631
715
  const response = await app.fetch(toFetchRequest(req));
632
- return await toRuntimeResult(response);
716
+ const resolvedResponse = applyRouteOverrides(response, matchedRoute);
717
+ return await toRuntimeResult(resolvedResponse);
633
718
  };
634
719
  return {
635
720
  handle
636
721
  };
637
722
  }
638
723
 
724
+ exports.handle = hono.handle;
639
725
  exports.compareRouteScore = compareRouteScore;
640
726
  exports.createRuntime = createRuntime;
727
+ exports.createRuntimeApp = createRuntimeApp;
641
728
  exports.matchRouteTokens = matchRouteTokens;
642
729
  exports.normalizePathname = normalizePathname;
643
730
  exports.parseRouteTemplate = parseRouteTemplate;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
- import { Context, MiddlewareHandler } from 'hono';
1
+ import { Context, MiddlewareHandler, Hono } from '@mokup/shared/hono';
2
+ export { handle } from '@mokup/shared/hono';
2
3
 
3
4
  type RouteToken = {
4
5
  type: 'static';
@@ -87,9 +88,10 @@ interface RuntimeOptions {
87
88
  }
88
89
  type ModuleMap = Record<string, Record<string, unknown>>;
89
90
 
91
+ declare function createRuntimeApp(options: RuntimeOptions): Promise<Hono>;
90
92
  declare function createRuntime(options: RuntimeOptions): {
91
93
  handle: (req: RuntimeRequest) => Promise<RuntimeResult | null>;
92
94
  };
93
95
 
94
- export { compareRouteScore, createRuntime, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
96
+ export { compareRouteScore, createRuntime, createRuntimeApp, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
95
97
  export type { HttpMethod, Manifest, ManifestModuleRef, ManifestResponse, ManifestRoute, MockContext, MockMiddleware, MockResponseHandler, ModuleMap, ParsedRouteTemplate, RouteToken, RuntimeOptions, RuntimeRequest, RuntimeResult };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
- import { Context, MiddlewareHandler } from 'hono';
1
+ import { Context, MiddlewareHandler, Hono } from '@mokup/shared/hono';
2
+ export { handle } from '@mokup/shared/hono';
2
3
 
3
4
  type RouteToken = {
4
5
  type: 'static';
@@ -87,9 +88,10 @@ interface RuntimeOptions {
87
88
  }
88
89
  type ModuleMap = Record<string, Record<string, unknown>>;
89
90
 
91
+ declare function createRuntimeApp(options: RuntimeOptions): Promise<Hono>;
90
92
  declare function createRuntime(options: RuntimeOptions): {
91
93
  handle: (req: RuntimeRequest) => Promise<RuntimeResult | null>;
92
94
  };
93
95
 
94
- export { compareRouteScore, createRuntime, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
96
+ export { compareRouteScore, createRuntime, createRuntimeApp, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
95
97
  export type { HttpMethod, Manifest, ManifestModuleRef, ManifestResponse, ManifestRoute, MockContext, MockMiddleware, MockResponseHandler, ModuleMap, ParsedRouteTemplate, RouteToken, RuntimeOptions, RuntimeRequest, RuntimeResult };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Context, MiddlewareHandler } from 'hono';
1
+ import { Context, MiddlewareHandler, Hono } from '@mokup/shared/hono';
2
+ export { handle } from '@mokup/shared/hono';
2
3
 
3
4
  type RouteToken = {
4
5
  type: 'static';
@@ -87,9 +88,10 @@ interface RuntimeOptions {
87
88
  }
88
89
  type ModuleMap = Record<string, Record<string, unknown>>;
89
90
 
91
+ declare function createRuntimeApp(options: RuntimeOptions): Promise<Hono>;
90
92
  declare function createRuntime(options: RuntimeOptions): {
91
93
  handle: (req: RuntimeRequest) => Promise<RuntimeResult | null>;
92
94
  };
93
95
 
94
- export { compareRouteScore, createRuntime, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
96
+ export { compareRouteScore, createRuntime, createRuntimeApp, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
95
97
  export type { HttpMethod, Manifest, ManifestModuleRef, ManifestResponse, ManifestRoute, MockContext, MockMiddleware, MockResponseHandler, ModuleMap, ParsedRouteTemplate, RouteToken, RuntimeOptions, RuntimeRequest, RuntimeResult };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { Hono } from 'hono';
2
- import { PatternRouter } from 'hono/router/pattern-router';
1
+ import { Hono, PatternRouter } from '@mokup/shared/hono';
2
+ export { handle } from '@mokup/shared/hono';
3
3
 
4
4
  const paramNamePattern = /^[\w-]+$/;
5
5
  const paramPattern = /^\[([^\]/]+)\]$/;
@@ -263,7 +263,10 @@ async function loadModuleExport(modulePath, exportName, moduleBase, moduleMap) {
263
263
  const resolvedUrl = directMapValue ? void 0 : resolveModuleUrl(modulePath, moduleBase);
264
264
  const resolvedMapValue = resolvedUrl ? moduleMap?.[resolvedUrl] : void 0;
265
265
  const moduleValue = directMapValue ?? resolvedMapValue;
266
- const module = moduleValue ?? await import(resolvedUrl ?? modulePath);
266
+ const module = moduleValue ?? await import(
267
+ /* @vite-ignore */
268
+ resolvedUrl ?? modulePath
269
+ );
267
270
  return module[exportName] ?? module.default ?? module;
268
271
  }
269
272
  function resolveModuleCacheKey(modulePath, exportName, moduleBase, moduleMap) {
@@ -403,7 +406,19 @@ function compileRoutes(manifest) {
403
406
  }
404
407
  function shouldTreatAsText(contentType) {
405
408
  const normalized = contentType.toLowerCase();
406
- return normalized.startsWith("text/") || normalized.includes("json") || normalized.includes("xml") || normalized.includes("javascript");
409
+ if (!normalized) {
410
+ return true;
411
+ }
412
+ if (normalized.startsWith("text/") || normalized.includes("json") || normalized.includes("xml") || normalized.includes("javascript")) {
413
+ return true;
414
+ }
415
+ if (normalized.startsWith("image/") || normalized.startsWith("audio/") || normalized.startsWith("video/") || normalized.includes("octet-stream")) {
416
+ return false;
417
+ }
418
+ if (normalized.startsWith("application/") && (normalized.includes("pdf") || normalized.includes("zip") || normalized.includes("gzip"))) {
419
+ return false;
420
+ }
421
+ return true;
407
422
  }
408
423
  async function toRuntimeResult(response) {
409
424
  const headers = {};
@@ -432,6 +447,18 @@ async function toRuntimeResult(response) {
432
447
  body: buffer
433
448
  };
434
449
  }
450
+ function isValidStatus(status) {
451
+ return typeof status === "number" && Number.isFinite(status) && status >= 200 && status <= 599;
452
+ }
453
+ function resolveStatus(routeStatus, responseStatus) {
454
+ if (isValidStatus(routeStatus)) {
455
+ return routeStatus;
456
+ }
457
+ if (isValidStatus(responseStatus)) {
458
+ return responseStatus;
459
+ }
460
+ return 200;
461
+ }
435
462
  function applyRouteOverrides(response, route) {
436
463
  const headers = new Headers(response.headers);
437
464
  const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
@@ -440,7 +467,7 @@ function applyRouteOverrides(response, route) {
440
467
  headers.set(key, value);
441
468
  }
442
469
  }
443
- const status = route.status ?? response.status;
470
+ const status = resolveStatus(route.status, response.status);
444
471
  if (status === response.status && !hasHeaders) {
445
472
  return response;
446
473
  }
@@ -542,6 +569,18 @@ async function buildApp(params) {
542
569
  }
543
570
  return app;
544
571
  }
572
+ async function createRuntimeApp(options) {
573
+ const manifest = typeof options.manifest === "function" ? await options.manifest() : options.manifest;
574
+ const moduleCache = /* @__PURE__ */ new Map();
575
+ const middlewareCache = /* @__PURE__ */ new Map();
576
+ return await buildApp({
577
+ manifest,
578
+ moduleCache,
579
+ middlewareCache,
580
+ ...typeof options.moduleBase !== "undefined" ? { moduleBase: options.moduleBase } : {},
581
+ ...typeof options.moduleMap !== "undefined" ? { moduleMap: options.moduleMap } : {}
582
+ });
583
+ }
545
584
  function appendQueryParams(url, query) {
546
585
  for (const [key, value] of Object.entries(query)) {
547
586
  if (Array.isArray(value)) {
@@ -577,7 +616,7 @@ function resolveRequestBody(req, contentType) {
577
616
  return String(body);
578
617
  }
579
618
  function toFetchRequest(req) {
580
- const url = new URL(req.path, "http://mokup.local");
619
+ const url = new URL(normalizePathname(req.path), "http://mokup.local");
581
620
  appendQueryParams(url, req.query);
582
621
  const headers = new Headers();
583
622
  for (const [key, value] of Object.entries(req.headers)) {
@@ -592,9 +631,35 @@ function toFetchRequest(req) {
592
631
  }
593
632
  return new Request(url.toString(), init);
594
633
  }
634
+ function isRelativeModulePath(modulePath) {
635
+ return !/^(?:data|http|https|file):/.test(modulePath);
636
+ }
637
+ function requiresModuleBase(modulePath, moduleMap) {
638
+ if (!isRelativeModulePath(modulePath)) {
639
+ return false;
640
+ }
641
+ if (moduleMap && Object.prototype.hasOwnProperty.call(moduleMap, modulePath)) {
642
+ return false;
643
+ }
644
+ return true;
645
+ }
646
+ function routeNeedsModuleBase(route, moduleMap) {
647
+ if (route.response.type === "module" && requiresModuleBase(route.response.module, moduleMap)) {
648
+ return true;
649
+ }
650
+ if (route.middleware) {
651
+ for (const middleware of route.middleware) {
652
+ if (requiresModuleBase(middleware.module, moduleMap)) {
653
+ return true;
654
+ }
655
+ }
656
+ }
657
+ return false;
658
+ }
595
659
  function createRuntime(options) {
596
660
  let manifestCache = null;
597
661
  let appPromise = null;
662
+ let compiledCache = null;
598
663
  const moduleCache = /* @__PURE__ */ new Map();
599
664
  const middlewareCache = /* @__PURE__ */ new Map();
600
665
  const getManifest = async () => {
@@ -618,20 +683,41 @@ function createRuntime(options) {
618
683
  }
619
684
  return appPromise;
620
685
  };
686
+ const getCompiled = async () => {
687
+ if (!compiledCache) {
688
+ compiledCache = compileRoutes(await getManifest());
689
+ }
690
+ return compiledCache;
691
+ };
621
692
  const handle = async (req) => {
622
- const app = await getApp();
623
693
  const method = normalizeMethod(req.method) ?? "GET";
624
694
  const matchMethod = method === "HEAD" ? "GET" : method;
625
- const match = app.router.match(matchMethod, req.path);
626
- if (!match || match[0].length === 0) {
695
+ const pathname = normalizePathname(req.path);
696
+ const compiled = await getCompiled();
697
+ let matchedRoute = null;
698
+ for (const entry of compiled) {
699
+ if (entry.method !== matchMethod) {
700
+ continue;
701
+ }
702
+ if (matchRouteTokens(entry.tokens, pathname)) {
703
+ matchedRoute = entry.route;
704
+ break;
705
+ }
706
+ }
707
+ if (!matchedRoute) {
627
708
  return null;
628
709
  }
710
+ if (typeof options.moduleBase === "undefined" && routeNeedsModuleBase(matchedRoute, options.moduleMap)) {
711
+ throw new Error("moduleBase is required for relative module paths.");
712
+ }
713
+ const app = await getApp();
629
714
  const response = await app.fetch(toFetchRequest(req));
630
- return await toRuntimeResult(response);
715
+ const resolvedResponse = applyRouteOverrides(response, matchedRoute);
716
+ return await toRuntimeResult(resolvedResponse);
631
717
  };
632
718
  return {
633
719
  handle
634
720
  };
635
721
  }
636
722
 
637
- export { compareRouteScore, createRuntime, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
723
+ export { compareRouteScore, createRuntime, createRuntimeApp, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mokup/runtime",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "description": "Cross-runtime mock matching and response handling for mokup.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://mokup.icebreaker.top",
@@ -27,7 +27,7 @@
27
27
  "dist"
28
28
  ],
29
29
  "dependencies": {
30
- "hono": "^4.11.4"
30
+ "@mokup/shared": "0.1.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "typescript": "^5.9.3",