typia 15.0.0 → 15.0.1

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 (110) hide show
  1. package/lib/http.d.ts +49 -24
  2. package/lib/http.js.map +1 -1
  3. package/lib/http.mjs.map +1 -1
  4. package/lib/internal/_httpFormDataReadBigint.js +7 -5
  5. package/lib/internal/_httpFormDataReadBigint.js.map +1 -1
  6. package/lib/internal/_httpFormDataReadBigint.mjs +1 -1
  7. package/lib/internal/_httpFormDataReadBigint.mjs.map +1 -1
  8. package/lib/internal/_httpFormDataReadBoolean.js +12 -10
  9. package/lib/internal/_httpFormDataReadBoolean.js.map +1 -1
  10. package/lib/internal/_httpFormDataReadBoolean.mjs +1 -1
  11. package/lib/internal/_httpFormDataReadBoolean.mjs.map +1 -1
  12. package/lib/internal/_httpFormDataReadNumber.js +7 -5
  13. package/lib/internal/_httpFormDataReadNumber.js.map +1 -1
  14. package/lib/internal/_httpFormDataReadNumber.mjs +1 -1
  15. package/lib/internal/_httpFormDataReadNumber.mjs.map +1 -1
  16. package/lib/internal/_httpFormDataReadString.d.ts +9 -1
  17. package/lib/internal/_httpFormDataReadString.js +10 -2
  18. package/lib/internal/_httpFormDataReadString.js.map +1 -1
  19. package/lib/internal/_httpFormDataReadString.mjs +9 -1
  20. package/lib/internal/_httpFormDataReadString.mjs.map +1 -1
  21. package/lib/internal/_httpHeaderReadBigint.js +5 -1
  22. package/lib/internal/_httpHeaderReadBigint.js.map +1 -1
  23. package/lib/internal/_httpHeaderReadBigint.mjs +2 -1
  24. package/lib/internal/_httpHeaderReadBigint.mjs.map +1 -1
  25. package/lib/internal/_httpHeaderReadNumber.js +5 -1
  26. package/lib/internal/_httpHeaderReadNumber.js.map +1 -1
  27. package/lib/internal/_httpHeaderReadNumber.mjs +2 -1
  28. package/lib/internal/_httpHeaderReadNumber.mjs.map +1 -1
  29. package/lib/internal/_httpParameterReadBigint.js +4 -0
  30. package/lib/internal/_httpParameterReadBigint.js.map +1 -1
  31. package/lib/internal/_httpParameterReadBigint.mjs +1 -0
  32. package/lib/internal/_httpParameterReadBigint.mjs.map +1 -1
  33. package/lib/internal/_httpParameterReadNumber.js +4 -0
  34. package/lib/internal/_httpParameterReadNumber.js.map +1 -1
  35. package/lib/internal/_httpParameterReadNumber.mjs +1 -0
  36. package/lib/internal/_httpParameterReadNumber.mjs.map +1 -1
  37. package/lib/internal/_httpParameterReadString.d.ts +9 -1
  38. package/lib/internal/_httpParameterReadString.js +9 -1
  39. package/lib/internal/_httpParameterReadString.js.map +1 -1
  40. package/lib/internal/_httpParameterReadString.mjs +9 -1
  41. package/lib/internal/_httpParameterReadString.mjs.map +1 -1
  42. package/lib/internal/_httpQueryReadBigint.js +8 -1
  43. package/lib/internal/_httpQueryReadBigint.js.map +1 -1
  44. package/lib/internal/_httpQueryReadBigint.mjs +1 -1
  45. package/lib/internal/_httpQueryReadBigint.mjs.map +1 -1
  46. package/lib/internal/_httpQueryReadBoolean.js +4 -1
  47. package/lib/internal/_httpQueryReadBoolean.js.map +1 -1
  48. package/lib/internal/_httpQueryReadBoolean.mjs +1 -1
  49. package/lib/internal/_httpQueryReadBoolean.mjs.map +1 -1
  50. package/lib/internal/_httpQueryReadNumber.js +8 -1
  51. package/lib/internal/_httpQueryReadNumber.js.map +1 -1
  52. package/lib/internal/_httpQueryReadNumber.mjs +1 -1
  53. package/lib/internal/_httpQueryReadNumber.mjs.map +1 -1
  54. package/lib/internal/_httpQueryReadString.d.ts +9 -1
  55. package/lib/internal/_httpQueryReadString.js +9 -1
  56. package/lib/internal/_httpQueryReadString.js.map +1 -1
  57. package/lib/internal/_httpQueryReadString.mjs +9 -1
  58. package/lib/internal/_httpQueryReadString.mjs.map +1 -1
  59. package/lib/internal/_randomBigint.d.ts +12 -1
  60. package/lib/internal/_randomBigint.js +90 -2
  61. package/lib/internal/_randomBigint.js.map +1 -1
  62. package/lib/internal/_randomBigint.mjs +70 -2
  63. package/lib/internal/_randomBigint.mjs.map +1 -1
  64. package/lib/internal/_randomInteger.js +21 -12
  65. package/lib/internal/_randomInteger.js.map +1 -1
  66. package/lib/internal/_randomInteger.mjs +21 -16
  67. package/lib/internal/_randomInteger.mjs.map +1 -1
  68. package/lib/internal/_randomNumber.js +19 -10
  69. package/lib/internal/_randomNumber.js.map +1 -1
  70. package/lib/internal/_randomNumber.mjs +19 -14
  71. package/lib/internal/_randomNumber.mjs.map +1 -1
  72. package/native/cmd/ttsc-typia/llm_evaluation_rejects_invalid_probability_test.go +9 -0
  73. package/native/cmd/ttsc-typia/random_recursive_min_items_diagnostic_test.go +18 -1
  74. package/native/core/factories/ExpressionFactory.go +16 -1
  75. package/native/core/factories/MetadataCommentTagFactory.go +177 -29
  76. package/native/core/factories/expression_factory_number_spells_non_finite_values_test.go +69 -0
  77. package/native/core/factories/metadata_comment_tag_factory_holds_bigint_tags_exact_test.go +168 -0
  78. package/native/core/factories/metadata_comment_tag_factory_reads_javascript_numbers_test.go +158 -0
  79. package/native/core/programmers/functional/FunctionalAssertParametersProgrammer.go +1 -3
  80. package/native/core/programmers/functional/FunctionalIsParametersProgrammer.go +107 -9
  81. package/native/core/programmers/functional/FunctionalValidateParametersProgrammer.go +1 -3
  82. package/native/core/programmers/http/HttpFormDataProgrammer.go +41 -2
  83. package/native/core/programmers/http/HttpHeadersProgrammer.go +56 -4
  84. package/native/core/programmers/http/HttpParameterProgrammer.go +1 -3
  85. package/native/core/programmers/http/HttpQueryProgrammer.go +26 -43
  86. package/native/core/programmers/iterate/json_schema_jsDocTags.go +10 -3
  87. package/native/core/programmers/iterate/json_schema_jsdoc_tags_cast_reads_javascript_numbers_test.go +64 -0
  88. package/native/core/programmers/llm/LlmEvaluationProgrammer.go +6 -2
  89. package/native/core/programmers/llm/LlmSchemaProgrammer.go +34 -4
  90. package/native/core/utils/NumberUtil.go +195 -0
  91. package/native/core/utils/number_util_rational_keeps_written_value_test.go +62 -0
  92. package/native/core/utils/number_util_reads_javascript_number_grammar_test.go +147 -0
  93. package/package.json +3 -3
  94. package/src/http.ts +61 -34
  95. package/src/internal/_httpFormDataReadBigint.ts +3 -1
  96. package/src/internal/_httpFormDataReadBoolean.ts +3 -1
  97. package/src/internal/_httpFormDataReadNumber.ts +3 -1
  98. package/src/internal/_httpFormDataReadString.ts +10 -1
  99. package/src/internal/_httpHeaderReadBigint.ts +6 -1
  100. package/src/internal/_httpHeaderReadNumber.ts +6 -1
  101. package/src/internal/_httpParameterReadBigint.ts +3 -0
  102. package/src/internal/_httpParameterReadNumber.ts +3 -0
  103. package/src/internal/_httpParameterReadString.ts +12 -2
  104. package/src/internal/_httpQueryReadBigint.ts +7 -1
  105. package/src/internal/_httpQueryReadBoolean.ts +3 -1
  106. package/src/internal/_httpQueryReadNumber.ts +7 -1
  107. package/src/internal/_httpQueryReadString.ts +10 -1
  108. package/src/internal/_randomBigint.ts +111 -3
  109. package/src/internal/_randomInteger.ts +31 -16
  110. package/src/internal/_randomNumber.ts +23 -12
@@ -0,0 +1,147 @@
1
+ package utils
2
+
3
+ import (
4
+ "math"
5
+ "testing"
6
+ )
7
+
8
+ // TestNumberUtilReadsJavaScriptNumberGrammar verifies NumberUtil.Read against
9
+ // JavaScript's `Number()`.
10
+ //
11
+ // Tag values and JSDoc extensions are TypeScript source, so the grammar that
12
+ // reads them is JavaScript's StringNumericLiteral. The expectations below are
13
+ // what `Number(text)` returns in JavaScript (samchon/typia#2442), not what the
14
+ // Go float parser accepts; the Go-only spellings (`NaN`, `Inf`, `0x1p4`,
15
+ // `1_000`) are the cases that used to leak through. Text without digits is the
16
+ // one deliberate difference: `Number("")` is 0, but it names no number here.
17
+ //
18
+ // 1. Read decimal, signed, exponent, and point-edge spellings.
19
+ // 2. Read hexadecimal, octal, and binary integers, and reject signed ones.
20
+ // 3. Classify `Infinity`, overflow, and underflow by finiteness.
21
+ // 4. Reject Go-only spellings, separators, and empty or blank text.
22
+ // 5. Spell values back exactly as JavaScript's `String()` does.
23
+ // 6. Read integer spellings exactly, beyond double precision.
24
+ func TestNumberUtilReadsJavaScriptNumberGrammar(t *testing.T) {
25
+ finite := func(value float64) NumberUtil_Reading {
26
+ return NumberUtil_Reading{Value: value, Numeric: true, Finite: true}
27
+ }
28
+ infinite := func(sign int) NumberUtil_Reading {
29
+ return NumberUtil_Reading{Value: math.Inf(sign), Numeric: true, Finite: false}
30
+ }
31
+ invalid := NumberUtil_Reading{}
32
+ for _, item := range []struct {
33
+ text string
34
+ expected NumberUtil_Reading
35
+ }{
36
+ // Decimal notation, including every point and sign edge `Number()` admits.
37
+ {"0", finite(0)},
38
+ {"3", finite(3)},
39
+ {"-2.5", finite(-2.5)},
40
+ {"+5", finite(5)},
41
+ {".5", finite(0.5)},
42
+ {"5.", finite(5)},
43
+ {"1e3", finite(1000)},
44
+ {"1E-3", finite(0.001)},
45
+ {"-0", finite(0)},
46
+ {" 12\t", finite(12)},
47
+ {"\u00a012\u2028", finite(12)},
48
+ {"007", finite(7)},
49
+ // Non-decimal integers: unsigned only, any letter case in the prefix.
50
+ {"0x10", finite(16)},
51
+ {"0XfF", finite(255)},
52
+ {"0o7", finite(7)},
53
+ {"0b101", finite(5)},
54
+ {"-0x10", invalid},
55
+ {"+0x10", invalid},
56
+ {"0o8", invalid},
57
+ {"0b2", invalid},
58
+ {"0x", invalid},
59
+ // Non-finite values are numeric but not finite.
60
+ {"Infinity", infinite(1)},
61
+ {"+Infinity", infinite(1)},
62
+ {"-Infinity", infinite(-1)},
63
+ {"1e400", infinite(1)},
64
+ {"-1e400", infinite(-1)},
65
+ {"1e-400", finite(0)},
66
+ // Go-only spellings and separators: NaN in JavaScript.
67
+ {"NaN", invalid},
68
+ {"nan", invalid},
69
+ {"Inf", invalid},
70
+ {"inf", invalid},
71
+ {"-inf", invalid},
72
+ {"infinity", invalid},
73
+ {"0x1p4", invalid},
74
+ {"1_000", invalid},
75
+ {"1e", invalid},
76
+ {".", invalid},
77
+ {"1.2.3", invalid},
78
+ {"12px", invalid},
79
+ // No digits at all.
80
+ {"", invalid},
81
+ {" ", invalid},
82
+ {"+", invalid},
83
+ } {
84
+ actual := NumberUtil.Read(item.text)
85
+ if actual != item.expected || math.Signbit(actual.Value) != math.Signbit(item.expected.Value) {
86
+ t.Fatalf("Read(%q) = %+v, expected %+v", item.text, actual, item.expected)
87
+ }
88
+ }
89
+
90
+ // String: the oracle is JavaScript's `String(value)`.
91
+ for _, item := range []struct {
92
+ value float64
93
+ expected string
94
+ }{
95
+ {0, "0"},
96
+ {math.Copysign(0, -1), "0"},
97
+ {16, "16"},
98
+ {-2.5, "-2.5"},
99
+ {0.001, "0.001"},
100
+ {0.000001, "0.000001"},
101
+ {1e6, "1000000"},
102
+ {123456789012345680000, "123456789012345680000"},
103
+ {1e21, "1e+21"},
104
+ {1e-7, "1e-7"},
105
+ {-1e-7, "-1e-7"},
106
+ {1.23e-18, "1.23e-18"},
107
+ {1.5e300, "1.5e+300"},
108
+ {9007199254740993, "9007199254740992"},
109
+ {math.NaN(), "NaN"},
110
+ {math.Inf(1), "Infinity"},
111
+ {math.Inf(-1), "-Infinity"},
112
+ } {
113
+ if actual := NumberUtil.String(item.value); actual != item.expected {
114
+ t.Fatalf("String(%v) = %q, expected %q", item.value, actual, item.expected)
115
+ }
116
+ }
117
+
118
+ // Integer: exact digits for integer spellings, nothing for the rest.
119
+ for _, item := range []struct {
120
+ text string
121
+ expected string
122
+ ok bool
123
+ }{
124
+ {"9007199254740993", "9007199254740993", true},
125
+ {"-12", "-12", true},
126
+ {"+5", "5", true},
127
+ {"007", "7", true},
128
+ {"-0", "0", true},
129
+ {" 42 ", "42", true},
130
+ {"0x10", "16", true},
131
+ {"0b101", "5", true},
132
+ {"0o17", "15", true},
133
+ {"0xffffffffffffffffff", "4722366482869645213695", true},
134
+ {"1e3", "", false},
135
+ {"1.0", "", false},
136
+ {"-0x10", "", false},
137
+ {"0o8", "", false},
138
+ {"", "", false},
139
+ {"Infinity", "", false},
140
+ } {
141
+ integer, ok := NumberUtil.Integer(item.text)
142
+ if ok != item.ok || (ok && integer.String() != item.expected) {
143
+ t.Fatalf("Integer(%q) = %v, %v; expected %q, %v", item.text, integer, ok, item.expected, item.ok)
144
+ }
145
+ }
146
+
147
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typia",
3
- "version": "15.0.0",
3
+ "version": "15.0.1",
4
4
  "description": "Superfast runtime validators with only one line",
5
5
  "main": "lib/index.js",
6
6
  "exports": {
@@ -43,8 +43,8 @@
43
43
  "homepage": "https://typia.io",
44
44
  "dependencies": {
45
45
  "randexp": "^0.5.3",
46
- "@typia/utils": "^15.0.0",
47
- "@typia/interface": "^15.0.0"
46
+ "@typia/interface": "^15.0.1",
47
+ "@typia/utils": "^15.0.1"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "ttsc": ">=0.19.2"
package/src/http.ts CHANGED
@@ -22,7 +22,10 @@ import { NoTransformConfigurationError } from "./transformers/NoTransformConfigu
22
22
  * Decodes `FormData` into type `T`.
23
23
  *
24
24
  * Parses a `FormData` instance with automatic type casting. Properties typed as
25
- * `boolean` or `Blob` are cast to expected types during decoding.
25
+ * `boolean`, `bigint`, or `number` are cast from their text, and `Blob` and
26
+ * `File` properties are read as given, except that the text `null` reads as
27
+ * `null`: as `undefined` instead for an optional single property whose type
28
+ * does not admit `null`, and always as `null` for an array element.
26
29
  *
27
30
  * Type `T` constraints:
28
31
  *
@@ -32,6 +35,11 @@ import { NoTransformConfigurationError } from "./transformers/NoTransformConfigu
32
35
  * types allowed
33
36
  * 4. No union types allowed
34
37
  *
38
+ * An absent key decodes a required array to `[]`, a nullable one to `null`, and
39
+ * an optional one to `undefined`. A string property keeps the text `null`
40
+ * unless its type admits `null`, and a blank numeric value decodes to
41
+ * `undefined`.
42
+ *
35
43
  * Does not validate the decoded value. For validation, use:
36
44
  *
37
45
  * - {@link assertFormData} — Throws on type mismatch
@@ -164,8 +172,8 @@ export function validateFormData(): never {
164
172
  * Decodes URL query string into type `T`.
165
173
  *
166
174
  * Parses a query string or `URLSearchParams` instance with automatic type
167
- * casting. Properties typed as `boolean` or `number` are cast to expected types
168
- * during decoding.
175
+ * casting. Properties typed as `boolean`, `bigint`, or `number` are cast to
176
+ * expected types during decoding.
169
177
  *
170
178
  * Type `T` constraints:
171
179
  *
@@ -174,6 +182,12 @@ export function validateFormData(): never {
174
182
  * 3. Only `boolean`, `bigint`, `number`, `string` or their array types allowed
175
183
  * 4. No union types allowed
176
184
  *
185
+ * An absent key decodes a required array to `[]`, a nullable one to `null`, and
186
+ * an optional one to `undefined`, since a query string has no other spelling
187
+ * for an empty array. An absent required non-array property throws
188
+ * `Error("missing <key>")`. A string property keeps the text `null` unless its
189
+ * type admits `null`, and a blank numeric value decodes to `undefined`.
190
+ *
177
191
  * Does not validate the decoded value. For validation, use:
178
192
  *
179
193
  * - {@link assertQuery} — Throws on type mismatch
@@ -308,14 +322,15 @@ export function validateQuery(): never {
308
322
  * Decodes HTTP headers into type `T`.
309
323
  *
310
324
  * Parses HTTP headers object with automatic type casting. Properties typed as
311
- * `boolean` or `number` are cast to expected types during decoding. Compatible
312
- * with Express and Fastify request headers.
325
+ * `boolean`, `bigint`, or `number` are cast to expected types during decoding.
326
+ * Compatible with Express and Fastify request headers.
313
327
  *
314
328
  * Type `T` constraints:
315
329
  *
316
330
  * 1. Must be an object type
317
331
  * 2. No dynamic properties allowed
318
- * 3. Property keys must be lowercase
332
+ * 3. Property keys are read by their lowercase name; two keys that differ only by
333
+ * case are not allowed
319
334
  * 4. Property values cannot be `null` (but `undefined` is allowed)
320
335
  * 5. Only `boolean`, `bigint`, `number`, `string` or their array types allowed
321
336
  * 6. No union types allowed
@@ -326,6 +341,10 @@ export function validateQuery(): never {
326
341
  * `max-forwards`, `proxy-authorization`, `referer`, `retry-after`, `server`,
327
342
  * `user-agent`
328
343
  *
344
+ * An absent optional array header, or one given as an empty array, is omitted,
345
+ * and an absent required one decodes to `[]`. `set-cookie` values are never
346
+ * split. A blank numeric header decodes to `undefined`.
347
+ *
329
348
  * Does not validate the decoded value. For validation, use:
330
349
  *
331
350
  * - {@link assertHeaders} — Throws on type mismatch
@@ -357,7 +376,8 @@ export function headers(): never {
357
376
  *
358
377
  * 1. Must be an object type
359
378
  * 2. No dynamic properties allowed
360
- * 3. Property keys must be lowercase
379
+ * 3. Property keys are read by their lowercase name; two keys that differ only by
380
+ * case are not allowed
361
381
  * 4. Property values cannot be `null` (but `undefined` is allowed)
362
382
  * 5. Only `boolean`, `bigint`, `number`, `string` or their array types allowed
363
383
  * 6. No union types allowed
@@ -403,7 +423,8 @@ export function assertHeaders(): never {
403
423
  *
404
424
  * 1. Must be an object type
405
425
  * 2. No dynamic properties allowed
406
- * 3. Property keys must be lowercase
426
+ * 3. Property keys are read by their lowercase name; two keys that differ only by
427
+ * case are not allowed
407
428
  * 4. Property values cannot be `null` (but `undefined` is allowed)
408
429
  * 5. Only `boolean`, `bigint`, `number`, `string` or their array types allowed
409
430
  * 6. No union types allowed
@@ -446,7 +467,8 @@ export function isHeaders(): never {
446
467
  *
447
468
  * 1. Must be an object type
448
469
  * 2. No dynamic properties allowed
449
- * 3. Property keys must be lowercase
470
+ * 3. Property keys are read by their lowercase name; two keys that differ only by
471
+ * case are not allowed
450
472
  * 4. Property values cannot be `null` (but `undefined` is allowed)
451
473
  * 5. Only `boolean`, `bigint`, `number`, `string` or their array types allowed
452
474
  * 6. No union types allowed
@@ -484,12 +506,15 @@ export function validateHeaders(): never {
484
506
  * Decodes URL path parameter into type `T`.
485
507
  *
486
508
  * Parses a path parameter string with automatic type casting. When type `T` is
487
- * `boolean` or `number`, casts the string value to the expected type. Also
488
- * performs type assertion via {@link assert}, throwing {@link TypeGuardError} on
489
- * mismatch.
509
+ * `boolean`, `bigint`, or `number`, casts the string value to the expected
510
+ * type. Also performs type assertion via {@link assert}, throwing
511
+ * {@link TypeGuardError} on mismatch.
490
512
  *
491
- * @template T Target atomic type (`boolean`, `bigint`, `number`, `string`, or
492
- * `null`)
513
+ * A blank value is not a number, and a string parameter keeps the text `null`
514
+ * unless `T` admits `null`.
515
+ *
516
+ * @template T Target atomic type (`boolean`, `bigint`, `number`, or `string`),
517
+ * optionally with `null`
493
518
  * @param input Path parameter string
494
519
  * @returns Decoded value of type `T`
495
520
  * @throws {TypeGuardError} When decoded value doesn't conform to type `T`
@@ -521,10 +546,12 @@ export function createFormData(): never;
521
546
  * @template T Target object type
522
547
  * @returns Reusable decoder function
523
548
  */
524
- export function createFormData<T extends object>(): (input: FormData) => T;
549
+ export function createFormData<T extends object>(): (
550
+ input: FormData,
551
+ ) => Resolved<T>;
525
552
 
526
553
  /** @internal */
527
- export function createFormData<T>(): (input: FormData) => T {
554
+ export function createFormData<T>(): (input: FormData) => Resolved<T> {
528
555
  NoTransformConfigurationError("http.createFormData");
529
556
  }
530
557
 
@@ -553,13 +580,13 @@ export function createAssertFormData<T extends object>(
553
580
  ): (
554
581
  input: FormData,
555
582
  errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
556
- ) => T;
583
+ ) => Resolved<T>;
557
584
 
558
585
  /** @internal */
559
586
  export function createAssertFormData<T>(): (
560
587
  input: FormData,
561
588
  errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
562
- ) => T {
589
+ ) => Resolved<T> {
563
590
  NoTransformConfigurationError("http.createAssertFormData");
564
591
  }
565
592
 
@@ -579,10 +606,10 @@ export function createIsFormData(): never;
579
606
  */
580
607
  export function createIsFormData<T extends object>(): (
581
608
  input: FormData,
582
- ) => T | null;
609
+ ) => Resolved<T> | null;
583
610
 
584
611
  /** @internal */
585
- export function createIsFormData<T>(): (input: FormData) => T | null {
612
+ export function createIsFormData<T>(): (input: FormData) => Resolved<T> | null {
586
613
  NoTransformConfigurationError("http.createIsFormData");
587
614
  }
588
615
 
@@ -627,12 +654,12 @@ export function createQuery(): never;
627
654
  */
628
655
  export function createQuery<T extends object>(): (
629
656
  input: string | IReadableURLSearchParams,
630
- ) => T;
657
+ ) => Resolved<T>;
631
658
 
632
659
  /** @internal */
633
660
  export function createQuery<T>(): (
634
661
  input: string | IReadableURLSearchParams,
635
- ) => T {
662
+ ) => Resolved<T> {
636
663
  NoTransformConfigurationError("http.createQuery");
637
664
  }
638
665
 
@@ -661,13 +688,13 @@ export function createAssertQuery<T extends object>(
661
688
  ): (
662
689
  input: string | IReadableURLSearchParams,
663
690
  errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
664
- ) => T;
691
+ ) => Resolved<T>;
665
692
 
666
693
  /** @internal */
667
694
  export function createAssertQuery<T>(): (
668
695
  input: string | IReadableURLSearchParams,
669
696
  errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
670
- ) => T {
697
+ ) => Resolved<T> {
671
698
  NoTransformConfigurationError("http.createAssertQuery");
672
699
  }
673
700
 
@@ -687,12 +714,12 @@ export function createIsQuery(): never;
687
714
  */
688
715
  export function createIsQuery<T extends object>(): (
689
716
  input: string | IReadableURLSearchParams,
690
- ) => T | null;
717
+ ) => Resolved<T> | null;
691
718
 
692
719
  /** @internal */
693
720
  export function createIsQuery<T>(): (
694
721
  input: string | IReadableURLSearchParams,
695
- ) => T | null {
722
+ ) => Resolved<T> | null {
696
723
  NoTransformConfigurationError("http.createIsQuery");
697
724
  }
698
725
 
@@ -737,12 +764,12 @@ export function createHeaders(): never;
737
764
  */
738
765
  export function createHeaders<T extends object>(): (
739
766
  input: Record<string, string | string[] | undefined>,
740
- ) => T;
767
+ ) => Resolved<T>;
741
768
 
742
769
  /** @internal */
743
770
  export function createHeaders<T>(): (
744
771
  input: Record<string, string | string[] | undefined>,
745
- ) => T {
772
+ ) => Resolved<T> {
746
773
  NoTransformConfigurationError("http.createHeaders");
747
774
  }
748
775
 
@@ -771,13 +798,13 @@ export function createAssertHeaders<T extends object>(
771
798
  ): (
772
799
  input: Record<string, string | string[] | undefined>,
773
800
  errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
774
- ) => T;
801
+ ) => Resolved<T>;
775
802
 
776
803
  /** @internal */
777
804
  export function createAssertHeaders<T>(): (
778
805
  input: Record<string, string | string[] | undefined>,
779
806
  errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
780
- ) => T {
807
+ ) => Resolved<T> {
781
808
  NoTransformConfigurationError("http.createAssertHeaders");
782
809
  }
783
810
 
@@ -797,12 +824,12 @@ export function createIsHeaders(): never;
797
824
  */
798
825
  export function createIsHeaders<T extends object>(): (
799
826
  input: Record<string, string | string[] | undefined>,
800
- ) => T | null;
827
+ ) => Resolved<T> | null;
801
828
 
802
829
  /** @internal */
803
830
  export function createIsHeaders<T>(): (
804
831
  input: Record<string, string | string[] | undefined>,
805
- ) => T | null {
832
+ ) => Resolved<T> | null {
806
833
  NoTransformConfigurationError("http.createIsHeaders");
807
834
  }
808
835
 
@@ -847,11 +874,11 @@ export function createParameter(): never;
847
874
  */
848
875
  export function createParameter<T extends Atomic.Type | null>(): (
849
876
  input: string,
850
- ) => T;
877
+ ) => Resolved<T>;
851
878
 
852
879
  /** @internal */
853
880
  export function createParameter<T extends Atomic.Type | null>(): (
854
881
  input: string,
855
- ) => T {
882
+ ) => Resolved<T> {
856
883
  NoTransformConfigurationError("http.createParameter");
857
884
  }
@@ -3,7 +3,9 @@ export const _httpFormDataReadBigint = (
3
3
  ): bigint | null | undefined =>
4
4
  input instanceof File
5
5
  ? (input as any)
6
- : !!input?.length
6
+ : // Blank text is absent, as the empty string always was: `BigInt(" ")`
7
+ // is 0n, which would read a whitespace value as a real zero (#2448).
8
+ typeof input === "string" && input.trim().length !== 0
7
9
  ? input === "null"
8
10
  ? null
9
11
  : (toBigint(input) as any)
@@ -3,7 +3,9 @@ export const _httpFormDataReadBoolean = (
3
3
  ): boolean | null | undefined =>
4
4
  input instanceof File
5
5
  ? (input as any)
6
- : input === null
6
+ : // `undefined` is as absent as `null`, which `FormData.get` returns; a
7
+ // stand-in that answers `undefined` must not throw on `.length`.
8
+ input === null || input === undefined
7
9
  ? undefined
8
10
  : input === "null"
9
11
  ? null
@@ -3,7 +3,9 @@ export const _httpFormDataReadNumber = (
3
3
  ): number | null | undefined =>
4
4
  input instanceof File
5
5
  ? (input as any)
6
- : !!input?.length
6
+ : // Blank text is absent, as the empty string always was: `Number(" ")`
7
+ // is 0, which would read a whitespace value as a real zero (#2448).
8
+ typeof input === "string" && input.trim().length !== 0
7
9
  ? input === "null"
8
10
  ? null
9
11
  : (toNumber(input) as any)
@@ -1,10 +1,19 @@
1
+ /**
2
+ * Reads one form field as a string.
3
+ *
4
+ * The text `"null"` spells `null` only where the property's type admits `null`;
5
+ * elsewhere it is the string `"null"` (#2450). The emitter passes `false` for a
6
+ * string whose type does not admit `null` and omits the argument otherwise, so
7
+ * code emitted before the parameter existed keeps its reading.
8
+ */
1
9
  export const _httpFormDataReadString = (
2
10
  input: string | File | null,
11
+ nullable: boolean = true,
3
12
  ): string | null | undefined =>
4
13
  input instanceof File
5
14
  ? (input as any)
6
15
  : input === null
7
16
  ? undefined
8
- : input === "null"
17
+ : nullable === true && input === "null"
9
18
  ? null
10
19
  : input;
@@ -1,5 +1,10 @@
1
1
  export const _httpHeaderReadBigint = (value: string | undefined) =>
2
- value !== undefined ? toBigint(value) : undefined;
2
+ // A blank header, or a blank element of a header list, is absent: `BigInt("")`
3
+ // is 0n, which let validators accept an empty header as a real zero (#2448).
4
+ value !== undefined && isBlank(value) === false ? toBigint(value) : undefined;
5
+
6
+ const isBlank = (value: string): boolean =>
7
+ typeof value === "string" && value.trim().length === 0;
3
8
 
4
9
  const toBigint = (str: string): bigint | string => {
5
10
  try {
@@ -1,5 +1,10 @@
1
1
  export const _httpHeaderReadNumber = (value: string | undefined) =>
2
- value !== undefined ? toNumber(value) : undefined;
2
+ // A blank header, or a blank element of a header list, is absent: `Number("")`
3
+ // is 0, which let validators accept an empty header as a real zero (#2448).
4
+ value !== undefined && isBlank(value) === false ? toNumber(value) : undefined;
5
+
6
+ const isBlank = (value: string): boolean =>
7
+ typeof value === "string" && value.trim().length === 0;
3
8
 
4
9
  const toNumber = (str: string): number | string => {
5
10
  const value: number = Number(str);
@@ -2,6 +2,9 @@ export const _httpParameterReadBigint = (value: string) =>
2
2
  value !== "null" ? toBigint(value) : null;
3
3
 
4
4
  const toBigint = (str: string): bigint | string => {
5
+ // Blank text stays text, so the assertion rejects it: `BigInt(" ")` is 0n,
6
+ // which read a blank path segment as a real zero (#2448).
7
+ if (str.trim().length === 0) return str;
5
8
  try {
6
9
  return BigInt(str);
7
10
  } catch {
@@ -2,6 +2,9 @@ export const _httpParameterReadNumber = (value: string) =>
2
2
  value !== "null" ? toNumber(value) : null;
3
3
 
4
4
  const toNumber = (str: string): number | string => {
5
+ // Blank text stays text, so the assertion rejects it: `Number(" ")` is 0,
6
+ // which read a blank path segment as a real zero (#2448).
7
+ if (str.trim().length === 0) return str;
5
8
  const value: number = Number(str);
6
9
  return isNaN(value) ? str : value;
7
10
  };
@@ -1,2 +1,12 @@
1
- export const _httpParameterReadString = (value: string) =>
2
- value !== "null" ? value : null;
1
+ /**
2
+ * Reads one path parameter as a string.
3
+ *
4
+ * The text `"null"` spells `null` only where the parameter's type admits
5
+ * `null`; elsewhere it is the string `"null"` (#2450). The emitter passes
6
+ * `false` for a string whose type does not admit `null` and omits the argument
7
+ * otherwise, so code emitted before the parameter existed keeps its reading.
8
+ */
9
+ export const _httpParameterReadString = (
10
+ value: string,
11
+ nullable: boolean = true,
12
+ ) => (nullable === true && value === "null" ? null : value);
@@ -1,7 +1,13 @@
1
1
  export const _httpQueryReadBigint = (
2
2
  str: string | null,
3
3
  ): bigint | null | undefined =>
4
- !!str?.length ? (str === "null" ? null : (toBigint(str) as any)) : undefined;
4
+ // Blank text is absent, as the empty string always was: `BigInt(" ")` is 0n,
5
+ // which would read a whitespace value as a real zero (#2448).
6
+ typeof str === "string" && str.trim().length !== 0
7
+ ? str === "null"
8
+ ? null
9
+ : (toBigint(str) as any)
10
+ : undefined;
5
11
 
6
12
  const toBigint = (str: string): bigint | string => {
7
13
  try {
@@ -1,7 +1,9 @@
1
1
  export const _httpQueryReadBoolean = (
2
2
  str: string | null,
3
3
  ): boolean | null | undefined =>
4
- str === null
4
+ // `undefined` is as absent as `null`, which the contract returns; a
5
+ // stand-in reader that answers `undefined` must not throw on `.length`.
6
+ str === null || str === undefined
5
7
  ? undefined
6
8
  : str === "null"
7
9
  ? null
@@ -1,7 +1,13 @@
1
1
  export const _httpQueryReadNumber = (
2
2
  str: string | null,
3
3
  ): number | null | undefined =>
4
- !!str?.length ? (str === "null" ? null : (toNumber(str) as any)) : undefined;
4
+ // Blank text is absent, as the empty string always was: `Number(" ")` is 0,
5
+ // which would read a whitespace value as a real zero (#2448).
6
+ typeof str === "string" && str.trim().length !== 0
7
+ ? str === "null"
8
+ ? null
9
+ : (toNumber(str) as any)
10
+ : undefined;
5
11
 
6
12
  const toNumber = (str: string): number | string => {
7
13
  const value: number = Number(str);
@@ -1,4 +1,13 @@
1
+ /**
2
+ * Reads one query value as a string.
3
+ *
4
+ * The text `"null"` spells `null` only where the property's type admits `null`;
5
+ * elsewhere it is the string `"null"` (#2450). The emitter passes `false` for a
6
+ * string whose type does not admit `null` and omits the argument otherwise, so
7
+ * code emitted before the parameter existed keeps its reading.
8
+ */
1
9
  export const _httpQueryReadString = (
2
10
  str: string | null,
11
+ nullable: boolean = true,
3
12
  ): string | null | undefined =>
4
- str === null ? undefined : str === "null" ? null : str;
13
+ str === null ? undefined : nullable === true && str === "null" ? null : str;