@zudojs/types 1.0.0 → 1.1.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.
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
@@ -55,9 +61,18 @@ clock.advance(1_000);
55
61
  - `Random` is branded, so `SeededRandom` — whose output is fully predictable
56
62
  from its seed — cannot be injected where unpredictability is required.
57
63
  Implement a secure generator through `defineSecureRandom()`.
58
- - `Random.int()` uses rejection sampling, not `% max`, so draws are uniform for
59
- every bound rather than only powers of two.
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.
60
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.
61
76
  - `toNumber` requires a finite number and refuses blank strings, hexadecimal
62
77
  literals and `1e999`; `toBoolean(NaN)` falls back rather than returning true.
63
78
  - `isUuid` accepts versions 1–8 including UUIDv7; use `isUuidV4` where the
@@ -69,9 +84,11 @@ clock.advance(1_000);
69
84
  - Type guards (`isPlainObject`, `isDate`, `isEmail`, `isUuid`, etc.)
70
85
  - Utility types (`Maybe`, `DeepReadonly`, `Prettify`, etc.)
71
86
  - Type converters and case transformers
72
- - Branded type utilities
73
87
  - Injectable `Clock` and `Random` primitives, with deterministic test doubles
74
88
 
89
+ Branded identifier types (`Brand<>`, `UserId`, `TenantId`, ...) are not in
90
+ this package; they live in `@zudojs/constants`.
91
+
75
92
  ## Use Cases
76
93
 
77
94
  - Runtime type checking
@@ -6,5 +6,7 @@
6
6
  * silently became public API.
7
7
  */
8
8
  export type { Clock, ClockSeconds, Random, PseudoRandom, } from "./runtime.core.js";
9
- export { systemClock, systemClockSeconds, defineSecureRandom, systemRandom, FixedClock, SeededRandom, } 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";
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -5,5 +5,7 @@
5
5
  * package without one, so anything newly exported from `runtime.core.ts`
6
6
  * silently became public API.
7
7
  */
8
- export { systemClock, systemClockSeconds, defineSecureRandom, systemRandom, FixedClock, SeededRandom, } from "./runtime.core.js";
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";
9
11
  //# sourceMappingURL=index.js.map
@@ -34,7 +34,11 @@ export interface Random {
34
34
  readonly [SecureRandomBrand]: true;
35
35
  /** Returns a random UUID v4 string. */
36
36
  uuid(): string;
37
- /** Returns a uniformly 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
+ */
38
42
  int(max: number): number;
39
43
  /** Returns a random string of the given length (alphanumeric). */
40
44
  string(length: number): string;
@@ -80,24 +84,5 @@ export declare class FixedClock implements Clock {
80
84
  set(time: number): void;
81
85
  advance(deltaMs: number): void;
82
86
  }
83
- /**
84
- * Deterministic generator useful for tests.
85
- *
86
- * Implements {@link PseudoRandom}, never {@link Random}: its output is fully
87
- * predictable from the seed, so injecting it where a secure generator is
88
- * expected would make every token guessable from a single observation.
89
- */
90
- export declare class SeededRandom implements PseudoRandom {
91
- readonly deterministic = true;
92
- private state;
93
- constructor(seed?: number);
94
- /** Returns a structurally valid v4 UUID derived from the seed. */
95
- uuid(): string;
96
- int(max: number): number;
97
- string(length: number): string;
98
- custom(length: number, alphabet: string): string;
99
- /** Advances the linear congruential state. */
100
- private next;
101
- }
102
87
  export {};
103
88
  //# sourceMappingURL=runtime.core.d.ts.map
@@ -5,7 +5,8 @@
5
5
  * avoid direct `Date.now()` and `Math.random()` calls. Tests can substitute
6
6
  * deterministic implementations.
7
7
  */
8
- import { randomInt, randomUUID } from "node:crypto";
8
+ import { randomUUID } from "node:crypto";
9
+ import { assertIntBound, cryptoWord, sampleInt } from "./runtime.int.js";
9
10
  /** Default Clock implementation backed by `Date.now()`. */
10
11
  export const systemClock = {
11
12
  now: () => Date.now(),
@@ -25,27 +26,13 @@ function cryptoUUID() {
25
26
  /**
26
27
  * Returns a uniformly random integer in [0, max).
27
28
  *
28
- * Rejection sampling, not `% max`. A modulo of a uniform 32-bit draw is only
29
- * uniform when `max` is a power of two; for other bounds the low values come
30
- * up more often, which is not acceptable from an interface documented as
31
- * cryptographically secure.
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
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
- return randomInt(max);
39
- }
40
- const range = 2 ** 32;
41
- const limit = range - (range % max);
42
- const buffer = new Uint32Array(1);
43
- for (;;) {
44
- globalThis.crypto.getRandomValues(buffer);
45
- const draw = buffer[0];
46
- if (draw < limit)
47
- return draw % max;
48
- }
34
+ assertIntBound(max, "Random");
35
+ return sampleInt(max, cryptoWord);
49
36
  }
50
37
  /** Builds a random string over an alphabet. */
51
38
  function randomString(length, alphabet, nextInt) {
@@ -97,46 +84,4 @@ export class FixedClock {
97
84
  this.current += deltaMs;
98
85
  }
99
86
  }
100
- /**
101
- * Deterministic generator useful for tests.
102
- *
103
- * Implements {@link PseudoRandom}, never {@link Random}: its output is fully
104
- * predictable from the seed, so injecting it where a secure generator is
105
- * expected would make every token guessable from a single observation.
106
- */
107
- export class SeededRandom {
108
- deterministic = true;
109
- state;
110
- constructor(seed = 1) {
111
- this.state = seed >>> 0 || 1;
112
- }
113
- /** Returns a structurally valid v4 UUID derived from the seed. */
114
- uuid() {
115
- const hex = (count) => Array.from({ length: count }, () => this.next().toString(16).padStart(8, "0").slice(-1)).join("");
116
- return [
117
- hex(8),
118
- hex(4),
119
- `4${hex(3)}`,
120
- `${"89ab".charAt(this.int(4))}${hex(3)}`,
121
- hex(12),
122
- ].join("-");
123
- }
124
- int(max) {
125
- if (!Number.isInteger(max) || max <= 0) {
126
- throw new RangeError("PseudoRandom.int(max) requires a positive integer max");
127
- }
128
- return this.next() % max;
129
- }
130
- string(length) {
131
- return this.custom(length, ALPHANUMERIC);
132
- }
133
- custom(length, alphabet) {
134
- return randomString(length, alphabet, (max) => this.int(max));
135
- }
136
- /** Advances the linear congruential state. */
137
- next() {
138
- this.state = (Math.imul(this.state, 1103515245) + 12345) & 0x7fffffff;
139
- return this.state;
140
- }
141
- }
142
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
@@ -11,8 +11,13 @@
11
11
  * boundary with `@zudojs/validation` or `@zudojs/schema` instead of relying on
12
12
  * this cast.
13
13
  *
14
- * Keys that would reach a prototype are dropped, so the parsed value cannot
15
- * seed a pollution chain downstream.
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.
16
21
  */
17
22
  export declare function safeJsonParse<T>(json: string, fallback: T): T;
18
23
  /**
@@ -17,8 +17,13 @@ const UNSAFE_KEYS = new Set([
17
17
  * boundary with `@zudojs/validation` or `@zudojs/schema` instead of relying on
18
18
  * this cast.
19
19
  *
20
- * Keys that would reach a prototype are dropped, so the parsed value cannot
21
- * seed a pollution chain downstream.
20
+ * `__proto__`, `constructor` and `prototype` keys are dropped at every depth.
21
+ * `JSON.parse` itself never routes them through a prototype, so this is a
22
+ * deliberate deny-list, not a parser fix: a downstream deep merge that walks
23
+ * `constructor.prototype` or `__proto__` would otherwise reach
24
+ * `Object.prototype`. A payload whose legitimate field is named
25
+ * `constructor` or `prototype` loses that field; parse it with plain
26
+ * `JSON.parse` and validate it instead.
22
27
  */
23
28
  export function safeJsonParse(json, fallback) {
24
29
  try {
@@ -76,7 +81,7 @@ export function toNumber(value, fallback = NaN) {
76
81
  const trimmed = value.trim();
77
82
  if (trimmed.length === 0)
78
83
  return fallback;
79
- if (!/^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/u.test(trimmed)) {
84
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/u.test(trimmed)) {
80
85
  return fallback;
81
86
  }
82
87
  const parsed = Number(trimmed);
@@ -156,10 +161,17 @@ export function snakeToCamel(str) {
156
161
  * Split a camelCase or PascalCase identifier into its words.
157
162
  *
158
163
  * Runs of capitals are kept together, so `parseHTTPResponse` yields
159
- * `["parse", "HTTP", "Response"]` rather than one word per letter.
164
+ * `["parse", "HTTP", "Response"]` rather than one word per letter. Letter
165
+ * classes are Unicode-aware (`caféAuLait` keeps its `é`), existing `_`, `-`
166
+ * and whitespace separators are word boundaries, and any other character is
167
+ * kept in place rather than silently dropped.
160
168
  */
161
169
  function splitCamelWords(str) {
162
- return str.match(/(?:[A-Z](?![a-z]))+|[A-Z]?[a-z0-9]+|[A-Z]|[0-9]+/gu) ?? [];
170
+ return str
171
+ .replace(/([\p{Ll}\p{N}])(\p{Lu})/gu, "$1\u0000$2")
172
+ .replace(/(\p{Lu})(\p{Lu}\p{Ll})/gu, "$1\u0000$2")
173
+ .split(/[\u0000\s_-]+/u)
174
+ .filter((word) => word.length > 0);
163
175
  }
164
176
  /**
165
177
  * Convert camelCase or PascalCase to snake_case.
@@ -49,10 +49,11 @@ export declare const MAX_EMAIL_LENGTH = 254;
49
49
  /**
50
50
  * Check if a value is a valid email string.
51
51
  *
52
- * This is the monorepo's single email check; `@zudojs/validation` re-exports
53
- * it as the `email` constraint. Two implementations previously disagreed
54
- * about the same address, so a value accepted at the edge could be rejected
55
- * halfway through a request.
52
+ * This is the monorepo's reference email check. `ValidationPattern.EMAIL`
53
+ * in `@zudojs/constants` (and therefore `createEmailAddress` and the schema
54
+ * `email` format) encodes the same acceptance set, including the 254
55
+ * character bound, and `@zudojs/validation`'s `email` constraint uses that
56
+ * pattern, so all three accept exactly the same addresses.
56
57
  */
57
58
  export declare function isEmail(value: unknown): value is string;
58
59
  /**
@@ -84,6 +85,10 @@ export declare function isIsoDateString(value: unknown): value is string;
84
85
  export declare function isIsoDateTimeString(value: unknown): value is string;
85
86
  /**
86
87
  * Check if a value is an array of a specific element type.
88
+ *
89
+ * Every index in `0..length-1` is read, so a hole is tested as `undefined`
90
+ * rather than skipped. `Array.prototype.every` skips holes, which made
91
+ * `new Array(3)` satisfy every guard and narrow three holes to `T[]`.
87
92
  */
88
93
  export declare function isArrayOfType<T>(value: unknown, guard: (item: unknown) => item is T): value is T[];
89
94
  /**
@@ -76,10 +76,11 @@ export const MAX_EMAIL_LENGTH = 254;
76
76
  /**
77
77
  * Check if a value is a valid email string.
78
78
  *
79
- * This is the monorepo's single email check; `@zudojs/validation` re-exports
80
- * it as the `email` constraint. Two implementations previously disagreed
81
- * about the same address, so a value accepted at the edge could be rejected
82
- * halfway through a request.
79
+ * This is the monorepo's reference email check. `ValidationPattern.EMAIL`
80
+ * in `@zudojs/constants` (and therefore `createEmailAddress` and the schema
81
+ * `email` format) encodes the same acceptance set, including the 254
82
+ * character bound, and `@zudojs/validation`'s `email` constraint uses that
83
+ * pattern, so all three accept exactly the same addresses.
83
84
  */
84
85
  export function isEmail(value) {
85
86
  if (typeof value !== "string")
@@ -168,11 +169,19 @@ export function isIsoDateTimeString(value) {
168
169
  }
169
170
  /**
170
171
  * Check if a value is an array of a specific element type.
172
+ *
173
+ * Every index in `0..length-1` is read, so a hole is tested as `undefined`
174
+ * rather than skipped. `Array.prototype.every` skips holes, which made
175
+ * `new Array(3)` satisfy every guard and narrow three holes to `T[]`.
171
176
  */
172
177
  export function isArrayOfType(value, guard) {
173
178
  if (!Array.isArray(value))
174
179
  return false;
175
- return value.every(guard);
180
+ for (let index = 0; index < value.length; index++) {
181
+ if (!guard(value[index]))
182
+ return false;
183
+ }
184
+ return true;
176
185
  }
177
186
  /**
178
187
  * Check if a value is defined (not null or undefined).
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@zudojs/types",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Shared type guards, utility types, and type converters for the Zudojs framework.",
5
5
  "license": "MIT",
6
+ "author": {
7
+ "name": "Oluwayemi Oyinlola",
8
+ "url": "https://github.com/oyinlola-tech"
9
+ },
6
10
  "type": "module",
7
11
  "main": "./dist/index.js",
8
12
  "module": "./dist/index.js",