@ailura/nestjs-hono-adapter 1.0.0
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/LICENSE +21 -0
- package/README.md +411 -0
- package/dist/body.d.ts +35 -0
- package/dist/body.d.ts.map +1 -0
- package/dist/body.js +180 -0
- package/dist/body.js.map +1 -0
- package/dist/bridge.d.ts +64 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +168 -0
- package/dist/bridge.js.map +1 -0
- package/dist/closing.d.ts +13 -0
- package/dist/closing.d.ts.map +1 -0
- package/dist/closing.js +30 -0
- package/dist/closing.js.map +1 -0
- package/dist/context.d.ts +22 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/cors-middleware.d.ts +64 -0
- package/dist/cors-middleware.d.ts.map +1 -0
- package/dist/cors-middleware.js +211 -0
- package/dist/cors-middleware.js.map +1 -0
- package/dist/handler-bridge.d.ts +51 -0
- package/dist/handler-bridge.d.ts.map +1 -0
- package/dist/handler-bridge.js +122 -0
- package/dist/handler-bridge.js.map +1 -0
- package/dist/hono-lifecycle.d.ts +90 -0
- package/dist/hono-lifecycle.d.ts.map +1 -0
- package/dist/hono-lifecycle.js +169 -0
- package/dist/hono-lifecycle.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/path.d.ts +3 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +144 -0
- package/dist/path.js.map +1 -0
- package/dist/query.d.ts +19 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +238 -0
- package/dist/query.js.map +1 -0
- package/dist/response-helpers.d.ts +16 -0
- package/dist/response-helpers.d.ts.map +1 -0
- package/dist/response-helpers.js +45 -0
- package/dist/response-helpers.js.map +1 -0
- package/dist/response-writer.d.ts +28 -0
- package/dist/response-writer.d.ts.map +1 -0
- package/dist/response-writer.js +52 -0
- package/dist/response-writer.js.map +1 -0
- package/dist/route-adapter.d.ts +53 -0
- package/dist/route-adapter.d.ts.map +1 -0
- package/dist/route-adapter.js +141 -0
- package/dist/route-adapter.js.map +1 -0
- package/dist/server-adapter.d.ts +106 -0
- package/dist/server-adapter.d.ts.map +1 -0
- package/dist/server-adapter.js +153 -0
- package/dist/server-adapter.js.map +1 -0
- package/dist/sse.d.ts +67 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +211 -0
- package/dist/sse.js.map +1 -0
- package/dist/static-assets.d.ts +39 -0
- package/dist/static-assets.d.ts.map +1 -0
- package/dist/static-assets.js +155 -0
- package/dist/static-assets.js.map +1 -0
- package/dist/version-filter.d.ts +24 -0
- package/dist/version-filter.d.ts.map +1 -0
- package/dist/version-filter.js +107 -0
- package/dist/version-filter.js.map +1 -0
- package/dist/versioned-route.d.ts +21 -0
- package/dist/versioned-route.d.ts.map +1 -0
- package/dist/versioned-route.js +15 -0
- package/dist/versioned-route.js.map +1 -0
- package/dist/views.d.ts +42 -0
- package/dist/views.d.ts.map +1 -0
- package/dist/views.js +110 -0
- package/dist/views.js.map +1 -0
- package/dist/ws-adapter.d.ts +81 -0
- package/dist/ws-adapter.d.ts.map +1 -0
- package/dist/ws-adapter.js +214 -0
- package/dist/ws-adapter.js.map +1 -0
- package/dist/ws-client.d.ts +68 -0
- package/dist/ws-client.d.ts.map +1 -0
- package/dist/ws-client.js +135 -0
- package/dist/ws-client.js.map +1 -0
- package/dist/ws-server.d.ts +23 -0
- package/dist/ws-server.d.ts.map +1 -0
- package/dist/ws-server.js +37 -0
- package/dist/ws-server.js.map +1 -0
- package/dist/ws.d.ts +14 -0
- package/dist/ws.d.ts.map +1 -0
- package/dist/ws.js +12 -0
- package/dist/ws.js.map +1 -0
- package/package.json +99 -0
- package/src/body.ts +251 -0
- package/src/bridge.ts +308 -0
- package/src/closing.ts +38 -0
- package/src/context.ts +25 -0
- package/src/cors-middleware.ts +347 -0
- package/src/handler-bridge.ts +226 -0
- package/src/hono-lifecycle.ts +259 -0
- package/src/index.ts +27 -0
- package/src/path.ts +169 -0
- package/src/query.ts +304 -0
- package/src/response-helpers.ts +60 -0
- package/src/response-writer.ts +100 -0
- package/src/route-adapter.ts +261 -0
- package/src/server-adapter.ts +294 -0
- package/src/sse.ts +274 -0
- package/src/static-assets.ts +247 -0
- package/src/version-filter.ts +170 -0
- package/src/versioned-route.ts +30 -0
- package/src/views.ts +188 -0
- package/src/ws-adapter.ts +329 -0
- package/src/ws-client.ts +190 -0
- package/src/ws-server.ts +40 -0
- package/src/ws.ts +13 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Next } from 'hono';
|
|
2
|
+
import type { NestHandler, NestRequest, NextHandler } from './bridge.ts';
|
|
3
|
+
import type { NestContext } from './context.ts';
|
|
4
|
+
/** The handler Hono runs for a route Nest registered. */
|
|
5
|
+
type HonoRouteHandler = (context: NestContext, next: Next) => Promise<Response>;
|
|
6
|
+
/**
|
|
7
|
+
* The handler Nest installs as its global exception layer. It
|
|
8
|
+
* takes the exception first, so it is not a route handler. The
|
|
9
|
+
* arguments are spelled as a tuple because their order is fixed
|
|
10
|
+
* by Nest rather than by this adapter.
|
|
11
|
+
*/
|
|
12
|
+
type NestExceptionHandler = (...args: [unknown, NestRequest, NestContext, NextHandler]) => unknown;
|
|
13
|
+
/** Runs the exception layer for one failed request. */
|
|
14
|
+
type ExceptionRunner = (error: unknown, context: NestContext) => Promise<Response>;
|
|
15
|
+
/**
|
|
16
|
+
* Everything the bridge needs to know about the adapter that
|
|
17
|
+
* built it. Each value is read per request rather than
|
|
18
|
+
* captured, because a deployment configures the parsers and the
|
|
19
|
+
* proxy behind the adapter after the routes exist.
|
|
20
|
+
*/
|
|
21
|
+
interface BridgeOptions {
|
|
22
|
+
readonly bodyLimit: () => number | undefined;
|
|
23
|
+
readonly bodyParsingEnabled: () => boolean;
|
|
24
|
+
readonly pendingStatus: (context: NestContext) => number | undefined;
|
|
25
|
+
readonly rawBody: () => boolean;
|
|
26
|
+
readonly trustProxy: () => boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Bridges one Nest handler to Hono: it builds the request Nest
|
|
30
|
+
* reads, runs the handler and hands back the response the
|
|
31
|
+
* adapter collected.
|
|
32
|
+
*
|
|
33
|
+
* An `@Sse()` route answers only when its observable completes,
|
|
34
|
+
* which is exactly what a long-lived stream never does, so the
|
|
35
|
+
* handler is raced against the stream it starts. The moment the
|
|
36
|
+
* stream commits its headers the response goes out, and the
|
|
37
|
+
* handler is left writing into it — with a late failure logged
|
|
38
|
+
* instead of escaping.
|
|
39
|
+
*/
|
|
40
|
+
declare function createRouteHandler(handler: NestHandler, options: BridgeOptions): HonoRouteHandler;
|
|
41
|
+
declare function runNestHandler(handler: NestHandler, context: NestContext, options: BridgeOptions): Promise<Response>;
|
|
42
|
+
/**
|
|
43
|
+
* Builds the runner for the exception layer. The payload is not
|
|
44
|
+
* read again here: the failure may well be that reading it
|
|
45
|
+
* failed, and a second attempt would replace the answer with a
|
|
46
|
+
* failure of its own.
|
|
47
|
+
*/
|
|
48
|
+
declare function createExceptionRunner(handler: NestExceptionHandler, options: BridgeOptions): ExceptionRunner;
|
|
49
|
+
export { createExceptionRunner, createRouteHandler, runNestHandler, };
|
|
50
|
+
export type { BridgeOptions, ExceptionRunner, NestExceptionHandler, };
|
|
51
|
+
//# sourceMappingURL=handler-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler-bridge.d.ts","sourceRoot":"","sources":["../src/handler-bridge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAIjC,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,WAAW,EAEZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AA2BhD,yDAAyD;AACzD,KAAK,gBAAgB,GAAG,CACtB,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,IAAI,KACP,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB;;;;;GAKG;AACH,KAAK,oBAAoB,GAAG,CAC1B,GAAG,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC,KACtD,OAAO,CAAC;AAEb,uDAAuD;AACvD,KAAK,eAAe,GAAG,CACrB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,WAAW,KACjB,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB;;;;;GAKG;AACH,UAAU,aAAa;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAC3C,QAAQ,CAAC,aAAa,EAAE,CACtB,OAAO,EAAE,WAAW,KACjB,MAAM,GAAG,SAAS,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC;CACpC;AA+ED;;;;;;;;;;;GAWG;AACH,iBAAS,kBAAkB,CACzB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,aAAa,GACrB,gBAAgB,CAelB;AAED,iBAAe,cAAc,CAC3B,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,QAAQ,CAAC,CAInB;AAED;;;;;GAKG;AACH,iBAAS,qBAAqB,CAC5B,OAAO,EAAE,oBAAoB,EAC7B,OAAO,EAAE,aAAa,GACrB,eAAe,CAUjB;AAED,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,GACf,CAAC;AACF,YAAY,EACV,aAAa,EACb,eAAe,EACf,oBAAoB,GACrB,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Logger } from '@nestjs/common';
|
|
2
|
+
import { readBody } from './body.js';
|
|
3
|
+
import { toNestRequest } from './bridge.js';
|
|
4
|
+
import { finalizeOnResponse } from './response-helpers.js';
|
|
5
|
+
import { mountSse } from './sse.js';
|
|
6
|
+
/**
|
|
7
|
+
* Called when a handler runs outside a Hono pipeline, where
|
|
8
|
+
* there is no next handler to continue to.
|
|
9
|
+
*/
|
|
10
|
+
const NOOP_NEXT = () => Promise.resolve();
|
|
11
|
+
/** Which of the two things a route handler races settled first. */
|
|
12
|
+
const HANDLED = 'handled';
|
|
13
|
+
const STREAMING = 'streaming';
|
|
14
|
+
/** Says a handler failed after the stream it started was sent. */
|
|
15
|
+
const logger = new Logger('SseRoute');
|
|
16
|
+
/** The message of a failure, whatever shape it was thrown in. */
|
|
17
|
+
function describe(error) {
|
|
18
|
+
if (error instanceof Error) {
|
|
19
|
+
return error.message;
|
|
20
|
+
}
|
|
21
|
+
return String(error);
|
|
22
|
+
}
|
|
23
|
+
function requestOptions(options) {
|
|
24
|
+
return { trustProxy: options.trustProxy() };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Builds the request Nest reads and the parser fills.
|
|
28
|
+
*
|
|
29
|
+
* Nest writes to the request bag, so the parsed payload is
|
|
30
|
+
* copied into it here instead of being passed along separately.
|
|
31
|
+
* A payload that cannot be parsed throws the exception Nest
|
|
32
|
+
* raises for a failed parse, which is what lets the exception
|
|
33
|
+
* layer answer it like any other failure.
|
|
34
|
+
*/
|
|
35
|
+
async function prepareRequest(context, options) {
|
|
36
|
+
const request = toNestRequest(context, requestOptions(options));
|
|
37
|
+
finalizeOnResponse(context);
|
|
38
|
+
if (!options.bodyParsingEnabled()) {
|
|
39
|
+
return request;
|
|
40
|
+
}
|
|
41
|
+
const parsed = await readBody(context, {
|
|
42
|
+
bodyLimit: options.bodyLimit(),
|
|
43
|
+
rawBody: options.rawBody(),
|
|
44
|
+
});
|
|
45
|
+
request.body = parsed.body;
|
|
46
|
+
request.files = parsed.files;
|
|
47
|
+
request.rawBody = parsed.rawBody;
|
|
48
|
+
return request;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Waits for a promise and says which of the two settled, so the
|
|
52
|
+
* race below cannot confuse a handler that answered with
|
|
53
|
+
* nothing and a stream that started.
|
|
54
|
+
*/
|
|
55
|
+
async function settledAs(promise, outcome) {
|
|
56
|
+
await promise;
|
|
57
|
+
return outcome;
|
|
58
|
+
}
|
|
59
|
+
function firstOutcome(handled, streaming) {
|
|
60
|
+
return Promise.race([
|
|
61
|
+
settledAs(handled, HANDLED),
|
|
62
|
+
settledAs(streaming, STREAMING),
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Reports a failure that arrived after the stream it opened was
|
|
67
|
+
* answered: the client already has its `200`, so there is
|
|
68
|
+
* nothing left to send it to.
|
|
69
|
+
*/
|
|
70
|
+
async function watchFailure(handled) {
|
|
71
|
+
try {
|
|
72
|
+
await handled;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
logger.error(`A handler failed after its event stream started: ${describe(error)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Bridges one Nest handler to Hono: it builds the request Nest
|
|
80
|
+
* reads, runs the handler and hands back the response the
|
|
81
|
+
* adapter collected.
|
|
82
|
+
*
|
|
83
|
+
* An `@Sse()` route answers only when its observable completes,
|
|
84
|
+
* which is exactly what a long-lived stream never does, so the
|
|
85
|
+
* handler is raced against the stream it starts. The moment the
|
|
86
|
+
* stream commits its headers the response goes out, and the
|
|
87
|
+
* handler is left writing into it — with a late failure logged
|
|
88
|
+
* instead of escaping.
|
|
89
|
+
*/
|
|
90
|
+
function createRouteHandler(handler, options) {
|
|
91
|
+
return async (context, next) => {
|
|
92
|
+
const request = await prepareRequest(context, options);
|
|
93
|
+
const started = mountSse(context, () => options.pendingStatus(context));
|
|
94
|
+
const handled = Promise.resolve(handler(request, context, next));
|
|
95
|
+
const outcome = await firstOutcome(handled, started);
|
|
96
|
+
if (outcome === STREAMING) {
|
|
97
|
+
void watchFailure(handled);
|
|
98
|
+
}
|
|
99
|
+
return context.res;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async function runNestHandler(handler, context, options) {
|
|
103
|
+
const request = await prepareRequest(context, options);
|
|
104
|
+
await handler(request, context, NOOP_NEXT);
|
|
105
|
+
return context.res;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Builds the runner for the exception layer. The payload is not
|
|
109
|
+
* read again here: the failure may well be that reading it
|
|
110
|
+
* failed, and a second attempt would replace the answer with a
|
|
111
|
+
* failure of its own.
|
|
112
|
+
*/
|
|
113
|
+
function createExceptionRunner(handler, options) {
|
|
114
|
+
return async (error, context) => {
|
|
115
|
+
const request = toNestRequest(context, requestOptions(options));
|
|
116
|
+
finalizeOnResponse(context);
|
|
117
|
+
await handler(error, request, context, NOOP_NEXT);
|
|
118
|
+
return context.res;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export { createExceptionRunner, createRouteHandler, runNestHandler, };
|
|
122
|
+
//# sourceMappingURL=handler-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler-bridge.js","sourceRoot":"","sources":["../src/handler-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { NestApplicationOptions } from '@nestjs/common';
|
|
2
|
+
import { secureHeaders } from 'hono/secure-headers';
|
|
3
|
+
import type { NestHono } from './context.ts';
|
|
4
|
+
import type { CorsOptions } from './cors-middleware.ts';
|
|
5
|
+
import { RouteAdapter } from './route-adapter.ts';
|
|
6
|
+
/** The security headers Hono is configured with. */
|
|
7
|
+
type SecureHeadersOptions = NonNullable<Parameters<typeof secureHeaders>[0]>;
|
|
8
|
+
/** The transport options the lifecycle reads. */
|
|
9
|
+
interface TransportOptions {
|
|
10
|
+
/** Largest body accepted, in bytes; `0` accepts any size. */
|
|
11
|
+
readonly bodyLimit?: number;
|
|
12
|
+
/** Whether `req.rawBody` keeps the bytes of every body. */
|
|
13
|
+
readonly rawBody?: boolean;
|
|
14
|
+
/** The security headers to send, or `false` to send none. */
|
|
15
|
+
readonly secureHeaders?: boolean | SecureHeadersOptions;
|
|
16
|
+
/** Whether `X-Forwarded-*` headers come from a proxy. */
|
|
17
|
+
readonly trustProxy?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Runs Nest on Hono: this owns the Hono application, the
|
|
21
|
+
* middleware every request passes through, and the Node server
|
|
22
|
+
* the fetch handler is served on. The routes Nest registers and
|
|
23
|
+
* the answers it writes are the business of the class below.
|
|
24
|
+
*
|
|
25
|
+
* The transport options Nest passes in are honoured: requests
|
|
26
|
+
* are served over TLS when `httpsOptions` is set, connections
|
|
27
|
+
* are dropped on shutdown when `forceCloseConnections` is set,
|
|
28
|
+
* and a request that arrives while the application is closing
|
|
29
|
+
* is answered with `503` when `return503OnClosing` is set.
|
|
30
|
+
*/
|
|
31
|
+
declare abstract class HonoLifecycle extends RouteAdapter {
|
|
32
|
+
protected readonly hono: NestHono;
|
|
33
|
+
protected readonly trustProxy: boolean;
|
|
34
|
+
protected bodyLimit: number;
|
|
35
|
+
protected bodyParsingEnabled: boolean;
|
|
36
|
+
protected rawBodyEnabled: boolean;
|
|
37
|
+
protected corsOptions: CorsOptions | undefined;
|
|
38
|
+
private closing;
|
|
39
|
+
private forceCloseConnections;
|
|
40
|
+
private return503OnClosing;
|
|
41
|
+
protected constructor(options?: TransportOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Identifier ecosystem packages branch on. It is the only
|
|
44
|
+
* value Nest itself does not consume.
|
|
45
|
+
*/
|
|
46
|
+
getType(): string;
|
|
47
|
+
/**
|
|
48
|
+
* The Hono application behind the adapter, for the middleware
|
|
49
|
+
* and routes only Hono knows how to express. Registering on
|
|
50
|
+
* it before the application listens puts them ahead of Nest's
|
|
51
|
+
* routes, after the headers and CORS this adapter installs.
|
|
52
|
+
*/
|
|
53
|
+
getHono(): NestHono;
|
|
54
|
+
/**
|
|
55
|
+
* Says that a Hono router scores its routes rather than
|
|
56
|
+
* matching them in the order they were added, so two routes
|
|
57
|
+
* cannot shadow each other and Nest does not have to sort
|
|
58
|
+
* them.
|
|
59
|
+
*/
|
|
60
|
+
isRouteOrderSensitive(): boolean;
|
|
61
|
+
/** Reads the shutdown options Nest hands the adapter. */
|
|
62
|
+
private readShutdownOptions;
|
|
63
|
+
/**
|
|
64
|
+
* Creates the Node server Hono's fetch handler is served
|
|
65
|
+
* through. Hono has no listener of its own, so `listen()` and
|
|
66
|
+
* `close()` operate on the value built here.
|
|
67
|
+
*/
|
|
68
|
+
initHttpServer(options: NestApplicationOptions): void;
|
|
69
|
+
listen(port: string | number, callback?: () => void): void;
|
|
70
|
+
listen(port: string | number, hostname: string, callback?: () => void): void;
|
|
71
|
+
/**
|
|
72
|
+
* Stops the server and, when the application asked for it,
|
|
73
|
+
* the connections it is still holding open. A server that
|
|
74
|
+
* never started listening has nothing to close, so the error
|
|
75
|
+
* it reports then is not a failure worth propagating.
|
|
76
|
+
*/
|
|
77
|
+
close(): Promise<void>;
|
|
78
|
+
/** Records that the payload's bytes have to be kept. */
|
|
79
|
+
protected keepRawBody(rawBody?: boolean): void;
|
|
80
|
+
/**
|
|
81
|
+
* Installs the security headers Hono sends with every answer,
|
|
82
|
+
* unless the deployment turned them off. They come from Hono
|
|
83
|
+
* rather than from Helmet, which is Express middleware: the
|
|
84
|
+
* families of headers are the same.
|
|
85
|
+
*/
|
|
86
|
+
private installSecurityHeaders;
|
|
87
|
+
}
|
|
88
|
+
export { HonoLifecycle };
|
|
89
|
+
export type { TransportOptions };
|
|
90
|
+
//# sourceMappingURL=hono-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono-lifecycle.d.ts","sourceRoot":"","sources":["../src/hono-lifecycle.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,KAAK,EAAE,QAAQ,EAAW,MAAM,cAAc,CAAC;AAEtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAelD,oDAAoD;AACpD,KAAK,oBAAoB,GAAG,WAAW,CACrC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CACpC,CAAC;AAKF,iDAAiD;AACjD,UAAU,gBAAgB;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;IACxD,yDAAyD;IACzD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAmBD;;;;;;;;;;;GAWG;AACH,uBAAe,aAAc,SAAQ,YAAY;IAC/C,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAClC,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IACvC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,kBAAkB,UAAS;IACrC,SAAS,CAAC,cAAc,EAAE,OAAO,CAAC;IAClC,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;IAC/C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,kBAAkB,CAAS;IAEnC,SAAS,aAAa,OAAO,GAAE,gBAAqB,EAmBnD;IAED;;;OAGG;IACa,OAAO,IAAI,MAAM,CAEhC;IAED;;;;;OAKG;IACI,OAAO,IAAI,QAAQ,CAEzB;IAED;;;;;OAKG;IACI,qBAAqB,IAAI,OAAO,CAEtC;IAED,yDAAyD;IACzD,OAAO,CAAC,mBAAmB;IAU3B;;;;OAIG;IACa,cAAc,CAC5B,OAAO,EAAE,sBAAsB,GAC9B,IAAI,CAkBN;IAEe,MAAM,CACpB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,GACpB,IAAI,CAAC;IACQ,MAAM,CACpB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,IAAI,GACpB,IAAI,CAAC;IAyBR;;;;;OAKG;IACmB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAe3C;IAED,wDAAwD;IACxD,SAAS,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAI7C;IAED;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;CAW/B;AAED,OAAO,EAAE,aAAa,EAAE,CAAC;AACzB,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { createServer as createHttpsServer } from 'node:https';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { createAdaptorServer } from '@hono/node-server';
|
|
4
|
+
import { Hono } from 'hono';
|
|
5
|
+
import { secureHeaders } from 'hono/secure-headers';
|
|
6
|
+
import { closingBridge } from './closing.js';
|
|
7
|
+
import { corsBridge } from './cors-middleware.js';
|
|
8
|
+
import { RouteAdapter } from './route-adapter.js';
|
|
9
|
+
/** The size a request body may reach before it is refused. */
|
|
10
|
+
const DEFAULT_BODY_LIMIT = 1_048_576;
|
|
11
|
+
/** Destroys the connections a server is still holding open. */
|
|
12
|
+
function closeConnections(server) {
|
|
13
|
+
if ('closeAllConnections' in server) {
|
|
14
|
+
server.closeAllConnections();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Says whether a close failure only means the server never ran. */
|
|
18
|
+
function isNotRunning(error) {
|
|
19
|
+
return (typeof error === 'object' &&
|
|
20
|
+
error !== null &&
|
|
21
|
+
'code' in error &&
|
|
22
|
+
error.code === 'ERR_SERVER_NOT_RUNNING');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Runs Nest on Hono: this owns the Hono application, the
|
|
26
|
+
* middleware every request passes through, and the Node server
|
|
27
|
+
* the fetch handler is served on. The routes Nest registers and
|
|
28
|
+
* the answers it writes are the business of the class below.
|
|
29
|
+
*
|
|
30
|
+
* The transport options Nest passes in are honoured: requests
|
|
31
|
+
* are served over TLS when `httpsOptions` is set, connections
|
|
32
|
+
* are dropped on shutdown when `forceCloseConnections` is set,
|
|
33
|
+
* and a request that arrives while the application is closing
|
|
34
|
+
* is answered with `503` when `return503OnClosing` is set.
|
|
35
|
+
*/
|
|
36
|
+
class HonoLifecycle extends RouteAdapter {
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
const hono = new Hono();
|
|
39
|
+
super(hono);
|
|
40
|
+
this.bodyParsingEnabled = false;
|
|
41
|
+
this.closing = false;
|
|
42
|
+
this.forceCloseConnections = false;
|
|
43
|
+
this.return503OnClosing = false;
|
|
44
|
+
this.hono = hono;
|
|
45
|
+
this.bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT;
|
|
46
|
+
this.rawBodyEnabled = options.rawBody ?? false;
|
|
47
|
+
this.trustProxy = options.trustProxy ?? false;
|
|
48
|
+
this.installSecurityHeaders(options.secureHeaders ?? true);
|
|
49
|
+
// CORS runs first, so a 503 sent while closing is readable.
|
|
50
|
+
hono.use('*', corsBridge(() => this.corsOptions));
|
|
51
|
+
hono.use('*', closingBridge(() => this.return503OnClosing && this.closing));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Identifier ecosystem packages branch on. It is the only
|
|
55
|
+
* value Nest itself does not consume.
|
|
56
|
+
*/
|
|
57
|
+
getType() {
|
|
58
|
+
return 'hono';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The Hono application behind the adapter, for the middleware
|
|
62
|
+
* and routes only Hono knows how to express. Registering on
|
|
63
|
+
* it before the application listens puts them ahead of Nest's
|
|
64
|
+
* routes, after the headers and CORS this adapter installs.
|
|
65
|
+
*/
|
|
66
|
+
getHono() {
|
|
67
|
+
return this.hono;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Says that a Hono router scores its routes rather than
|
|
71
|
+
* matching them in the order they were added, so two routes
|
|
72
|
+
* cannot shadow each other and Nest does not have to sort
|
|
73
|
+
* them.
|
|
74
|
+
*/
|
|
75
|
+
isRouteOrderSensitive() {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
/** Reads the shutdown options Nest hands the adapter. */
|
|
79
|
+
readShutdownOptions(options) {
|
|
80
|
+
const closing = options;
|
|
81
|
+
this.forceCloseConnections =
|
|
82
|
+
options.forceCloseConnections ?? false;
|
|
83
|
+
this.return503OnClosing =
|
|
84
|
+
closing.return503OnClosing ?? false;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Creates the Node server Hono's fetch handler is served
|
|
88
|
+
* through. Hono has no listener of its own, so `listen()` and
|
|
89
|
+
* `close()` operate on the value built here.
|
|
90
|
+
*/
|
|
91
|
+
initHttpServer(options) {
|
|
92
|
+
this.readShutdownOptions(options);
|
|
93
|
+
if (options.rawBody === true) {
|
|
94
|
+
this.rawBodyEnabled = true;
|
|
95
|
+
}
|
|
96
|
+
const certificate = options.httpsOptions;
|
|
97
|
+
if (certificate === undefined) {
|
|
98
|
+
this.setHttpServer(createAdaptorServer({ fetch: this.hono.fetch }));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const adaptorOptions = {
|
|
102
|
+
createServer: createHttpsServer,
|
|
103
|
+
fetch: this.hono.fetch,
|
|
104
|
+
serverOptions: certificate,
|
|
105
|
+
};
|
|
106
|
+
this.setHttpServer(createAdaptorServer(adaptorOptions));
|
|
107
|
+
}
|
|
108
|
+
listen(port, hostnameOrCallback, callback) {
|
|
109
|
+
if (typeof port === 'string') {
|
|
110
|
+
this.httpServer.listen(port, callback);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (typeof hostnameOrCallback === 'function') {
|
|
114
|
+
this.httpServer.listen(port, hostnameOrCallback);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (typeof hostnameOrCallback === 'string') {
|
|
118
|
+
this.httpServer.listen(port, hostnameOrCallback, callback);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.httpServer.listen(port, callback);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Stops the server and, when the application asked for it,
|
|
125
|
+
* the connections it is still holding open. A server that
|
|
126
|
+
* never started listening has nothing to close, so the error
|
|
127
|
+
* it reports then is not a failure worth propagating.
|
|
128
|
+
*/
|
|
129
|
+
async close() {
|
|
130
|
+
this.closing = true;
|
|
131
|
+
const { httpServer } = this;
|
|
132
|
+
if (this.forceCloseConnections) {
|
|
133
|
+
closeConnections(httpServer);
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await promisify((done) => {
|
|
137
|
+
httpServer.close(done);
|
|
138
|
+
})();
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (!isNotRunning(error)) {
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Records that the payload's bytes have to be kept. */
|
|
147
|
+
keepRawBody(rawBody) {
|
|
148
|
+
if (rawBody === true) {
|
|
149
|
+
this.rawBodyEnabled = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Installs the security headers Hono sends with every answer,
|
|
154
|
+
* unless the deployment turned them off. They come from Hono
|
|
155
|
+
* rather than from Helmet, which is Express middleware: the
|
|
156
|
+
* families of headers are the same.
|
|
157
|
+
*/
|
|
158
|
+
installSecurityHeaders(headers) {
|
|
159
|
+
if (headers === true) {
|
|
160
|
+
this.hono.use('*', secureHeaders());
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (headers !== false) {
|
|
164
|
+
this.hono.use('*', secureHeaders(headers));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export { HonoLifecycle };
|
|
169
|
+
//# sourceMappingURL=hono-lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono-lifecycle.js","sourceRoot":"","sources":["../src/hono-lifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public surface of the adapter: the class a bootstrap
|
|
3
|
+
* hands to `NestFactory.create`, the options it and
|
|
4
|
+
* `enableCors` accept, and the types a handler or a middleware
|
|
5
|
+
* needs to describe what it touches.
|
|
6
|
+
*/
|
|
7
|
+
export { ServerAdapter } from './server-adapter.ts';
|
|
8
|
+
export type { ParsedBody } from './body.ts';
|
|
9
|
+
export type { NestHandler, NestRequest, NextHandler, } from './bridge.ts';
|
|
10
|
+
export type { NestContext, NestHono, NodeEnv, } from './context.ts';
|
|
11
|
+
export type { CorsOptions } from './cors-middleware.ts';
|
|
12
|
+
export type { ParsedQuery } from './query.ts';
|
|
13
|
+
export type { StaticAssetsOptions } from './static-assets.ts';
|
|
14
|
+
export type { ViewData, ViewEngine, ViewOptions, } from './views.ts';
|
|
15
|
+
export type { ServerAdapterOptions } from './server-adapter.ts';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,YAAY,EACV,WAAW,EACX,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,WAAW,EACX,QAAQ,EACR,OAAO,GACR,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,YAAY,EACV,QAAQ,EACR,UAAU,EACV,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public surface of the adapter: the class a bootstrap
|
|
3
|
+
* hands to `NestFactory.create`, the options it and
|
|
4
|
+
* `enableCors` accept, and the types a handler or a middleware
|
|
5
|
+
* needs to describe what it touches.
|
|
6
|
+
*/
|
|
7
|
+
export { ServerAdapter } from './server-adapter.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/path.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAoKA,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/path.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A parameter name, which both dialects write the same way. The
|
|
3
|
+
* whole match is the name, so the expression captures nothing.
|
|
4
|
+
*/
|
|
5
|
+
const PARAMETER = /^[A-Za-z0-9_]+/u;
|
|
6
|
+
/** The name of a wildcard, which the v8 syntax spells `*rest`. */
|
|
7
|
+
const WILDCARD = /^[A-Za-z0-9_]+/u;
|
|
8
|
+
/** An optional segment, which the v8 syntax spells `{/:id}`. */
|
|
9
|
+
const OPTIONAL_SEGMENT = /^:(?<name>[A-Za-z0-9_]+)(?:\((?<constraint>[^()]*)\))?\??$/u;
|
|
10
|
+
/** The characters this translator has no meaning for. */
|
|
11
|
+
const REFUSED = new Set(['}', '(', ')']);
|
|
12
|
+
const MISSING = -1;
|
|
13
|
+
function unsupported(path) {
|
|
14
|
+
return new TypeError(`The route path "${path}" is not one the Hono adapter ` +
|
|
15
|
+
'can translate. Write it the way Hono reads it: ' +
|
|
16
|
+
'`:name`, `:name?`, `:name{regex}`, `{/:name}` or `*`.');
|
|
17
|
+
}
|
|
18
|
+
function withoutLeadingSlash(content) {
|
|
19
|
+
if (content.startsWith('/')) {
|
|
20
|
+
return content.slice(1);
|
|
21
|
+
}
|
|
22
|
+
return content;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Translates one optional segment. Hono can only make a single
|
|
26
|
+
* parameter optional, so a group that holds a literal or more
|
|
27
|
+
* than one segment is refused instead of quietly ignored.
|
|
28
|
+
*/
|
|
29
|
+
function translateGroup(content, path) {
|
|
30
|
+
const match = OPTIONAL_SEGMENT.exec(withoutLeadingSlash(content));
|
|
31
|
+
if (match === null) {
|
|
32
|
+
throw unsupported(path);
|
|
33
|
+
}
|
|
34
|
+
const { constraint, name } = match.groups ?? {
|
|
35
|
+
constraint: undefined,
|
|
36
|
+
name: '',
|
|
37
|
+
};
|
|
38
|
+
if (constraint === undefined) {
|
|
39
|
+
return `/:${name}?`;
|
|
40
|
+
}
|
|
41
|
+
return `/:${name}{${constraint}}?`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Rewrites one route path into the dialect Hono's router reads.
|
|
45
|
+
*
|
|
46
|
+
* The two differ in three places: a constraint is written
|
|
47
|
+
* `:id(\\d+)` rather than `:id{\\d+}`, an optional segment is
|
|
48
|
+
* written `{/:id}` rather than `/:id?`, and a named wildcard is
|
|
49
|
+
* written `*rest` rather than `*`. Anything else is refused
|
|
50
|
+
* with the path that caused it, because a route that is
|
|
51
|
+
* registered but never matches is worse than one that fails at
|
|
52
|
+
* startup.
|
|
53
|
+
*/
|
|
54
|
+
class PathTranslator {
|
|
55
|
+
constructor(path) {
|
|
56
|
+
this.index = 0;
|
|
57
|
+
this.translated = '';
|
|
58
|
+
this.path = path;
|
|
59
|
+
}
|
|
60
|
+
translate() {
|
|
61
|
+
while (this.index < this.path.length) {
|
|
62
|
+
this.step();
|
|
63
|
+
}
|
|
64
|
+
return this.translated;
|
|
65
|
+
}
|
|
66
|
+
step() {
|
|
67
|
+
const character = this.path.charAt(this.index);
|
|
68
|
+
if (REFUSED.has(character)) {
|
|
69
|
+
throw unsupported(this.path);
|
|
70
|
+
}
|
|
71
|
+
if (this.readSpecial(character)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.emit(character);
|
|
75
|
+
this.index += 1;
|
|
76
|
+
}
|
|
77
|
+
/** Copies a character, or handles the ones that mean more. */
|
|
78
|
+
readSpecial(character) {
|
|
79
|
+
if (character === '{') {
|
|
80
|
+
this.readGroup();
|
|
81
|
+
}
|
|
82
|
+
else if (character === ':') {
|
|
83
|
+
this.readParameter();
|
|
84
|
+
}
|
|
85
|
+
else if (character === '*') {
|
|
86
|
+
this.readWildcard();
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
emit(text) {
|
|
94
|
+
this.translated += text;
|
|
95
|
+
}
|
|
96
|
+
readGroup() {
|
|
97
|
+
const end = this.path.indexOf('}', this.index);
|
|
98
|
+
if (end === MISSING) {
|
|
99
|
+
throw unsupported(this.path);
|
|
100
|
+
}
|
|
101
|
+
const content = this.path.slice(this.index + 1, end);
|
|
102
|
+
this.emit(translateGroup(content, this.path));
|
|
103
|
+
this.index = end + 1;
|
|
104
|
+
}
|
|
105
|
+
readParameter() {
|
|
106
|
+
const match = PARAMETER.exec(this.path.slice(this.index + 1));
|
|
107
|
+
if (match === null) {
|
|
108
|
+
this.emit(':');
|
|
109
|
+
this.index += 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const [name] = match;
|
|
113
|
+
this.index += name.length + 1;
|
|
114
|
+
this.closeParameter(name);
|
|
115
|
+
}
|
|
116
|
+
closeParameter(name) {
|
|
117
|
+
if (this.path.charAt(this.index) !== '(') {
|
|
118
|
+
this.emit(`:${name}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const end = this.path.indexOf(')', this.index);
|
|
122
|
+
if (end === MISSING) {
|
|
123
|
+
throw unsupported(this.path);
|
|
124
|
+
}
|
|
125
|
+
const constraint = this.path.slice(this.index + 1, end);
|
|
126
|
+
this.emit(`:${name}{${constraint}}`);
|
|
127
|
+
this.index = end + 1;
|
|
128
|
+
}
|
|
129
|
+
readWildcard() {
|
|
130
|
+
const match = WILDCARD.exec(this.path.slice(this.index + 1));
|
|
131
|
+
this.emit('*');
|
|
132
|
+
if (match === null) {
|
|
133
|
+
this.index += 1;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const [name] = match;
|
|
137
|
+
this.index += name.length + 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function toHonoPath(path) {
|
|
141
|
+
return new PathTranslator(path).translate();
|
|
142
|
+
}
|
|
143
|
+
export { toHonoPath };
|
|
144
|
+
//# sourceMappingURL=path.js.map
|
package/dist/path.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"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"}
|