@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.
- package/LICENSE +21 -0
- package/README.md +62 -7
- package/dist/runtime/index.d.ts +11 -1
- package/dist/runtime/index.js +10 -1
- package/dist/runtime/runtime.core.d.ts +49 -11
- package/dist/runtime/runtime.core.js +35 -52
- package/dist/runtime/runtime.int.d.ts +31 -0
- package/dist/runtime/runtime.int.js +54 -0
- package/dist/runtime/runtime.seeded.d.ts +31 -0
- package/dist/runtime/runtime.seeded.js +70 -0
- package/dist/typeConverters/typeConverters.core.d.ts +38 -3
- package/dist/typeConverters/typeConverters.core.js +111 -20
- package/dist/typeGuards/index.d.ts +1 -1
- package/dist/typeGuards/index.js +1 -1
- package/dist/typeGuards/typeGuards.core.d.ts +53 -4
- package/dist/typeGuards/typeGuards.core.js +112 -13
- package/dist/typeUtilities/index.d.ts +1 -1
- package/dist/typeUtilities/typeUtilities.core.d.ts +22 -3
- package/package.json +23 -12
- package/dist/.tsbuildinfo +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/runtime.core.d.ts.map +0 -1
- package/dist/runtime/runtime.core.js.map +0 -1
- package/dist/typeConverters/index.d.ts.map +0 -1
- package/dist/typeConverters/index.js.map +0 -1
- package/dist/typeConverters/typeConverters.core.d.ts.map +0 -1
- package/dist/typeConverters/typeConverters.core.js.map +0 -1
- package/dist/typeGuards/index.d.ts.map +0 -1
- package/dist/typeGuards/index.js.map +0 -1
- package/dist/typeGuards/typeGuards.core.d.ts.map +0 -1
- package/dist/typeGuards/typeGuards.core.js.map +0 -1
- package/dist/typeUtilities/index.d.ts.map +0 -1
- package/dist/typeUtilities/index.js.map +0 -1
- package/dist/typeUtilities/typeUtilities.core.d.ts.map +0 -1
- 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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
|
|
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
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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(
|
|
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
|