@zudojs/http 1.2.0 → 1.4.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.
Files changed (127) hide show
  1. package/README.md +221 -0
  2. package/dist/httpAdapter/http.adapters.d.ts +24 -3
  3. package/dist/httpAdapter/http.adapters.js +20 -11
  4. package/dist/httpAdapter/node/httpNode.adapter.d.ts +2 -1
  5. package/dist/httpAdapter/node/httpNode.adapter.js +17 -2
  6. package/dist/httpAdapter/node/httpNode.request.js +7 -1
  7. package/dist/httpAdapter/node/httpNode.type.d.ts +14 -0
  8. package/dist/httpAgent/http.agent.d.ts +22 -3
  9. package/dist/httpAgent/http.agent.js +52 -14
  10. package/dist/httpCacheControl/httpCacheControl.freshness.d.ts +7 -1
  11. package/dist/httpCacheControl/httpCacheControl.freshness.js +30 -3
  12. package/dist/httpClient/httpClient.retry.d.ts +17 -12
  13. package/dist/httpClient/httpClient.retry.js +35 -10
  14. package/dist/httpClient/httpClient.type.d.ts +14 -0
  15. package/dist/httpErrors/httpError.base.js +2 -2
  16. package/dist/httpErrors/httpError.util.d.ts +8 -0
  17. package/dist/httpErrors/httpError.util.js +12 -0
  18. package/dist/httpFetchMount/httpFetchMount.core.d.ts +25 -0
  19. package/dist/httpFetchMount/httpFetchMount.core.js +84 -0
  20. package/dist/httpFetchMount/httpFetchMount.request.d.ts +21 -0
  21. package/dist/httpFetchMount/httpFetchMount.request.js +100 -0
  22. package/dist/httpFetchMount/httpFetchMount.type.d.ts +56 -0
  23. package/dist/httpFetchMount/httpFetchMount.type.js +5 -0
  24. package/dist/httpFetchMount/index.d.ts +11 -0
  25. package/dist/httpFetchMount/index.js +10 -0
  26. package/dist/httpKeepAlive/httpKeepAlive.core.js +14 -2
  27. package/dist/httpMiddleware/builtin/logging/httpMiddleware.logging.d.ts +18 -0
  28. package/dist/httpMiddleware/builtin/logging/httpMiddleware.logging.js +17 -1
  29. package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.d.ts +6 -3
  30. package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.js +34 -6
  31. package/dist/httpMiddleware/builtin/security/httpMiddleware.security.d.ts +11 -0
  32. package/dist/httpMiddleware/builtin/security/httpMiddleware.security.js +25 -12
  33. package/dist/httpMiddleware/httpMiddleware.type.d.ts +9 -1
  34. package/dist/httpMiddleware/pipeline/httpPipeline.execution.js +22 -45
  35. package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.d.ts +36 -0
  36. package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.js +57 -0
  37. package/dist/httpMiddleware/pipeline/httpPipeline.helper.d.ts +2 -1
  38. package/dist/httpMiddleware/pipeline/httpPipeline.helper.js +10 -0
  39. package/dist/httpMiddleware/pipeline/index.d.ts +1 -0
  40. package/dist/httpMiddleware/pipeline/index.js +1 -0
  41. package/dist/httpNegotiation/httpNegotiation.core.d.ts +13 -0
  42. package/dist/httpNegotiation/httpNegotiation.core.js +60 -8
  43. package/dist/httpOpenApi/httpOpenApi.document.d.ts +44 -0
  44. package/dist/httpOpenApi/httpOpenApi.document.js +61 -0
  45. package/dist/httpOpenApi/httpOpenApi.mount.d.ts +31 -0
  46. package/dist/httpOpenApi/httpOpenApi.mount.js +58 -0
  47. package/dist/httpOpenApi/httpOpenApi.type.d.ts +54 -0
  48. package/dist/httpOpenApi/httpOpenApi.type.js +5 -0
  49. package/dist/httpOpenApi/index.d.ts +13 -0
  50. package/dist/httpOpenApi/index.js +12 -0
  51. package/dist/httpOpenApi/routeTable/index.d.ts +11 -0
  52. package/dist/httpOpenApi/routeTable/index.js +11 -0
  53. package/dist/httpOpenApi/routeTable/routeTable.collect.d.ts +19 -0
  54. package/dist/httpOpenApi/routeTable/routeTable.collect.js +89 -0
  55. package/dist/httpOpenApi/routeTable/routeTable.merge.d.ts +15 -0
  56. package/dist/httpOpenApi/routeTable/routeTable.merge.js +37 -0
  57. package/dist/httpOpenApi/routeTable/routeTable.template.d.ts +30 -0
  58. package/dist/httpOpenApi/routeTable/routeTable.template.js +67 -0
  59. package/dist/httpProxy/http.proxy.d.ts +9 -0
  60. package/dist/httpProxy/http.proxy.js +35 -3
  61. package/dist/httpQuery/index.d.ts +11 -2
  62. package/dist/httpQuery/index.js +11 -2
  63. package/dist/httpQuery/queryParse/index.d.ts +10 -0
  64. package/dist/httpQuery/queryParse/index.js +10 -0
  65. package/dist/httpQuery/queryParse/queryParse.flat.d.ts +13 -0
  66. package/dist/httpQuery/queryParse/queryParse.flat.js +44 -0
  67. package/dist/httpQuery/queryParse/queryParse.nested.d.ts +25 -0
  68. package/dist/httpQuery/queryParse/queryParse.nested.js +112 -0
  69. package/dist/httpQuery/queryParse/queryParse.tokenizer.d.ts +37 -0
  70. package/dist/httpQuery/queryParse/queryParse.tokenizer.js +95 -0
  71. package/dist/httpQuery/queryRequest/index.d.ts +9 -0
  72. package/dist/httpQuery/queryRequest/index.js +9 -0
  73. package/dist/httpQuery/queryRequest/query.request.d.ts +43 -0
  74. package/dist/httpQuery/queryRequest/query.request.js +96 -0
  75. package/dist/httpQuery/querySerialize/index.d.ts +10 -0
  76. package/dist/httpQuery/querySerialize/index.js +10 -0
  77. package/dist/httpQuery/querySerialize/query.util.d.ts +21 -0
  78. package/dist/httpQuery/querySerialize/query.util.js +67 -0
  79. package/dist/httpQuery/querySerialize/querySerialize.core.d.ts +12 -0
  80. package/dist/httpQuery/querySerialize/querySerialize.core.js +97 -0
  81. package/dist/httpQuery/queryTypes/index.d.ts +11 -0
  82. package/dist/httpQuery/queryTypes/index.js +9 -0
  83. package/dist/httpQuery/queryTypes/query.container.d.ts +16 -0
  84. package/dist/httpQuery/queryTypes/query.container.js +51 -0
  85. package/dist/httpQuery/queryTypes/query.limit.d.ts +25 -0
  86. package/dist/httpQuery/queryTypes/query.limit.js +32 -0
  87. package/dist/httpQuery/queryTypes/query.type.d.ts +62 -0
  88. package/dist/httpQuery/queryTypes/query.type.js +2 -0
  89. package/dist/httpRedirect/http.redirect.d.ts +6 -0
  90. package/dist/httpRedirect/http.redirect.js +53 -2
  91. package/dist/httpRequest/http.request.d.ts +61 -2
  92. package/dist/httpRequest/http.request.js +86 -35
  93. package/dist/httpRequest/httpRequest.context.d.ts +8 -0
  94. package/dist/httpRequest/httpRequest.context.js +23 -18
  95. package/dist/httpRequest/index.d.ts +1 -0
  96. package/dist/httpRequest/index.js +1 -0
  97. package/dist/httpRequest/requestId/httpRequest.requestId.d.ts +25 -0
  98. package/dist/httpRequest/requestId/httpRequest.requestId.js +34 -0
  99. package/dist/httpRequest/requestId/index.d.ts +7 -0
  100. package/dist/httpRequest/requestId/index.js +7 -0
  101. package/dist/httpRequest/target/httpRequest.target.d.ts +2 -2
  102. package/dist/httpRequest/target/httpRequest.target.js +23 -5
  103. package/dist/httpResponse/httpResponse.writer.js +15 -0
  104. package/dist/httpRouter/core/factory/httpRoute.factory.base.d.ts +24 -4
  105. package/dist/httpRouter/core/factory/httpRoute.factory.base.js +73 -17
  106. package/dist/httpRouter/core/factory/httpRoute.factory.js +3 -3
  107. package/dist/httpRouter/core/group/httpRouterGroup.core.js +13 -0
  108. package/dist/httpRouter/core/register/httpRouter.register.js +19 -21
  109. package/dist/httpRouter/core/types/httpRouter.type.d.ts +37 -1
  110. package/dist/httpRouter/core/util/httpRoute.util.d.ts +55 -0
  111. package/dist/httpRouter/core/util/httpRoute.util.js +101 -4
  112. package/dist/httpRouter/dispatch/httpRoute.dispatcher.d.ts +11 -0
  113. package/dist/httpRouter/dispatch/httpRoute.dispatcher.js +25 -6
  114. package/dist/httpRouter/matching/httpRoute.matcher.core.js +51 -6
  115. package/dist/httpRouter/matching/httpRoute.matcher.d.ts +0 -1
  116. package/dist/httpRouter/matching/httpRoute.matcher.js +64 -26
  117. package/dist/httpRouter/pattern/httpRoute.pattern.parse.d.ts +16 -0
  118. package/dist/httpRouter/pattern/httpRoute.pattern.parse.js +40 -11
  119. package/dist/httpRouter/pattern/index.d.ts +1 -1
  120. package/dist/httpRouter/pattern/index.js +1 -1
  121. package/dist/httpSecurity/httpSecurity.config.js +4 -1
  122. package/dist/httpSecurity/httpSecurity.validator.js +16 -7
  123. package/dist/httpServer/factory/httpServer.factory.d.ts +10 -9
  124. package/dist/httpServer/factory/httpServer.factory.js +8 -0
  125. package/dist/index.d.ts +2 -0
  126. package/dist/index.js +2 -0
  127. package/package.json +11 -8
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @zudojs/http/httpQuery/queryParse
3
+ *
4
+ * The query-string tokenizer and the two parsers built on it: a nested,
5
+ * bracket-path parser and a flat one. Both enforce the same limits.
6
+ */
7
+ export { decodeQueryComponent, stripQueryPrefix, tokenizeQuery, tokenizeSearchParams, } from "./queryParse.tokenizer.js";
8
+ export { normalizeQueryValue, parseQuery, parseQueryKey, } from "./queryParse.nested.js";
9
+ export { parseQueryString } from "./queryParse.flat.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @zudojs/http/httpQuery/queryParse
3
+ *
4
+ * The query-string tokenizer and the two parsers built on it: a nested,
5
+ * bracket-path parser and a flat one. Both enforce the same limits.
6
+ */
7
+ export { decodeQueryComponent, stripQueryPrefix, tokenizeQuery, tokenizeSearchParams, } from "./queryParse.tokenizer.js";
8
+ export { normalizeQueryValue, parseQuery, parseQueryKey, } from "./queryParse.nested.js";
9
+ export { parseQueryString } from "./queryParse.flat.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ import type { QueryStringParseOptions } from "../queryTypes/index.js";
2
+ /**
3
+ * Parses a query string into a flat record of decoded values.
4
+ *
5
+ * Unlike {@link parseQuery} this applies no bracket-path nesting and no value
6
+ * coercion: every value is the decoded string, and a repeated name yields an
7
+ * array. Both functions share one tokenizer and one set of limits.
8
+ *
9
+ * The record has a `null` prototype and `__proto__` / `constructor` /
10
+ * `prototype` are dropped, so no parameter name can reach a prototype chain.
11
+ */
12
+ export declare function parseQueryString(input: string | undefined | null, options?: QueryStringParseOptions): Record<string, string | string[]>;
13
+ //# sourceMappingURL=queryParse.flat.d.ts.map
@@ -0,0 +1,44 @@
1
+ import { createQueryContainer, isForbiddenQueryKey, ownValue, resolveLimits, } from "../queryTypes/index.js";
2
+ import { stripQueryPrefix, tokenizeQuery } from "./queryParse.tokenizer.js";
3
+ /**
4
+ * Parses a query string into a flat record of decoded values.
5
+ *
6
+ * Unlike {@link parseQuery} this applies no bracket-path nesting and no value
7
+ * coercion: every value is the decoded string, and a repeated name yields an
8
+ * array. Both functions share one tokenizer and one set of limits.
9
+ *
10
+ * The record has a `null` prototype and `__proto__` / `constructor` /
11
+ * `prototype` are dropped, so no parameter name can reach a prototype chain.
12
+ */
13
+ export function parseQueryString(input, options = {}) {
14
+ const result = createQueryContainer();
15
+ if (input === undefined || input === null || input === "") {
16
+ return result;
17
+ }
18
+ const value = stripQueryPrefix(input);
19
+ if (value.length === 0) {
20
+ return result;
21
+ }
22
+ const allowEmptyKeys = options.allowEmptyKeys ?? true;
23
+ const entries = tokenizeQuery(value, resolveLimits(options), true, options.decodePlusAsSpace !== false, false);
24
+ for (const [key, item] of entries) {
25
+ if (!allowEmptyKeys && key.length === 0) {
26
+ continue;
27
+ }
28
+ if (isForbiddenQueryKey(key)) {
29
+ continue;
30
+ }
31
+ const existing = ownValue(result, key);
32
+ if (existing === undefined) {
33
+ result[key] = item;
34
+ continue;
35
+ }
36
+ if (Array.isArray(existing)) {
37
+ existing.push(item);
38
+ continue;
39
+ }
40
+ result[key] = [existing, item];
41
+ }
42
+ return result;
43
+ }
44
+ //# sourceMappingURL=queryParse.flat.js.map
@@ -0,0 +1,25 @@
1
+ import type { QueryObject, QueryParseOptions, QueryPrimitive } from "../queryTypes/index.js";
2
+ /**
3
+ * Parses a query string into a nested object.
4
+ *
5
+ * The returned objects have a `null` prototype: a parameter named
6
+ * `__proto__` can never reach `Object.prototype`, and a lookup of an
7
+ * unrelated key can never return an inherited member.
8
+ */
9
+ export declare function parseQuery(query: string | URLSearchParams | undefined, options?: QueryParseOptions): QueryObject;
10
+ /**
11
+ * Splits a bracket/dot path such as `a[b][c]` into its segments.
12
+ *
13
+ * A path containing `__proto__`, `constructor` or `prototype` yields an empty
14
+ * array, which callers treat as "drop this parameter entirely".
15
+ */
16
+ export declare function parseQueryKey(key: string): string[];
17
+ /**
18
+ * Coerces the well-known literals and numbers that round-trip exactly.
19
+ *
20
+ * A numeric literal is only converted when `String(Number(value)) === value`,
21
+ * so a 20-digit identifier stays the string it arrived as instead of being
22
+ * silently rewritten to a different number.
23
+ */
24
+ export declare function normalizeQueryValue(value: string): QueryPrimitive;
25
+ //# sourceMappingURL=queryParse.nested.d.ts.map
@@ -0,0 +1,112 @@
1
+ import { createQueryContainer, DEFAULT_QUERY_MAX_DEPTH, HTTPQueryLimitError, isForbiddenQueryKey, isQueryObject, ownValue, resolveLimits, } from "../queryTypes/index.js";
2
+ import { stripQueryPrefix, tokenizeQuery, tokenizeSearchParams, } from "./queryParse.tokenizer.js";
3
+ /**
4
+ * Parses a query string into a nested object.
5
+ *
6
+ * The returned objects have a `null` prototype: a parameter named
7
+ * `__proto__` can never reach `Object.prototype`, and a lookup of an
8
+ * unrelated key can never return an inherited member.
9
+ */
10
+ export function parseQuery(query, options = {}) {
11
+ if (query === undefined) {
12
+ return createQueryContainer();
13
+ }
14
+ const limits = resolveLimits(options);
15
+ const maxDepth = options.maxDepth ?? DEFAULT_QUERY_MAX_DEPTH;
16
+ const commaSeparated = options.commaSeparated === true;
17
+ const entries = query instanceof URLSearchParams
18
+ ? tokenizeSearchParams(query, limits, commaSeparated)
19
+ : tokenizeQuery(stripQueryPrefix(query), limits, options.decode !== false, options.plusAsSpace !== false, commaSeparated);
20
+ const result = createQueryContainer();
21
+ for (const [key, value] of entries) {
22
+ appendQueryValue(result, parseQueryKey(key), value, maxDepth);
23
+ }
24
+ return result;
25
+ }
26
+ /**
27
+ * Splits a bracket/dot path such as `a[b][c]` into its segments.
28
+ *
29
+ * A path containing `__proto__`, `constructor` or `prototype` yields an empty
30
+ * array, which callers treat as "drop this parameter entirely".
31
+ */
32
+ export function parseQueryKey(key) {
33
+ if (!key) {
34
+ return [""];
35
+ }
36
+ const normalized = key.replace(/\]/g, "");
37
+ const segments = normalized.split(/[.[\]]+/).filter((part) => part.length > 0);
38
+ if (segments.some(isForbiddenQueryKey)) {
39
+ return [];
40
+ }
41
+ return segments;
42
+ }
43
+ /**
44
+ * Writes one value into the nested result object.
45
+ *
46
+ * Iterative rather than recursive so a deeply nested key cannot overflow the
47
+ * stack, depth-capped so it cannot allocate without bound, and every level is
48
+ * a null-prototype container read through `hasOwnProperty`.
49
+ */
50
+ function appendQueryValue(target, path, value, maxDepth) {
51
+ if (path.length === 0) {
52
+ return;
53
+ }
54
+ if (path.length > maxDepth) {
55
+ throw new HTTPQueryLimitError("Query parameter nesting exceeds the maximum allowed depth.");
56
+ }
57
+ let current = target;
58
+ for (let index = 0; index < path.length - 1; index += 1) {
59
+ const segment = path[index];
60
+ if (segment === undefined) {
61
+ continue;
62
+ }
63
+ const existing = ownValue(current, segment);
64
+ if (isQueryObject(existing)) {
65
+ current = existing;
66
+ continue;
67
+ }
68
+ const next = createQueryContainer();
69
+ current[segment] = next;
70
+ current = next;
71
+ }
72
+ const leaf = path[path.length - 1];
73
+ if (leaf === undefined) {
74
+ return;
75
+ }
76
+ const existing = ownValue(current, leaf);
77
+ if (existing === undefined) {
78
+ current[leaf] = normalizeQueryValue(value);
79
+ return;
80
+ }
81
+ if (Array.isArray(existing)) {
82
+ existing.push(normalizeQueryValue(value));
83
+ return;
84
+ }
85
+ current[leaf] = [existing, normalizeQueryValue(value)];
86
+ }
87
+ /**
88
+ * Coerces the well-known literals and numbers that round-trip exactly.
89
+ *
90
+ * A numeric literal is only converted when `String(Number(value)) === value`,
91
+ * so a 20-digit identifier stays the string it arrived as instead of being
92
+ * silently rewritten to a different number.
93
+ */
94
+ export function normalizeQueryValue(value) {
95
+ if (value === "null") {
96
+ return null;
97
+ }
98
+ if (value === "true") {
99
+ return true;
100
+ }
101
+ if (value === "false") {
102
+ return false;
103
+ }
104
+ if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) {
105
+ const number = Number(value);
106
+ if (Number.isFinite(number) && String(number) === value) {
107
+ return number;
108
+ }
109
+ }
110
+ return value;
111
+ }
112
+ //# sourceMappingURL=queryParse.nested.js.map
@@ -0,0 +1,37 @@
1
+ import type { QueryLimits } from "../queryTypes/index.js";
2
+ /**
3
+ * Percent-decodes a query component.
4
+ *
5
+ * A malformed sequence such as `%ZZ` makes `decodeURIComponent` throw a
6
+ * `URIError`. Any client can send one, so the raw component is returned
7
+ * instead of propagating an unhandled error onto the request path. The raw
8
+ * bytes are never interpolated into an error message.
9
+ */
10
+ export declare function decodeQueryComponent(value: string, decode?: boolean, plusAsSpace?: boolean): string;
11
+ export declare function stripQueryPrefix(input: string): string;
12
+ /**
13
+ * Splits a raw query string into decoded `[name, value]` pairs, enforcing
14
+ * every configured limit as it goes.
15
+ *
16
+ * This is the single tokenizer for the module: {@link parseQuery} and
17
+ * {@link parseQueryString} are both built on it — including their
18
+ * `URLSearchParams` entry points — so the limits and the decoding rules
19
+ * cannot drift apart.
20
+ *
21
+ * `maxKeys` counts *emitted* pairs, not `&`-separated segments. Counting
22
+ * segments let `?a=1,2,3,...` expand past the cap under `commaSeparated`,
23
+ * because the split happened after the check: a single parameter could yield
24
+ * thousands of entries with `maxKeys` set to 1.
25
+ */
26
+ export declare function tokenizeQuery(query: string, limits: QueryLimits, decode: boolean, plusAsSpace: boolean, commaSeparated: boolean): Array<readonly [string, string]>;
27
+ /**
28
+ * Tokenizes a `URLSearchParams` through the same path as a raw string.
29
+ *
30
+ * `toString()` re-encodes, and the tokenizer decodes again, so the round-trip
31
+ * is lossless. Routing it through the tokenizer is what makes `maxTotalLength`
32
+ * and `commaSeparated` apply here at all: the previous bespoke loop checked
33
+ * only the per-key and per-value caps, so `parseQuery(new URLSearchParams(…))`
34
+ * silently ignored two of the four documented limits and the comma option.
35
+ */
36
+ export declare function tokenizeSearchParams(params: URLSearchParams, limits: QueryLimits, commaSeparated: boolean): Array<readonly [string, string]>;
37
+ //# sourceMappingURL=queryParse.tokenizer.d.ts.map
@@ -0,0 +1,95 @@
1
+ import { HTTPQueryLimitError } from "../queryTypes/index.js";
2
+ /**
3
+ * Percent-decodes a query component.
4
+ *
5
+ * A malformed sequence such as `%ZZ` makes `decodeURIComponent` throw a
6
+ * `URIError`. Any client can send one, so the raw component is returned
7
+ * instead of propagating an unhandled error onto the request path. The raw
8
+ * bytes are never interpolated into an error message.
9
+ */
10
+ export function decodeQueryComponent(value, decode = true, plusAsSpace = true) {
11
+ const input = plusAsSpace ? value.replace(/\+/g, " ") : value;
12
+ if (!decode) {
13
+ return input;
14
+ }
15
+ try {
16
+ return decodeURIComponent(input);
17
+ }
18
+ catch {
19
+ return input;
20
+ }
21
+ }
22
+ export function stripQueryPrefix(input) {
23
+ let value = input;
24
+ if (value.startsWith("?")) {
25
+ value = value.slice(1);
26
+ }
27
+ const hashIndex = value.indexOf("#");
28
+ if (hashIndex !== -1) {
29
+ value = value.slice(0, hashIndex);
30
+ }
31
+ return value;
32
+ }
33
+ /**
34
+ * Splits a raw query string into decoded `[name, value]` pairs, enforcing
35
+ * every configured limit as it goes.
36
+ *
37
+ * This is the single tokenizer for the module: {@link parseQuery} and
38
+ * {@link parseQueryString} are both built on it — including their
39
+ * `URLSearchParams` entry points — so the limits and the decoding rules
40
+ * cannot drift apart.
41
+ *
42
+ * `maxKeys` counts *emitted* pairs, not `&`-separated segments. Counting
43
+ * segments let `?a=1,2,3,...` expand past the cap under `commaSeparated`,
44
+ * because the split happened after the check: a single parameter could yield
45
+ * thousands of entries with `maxKeys` set to 1.
46
+ */
47
+ export function tokenizeQuery(query, limits, decode, plusAsSpace, commaSeparated) {
48
+ if (query.length > limits.maxTotalLength) {
49
+ throw new HTTPQueryLimitError("Query string exceeds the maximum allowed length.");
50
+ }
51
+ const entries = [];
52
+ const push = (key, value) => {
53
+ if (entries.length >= limits.maxKeys) {
54
+ throw new HTTPQueryLimitError("Query string contains too many parameters.");
55
+ }
56
+ entries.push([key, value]);
57
+ };
58
+ for (const part of query.split("&")) {
59
+ if (part === "") {
60
+ continue;
61
+ }
62
+ const separator = part.indexOf("=");
63
+ const rawKey = separator === -1 ? part : part.slice(0, separator);
64
+ const rawValue = separator === -1 ? "" : part.slice(separator + 1);
65
+ const key = decodeQueryComponent(rawKey, decode, plusAsSpace);
66
+ if (key.length > limits.maxKeyLength) {
67
+ throw new HTTPQueryLimitError("Query parameter name exceeds the maximum allowed length.");
68
+ }
69
+ const value = decodeQueryComponent(rawValue, decode, plusAsSpace);
70
+ if (value.length > limits.maxValueLength) {
71
+ throw new HTTPQueryLimitError("Query parameter value exceeds the maximum allowed length.");
72
+ }
73
+ if (commaSeparated && value.includes(",")) {
74
+ for (const item of value.split(",")) {
75
+ push(key, item);
76
+ }
77
+ continue;
78
+ }
79
+ push(key, value);
80
+ }
81
+ return entries;
82
+ }
83
+ /**
84
+ * Tokenizes a `URLSearchParams` through the same path as a raw string.
85
+ *
86
+ * `toString()` re-encodes, and the tokenizer decodes again, so the round-trip
87
+ * is lossless. Routing it through the tokenizer is what makes `maxTotalLength`
88
+ * and `commaSeparated` apply here at all: the previous bespoke loop checked
89
+ * only the per-key and per-value caps, so `parseQuery(new URLSearchParams(…))`
90
+ * silently ignored two of the four documented limits and the comma option.
91
+ */
92
+ export function tokenizeSearchParams(params, limits, commaSeparated) {
93
+ return tokenizeQuery(params.toString(), limits, true, true, commaSeparated);
94
+ }
95
+ //# sourceMappingURL=queryParse.tokenizer.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @zudojs/http/httpQuery/queryRequest
3
+ *
4
+ * Accessors that read the query component of an {@link HTTPRequest}. Every
5
+ * accessor here answers about the same parsed object, so middleware and a
6
+ * handler asking the same question get the same answer.
7
+ */
8
+ export { getQuery, getQueryString, getQueryStrings, getQueryValue, getSearchParams, hasQuery, querySize, } from "./query.request.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @zudojs/http/httpQuery/queryRequest
3
+ *
4
+ * Accessors that read the query component of an {@link HTTPRequest}. Every
5
+ * accessor here answers about the same parsed object, so middleware and a
6
+ * handler asking the same question get the same answer.
7
+ */
8
+ export { getQuery, getQueryString, getQueryStrings, getQueryValue, getSearchParams, hasQuery, querySize, } from "./query.request.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,43 @@
1
+ import type { HTTPRequest } from "../../httpTypes/http.types.js";
2
+ import type { QueryObject, QueryParseOptions, QueryValue } from "../queryTypes/index.js";
3
+ export declare function getQuery(request: HTTPRequest, options?: QueryParseOptions): QueryObject;
4
+ export declare function getQueryValue(request: HTTPRequest, key: string): QueryValue | undefined;
5
+ /**
6
+ * Returns a single parameter as a string.
7
+ *
8
+ * A parameter that appeared more than once, or as a bracket path, has no
9
+ * single string form and yields `undefined`; use {@link getQueryStrings} for
10
+ * those. A literal `?a=null` yields `"null"` — the text the client sent.
11
+ * This used to return `null` while declaring `string | undefined`, so a
12
+ * caller doing `value.length` on a non-`undefined` result crashed.
13
+ */
14
+ export declare function getQueryString(request: HTTPRequest, key: string): string | undefined;
15
+ /**
16
+ * Returns every value a parameter carries, as strings.
17
+ *
18
+ * The parsed shape is attacker-chosen: `?a[b]=1&a=2` puts a null-prototype
19
+ * object inside the array for `a`, and `String()` on such an object throws
20
+ * `TypeError: Cannot convert object to primitive value`. Stringifying through
21
+ * {@link queryValueToString} keeps this total for every reachable input.
22
+ */
23
+ export declare function getQueryStrings(request: HTTPRequest, key: string): string[];
24
+ /**
25
+ * Returns the raw search parameters of a request-target.
26
+ *
27
+ * This is the undecoded, unnested view. It applies none of the parser's
28
+ * limits and drops no key, so prefer {@link getQuery} for anything that
29
+ * reaches application code.
30
+ */
31
+ export declare function getSearchParams(request: HTTPRequest): URLSearchParams;
32
+ /**
33
+ * Reports whether a parameter is present in the parsed query.
34
+ *
35
+ * Answers about the same object {@link getQuery} returns, not about the raw
36
+ * search params. The two used to disagree: `hasQuery(req, "a")` was `false`
37
+ * for `?a[b]=1` (which `getQuery` exposes as `a`), and
38
+ * `hasQuery(req, "__proto__")` was `true` for a key the parser drops.
39
+ */
40
+ export declare function hasQuery(request: HTTPRequest, key: string): boolean;
41
+ /** Number of distinct parameter names in the parsed query. */
42
+ export declare function querySize(request: HTTPRequest): number;
43
+ //# sourceMappingURL=query.request.d.ts.map
@@ -0,0 +1,96 @@
1
+ import { createQueryContainer, ownValue, queryValueToString, } from "../queryTypes/index.js";
2
+ import { parseQuery } from "../queryParse/index.js";
3
+ /**
4
+ * Extracts the query component of a request-target.
5
+ *
6
+ * The fragment is stripped: a proxy or a client that leaves `#…` on the
7
+ * request line must not turn it into a parameter.
8
+ */
9
+ function queryComponent(url) {
10
+ if (!url) {
11
+ return undefined;
12
+ }
13
+ const queryIndex = url.indexOf("?");
14
+ if (queryIndex === -1) {
15
+ return undefined;
16
+ }
17
+ const hashIndex = url.indexOf("#", queryIndex + 1);
18
+ return url.slice(queryIndex + 1, hashIndex === -1 ? undefined : hashIndex);
19
+ }
20
+ export function getQuery(request, options = {}) {
21
+ const query = queryComponent(request.url);
22
+ if (query === undefined) {
23
+ return createQueryContainer();
24
+ }
25
+ return parseQuery(query, options);
26
+ }
27
+ export function getQueryValue(request, key) {
28
+ return ownValue(getQuery(request), key);
29
+ }
30
+ /**
31
+ * Returns a single parameter as a string.
32
+ *
33
+ * A parameter that appeared more than once, or as a bracket path, has no
34
+ * single string form and yields `undefined`; use {@link getQueryStrings} for
35
+ * those. A literal `?a=null` yields `"null"` — the text the client sent.
36
+ * This used to return `null` while declaring `string | undefined`, so a
37
+ * caller doing `value.length` on a non-`undefined` result crashed.
38
+ */
39
+ export function getQueryString(request, key) {
40
+ const value = getQueryValue(request, key);
41
+ if (value === undefined || Array.isArray(value)) {
42
+ return undefined;
43
+ }
44
+ if (typeof value === "object" && value !== null) {
45
+ return undefined;
46
+ }
47
+ return queryValueToString(value);
48
+ }
49
+ /**
50
+ * Returns every value a parameter carries, as strings.
51
+ *
52
+ * The parsed shape is attacker-chosen: `?a[b]=1&a=2` puts a null-prototype
53
+ * object inside the array for `a`, and `String()` on such an object throws
54
+ * `TypeError: Cannot convert object to primitive value`. Stringifying through
55
+ * {@link queryValueToString} keeps this total for every reachable input.
56
+ */
57
+ export function getQueryStrings(request, key) {
58
+ const value = getQueryValue(request, key);
59
+ if (value === undefined) {
60
+ return [];
61
+ }
62
+ if (Array.isArray(value)) {
63
+ return value.map(queryValueToString);
64
+ }
65
+ return [queryValueToString(value)];
66
+ }
67
+ /**
68
+ * Returns the raw search parameters of a request-target.
69
+ *
70
+ * This is the undecoded, unnested view. It applies none of the parser's
71
+ * limits and drops no key, so prefer {@link getQuery} for anything that
72
+ * reaches application code.
73
+ */
74
+ export function getSearchParams(request) {
75
+ const query = queryComponent(request.url ?? "");
76
+ if (query === undefined) {
77
+ return new URLSearchParams();
78
+ }
79
+ return new URLSearchParams(query);
80
+ }
81
+ /**
82
+ * Reports whether a parameter is present in the parsed query.
83
+ *
84
+ * Answers about the same object {@link getQuery} returns, not about the raw
85
+ * search params. The two used to disagree: `hasQuery(req, "a")` was `false`
86
+ * for `?a[b]=1` (which `getQuery` exposes as `a`), and
87
+ * `hasQuery(req, "__proto__")` was `true` for a key the parser drops.
88
+ */
89
+ export function hasQuery(request, key) {
90
+ return getQueryValue(request, key) !== undefined;
91
+ }
92
+ /** Number of distinct parameter names in the parsed query. */
93
+ export function querySize(request) {
94
+ return Object.keys(getQuery(request)).length;
95
+ }
96
+ //# sourceMappingURL=query.request.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @zudojs/http/httpQuery/querySerialize
3
+ *
4
+ * Serializing an object back into a query string, and the clone/merge
5
+ * helpers that operate on an already-parsed query. All three are bounded the
6
+ * same way the parser is.
7
+ */
8
+ export { buildQueryString, stringifyQuery, } from "./querySerialize.core.js";
9
+ export { cloneQuery, mergeQuery } from "./query.util.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @zudojs/http/httpQuery/querySerialize
3
+ *
4
+ * Serializing an object back into a query string, and the clone/merge
5
+ * helpers that operate on an already-parsed query. All three are bounded the
6
+ * same way the parser is.
7
+ */
8
+ export { buildQueryString, stringifyQuery, } from "./querySerialize.core.js";
9
+ export { cloneQuery, mergeQuery } from "./query.util.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,21 @@
1
+ import type { QueryObject } from "../queryTypes/index.js";
2
+ /**
3
+ * Deep-copies a parsed query object.
4
+ *
5
+ * Structural rather than a `JSON.parse(JSON.stringify(…))` round-trip. That
6
+ * round-trip rebuilt every level with `Object.prototype`, so the clone of a
7
+ * hardened, null-prototype query silently regained `toString`, `constructor`
8
+ * and every other inherited member — the exact property the parser exists to
9
+ * guarantee. It also violated the repo rule against calling `JSON` directly.
10
+ */
11
+ export declare function cloneQuery(query: QueryObject): QueryObject;
12
+ /**
13
+ * Merges query objects left to right into a fresh null-prototype object.
14
+ *
15
+ * Every value is deep-copied on the way in. Assigning the source's own
16
+ * nested object by reference made the result alias its inputs, so mutating
17
+ * `merged.a.b` also changed the source query the caller had already handed
18
+ * to something else.
19
+ */
20
+ export declare function mergeQuery(...queries: QueryObject[]): QueryObject;
21
+ //# sourceMappingURL=query.util.d.ts.map
@@ -0,0 +1,67 @@
1
+ import { createQueryContainer, DEFAULT_QUERY_MAX_DEPTH, HTTPQueryLimitError, isForbiddenQueryKey, isQueryObject, ownValue, } from "../queryTypes/index.js";
2
+ /**
3
+ * Deep-copies a parsed query object.
4
+ *
5
+ * Structural rather than a `JSON.parse(JSON.stringify(…))` round-trip. That
6
+ * round-trip rebuilt every level with `Object.prototype`, so the clone of a
7
+ * hardened, null-prototype query silently regained `toString`, `constructor`
8
+ * and every other inherited member — the exact property the parser exists to
9
+ * guarantee. It also violated the repo rule against calling `JSON` directly.
10
+ */
11
+ export function cloneQuery(query) {
12
+ return cloneQueryObject(query, DEFAULT_QUERY_MAX_DEPTH);
13
+ }
14
+ function cloneQueryObject(source, depth) {
15
+ if (depth <= 0) {
16
+ throw new HTTPQueryLimitError("Query object nesting exceeds the maximum allowed depth.");
17
+ }
18
+ const result = createQueryContainer();
19
+ for (const [key, value] of Object.entries(source)) {
20
+ if (isForbiddenQueryKey(key)) {
21
+ continue;
22
+ }
23
+ result[key] = cloneQueryValue(value, depth);
24
+ }
25
+ return result;
26
+ }
27
+ function cloneQueryValue(value, depth) {
28
+ if (Array.isArray(value)) {
29
+ return value.map((item) => cloneQueryValue(item, depth - 1));
30
+ }
31
+ if (isQueryObject(value)) {
32
+ return cloneQueryObject(value, depth - 1);
33
+ }
34
+ return value;
35
+ }
36
+ /**
37
+ * Merges query objects left to right into a fresh null-prototype object.
38
+ *
39
+ * Every value is deep-copied on the way in. Assigning the source's own
40
+ * nested object by reference made the result alias its inputs, so mutating
41
+ * `merged.a.b` also changed the source query the caller had already handed
42
+ * to something else.
43
+ */
44
+ export function mergeQuery(...queries) {
45
+ const result = createQueryContainer();
46
+ for (const query of queries) {
47
+ mergeQueryObject(result, query, DEFAULT_QUERY_MAX_DEPTH);
48
+ }
49
+ return result;
50
+ }
51
+ function mergeQueryObject(target, source, depth) {
52
+ if (depth <= 0) {
53
+ throw new HTTPQueryLimitError("Query object nesting exceeds the maximum allowed depth.");
54
+ }
55
+ for (const [key, value] of Object.entries(source)) {
56
+ if (isForbiddenQueryKey(key)) {
57
+ continue;
58
+ }
59
+ const existing = ownValue(target, key);
60
+ if (isQueryObject(existing) && isQueryObject(value)) {
61
+ mergeQueryObject(existing, value, depth - 1);
62
+ continue;
63
+ }
64
+ target[key] = cloneQueryValue(value, depth);
65
+ }
66
+ }
67
+ //# sourceMappingURL=query.util.js.map
@@ -0,0 +1,12 @@
1
+ import type { QueryObject, QueryStringifyOptions } from "../queryTypes/index.js";
2
+ /**
3
+ * Serializes an object into a query string.
4
+ *
5
+ * Bounded in the same two ways the parser is. Nesting deeper than `maxDepth`
6
+ * and any cycle both throw {@link HTTPQueryLimitError}; previously either one
7
+ * produced a bare `RangeError: Maximum call stack size exceeded` from inside
8
+ * the walk, which no caller could distinguish from an internal fault.
9
+ */
10
+ export declare function stringifyQuery(query: QueryObject | Record<string, unknown>, options?: QueryStringifyOptions): string;
11
+ export declare function buildQueryString(query: QueryObject | Record<string, unknown>, options?: QueryStringifyOptions): string;
12
+ //# sourceMappingURL=querySerialize.core.d.ts.map