stratal 0.0.18 → 0.0.20
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/README.md +8 -8
- package/dist/{base-email.provider-Cuw4OAB0.mjs → base-email.provider-CfQCA08m.mjs} +1 -1
- package/dist/{base-email.provider-Cuw4OAB0.mjs.map → base-email.provider-CfQCA08m.mjs.map} +1 -1
- package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
- package/dist/bin/quarry.mjs +26 -35
- package/dist/bin/quarry.mjs.map +1 -1
- package/dist/cache/index.d.mts +2 -153
- package/dist/cache/index.d.mts.map +1 -1
- package/dist/cache/index.mjs +4 -6
- package/dist/cache/index.mjs.map +1 -1
- package/dist/cache.service-DsnKuNyO.d.mts +156 -0
- package/dist/cache.service-DsnKuNyO.d.mts.map +1 -0
- package/dist/cache.tokens-B7Rw1C9Q.mjs +6 -0
- package/dist/cache.tokens-B7Rw1C9Q.mjs.map +1 -0
- package/dist/{colors-BTAnQRGU.mjs → colors-DJaRDXoS.mjs} +1 -1
- package/dist/{colors-BTAnQRGU.mjs.map → colors-DJaRDXoS.mjs.map} +1 -1
- package/dist/{command-DjGqCYHv.mjs → command-BgSlsS4M.mjs} +2 -2
- package/dist/{command-DjGqCYHv.mjs.map → command-BgSlsS4M.mjs.map} +1 -1
- package/dist/{command-B1YuV-UZ.d.mts → command-Bu-PjJrX.d.mts} +2 -2
- package/dist/{command-B1YuV-UZ.d.mts.map → command-Bu-PjJrX.d.mts.map} +1 -1
- package/dist/config/index.d.mts +81 -37
- package/dist/config/index.d.mts.map +1 -1
- package/dist/config/index.mjs +126 -45
- package/dist/config/index.mjs.map +1 -1
- package/dist/{consumer-registry-BkuHXR_u.d.mts → consumer-registry-B7yUNh0q.d.mts} +1 -1
- package/dist/{consumer-registry-BkuHXR_u.d.mts.map → consumer-registry-B7yUNh0q.d.mts.map} +1 -1
- package/dist/controller.decorator-DQzenvSN.mjs +66 -0
- package/dist/controller.decorator-DQzenvSN.mjs.map +1 -0
- package/dist/cron/index.d.mts +4 -3
- package/dist/cron/index.d.mts.map +1 -1
- package/dist/cron/index.mjs +1 -1
- package/dist/{cron-manager-1KnZvojs.mjs → cron-manager-7Symz_TE.mjs} +29 -19
- package/dist/cron-manager-7Symz_TE.mjs.map +1 -0
- package/dist/{cron-manager-BnEZquBL.d.mts → cron-manager-BEsH1mjW.d.mts} +27 -13
- package/dist/cron-manager-BEsH1mjW.d.mts.map +1 -0
- package/dist/di/index.d.mts +1 -1
- package/dist/di/index.mjs +2 -2
- package/dist/email/index.d.mts +3 -3
- package/dist/email/index.mjs +87 -10
- package/dist/email/index.mjs.map +1 -1
- package/dist/{en-3QnZwP-u.mjs → en-DSH_bhh6.mjs} +10 -30
- package/dist/en-DSH_bhh6.mjs.map +1 -0
- package/dist/env-D1rcZ8_r.d.mts +25 -0
- package/dist/env-D1rcZ8_r.d.mts.map +1 -0
- package/dist/errors/index.d.mts +1 -1
- package/dist/errors/index.mjs +1 -1
- package/dist/{errors--RBIvDXr.mjs → errors-BdyV5PnY.mjs} +180 -15
- package/dist/errors-BdyV5PnY.mjs.map +1 -0
- package/dist/{errors-B7hCnXgB.mjs → errors-Da3Pz2X7.mjs} +14 -7
- package/dist/errors-Da3Pz2X7.mjs.map +1 -0
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +1 -1
- package/dist/{events-UTJliZhl.mjs → events-COKixqnG.mjs} +2 -2
- package/dist/{events-UTJliZhl.mjs.map → events-COKixqnG.mjs.map} +1 -1
- package/dist/{gateway-context-BdBFoQd8.mjs → gateway-context-CdJjpUCW.mjs} +5 -70
- package/dist/gateway-context-CdJjpUCW.mjs.map +1 -0
- package/dist/guards/index.d.mts +14 -5
- package/dist/guards/index.d.mts.map +1 -1
- package/dist/guards/index.mjs +1 -1
- package/dist/{guards-MtDgcHnF.mjs → guards-DUk_Kzst.mjs} +1 -1
- package/dist/guards-DUk_Kzst.mjs.map +1 -0
- package/dist/http-method.decorator-DXwxAfb_.mjs +96 -0
- package/dist/http-method.decorator-DXwxAfb_.mjs.map +1 -0
- package/dist/i18n/index.d.mts +3 -3
- package/dist/i18n/index.mjs +2 -2
- package/dist/i18n/messages/en/index.d.mts +1 -1
- package/dist/i18n/messages/en/index.mjs +1 -1
- package/dist/i18n/utils/index.mjs +1 -1
- package/dist/i18n/validation/index.d.mts +2 -2
- package/dist/i18n/validation/index.mjs +2 -2
- package/dist/{i18n.module-BpLLLCTg.mjs → i18n.module-BBlNNlcG.mjs} +234 -204
- package/dist/i18n.module-BBlNNlcG.mjs.map +1 -0
- package/dist/index-7-hU3GTV.d.mts +101 -0
- package/dist/index-7-hU3GTV.d.mts.map +1 -0
- package/dist/{index-Dfpd_ypO.d.mts → index-Bnpfq6uk.d.mts} +81 -19
- package/dist/index-Bnpfq6uk.d.mts.map +1 -0
- package/dist/{index-BDh9J2KD.d.mts → index-C1KvMncZ.d.mts} +9 -29
- package/dist/{index-BDh9J2KD.d.mts.map → index-C1KvMncZ.d.mts.map} +1 -1
- package/dist/{index-DPxmo6AY.d.mts → index-CjaQ6_tZ.d.mts} +5 -4
- package/dist/index-CjaQ6_tZ.d.mts.map +1 -0
- package/dist/{index-BrmS34sa.d.mts → index-D0US0X14.d.mts} +375 -235
- package/dist/index-D0US0X14.d.mts.map +1 -0
- package/dist/{index-BR23zDMy.d.mts → index-DBd_2wv8.d.mts} +1 -1
- package/dist/{index-BR23zDMy.d.mts.map → index-DBd_2wv8.d.mts.map} +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{is-command-PvULqiTa.mjs → is-command-C6a7WTPw.mjs} +2 -2
- package/dist/{is-command-PvULqiTa.mjs.map → is-command-C6a7WTPw.mjs.map} +1 -1
- package/dist/{is-seeder-BN9Ej1r7.mjs → is-seeder-CebjZCDn.mjs} +1 -1
- package/dist/{is-seeder-BN9Ej1r7.mjs.map → is-seeder-CebjZCDn.mjs.map} +1 -1
- package/dist/logger/index.d.mts +1 -1
- package/dist/logger/index.mjs +1 -1
- package/dist/{logger-c0ftIK4G.mjs → logger-V6Ms3QnQ.mjs} +38 -20
- package/dist/{logger-c0ftIK4G.mjs.map → logger-V6Ms3QnQ.mjs.map} +1 -1
- package/dist/macroable/index.d.mts +2 -0
- package/dist/macroable/index.mjs +2 -0
- package/dist/macroable-BmufBshB.mjs +122 -0
- package/dist/macroable-BmufBshB.mjs.map +1 -0
- package/dist/module/index.d.mts +2 -2
- package/dist/module/index.mjs +1 -1
- package/dist/{module-C3YZ-kZN.mjs → module-Dk2qTa77.mjs} +160 -19
- package/dist/module-Dk2qTa77.mjs.map +1 -0
- package/dist/openapi/index.d.mts +3 -3
- package/dist/openapi/index.mjs +2 -2
- package/dist/{openapi-tools.service-B77QXD56.mjs → openapi-tools.service-Zs-Ewv7F.mjs} +4 -1
- package/dist/{openapi-tools.service-B77QXD56.mjs.map → openapi-tools.service-Zs-Ewv7F.mjs.map} +1 -1
- package/dist/{openapi.service-6yj0BUY4.d.mts → openapi.service-BLgvn3hJ.d.mts} +3 -3
- package/dist/{openapi.service-6yj0BUY4.d.mts.map → openapi.service-BLgvn3hJ.d.mts.map} +1 -1
- package/dist/quarry/index.d.mts +7 -7
- package/dist/quarry/index.d.mts.map +1 -1
- package/dist/quarry/index.mjs +4 -4
- package/dist/{quarry-registry-CQCIlYTO.mjs → quarry-registry-DNEej-Db.mjs} +17 -15
- package/dist/quarry-registry-DNEej-Db.mjs.map +1 -0
- package/dist/queue/index.d.mts +2 -2
- package/dist/queue/index.mjs +2 -2
- package/dist/{queue.module-DIjD6nr-.mjs → queue.module-BCdCiySt.mjs} +4 -4
- package/dist/{queue.module-DIjD6nr-.mjs.map → queue.module-BCdCiySt.mjs.map} +1 -1
- package/dist/r2-storage.provider-Co6F0ZYV.mjs +244 -0
- package/dist/r2-storage.provider-Co6F0ZYV.mjs.map +1 -0
- package/dist/rate-limit.decorator--o6Q6p9w.mjs +55 -0
- package/dist/rate-limit.decorator--o6Q6p9w.mjs.map +1 -0
- package/dist/rate-limiter/index.d.mts +420 -0
- package/dist/rate-limiter/index.d.mts.map +1 -0
- package/dist/rate-limiter/index.mjs +365 -0
- package/dist/rate-limiter/index.mjs.map +1 -0
- package/dist/{resend.provider-Bvw36rQy.mjs → resend.provider-M6qRLrcy.mjs} +2 -2
- package/dist/{resend.provider-Bvw36rQy.mjs.map → resend.provider-M6qRLrcy.mjs.map} +1 -1
- package/dist/router/index.d.mts +2 -2
- package/dist/router/index.mjs +7 -5
- package/dist/seeder/index.d.mts +3 -3
- package/dist/seeder/index.mjs +2 -2
- package/dist/{seeder-D7VXULXB.mjs → seeder-CJAOHEIo.mjs} +5 -5
- package/dist/{seeder-D7VXULXB.mjs.map → seeder-CJAOHEIo.mjs.map} +1 -1
- package/dist/{setup-BRIN-iYT.mjs → setup-CefZKV_e.mjs} +1 -1
- package/dist/{setup-BRIN-iYT.mjs.map → setup-CefZKV_e.mjs.map} +1 -1
- package/dist/signed-url-BQPbv2In.mjs +74 -0
- package/dist/signed-url-BQPbv2In.mjs.map +1 -0
- package/dist/{smtp.provider-CAwpvzvD.mjs → smtp.provider-w0Ve52Xg.mjs} +2 -2
- package/dist/{smtp.provider-CAwpvzvD.mjs.map → smtp.provider-w0Ve52Xg.mjs.map} +1 -1
- package/dist/storage/index.d.mts +39 -17
- package/dist/storage/index.d.mts.map +1 -1
- package/dist/storage/index.mjs +3 -3
- package/dist/storage/providers/index.d.mts +30 -70
- package/dist/storage/providers/index.d.mts.map +1 -1
- package/dist/storage/providers/index.mjs +2 -2
- package/dist/{storage-CJ-QOwNv.mjs → storage-1zw-6Yiz.mjs} +101 -27
- package/dist/storage-1zw-6Yiz.mjs.map +1 -0
- package/dist/{storage-provider.interface-YRtyYBxV.d.mts → storage-provider.interface-Bd6vA4ak.d.mts} +20 -21
- package/dist/storage-provider.interface-Bd6vA4ak.d.mts.map +1 -0
- package/dist/{stratal-B7G4i9-N.mjs → stratal-DeEcGgdq.mjs} +57 -26
- package/dist/stratal-DeEcGgdq.mjs.map +1 -0
- package/dist/{types-CN0zONAZ.d.mts → types-cySNS_lp.d.mts} +1 -1
- package/dist/types-cySNS_lp.d.mts.map +1 -0
- package/dist/{usage-generator-Cl1HPlUp.mjs → usage-generator-BUdlhnCK.mjs} +2 -2
- package/dist/{usage-generator-Cl1HPlUp.mjs.map → usage-generator-BUdlhnCK.mjs.map} +1 -1
- package/dist/{validation-B4bePOa_.mjs → validation-DtJwAv7O.mjs} +62 -8
- package/dist/validation-DtJwAv7O.mjs.map +1 -0
- package/dist/websocket/index.d.mts +9 -4
- package/dist/websocket/index.d.mts.map +1 -1
- package/dist/websocket/index.mjs +1 -1
- package/dist/workers/index.d.mts +2 -1
- package/dist/workers/index.d.mts.map +1 -1
- package/dist/workers/index.mjs +2 -2
- package/package.json +32 -40
- package/dist/cron-manager-1KnZvojs.mjs.map +0 -1
- package/dist/cron-manager-BnEZquBL.d.mts.map +0 -1
- package/dist/en-3QnZwP-u.mjs.map +0 -1
- package/dist/errors--RBIvDXr.mjs.map +0 -1
- package/dist/errors-B7hCnXgB.mjs.map +0 -1
- package/dist/gateway-context-BdBFoQd8.mjs.map +0 -1
- package/dist/guards-MtDgcHnF.mjs.map +0 -1
- package/dist/i18n.module-BpLLLCTg.mjs.map +0 -1
- package/dist/index-BrmS34sa.d.mts.map +0 -1
- package/dist/index-DPxmo6AY.d.mts.map +0 -1
- package/dist/index-Dfpd_ypO.d.mts.map +0 -1
- package/dist/module-C3YZ-kZN.mjs.map +0 -1
- package/dist/quarry-registry-CQCIlYTO.mjs.map +0 -1
- package/dist/s3-storage.provider-BAhHDMI3.mjs +0 -343
- package/dist/s3-storage.provider-BAhHDMI3.mjs.map +0 -1
- package/dist/storage-CJ-QOwNv.mjs.map +0 -1
- package/dist/storage-provider.interface-YRtyYBxV.d.mts.map +0 -1
- package/dist/stratal-B7G4i9-N.mjs.map +0 -1
- package/dist/types-CN0zONAZ.d.mts.map +0 -1
- package/dist/validation-B4bePOa_.mjs.map +0 -1
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { A as Scope, D as runWithContainer, E as getContainer, H as ApplicationError, V as ROUTER_TOKENS, a as createHttpExceptionContext, c as DEFAULT_CONTENT_TYPE, d as ROUTER_CONTEXT_KEYS, f as ROUTE_METADATA_KEYS, k as ERROR_CODES, l as HTTP_METHODS, m as VERSION_NEUTRAL, p as SECURITY_SCHEMES, s as RouterContext, u as METHOD_STATUS_CODES, w as I18N_TOKENS } from "./errors
|
|
2
|
-
import { a as __decorate, d as CONTAINER_TOKEN, f as DI_TOKENS, g as getMethodInjections, o as __decorateParam, p as Transient, s as __decorateMetadata, u as LOGGER_TOKENS } from "./logger-
|
|
3
|
-
import { S as
|
|
4
|
-
import {
|
|
5
|
-
import { n as OPENAPI_TOKENS } from "./openapi-tools.service-
|
|
6
|
-
import { t as en_exports } from "./en-
|
|
7
|
-
import { i as getMethodGuards, r as getControllerGuards, t as GuardExecutionService } from "./guards-
|
|
8
|
-
import {
|
|
9
|
-
import { t as
|
|
1
|
+
import { A as Scope, D as runWithContainer, E as getContainer, H as ApplicationError, V as ROUTER_TOKENS, a as createHttpExceptionContext, c as DEFAULT_CONTENT_TYPE, d as ROUTER_CONTEXT_KEYS, f as ROUTE_METADATA_KEYS, k as ERROR_CODES, l as HTTP_METHODS, m as VERSION_NEUTRAL, p as SECURITY_SCHEMES, s as RouterContext, u as METHOD_STATUS_CODES, w as I18N_TOKENS } from "./errors-BdyV5PnY.mjs";
|
|
2
|
+
import { a as __decorate, d as CONTAINER_TOKEN, f as DI_TOKENS, g as getMethodInjections, o as __decorateParam, p as Transient, s as __decorateMetadata, u as LOGGER_TOKENS } from "./logger-V6Ms3QnQ.mjs";
|
|
3
|
+
import { S as createThrottleMiddleware, b as ControllerRegistrationError, c as InvalidSignatureError, d as MissingRouteParamError, f as ResponseValidationError, g as RouteNotFoundError, h as SchemaValidationError, k as Module, l as MiddlewareNextCalledMultipleTimesError, o as DomainMismatchError, p as RouteNameNotFoundError, s as DuplicateRouteNameError, u as MissingEnvironmentVariableError, v as OpenAPIRouteRegistrationError, x as ControllerMethodNotFoundError, y as HonoAppAlreadyConfiguredError } from "./module-Dk2qTa77.mjs";
|
|
4
|
+
import { c as backendErrorMap, i as z, l as runWithErrorMapContext, r as validation_exports, t as OpenAPIHono } from "./validation-DtJwAv7O.mjs";
|
|
5
|
+
import { n as OPENAPI_TOKENS } from "./openapi-tools.service-Zs-Ewv7F.mjs";
|
|
6
|
+
import { t as en_exports } from "./en-DSH_bhh6.mjs";
|
|
7
|
+
import { i as getMethodGuards, r as getControllerGuards, t as GuardExecutionService } from "./guards-DUk_Kzst.mjs";
|
|
8
|
+
import { n as getControllerOptions, r as getControllerRoute } from "./controller.decorator-DQzenvSN.mjs";
|
|
9
|
+
import { c as getWsOnMessageMethod, d as isGateway, o as getWsOnCloseMethod, s as getWsOnErrorMethod, t as GatewayContext } from "./gateway-context-CdJjpUCW.mjs";
|
|
10
|
+
import "./http-method.decorator-DXwxAfb_.mjs";
|
|
11
|
+
import { n as getRateLimits } from "./rate-limit.decorator--o6Q6p9w.mjs";
|
|
12
|
+
import { n as verifySignedUrl, t as signUrl } from "./signed-url-BQPbv2In.mjs";
|
|
13
|
+
import { t as setupI18nCompiler } from "./setup-CefZKV_e.mjs";
|
|
10
14
|
import { inject } from "tsyringe";
|
|
11
15
|
import { createCoreContext, translate } from "@intlify/core-base";
|
|
12
16
|
import { swaggerUI } from "@hono/swagger-ui";
|
|
@@ -705,104 +709,88 @@ function createMiddlewareChain(classes) {
|
|
|
705
709
|
let current = next;
|
|
706
710
|
for (let i = classes.length - 1; i >= 0; i--) {
|
|
707
711
|
const prevNext = current;
|
|
708
|
-
const
|
|
709
|
-
current = () =>
|
|
712
|
+
const middlewareClass = classes[i];
|
|
713
|
+
current = () => {
|
|
714
|
+
const middleware = requestContainer.resolve(middlewareClass);
|
|
715
|
+
let called = false;
|
|
716
|
+
const guardedNext = () => {
|
|
717
|
+
if (called) {
|
|
718
|
+
const err = new MiddlewareNextCalledMultipleTimesError(middlewareClass.name ?? "anonymous");
|
|
719
|
+
console.error("[STRATAL DEBUG] next() called multiple times for " + middlewareClass.name);
|
|
720
|
+
console.error("[STRATAL DEBUG] Stack trace:", (/* @__PURE__ */ new Error()).stack);
|
|
721
|
+
return Promise.reject(err);
|
|
722
|
+
}
|
|
723
|
+
called = true;
|
|
724
|
+
return prevNext();
|
|
725
|
+
};
|
|
726
|
+
return middleware.handle(ctx, guardedNext);
|
|
727
|
+
};
|
|
710
728
|
}
|
|
711
729
|
const result = await current();
|
|
712
730
|
if (result instanceof Response) return result;
|
|
713
731
|
};
|
|
714
732
|
}
|
|
715
733
|
//#endregion
|
|
716
|
-
//#region src/router/
|
|
717
|
-
/**
|
|
718
|
-
* Creates an HTTP method decorator factory for the given HTTP method.
|
|
719
|
-
*
|
|
720
|
-
* The returned decorator stores {@link ExplicitRouteMetadata} on the method and
|
|
721
|
-
* tracks the method name under {@link ROUTE_METADATA_KEYS.DECORATED_METHODS}
|
|
722
|
-
* on the controller prototype so they can be discovered at registration time.
|
|
723
|
-
*/
|
|
724
|
-
function createHttpMethodDecorator(method) {
|
|
725
|
-
return function(path, config) {
|
|
726
|
-
return function(target, propertyKey, descriptor) {
|
|
727
|
-
const metadata = {
|
|
728
|
-
type: "explicit",
|
|
729
|
-
method,
|
|
730
|
-
path,
|
|
731
|
-
config: config ?? { response: z.any() }
|
|
732
|
-
};
|
|
733
|
-
Reflect.defineMetadata(ROUTE_METADATA_KEYS.ROUTE_CONFIG, metadata, target, propertyKey);
|
|
734
|
-
const existing = Reflect.getOwnMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, target) ?? [];
|
|
735
|
-
existing.push(propertyKey);
|
|
736
|
-
Reflect.defineMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, existing, target);
|
|
737
|
-
return descriptor;
|
|
738
|
-
};
|
|
739
|
-
};
|
|
740
|
-
}
|
|
734
|
+
//#region src/router/trailing-slash.ts
|
|
741
735
|
/**
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
* @param path - Route path relative to the controller base path
|
|
745
|
-
* @param config - Optional route configuration (response schema, body, params, etc.)
|
|
746
|
-
*
|
|
747
|
-
* @example
|
|
748
|
-
* ```typescript
|
|
749
|
-
* @Controller('/api/v1/users')
|
|
750
|
-
* class UsersController {
|
|
751
|
-
* @Get('/', { response: z.array(userSchema), summary: 'List users' })
|
|
752
|
-
* async list(ctx: RouterContext) { ... }
|
|
736
|
+
* Apply a trailing-slash mode to a URL or path.
|
|
753
737
|
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
|
|
759
|
-
const Get = createHttpMethodDecorator("get");
|
|
760
|
-
/**
|
|
761
|
-
* Registers a POST route on the controller method.
|
|
738
|
+
* - `'ignore'` — return as-is.
|
|
739
|
+
* - `'always'` — append `/` to the pathname unless it already has one.
|
|
740
|
+
* Skipped when the last segment contains `.` (file-like paths) and for the
|
|
741
|
+
* root `/` path.
|
|
742
|
+
* - `'never'` — strip a trailing `/` from the pathname. Skipped for root.
|
|
762
743
|
*
|
|
763
|
-
*
|
|
764
|
-
*
|
|
744
|
+
* Preserves query string and hash. Handles both relative paths
|
|
745
|
+
* (`/foo?x=1`) and absolute URLs (`https://host/foo?x=1`).
|
|
765
746
|
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
* @Controller('/api/v1/users')
|
|
769
|
-
* class UsersController {
|
|
770
|
-
* @Post('/', { body: createUserSchema, response: userSchema, statusCode: 201 })
|
|
771
|
-
* async createUser(ctx: RouterContext) { ... }
|
|
772
|
-
* }
|
|
773
|
-
* ```
|
|
747
|
+
* Used by URL-generation helpers and the redirect middleware so canonical
|
|
748
|
+
* form is computed in one place.
|
|
774
749
|
*/
|
|
775
|
-
|
|
750
|
+
function applyTrailingSlash(url, mode) {
|
|
751
|
+
if (mode === "ignore") return url;
|
|
752
|
+
const isAbsolute = /^https?:\/\//i.test(url);
|
|
753
|
+
const parsed = isAbsolute ? new URL(url) : new URL(url, "http://placeholder.local");
|
|
754
|
+
const path = parsed.pathname;
|
|
755
|
+
if (path === "/") return url;
|
|
756
|
+
const hasTrailing = path.endsWith("/");
|
|
757
|
+
if (mode === "always" && !hasTrailing) {
|
|
758
|
+
if (path.slice(path.lastIndexOf("/") + 1).includes(".")) return url;
|
|
759
|
+
parsed.pathname = `${path}/`;
|
|
760
|
+
} else if (mode === "never" && hasTrailing) parsed.pathname = path.slice(0, -1);
|
|
761
|
+
else return url;
|
|
762
|
+
return isAbsolute ? parsed.toString() : `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
763
|
+
}
|
|
764
|
+
//#endregion
|
|
765
|
+
//#region src/router/middleware/trailing-slash-redirect.ts
|
|
766
|
+
const REDIRECT_STATUS = 308;
|
|
776
767
|
/**
|
|
777
|
-
*
|
|
768
|
+
* Create a Hono middleware that canonicalises trailing slashes via 308 redirects.
|
|
778
769
|
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
* Registers a PATCH route on the controller method.
|
|
770
|
+
* - `'ignore'` — returns `null`; routes match both `/foo` and `/foo/` natively
|
|
771
|
+
* (Hono handles this when constructed with `strict: false`).
|
|
772
|
+
* - `'always'` — non-trailing requests redirect to the trailing-slash form.
|
|
773
|
+
* Paths whose last segment contains `.` (e.g. `/api/openapi.json`) are skipped.
|
|
774
|
+
* - `'never'` — trailing requests redirect to the non-trailing form.
|
|
785
775
|
*
|
|
786
|
-
*
|
|
787
|
-
* @param config - Optional route configuration
|
|
788
|
-
*/
|
|
789
|
-
const Patch = createHttpMethodDecorator("patch");
|
|
790
|
-
/**
|
|
791
|
-
* Registers a DELETE route on the controller method.
|
|
776
|
+
* Root (`/`) is always passed through unchanged.
|
|
792
777
|
*
|
|
793
|
-
*
|
|
794
|
-
* @param config - Optional route configuration
|
|
795
|
-
*/
|
|
796
|
-
const Delete = createHttpMethodDecorator("delete");
|
|
797
|
-
/**
|
|
798
|
-
* Registers an ALL (any HTTP method) route on the controller method.
|
|
799
|
-
* Routes using @All are registered without OpenAPI validation
|
|
800
|
-
* since OpenAPI does not support a catch-all HTTP method.
|
|
778
|
+
* 308 is used so that POST/PUT/PATCH bodies survive the redirect.
|
|
801
779
|
*
|
|
802
|
-
*
|
|
803
|
-
*
|
|
780
|
+
* Location headers are emitted as path-relative URIs so the user agent
|
|
781
|
+
* resolves them against the effective request URI — sidestepping scheme
|
|
782
|
+
* mismatches behind HTTPS-terminating proxies that proxy HTTPS pages to an
|
|
783
|
+
* HTTP-speaking backend (which would otherwise produce a mixed-content block).
|
|
804
784
|
*/
|
|
805
|
-
|
|
785
|
+
function createTrailingSlashRedirect(mode) {
|
|
786
|
+
if (mode === "ignore") return null;
|
|
787
|
+
return async (c, next) => {
|
|
788
|
+
const url = new URL(c.req.url);
|
|
789
|
+
const canonicalPath = applyTrailingSlash(url.pathname, mode);
|
|
790
|
+
if (canonicalPath === url.pathname) return next();
|
|
791
|
+
return c.redirect(`${canonicalPath}${url.search}`, REDIRECT_STATUS);
|
|
792
|
+
};
|
|
793
|
+
}
|
|
806
794
|
//#endregion
|
|
807
795
|
//#region src/router/decorators/route.decorator.ts
|
|
808
796
|
/**
|
|
@@ -1212,10 +1200,12 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1212
1200
|
const controllerOpts = getControllerOptions(ControllerClass);
|
|
1213
1201
|
const controllerGuards = getControllerGuards(ControllerClass)?.guards ?? [];
|
|
1214
1202
|
const routerConfig = this.routerResolver?.resolveForController(ControllerClass) ?? { middleware: [] };
|
|
1203
|
+
const classThrottleMiddleware = Array.from(new Set(getRateLimits(ControllerClass).map(createThrottleMiddleware)));
|
|
1215
1204
|
const basePath = routerConfig.prefix ? this.joinPaths(routerConfig.prefix, controllerRoute) : controllerRoute;
|
|
1216
1205
|
const effectiveVersion = controllerOpts?.version ?? routerConfig.version;
|
|
1217
1206
|
const effectiveDomain = controllerOpts?.domain ?? routerConfig.domain;
|
|
1218
1207
|
if (isWsGateway) {
|
|
1208
|
+
const wsMiddleware = [...routerConfig.middleware, ...classThrottleMiddleware];
|
|
1219
1209
|
const expandedRoutes = this.registry.register({
|
|
1220
1210
|
method: "ws",
|
|
1221
1211
|
basePath,
|
|
@@ -1224,10 +1214,10 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1224
1214
|
controller: ControllerClass.name,
|
|
1225
1215
|
action: "ws",
|
|
1226
1216
|
hidden: routerConfig.hideFromDocs ?? false,
|
|
1227
|
-
middleware:
|
|
1217
|
+
middleware: wsMiddleware.map((m) => m.name)
|
|
1228
1218
|
});
|
|
1229
1219
|
for (const route of expandedRoutes) actions.set(route, () => {
|
|
1230
|
-
if (
|
|
1220
|
+
if (wsMiddleware.length > 0) this.app.use(route.path, createMiddlewareChain(wsMiddleware));
|
|
1231
1221
|
if (effectiveDomain) {
|
|
1232
1222
|
const domainHandler = createDomainMiddleware(effectiveDomain);
|
|
1233
1223
|
this.app.use(route.path, domainHandler);
|
|
@@ -1241,6 +1231,7 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1241
1231
|
this.controllerClasses.set(className, ControllerClass);
|
|
1242
1232
|
const prototype = ControllerClass.prototype;
|
|
1243
1233
|
if (prototype.handle) {
|
|
1234
|
+
const wildcardMiddleware = [...routerConfig.middleware, ...classThrottleMiddleware];
|
|
1244
1235
|
const expandedRoutes = this.registry.register({
|
|
1245
1236
|
method: "all",
|
|
1246
1237
|
basePath,
|
|
@@ -1249,10 +1240,10 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1249
1240
|
controller: className,
|
|
1250
1241
|
action: "handle",
|
|
1251
1242
|
hidden: routerConfig.hideFromDocs ?? false,
|
|
1252
|
-
middleware:
|
|
1243
|
+
middleware: wildcardMiddleware.map((m) => m.name)
|
|
1253
1244
|
});
|
|
1254
1245
|
for (const route of expandedRoutes) actions.set(route, () => {
|
|
1255
|
-
if (
|
|
1246
|
+
if (wildcardMiddleware.length > 0) this.app.use(route.path, createMiddlewareChain(wildcardMiddleware));
|
|
1256
1247
|
this.registerWildcardRoute(ControllerClass, route.path);
|
|
1257
1248
|
});
|
|
1258
1249
|
return;
|
|
@@ -1273,16 +1264,31 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1273
1264
|
else if (meta.type === "explicit") hasExplicit = true;
|
|
1274
1265
|
}
|
|
1275
1266
|
if (hasConvention && hasExplicit) throw new ControllerRegistrationError(ControllerClass.name, "Cannot mix @Route() with HTTP method decorators (@Get, @Post, etc.) in the same controller. Use one pattern or the other.");
|
|
1276
|
-
let scopedMiddlewareApplied = false;
|
|
1277
1267
|
const routerHidden = routerConfig.hideFromDocs;
|
|
1278
1268
|
const controllerHidden = controllerOpts?.hideFromDocs ?? false;
|
|
1279
|
-
const
|
|
1280
|
-
const
|
|
1269
|
+
const routerName = routerConfig.name;
|
|
1270
|
+
const controllerName = controllerOpts?.name;
|
|
1271
|
+
const effectiveNamePrefix = routerName && controllerName ? `${routerName}${controllerName}` : routerName ?? controllerName;
|
|
1281
1272
|
for (const { method: methodName, meta } of methodMetadata) {
|
|
1282
1273
|
const resolved = this.resolveMethodAndPath(meta, methodName, basePath, className);
|
|
1283
1274
|
if (!resolved) continue;
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1275
|
+
const methodThrottleMiddleware = getRateLimits(prototype, methodName).map(createThrottleMiddleware);
|
|
1276
|
+
const effectiveMiddleware = Array.from(new Set([
|
|
1277
|
+
...routerConfig.middleware,
|
|
1278
|
+
...classThrottleMiddleware,
|
|
1279
|
+
...methodThrottleMiddleware
|
|
1280
|
+
]));
|
|
1281
|
+
const middlewareNames = effectiveMiddleware.map((m) => m.name);
|
|
1282
|
+
const { httpMethod, fullPath, routeConfig: rawRouteConfig, statusCodeOverride } = resolved;
|
|
1283
|
+
let mergedParams = rawRouteConfig.params;
|
|
1284
|
+
if (routerConfig.params) {
|
|
1285
|
+
const prefixShape = routerConfig.params.shape;
|
|
1286
|
+
mergedParams = mergedParams ? mergedParams.extend(prefixShape) : routerConfig.params.extend({});
|
|
1287
|
+
}
|
|
1288
|
+
const routeConfig = mergedParams === rawRouteConfig.params ? rawRouteConfig : {
|
|
1289
|
+
...rawRouteConfig,
|
|
1290
|
+
params: mergedParams
|
|
1291
|
+
};
|
|
1286
1292
|
const hideFromDocs = routeConfig.hideFromDocs ?? routerHidden ?? controllerHidden;
|
|
1287
1293
|
let routeName;
|
|
1288
1294
|
if (routeConfig.name) routeName = effectiveNamePrefix ? `${effectiveNamePrefix}${routeConfig.name}` : routeConfig.name;
|
|
@@ -1306,11 +1312,6 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1306
1312
|
const responseSchema = httpMethod !== "all" ? this.extractResponseSchema(routeConfig) : null;
|
|
1307
1313
|
const handler = this.createControllerHandler(ControllerClass, methodName, responseSchema);
|
|
1308
1314
|
for (const route of expandedRoutes) actions.set(route, () => {
|
|
1309
|
-
if (!scopedMiddlewareApplied && routerConfig.middleware.length > 0) {
|
|
1310
|
-
const primaryRoute = expandedRoutes.find((r) => !r.isLocaleVariant) ?? expandedRoutes[0];
|
|
1311
|
-
this.app.use(`${primaryRoute.path}/*`, createMiddlewareChain(routerConfig.middleware));
|
|
1312
|
-
scopedMiddlewareApplied = true;
|
|
1313
|
-
}
|
|
1314
1315
|
if (effectiveDomain) {
|
|
1315
1316
|
const domainHandler = createDomainMiddleware(effectiveDomain);
|
|
1316
1317
|
this.app.use(route.path, domainHandler);
|
|
@@ -1329,12 +1330,13 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1329
1330
|
path: route.path,
|
|
1330
1331
|
methodName
|
|
1331
1332
|
});
|
|
1332
|
-
if (
|
|
1333
|
-
|
|
1333
|
+
if (effectiveMiddleware.length > 0) this.app.use(route.path, createMiddlewareChain(effectiveMiddleware));
|
|
1334
|
+
if (allGuards.length > 0) this.app.use(route.path, this.createGuardMiddleware(allGuards));
|
|
1335
|
+
this.app.all(route.path, handler);
|
|
1334
1336
|
return;
|
|
1335
1337
|
}
|
|
1336
1338
|
const metadata = this.mergeMetadata(controllerOpts, routeConfig, ControllerClass, methodName);
|
|
1337
|
-
const openApiRoute = this.buildOpenAPIRoute(httpMethod, route.path, routeConfig, metadata,
|
|
1339
|
+
const openApiRoute = this.buildOpenAPIRoute(httpMethod, route.path, routeConfig, metadata, meta.type === "convention" ? methodName : void 0, statusCodeOverride, route.isLocaleVariant ?? false);
|
|
1338
1340
|
this.logger.info(`Registering route`, {
|
|
1339
1341
|
controller: className,
|
|
1340
1342
|
method: httpMethod.toUpperCase(),
|
|
@@ -1343,7 +1345,8 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1343
1345
|
tags: metadata.tags,
|
|
1344
1346
|
hidden: route.hidden
|
|
1345
1347
|
});
|
|
1346
|
-
this.
|
|
1348
|
+
const wrappedHandler = this.wrapHandlerWithChain(handler, effectiveMiddleware, allGuards);
|
|
1349
|
+
this.app.openapi(openApiRoute, wrappedHandler);
|
|
1347
1350
|
if (!route.hidden) {
|
|
1348
1351
|
const { hide: _, ...specRoute } = openApiRoute;
|
|
1349
1352
|
this.app.openAPIRegistry.registerPath({
|
|
@@ -1416,6 +1419,31 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1416
1419
|
};
|
|
1417
1420
|
}
|
|
1418
1421
|
/**
|
|
1422
|
+
* Wrap a controller handler with a `scopedMiddleware → guards → handler`
|
|
1423
|
+
* chain that runs *inside* the Hono route handler — after request
|
|
1424
|
+
* validators have populated `c.req.valid(...)`. This is the only place
|
|
1425
|
+
* we can run user middleware after `@hono/zod-openapi`'s validators in
|
|
1426
|
+
* the same pipeline.
|
|
1427
|
+
*
|
|
1428
|
+
* Returns a Hono handler with the same signature as the original so
|
|
1429
|
+
* `app.openapi(route, wrapped)` works transparently.
|
|
1430
|
+
*/
|
|
1431
|
+
wrapHandlerWithChain(handler, scopedMiddleware, guards) {
|
|
1432
|
+
if (scopedMiddleware.length === 0 && guards.length === 0) return handler;
|
|
1433
|
+
const scopedChain = scopedMiddleware.length > 0 ? createMiddlewareChain(scopedMiddleware) : null;
|
|
1434
|
+
const guardChain = guards.length > 0 ? this.createGuardMiddleware(guards) : null;
|
|
1435
|
+
return async (c) => {
|
|
1436
|
+
let captured;
|
|
1437
|
+
const runHandler = async () => {
|
|
1438
|
+
captured = await handler(c);
|
|
1439
|
+
};
|
|
1440
|
+
const runGuards = guardChain ? () => guardChain(c, runHandler) : runHandler;
|
|
1441
|
+
const result = await (scopedChain ? () => scopedChain(c, runGuards) : runGuards)();
|
|
1442
|
+
if (result instanceof Response) return result;
|
|
1443
|
+
return captured;
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1419
1447
|
* Register wildcard route for non-RESTful controllers
|
|
1420
1448
|
*/
|
|
1421
1449
|
registerWildcardRoute(ControllerClass, route) {
|
|
@@ -1453,9 +1481,9 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1453
1481
|
* Join a base path and a route path, normalizing slashes
|
|
1454
1482
|
*/
|
|
1455
1483
|
joinPaths(basePath, routePath) {
|
|
1456
|
-
if (
|
|
1457
|
-
if (
|
|
1458
|
-
if (
|
|
1484
|
+
if (basePath.endsWith("/")) basePath = basePath.slice(0, -1);
|
|
1485
|
+
if (routePath === "/" || routePath === "") return basePath || "/";
|
|
1486
|
+
if (!routePath.startsWith("/")) routePath = "/" + routePath;
|
|
1459
1487
|
return basePath + routePath;
|
|
1460
1488
|
}
|
|
1461
1489
|
/**
|
|
@@ -1492,12 +1520,13 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1492
1520
|
}
|
|
1493
1521
|
/**
|
|
1494
1522
|
* Build OpenAPI route configuration from metadata
|
|
1495
|
-
* Creates a route definition compatible with @hono/zod-openapi
|
|
1496
|
-
* Includes guard execution for proper access control
|
|
1523
|
+
* Creates a route definition compatible with @hono/zod-openapi.
|
|
1497
1524
|
*
|
|
1498
|
-
*
|
|
1525
|
+
* Scoped middleware and guards are NOT attached to `route.middleware`
|
|
1526
|
+
* here — they're composed into a wrapped handler in `collectRoutes` so
|
|
1527
|
+
* they run after Hono's request validators. See `wrapHandlerWithChain`.
|
|
1499
1528
|
*/
|
|
1500
|
-
buildOpenAPIRoute(method, path, routeConfig, metadata,
|
|
1529
|
+
buildOpenAPIRoute(method, path, routeConfig, metadata, methodName, statusCodeOverride, hasLocaleParam = false) {
|
|
1501
1530
|
try {
|
|
1502
1531
|
const route = {
|
|
1503
1532
|
method,
|
|
@@ -1506,7 +1535,6 @@ let RouteRegistrationService = class RouteRegistrationService {
|
|
|
1506
1535
|
responses: {},
|
|
1507
1536
|
hide: true
|
|
1508
1537
|
};
|
|
1509
|
-
if (guards.length > 0) route.middleware = [this.createGuardMiddleware(guards)];
|
|
1510
1538
|
if (routeConfig.body) {
|
|
1511
1539
|
const bodySchema = this.isRouteBodyObject(routeConfig.body) ? routeConfig.body.schema : routeConfig.body;
|
|
1512
1540
|
const bodyContentType = this.isRouteBodyObject(routeConfig.body) ? routeConfig.body.contentType ?? "application/json" : DEFAULT_CONTENT_TYPE;
|
|
@@ -1680,12 +1708,16 @@ let HonoApp = class HonoApp extends OpenAPIHono {
|
|
|
1680
1708
|
* Used by private methods to register middleware without going through the override.
|
|
1681
1709
|
*/
|
|
1682
1710
|
nativeUse;
|
|
1683
|
-
constructor(container, logger) {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1711
|
+
constructor(container, logger, application) {
|
|
1712
|
+
const trailingSlash = application.config.trailingSlash ?? "ignore";
|
|
1713
|
+
super({
|
|
1714
|
+
strict: false,
|
|
1715
|
+
defaultHook: (result, c) => {
|
|
1716
|
+
if (!result.success) throw new SchemaValidationError(result.error);
|
|
1717
|
+
const override = c.get("validationSuccessResponse");
|
|
1718
|
+
if (override) return override;
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1689
1721
|
this._container = container;
|
|
1690
1722
|
this._logger = logger;
|
|
1691
1723
|
this.nativeUse = this.use;
|
|
@@ -1700,6 +1732,8 @@ let HonoApp = class HonoApp extends OpenAPIHono {
|
|
|
1700
1732
|
}
|
|
1701
1733
|
return this.nativeUse(...args);
|
|
1702
1734
|
});
|
|
1735
|
+
const trailingSlashRedirect = createTrailingSlashRedirect(trailingSlash);
|
|
1736
|
+
if (trailingSlashRedirect) this.nativeUse("*", trailingSlashRedirect);
|
|
1703
1737
|
this.setupRequestScope();
|
|
1704
1738
|
this.applyGlobalMiddleware();
|
|
1705
1739
|
}
|
|
@@ -1742,7 +1776,12 @@ HonoApp = __decorate([
|
|
|
1742
1776
|
Transient(),
|
|
1743
1777
|
__decorateParam(0, inject(CONTAINER_TOKEN)),
|
|
1744
1778
|
__decorateParam(1, inject(LOGGER_TOKENS.LoggerService)),
|
|
1745
|
-
|
|
1779
|
+
__decorateParam(2, inject(DI_TOKENS.Application)),
|
|
1780
|
+
__decorateMetadata("design:paramtypes", [
|
|
1781
|
+
Object,
|
|
1782
|
+
Object,
|
|
1783
|
+
Object
|
|
1784
|
+
])
|
|
1746
1785
|
], HonoApp);
|
|
1747
1786
|
//#endregion
|
|
1748
1787
|
//#region src/router/services/locale-path.service.ts
|
|
@@ -1891,10 +1930,21 @@ VersioningService = __decorate([
|
|
|
1891
1930
|
], VersioningService);
|
|
1892
1931
|
//#endregion
|
|
1893
1932
|
//#region src/router/route-registry.ts
|
|
1933
|
+
const CONCRETE_HTTP_METHODS = [
|
|
1934
|
+
"get",
|
|
1935
|
+
"post",
|
|
1936
|
+
"put",
|
|
1937
|
+
"delete",
|
|
1938
|
+
"patch",
|
|
1939
|
+
"head",
|
|
1940
|
+
"options",
|
|
1941
|
+
"trace"
|
|
1942
|
+
];
|
|
1894
1943
|
let RouteRegistry = class RouteRegistry {
|
|
1895
1944
|
routes = [];
|
|
1896
1945
|
namedRoutes = /* @__PURE__ */ new Map();
|
|
1897
1946
|
_sortedCache = null;
|
|
1947
|
+
_routeToNameCache = null;
|
|
1898
1948
|
constructor(versioningService, localePathService) {
|
|
1899
1949
|
this.versioningService = versioningService;
|
|
1900
1950
|
this.localePathService = localePathService;
|
|
@@ -1945,6 +1995,7 @@ let RouteRegistry = class RouteRegistry {
|
|
|
1945
1995
|
}
|
|
1946
1996
|
}
|
|
1947
1997
|
this._sortedCache = null;
|
|
1998
|
+
this._routeToNameCache = null;
|
|
1948
1999
|
return expandedRoutes;
|
|
1949
2000
|
}
|
|
1950
2001
|
/** Get a named route by name */
|
|
@@ -1955,6 +2006,25 @@ let RouteRegistry = class RouteRegistry {
|
|
|
1955
2006
|
has(name) {
|
|
1956
2007
|
return this.namedRoutes.has(name);
|
|
1957
2008
|
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Resolve a Hono-style route path pattern (e.g. as exposed by `c.req.routePath`)
|
|
2011
|
+
* back to its registered name, scoped to the request's HTTP method. Locale variant
|
|
2012
|
+
* paths resolve to the canonical primary route name. Method matching is
|
|
2013
|
+
* case-insensitive; routes registered with `'all'` resolve under any verb.
|
|
2014
|
+
*/
|
|
2015
|
+
findNameByRoute(method, path) {
|
|
2016
|
+
this._routeToNameCache ??= this.buildRouteToNameCache();
|
|
2017
|
+
return this._routeToNameCache.get(`${method.toLowerCase()}:${path}`);
|
|
2018
|
+
}
|
|
2019
|
+
buildRouteToNameCache() {
|
|
2020
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2021
|
+
for (const route of this.namedRoutes.values()) {
|
|
2022
|
+
const methods = route.method === "all" ? CONCRETE_HTTP_METHODS : [route.method];
|
|
2023
|
+
const paths = route.localePaths ? [route.path, ...route.localePaths] : [route.path];
|
|
2024
|
+
for (const m of methods) for (const p of paths) cache.set(`${m}:${p}`, route.name);
|
|
2025
|
+
}
|
|
2026
|
+
return cache;
|
|
2027
|
+
}
|
|
1958
2028
|
/** Get all routes sorted by specificity (static > param > wildcard, primary before locale) */
|
|
1959
2029
|
all() {
|
|
1960
2030
|
this._sortedCache ??= sortRoutesBySpecificity(this.routes);
|
|
@@ -1972,78 +2042,18 @@ RouteRegistry = __decorate([
|
|
|
1972
2042
|
__decorateMetadata("design:paramtypes", [Object, Object])
|
|
1973
2043
|
], RouteRegistry);
|
|
1974
2044
|
//#endregion
|
|
1975
|
-
//#region src/router/
|
|
1976
|
-
/**
|
|
1977
|
-
* Import a signing key for HMAC-SHA256.
|
|
1978
|
-
*/
|
|
1979
|
-
async function importKey(secret) {
|
|
1980
|
-
const encoder = new TextEncoder();
|
|
1981
|
-
return crypto.subtle.importKey("raw", encoder.encode(secret), {
|
|
1982
|
-
name: "HMAC",
|
|
1983
|
-
hash: "SHA-256"
|
|
1984
|
-
}, false, ["sign", "verify"]);
|
|
1985
|
-
}
|
|
1986
|
-
/**
|
|
1987
|
-
* Encode an ArrayBuffer as base64url (URL-safe base64).
|
|
1988
|
-
*/
|
|
1989
|
-
function toBase64Url(buffer) {
|
|
1990
|
-
const bytes = new Uint8Array(buffer);
|
|
1991
|
-
let binary = "";
|
|
1992
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
1993
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1994
|
-
}
|
|
1995
|
-
/**
|
|
1996
|
-
* Sign a URL with HMAC-SHA256.
|
|
1997
|
-
*
|
|
1998
|
-
* Appends `signature` and optionally `expires` query parameters.
|
|
1999
|
-
* The signature covers the pathname + search (excluding the signature params themselves).
|
|
2000
|
-
*
|
|
2001
|
-
* @param url - Full URL or path to sign
|
|
2002
|
-
* @param secret - HMAC secret key (e.g., from env.APP_SECRET)
|
|
2003
|
-
* @param options - Optional expiration
|
|
2004
|
-
* @returns URL string with `signature` (and `expires`) query params appended
|
|
2005
|
-
*/
|
|
2006
|
-
async function signUrl(url, secret, options) {
|
|
2007
|
-
const parsedUrl = new URL(url, "https://placeholder.local");
|
|
2008
|
-
const key = await importKey(secret);
|
|
2009
|
-
if (options?.expiresIn) {
|
|
2010
|
-
const expires = Math.floor(Date.now() / 1e3) + options.expiresIn;
|
|
2011
|
-
parsedUrl.searchParams.set("expires", String(expires));
|
|
2012
|
-
}
|
|
2013
|
-
const dataToSign = `${parsedUrl.pathname}?${parsedUrl.searchParams.toString()}`;
|
|
2014
|
-
const encoder = new TextEncoder();
|
|
2015
|
-
const signature = toBase64Url(await crypto.subtle.sign("HMAC", key, encoder.encode(dataToSign)));
|
|
2016
|
-
parsedUrl.searchParams.set("signature", signature);
|
|
2017
|
-
return url.startsWith("http") ? parsedUrl.toString() : `${parsedUrl.pathname}?${parsedUrl.searchParams.toString()}`;
|
|
2018
|
-
}
|
|
2045
|
+
//#region src/router/uri.ts
|
|
2019
2046
|
/**
|
|
2020
|
-
*
|
|
2047
|
+
* Encode a value for use as a path parameter.
|
|
2021
2048
|
*
|
|
2022
|
-
*
|
|
2023
|
-
*
|
|
2024
|
-
*
|
|
2049
|
+
* Splits on `/` and encodes each segment with `encodeURIComponent`, so callers
|
|
2050
|
+
* can pass slash-containing values for catch-all params (e.g. `:slug{.+}`) and
|
|
2051
|
+
* still get a usable URL — `'auth/login'` becomes `'auth/login'`, not
|
|
2052
|
+
* `'auth%2Flogin'`. Single segments behave exactly like `encodeURIComponent`.
|
|
2025
2053
|
*/
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
const signature = parsedUrl.searchParams.get("signature");
|
|
2029
|
-
if (!signature) return false;
|
|
2030
|
-
const expires = parsedUrl.searchParams.get("expires");
|
|
2031
|
-
if (expires) {
|
|
2032
|
-
const expiryTime = parseInt(expires, 10);
|
|
2033
|
-
if (isNaN(expiryTime) || Math.floor(Date.now() / 1e3) > expiryTime) return false;
|
|
2034
|
-
}
|
|
2035
|
-
parsedUrl.searchParams.delete("signature");
|
|
2036
|
-
const dataToVerify = `${parsedUrl.pathname}?${parsedUrl.searchParams.toString()}`;
|
|
2037
|
-
const base64 = signature.replace(/-/g, "+").replace(/_/g, "/");
|
|
2038
|
-
const binaryStr = atob(base64);
|
|
2039
|
-
const signatureBytes = new Uint8Array(binaryStr.length);
|
|
2040
|
-
for (let i = 0; i < binaryStr.length; i++) signatureBytes[i] = binaryStr.charCodeAt(i);
|
|
2041
|
-
const key = await importKey(secret);
|
|
2042
|
-
const encoder = new TextEncoder();
|
|
2043
|
-
return crypto.subtle.verify("HMAC", key, signatureBytes, encoder.encode(dataToVerify));
|
|
2054
|
+
function encodePathParam(value) {
|
|
2055
|
+
return value.split("/").map(encodeURIComponent).join("/");
|
|
2044
2056
|
}
|
|
2045
|
-
//#endregion
|
|
2046
|
-
//#region src/router/uri.ts
|
|
2047
2057
|
/**
|
|
2048
2058
|
* Build a URL from a registered route, filling path/domain params and appending extras as query string.
|
|
2049
2059
|
*
|
|
@@ -2067,7 +2077,7 @@ function buildRouteUrl(route, name, params) {
|
|
|
2067
2077
|
for (const paramName of route.paramNames) {
|
|
2068
2078
|
const value = allParams[paramName];
|
|
2069
2079
|
if (value === void 0) throw new MissingRouteParamError(paramName, name, route.path);
|
|
2070
|
-
url = url.replace(new RegExp(`:${paramName}(\\{[^}]*\\})?`),
|
|
2080
|
+
url = url.replace(new RegExp(`:${paramName}(\\{[^}]*\\})?`), encodePathParam(value));
|
|
2071
2081
|
consumedKeys.add(paramName);
|
|
2072
2082
|
}
|
|
2073
2083
|
let domain;
|
|
@@ -2090,9 +2100,11 @@ function buildRouteUrl(route, name, params) {
|
|
|
2090
2100
|
}
|
|
2091
2101
|
let Uri = class Uri {
|
|
2092
2102
|
_defaults = {};
|
|
2093
|
-
|
|
2103
|
+
trailingSlash;
|
|
2104
|
+
constructor(registry, routerContext, application) {
|
|
2094
2105
|
this.registry = registry;
|
|
2095
2106
|
this.routerContext = routerContext;
|
|
2107
|
+
this.trailingSlash = application.config.trailingSlash ?? "ignore";
|
|
2096
2108
|
}
|
|
2097
2109
|
/**
|
|
2098
2110
|
* Set default URL parameters for this request.
|
|
@@ -2107,6 +2119,16 @@ let Uri = class Uri {
|
|
|
2107
2119
|
};
|
|
2108
2120
|
}
|
|
2109
2121
|
/**
|
|
2122
|
+
* Read the currently configured default URL parameters.
|
|
2123
|
+
*
|
|
2124
|
+
* Used by frameworks that need to share these with the client (e.g. the
|
|
2125
|
+
* Inertia adapter ships them as a shared prop so `route()` calls in the
|
|
2126
|
+
* browser auto-fill the same sticky params as the server).
|
|
2127
|
+
*/
|
|
2128
|
+
getDefaults() {
|
|
2129
|
+
return { ...this._defaults };
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2110
2132
|
* Generate a URL from a named route.
|
|
2111
2133
|
*
|
|
2112
2134
|
* Keys matching `:param` placeholders fill the path.
|
|
@@ -2125,10 +2147,10 @@ let Uri = class Uri {
|
|
|
2125
2147
|
route(name, params, options) {
|
|
2126
2148
|
const registeredRoute = this.registry.get(name);
|
|
2127
2149
|
if (!registeredRoute) throw new RouteNameNotFoundError(name);
|
|
2128
|
-
let url = buildRouteUrl(registeredRoute, name, {
|
|
2150
|
+
let url = applyTrailingSlash(buildRouteUrl(registeredRoute, name, {
|
|
2129
2151
|
...this._defaults,
|
|
2130
2152
|
...params
|
|
2131
|
-
});
|
|
2153
|
+
}), this.trailingSlash);
|
|
2132
2154
|
if (options?.absolute && !url.startsWith("http")) url = `${new URL(this.routerContext.c.req.url).origin}${url}`;
|
|
2133
2155
|
return url;
|
|
2134
2156
|
}
|
|
@@ -2176,14 +2198,14 @@ let Uri = class Uri {
|
|
|
2176
2198
|
* Get the current request URL pathname (without query string).
|
|
2177
2199
|
*/
|
|
2178
2200
|
current() {
|
|
2179
|
-
return new URL(this.routerContext.c.req.url).pathname;
|
|
2201
|
+
return applyTrailingSlash(new URL(this.routerContext.c.req.url).pathname, this.trailingSlash);
|
|
2180
2202
|
}
|
|
2181
2203
|
/**
|
|
2182
2204
|
* Get the current request URL with query string (pathname + search).
|
|
2183
2205
|
*/
|
|
2184
2206
|
full() {
|
|
2185
2207
|
const parsed = new URL(this.routerContext.c.req.url);
|
|
2186
|
-
return `${parsed.pathname}${parsed.search}
|
|
2208
|
+
return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash);
|
|
2187
2209
|
}
|
|
2188
2210
|
/**
|
|
2189
2211
|
* Get the previous request URL from the Referer header.
|
|
@@ -2215,7 +2237,7 @@ let Uri = class Uri {
|
|
|
2215
2237
|
* @param options - URL generation options
|
|
2216
2238
|
*/
|
|
2217
2239
|
to(path, queryParams, options) {
|
|
2218
|
-
let url = path;
|
|
2240
|
+
let url = applyTrailingSlash(path, this.trailingSlash);
|
|
2219
2241
|
if (queryParams) {
|
|
2220
2242
|
const entries = Object.entries(queryParams);
|
|
2221
2243
|
if (entries.length > 0) {
|
|
@@ -2235,7 +2257,7 @@ let Uri = class Uri {
|
|
|
2235
2257
|
query(path, queryParams) {
|
|
2236
2258
|
const parsed = new URL(path, "https://placeholder.local");
|
|
2237
2259
|
for (const [key, value] of Object.entries(queryParams)) parsed.searchParams.set(key, value);
|
|
2238
|
-
return `${parsed.pathname}${parsed.search}
|
|
2260
|
+
return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash);
|
|
2239
2261
|
}
|
|
2240
2262
|
getAppSecret() {
|
|
2241
2263
|
const secret = this.routerContext.c.env.APP_SECRET;
|
|
@@ -2247,7 +2269,12 @@ Uri = __decorate([
|
|
|
2247
2269
|
Transient(),
|
|
2248
2270
|
__decorateParam(0, inject(ROUTER_TOKENS.RouteRegistry)),
|
|
2249
2271
|
__decorateParam(1, inject(ROUTER_TOKENS.RouterContext)),
|
|
2250
|
-
|
|
2272
|
+
__decorateParam(2, inject(DI_TOKENS.Application)),
|
|
2273
|
+
__decorateMetadata("design:paramtypes", [
|
|
2274
|
+
Object,
|
|
2275
|
+
Object,
|
|
2276
|
+
Object
|
|
2277
|
+
])
|
|
2251
2278
|
], Uri);
|
|
2252
2279
|
//#endregion
|
|
2253
2280
|
//#region src/router/route-url.ts
|
|
@@ -2277,9 +2304,12 @@ Uri = __decorate([
|
|
|
2277
2304
|
* ```
|
|
2278
2305
|
*/
|
|
2279
2306
|
function route(name, params) {
|
|
2280
|
-
const
|
|
2307
|
+
const container = getContainer();
|
|
2308
|
+
const registry = container.resolve(ROUTER_TOKENS.RouteRegistry);
|
|
2309
|
+
const application = container.resolve(DI_TOKENS.Application);
|
|
2310
|
+
const registeredRoute = registry.get(name);
|
|
2281
2311
|
if (!registeredRoute) throw new RouteNameNotFoundError(name);
|
|
2282
|
-
return buildRouteUrl(registeredRoute, name, params);
|
|
2312
|
+
return applyTrailingSlash(buildRouteUrl(registeredRoute, name, params), application.config.trailingSlash ?? "ignore");
|
|
2283
2313
|
}
|
|
2284
2314
|
//#endregion
|
|
2285
2315
|
//#region src/router/middleware/verify-signature.middleware.ts
|
|
@@ -2457,6 +2487,6 @@ I18nModule = _I18nModule = __decorate([Module({ providers: [
|
|
|
2457
2487
|
}
|
|
2458
2488
|
] })], I18nModule);
|
|
2459
2489
|
//#endregion
|
|
2460
|
-
export {
|
|
2490
|
+
export { OpenAPIModule as A, LocaleNotSupportedError as B, validationErrorResponseSchema as C, createMiddlewareChain as D, getRouteMetadata as E, getMessages as F, I18nContextMiddleware as H, messages as I, buildDetectorOptions as L, MessageRegistry as M, MessageLoaderService as N, createDomainMiddleware as O, getLocales as P, resolveI18nOptions as R, uuidParamSchema as S, getRouteDecoratedMethods as T, OpenAPIConfigService as V, commonErrorSchemas as _, buildRouteUrl as a, paginationQuerySchema as b, LocalePathService as c, extractDomainParamNames as d, extractParamNames as f, toOpenAPIPath as g, sortRoutesBySpecificity as h, Uri as i, OpenAPIService as j, parseDomainPattern as k, HonoApp as l, getPathSpecificityScore as m, VerifySignatureMiddleware as n, RouteRegistry as o, generateConventionRouteName as p, route as r, VersioningService as s, I18nModule as t, RouteRegistrationService as u, errorResponseSchema as v, Route as w, successMessageSchema as x, paginatedResponseSchema as y, TranslationMissingError as z };
|
|
2461
2491
|
|
|
2462
|
-
//# sourceMappingURL=i18n.module-
|
|
2492
|
+
//# sourceMappingURL=i18n.module-BBlNNlcG.mjs.map
|