@zudojs/types 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -7
  3. package/dist/runtime/index.d.ts +11 -1
  4. package/dist/runtime/index.js +10 -1
  5. package/dist/runtime/runtime.core.d.ts +49 -11
  6. package/dist/runtime/runtime.core.js +35 -52
  7. package/dist/runtime/runtime.int.d.ts +31 -0
  8. package/dist/runtime/runtime.int.js +54 -0
  9. package/dist/runtime/runtime.seeded.d.ts +31 -0
  10. package/dist/runtime/runtime.seeded.js +70 -0
  11. package/dist/typeConverters/typeConverters.core.d.ts +38 -3
  12. package/dist/typeConverters/typeConverters.core.js +111 -20
  13. package/dist/typeGuards/index.d.ts +1 -1
  14. package/dist/typeGuards/index.js +1 -1
  15. package/dist/typeGuards/typeGuards.core.d.ts +53 -4
  16. package/dist/typeGuards/typeGuards.core.js +112 -13
  17. package/dist/typeUtilities/index.d.ts +1 -1
  18. package/dist/typeUtilities/typeUtilities.core.d.ts +22 -3
  19. package/package.json +23 -12
  20. package/dist/.tsbuildinfo +0 -1
  21. package/dist/index.d.ts.map +0 -1
  22. package/dist/index.js.map +0 -1
  23. package/dist/runtime/index.d.ts.map +0 -1
  24. package/dist/runtime/index.js.map +0 -1
  25. package/dist/runtime/runtime.core.d.ts.map +0 -1
  26. package/dist/runtime/runtime.core.js.map +0 -1
  27. package/dist/typeConverters/index.d.ts.map +0 -1
  28. package/dist/typeConverters/index.js.map +0 -1
  29. package/dist/typeConverters/typeConverters.core.d.ts.map +0 -1
  30. package/dist/typeConverters/typeConverters.core.js.map +0 -1
  31. package/dist/typeGuards/index.d.ts.map +0 -1
  32. package/dist/typeGuards/index.js.map +0 -1
  33. package/dist/typeGuards/typeGuards.core.d.ts.map +0 -1
  34. package/dist/typeGuards/typeGuards.core.js.map +0 -1
  35. package/dist/typeUtilities/index.d.ts.map +0 -1
  36. package/dist/typeUtilities/index.js.map +0 -1
  37. package/dist/typeUtilities/typeUtilities.core.d.ts.map +0 -1
  38. package/dist/typeUtilities/typeUtilities.core.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zudojs Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  Shared type guards, utility types, and type converters for the Zudojs framework.
4
4
 
5
+ <!-- zudo-docs:start -->
6
+
7
+ **Documentation:** [zudojs.oyinlola.site/docs/packages-types](https://zudojs.oyinlola.site/docs/packages-types) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-types.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
8
+
9
+ <!-- zudo-docs:end -->
10
+
5
11
  ## Installation
6
12
 
7
13
  ```bash
@@ -13,26 +19,75 @@ npm install @zudojs/types
13
19
  ```typescript
14
20
  import {
15
21
  isPlainObject,
16
- isDate,
17
22
  isEmail,
18
- Maybe,
19
- DeepReadonly,
23
+ isUuid,
24
+ systemClock,
25
+ systemRandom,
26
+ toNumber,
20
27
  } from "@zudojs/types";
28
+ import type { Maybe, DeepReadonly } from "@zudojs/types";
21
29
 
22
30
  if (isPlainObject(value)) {
23
- console.log(value.keys());
31
+ for (const key of Object.keys(value)) {
32
+ console.log(key, value[key]);
33
+ }
24
34
  }
25
35
 
26
36
  const id: Maybe<string> = null;
27
37
  const config: DeepReadonly<AppConfig> = { db: { host: "localhost" } };
38
+
39
+ // Injectable runtime primitives, so tests can substitute deterministic ones.
40
+ const token = systemRandom.string(32);
41
+ const now = systemClock.now();
42
+
43
+ // Converters refuse rather than guessing: "" and "0x10" fall back.
44
+ const limit = toNumber(query.limit, 20);
45
+ ```
46
+
47
+ Deterministic doubles for tests:
48
+
49
+ ```typescript
50
+ import { FixedClock, SeededRandom } from "@zudojs/types";
51
+ import type { PseudoRandom } from "@zudojs/types";
52
+
53
+ const clock = new FixedClock(0);
54
+ const random: PseudoRandom = new SeededRandom(42);
55
+
56
+ clock.advance(1_000);
28
57
  ```
29
58
 
59
+ ## Safety Notes
60
+
61
+ - `Random` is branded, so `SeededRandom` — whose output is fully predictable
62
+ from its seed — cannot be injected where unpredictability is required.
63
+ Implement a secure generator through `defineSecureRandom()`.
64
+ - `Random.int(max)` uses rejection sampling, not `% max`, so draws are uniform
65
+ for every bound rather than only powers of two. `max` must be an integer
66
+ from 1 to `MAX_RANDOM_INT_BOUND` (`Number.MAX_SAFE_INTEGER`); bounds above
67
+ 2^32 draw 53 bits. Anything else throws a `RangeError`.
68
+ - `SeededRandom` is mulberry32-backed: its `uuid()` values do not repeat after
69
+ 16 draws and `int(2)` does not alternate.
70
+ - `mapToObject` and `safeJsonParse` cannot be used to reach a prototype.
71
+ `safeJsonParse` drops `__proto__`, `constructor` and `prototype` keys at
72
+ every depth as a deliberate deny-list, so a payload with a legitimate
73
+ `constructor` field loses it.
74
+ - `camelToSnake` / `camelToKebab` are Unicode-aware (`caféAuLait` becomes
75
+ `café_au_lait`) and keep characters other than `_`, `-` and whitespace.
76
+ - `toNumber` requires a finite number and refuses blank strings, hexadecimal
77
+ literals and `1e999`; `toBoolean(NaN)` falls back rather than returning true.
78
+ - `isUuid` accepts versions 1–8 including UUIDv7; use `isUuidV4` where the
79
+ version matters. `isPromise` narrows only to a native `Promise` — use
80
+ `isThenable` for anything awaitable.
81
+
30
82
  ## Features
31
83
 
32
- - Type guards (`isPlainObject`, `isDate`, `isEmail`, etc.)
84
+ - Type guards (`isPlainObject`, `isDate`, `isEmail`, `isUuid`, etc.)
33
85
  - Utility types (`Maybe`, `DeepReadonly`, `Prettify`, etc.)
34
- - Type converters and transformers
35
- - Branded type utilities
86
+ - Type converters and case transformers
87
+ - Injectable `Clock` and `Random` primitives, with deterministic test doubles
88
+
89
+ Branded identifier types (`Brand<>`, `UserId`, `TenantId`, ...) are not in
90
+ this package; they live in `@zudojs/constants`.
36
91
 
37
92
  ## Use Cases
38
93
 
@@ -1,2 +1,12 @@
1
- export * from "./runtime.core.js";
1
+ /**
2
+ * @zudojs/types — Runtime primitives barrel.
3
+ *
4
+ * An explicit list rather than `export *`: this was the only barrel in the
5
+ * package without one, so anything newly exported from `runtime.core.ts`
6
+ * silently became public API.
7
+ */
8
+ export type { Clock, ClockSeconds, Random, PseudoRandom, } from "./runtime.core.js";
9
+ export { systemClock, systemClockSeconds, defineSecureRandom, systemRandom, FixedClock, } from "./runtime.core.js";
10
+ export { SeededRandom } from "./runtime.seeded.js";
11
+ export { MAX_RANDOM_INT_BOUND } from "./runtime.int.js";
2
12
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,11 @@
1
- export * from "./runtime.core.js";
1
+ /**
2
+ * @zudojs/types — Runtime primitives barrel.
3
+ *
4
+ * An explicit list rather than `export *`: this was the only barrel in the
5
+ * package without one, so anything newly exported from `runtime.core.ts`
6
+ * silently became public API.
7
+ */
8
+ export { systemClock, systemClockSeconds, defineSecureRandom, systemRandom, FixedClock, } from "./runtime.core.js";
9
+ export { SeededRandom } from "./runtime.seeded.js";
10
+ export { MAX_RANDOM_INT_BOUND } from "./runtime.int.js";
2
11
  //# sourceMappingURL=index.js.map
@@ -13,21 +13,67 @@ export interface Clock {
13
13
  export interface ClockSeconds {
14
14
  nowSeconds(): number;
15
15
  }
16
- /** Returns cryptographically-secure random values. */
16
+ /**
17
+ * Brand marking an implementation as cryptographically secure.
18
+ *
19
+ * Structural typing alone would let any object with the right method names
20
+ * satisfy `Random`, including a seeded test generator. The brand makes the
21
+ * claim explicit: an implementor opts in through {@link defineSecureRandom}.
22
+ */
23
+ declare const SecureRandomBrand: unique symbol;
24
+ /**
25
+ * Returns cryptographically-secure random values.
26
+ *
27
+ * Implementations of this interface are safe for tokens, identifiers and
28
+ * secrets. A deterministic generator must implement {@link PseudoRandom}
29
+ * instead, so a test double cannot be injected where unpredictability is the
30
+ * requirement.
31
+ */
17
32
  export interface Random {
33
+ /** @internal Marks the implementation as unpredictable. */
34
+ readonly [SecureRandomBrand]: true;
18
35
  /** Returns a random UUID v4 string. */
19
36
  uuid(): string;
20
- /** Returns a random integer in [0, max). */
37
+ /**
38
+ * Returns a uniformly random integer in [0, max).
39
+ *
40
+ * @throws RangeError unless `max` is a safe integer of at least 1.
41
+ */
21
42
  int(max: number): number;
22
43
  /** Returns a random string of the given length (alphanumeric). */
23
44
  string(length: number): string;
24
45
  /** Returns a random string of the given length from the given alphabet. */
25
46
  custom(length: number, alphabet: string): string;
26
47
  }
48
+ /**
49
+ * A reproducible generator for tests.
50
+ *
51
+ * Structurally identical to {@link Random} but nominally distinct, so it
52
+ * cannot be passed where a `Random` is required.
53
+ */
54
+ export interface PseudoRandom {
55
+ /** Marks this as a deterministic generator, not a secure one. */
56
+ readonly deterministic: true;
57
+ uuid(): string;
58
+ int(max: number): number;
59
+ string(length: number): string;
60
+ custom(length: number, alphabet: string): string;
61
+ }
27
62
  /** Default Clock implementation backed by `Date.now()`. */
28
63
  export declare const systemClock: Clock;
29
64
  /** Default ClockSeconds implementation backed by `Math.floor(Date.now() / 1000)`. */
30
65
  export declare const systemClockSeconds: ClockSeconds;
66
+ /**
67
+ * Declare an implementation cryptographically secure.
68
+ *
69
+ * Call this only for a generator whose output is genuinely unpredictable —
70
+ * one backed by `node:crypto`, Web Crypto, or a hardware source. It is the
71
+ * single supported way to produce a {@link Random}.
72
+ *
73
+ * @param implementation - The generator's operations.
74
+ * @returns The implementation, branded as secure.
75
+ */
76
+ export declare function defineSecureRandom(implementation: Omit<Random, typeof SecureRandomBrand>): Random;
31
77
  /** Default Random implementation backed by `node:crypto`. */
32
78
  export declare const systemRandom: Random;
33
79
  /** Deterministic Clock useful for tests. */
@@ -38,13 +84,5 @@ export declare class FixedClock implements Clock {
38
84
  set(time: number): void;
39
85
  advance(deltaMs: number): void;
40
86
  }
41
- /** Deterministic Random useful for tests. */
42
- export declare class SeededRandom implements Random {
43
- private state;
44
- constructor(seed?: number);
45
- uuid(): string;
46
- int(max: number): number;
47
- string(length: number): string;
48
- custom(length: number, alphabet: string): string;
49
- }
87
+ export {};
50
88
  //# sourceMappingURL=runtime.core.d.ts.map
@@ -5,6 +5,8 @@
5
5
  * avoid direct `Date.now()` and `Math.random()` calls. Tests can substitute
6
6
  * deterministic implementations.
7
7
  */
8
+ import { randomUUID } from "node:crypto";
9
+ import { assertIntBound, cryptoWord, sampleInt } from "./runtime.int.js";
8
10
  /** Default Clock implementation backed by `Date.now()`. */
9
11
  export const systemClock = {
10
12
  now: () => Date.now(),
@@ -13,37 +15,27 @@ export const systemClock = {
13
15
  export const systemClockSeconds = {
14
16
  nowSeconds: () => Math.floor(Date.now() / 1000),
15
17
  };
16
- /** Default Random implementation backed by `node:crypto`. */
17
- export const systemRandom = {
18
- uuid: () => cryptoUUID(),
19
- int: (max) => cryptoInt(max),
20
- string: (length) => randomString(length, ALPHANUMERIC),
21
- custom: (length, alphabet) => randomString(length, alphabet),
22
- };
23
18
  const ALPHANUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
19
+ /** Returns a v4 UUID, preferring the Web Crypto implementation. */
24
20
  function cryptoUUID() {
25
21
  if (typeof globalThis.crypto?.randomUUID === "function") {
26
22
  return globalThis.crypto.randomUUID();
27
23
  }
28
- // node:crypto fallback (older Node, edge runtimes)
29
- // eslint-disable-next-line @typescript-eslint/no-require-imports
30
- const { randomUUID } = require("node:crypto");
31
24
  return randomUUID();
32
25
  }
26
+ /**
27
+ * Returns a uniformly random integer in [0, max).
28
+ *
29
+ * Rejection sampling, not `% max`: a modulo of a uniform draw is only uniform
30
+ * when `max` divides the range. Bounds above `2**32` draw 53 bits, so every
31
+ * safe-integer bound terminates; anything else is a `RangeError`.
32
+ */
33
33
  function cryptoInt(max) {
34
- if (!Number.isInteger(max) || max <= 0) {
35
- throw new RangeError("Random.int(max) requires a positive integer max");
36
- }
37
- if (typeof globalThis.crypto?.getRandomValues === "function") {
38
- const buf = new Uint32Array(1);
39
- globalThis.crypto.getRandomValues(buf);
40
- return buf[0] % max;
41
- }
42
- // eslint-disable-next-line @typescript-eslint/no-require-imports
43
- const { randomInt } = require("node:crypto");
44
- return randomInt(max);
34
+ assertIntBound(max, "Random");
35
+ return sampleInt(max, cryptoWord);
45
36
  }
46
- function randomString(length, alphabet) {
37
+ /** Builds a random string over an alphabet. */
38
+ function randomString(length, alphabet, nextInt) {
47
39
  if (!Number.isInteger(length) || length < 0) {
48
40
  throw new RangeError("Random.string(length) requires a non-negative integer");
49
41
  }
@@ -52,10 +44,30 @@ function randomString(length, alphabet) {
52
44
  }
53
45
  let out = "";
54
46
  for (let i = 0; i < length; i++) {
55
- out += alphabet.charAt(cryptoInt(alphabet.length));
47
+ out += alphabet.charAt(nextInt(alphabet.length));
56
48
  }
57
49
  return out;
58
50
  }
51
+ /**
52
+ * Declare an implementation cryptographically secure.
53
+ *
54
+ * Call this only for a generator whose output is genuinely unpredictable —
55
+ * one backed by `node:crypto`, Web Crypto, or a hardware source. It is the
56
+ * single supported way to produce a {@link Random}.
57
+ *
58
+ * @param implementation - The generator's operations.
59
+ * @returns The implementation, branded as secure.
60
+ */
61
+ export function defineSecureRandom(implementation) {
62
+ return implementation;
63
+ }
64
+ /** Default Random implementation backed by `node:crypto`. */
65
+ export const systemRandom = defineSecureRandom({
66
+ uuid: () => cryptoUUID(),
67
+ int: (max) => cryptoInt(max),
68
+ string: (length) => randomString(length, ALPHANUMERIC, cryptoInt),
69
+ custom: (length, alphabet) => randomString(length, alphabet, cryptoInt),
70
+ });
59
71
  /** Deterministic Clock useful for tests. */
60
72
  export class FixedClock {
61
73
  current;
@@ -72,33 +84,4 @@ export class FixedClock {
72
84
  this.current += deltaMs;
73
85
  }
74
86
  }
75
- /** Deterministic Random useful for tests. */
76
- export class SeededRandom {
77
- state;
78
- constructor(seed = 1) {
79
- this.state = seed;
80
- }
81
- uuid() {
82
- const hex = this.state.toString(16).padStart(8, "0");
83
- this.state = (this.state * 1103515245 + 12345) & 0x7fffffff;
84
- return `${hex}-${this.state.toString(16).padStart(8, "0")}-0000-0000-000000000000`;
85
- }
86
- int(max) {
87
- if (!Number.isInteger(max) || max <= 0) {
88
- throw new RangeError("SeededRandom.int(max) requires a positive integer max");
89
- }
90
- this.state = (this.state * 1103515245 + 12345) & 0x7fffffff;
91
- return this.state % max;
92
- }
93
- string(length) {
94
- return this.custom(length, ALPHANUMERIC);
95
- }
96
- custom(length, alphabet) {
97
- let out = "";
98
- for (let i = 0; i < length; i++) {
99
- out += alphabet.charAt(this.int(alphabet.length));
100
- }
101
- return out;
102
- }
103
- }
104
87
  //# sourceMappingURL=runtime.core.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @zudojs/types/runtime — uniform integer sampling.
3
+ *
4
+ * Shared by the secure generator and the seeded test generator, so both
5
+ * accept the same bounds and both are uniform.
6
+ */
7
+ /** Largest bound `int(max)` accepts: every result must be a safe integer. */
8
+ export declare const MAX_RANDOM_INT_BOUND: number;
9
+ /** Produces uniformly distributed unsigned 32-bit words. */
10
+ export type WordSource = () => number;
11
+ /**
12
+ * Validates an `int(max)` bound.
13
+ *
14
+ * @throws RangeError when `max` is not an integer in `[1, 2**53 - 1]`.
15
+ */
16
+ export declare function assertIntBound(max: number, owner: string): void;
17
+ /**
18
+ * Returns a uniformly random integer in `[0, max)` by rejection sampling.
19
+ *
20
+ * Bounds up to `2**32` draw one 32-bit word; larger bounds draw 53 bits from
21
+ * two words. The accept limit is the largest multiple of `max` inside the
22
+ * sampled range, which is never zero, so the loop terminates with
23
+ * probability 1 and in practice within a couple of draws.
24
+ *
25
+ * @param max - Exclusive upper bound, validated by {@link assertIntBound}.
26
+ * @param nextWord - Source of uniform unsigned 32-bit words.
27
+ */
28
+ export declare function sampleInt(max: number, nextWord: WordSource): number;
29
+ /** Returns one cryptographically secure unsigned 32-bit word. */
30
+ export declare function cryptoWord(): number;
31
+ //# sourceMappingURL=runtime.int.d.ts.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @zudojs/types/runtime — uniform integer sampling.
3
+ *
4
+ * Shared by the secure generator and the seeded test generator, so both
5
+ * accept the same bounds and both are uniform.
6
+ */
7
+ import { randomFillSync } from "node:crypto";
8
+ /** Largest bound `int(max)` accepts: every result must be a safe integer. */
9
+ export const MAX_RANDOM_INT_BOUND = Number.MAX_SAFE_INTEGER;
10
+ const WORD = 2 ** 32;
11
+ const HIGH_BITS = 2 ** 21;
12
+ /**
13
+ * Validates an `int(max)` bound.
14
+ *
15
+ * @throws RangeError when `max` is not an integer in `[1, 2**53 - 1]`.
16
+ */
17
+ export function assertIntBound(max, owner) {
18
+ if (!Number.isSafeInteger(max) || max <= 0) {
19
+ throw new RangeError(`${owner}.int(max) requires a positive integer max no larger than ${MAX_RANDOM_INT_BOUND}`);
20
+ }
21
+ }
22
+ /**
23
+ * Returns a uniformly random integer in `[0, max)` by rejection sampling.
24
+ *
25
+ * Bounds up to `2**32` draw one 32-bit word; larger bounds draw 53 bits from
26
+ * two words. The accept limit is the largest multiple of `max` inside the
27
+ * sampled range, which is never zero, so the loop terminates with
28
+ * probability 1 and in practice within a couple of draws.
29
+ *
30
+ * @param max - Exclusive upper bound, validated by {@link assertIntBound}.
31
+ * @param nextWord - Source of uniform unsigned 32-bit words.
32
+ */
33
+ export function sampleInt(max, nextWord) {
34
+ const range = max <= WORD ? WORD : WORD * HIGH_BITS;
35
+ const limit = range - (range % max);
36
+ for (;;) {
37
+ const low = nextWord();
38
+ const draw = range === WORD ? low : (nextWord() % HIGH_BITS) * WORD + low;
39
+ if (draw < limit)
40
+ return draw % max;
41
+ }
42
+ }
43
+ const cryptoBuffer = new Uint32Array(1);
44
+ /** Returns one cryptographically secure unsigned 32-bit word. */
45
+ export function cryptoWord() {
46
+ if (typeof globalThis.crypto?.getRandomValues === "function") {
47
+ globalThis.crypto.getRandomValues(cryptoBuffer);
48
+ }
49
+ else {
50
+ randomFillSync(cryptoBuffer);
51
+ }
52
+ return cryptoBuffer[0];
53
+ }
54
+ //# sourceMappingURL=runtime.int.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @zudojs/types/runtime — deterministic generator for tests.
3
+ */
4
+ import type { PseudoRandom } from "./runtime.core.js";
5
+ /**
6
+ * Deterministic generator useful for tests.
7
+ *
8
+ * Implements {@link PseudoRandom}, never `Random`: its output is fully
9
+ * predictable from the seed, so injecting it where a secure generator is
10
+ * expected would make every token guessable from a single observation.
11
+ *
12
+ * Backed by mulberry32, whose full 32-bit output is well distributed. The
13
+ * previous linear congruential generator exposed its low bits directly, so
14
+ * `uuid()` cycled after 16 values and `int(2)` alternated.
15
+ */
16
+ export declare class SeededRandom implements PseudoRandom {
17
+ readonly deterministic = true;
18
+ private state;
19
+ constructor(seed?: number);
20
+ /** Returns a structurally valid v4 UUID derived from the seed. */
21
+ uuid(): string;
22
+ /** Returns a uniformly distributed integer in `[0, max)`. */
23
+ int(max: number): number;
24
+ /** Returns an alphanumeric string of the given length. */
25
+ string(length: number): string;
26
+ /** Returns a string of the given length drawn from `alphabet`. */
27
+ custom(length: number, alphabet: string): string;
28
+ /** Advances the mulberry32 state and returns an unsigned 32-bit word. */
29
+ private next;
30
+ }
31
+ //# sourceMappingURL=runtime.seeded.d.ts.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @zudojs/types/runtime — deterministic generator for tests.
3
+ */
4
+ import { assertIntBound, sampleInt } from "./runtime.int.js";
5
+ const ALPHANUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
6
+ /**
7
+ * Deterministic generator useful for tests.
8
+ *
9
+ * Implements {@link PseudoRandom}, never `Random`: its output is fully
10
+ * predictable from the seed, so injecting it where a secure generator is
11
+ * expected would make every token guessable from a single observation.
12
+ *
13
+ * Backed by mulberry32, whose full 32-bit output is well distributed. The
14
+ * previous linear congruential generator exposed its low bits directly, so
15
+ * `uuid()` cycled after 16 values and `int(2)` alternated.
16
+ */
17
+ export class SeededRandom {
18
+ deterministic = true;
19
+ state;
20
+ constructor(seed = 1) {
21
+ this.state = seed >>> 0 || 1;
22
+ }
23
+ /** Returns a structurally valid v4 UUID derived from the seed. */
24
+ uuid() {
25
+ let hex = "";
26
+ for (let i = 0; i < 4; i++) {
27
+ hex += this.next().toString(16).padStart(8, "0");
28
+ }
29
+ const variant = "89ab".charAt(this.int(4));
30
+ return [
31
+ hex.slice(0, 8),
32
+ hex.slice(8, 12),
33
+ `4${hex.slice(13, 16)}`,
34
+ `${variant}${hex.slice(17, 20)}`,
35
+ hex.slice(20, 32),
36
+ ].join("-");
37
+ }
38
+ /** Returns a uniformly distributed integer in `[0, max)`. */
39
+ int(max) {
40
+ assertIntBound(max, "PseudoRandom");
41
+ return sampleInt(max, () => this.next());
42
+ }
43
+ /** Returns an alphanumeric string of the given length. */
44
+ string(length) {
45
+ return this.custom(length, ALPHANUMERIC);
46
+ }
47
+ /** Returns a string of the given length drawn from `alphabet`. */
48
+ custom(length, alphabet) {
49
+ if (!Number.isInteger(length) || length < 0) {
50
+ throw new RangeError("Random.string(length) requires a non-negative integer");
51
+ }
52
+ if (alphabet.length === 0) {
53
+ throw new RangeError("alphabet must not be empty");
54
+ }
55
+ let out = "";
56
+ for (let i = 0; i < length; i++) {
57
+ out += alphabet.charAt(this.int(alphabet.length));
58
+ }
59
+ return out;
60
+ }
61
+ /** Advances the mulberry32 state and returns an unsigned 32-bit word. */
62
+ next() {
63
+ this.state = (this.state + 0x6d2b79f5) >>> 0;
64
+ let t = this.state;
65
+ t = Math.imul(t ^ (t >>> 15), t | 1);
66
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
67
+ return (t ^ (t >>> 14)) >>> 0;
68
+ }
69
+ }
70
+ //# sourceMappingURL=runtime.seeded.js.map
@@ -5,18 +5,43 @@
5
5
  */
6
6
  /**
7
7
  * Safely parse JSON with a fallback value.
8
+ *
9
+ * "Safe" here means only that malformed JSON yields the fallback rather than
10
+ * throwing. The result is cast to `T` without validation — parse a trust
11
+ * boundary with `@zudojs/validation` or `@zudojs/schema` instead of relying on
12
+ * this cast.
13
+ *
14
+ * `__proto__`, `constructor` and `prototype` keys are dropped at every depth.
15
+ * `JSON.parse` itself never routes them through a prototype, so this is a
16
+ * deliberate deny-list, not a parser fix: a downstream deep merge that walks
17
+ * `constructor.prototype` or `__proto__` would otherwise reach
18
+ * `Object.prototype`. A payload whose legitimate field is named
19
+ * `constructor` or `prototype` loses that field; parse it with plain
20
+ * `JSON.parse` and validate it instead.
8
21
  */
9
22
  export declare function safeJsonParse<T>(json: string, fallback: T): T;
10
23
  /**
11
24
  * Convert a value to a string safely.
25
+ *
26
+ * Always returns a string. `JSON.stringify` returns the *value* `undefined`
27
+ * — not a string, and without throwing — for functions, symbols and
28
+ * `undefined`, so its result is checked rather than returned directly.
12
29
  */
13
30
  export declare function toString(value: unknown, fallback?: string): string;
14
31
  /**
15
- * Convert a value to a number safely.
32
+ * Convert a value to a finite number safely.
33
+ *
34
+ * Blank strings, hexadecimal literals and infinities all fall back rather than
35
+ * converting: a missing query parameter arriving as `""` becoming a real zero
36
+ * silently turns into a page size, a price or a limit.
16
37
  */
17
38
  export declare function toNumber(value: unknown, fallback?: number): number;
18
39
  /**
19
40
  * Convert a value to a boolean safely.
41
+ *
42
+ * `NaN` falls back rather than converting to true. It is what `toNumber`
43
+ * produces on failure, so chaining the two would otherwise turn a parse
44
+ * failure into the permissive answer for a flag.
20
45
  */
21
46
  export declare function toBoolean(value: unknown, fallback?: boolean): boolean;
22
47
  /**
@@ -25,6 +50,12 @@ export declare function toBoolean(value: unknown, fallback?: boolean): boolean;
25
50
  export declare function toArray<T>(value: T | T[]): T[];
26
51
  /**
27
52
  * Convert a Map to a plain object.
53
+ *
54
+ * Built on a null-prototype object with `defineProperty`. Assigning into an
55
+ * object literal routes a `__proto__` key through the prototype setter, so a
56
+ * Map built from request data — headers, form fields, query parameters — could
57
+ * replace the result's prototype with attacker-supplied values that
58
+ * `Object.keys` does not reveal.
28
59
  */
29
60
  export declare function mapToObject<K extends string | number | symbol, V>(map: Map<K, V>): Record<K, V>;
30
61
  /**
@@ -36,7 +67,11 @@ export declare function objectToMap<K extends string | number | symbol, V>(obj:
36
67
  */
37
68
  export declare function snakeToCamel(str: string): string;
38
69
  /**
39
- * Convert camelCase to snake_case.
70
+ * Convert camelCase or PascalCase to snake_case.
71
+ *
72
+ * Leading capitals do not produce a leading separator, and acronyms survive
73
+ * as single words — an identifier like `_hello_world` is not a valid column
74
+ * name, and `parse_h_t_t_p_response` is not a useful one.
40
75
  */
41
76
  export declare function camelToSnake(str: string): string;
42
77
  /**
@@ -44,7 +79,7 @@ export declare function camelToSnake(str: string): string;
44
79
  */
45
80
  export declare function kebabToCamel(str: string): string;
46
81
  /**
47
- * Convert camelCase to kebab-case.
82
+ * Convert camelCase or PascalCase to kebab-case.
48
83
  */
49
84
  export declare function camelToKebab(str: string): string;
50
85
  //# sourceMappingURL=typeConverters.core.d.ts.map