@nestia/fetcher 12.0.0 → 12.1.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/lib/NestiaSimulator.js +2 -1
- package/lib/NestiaSimulator.js.map +1 -1
- package/lib/NestiaSimulator.mjs +2 -1
- package/lib/NestiaSimulator.mjs.map +1 -1
- package/lib/internal/FetcherBase.js +14 -8
- package/lib/internal/FetcherBase.js.map +1 -1
- package/lib/internal/FetcherBase.mjs +12 -4
- package/lib/internal/FetcherBase.mjs.map +1 -1
- package/lib/internal/join_host_and_path.d.ts +1 -0
- package/lib/internal/join_host_and_path.js +47 -0
- package/lib/internal/join_host_and_path.js.map +1 -0
- package/lib/internal/join_host_and_path.mjs +44 -0
- package/lib/internal/join_host_and_path.mjs.map +1 -0
- package/package.json +3 -3
- package/src/NestiaSimulator.ts +2 -1
- package/src/internal/FetcherBase.ts +16 -9
- package/src/internal/join_host_and_path.ts +41 -0
package/lib/NestiaSimulator.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.NestiaSimulator = void 0;
|
|
4
4
|
const HttpError_1 = require("./HttpError");
|
|
5
|
+
const join_host_and_path_1 = require("./internal/join_host_and_path");
|
|
5
6
|
var NestiaSimulator;
|
|
6
7
|
(function (NestiaSimulator) {
|
|
7
8
|
NestiaSimulator.assert = (props) => {
|
|
@@ -22,7 +23,7 @@ var NestiaSimulator;
|
|
|
22
23
|
}
|
|
23
24
|
catch (exp) {
|
|
24
25
|
if (isTypeGuardError(exp))
|
|
25
|
-
throw new HttpError_1.HttpError(props.method, props.host
|
|
26
|
+
throw new HttpError_1.HttpError(props.method, (0, join_host_and_path_1.join_host_and_path)(props.host, props.path), 400, {
|
|
26
27
|
"Content-Type": props.contentType,
|
|
27
28
|
}, JSON.stringify({
|
|
28
29
|
method: exp.method,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NestiaSimulator.js","sourceRoot":"","sources":["../src/NestiaSimulator.ts"],"names":[],"mappings":";;;AAAA,2CAAwC;
|
|
1
|
+
{"version":3,"file":"NestiaSimulator.js","sourceRoot":"","sources":["../src/NestiaSimulator.ts"],"names":[],"mappings":";;;AAAA,2CAAwC;AACxC,sEAAmE;AAEnE,IAAiB,eAAe,CAgE/B;AAhED,WAAiB,eAAe;IAQjB,sBAAM,GAAG,CAAC,KAAa,EAAE,EAAE;QACtC,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC;YACnB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC;YACnB,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;SAClB,CAAC;IACJ,CAAC,CAAC;IACF,MAAM,KAAK,GACT,CAAC,KAAa,EAAE,EAAE,CAClB,CAAC,IAAY,EAAE,EAAE,CACjB,CAAI,IAAa,EAAQ,EAAE;QACzB,QAAQ,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,kBAAkB,IAAI,YAAY,GAAG,CAAC,QAAQ,QAAQ,CAAC,CACvE,KAAK,CACN,CAAC,IAAI,CAAC,CAAC;IACV,CAAC,CAAC;IAEJ,MAAM,KAAK,GACT,CAAC,KAAa,EAAE,EAAE,CAClB,CAAI,IAAa,EAAQ,EAAE,CACzB,QAAQ,CACN,GAAG,EAAE,CAAC,+DAA+D,CACtE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;IAEnB,MAAM,IAAI,GACR,CAAC,KAAa,EAAE,EAAE,CAClB,CAAI,IAAa,EAAQ,EAAE,CACzB,QAAQ,CAAC,GAAG,EAAE,CAAC,kDAAkD,CAAC,CAAC,KAAK,CAAC,CACvE,IAAI,CACL,CAAC;IAEN,MAAM,QAAQ,GACZ,CAAC,OAAwC,EAAE,IAAa,EAAE,EAAE,CAC5D,CAAC,KAAa,EAAE,EAAE,CAClB,CAAI,IAAa,EAAQ,EAAE;QACzB,IAAI,CAAC;YACH,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,gBAAgB,CAAC,GAAG,CAAC;gBACvB,MAAM,IAAI,qBAAS,CACjB,KAAK,CAAC,MAAM,EACZ,IAAA,uCAAkB,EAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAC1C,GAAG,EACH;oBACE,cAAc,EAAE,KAAK,CAAC,WAAW;iBAClC,EACD,IAAI,CAAC,SAAS,CAAC;oBACb,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,IAAI,EAAE,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,GAAG,CAAC,IAAI;oBACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ;oBACtB,KAAK,EAAE,GAAG,CAAC,KAAK;oBAChB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC;iBACtB,CAAC,CACH,CAAC;YACJ,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;AACN,CAAC,EAhEgB,eAAe,aAAf,eAAe,GAAf,eAAe,QAgE/B;AAED,MAAM,gBAAgB,GAAG,CAAC,KAAU,EAA2B,EAAE,CAC/D,QAAQ,KAAK,OAAO,KAAK,CAAC,MAAM;IAChC,CAAC,SAAS,KAAK,KAAK,CAAC,IAAI,IAAI,QAAQ,KAAK,OAAO,KAAK,CAAC,IAAI,CAAC;IAC5D,QAAQ,KAAK,OAAO,KAAK,CAAC,QAAQ;IAClC,QAAQ,KAAK,OAAO,KAAK,CAAC,IAAI;IAC9B,QAAQ,KAAK,OAAO,KAAK,CAAC,OAAO;IACjC,CAAC,SAAS,KAAK,KAAK,CAAC,KAAK,IAAI,QAAQ,KAAK,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC"}
|
package/lib/NestiaSimulator.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HttpError } from "./HttpError.mjs";
|
|
2
|
+
import { join_host_and_path } from "./internal/join_host_and_path.mjs";
|
|
2
3
|
//#region src/NestiaSimulator.ts
|
|
3
4
|
let NestiaSimulator;
|
|
4
5
|
(function(_NestiaSimulator) {
|
|
@@ -18,7 +19,7 @@ let NestiaSimulator;
|
|
|
18
19
|
try {
|
|
19
20
|
task();
|
|
20
21
|
} catch (exp) {
|
|
21
|
-
if (isTypeGuardError(exp)) throw new HttpError(props.method, props.host
|
|
22
|
+
if (isTypeGuardError(exp)) throw new HttpError(props.method, join_host_and_path(props.host, props.path), 400, { "Content-Type": props.contentType }, JSON.stringify({
|
|
22
23
|
method: exp.method,
|
|
23
24
|
path: path ?? exp.path,
|
|
24
25
|
expected: exp.expected,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NestiaSimulator.mjs","names":[],"sources":["../src/NestiaSimulator.ts"],"sourcesContent":["import { HttpError } from \"./HttpError\";\n\nexport namespace NestiaSimulator {\n export interface IProps {\n host: string;\n path: string;\n method: \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\";\n contentType: string;\n }\n\n export const assert = (props: IProps) => {\n return {\n param: param(props),\n query: query(props),\n body: body(props),\n };\n };\n const param =\n (props: IProps) =>\n (name: string) =>\n <T>(task: () => T): void => {\n validate((exp) => `URL parameter \"${name}\" is not ${exp.expected} type.`)(\n props,\n )(task);\n };\n\n const query =\n (props: IProps) =>\n <T>(task: () => T): void =>\n validate(\n () => \"Request query parameters are not following the promised type.\",\n )(props)(task);\n\n const body =\n (props: IProps) =>\n <T>(task: () => T): void =>\n validate(() => \"Request body is not following the promised type.\")(props)(\n task,\n );\n\n const validate =\n (message: (exp: TypeGuardError) => string, path?: string) =>\n (props: IProps) =>\n <T>(task: () => T): void => {\n try {\n task();\n } catch (exp) {\n if (isTypeGuardError(exp))\n throw new HttpError(\n props.method,\n props.host
|
|
1
|
+
{"version":3,"file":"NestiaSimulator.mjs","names":[],"sources":["../src/NestiaSimulator.ts"],"sourcesContent":["import { HttpError } from \"./HttpError\";\nimport { join_host_and_path } from \"./internal/join_host_and_path\";\n\nexport namespace NestiaSimulator {\n export interface IProps {\n host: string;\n path: string;\n method: \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\";\n contentType: string;\n }\n\n export const assert = (props: IProps) => {\n return {\n param: param(props),\n query: query(props),\n body: body(props),\n };\n };\n const param =\n (props: IProps) =>\n (name: string) =>\n <T>(task: () => T): void => {\n validate((exp) => `URL parameter \"${name}\" is not ${exp.expected} type.`)(\n props,\n )(task);\n };\n\n const query =\n (props: IProps) =>\n <T>(task: () => T): void =>\n validate(\n () => \"Request query parameters are not following the promised type.\",\n )(props)(task);\n\n const body =\n (props: IProps) =>\n <T>(task: () => T): void =>\n validate(() => \"Request body is not following the promised type.\")(props)(\n task,\n );\n\n const validate =\n (message: (exp: TypeGuardError) => string, path?: string) =>\n (props: IProps) =>\n <T>(task: () => T): void => {\n try {\n task();\n } catch (exp) {\n if (isTypeGuardError(exp))\n throw new HttpError(\n props.method,\n join_host_and_path(props.host, props.path),\n 400,\n {\n \"Content-Type\": props.contentType,\n },\n JSON.stringify({\n method: exp.method,\n path: path ?? exp.path,\n expected: exp.expected,\n value: exp.value,\n message: message(exp),\n }),\n );\n throw exp;\n }\n };\n}\n\nconst isTypeGuardError = (input: any): input is TypeGuardError =>\n \"string\" === typeof input.method &&\n (undefined === input.path || \"string\" === typeof input.path) &&\n \"string\" === typeof input.expected &&\n \"string\" === typeof input.name &&\n \"string\" === typeof input.message &&\n (undefined === input.stack || \"string\" === typeof input.stack);\n\ninterface TypeGuardError extends Error {\n method: string;\n path: string | undefined;\n expected: string;\n value: any;\n}\n"],"mappings":";;;AAGO,IAAA;;4BAQkB,UAAkB;EACvC,OAAO;GACL,OAAO,MAAM,KAAK;GAClB,OAAO,MAAM,KAAK;GAClB,MAAM,KAAK,KAAK;EAClB;CACF;CACA,MAAM,SACH,WACA,UACG,SAAwB;EAC1B,UAAU,QAAQ,kBAAkB,KAAK,WAAW,IAAI,SAAS,OAAO,CAAC,CACvE,KACF,CAAC,CAAC,IAAI;CACR;CAEF,MAAM,SACH,WACG,SACF,eACQ,+DACR,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI;CAEjB,MAAM,QACH,WACG,SACF,eAAe,kDAAkD,CAAC,CAAC,KAAK,CAAC,CACvE,IACF;CAEJ,MAAM,YACH,SAA0C,UAC1C,WACG,SAAwB;EAC1B,IAAI;GACF,KAAK;EACP,SAAS,KAAK;GACZ,IAAI,iBAAiB,GAAG,GACtB,MAAM,IAAI,UACR,MAAM,QACN,mBAAmB,MAAM,MAAM,MAAM,IAAI,GACzC,KACA,EACE,gBAAgB,MAAM,YACxB,GACA,KAAK,UAAU;IACb,QAAQ,IAAI;IACZ,MAAM,QAAQ,IAAI;IAClB,UAAU,IAAI;IACd,OAAO,IAAI;IACX,SAAS,QAAQ,GAAG;GACtB,CAAC,CACH;GACF,MAAM;EACR;CACF;GACH,oBAAA,kBAAA,CAAA,EAAD;AAEA,MAAM,oBAAoB,UACxB,aAAa,OAAO,MAAM,WACzB,KAAA,MAAc,MAAM,QAAQ,aAAa,OAAO,MAAM,SACvD,aAAa,OAAO,MAAM,YAC1B,aAAa,OAAO,MAAM,QAC1B,aAAa,OAAO,MAAM,YACzB,KAAA,MAAc,MAAM,SAAS,aAAa,OAAO,MAAM"}
|
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.FetcherBase = void 0;
|
|
13
13
|
const HttpError_1 = require("../HttpError");
|
|
14
14
|
const is_binary_response_content_type_1 = require("./is_binary_response_content_type");
|
|
15
|
+
const join_host_and_path_1 = require("./join_host_and_path");
|
|
15
16
|
/** @internal */
|
|
16
17
|
var FetcherBase;
|
|
17
18
|
(function (FetcherBase) {
|
|
@@ -34,11 +35,13 @@ var FetcherBase;
|
|
|
34
35
|
if (input !== undefined) {
|
|
35
36
|
if (((_a = route.request) === null || _a === void 0 ? void 0 : _a.type) === undefined)
|
|
36
37
|
throw new Error(`Error on ${props.className}.fetch(): no content-type being configured.`);
|
|
37
|
-
else if (route.request.type !== "multipart/form-data")
|
|
38
|
+
else if (route.request.type !== "multipart/form-data") {
|
|
39
|
+
deleteHeader(headers, "content-type");
|
|
38
40
|
headers["Content-Type"] = route.request.type;
|
|
41
|
+
}
|
|
39
42
|
}
|
|
40
|
-
else if (input === undefined
|
|
41
|
-
|
|
43
|
+
else if (input === undefined)
|
|
44
|
+
deleteHeader(headers, "content-type");
|
|
42
45
|
// INIT REQUEST DATA
|
|
43
46
|
const init = Object.assign(Object.assign({}, ((_j = connection.options) !== null && _j !== void 0 ? _j : {})), { method: route.method, headers: (() => {
|
|
44
47
|
const output = [];
|
|
@@ -67,11 +70,8 @@ var FetcherBase;
|
|
|
67
70
|
// RESPONSE MESSAGE
|
|
68
71
|
//----
|
|
69
72
|
// URL SPECIFICATION
|
|
70
|
-
const path =
|
|
71
|
-
|
|
72
|
-
? `/${route.path}`
|
|
73
|
-
: route.path;
|
|
74
|
-
const url = new URL(`${connection.host}${path}`);
|
|
73
|
+
const path = (0, join_host_and_path_1.normalize_route_path)(route.path);
|
|
74
|
+
const url = new URL((0, join_host_and_path_1.join_host_and_path)(connection.host, route.path));
|
|
75
75
|
// DO FETCH
|
|
76
76
|
const event = {
|
|
77
77
|
route,
|
|
@@ -193,4 +193,10 @@ const response_headers_to_object = (headers) => {
|
|
|
193
193
|
});
|
|
194
194
|
return output;
|
|
195
195
|
};
|
|
196
|
+
const deleteHeader = (headers, name) => {
|
|
197
|
+
const normalized = name.toLowerCase();
|
|
198
|
+
for (const key of Object.keys(headers))
|
|
199
|
+
if (key.toLowerCase() === normalized)
|
|
200
|
+
delete headers[key];
|
|
201
|
+
};
|
|
196
202
|
//# sourceMappingURL=FetcherBase.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FetcherBase.js","sourceRoot":"","sources":["../../src/internal/FetcherBase.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAAyC;AAKzC,uFAAoF;
|
|
1
|
+
{"version":3,"file":"FetcherBase.js","sourceRoot":"","sources":["../../src/internal/FetcherBase.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HttpError } from "../HttpError.mjs";
|
|
2
2
|
import { is_binary_response_content_type } from "./is_binary_response_content_type.mjs";
|
|
3
|
+
import { join_host_and_path, normalize_route_path } from "./join_host_and_path.mjs";
|
|
3
4
|
//#region src/internal/FetcherBase.ts
|
|
4
5
|
let FetcherBase;
|
|
5
6
|
(function(_FetcherBase) {
|
|
@@ -14,8 +15,11 @@ let FetcherBase;
|
|
|
14
15
|
const headers = { ...connection.headers ?? {} };
|
|
15
16
|
if (input !== void 0) {
|
|
16
17
|
if (route.request?.type === void 0) throw new Error(`Error on ${props.className}.fetch(): no content-type being configured.`);
|
|
17
|
-
else if (route.request.type !== "multipart/form-data")
|
|
18
|
-
|
|
18
|
+
else if (route.request.type !== "multipart/form-data") {
|
|
19
|
+
deleteHeader(headers, "content-type");
|
|
20
|
+
headers["Content-Type"] = route.request.type;
|
|
21
|
+
}
|
|
22
|
+
} else if (input === void 0) deleteHeader(headers, "content-type");
|
|
19
23
|
const init = {
|
|
20
24
|
...connection.options ?? {},
|
|
21
25
|
method: route.method,
|
|
@@ -28,8 +32,8 @@ let FetcherBase;
|
|
|
28
32
|
})()
|
|
29
33
|
};
|
|
30
34
|
if (input !== void 0) init.body = props.encode(route.request?.type === "application/x-www-form-urlencoded" ? request_query_body(input) : route.request?.type === "multipart/form-data" ? request_form_data_body(input) : route.request?.type !== "text/plain" ? (stringify ?? JSON.stringify)(input) : input, headers);
|
|
31
|
-
const path =
|
|
32
|
-
const url = new URL(
|
|
35
|
+
const path = normalize_route_path(route.path);
|
|
36
|
+
const url = new URL(join_host_and_path(connection.host, route.path));
|
|
33
37
|
const event = {
|
|
34
38
|
route,
|
|
35
39
|
path,
|
|
@@ -111,6 +115,10 @@ const response_headers_to_object = (headers) => {
|
|
|
111
115
|
});
|
|
112
116
|
return output;
|
|
113
117
|
};
|
|
118
|
+
const deleteHeader = (headers, name) => {
|
|
119
|
+
const normalized = name.toLowerCase();
|
|
120
|
+
for (const key of Object.keys(headers)) if (key.toLowerCase() === normalized) delete headers[key];
|
|
121
|
+
};
|
|
114
122
|
//#endregion
|
|
115
123
|
export { FetcherBase };
|
|
116
124
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FetcherBase.mjs","names":[],"sources":["../../src/internal/FetcherBase.ts"],"sourcesContent":["import { HttpError } from \"../HttpError\";\nimport { IConnection } from \"../IConnection\";\nimport { IFetchEvent } from \"../IFetchEvent\";\nimport { IFetchRoute } from \"../IFetchRoute\";\nimport { IPropagation } from \"../IPropagation\";\nimport { is_binary_response_content_type } from \"./is_binary_response_content_type\";\n\n/** @internal */\nexport namespace FetcherBase {\n export interface IProps {\n className: string;\n encode: (\n input: any,\n headers: Record<string, IConnection.HeaderValue | undefined>,\n ) => string;\n decode: (\n input: string,\n headers: Record<string, IConnection.HeaderValue | undefined>,\n ) => any;\n }\n\n export const request =\n (props: IProps) =>\n async <Input, Output>(\n connection: IConnection,\n route: IFetchRoute<\"DELETE\" | \"GET\" | \"HEAD\" | \"PATCH\" | \"POST\" | \"PUT\">,\n input?: Input,\n stringify?: (input: Input) => string,\n ): Promise<Output> => {\n const result = await _Propagate(\"fetch\")(props)(\n connection,\n route,\n input,\n stringify,\n );\n if ((result as any).success === false)\n throw new HttpError(\n route.method,\n route.path,\n result.status as any as number,\n result.headers,\n result.data as string,\n );\n return result.data as Output;\n };\n\n export const propagate =\n (props: IProps) =>\n async <Input>(\n connection: IConnection,\n route: IFetchRoute<\"DELETE\" | \"GET\" | \"HEAD\" | \"PATCH\" | \"POST\" | \"PUT\">,\n input?: Input,\n stringify?: (input: Input) => string,\n ): Promise<IPropagation<any, any>> =>\n _Propagate(\"propagate\")(props)(connection, route, input, stringify);\n\n /** @internal */\n const _Propagate =\n (method: string) =>\n (props: IProps) =>\n async <Input>(\n connection: IConnection,\n route: IFetchRoute<\"DELETE\" | \"GET\" | \"HEAD\" | \"PATCH\" | \"POST\" | \"PUT\">,\n input?: Input,\n stringify?: (input: Input) => string,\n ): Promise<IPropagation<any, any>> => {\n //----\n // REQUEST MESSAGE\n //----\n // METHOD & HEADERS\n const headers: Record<string, IConnection.HeaderValue | undefined> = {\n ...(connection.headers ?? {}),\n };\n if (input !== undefined) {\n if (route.request?.type === undefined)\n throw new Error(\n `Error on ${props.className}.fetch(): no content-type being configured.`,\n );\n else if (route.request.type !== \"multipart/form-data\")\n headers[\"Content-Type\"] = route.request.type;\n } else if (input === undefined && headers[\"Content-Type\"] !== undefined)\n delete headers[\"Content-Type\"];\n\n // INIT REQUEST DATA\n const init: RequestInit = {\n ...(connection.options ?? {}),\n method: route.method,\n headers: (() => {\n const output: [string, string][] = [];\n for (const [key, value] of Object.entries(headers))\n if (value === undefined) continue;\n else if (Array.isArray(value))\n for (const v of value) output.push([key, String(v)]);\n else output.push([key, String(value)]);\n return output;\n })(),\n };\n\n // CONSTRUCT BODY DATA\n if (input !== undefined)\n init.body = props.encode(\n // BODY TRANSFORM\n route.request?.type === \"application/x-www-form-urlencoded\"\n ? request_query_body(input)\n : route.request?.type === \"multipart/form-data\"\n ? request_form_data_body(input as any)\n : route.request?.type !== \"text/plain\"\n ? (stringify ?? JSON.stringify)(input)\n : input,\n headers,\n );\n\n //----\n // RESPONSE MESSAGE\n //----\n // URL SPECIFICATION\n const path: string =\n connection.host[connection.host.length - 1] !== \"/\" &&\n route.path[0] !== \"/\"\n ? `/${route.path}`\n : route.path;\n const url: URL = new URL(`${connection.host}${path}`);\n\n // DO FETCH\n const event: IFetchEvent = {\n route,\n path,\n status: null,\n input,\n output: undefined,\n started_at: new Date(),\n respond_at: null,\n completed_at: null!,\n };\n try {\n // TRY FETCH\n const response: Response = await (connection.fetch ?? fetch)(\n url.href,\n init,\n );\n event.respond_at = new Date();\n event.status = response.status;\n\n // CONSTRUCT RESULT DATA\n const result: IPropagation<any, any> = {\n success:\n response.status === 200 ||\n response.status === 201 ||\n response.status === route.status,\n status: response.status,\n headers: response_headers_to_object(response.headers),\n data: undefined!,\n } as any;\n if ((result as any).success === false) {\n // WHEN FAILED\n result.data = await response.text();\n const type = response.headers.get(\"content-type\");\n if (\n method !== \"fetch\" &&\n type &&\n type.indexOf(\"application/json\") !== -1\n )\n try {\n result.data = JSON.parse(result.data);\n } catch {}\n } else {\n // WHEN SUCCESS\n if (route.method === \"HEAD\") result.data = undefined!;\n else if (route.response?.type === \"application/json\") {\n const text: string = await response.text();\n result.data = text.length ? JSON.parse(text) : undefined;\n } else if (\n route.response?.type === \"application/x-www-form-urlencoded\"\n ) {\n const query: URLSearchParams = new URLSearchParams(\n await response.text(),\n );\n result.data = route.parseQuery ? route.parseQuery(query) : query;\n } else if (is_binary_response_content_type(route.response?.type))\n result.data = response.body ?? new ReadableStream<Uint8Array>();\n else\n result.data = props.decode(await response.text(), result.headers);\n }\n event.output = result.data;\n return result;\n } catch (exp) {\n throw exp;\n } finally {\n event.completed_at = new Date();\n if (connection.logger)\n try {\n await connection.logger(event);\n } catch {}\n }\n };\n}\n\n/** @internal */\nconst request_query_body = (input: any): URLSearchParams => {\n const q: URLSearchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(input))\n if (value === undefined) continue;\n else if (Array.isArray(value))\n value.forEach((elem) => q.append(key, String(elem)));\n else q.set(key, String(value));\n return q;\n};\n\n/** @internal */\nconst request_form_data_body = (input: Record<string, any>): FormData => {\n const encoded: FormData = new FormData();\n const append = (key: string) => (value: any) => {\n if (value === undefined) return;\n else if (typeof File === \"function\" && value instanceof File)\n encoded.append(key, value, value.name);\n else encoded.append(key, value);\n };\n for (const [key, value] of Object.entries(input))\n if (Array.isArray(value)) value.map(append(key));\n else append(key)(value);\n return encoded;\n};\n\n/** @internal */\nconst response_headers_to_object = (\n headers: Headers,\n): Record<string, string | string[]> => {\n const output: Record<string, string | string[]> = {};\n const cookieHeaders: string[] | undefined =\n typeof (headers as any).getSetCookie === \"function\"\n ? (headers as any).getSetCookie()\n : undefined;\n if (cookieHeaders?.length) output[\"set-cookie\"] = cookieHeaders;\n\n headers.forEach((value, key) => {\n if (key.toLowerCase() === \"set-cookie\") {\n if (cookieHeaders?.length) return;\n output[key] ??= [];\n (output[key] as string[]).push(value);\n } else output[key] = value;\n });\n return output;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FetcherBase.mjs","names":[],"sources":["../../src/internal/FetcherBase.ts"],"sourcesContent":["import { HttpError } from \"../HttpError\";\nimport { IConnection } from \"../IConnection\";\nimport { IFetchEvent } from \"../IFetchEvent\";\nimport { IFetchRoute } from \"../IFetchRoute\";\nimport { IPropagation } from \"../IPropagation\";\nimport { is_binary_response_content_type } from \"./is_binary_response_content_type\";\nimport { join_host_and_path, normalize_route_path } from \"./join_host_and_path\";\n\n/** @internal */\nexport namespace FetcherBase {\n export interface IProps {\n className: string;\n encode: (\n input: any,\n headers: Record<string, IConnection.HeaderValue | undefined>,\n ) => string;\n decode: (\n input: string,\n headers: Record<string, IConnection.HeaderValue | undefined>,\n ) => any;\n }\n\n export const request =\n (props: IProps) =>\n async <Input, Output>(\n connection: IConnection,\n route: IFetchRoute<\"DELETE\" | \"GET\" | \"HEAD\" | \"PATCH\" | \"POST\" | \"PUT\">,\n input?: Input,\n stringify?: (input: Input) => string,\n ): Promise<Output> => {\n const result = await _Propagate(\"fetch\")(props)(\n connection,\n route,\n input,\n stringify,\n );\n if ((result as any).success === false)\n throw new HttpError(\n route.method,\n route.path,\n result.status as any as number,\n result.headers,\n result.data as string,\n );\n return result.data as Output;\n };\n\n export const propagate =\n (props: IProps) =>\n async <Input>(\n connection: IConnection,\n route: IFetchRoute<\"DELETE\" | \"GET\" | \"HEAD\" | \"PATCH\" | \"POST\" | \"PUT\">,\n input?: Input,\n stringify?: (input: Input) => string,\n ): Promise<IPropagation<any, any>> =>\n _Propagate(\"propagate\")(props)(connection, route, input, stringify);\n\n /** @internal */\n const _Propagate =\n (method: string) =>\n (props: IProps) =>\n async <Input>(\n connection: IConnection,\n route: IFetchRoute<\"DELETE\" | \"GET\" | \"HEAD\" | \"PATCH\" | \"POST\" | \"PUT\">,\n input?: Input,\n stringify?: (input: Input) => string,\n ): Promise<IPropagation<any, any>> => {\n //----\n // REQUEST MESSAGE\n //----\n // METHOD & HEADERS\n const headers: Record<string, IConnection.HeaderValue | undefined> = {\n ...(connection.headers ?? {}),\n };\n if (input !== undefined) {\n if (route.request?.type === undefined)\n throw new Error(\n `Error on ${props.className}.fetch(): no content-type being configured.`,\n );\n else if (route.request.type !== \"multipart/form-data\") {\n deleteHeader(headers, \"content-type\");\n headers[\"Content-Type\"] = route.request.type;\n }\n } else if (input === undefined) deleteHeader(headers, \"content-type\");\n\n // INIT REQUEST DATA\n const init: RequestInit = {\n ...(connection.options ?? {}),\n method: route.method,\n headers: (() => {\n const output: [string, string][] = [];\n for (const [key, value] of Object.entries(headers))\n if (value === undefined) continue;\n else if (Array.isArray(value))\n for (const v of value) output.push([key, String(v)]);\n else output.push([key, String(value)]);\n return output;\n })(),\n };\n\n // CONSTRUCT BODY DATA\n if (input !== undefined)\n init.body = props.encode(\n // BODY TRANSFORM\n route.request?.type === \"application/x-www-form-urlencoded\"\n ? request_query_body(input)\n : route.request?.type === \"multipart/form-data\"\n ? request_form_data_body(input as any)\n : route.request?.type !== \"text/plain\"\n ? (stringify ?? JSON.stringify)(input)\n : input,\n headers,\n );\n\n //----\n // RESPONSE MESSAGE\n //----\n // URL SPECIFICATION\n const path: string = normalize_route_path(route.path);\n const url: URL = new URL(join_host_and_path(connection.host, route.path));\n\n // DO FETCH\n const event: IFetchEvent = {\n route,\n path,\n status: null,\n input,\n output: undefined,\n started_at: new Date(),\n respond_at: null,\n completed_at: null!,\n };\n try {\n // TRY FETCH\n const response: Response = await (connection.fetch ?? fetch)(\n url.href,\n init,\n );\n event.respond_at = new Date();\n event.status = response.status;\n\n // CONSTRUCT RESULT DATA\n const result: IPropagation<any, any> = {\n success:\n response.status === 200 ||\n response.status === 201 ||\n response.status === route.status,\n status: response.status,\n headers: response_headers_to_object(response.headers),\n data: undefined!,\n } as any;\n if ((result as any).success === false) {\n // WHEN FAILED\n result.data = await response.text();\n const type = response.headers.get(\"content-type\");\n if (\n method !== \"fetch\" &&\n type &&\n type.indexOf(\"application/json\") !== -1\n )\n try {\n result.data = JSON.parse(result.data);\n } catch {}\n } else {\n // WHEN SUCCESS\n if (route.method === \"HEAD\") result.data = undefined!;\n else if (route.response?.type === \"application/json\") {\n const text: string = await response.text();\n result.data = text.length ? JSON.parse(text) : undefined;\n } else if (\n route.response?.type === \"application/x-www-form-urlencoded\"\n ) {\n const query: URLSearchParams = new URLSearchParams(\n await response.text(),\n );\n result.data = route.parseQuery ? route.parseQuery(query) : query;\n } else if (is_binary_response_content_type(route.response?.type))\n result.data = response.body ?? new ReadableStream<Uint8Array>();\n else\n result.data = props.decode(await response.text(), result.headers);\n }\n event.output = result.data;\n return result;\n } catch (exp) {\n throw exp;\n } finally {\n event.completed_at = new Date();\n if (connection.logger)\n try {\n await connection.logger(event);\n } catch {}\n }\n };\n}\n\n/** @internal */\nconst request_query_body = (input: any): URLSearchParams => {\n const q: URLSearchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(input))\n if (value === undefined) continue;\n else if (Array.isArray(value))\n value.forEach((elem) => q.append(key, String(elem)));\n else q.set(key, String(value));\n return q;\n};\n\n/** @internal */\nconst request_form_data_body = (input: Record<string, any>): FormData => {\n const encoded: FormData = new FormData();\n const append = (key: string) => (value: any) => {\n if (value === undefined) return;\n else if (typeof File === \"function\" && value instanceof File)\n encoded.append(key, value, value.name);\n else encoded.append(key, value);\n };\n for (const [key, value] of Object.entries(input))\n if (Array.isArray(value)) value.map(append(key));\n else append(key)(value);\n return encoded;\n};\n\n/** @internal */\nconst response_headers_to_object = (\n headers: Headers,\n): Record<string, string | string[]> => {\n const output: Record<string, string | string[]> = {};\n const cookieHeaders: string[] | undefined =\n typeof (headers as any).getSetCookie === \"function\"\n ? (headers as any).getSetCookie()\n : undefined;\n if (cookieHeaders?.length) output[\"set-cookie\"] = cookieHeaders;\n\n headers.forEach((value, key) => {\n if (key.toLowerCase() === \"set-cookie\") {\n if (cookieHeaders?.length) return;\n output[key] ??= [];\n (output[key] as string[]).push(value);\n } else output[key] = value;\n });\n return output;\n};\n\nconst deleteHeader = (\n headers: Record<string, IConnection.HeaderValue | undefined>,\n name: string,\n): void => {\n const normalized: string = name.toLowerCase();\n for (const key of Object.keys(headers))\n if (key.toLowerCase() === normalized) delete headers[key];\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.join_host_and_path = exports.normalize_route_path = void 0;
|
|
4
|
+
/** @internal */
|
|
5
|
+
const SLASH = 0x2f;
|
|
6
|
+
/**
|
|
7
|
+
* Normalize a route path to exactly one leading separator.
|
|
8
|
+
*
|
|
9
|
+
* `@nestia/sdk` always emits a leading-slash path, but a hand-written
|
|
10
|
+
* {@link IFetchRoute.path} may omit it, and a server-composed path may carry
|
|
11
|
+
* several. Interior separators are left alone: only the boundary is
|
|
12
|
+
* normalized.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
const normalize_route_path = (path) => {
|
|
17
|
+
let start = 0;
|
|
18
|
+
while (start < path.length && path.charCodeAt(start) === SLASH)
|
|
19
|
+
++start;
|
|
20
|
+
return `/${path.slice(start)}`;
|
|
21
|
+
};
|
|
22
|
+
exports.normalize_route_path = normalize_route_path;
|
|
23
|
+
/**
|
|
24
|
+
* Join a host address and a route path with exactly one separator.
|
|
25
|
+
*
|
|
26
|
+
* `IConnection.host` may or may not end with a separator, and the route path
|
|
27
|
+
* may or may not begin with one; all four spellings must produce the same URL.
|
|
28
|
+
* `new URL()` does not collapse a doubled separator inside a pathname, so the
|
|
29
|
+
* join has to be exact — `http://localhost:3000/` plus `/health` must not
|
|
30
|
+
* become `http://localhost:3000//health`, which most routers treat as an
|
|
31
|
+
* unmapped path.
|
|
32
|
+
*
|
|
33
|
+
* Both boundaries are scanned rather than matched with a regular expression.
|
|
34
|
+
* `host` is caller-supplied, and a trailing-separator pattern backtracks
|
|
35
|
+
* polynomially over a run of separators, so a pathological host would cost
|
|
36
|
+
* quadratic time on every request.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
const join_host_and_path = (host, path) => {
|
|
41
|
+
let end = host.length;
|
|
42
|
+
while (end > 0 && host.charCodeAt(end - 1) === SLASH)
|
|
43
|
+
--end;
|
|
44
|
+
return `${host.slice(0, end)}${(0, exports.normalize_route_path)(path)}`;
|
|
45
|
+
};
|
|
46
|
+
exports.join_host_and_path = join_host_and_path;
|
|
47
|
+
//# sourceMappingURL=join_host_and_path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"join_host_and_path.js","sourceRoot":"","sources":["../../src/internal/join_host_and_path.ts"],"names":[],"mappings":";;;AAAA,gBAAgB;AAChB,MAAM,KAAK,GAAG,IAAI,CAAC;AAEnB;;;;;;;;;GASG;AACI,MAAM,oBAAoB,GAAG,CAAC,IAAY,EAAU,EAAE;IAC3D,IAAI,KAAK,GAAW,CAAC,CAAC;IACtB,OAAO,KAAK,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,KAAK;QAAE,EAAE,KAAK,CAAC;IACxE,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;AACjC,CAAC,CAAC;AAJW,QAAA,oBAAoB,GAApB,oBAAoB,CAI/B;AAEF;;;;;;;;;;;;;;;;GAgBG;AACI,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAE,IAAY,EAAU,EAAE;IACvE,IAAI,GAAG,GAAW,IAAI,CAAC,MAAM,CAAC;IAC9B,OAAO,GAAG,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,KAAK;QAAE,EAAE,GAAG,CAAC;IAC5D,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,IAAA,QAAA,oBAAoB,EAAC,IAAI,CAAC,EAAE,CAAC;AAC9D,CAAC,CAAC;AAJW,QAAA,kBAAkB,GAAlB,kBAAkB,CAI7B"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/internal/join_host_and_path.ts
|
|
2
|
+
/** @internal */
|
|
3
|
+
const SLASH = 47;
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a route path to exactly one leading separator.
|
|
6
|
+
*
|
|
7
|
+
* `@nestia/sdk` always emits a leading-slash path, but a hand-written
|
|
8
|
+
* {@link IFetchRoute.path} may omit it, and a server-composed path may carry
|
|
9
|
+
* several. Interior separators are left alone: only the boundary is
|
|
10
|
+
* normalized.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
const normalize_route_path = (path) => {
|
|
15
|
+
let start = 0;
|
|
16
|
+
while (start < path.length && path.charCodeAt(start) === SLASH) ++start;
|
|
17
|
+
return `/${path.slice(start)}`;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Join a host address and a route path with exactly one separator.
|
|
21
|
+
*
|
|
22
|
+
* `IConnection.host` may or may not end with a separator, and the route path
|
|
23
|
+
* may or may not begin with one; all four spellings must produce the same URL.
|
|
24
|
+
* `new URL()` does not collapse a doubled separator inside a pathname, so the
|
|
25
|
+
* join has to be exact — `http://localhost:3000/` plus `/health` must not
|
|
26
|
+
* become `http://localhost:3000//health`, which most routers treat as an
|
|
27
|
+
* unmapped path.
|
|
28
|
+
*
|
|
29
|
+
* Both boundaries are scanned rather than matched with a regular expression.
|
|
30
|
+
* `host` is caller-supplied, and a trailing-separator pattern backtracks
|
|
31
|
+
* polynomially over a run of separators, so a pathological host would cost
|
|
32
|
+
* quadratic time on every request.
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
const join_host_and_path = (host, path) => {
|
|
37
|
+
let end = host.length;
|
|
38
|
+
while (end > 0 && host.charCodeAt(end - 1) === SLASH) --end;
|
|
39
|
+
return `${host.slice(0, end)}${normalize_route_path(path)}`;
|
|
40
|
+
};
|
|
41
|
+
//#endregion
|
|
42
|
+
export { join_host_and_path, normalize_route_path };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=join_host_and_path.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"join_host_and_path.mjs","names":[],"sources":["../../src/internal/join_host_and_path.ts"],"sourcesContent":["/** @internal */\nconst SLASH = 0x2f;\n\n/**\n * Normalize a route path to exactly one leading separator.\n *\n * `@nestia/sdk` always emits a leading-slash path, but a hand-written\n * {@link IFetchRoute.path} may omit it, and a server-composed path may carry\n * several. Interior separators are left alone: only the boundary is\n * normalized.\n *\n * @internal\n */\nexport const normalize_route_path = (path: string): string => {\n let start: number = 0;\n while (start < path.length && path.charCodeAt(start) === SLASH) ++start;\n return `/${path.slice(start)}`;\n};\n\n/**\n * Join a host address and a route path with exactly one separator.\n *\n * `IConnection.host` may or may not end with a separator, and the route path\n * may or may not begin with one; all four spellings must produce the same URL.\n * `new URL()` does not collapse a doubled separator inside a pathname, so the\n * join has to be exact — `http://localhost:3000/` plus `/health` must not\n * become `http://localhost:3000//health`, which most routers treat as an\n * unmapped path.\n *\n * Both boundaries are scanned rather than matched with a regular expression.\n * `host` is caller-supplied, and a trailing-separator pattern backtracks\n * polynomially over a run of separators, so a pathological host would cost\n * quadratic time on every request.\n *\n * @internal\n */\nexport const join_host_and_path = (host: string, path: string): string => {\n let end: number = host.length;\n while (end > 0 && host.charCodeAt(end - 1) === SLASH) --end;\n return `${host.slice(0, end)}${normalize_route_path(path)}`;\n};\n"],"mappings":";;AACA,MAAM,QAAQ;;;;;;;;;;;AAYd,MAAa,wBAAwB,SAAyB;CAC5D,IAAI,QAAgB;CACpB,OAAO,QAAQ,KAAK,UAAU,KAAK,WAAW,KAAK,MAAM,OAAO,EAAE;CAClE,OAAO,IAAI,KAAK,MAAM,KAAK;AAC7B;;;;;;;;;;;;;;;;;;AAmBA,MAAa,sBAAsB,MAAc,SAAyB;CACxE,IAAI,MAAc,KAAK;CACvB,OAAO,MAAM,KAAK,KAAK,WAAW,MAAM,CAAC,MAAM,OAAO,EAAE;CACxD,OAAO,GAAG,KAAK,MAAM,GAAG,GAAG,IAAI,qBAAqB,IAAI;AAC1D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestia/fetcher",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.1.0",
|
|
4
4
|
"description": "Fetcher library of Nestia SDK",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://nestia.io",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@typia/interface": "^13.0
|
|
41
|
-
"@typia/utils": "^13.0
|
|
40
|
+
"@typia/interface": "^13.2.0",
|
|
41
|
+
"@typia/utils": "^13.2.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^25.3.3",
|
package/src/NestiaSimulator.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HttpError } from "./HttpError";
|
|
2
|
+
import { join_host_and_path } from "./internal/join_host_and_path";
|
|
2
3
|
|
|
3
4
|
export namespace NestiaSimulator {
|
|
4
5
|
export interface IProps {
|
|
@@ -48,7 +49,7 @@ export namespace NestiaSimulator {
|
|
|
48
49
|
if (isTypeGuardError(exp))
|
|
49
50
|
throw new HttpError(
|
|
50
51
|
props.method,
|
|
51
|
-
props.host
|
|
52
|
+
join_host_and_path(props.host, props.path),
|
|
52
53
|
400,
|
|
53
54
|
{
|
|
54
55
|
"Content-Type": props.contentType,
|
|
@@ -4,6 +4,7 @@ import { IFetchEvent } from "../IFetchEvent";
|
|
|
4
4
|
import { IFetchRoute } from "../IFetchRoute";
|
|
5
5
|
import { IPropagation } from "../IPropagation";
|
|
6
6
|
import { is_binary_response_content_type } from "./is_binary_response_content_type";
|
|
7
|
+
import { join_host_and_path, normalize_route_path } from "./join_host_and_path";
|
|
7
8
|
|
|
8
9
|
/** @internal */
|
|
9
10
|
export namespace FetcherBase {
|
|
@@ -76,10 +77,11 @@ export namespace FetcherBase {
|
|
|
76
77
|
throw new Error(
|
|
77
78
|
`Error on ${props.className}.fetch(): no content-type being configured.`,
|
|
78
79
|
);
|
|
79
|
-
else if (route.request.type !== "multipart/form-data")
|
|
80
|
+
else if (route.request.type !== "multipart/form-data") {
|
|
81
|
+
deleteHeader(headers, "content-type");
|
|
80
82
|
headers["Content-Type"] = route.request.type;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
}
|
|
84
|
+
} else if (input === undefined) deleteHeader(headers, "content-type");
|
|
83
85
|
|
|
84
86
|
// INIT REQUEST DATA
|
|
85
87
|
const init: RequestInit = {
|
|
@@ -114,12 +116,8 @@ export namespace FetcherBase {
|
|
|
114
116
|
// RESPONSE MESSAGE
|
|
115
117
|
//----
|
|
116
118
|
// URL SPECIFICATION
|
|
117
|
-
const path: string =
|
|
118
|
-
|
|
119
|
-
route.path[0] !== "/"
|
|
120
|
-
? `/${route.path}`
|
|
121
|
-
: route.path;
|
|
122
|
-
const url: URL = new URL(`${connection.host}${path}`);
|
|
119
|
+
const path: string = normalize_route_path(route.path);
|
|
120
|
+
const url: URL = new URL(join_host_and_path(connection.host, route.path));
|
|
123
121
|
|
|
124
122
|
// DO FETCH
|
|
125
123
|
const event: IFetchEvent = {
|
|
@@ -241,3 +239,12 @@ const response_headers_to_object = (
|
|
|
241
239
|
});
|
|
242
240
|
return output;
|
|
243
241
|
};
|
|
242
|
+
|
|
243
|
+
const deleteHeader = (
|
|
244
|
+
headers: Record<string, IConnection.HeaderValue | undefined>,
|
|
245
|
+
name: string,
|
|
246
|
+
): void => {
|
|
247
|
+
const normalized: string = name.toLowerCase();
|
|
248
|
+
for (const key of Object.keys(headers))
|
|
249
|
+
if (key.toLowerCase() === normalized) delete headers[key];
|
|
250
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
const SLASH = 0x2f;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a route path to exactly one leading separator.
|
|
6
|
+
*
|
|
7
|
+
* `@nestia/sdk` always emits a leading-slash path, but a hand-written
|
|
8
|
+
* {@link IFetchRoute.path} may omit it, and a server-composed path may carry
|
|
9
|
+
* several. Interior separators are left alone: only the boundary is
|
|
10
|
+
* normalized.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export const normalize_route_path = (path: string): string => {
|
|
15
|
+
let start: number = 0;
|
|
16
|
+
while (start < path.length && path.charCodeAt(start) === SLASH) ++start;
|
|
17
|
+
return `/${path.slice(start)}`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Join a host address and a route path with exactly one separator.
|
|
22
|
+
*
|
|
23
|
+
* `IConnection.host` may or may not end with a separator, and the route path
|
|
24
|
+
* may or may not begin with one; all four spellings must produce the same URL.
|
|
25
|
+
* `new URL()` does not collapse a doubled separator inside a pathname, so the
|
|
26
|
+
* join has to be exact — `http://localhost:3000/` plus `/health` must not
|
|
27
|
+
* become `http://localhost:3000//health`, which most routers treat as an
|
|
28
|
+
* unmapped path.
|
|
29
|
+
*
|
|
30
|
+
* Both boundaries are scanned rather than matched with a regular expression.
|
|
31
|
+
* `host` is caller-supplied, and a trailing-separator pattern backtracks
|
|
32
|
+
* polynomially over a run of separators, so a pathological host would cost
|
|
33
|
+
* quadratic time on every request.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export const join_host_and_path = (host: string, path: string): string => {
|
|
38
|
+
let end: number = host.length;
|
|
39
|
+
while (end > 0 && host.charCodeAt(end - 1) === SLASH) --end;
|
|
40
|
+
return `${host.slice(0, end)}${normalize_route_path(path)}`;
|
|
41
|
+
};
|