@twin.org/api-models 0.9.1-next.2 → 0.9.1-next.4

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.
@@ -0,0 +1,118 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { GeneralError, Guards, Is, StringHelper } from "@twin.org/core";
4
+ import { HeaderHelper, HeaderTypes, MimeTypes } from "@twin.org/web";
5
+ import { HttpUrlHelper } from "./httpUrlHelper.js";
6
+ /**
7
+ * Class to help with handling http headers.
8
+ */
9
+ export class HttpHeaderHelper {
10
+ /**
11
+ * Runtime name for the class.
12
+ */
13
+ static CLASS_NAME = "HttpHeaderHelper";
14
+ /**
15
+ * Set the Link header with a next cursor relation when a cursor is present.
16
+ * @param headers The response headers to mutate.
17
+ * @param url The request URL used as the base for the link.
18
+ * @param publicOrigin The public origin to substitute into the URL, if any.
19
+ * @param cursor The cursor value; when undefined or empty the header is not set.
20
+ */
21
+ static buildCursor(headers, url, publicOrigin, cursor) {
22
+ if (Is.stringValue(cursor)) {
23
+ headers[HeaderTypes.Link] = HeaderHelper.createLinkHeader(HttpUrlHelper.replaceOrigin(url, publicOrigin), { cursor }, "next");
24
+ }
25
+ }
26
+ /**
27
+ * Set the Location header to the encoded ID, optionally placed within a URL template.
28
+ * When the template contains `:id` (e.g. "/path1/:id/path2" or "/path?p=:id") the
29
+ * encoded ID is substituted at that position; otherwise it is appended.
30
+ * When no template is provided the bare encoded ID is used.
31
+ * Callers that need to combine a public origin with a path should use
32
+ * HttpUrlHelper.combineOriginPath to build the template before calling this method.
33
+ * @param headers The response headers to mutate.
34
+ * @param id The resource ID to encode and place.
35
+ * @param urlTemplate The optional URL template (absolute or relative).
36
+ */
37
+ static buildId(headers, id, urlTemplate) {
38
+ Guards.stringValue(HttpHeaderHelper.CLASS_NAME, "id", id);
39
+ const encodedId = encodeURIComponent(id);
40
+ if (Is.stringValue(urlTemplate)) {
41
+ if (urlTemplate.includes(":id")) {
42
+ headers[HeaderTypes.Location] = urlTemplate.replace(/:id/, encodedId);
43
+ }
44
+ else {
45
+ headers[HeaderTypes.Location] =
46
+ `${StringHelper.trimTrailingSlashes(urlTemplate)}/${encodedId}`;
47
+ }
48
+ }
49
+ else {
50
+ headers[HeaderTypes.Location] = encodedId;
51
+ }
52
+ }
53
+ /**
54
+ * Set the Content-Type header to JSON-LD or JSON depending on the request Accept header.
55
+ * Uses HeaderHelper.extractAccept which parses the Accept header per RFC 7231 and returns
56
+ * entries ordered by quality descending, preserving original order for equal q-values.
57
+ * @param headers The response headers to mutate.
58
+ * @param requestHeaders The request headers to inspect for the Accept value.
59
+ */
60
+ static buildJsonContentType(headers, requestHeaders) {
61
+ const accepted = HeaderHelper.extractAccept(requestHeaders);
62
+ let contentType = MimeTypes.Json;
63
+ if (Is.arrayValue(accepted)) {
64
+ for (const { mimeType } of accepted) {
65
+ if (mimeType === MimeTypes.JsonLd) {
66
+ contentType = MimeTypes.JsonLd;
67
+ break;
68
+ }
69
+ if (mimeType === MimeTypes.Json || mimeType === "application/*" || mimeType === "*/*") {
70
+ break;
71
+ }
72
+ }
73
+ }
74
+ headers[HeaderTypes.ContentType] = contentType;
75
+ }
76
+ /**
77
+ * Extract the cursor from the Link header's next relation.
78
+ * @param headers The response headers to extract the cursor from.
79
+ * @returns The cursor value or undefined if not present.
80
+ */
81
+ static extractCursor(headers) {
82
+ return HeaderHelper.extractLinkHeaderRelation(headers?.[HeaderTypes.Link], "next")
83
+ ?.urlQueryParams?.cursor;
84
+ }
85
+ /**
86
+ * Extract the resource ID from the Location response header.
87
+ * Handles absolute URLs, relative paths, and bare ID values.
88
+ * When a templateUrl containing ':id' is supplied (e.g. "/path1/:id/path2" or
89
+ * "https://host/path/:id") the ID is extracted via pattern matching at the ':id'
90
+ * position. Without a matching template the last path segment is returned.
91
+ * @param headers The response headers containing the Location header.
92
+ * @param templateUrl Optional URL template containing the ':id' placeholder, e.g. "/path1/:id/path2".
93
+ * @returns The extracted ID string.
94
+ * @throws GeneralError If the Location header is missing or the ID cannot be extracted.
95
+ */
96
+ static extractId(headers, templateUrl) {
97
+ const location = headers?.[HeaderTypes.Location];
98
+ if (!Is.stringValue(location)) {
99
+ throw new GeneralError(HttpHeaderHelper.CLASS_NAME, "locationMissing");
100
+ }
101
+ const withoutQuery = location.split("?")[0];
102
+ if (Is.stringValue(templateUrl) && templateUrl.includes(":id")) {
103
+ const escaped = templateUrl.replace(/[.+^${}()|[\]\\?]/g, "\\$&");
104
+ const pattern = new RegExp(escaped.replace(":id", "([^/?#]+)"));
105
+ const match = pattern.exec(location);
106
+ if (!Is.stringValue(match?.[1])) {
107
+ throw new GeneralError(HttpHeaderHelper.CLASS_NAME, "idNotFound");
108
+ }
109
+ return decodeURIComponent(match[1]);
110
+ }
111
+ const id = withoutQuery.includes("/") ? withoutQuery.split("/").pop() : withoutQuery;
112
+ if (!Is.stringValue(id)) {
113
+ throw new GeneralError(HttpHeaderHelper.CLASS_NAME, "idNotFound");
114
+ }
115
+ return decodeURIComponent(id);
116
+ }
117
+ }
118
+ //# sourceMappingURL=httpHeaderHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpHeaderHelper.js","sourceRoot":"","sources":["../../../src/helpers/httpHeaderHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { GeneralError, Guards, Is, StringHelper } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderHelper, HeaderTypes, MimeTypes, type IHttpHeaders } from \"@twin.org/web\";\nimport { HttpUrlHelper } from \"./httpUrlHelper.js\";\n\n/**\n * Class to help with handling http headers.\n */\nexport class HttpHeaderHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<HttpHeaderHelper>();\n\n\t/**\n\t * Set the Link header with a next cursor relation when a cursor is present.\n\t * @param headers The response headers to mutate.\n\t * @param url The request URL used as the base for the link.\n\t * @param publicOrigin The public origin to substitute into the URL, if any.\n\t * @param cursor The cursor value; when undefined or empty the header is not set.\n\t */\n\tpublic static buildCursor(\n\t\theaders: IHttpHeaders,\n\t\turl: string,\n\t\tpublicOrigin: string | undefined,\n\t\tcursor: string | undefined\n\t): asserts headers is IHttpHeaders & { [HeaderTypes.Link]: string | undefined } {\n\t\tif (Is.stringValue(cursor)) {\n\t\t\theaders[HeaderTypes.Link] = HeaderHelper.createLinkHeader(\n\t\t\t\tHttpUrlHelper.replaceOrigin(url, publicOrigin),\n\t\t\t\t{ cursor },\n\t\t\t\t\"next\"\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Set the Location header to the encoded ID, optionally placed within a URL template.\n\t * When the template contains `:id` (e.g. \"/path1/:id/path2\" or \"/path?p=:id\") the\n\t * encoded ID is substituted at that position; otherwise it is appended.\n\t * When no template is provided the bare encoded ID is used.\n\t * Callers that need to combine a public origin with a path should use\n\t * HttpUrlHelper.combineOriginPath to build the template before calling this method.\n\t * @param headers The response headers to mutate.\n\t * @param id The resource ID to encode and place.\n\t * @param urlTemplate The optional URL template (absolute or relative).\n\t */\n\tpublic static buildId(\n\t\theaders: IHttpHeaders,\n\t\tid: string,\n\t\turlTemplate?: string\n\t): asserts headers is IHttpHeaders & { [HeaderTypes.Location]: string } {\n\t\tGuards.stringValue(HttpHeaderHelper.CLASS_NAME, nameof(id), id);\n\t\tconst encodedId = encodeURIComponent(id);\n\n\t\tif (Is.stringValue(urlTemplate)) {\n\t\t\tif (urlTemplate.includes(\":id\")) {\n\t\t\t\theaders[HeaderTypes.Location] = urlTemplate.replace(/:id/, encodedId);\n\t\t\t} else {\n\t\t\t\theaders[HeaderTypes.Location] =\n\t\t\t\t\t`${StringHelper.trimTrailingSlashes(urlTemplate)}/${encodedId}`;\n\t\t\t}\n\t\t} else {\n\t\t\theaders[HeaderTypes.Location] = encodedId;\n\t\t}\n\t}\n\n\t/**\n\t * Set the Content-Type header to JSON-LD or JSON depending on the request Accept header.\n\t * Uses HeaderHelper.extractAccept which parses the Accept header per RFC 7231 and returns\n\t * entries ordered by quality descending, preserving original order for equal q-values.\n\t * @param headers The response headers to mutate.\n\t * @param requestHeaders The request headers to inspect for the Accept value.\n\t */\n\tpublic static buildJsonContentType(\n\t\theaders: IHttpHeaders,\n\t\trequestHeaders?: IHttpHeaders\n\t): asserts headers is IHttpHeaders & {\n\t\t[HeaderTypes.ContentType]: typeof MimeTypes.JsonLd | typeof MimeTypes.Json;\n\t} {\n\t\tconst accepted = HeaderHelper.extractAccept(requestHeaders);\n\t\tlet contentType: string = MimeTypes.Json;\n\t\tif (Is.arrayValue(accepted)) {\n\t\t\tfor (const { mimeType } of accepted) {\n\t\t\t\tif (mimeType === MimeTypes.JsonLd) {\n\t\t\t\t\tcontentType = MimeTypes.JsonLd;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (mimeType === MimeTypes.Json || mimeType === \"application/*\" || mimeType === \"*/*\") {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\theaders[HeaderTypes.ContentType] = contentType;\n\t}\n\n\t/**\n\t * Extract the cursor from the Link header's next relation.\n\t * @param headers The response headers to extract the cursor from.\n\t * @returns The cursor value or undefined if not present.\n\t */\n\tpublic static extractCursor(headers?: IHttpHeaders): string | undefined {\n\t\treturn HeaderHelper.extractLinkHeaderRelation(headers?.[HeaderTypes.Link], \"next\")\n\t\t\t?.urlQueryParams?.cursor;\n\t}\n\n\t/**\n\t * Extract the resource ID from the Location response header.\n\t * Handles absolute URLs, relative paths, and bare ID values.\n\t * When a templateUrl containing ':id' is supplied (e.g. \"/path1/:id/path2\" or\n\t * \"https://host/path/:id\") the ID is extracted via pattern matching at the ':id'\n\t * position. Without a matching template the last path segment is returned.\n\t * @param headers The response headers containing the Location header.\n\t * @param templateUrl Optional URL template containing the ':id' placeholder, e.g. \"/path1/:id/path2\".\n\t * @returns The extracted ID string.\n\t * @throws GeneralError If the Location header is missing or the ID cannot be extracted.\n\t */\n\tpublic static extractId(headers?: IHttpHeaders, templateUrl?: string): string {\n\t\tconst location = headers?.[HeaderTypes.Location];\n\t\tif (!Is.stringValue(location)) {\n\t\t\tthrow new GeneralError(HttpHeaderHelper.CLASS_NAME, \"locationMissing\");\n\t\t}\n\n\t\tconst withoutQuery = location.split(\"?\")[0];\n\n\t\tif (Is.stringValue(templateUrl) && templateUrl.includes(\":id\")) {\n\t\t\tconst escaped = templateUrl.replace(/[.+^${}()|[\\]\\\\?]/g, \"\\\\$&\");\n\t\t\tconst pattern = new RegExp(escaped.replace(\":id\", \"([^/?#]+)\"));\n\t\t\tconst match = pattern.exec(location);\n\t\t\tif (!Is.stringValue(match?.[1])) {\n\t\t\t\tthrow new GeneralError(HttpHeaderHelper.CLASS_NAME, \"idNotFound\");\n\t\t\t}\n\t\t\treturn decodeURIComponent(match[1]);\n\t\t}\n\n\t\tconst id = withoutQuery.includes(\"/\") ? withoutQuery.split(\"/\").pop() : withoutQuery;\n\n\t\tif (!Is.stringValue(id)) {\n\t\t\tthrow new GeneralError(HttpHeaderHelper.CLASS_NAME, \"idNotFound\");\n\t\t}\n\t\treturn decodeURIComponent(id);\n\t}\n}\n"]}
@@ -57,14 +57,26 @@ export class HttpUrlHelper {
57
57
  catch { }
58
58
  }
59
59
  /**
60
- * Combine the urls parts.
61
- * @param origin The origin to combine.
62
- * @param pathAndSearch The path and search to combine.
63
- * @returns The combined parts.
60
+ * Combine an optional origin and an optional path into a single URL string.
61
+ * Relative paths (no protocol, no leading slash) have a leading slash prepended.
62
+ * Trailing slashes are trimmed from the origin before joining.
63
+ * Returns undefined when both arguments are absent or empty.
64
+ * @param origin The optional origin (e.g. "https://example.com").
65
+ * @param path The optional path or URL template (e.g. "/api/items" or "api/items").
66
+ * @returns The combined string, or undefined when both are absent.
64
67
  */
65
- static combineParts(origin, pathAndSearch) {
66
- if (Is.string(origin) && Is.string(pathAndSearch)) {
67
- return `${StringHelper.trimTrailingSlashes(origin)}/${StringHelper.trimLeadingSlashes(pathAndSearch)}`;
68
+ static combineOriginPath(origin, path) {
69
+ const normalizedPath = Is.stringValue(path) && !path.includes("://")
70
+ ? `/${StringHelper.trimLeadingSlashes(path)}`
71
+ : path;
72
+ if (Is.stringValue(origin) && Is.stringValue(normalizedPath)) {
73
+ return `${StringHelper.trimTrailingSlashes(origin)}${normalizedPath}`;
74
+ }
75
+ else if (Is.stringValue(origin)) {
76
+ return origin;
77
+ }
78
+ else if (Is.stringValue(normalizedPath)) {
79
+ return normalizedPath;
68
80
  }
69
81
  }
70
82
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"httpUrlHelper.js","sourceRoot":"","sources":["../../../src/helpers/httpUrlHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is, StringHelper } from \"@twin.org/core\";\n\n/**\n * Class to help with handling http URLs.\n */\nexport class HttpUrlHelper {\n\t/**\n\t * Extract the origin from the url which includes protocol,host,port.\n\t * @see https://developer.mozilla.org/en-US/docs/Web/API/URL/origin\n\t * @param url The url to extract the origin from.\n\t * @returns The extracted origin.\n\t */\n\tpublic static extractOrigin(url: string): string | undefined {\n\t\ttry {\n\t\t\tconst convertedUrl = new URL(url);\n\t\t\treturn convertedUrl.origin;\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Extract the path from the url.\n\t * @see https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname\n\t * @param url The url to extract the path from.\n\t * @returns The extracted path.\n\t */\n\tpublic static extractPath(url: string): string | undefined {\n\t\ttry {\n\t\t\tconst convertedUrl = new URL(url);\n\t\t\treturn convertedUrl.pathname;\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Extract the search from the url.\n\t * @see https://developer.mozilla.org/en-US/docs/Web/API/URL/search\n\t * @param url The url to extract the search from.\n\t * @returns The extracted search.\n\t */\n\tpublic static extractSearch(url: string): string | undefined {\n\t\ttry {\n\t\t\tconst convertedUrl = new URL(url);\n\t\t\treturn convertedUrl.search;\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Extract the path and search from the url.\n\t * @param url The url to extract the path and search from.\n\t * @returns The extracted path and search.\n\t */\n\tpublic static extractPathAndSearch(url: string): string | undefined {\n\t\ttry {\n\t\t\tconst convertedUrl = new URL(url);\n\t\t\treturn `${convertedUrl.pathname}${convertedUrl.search}`;\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Combine the urls parts.\n\t * @param origin The origin to combine.\n\t * @param pathAndSearch The path and search to combine.\n\t * @returns The combined parts.\n\t */\n\tpublic static combineParts(origin: string, pathAndSearch: string): string | undefined {\n\t\tif (Is.string(origin) && Is.string(pathAndSearch)) {\n\t\t\treturn `${StringHelper.trimTrailingSlashes(origin)}/${StringHelper.trimLeadingSlashes(pathAndSearch)}`;\n\t\t}\n\t}\n\n\t/**\n\t * Encode a single URL path segment per RFC 3986 §3.3.\n\t * Unlike encodeURIComponent, sub-delimiters ($ & + , ; =) and the colon and\n\t * at-sign characters that are valid unencoded in path segments are preserved.\n\t * @see https://datatracker.ietf.org/doc/html/rfc3986#section-3.3\n\t * @param segment The raw path segment value to encode.\n\t * @returns The percent-encoded path segment.\n\t */\n\tpublic static encodeUriPathSegment(segment: string): string {\n\t\t// RFC 3986 §3.3: only encode characters outside the allowed path segment set.\n\t\t// Allowed: unreserved (A-Za-z0-9 - . _ ~), sub-delimiters (! $ & ' ( ) * + , ; =), and : @\n\t\treturn segment.replace(/[^\\w!$&'()*+,.:;=@~-]/g, ch => encodeURIComponent(ch));\n\t}\n\n\t/**\n\t * Replace the origin in the url.\n\t * @param url The url to replace the origin in.\n\t * @param newOrigin The new origin to use.\n\t * @returns The url with the replaced origin.\n\t */\n\tpublic static replaceOrigin(url: string, newOrigin?: string): string {\n\t\tif (!Is.stringValue(url) || !Is.stringValue(newOrigin) || !newOrigin.startsWith(\"http\")) {\n\t\t\treturn url;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsedUrl = new URL(url.startsWith(\"/\") ? `http://placeholder${url}` : url);\n\t\t\tconst newParsedUrl = new URL(newOrigin);\n\t\t\tparsedUrl.protocol = newParsedUrl.protocol;\n\t\t\tparsedUrl.host = newParsedUrl.host;\n\t\t\tparsedUrl.port = newParsedUrl.port;\n\t\t\treturn parsedUrl.toString();\n\t\t} catch {}\n\n\t\treturn url;\n\t}\n\n\t/**\n\t * Add a query string parameter to the url.\n\t * @param url The url to add the query string parameter to.\n\t * @param key The key of the query string parameter.\n\t * @param value The value of the query string parameter.\n\t * @returns The url with the added query string parameter.\n\t */\n\tpublic static addQueryStringParam(url: string, key: string, value: string): string {\n\t\tif (!Is.stringValue(url) || !Is.stringValue(key) || !Is.stringValue(value)) {\n\t\t\treturn url;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsedUrl = new URL(url);\n\t\t\tparsedUrl.searchParams.set(key, value);\n\t\t\treturn parsedUrl.toString();\n\t\t} catch {}\n\n\t\treturn url;\n\t}\n\n\t/**\n\t * Get a query string parameter from the url.\n\t * @param url The url to get the query string parameter from.\n\t * @param key The key of the query string parameter.\n\t * @returns The value of the query string parameter.\n\t */\n\tpublic static getQueryStringParam(url: string, key: string): string | undefined {\n\t\tif (!Is.stringValue(url) || !Is.stringValue(key)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsedUrl = new URL(url);\n\t\t\treturn parsedUrl.searchParams.get(key) ?? undefined;\n\t\t} catch {}\n\n\t\treturn undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"httpUrlHelper.js","sourceRoot":"","sources":["../../../src/helpers/httpUrlHelper.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAElD;;GAEG;AACH,MAAM,OAAO,aAAa;IACzB;;;;;OAKG;IACI,MAAM,CAAC,aAAa,CAAC,GAAW;QACtC,IAAI,CAAC;YACJ,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,OAAO,YAAY,CAAC,MAAM,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,WAAW,CAAC,GAAW;QACpC,IAAI,CAAC;YACJ,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,OAAO,YAAY,CAAC,QAAQ,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,aAAa,CAAC,GAAW;QACtC,IAAI,CAAC;YACJ,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,OAAO,YAAY,CAAC,MAAM,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,oBAAoB,CAAC,GAAW;QAC7C,IAAI,CAAC;YACJ,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,OAAO,GAAG,YAAY,CAAC,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IAED;;;;;;;;OAQG;IACI,MAAM,CAAC,iBAAiB,CAAC,MAAe,EAAE,IAAa;QAC7D,MAAM,cAAc,GACnB,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAC5C,CAAC,CAAC,IAAI,YAAY,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE;YAC7C,CAAC,CAAC,IAAI,CAAC;QAET,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,CAAC;YAC9D,OAAO,GAAG,YAAY,CAAC,mBAAmB,CAAC,MAAM,CAAC,GAAG,cAAc,EAAE,CAAC;QACvE,CAAC;aAAM,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;YACnC,OAAO,MAAM,CAAC;QACf,CAAC;aAAM,IAAI,EAAE,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,CAAC;YAC3C,OAAO,cAAc,CAAC;QACvB,CAAC;IACF,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,oBAAoB,CAAC,OAAe;QACjD,8EAA8E;QAC9E,2FAA2F;QAC3F,OAAO,OAAO,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,EAAE,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC;IAChF,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,aAAa,CAAC,GAAW,EAAE,SAAkB;QAC1D,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACzF,OAAO,GAAG,CAAC;QACZ,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAClF,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;YACxC,SAAS,CAAC,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;YAC3C,SAAS,CAAC,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC;YACnC,SAAS,CAAC,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC;YACnC,OAAO,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QAEV,OAAO,GAAG,CAAC;IACZ,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,mBAAmB,CAAC,GAAW,EAAE,GAAW,EAAE,KAAa;QACxE,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5E,OAAO,GAAG,CAAC;QACZ,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACvC,OAAO,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QAEV,OAAO,GAAG,CAAC;IACZ,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,mBAAmB,CAAC,GAAW,EAAE,GAAW;QACzD,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;YAClD,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,OAAO,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QAEV,OAAO,SAAS,CAAC;IAClB,CAAC;CACD","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is, StringHelper } from \"@twin.org/core\";\n\n/**\n * Class to help with handling http URLs.\n */\nexport class HttpUrlHelper {\n\t/**\n\t * Extract the origin from the url which includes protocol,host,port.\n\t * @see https://developer.mozilla.org/en-US/docs/Web/API/URL/origin\n\t * @param url The url to extract the origin from.\n\t * @returns The extracted origin.\n\t */\n\tpublic static extractOrigin(url: string): string | undefined {\n\t\ttry {\n\t\t\tconst convertedUrl = new URL(url);\n\t\t\treturn convertedUrl.origin;\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Extract the path from the url.\n\t * @see https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname\n\t * @param url The url to extract the path from.\n\t * @returns The extracted path.\n\t */\n\tpublic static extractPath(url: string): string | undefined {\n\t\ttry {\n\t\t\tconst convertedUrl = new URL(url);\n\t\t\treturn convertedUrl.pathname;\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Extract the search from the url.\n\t * @see https://developer.mozilla.org/en-US/docs/Web/API/URL/search\n\t * @param url The url to extract the search from.\n\t * @returns The extracted search.\n\t */\n\tpublic static extractSearch(url: string): string | undefined {\n\t\ttry {\n\t\t\tconst convertedUrl = new URL(url);\n\t\t\treturn convertedUrl.search;\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Extract the path and search from the url.\n\t * @param url The url to extract the path and search from.\n\t * @returns The extracted path and search.\n\t */\n\tpublic static extractPathAndSearch(url: string): string | undefined {\n\t\ttry {\n\t\t\tconst convertedUrl = new URL(url);\n\t\t\treturn `${convertedUrl.pathname}${convertedUrl.search}`;\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Combine an optional origin and an optional path into a single URL string.\n\t * Relative paths (no protocol, no leading slash) have a leading slash prepended.\n\t * Trailing slashes are trimmed from the origin before joining.\n\t * Returns undefined when both arguments are absent or empty.\n\t * @param origin The optional origin (e.g. \"https://example.com\").\n\t * @param path The optional path or URL template (e.g. \"/api/items\" or \"api/items\").\n\t * @returns The combined string, or undefined when both are absent.\n\t */\n\tpublic static combineOriginPath(origin?: string, path?: string): string | undefined {\n\t\tconst normalizedPath =\n\t\t\tIs.stringValue(path) && !path.includes(\"://\")\n\t\t\t\t? `/${StringHelper.trimLeadingSlashes(path)}`\n\t\t\t\t: path;\n\n\t\tif (Is.stringValue(origin) && Is.stringValue(normalizedPath)) {\n\t\t\treturn `${StringHelper.trimTrailingSlashes(origin)}${normalizedPath}`;\n\t\t} else if (Is.stringValue(origin)) {\n\t\t\treturn origin;\n\t\t} else if (Is.stringValue(normalizedPath)) {\n\t\t\treturn normalizedPath;\n\t\t}\n\t}\n\n\t/**\n\t * Encode a single URL path segment per RFC 3986 §3.3.\n\t * Unlike encodeURIComponent, sub-delimiters ($ & + , ; =) and the colon and\n\t * at-sign characters that are valid unencoded in path segments are preserved.\n\t * @see https://datatracker.ietf.org/doc/html/rfc3986#section-3.3\n\t * @param segment The raw path segment value to encode.\n\t * @returns The percent-encoded path segment.\n\t */\n\tpublic static encodeUriPathSegment(segment: string): string {\n\t\t// RFC 3986 §3.3: only encode characters outside the allowed path segment set.\n\t\t// Allowed: unreserved (A-Za-z0-9 - . _ ~), sub-delimiters (! $ & ' ( ) * + , ; =), and : @\n\t\treturn segment.replace(/[^\\w!$&'()*+,.:;=@~-]/g, ch => encodeURIComponent(ch));\n\t}\n\n\t/**\n\t * Replace the origin in the url.\n\t * @param url The url to replace the origin in.\n\t * @param newOrigin The new origin to use.\n\t * @returns The url with the replaced origin.\n\t */\n\tpublic static replaceOrigin(url: string, newOrigin?: string): string {\n\t\tif (!Is.stringValue(url) || !Is.stringValue(newOrigin) || !newOrigin.startsWith(\"http\")) {\n\t\t\treturn url;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsedUrl = new URL(url.startsWith(\"/\") ? `http://placeholder${url}` : url);\n\t\t\tconst newParsedUrl = new URL(newOrigin);\n\t\t\tparsedUrl.protocol = newParsedUrl.protocol;\n\t\t\tparsedUrl.host = newParsedUrl.host;\n\t\t\tparsedUrl.port = newParsedUrl.port;\n\t\t\treturn parsedUrl.toString();\n\t\t} catch {}\n\n\t\treturn url;\n\t}\n\n\t/**\n\t * Add a query string parameter to the url.\n\t * @param url The url to add the query string parameter to.\n\t * @param key The key of the query string parameter.\n\t * @param value The value of the query string parameter.\n\t * @returns The url with the added query string parameter.\n\t */\n\tpublic static addQueryStringParam(url: string, key: string, value: string): string {\n\t\tif (!Is.stringValue(url) || !Is.stringValue(key) || !Is.stringValue(value)) {\n\t\t\treturn url;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsedUrl = new URL(url);\n\t\t\tparsedUrl.searchParams.set(key, value);\n\t\t\treturn parsedUrl.toString();\n\t\t} catch {}\n\n\t\treturn url;\n\t}\n\n\t/**\n\t * Get a query string parameter from the url.\n\t * @param url The url to get the query string parameter from.\n\t * @param key The key of the query string parameter.\n\t * @returns The value of the query string parameter.\n\t */\n\tpublic static getQueryStringParam(url: string, key: string): string | undefined {\n\t\tif (!Is.stringValue(url) || !Is.stringValue(key)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsedUrl = new URL(url);\n\t\t\treturn parsedUrl.searchParams.get(key) ?? undefined;\n\t\t} catch {}\n\n\t\treturn undefined;\n\t}\n}\n"]}
package/dist/es/index.js CHANGED
@@ -6,6 +6,7 @@ export * from "./factories/mimeTypeProcessorFactory.js";
6
6
  export * from "./factories/restRouteProcessorFactory.js";
7
7
  export * from "./factories/socketRouteProcessorFactory.js";
8
8
  export * from "./helpers/httpErrorHelper.js";
9
+ export * from "./helpers/httpHeaderHelper.js";
9
10
  export * from "./helpers/httpParameterHelper.js";
10
11
  export * from "./helpers/httpUrlHelper.js";
11
12
  export * from "./models/api/IServerFavIconResponse.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,yCAAyC,CAAC;AACxD,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,0CAA0C,CAAC;AACzD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,yCAAyC,CAAC;AACxD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,kDAAkD,CAAC;AACjE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,iDAAiD,CAAC;AAChE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,gDAAgD,CAAC;AAC/D,cAAc,sDAAsD,CAAC;AACrE,cAAc,uDAAuD,CAAC;AACtE,cAAc,oDAAoD,CAAC;AACnE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,iDAAiD,CAAC;AAChE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,kDAAkD,CAAC;AACjE,cAAc,2CAA2C,CAAC;AAC1D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yCAAyC,CAAC;AACxD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yCAAyC,CAAC;AACxD,cAAc,sCAAsC,CAAC;AACrD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,wDAAwD,CAAC;AACvE,cAAc,8CAA8C,CAAC;AAC7D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,iCAAiC,CAAC;AAChD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,yBAAyB,CAAC;AACxC,cAAc,wCAAwC,CAAC;AACvD,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,0CAA0C,CAAC;AACzD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,yCAAyC,CAAC;AACxD,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4CAA4C,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./errors/forbiddenError.js\";\nexport * from \"./errors/tooManyRequestsError.js\";\nexport * from \"./factories/mimeTypeProcessorFactory.js\";\nexport * from \"./factories/restRouteProcessorFactory.js\";\nexport * from \"./factories/socketRouteProcessorFactory.js\";\nexport * from \"./helpers/httpErrorHelper.js\";\nexport * from \"./helpers/httpParameterHelper.js\";\nexport * from \"./helpers/httpUrlHelper.js\";\nexport * from \"./models/api/IServerFavIconResponse.js\";\nexport * from \"./models/api/IServerHealthResponse.js\";\nexport * from \"./models/api/IServerInfoResponse.js\";\nexport * from \"./models/api/IServerLivezResponse.js\";\nexport * from \"./models/api/IServerReadyzResponse.js\";\nexport * from \"./models/api/IServerRootResponse.js\";\nexport * from \"./models/api/IServerSpecResponse.js\";\nexport * from \"./models/config/IBaseRestClientConfig.js\";\nexport * from \"./models/config/IBaseSocketClientConfig.js\";\nexport * from \"./models/httpContextIdKeys.js\";\nexport * from \"./models/protocol/IHttpRequest.js\";\nexport * from \"./models/protocol/IHttpRequestContext.js\";\nexport * from \"./models/protocol/IHttpRequestPathParams.js\";\nexport * from \"./models/protocol/IHttpRequestQuery.js\";\nexport * from \"./models/protocol/IHttpResponse.js\";\nexport * from \"./models/protocol/IHttpServerRequest.js\";\nexport * from \"./models/protocol/ISocketRequestContext.js\";\nexport * from \"./models/protocol/ISocketServerRequest.js\";\nexport * from \"./models/requests/INoContentRequest.js\";\nexport * from \"./models/responses/errors/IBadRequestResponse.js\";\nexport * from \"./models/responses/errors/IConflictResponse.js\";\nexport * from \"./models/responses/errors/IForbiddenResponse.js\";\nexport * from \"./models/responses/errors/IInternalServerErrorResponse.js\";\nexport * from \"./models/responses/errors/INotFoundResponse.js\";\nexport * from \"./models/responses/errors/INotImplementedResponse.js\";\nexport * from \"./models/responses/errors/ITooManyRequestsResponse.js\";\nexport * from \"./models/responses/errors/IUnauthorizedResponse.js\";\nexport * from \"./models/responses/errors/IUnprocessableEntityResponse.js\";\nexport * from \"./models/responses/success/IAcceptedResponse.js\";\nexport * from \"./models/responses/success/ICreatedResponse.js\";\nexport * from \"./models/responses/success/INoContentResponse.js\";\nexport * from \"./models/responses/success/IOkResponse.js\";\nexport * from \"./models/routes/IBaseRoute.js\";\nexport * from \"./models/routes/IBaseRouteEntryPoint.js\";\nexport * from \"./models/routes/IRestRoute.js\";\nexport * from \"./models/routes/IRestRouteEntryPoint.js\";\nexport * from \"./models/routes/IRestRouteExample.js\";\nexport * from \"./models/routes/IRestRouteRequestExample.js\";\nexport * from \"./models/routes/IRestRouteResponseAttachmentOptions.js\";\nexport * from \"./models/routes/IRestRouteResponseExample.js\";\nexport * from \"./models/routes/IRestRouteResponseOptions.js\";\nexport * from \"./models/routes/ISocketRoute.js\";\nexport * from \"./models/routes/ISocketRouteEntryPoint.js\";\nexport * from \"./models/routes/ITag.js\";\nexport * from \"./models/server/IBaseRouteProcessor.js\";\nexport * from \"./models/server/IMimeTypeProcessor.js\";\nexport * from \"./models/server/IRestRouteProcessor.js\";\nexport * from \"./models/server/ISocketRouteProcessor.js\";\nexport * from \"./models/server/IWebServer.js\";\nexport * from \"./models/server/IWebServerOptions.js\";\nexport * from \"./models/services/IHealthComponent.js\";\nexport * from \"./models/services/IInformationComponent.js\";\nexport * from \"./models/services/IPlatformComponent.js\";\nexport * from \"./models/services/IServerInfo.js\";\nexport * from \"./models/services/ITenant.js\";\nexport * from \"./models/services/ITenantAdminComponent.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,yCAAyC,CAAC;AACxD,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AACjD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,0CAA0C,CAAC;AACzD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,yCAAyC,CAAC;AACxD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,kDAAkD,CAAC;AACjE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,iDAAiD,CAAC;AAChE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,gDAAgD,CAAC;AAC/D,cAAc,sDAAsD,CAAC;AACrE,cAAc,uDAAuD,CAAC;AACtE,cAAc,oDAAoD,CAAC;AACnE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,iDAAiD,CAAC;AAChE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,kDAAkD,CAAC;AACjE,cAAc,2CAA2C,CAAC;AAC1D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yCAAyC,CAAC;AACxD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yCAAyC,CAAC;AACxD,cAAc,sCAAsC,CAAC;AACrD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,wDAAwD,CAAC;AACvE,cAAc,8CAA8C,CAAC;AAC7D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,iCAAiC,CAAC;AAChD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,yBAAyB,CAAC;AACxC,cAAc,wCAAwC,CAAC;AACvD,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,0CAA0C,CAAC;AACzD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,yCAAyC,CAAC;AACxD,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4CAA4C,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./errors/forbiddenError.js\";\nexport * from \"./errors/tooManyRequestsError.js\";\nexport * from \"./factories/mimeTypeProcessorFactory.js\";\nexport * from \"./factories/restRouteProcessorFactory.js\";\nexport * from \"./factories/socketRouteProcessorFactory.js\";\nexport * from \"./helpers/httpErrorHelper.js\";\nexport * from \"./helpers/httpHeaderHelper.js\";\nexport * from \"./helpers/httpParameterHelper.js\";\nexport * from \"./helpers/httpUrlHelper.js\";\nexport * from \"./models/api/IServerFavIconResponse.js\";\nexport * from \"./models/api/IServerHealthResponse.js\";\nexport * from \"./models/api/IServerInfoResponse.js\";\nexport * from \"./models/api/IServerLivezResponse.js\";\nexport * from \"./models/api/IServerReadyzResponse.js\";\nexport * from \"./models/api/IServerRootResponse.js\";\nexport * from \"./models/api/IServerSpecResponse.js\";\nexport * from \"./models/config/IBaseRestClientConfig.js\";\nexport * from \"./models/config/IBaseSocketClientConfig.js\";\nexport * from \"./models/httpContextIdKeys.js\";\nexport * from \"./models/protocol/IHttpRequest.js\";\nexport * from \"./models/protocol/IHttpRequestContext.js\";\nexport * from \"./models/protocol/IHttpRequestPathParams.js\";\nexport * from \"./models/protocol/IHttpRequestQuery.js\";\nexport * from \"./models/protocol/IHttpResponse.js\";\nexport * from \"./models/protocol/IHttpServerRequest.js\";\nexport * from \"./models/protocol/ISocketRequestContext.js\";\nexport * from \"./models/protocol/ISocketServerRequest.js\";\nexport * from \"./models/requests/INoContentRequest.js\";\nexport * from \"./models/responses/errors/IBadRequestResponse.js\";\nexport * from \"./models/responses/errors/IConflictResponse.js\";\nexport * from \"./models/responses/errors/IForbiddenResponse.js\";\nexport * from \"./models/responses/errors/IInternalServerErrorResponse.js\";\nexport * from \"./models/responses/errors/INotFoundResponse.js\";\nexport * from \"./models/responses/errors/INotImplementedResponse.js\";\nexport * from \"./models/responses/errors/ITooManyRequestsResponse.js\";\nexport * from \"./models/responses/errors/IUnauthorizedResponse.js\";\nexport * from \"./models/responses/errors/IUnprocessableEntityResponse.js\";\nexport * from \"./models/responses/success/IAcceptedResponse.js\";\nexport * from \"./models/responses/success/ICreatedResponse.js\";\nexport * from \"./models/responses/success/INoContentResponse.js\";\nexport * from \"./models/responses/success/IOkResponse.js\";\nexport * from \"./models/routes/IBaseRoute.js\";\nexport * from \"./models/routes/IBaseRouteEntryPoint.js\";\nexport * from \"./models/routes/IRestRoute.js\";\nexport * from \"./models/routes/IRestRouteEntryPoint.js\";\nexport * from \"./models/routes/IRestRouteExample.js\";\nexport * from \"./models/routes/IRestRouteRequestExample.js\";\nexport * from \"./models/routes/IRestRouteResponseAttachmentOptions.js\";\nexport * from \"./models/routes/IRestRouteResponseExample.js\";\nexport * from \"./models/routes/IRestRouteResponseOptions.js\";\nexport * from \"./models/routes/ISocketRoute.js\";\nexport * from \"./models/routes/ISocketRouteEntryPoint.js\";\nexport * from \"./models/routes/ITag.js\";\nexport * from \"./models/server/IBaseRouteProcessor.js\";\nexport * from \"./models/server/IMimeTypeProcessor.js\";\nexport * from \"./models/server/IRestRouteProcessor.js\";\nexport * from \"./models/server/ISocketRouteProcessor.js\";\nexport * from \"./models/server/IWebServer.js\";\nexport * from \"./models/server/IWebServerOptions.js\";\nexport * from \"./models/services/IHealthComponent.js\";\nexport * from \"./models/services/IInformationComponent.js\";\nexport * from \"./models/services/IPlatformComponent.js\";\nexport * from \"./models/services/IServerInfo.js\";\nexport * from \"./models/services/ITenant.js\";\nexport * from \"./models/services/ITenantAdminComponent.js\";\n"]}
@@ -0,0 +1,62 @@
1
+ import { HeaderTypes, MimeTypes, type IHttpHeaders } from "@twin.org/web";
2
+ /**
3
+ * Class to help with handling http headers.
4
+ */
5
+ export declare class HttpHeaderHelper {
6
+ /**
7
+ * Runtime name for the class.
8
+ */
9
+ static readonly CLASS_NAME: string;
10
+ /**
11
+ * Set the Link header with a next cursor relation when a cursor is present.
12
+ * @param headers The response headers to mutate.
13
+ * @param url The request URL used as the base for the link.
14
+ * @param publicOrigin The public origin to substitute into the URL, if any.
15
+ * @param cursor The cursor value; when undefined or empty the header is not set.
16
+ */
17
+ static buildCursor(headers: IHttpHeaders, url: string, publicOrigin: string | undefined, cursor: string | undefined): asserts headers is IHttpHeaders & {
18
+ [HeaderTypes.Link]: string | undefined;
19
+ };
20
+ /**
21
+ * Set the Location header to the encoded ID, optionally placed within a URL template.
22
+ * When the template contains `:id` (e.g. "/path1/:id/path2" or "/path?p=:id") the
23
+ * encoded ID is substituted at that position; otherwise it is appended.
24
+ * When no template is provided the bare encoded ID is used.
25
+ * Callers that need to combine a public origin with a path should use
26
+ * HttpUrlHelper.combineOriginPath to build the template before calling this method.
27
+ * @param headers The response headers to mutate.
28
+ * @param id The resource ID to encode and place.
29
+ * @param urlTemplate The optional URL template (absolute or relative).
30
+ */
31
+ static buildId(headers: IHttpHeaders, id: string, urlTemplate?: string): asserts headers is IHttpHeaders & {
32
+ [HeaderTypes.Location]: string;
33
+ };
34
+ /**
35
+ * Set the Content-Type header to JSON-LD or JSON depending on the request Accept header.
36
+ * Uses HeaderHelper.extractAccept which parses the Accept header per RFC 7231 and returns
37
+ * entries ordered by quality descending, preserving original order for equal q-values.
38
+ * @param headers The response headers to mutate.
39
+ * @param requestHeaders The request headers to inspect for the Accept value.
40
+ */
41
+ static buildJsonContentType(headers: IHttpHeaders, requestHeaders?: IHttpHeaders): asserts headers is IHttpHeaders & {
42
+ [HeaderTypes.ContentType]: typeof MimeTypes.JsonLd | typeof MimeTypes.Json;
43
+ };
44
+ /**
45
+ * Extract the cursor from the Link header's next relation.
46
+ * @param headers The response headers to extract the cursor from.
47
+ * @returns The cursor value or undefined if not present.
48
+ */
49
+ static extractCursor(headers?: IHttpHeaders): string | undefined;
50
+ /**
51
+ * Extract the resource ID from the Location response header.
52
+ * Handles absolute URLs, relative paths, and bare ID values.
53
+ * When a templateUrl containing ':id' is supplied (e.g. "/path1/:id/path2" or
54
+ * "https://host/path/:id") the ID is extracted via pattern matching at the ':id'
55
+ * position. Without a matching template the last path segment is returned.
56
+ * @param headers The response headers containing the Location header.
57
+ * @param templateUrl Optional URL template containing the ':id' placeholder, e.g. "/path1/:id/path2".
58
+ * @returns The extracted ID string.
59
+ * @throws GeneralError If the Location header is missing or the ID cannot be extracted.
60
+ */
61
+ static extractId(headers?: IHttpHeaders, templateUrl?: string): string;
62
+ }
@@ -30,12 +30,15 @@ export declare class HttpUrlHelper {
30
30
  */
31
31
  static extractPathAndSearch(url: string): string | undefined;
32
32
  /**
33
- * Combine the urls parts.
34
- * @param origin The origin to combine.
35
- * @param pathAndSearch The path and search to combine.
36
- * @returns The combined parts.
33
+ * Combine an optional origin and an optional path into a single URL string.
34
+ * Relative paths (no protocol, no leading slash) have a leading slash prepended.
35
+ * Trailing slashes are trimmed from the origin before joining.
36
+ * Returns undefined when both arguments are absent or empty.
37
+ * @param origin The optional origin (e.g. "https://example.com").
38
+ * @param path The optional path or URL template (e.g. "/api/items" or "api/items").
39
+ * @returns The combined string, or undefined when both are absent.
37
40
  */
38
- static combineParts(origin: string, pathAndSearch: string): string | undefined;
41
+ static combineOriginPath(origin?: string, path?: string): string | undefined;
39
42
  /**
40
43
  * Encode a single URL path segment per RFC 3986 §3.3.
41
44
  * Unlike encodeURIComponent, sub-delimiters ($ & + , ; =) and the colon and
@@ -4,6 +4,7 @@ export * from "./factories/mimeTypeProcessorFactory.js";
4
4
  export * from "./factories/restRouteProcessorFactory.js";
5
5
  export * from "./factories/socketRouteProcessorFactory.js";
6
6
  export * from "./helpers/httpErrorHelper.js";
7
+ export * from "./helpers/httpHeaderHelper.js";
7
8
  export * from "./helpers/httpParameterHelper.js";
8
9
  export * from "./helpers/httpUrlHelper.js";
9
10
  export * from "./models/api/IServerFavIconResponse.js";
package/docs/changelog.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.1-next.4](https://github.com/iotaledger/twin-api/compare/api-models-v0.9.1-next.3...api-models-v0.9.1-next.4) (2026-06-30)
4
+
5
+
6
+ ### Features
7
+
8
+ * improved rest handling ([#211](https://github.com/iotaledger/twin-api/issues/211)) ([12c50ea](https://github.com/iotaledger/twin-api/commit/12c50ea7655276eed7a73d311ac5286476c98201))
9
+
10
+ ## [0.9.1-next.3](https://github.com/iotaledger/twin-api/compare/api-models-v0.9.1-next.2...api-models-v0.9.1-next.3) (2026-06-29)
11
+
12
+
13
+ ### Features
14
+
15
+ * enhanced rest handling ([#208](https://github.com/iotaledger/twin-api/issues/208)) ([99d5f3f](https://github.com/iotaledger/twin-api/commit/99d5f3f96d262e57828d98d3f3b1e3da8a863378))
16
+
3
17
  ## [0.9.1-next.2](https://github.com/iotaledger/twin-api/compare/api-models-v0.9.1-next.1...api-models-v0.9.1-next.2) (2026-06-26)
4
18
 
5
19
 
@@ -0,0 +1,182 @@
1
+ # Class: HttpHeaderHelper
2
+
3
+ Class to help with handling http headers.
4
+
5
+ ## Constructors
6
+
7
+ ### Constructor
8
+
9
+ > **new HttpHeaderHelper**(): `HttpHeaderHelper`
10
+
11
+ #### Returns
12
+
13
+ `HttpHeaderHelper`
14
+
15
+ ## Properties
16
+
17
+ ### CLASS\_NAME {#class_name}
18
+
19
+ > `readonly` `static` **CLASS\_NAME**: `string`
20
+
21
+ Runtime name for the class.
22
+
23
+ ## Methods
24
+
25
+ ### buildCursor() {#buildcursor}
26
+
27
+ > `static` **buildCursor**(`headers`, `url`, `publicOrigin`, `cursor`): asserts headers is IHttpHeaders & \{ link: string \| undefined \}
28
+
29
+ Set the Link header with a next cursor relation when a cursor is present.
30
+
31
+ #### Parameters
32
+
33
+ ##### headers
34
+
35
+ `IHttpHeaders`
36
+
37
+ The response headers to mutate.
38
+
39
+ ##### url
40
+
41
+ `string`
42
+
43
+ The request URL used as the base for the link.
44
+
45
+ ##### publicOrigin
46
+
47
+ `string` \| `undefined`
48
+
49
+ The public origin to substitute into the URL, if any.
50
+
51
+ ##### cursor
52
+
53
+ `string` \| `undefined`
54
+
55
+ The cursor value; when undefined or empty the header is not set.
56
+
57
+ #### Returns
58
+
59
+ asserts headers is IHttpHeaders & \{ link: string \| undefined \}
60
+
61
+ ***
62
+
63
+ ### buildId() {#buildid}
64
+
65
+ > `static` **buildId**(`headers`, `id`, `urlTemplate?`): `asserts headers is IHttpHeaders & { location: string }`
66
+
67
+ Set the Location header to the encoded ID, optionally placed within a URL template.
68
+ When the template contains `:id` (e.g. "/path1/:id/path2" or "/path?p=:id") the
69
+ encoded ID is substituted at that position; otherwise it is appended.
70
+ When no template is provided the bare encoded ID is used.
71
+ Callers that need to combine a public origin with a path should use
72
+ HttpUrlHelper.combineOriginPath to build the template before calling this method.
73
+
74
+ #### Parameters
75
+
76
+ ##### headers
77
+
78
+ `IHttpHeaders`
79
+
80
+ The response headers to mutate.
81
+
82
+ ##### id
83
+
84
+ `string`
85
+
86
+ The resource ID to encode and place.
87
+
88
+ ##### urlTemplate?
89
+
90
+ `string`
91
+
92
+ The optional URL template (absolute or relative).
93
+
94
+ #### Returns
95
+
96
+ `asserts headers is IHttpHeaders & { location: string }`
97
+
98
+ ***
99
+
100
+ ### buildJsonContentType() {#buildjsoncontenttype}
101
+
102
+ > `static` **buildJsonContentType**(`headers`, `requestHeaders?`): asserts headers is IHttpHeaders & \{ content-type: "application/json" \| "application/ld+json" \}
103
+
104
+ Set the Content-Type header to JSON-LD or JSON depending on the request Accept header.
105
+ Uses HeaderHelper.extractAccept which parses the Accept header per RFC 7231 and returns
106
+ entries ordered by quality descending, preserving original order for equal q-values.
107
+
108
+ #### Parameters
109
+
110
+ ##### headers
111
+
112
+ `IHttpHeaders`
113
+
114
+ The response headers to mutate.
115
+
116
+ ##### requestHeaders?
117
+
118
+ `IHttpHeaders`
119
+
120
+ The request headers to inspect for the Accept value.
121
+
122
+ #### Returns
123
+
124
+ asserts headers is IHttpHeaders & \{ content-type: "application/json" \| "application/ld+json" \}
125
+
126
+ ***
127
+
128
+ ### extractCursor() {#extractcursor}
129
+
130
+ > `static` **extractCursor**(`headers?`): `string` \| `undefined`
131
+
132
+ Extract the cursor from the Link header's next relation.
133
+
134
+ #### Parameters
135
+
136
+ ##### headers?
137
+
138
+ `IHttpHeaders`
139
+
140
+ The response headers to extract the cursor from.
141
+
142
+ #### Returns
143
+
144
+ `string` \| `undefined`
145
+
146
+ The cursor value or undefined if not present.
147
+
148
+ ***
149
+
150
+ ### extractId() {#extractid}
151
+
152
+ > `static` **extractId**(`headers?`, `templateUrl?`): `string`
153
+
154
+ Extract the resource ID from the Location response header.
155
+ Handles absolute URLs, relative paths, and bare ID values.
156
+ When a templateUrl containing ':id' is supplied (e.g. "/path1/:id/path2" or
157
+ "https://host/path/:id") the ID is extracted via pattern matching at the ':id'
158
+ position. Without a matching template the last path segment is returned.
159
+
160
+ #### Parameters
161
+
162
+ ##### headers?
163
+
164
+ `IHttpHeaders`
165
+
166
+ The response headers containing the Location header.
167
+
168
+ ##### templateUrl?
169
+
170
+ `string`
171
+
172
+ Optional URL template containing the ':id' placeholder, e.g. "/path1/:id/path2".
173
+
174
+ #### Returns
175
+
176
+ `string`
177
+
178
+ The extracted ID string.
179
+
180
+ #### Throws
181
+
182
+ GeneralError If the Location header is missing or the ID cannot be extracted.
@@ -114,31 +114,34 @@ The extracted path and search.
114
114
 
115
115
  ***
116
116
 
117
- ### combineParts() {#combineparts}
117
+ ### combineOriginPath() {#combineoriginpath}
118
118
 
119
- > `static` **combineParts**(`origin`, `pathAndSearch`): `string` \| `undefined`
119
+ > `static` **combineOriginPath**(`origin?`, `path?`): `string` \| `undefined`
120
120
 
121
- Combine the urls parts.
121
+ Combine an optional origin and an optional path into a single URL string.
122
+ Relative paths (no protocol, no leading slash) have a leading slash prepended.
123
+ Trailing slashes are trimmed from the origin before joining.
124
+ Returns undefined when both arguments are absent or empty.
122
125
 
123
126
  #### Parameters
124
127
 
125
- ##### origin
128
+ ##### origin?
126
129
 
127
130
  `string`
128
131
 
129
- The origin to combine.
132
+ The optional origin (e.g. "https://example.com").
130
133
 
131
- ##### pathAndSearch
134
+ ##### path?
132
135
 
133
136
  `string`
134
137
 
135
- The path and search to combine.
138
+ The optional path or URL template (e.g. "/api/items" or "api/items").
136
139
 
137
140
  #### Returns
138
141
 
139
142
  `string` \| `undefined`
140
143
 
141
- The combined parts.
144
+ The combined string, or undefined when both are absent.
142
145
 
143
146
  ***
144
147
 
@@ -5,6 +5,7 @@
5
5
  - [ForbiddenError](classes/ForbiddenError.md)
6
6
  - [TooManyRequestsError](classes/TooManyRequestsError.md)
7
7
  - [HttpErrorHelper](classes/HttpErrorHelper.md)
8
+ - [HttpHeaderHelper](classes/HttpHeaderHelper.md)
8
9
  - [HttpParameterHelper](classes/HttpParameterHelper.md)
9
10
  - [HttpUrlHelper](classes/HttpUrlHelper.md)
10
11
 
package/locales/en.json CHANGED
@@ -1 +1,8 @@
1
- {}
1
+ {
2
+ "error": {
3
+ "httpHeaderHelper": {
4
+ "locationMissing": "The Location response header is missing or empty",
5
+ "idNotFound": "The resource ID could not be extracted from the Location header"
6
+ }
7
+ }
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/api-models",
3
- "version": "0.9.1-next.2",
3
+ "version": "0.9.1-next.4",
4
4
  "description": "Shared API contracts, route types, and response models used across services and clients.",
5
5
  "repository": {
6
6
  "type": "git",