@mokup/runtime 0.0.1 → 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 +317 -176
- package/dist/index.d.cts +9 -14
- package/dist/index.d.mts +9 -14
- package/dist/index.d.ts +9 -14
- package/dist/index.mjs +317 -177
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const hono = require('@mokup/shared/hono');
|
|
4
|
+
|
|
3
5
|
const paramNamePattern = /^[\w-]+$/;
|
|
4
6
|
const paramPattern = /^\[([^\]/]+)\]$/;
|
|
5
7
|
const catchallPattern = /^\[\.\.\.([^\]/]+)\]$/;
|
|
@@ -227,14 +229,14 @@ function normalizeRules(value) {
|
|
|
227
229
|
}
|
|
228
230
|
];
|
|
229
231
|
}
|
|
230
|
-
async function executeRule(rule,
|
|
232
|
+
async function executeRule(rule, context) {
|
|
231
233
|
if (!rule) {
|
|
232
234
|
return void 0;
|
|
233
235
|
}
|
|
234
236
|
const value = rule.response;
|
|
235
237
|
if (typeof value === "function") {
|
|
236
238
|
const handler = value;
|
|
237
|
-
return handler(
|
|
239
|
+
return handler(context);
|
|
238
240
|
}
|
|
239
241
|
return value;
|
|
240
242
|
}
|
|
@@ -262,7 +264,10 @@ async function loadModuleExport(modulePath, exportName, moduleBase, moduleMap) {
|
|
|
262
264
|
const resolvedUrl = directMapValue ? void 0 : resolveModuleUrl(modulePath, moduleBase);
|
|
263
265
|
const resolvedMapValue = resolvedUrl ? moduleMap?.[resolvedUrl] : void 0;
|
|
264
266
|
const moduleValue = directMapValue ?? resolvedMapValue;
|
|
265
|
-
const module = moduleValue ?? await import(
|
|
267
|
+
const module = moduleValue ?? await import(
|
|
268
|
+
/* @vite-ignore */
|
|
269
|
+
resolvedUrl ?? modulePath
|
|
270
|
+
);
|
|
266
271
|
return module[exportName] ?? module.default ?? module;
|
|
267
272
|
}
|
|
268
273
|
function resolveModuleCacheKey(modulePath, exportName, moduleBase, moduleMap) {
|
|
@@ -340,108 +345,177 @@ function normalizeMethod(method) {
|
|
|
340
345
|
}
|
|
341
346
|
return void 0;
|
|
342
347
|
}
|
|
343
|
-
function mergeHeaders(base, override) {
|
|
344
|
-
if (!override) {
|
|
345
|
-
return base;
|
|
346
|
-
}
|
|
347
|
-
return {
|
|
348
|
-
...base,
|
|
349
|
-
...override
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
348
|
function delay(ms) {
|
|
353
349
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
354
350
|
}
|
|
355
351
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
return
|
|
352
|
+
function decodeBase64(value) {
|
|
353
|
+
if (typeof atob === "function") {
|
|
354
|
+
const binary = atob(value);
|
|
355
|
+
const bytes = new Uint8Array(binary.length);
|
|
356
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
357
|
+
bytes[i] = binary.charCodeAt(i);
|
|
358
|
+
}
|
|
359
|
+
return bytes;
|
|
364
360
|
}
|
|
365
|
-
|
|
366
|
-
|
|
361
|
+
throw new Error("Base64 decoding is not supported in this runtime.");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function toHonoPath(tokens) {
|
|
365
|
+
if (!tokens || tokens.length === 0) {
|
|
366
|
+
return "/";
|
|
367
367
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
record[key] = value;
|
|
368
|
+
const segments = tokens.map((token) => {
|
|
369
|
+
if (token.type === "static") {
|
|
370
|
+
return token.value;
|
|
372
371
|
}
|
|
373
|
-
|
|
374
|
-
|
|
372
|
+
if (token.type === "param") {
|
|
373
|
+
return `:${token.name}`;
|
|
374
|
+
}
|
|
375
|
+
if (token.type === "catchall") {
|
|
376
|
+
return `:${token.name}{.+}`;
|
|
377
|
+
}
|
|
378
|
+
return `:${token.name}{.+}?`;
|
|
379
|
+
});
|
|
380
|
+
return `/${segments.join("/")}`;
|
|
375
381
|
}
|
|
376
|
-
function
|
|
377
|
-
|
|
378
|
-
|
|
382
|
+
function compileRoutes(manifest) {
|
|
383
|
+
const compiled = [];
|
|
384
|
+
for (const route of manifest.routes) {
|
|
385
|
+
const method = normalizeMethod(route.method) ?? "GET";
|
|
386
|
+
const parsed = route.tokens ? {
|
|
387
|
+
tokens: route.tokens,
|
|
388
|
+
score: route.score ?? scoreRouteTokens(route.tokens),
|
|
389
|
+
errors: []
|
|
390
|
+
} : parseRouteTemplate(route.url);
|
|
391
|
+
if (parsed.errors.length > 0) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
compiled.push({
|
|
395
|
+
route,
|
|
396
|
+
method,
|
|
397
|
+
tokens: route.tokens ?? parsed.tokens,
|
|
398
|
+
score: route.score ?? parsed.score
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
return compiled.sort((a, b) => {
|
|
402
|
+
if (a.method !== b.method) {
|
|
403
|
+
return a.method.localeCompare(b.method);
|
|
404
|
+
}
|
|
405
|
+
return compareRouteScore(a.score, b.score);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
function shouldTreatAsText(contentType) {
|
|
409
|
+
const normalized = contentType.toLowerCase();
|
|
410
|
+
if (!normalized) {
|
|
411
|
+
return true;
|
|
379
412
|
}
|
|
380
|
-
if (
|
|
381
|
-
return
|
|
382
|
-
body,
|
|
383
|
-
contentType: contentType ?? "text/plain; charset=utf-8"
|
|
384
|
-
};
|
|
413
|
+
if (normalized.startsWith("text/") || normalized.includes("json") || normalized.includes("xml") || normalized.includes("javascript")) {
|
|
414
|
+
return true;
|
|
385
415
|
}
|
|
386
|
-
if (
|
|
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;
|
|
423
|
+
}
|
|
424
|
+
async function toRuntimeResult(response) {
|
|
425
|
+
const headers = {};
|
|
426
|
+
response.headers.forEach((value, key) => {
|
|
427
|
+
headers[key.toLowerCase()] = value;
|
|
428
|
+
});
|
|
429
|
+
if (!response.body || [204, 205, 304].includes(response.status)) {
|
|
387
430
|
return {
|
|
388
|
-
|
|
389
|
-
|
|
431
|
+
status: response.status,
|
|
432
|
+
headers,
|
|
433
|
+
body: null
|
|
390
434
|
};
|
|
391
435
|
}
|
|
392
|
-
|
|
436
|
+
const contentType = headers["content-type"] ?? "";
|
|
437
|
+
if (shouldTreatAsText(contentType)) {
|
|
393
438
|
return {
|
|
394
|
-
|
|
395
|
-
|
|
439
|
+
status: response.status,
|
|
440
|
+
headers,
|
|
441
|
+
body: await response.text()
|
|
396
442
|
};
|
|
397
443
|
}
|
|
444
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
398
445
|
return {
|
|
399
|
-
|
|
400
|
-
|
|
446
|
+
status: response.status,
|
|
447
|
+
headers,
|
|
448
|
+
body: buffer
|
|
401
449
|
};
|
|
402
450
|
}
|
|
403
|
-
function
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
+
}
|
|
463
|
+
function applyRouteOverrides(response, route) {
|
|
464
|
+
const headers = new Headers(response.headers);
|
|
465
|
+
const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
|
|
466
|
+
if (route.headers) {
|
|
467
|
+
for (const [key, value] of Object.entries(route.headers)) {
|
|
468
|
+
headers.set(key, value);
|
|
409
469
|
}
|
|
410
|
-
return bytes;
|
|
411
470
|
}
|
|
412
|
-
|
|
471
|
+
const status = resolveStatus(route.status, response.status);
|
|
472
|
+
if (status === response.status && !hasHeaders) {
|
|
473
|
+
return response;
|
|
474
|
+
}
|
|
475
|
+
return new Response(response.body, { status, headers });
|
|
413
476
|
}
|
|
414
|
-
function
|
|
415
|
-
if (
|
|
416
|
-
return
|
|
477
|
+
function normalizeHandlerValue(c, value) {
|
|
478
|
+
if (value instanceof Response) {
|
|
479
|
+
return value;
|
|
480
|
+
}
|
|
481
|
+
if (typeof value === "undefined") {
|
|
482
|
+
const response = c.body(null);
|
|
483
|
+
if (response.status === 200) {
|
|
484
|
+
return new Response(response.body, {
|
|
485
|
+
status: 204,
|
|
486
|
+
headers: response.headers
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
return response;
|
|
490
|
+
}
|
|
491
|
+
if (typeof value === "string") {
|
|
492
|
+
return c.text(value);
|
|
417
493
|
}
|
|
418
|
-
|
|
494
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
495
|
+
if (!c.res.headers.get("content-type")) {
|
|
496
|
+
c.header("content-type", "application/octet-stream");
|
|
497
|
+
}
|
|
498
|
+
const data = value instanceof ArrayBuffer ? value : new Uint8Array(value);
|
|
499
|
+
return c.body(data);
|
|
500
|
+
}
|
|
501
|
+
return c.json(value);
|
|
419
502
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const ctx = {
|
|
424
|
-
delay,
|
|
425
|
-
json: (data) => data
|
|
426
|
-
};
|
|
427
|
-
const runHandler = async () => {
|
|
503
|
+
function createRouteHandler(params) {
|
|
504
|
+
const { route, moduleCache, moduleBase, moduleMap } = params;
|
|
505
|
+
return async (c) => {
|
|
428
506
|
if (route.response.type === "json") {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
507
|
+
if (typeof route.response.body === "undefined") {
|
|
508
|
+
return normalizeHandlerValue(c, void 0);
|
|
509
|
+
}
|
|
510
|
+
return c.json(route.response.body);
|
|
433
511
|
}
|
|
434
512
|
if (route.response.type === "text") {
|
|
435
|
-
return
|
|
436
|
-
value: route.response.body,
|
|
437
|
-
contentType: "text/plain; charset=utf-8"
|
|
438
|
-
};
|
|
513
|
+
return c.text(route.response.body);
|
|
439
514
|
}
|
|
440
515
|
if (route.response.type === "binary") {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
};
|
|
516
|
+
const data = new Uint8Array(decodeBase64(route.response.body));
|
|
517
|
+
c.header("content-type", "application/octet-stream");
|
|
518
|
+
return c.body(data);
|
|
445
519
|
}
|
|
446
520
|
const rule = await loadModuleRule(
|
|
447
521
|
route.response,
|
|
@@ -449,75 +523,144 @@ async function executeRoute(route, req, moduleCache, middlewareCache, moduleBase
|
|
|
449
523
|
moduleBase,
|
|
450
524
|
moduleMap
|
|
451
525
|
);
|
|
452
|
-
const value = await executeRule(rule,
|
|
453
|
-
return
|
|
526
|
+
const value = await executeRule(rule, c);
|
|
527
|
+
return normalizeHandlerValue(c, value);
|
|
454
528
|
};
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
return runHandler();
|
|
465
|
-
}
|
|
466
|
-
let nextResult;
|
|
467
|
-
const next = async () => {
|
|
468
|
-
nextResult = await dispatch(index + 1);
|
|
469
|
-
return nextResult.value;
|
|
470
|
-
};
|
|
471
|
-
const value = await handler(req, responder, ctx, next);
|
|
472
|
-
if (typeof value !== "undefined") {
|
|
473
|
-
return { value };
|
|
474
|
-
}
|
|
475
|
-
if (nextResult) {
|
|
476
|
-
return nextResult;
|
|
477
|
-
}
|
|
478
|
-
return { value: void 0 };
|
|
479
|
-
};
|
|
480
|
-
return dispatch(0);
|
|
529
|
+
}
|
|
530
|
+
function createFinalizeMiddleware(route) {
|
|
531
|
+
return async (c, next) => {
|
|
532
|
+
const response = await next();
|
|
533
|
+
const resolved = response ?? c.res;
|
|
534
|
+
if (route.delay && route.delay > 0) {
|
|
535
|
+
await delay(route.delay);
|
|
536
|
+
}
|
|
537
|
+
return applyRouteOverrides(resolved, route);
|
|
481
538
|
};
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
539
|
+
}
|
|
540
|
+
async function buildApp(params) {
|
|
541
|
+
const { manifest, moduleCache, middlewareCache, moduleBase, moduleMap } = params;
|
|
542
|
+
const app = new hono.Hono({ router: new hono.PatternRouter(), strict: false });
|
|
543
|
+
const compiled = compileRoutes(manifest);
|
|
544
|
+
for (const entry of compiled) {
|
|
545
|
+
const middlewares = [];
|
|
546
|
+
for (const middleware of entry.route.middleware ?? []) {
|
|
547
|
+
const handler2 = await loadModuleMiddleware(
|
|
548
|
+
middleware,
|
|
549
|
+
middlewareCache,
|
|
550
|
+
moduleBase,
|
|
551
|
+
moduleMap
|
|
552
|
+
);
|
|
553
|
+
if (handler2) {
|
|
554
|
+
middlewares.push(handler2);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const handler = createRouteHandler({
|
|
558
|
+
route: entry.route,
|
|
559
|
+
moduleCache,
|
|
560
|
+
...typeof moduleBase !== "undefined" ? { moduleBase } : {},
|
|
561
|
+
...typeof moduleMap !== "undefined" ? { moduleMap } : {}
|
|
562
|
+
});
|
|
563
|
+
app.on(
|
|
564
|
+
entry.method,
|
|
565
|
+
toHonoPath(entry.tokens),
|
|
566
|
+
createFinalizeMiddleware(entry.route),
|
|
567
|
+
...middlewares,
|
|
568
|
+
handler
|
|
489
569
|
);
|
|
490
|
-
|
|
491
|
-
|
|
570
|
+
}
|
|
571
|
+
return app;
|
|
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
|
+
}
|
|
585
|
+
function appendQueryParams(url, query) {
|
|
586
|
+
for (const [key, value] of Object.entries(query)) {
|
|
587
|
+
if (Array.isArray(value)) {
|
|
588
|
+
for (const entry of value) {
|
|
589
|
+
url.searchParams.append(key, entry);
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
url.searchParams.append(key, value);
|
|
492
593
|
}
|
|
493
594
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
await delay(route.delay);
|
|
595
|
+
}
|
|
596
|
+
function resolveRequestBody(req, contentType) {
|
|
597
|
+
if (typeof req.rawBody !== "undefined") {
|
|
598
|
+
return req.rawBody;
|
|
499
599
|
}
|
|
500
|
-
const
|
|
501
|
-
if (
|
|
502
|
-
|
|
600
|
+
const body = req.body;
|
|
601
|
+
if (typeof body === "undefined") {
|
|
602
|
+
return void 0;
|
|
503
603
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
route.status ?? responder.statusCode,
|
|
507
|
-
normalized.body
|
|
508
|
-
);
|
|
509
|
-
if (normalized.contentType && !headers["content-type"]) {
|
|
510
|
-
headers["content-type"] = normalized.contentType;
|
|
604
|
+
if (typeof body === "string") {
|
|
605
|
+
return body;
|
|
511
606
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
607
|
+
if (body instanceof Uint8Array || body instanceof ArrayBuffer) {
|
|
608
|
+
return body;
|
|
609
|
+
}
|
|
610
|
+
if (typeof body === "object") {
|
|
611
|
+
const normalized = contentType.toLowerCase();
|
|
612
|
+
if (normalized.includes("json")) {
|
|
613
|
+
return JSON.stringify(body);
|
|
614
|
+
}
|
|
615
|
+
return JSON.stringify(body);
|
|
616
|
+
}
|
|
617
|
+
return String(body);
|
|
618
|
+
}
|
|
619
|
+
function toFetchRequest(req) {
|
|
620
|
+
const url = new URL(normalizePathname(req.path), "http://mokup.local");
|
|
621
|
+
appendQueryParams(url, req.query);
|
|
622
|
+
const headers = new Headers();
|
|
623
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
624
|
+
headers.set(key, value);
|
|
625
|
+
}
|
|
626
|
+
const method = normalizeMethod(req.method) ?? "GET";
|
|
627
|
+
const contentType = headers.get("content-type") ?? "";
|
|
628
|
+
const body = resolveRequestBody(req, contentType);
|
|
629
|
+
const init = { method, headers };
|
|
630
|
+
if (typeof body !== "undefined" && method !== "GET" && method !== "HEAD") {
|
|
631
|
+
init.body = body;
|
|
632
|
+
}
|
|
633
|
+
return new Request(url.toString(), init);
|
|
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;
|
|
517
659
|
}
|
|
518
660
|
function createRuntime(options) {
|
|
519
661
|
let manifestCache = null;
|
|
520
|
-
let
|
|
662
|
+
let appPromise = null;
|
|
663
|
+
let compiledCache = null;
|
|
521
664
|
const moduleCache = /* @__PURE__ */ new Map();
|
|
522
665
|
const middlewareCache = /* @__PURE__ */ new Map();
|
|
523
666
|
const getManifest = async () => {
|
|
@@ -526,64 +669,62 @@ function createRuntime(options) {
|
|
|
526
669
|
}
|
|
527
670
|
return manifestCache;
|
|
528
671
|
};
|
|
529
|
-
const
|
|
530
|
-
if (
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
const tokens = route.tokens ?? parsed.tokens;
|
|
542
|
-
const score = route.score ?? parsed.score;
|
|
543
|
-
const list = map.get(method) ?? [];
|
|
544
|
-
list.push({ route, tokens, score });
|
|
545
|
-
map.set(method, list);
|
|
672
|
+
const getApp = async () => {
|
|
673
|
+
if (!appPromise) {
|
|
674
|
+
appPromise = (async () => {
|
|
675
|
+
const manifest = await getManifest();
|
|
676
|
+
return buildApp({
|
|
677
|
+
manifest,
|
|
678
|
+
moduleCache,
|
|
679
|
+
middlewareCache,
|
|
680
|
+
...typeof options.moduleBase !== "undefined" ? { moduleBase: options.moduleBase } : {},
|
|
681
|
+
...typeof options.moduleMap !== "undefined" ? { moduleMap: options.moduleMap } : {}
|
|
682
|
+
});
|
|
683
|
+
})();
|
|
546
684
|
}
|
|
547
|
-
|
|
548
|
-
|
|
685
|
+
return appPromise;
|
|
686
|
+
};
|
|
687
|
+
const getCompiled = async () => {
|
|
688
|
+
if (!compiledCache) {
|
|
689
|
+
compiledCache = compileRoutes(await getManifest());
|
|
549
690
|
}
|
|
550
|
-
|
|
551
|
-
return compiledRoutes;
|
|
691
|
+
return compiledCache;
|
|
552
692
|
};
|
|
553
693
|
const handle = async (req) => {
|
|
554
694
|
const method = normalizeMethod(req.method) ?? "GET";
|
|
555
|
-
const
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const matched = matchRouteTokens(entry.tokens, req.path);
|
|
562
|
-
if (!matched) {
|
|
695
|
+
const matchMethod = method === "HEAD" ? "GET" : method;
|
|
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) {
|
|
563
701
|
continue;
|
|
564
702
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
return executeRoute(
|
|
570
|
-
entry.route,
|
|
571
|
-
requestWithParams,
|
|
572
|
-
moduleCache,
|
|
573
|
-
middlewareCache,
|
|
574
|
-
options.moduleBase,
|
|
575
|
-
options.moduleMap
|
|
576
|
-
);
|
|
703
|
+
if (matchRouteTokens(entry.tokens, pathname)) {
|
|
704
|
+
matchedRoute = entry.route;
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
577
707
|
}
|
|
578
|
-
|
|
708
|
+
if (!matchedRoute) {
|
|
709
|
+
return null;
|
|
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();
|
|
715
|
+
const response = await app.fetch(toFetchRequest(req));
|
|
716
|
+
const resolvedResponse = applyRouteOverrides(response, matchedRoute);
|
|
717
|
+
return await toRuntimeResult(resolvedResponse);
|
|
579
718
|
};
|
|
580
719
|
return {
|
|
581
720
|
handle
|
|
582
721
|
};
|
|
583
722
|
}
|
|
584
723
|
|
|
724
|
+
exports.handle = hono.handle;
|
|
585
725
|
exports.compareRouteScore = compareRouteScore;
|
|
586
726
|
exports.createRuntime = createRuntime;
|
|
727
|
+
exports.createRuntimeApp = createRuntimeApp;
|
|
587
728
|
exports.matchRouteTokens = matchRouteTokens;
|
|
588
729
|
exports.normalizePathname = normalizePathname;
|
|
589
730
|
exports.parseRouteTemplate = parseRouteTemplate;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Context, MiddlewareHandler, Hono } from '@mokup/shared/hono';
|
|
2
|
+
export { handle } from '@mokup/shared/hono';
|
|
3
|
+
|
|
1
4
|
type RouteToken = {
|
|
2
5
|
type: 'static';
|
|
3
6
|
value: string;
|
|
@@ -75,18 +78,9 @@ interface RuntimeResult {
|
|
|
75
78
|
headers: Record<string, string>;
|
|
76
79
|
body: string | Uint8Array | null;
|
|
77
80
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
interface MockResponder {
|
|
83
|
-
statusCode: number;
|
|
84
|
-
setHeader: (key: string, value: string) => void;
|
|
85
|
-
getHeader: (key: string) => string | undefined;
|
|
86
|
-
removeHeader: (key: string) => void;
|
|
87
|
-
}
|
|
88
|
-
type MockMiddleware = (req: RuntimeRequest, res: MockResponder, ctx: MockContext, next: () => Promise<unknown>) => unknown | Promise<unknown>;
|
|
89
|
-
type MockResponseHandler = (req: RuntimeRequest, res: MockResponder, ctx: MockContext) => unknown | Promise<unknown>;
|
|
81
|
+
type MockContext = Context;
|
|
82
|
+
type MockMiddleware = MiddlewareHandler;
|
|
83
|
+
type MockResponseHandler = (context: Context) => Response | Promise<Response> | unknown;
|
|
90
84
|
interface RuntimeOptions {
|
|
91
85
|
manifest: Manifest | (() => Promise<Manifest>);
|
|
92
86
|
moduleBase?: string | URL;
|
|
@@ -94,9 +88,10 @@ interface RuntimeOptions {
|
|
|
94
88
|
}
|
|
95
89
|
type ModuleMap = Record<string, Record<string, unknown>>;
|
|
96
90
|
|
|
91
|
+
declare function createRuntimeApp(options: RuntimeOptions): Promise<Hono>;
|
|
97
92
|
declare function createRuntime(options: RuntimeOptions): {
|
|
98
93
|
handle: (req: RuntimeRequest) => Promise<RuntimeResult | null>;
|
|
99
94
|
};
|
|
100
95
|
|
|
101
|
-
export { compareRouteScore, createRuntime, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
|
|
102
|
-
export type { HttpMethod, Manifest, ManifestModuleRef, ManifestResponse, ManifestRoute, MockContext, MockMiddleware,
|
|
96
|
+
export { compareRouteScore, createRuntime, createRuntimeApp, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
|
|
97
|
+
export type { HttpMethod, Manifest, ManifestModuleRef, ManifestResponse, ManifestRoute, MockContext, MockMiddleware, MockResponseHandler, ModuleMap, ParsedRouteTemplate, RouteToken, RuntimeOptions, RuntimeRequest, RuntimeResult };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Context, MiddlewareHandler, Hono } from '@mokup/shared/hono';
|
|
2
|
+
export { handle } from '@mokup/shared/hono';
|
|
3
|
+
|
|
1
4
|
type RouteToken = {
|
|
2
5
|
type: 'static';
|
|
3
6
|
value: string;
|
|
@@ -75,18 +78,9 @@ interface RuntimeResult {
|
|
|
75
78
|
headers: Record<string, string>;
|
|
76
79
|
body: string | Uint8Array | null;
|
|
77
80
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
interface MockResponder {
|
|
83
|
-
statusCode: number;
|
|
84
|
-
setHeader: (key: string, value: string) => void;
|
|
85
|
-
getHeader: (key: string) => string | undefined;
|
|
86
|
-
removeHeader: (key: string) => void;
|
|
87
|
-
}
|
|
88
|
-
type MockMiddleware = (req: RuntimeRequest, res: MockResponder, ctx: MockContext, next: () => Promise<unknown>) => unknown | Promise<unknown>;
|
|
89
|
-
type MockResponseHandler = (req: RuntimeRequest, res: MockResponder, ctx: MockContext) => unknown | Promise<unknown>;
|
|
81
|
+
type MockContext = Context;
|
|
82
|
+
type MockMiddleware = MiddlewareHandler;
|
|
83
|
+
type MockResponseHandler = (context: Context) => Response | Promise<Response> | unknown;
|
|
90
84
|
interface RuntimeOptions {
|
|
91
85
|
manifest: Manifest | (() => Promise<Manifest>);
|
|
92
86
|
moduleBase?: string | URL;
|
|
@@ -94,9 +88,10 @@ interface RuntimeOptions {
|
|
|
94
88
|
}
|
|
95
89
|
type ModuleMap = Record<string, Record<string, unknown>>;
|
|
96
90
|
|
|
91
|
+
declare function createRuntimeApp(options: RuntimeOptions): Promise<Hono>;
|
|
97
92
|
declare function createRuntime(options: RuntimeOptions): {
|
|
98
93
|
handle: (req: RuntimeRequest) => Promise<RuntimeResult | null>;
|
|
99
94
|
};
|
|
100
95
|
|
|
101
|
-
export { compareRouteScore, createRuntime, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
|
|
102
|
-
export type { HttpMethod, Manifest, ManifestModuleRef, ManifestResponse, ManifestRoute, MockContext, MockMiddleware,
|
|
96
|
+
export { compareRouteScore, createRuntime, createRuntimeApp, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
|
|
97
|
+
export type { HttpMethod, Manifest, ManifestModuleRef, ManifestResponse, ManifestRoute, MockContext, MockMiddleware, MockResponseHandler, ModuleMap, ParsedRouteTemplate, RouteToken, RuntimeOptions, RuntimeRequest, RuntimeResult };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Context, MiddlewareHandler, Hono } from '@mokup/shared/hono';
|
|
2
|
+
export { handle } from '@mokup/shared/hono';
|
|
3
|
+
|
|
1
4
|
type RouteToken = {
|
|
2
5
|
type: 'static';
|
|
3
6
|
value: string;
|
|
@@ -75,18 +78,9 @@ interface RuntimeResult {
|
|
|
75
78
|
headers: Record<string, string>;
|
|
76
79
|
body: string | Uint8Array | null;
|
|
77
80
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
interface MockResponder {
|
|
83
|
-
statusCode: number;
|
|
84
|
-
setHeader: (key: string, value: string) => void;
|
|
85
|
-
getHeader: (key: string) => string | undefined;
|
|
86
|
-
removeHeader: (key: string) => void;
|
|
87
|
-
}
|
|
88
|
-
type MockMiddleware = (req: RuntimeRequest, res: MockResponder, ctx: MockContext, next: () => Promise<unknown>) => unknown | Promise<unknown>;
|
|
89
|
-
type MockResponseHandler = (req: RuntimeRequest, res: MockResponder, ctx: MockContext) => unknown | Promise<unknown>;
|
|
81
|
+
type MockContext = Context;
|
|
82
|
+
type MockMiddleware = MiddlewareHandler;
|
|
83
|
+
type MockResponseHandler = (context: Context) => Response | Promise<Response> | unknown;
|
|
90
84
|
interface RuntimeOptions {
|
|
91
85
|
manifest: Manifest | (() => Promise<Manifest>);
|
|
92
86
|
moduleBase?: string | URL;
|
|
@@ -94,9 +88,10 @@ interface RuntimeOptions {
|
|
|
94
88
|
}
|
|
95
89
|
type ModuleMap = Record<string, Record<string, unknown>>;
|
|
96
90
|
|
|
91
|
+
declare function createRuntimeApp(options: RuntimeOptions): Promise<Hono>;
|
|
97
92
|
declare function createRuntime(options: RuntimeOptions): {
|
|
98
93
|
handle: (req: RuntimeRequest) => Promise<RuntimeResult | null>;
|
|
99
94
|
};
|
|
100
95
|
|
|
101
|
-
export { compareRouteScore, createRuntime, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
|
|
102
|
-
export type { HttpMethod, Manifest, ManifestModuleRef, ManifestResponse, ManifestRoute, MockContext, MockMiddleware,
|
|
96
|
+
export { compareRouteScore, createRuntime, createRuntimeApp, matchRouteTokens, normalizePathname, parseRouteTemplate, scoreRouteTokens };
|
|
97
|
+
export type { HttpMethod, Manifest, ManifestModuleRef, ManifestResponse, ManifestRoute, MockContext, MockMiddleware, MockResponseHandler, ModuleMap, ParsedRouteTemplate, RouteToken, RuntimeOptions, RuntimeRequest, RuntimeResult };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Hono, PatternRouter } from '@mokup/shared/hono';
|
|
2
|
+
export { handle } from '@mokup/shared/hono';
|
|
3
|
+
|
|
1
4
|
const paramNamePattern = /^[\w-]+$/;
|
|
2
5
|
const paramPattern = /^\[([^\]/]+)\]$/;
|
|
3
6
|
const catchallPattern = /^\[\.\.\.([^\]/]+)\]$/;
|
|
@@ -225,14 +228,14 @@ function normalizeRules(value) {
|
|
|
225
228
|
}
|
|
226
229
|
];
|
|
227
230
|
}
|
|
228
|
-
async function executeRule(rule,
|
|
231
|
+
async function executeRule(rule, context) {
|
|
229
232
|
if (!rule) {
|
|
230
233
|
return void 0;
|
|
231
234
|
}
|
|
232
235
|
const value = rule.response;
|
|
233
236
|
if (typeof value === "function") {
|
|
234
237
|
const handler = value;
|
|
235
|
-
return handler(
|
|
238
|
+
return handler(context);
|
|
236
239
|
}
|
|
237
240
|
return value;
|
|
238
241
|
}
|
|
@@ -260,7 +263,10 @@ async function loadModuleExport(modulePath, exportName, moduleBase, moduleMap) {
|
|
|
260
263
|
const resolvedUrl = directMapValue ? void 0 : resolveModuleUrl(modulePath, moduleBase);
|
|
261
264
|
const resolvedMapValue = resolvedUrl ? moduleMap?.[resolvedUrl] : void 0;
|
|
262
265
|
const moduleValue = directMapValue ?? resolvedMapValue;
|
|
263
|
-
const module = moduleValue ?? await import(
|
|
266
|
+
const module = moduleValue ?? await import(
|
|
267
|
+
/* @vite-ignore */
|
|
268
|
+
resolvedUrl ?? modulePath
|
|
269
|
+
);
|
|
264
270
|
return module[exportName] ?? module.default ?? module;
|
|
265
271
|
}
|
|
266
272
|
function resolveModuleCacheKey(modulePath, exportName, moduleBase, moduleMap) {
|
|
@@ -338,108 +344,177 @@ function normalizeMethod(method) {
|
|
|
338
344
|
}
|
|
339
345
|
return void 0;
|
|
340
346
|
}
|
|
341
|
-
function mergeHeaders(base, override) {
|
|
342
|
-
if (!override) {
|
|
343
|
-
return base;
|
|
344
|
-
}
|
|
345
|
-
return {
|
|
346
|
-
...base,
|
|
347
|
-
...override
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
347
|
function delay(ms) {
|
|
351
348
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
352
349
|
}
|
|
353
350
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return
|
|
351
|
+
function decodeBase64(value) {
|
|
352
|
+
if (typeof atob === "function") {
|
|
353
|
+
const binary = atob(value);
|
|
354
|
+
const bytes = new Uint8Array(binary.length);
|
|
355
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
356
|
+
bytes[i] = binary.charCodeAt(i);
|
|
357
|
+
}
|
|
358
|
+
return bytes;
|
|
362
359
|
}
|
|
363
|
-
|
|
364
|
-
|
|
360
|
+
throw new Error("Base64 decoding is not supported in this runtime.");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function toHonoPath(tokens) {
|
|
364
|
+
if (!tokens || tokens.length === 0) {
|
|
365
|
+
return "/";
|
|
365
366
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
record[key] = value;
|
|
367
|
+
const segments = tokens.map((token) => {
|
|
368
|
+
if (token.type === "static") {
|
|
369
|
+
return token.value;
|
|
370
370
|
}
|
|
371
|
-
|
|
372
|
-
|
|
371
|
+
if (token.type === "param") {
|
|
372
|
+
return `:${token.name}`;
|
|
373
|
+
}
|
|
374
|
+
if (token.type === "catchall") {
|
|
375
|
+
return `:${token.name}{.+}`;
|
|
376
|
+
}
|
|
377
|
+
return `:${token.name}{.+}?`;
|
|
378
|
+
});
|
|
379
|
+
return `/${segments.join("/")}`;
|
|
373
380
|
}
|
|
374
|
-
function
|
|
375
|
-
|
|
376
|
-
|
|
381
|
+
function compileRoutes(manifest) {
|
|
382
|
+
const compiled = [];
|
|
383
|
+
for (const route of manifest.routes) {
|
|
384
|
+
const method = normalizeMethod(route.method) ?? "GET";
|
|
385
|
+
const parsed = route.tokens ? {
|
|
386
|
+
tokens: route.tokens,
|
|
387
|
+
score: route.score ?? scoreRouteTokens(route.tokens),
|
|
388
|
+
errors: []
|
|
389
|
+
} : parseRouteTemplate(route.url);
|
|
390
|
+
if (parsed.errors.length > 0) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
compiled.push({
|
|
394
|
+
route,
|
|
395
|
+
method,
|
|
396
|
+
tokens: route.tokens ?? parsed.tokens,
|
|
397
|
+
score: route.score ?? parsed.score
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return compiled.sort((a, b) => {
|
|
401
|
+
if (a.method !== b.method) {
|
|
402
|
+
return a.method.localeCompare(b.method);
|
|
403
|
+
}
|
|
404
|
+
return compareRouteScore(a.score, b.score);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
function shouldTreatAsText(contentType) {
|
|
408
|
+
const normalized = contentType.toLowerCase();
|
|
409
|
+
if (!normalized) {
|
|
410
|
+
return true;
|
|
377
411
|
}
|
|
378
|
-
if (
|
|
379
|
-
return
|
|
380
|
-
body,
|
|
381
|
-
contentType: contentType ?? "text/plain; charset=utf-8"
|
|
382
|
-
};
|
|
412
|
+
if (normalized.startsWith("text/") || normalized.includes("json") || normalized.includes("xml") || normalized.includes("javascript")) {
|
|
413
|
+
return true;
|
|
383
414
|
}
|
|
384
|
-
if (
|
|
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;
|
|
422
|
+
}
|
|
423
|
+
async function toRuntimeResult(response) {
|
|
424
|
+
const headers = {};
|
|
425
|
+
response.headers.forEach((value, key) => {
|
|
426
|
+
headers[key.toLowerCase()] = value;
|
|
427
|
+
});
|
|
428
|
+
if (!response.body || [204, 205, 304].includes(response.status)) {
|
|
385
429
|
return {
|
|
386
|
-
|
|
387
|
-
|
|
430
|
+
status: response.status,
|
|
431
|
+
headers,
|
|
432
|
+
body: null
|
|
388
433
|
};
|
|
389
434
|
}
|
|
390
|
-
|
|
435
|
+
const contentType = headers["content-type"] ?? "";
|
|
436
|
+
if (shouldTreatAsText(contentType)) {
|
|
391
437
|
return {
|
|
392
|
-
|
|
393
|
-
|
|
438
|
+
status: response.status,
|
|
439
|
+
headers,
|
|
440
|
+
body: await response.text()
|
|
394
441
|
};
|
|
395
442
|
}
|
|
443
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
396
444
|
return {
|
|
397
|
-
|
|
398
|
-
|
|
445
|
+
status: response.status,
|
|
446
|
+
headers,
|
|
447
|
+
body: buffer
|
|
399
448
|
};
|
|
400
449
|
}
|
|
401
|
-
function
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
+
}
|
|
462
|
+
function applyRouteOverrides(response, route) {
|
|
463
|
+
const headers = new Headers(response.headers);
|
|
464
|
+
const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
|
|
465
|
+
if (route.headers) {
|
|
466
|
+
for (const [key, value] of Object.entries(route.headers)) {
|
|
467
|
+
headers.set(key, value);
|
|
407
468
|
}
|
|
408
|
-
return bytes;
|
|
409
469
|
}
|
|
410
|
-
|
|
470
|
+
const status = resolveStatus(route.status, response.status);
|
|
471
|
+
if (status === response.status && !hasHeaders) {
|
|
472
|
+
return response;
|
|
473
|
+
}
|
|
474
|
+
return new Response(response.body, { status, headers });
|
|
411
475
|
}
|
|
412
|
-
function
|
|
413
|
-
if (
|
|
414
|
-
return
|
|
476
|
+
function normalizeHandlerValue(c, value) {
|
|
477
|
+
if (value instanceof Response) {
|
|
478
|
+
return value;
|
|
479
|
+
}
|
|
480
|
+
if (typeof value === "undefined") {
|
|
481
|
+
const response = c.body(null);
|
|
482
|
+
if (response.status === 200) {
|
|
483
|
+
return new Response(response.body, {
|
|
484
|
+
status: 204,
|
|
485
|
+
headers: response.headers
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
return response;
|
|
489
|
+
}
|
|
490
|
+
if (typeof value === "string") {
|
|
491
|
+
return c.text(value);
|
|
415
492
|
}
|
|
416
|
-
|
|
493
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
494
|
+
if (!c.res.headers.get("content-type")) {
|
|
495
|
+
c.header("content-type", "application/octet-stream");
|
|
496
|
+
}
|
|
497
|
+
const data = value instanceof ArrayBuffer ? value : new Uint8Array(value);
|
|
498
|
+
return c.body(data);
|
|
499
|
+
}
|
|
500
|
+
return c.json(value);
|
|
417
501
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const ctx = {
|
|
422
|
-
delay,
|
|
423
|
-
json: (data) => data
|
|
424
|
-
};
|
|
425
|
-
const runHandler = async () => {
|
|
502
|
+
function createRouteHandler(params) {
|
|
503
|
+
const { route, moduleCache, moduleBase, moduleMap } = params;
|
|
504
|
+
return async (c) => {
|
|
426
505
|
if (route.response.type === "json") {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
506
|
+
if (typeof route.response.body === "undefined") {
|
|
507
|
+
return normalizeHandlerValue(c, void 0);
|
|
508
|
+
}
|
|
509
|
+
return c.json(route.response.body);
|
|
431
510
|
}
|
|
432
511
|
if (route.response.type === "text") {
|
|
433
|
-
return
|
|
434
|
-
value: route.response.body,
|
|
435
|
-
contentType: "text/plain; charset=utf-8"
|
|
436
|
-
};
|
|
512
|
+
return c.text(route.response.body);
|
|
437
513
|
}
|
|
438
514
|
if (route.response.type === "binary") {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
};
|
|
515
|
+
const data = new Uint8Array(decodeBase64(route.response.body));
|
|
516
|
+
c.header("content-type", "application/octet-stream");
|
|
517
|
+
return c.body(data);
|
|
443
518
|
}
|
|
444
519
|
const rule = await loadModuleRule(
|
|
445
520
|
route.response,
|
|
@@ -447,75 +522,144 @@ async function executeRoute(route, req, moduleCache, middlewareCache, moduleBase
|
|
|
447
522
|
moduleBase,
|
|
448
523
|
moduleMap
|
|
449
524
|
);
|
|
450
|
-
const value = await executeRule(rule,
|
|
451
|
-
return
|
|
525
|
+
const value = await executeRule(rule, c);
|
|
526
|
+
return normalizeHandlerValue(c, value);
|
|
452
527
|
};
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
return runHandler();
|
|
463
|
-
}
|
|
464
|
-
let nextResult;
|
|
465
|
-
const next = async () => {
|
|
466
|
-
nextResult = await dispatch(index + 1);
|
|
467
|
-
return nextResult.value;
|
|
468
|
-
};
|
|
469
|
-
const value = await handler(req, responder, ctx, next);
|
|
470
|
-
if (typeof value !== "undefined") {
|
|
471
|
-
return { value };
|
|
472
|
-
}
|
|
473
|
-
if (nextResult) {
|
|
474
|
-
return nextResult;
|
|
475
|
-
}
|
|
476
|
-
return { value: void 0 };
|
|
477
|
-
};
|
|
478
|
-
return dispatch(0);
|
|
528
|
+
}
|
|
529
|
+
function createFinalizeMiddleware(route) {
|
|
530
|
+
return async (c, next) => {
|
|
531
|
+
const response = await next();
|
|
532
|
+
const resolved = response ?? c.res;
|
|
533
|
+
if (route.delay && route.delay > 0) {
|
|
534
|
+
await delay(route.delay);
|
|
535
|
+
}
|
|
536
|
+
return applyRouteOverrides(resolved, route);
|
|
479
537
|
};
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
538
|
+
}
|
|
539
|
+
async function buildApp(params) {
|
|
540
|
+
const { manifest, moduleCache, middlewareCache, moduleBase, moduleMap } = params;
|
|
541
|
+
const app = new Hono({ router: new PatternRouter(), strict: false });
|
|
542
|
+
const compiled = compileRoutes(manifest);
|
|
543
|
+
for (const entry of compiled) {
|
|
544
|
+
const middlewares = [];
|
|
545
|
+
for (const middleware of entry.route.middleware ?? []) {
|
|
546
|
+
const handler2 = await loadModuleMiddleware(
|
|
547
|
+
middleware,
|
|
548
|
+
middlewareCache,
|
|
549
|
+
moduleBase,
|
|
550
|
+
moduleMap
|
|
551
|
+
);
|
|
552
|
+
if (handler2) {
|
|
553
|
+
middlewares.push(handler2);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
const handler = createRouteHandler({
|
|
557
|
+
route: entry.route,
|
|
558
|
+
moduleCache,
|
|
559
|
+
...typeof moduleBase !== "undefined" ? { moduleBase } : {},
|
|
560
|
+
...typeof moduleMap !== "undefined" ? { moduleMap } : {}
|
|
561
|
+
});
|
|
562
|
+
app.on(
|
|
563
|
+
entry.method,
|
|
564
|
+
toHonoPath(entry.tokens),
|
|
565
|
+
createFinalizeMiddleware(entry.route),
|
|
566
|
+
...middlewares,
|
|
567
|
+
handler
|
|
487
568
|
);
|
|
488
|
-
|
|
489
|
-
|
|
569
|
+
}
|
|
570
|
+
return app;
|
|
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
|
+
}
|
|
584
|
+
function appendQueryParams(url, query) {
|
|
585
|
+
for (const [key, value] of Object.entries(query)) {
|
|
586
|
+
if (Array.isArray(value)) {
|
|
587
|
+
for (const entry of value) {
|
|
588
|
+
url.searchParams.append(key, entry);
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
url.searchParams.append(key, value);
|
|
490
592
|
}
|
|
491
593
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
await delay(route.delay);
|
|
594
|
+
}
|
|
595
|
+
function resolveRequestBody(req, contentType) {
|
|
596
|
+
if (typeof req.rawBody !== "undefined") {
|
|
597
|
+
return req.rawBody;
|
|
497
598
|
}
|
|
498
|
-
const
|
|
499
|
-
if (
|
|
500
|
-
|
|
599
|
+
const body = req.body;
|
|
600
|
+
if (typeof body === "undefined") {
|
|
601
|
+
return void 0;
|
|
501
602
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
route.status ?? responder.statusCode,
|
|
505
|
-
normalized.body
|
|
506
|
-
);
|
|
507
|
-
if (normalized.contentType && !headers["content-type"]) {
|
|
508
|
-
headers["content-type"] = normalized.contentType;
|
|
603
|
+
if (typeof body === "string") {
|
|
604
|
+
return body;
|
|
509
605
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
606
|
+
if (body instanceof Uint8Array || body instanceof ArrayBuffer) {
|
|
607
|
+
return body;
|
|
608
|
+
}
|
|
609
|
+
if (typeof body === "object") {
|
|
610
|
+
const normalized = contentType.toLowerCase();
|
|
611
|
+
if (normalized.includes("json")) {
|
|
612
|
+
return JSON.stringify(body);
|
|
613
|
+
}
|
|
614
|
+
return JSON.stringify(body);
|
|
615
|
+
}
|
|
616
|
+
return String(body);
|
|
617
|
+
}
|
|
618
|
+
function toFetchRequest(req) {
|
|
619
|
+
const url = new URL(normalizePathname(req.path), "http://mokup.local");
|
|
620
|
+
appendQueryParams(url, req.query);
|
|
621
|
+
const headers = new Headers();
|
|
622
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
623
|
+
headers.set(key, value);
|
|
624
|
+
}
|
|
625
|
+
const method = normalizeMethod(req.method) ?? "GET";
|
|
626
|
+
const contentType = headers.get("content-type") ?? "";
|
|
627
|
+
const body = resolveRequestBody(req, contentType);
|
|
628
|
+
const init = { method, headers };
|
|
629
|
+
if (typeof body !== "undefined" && method !== "GET" && method !== "HEAD") {
|
|
630
|
+
init.body = body;
|
|
631
|
+
}
|
|
632
|
+
return new Request(url.toString(), init);
|
|
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;
|
|
515
658
|
}
|
|
516
659
|
function createRuntime(options) {
|
|
517
660
|
let manifestCache = null;
|
|
518
|
-
let
|
|
661
|
+
let appPromise = null;
|
|
662
|
+
let compiledCache = null;
|
|
519
663
|
const moduleCache = /* @__PURE__ */ new Map();
|
|
520
664
|
const middlewareCache = /* @__PURE__ */ new Map();
|
|
521
665
|
const getManifest = async () => {
|
|
@@ -524,60 +668,56 @@ function createRuntime(options) {
|
|
|
524
668
|
}
|
|
525
669
|
return manifestCache;
|
|
526
670
|
};
|
|
527
|
-
const
|
|
528
|
-
if (
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
const tokens = route.tokens ?? parsed.tokens;
|
|
540
|
-
const score = route.score ?? parsed.score;
|
|
541
|
-
const list = map.get(method) ?? [];
|
|
542
|
-
list.push({ route, tokens, score });
|
|
543
|
-
map.set(method, list);
|
|
671
|
+
const getApp = async () => {
|
|
672
|
+
if (!appPromise) {
|
|
673
|
+
appPromise = (async () => {
|
|
674
|
+
const manifest = await getManifest();
|
|
675
|
+
return buildApp({
|
|
676
|
+
manifest,
|
|
677
|
+
moduleCache,
|
|
678
|
+
middlewareCache,
|
|
679
|
+
...typeof options.moduleBase !== "undefined" ? { moduleBase: options.moduleBase } : {},
|
|
680
|
+
...typeof options.moduleMap !== "undefined" ? { moduleMap: options.moduleMap } : {}
|
|
681
|
+
});
|
|
682
|
+
})();
|
|
544
683
|
}
|
|
545
|
-
|
|
546
|
-
|
|
684
|
+
return appPromise;
|
|
685
|
+
};
|
|
686
|
+
const getCompiled = async () => {
|
|
687
|
+
if (!compiledCache) {
|
|
688
|
+
compiledCache = compileRoutes(await getManifest());
|
|
547
689
|
}
|
|
548
|
-
|
|
549
|
-
return compiledRoutes;
|
|
690
|
+
return compiledCache;
|
|
550
691
|
};
|
|
551
692
|
const handle = async (req) => {
|
|
552
693
|
const method = normalizeMethod(req.method) ?? "GET";
|
|
553
|
-
const
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const matched = matchRouteTokens(entry.tokens, req.path);
|
|
560
|
-
if (!matched) {
|
|
694
|
+
const matchMethod = method === "HEAD" ? "GET" : method;
|
|
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) {
|
|
561
700
|
continue;
|
|
562
701
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
return executeRoute(
|
|
568
|
-
entry.route,
|
|
569
|
-
requestWithParams,
|
|
570
|
-
moduleCache,
|
|
571
|
-
middlewareCache,
|
|
572
|
-
options.moduleBase,
|
|
573
|
-
options.moduleMap
|
|
574
|
-
);
|
|
702
|
+
if (matchRouteTokens(entry.tokens, pathname)) {
|
|
703
|
+
matchedRoute = entry.route;
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
575
706
|
}
|
|
576
|
-
|
|
707
|
+
if (!matchedRoute) {
|
|
708
|
+
return null;
|
|
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();
|
|
714
|
+
const response = await app.fetch(toFetchRequest(req));
|
|
715
|
+
const resolvedResponse = applyRouteOverrides(response, matchedRoute);
|
|
716
|
+
return await toRuntimeResult(resolvedResponse);
|
|
577
717
|
};
|
|
578
718
|
return {
|
|
579
719
|
handle
|
|
580
720
|
};
|
|
581
721
|
}
|
|
582
722
|
|
|
583
|
-
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.
|
|
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",
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"dist"
|
|
28
28
|
],
|
|
29
|
-
"dependencies": {
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@mokup/shared": "0.1.0"
|
|
31
|
+
},
|
|
30
32
|
"devDependencies": {
|
|
31
33
|
"typescript": "^5.9.3",
|
|
32
34
|
"unbuild": "^3.6.1"
|