@zudojs/constants 1.0.1 → 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 constants, enums, branded types, and type-safe literals for the Zudojs framework.
4
4
 
5
+ <!-- zudo-docs:start -->
6
+
7
+ **Documentation:** [zudojs.oyinlola.site/docs/packages-constants](https://zudojs.oyinlola.site/docs/packages-constants) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-constants.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
8
+
9
+ <!-- zudo-docs:end -->
10
+
5
11
  ## Installation
6
12
 
7
13
  ```bash
@@ -31,7 +37,11 @@ formatDuration(150_000); // "2m 30s"
31
37
  All constant objects are `Object.freeze`d `as const` maps, and every map has a
32
38
  matching literal-union type (`HttpStatusCode`, `HttpMethod`, `Environment`,
33
39
  `LifecycleState`, `SchemaIssueCode`, ...). Sets such as `HTTP_METHODS` and
34
- `SCHEMA_FORBIDDEN_KEYS` are immutable at runtime: `add`/`delete`/`clear` throw.
40
+ `SCHEMA_FORBIDDEN_KEYS` are immutable at runtime: `add`/`delete`/`clear` throw,
41
+ and their values live in a private store, so `Set.prototype.clear.call(set)`
42
+ cannot empty them either. They implement `ReadonlySet` but are not `Set`
43
+ instances (`instanceof Set` is `false`); copy with `new Set(value)` when a
44
+ mutable set is needed.
35
45
 
36
46
  ## Modules
37
47
 
@@ -81,7 +91,11 @@ formatDuration(4_500_000); // "1h 15m"
81
91
  - Branded types (`UserId`, `EventId`, `Timestamp`, `Url`, `EmailAddress`, ...)
82
92
  with `createX` factories — the validating ones (`createTimestamp`,
83
93
  `createUrl`, `createEmailAddress`, `createHexString`, `createBase64String`,
84
- `createJsonString`) throw `InvalidConstantError` on bad input
94
+ `createJsonString`, `createTenantId`) throw `InvalidConstantError` on bad
95
+ input. `createTimestamp` rejects impossible dates such as `2024-02-30`;
96
+ `createTenantId` NFKC-normalizes, trims and lowercases, then enforces
97
+ `TENANT_ID_PATTERN` (`[a-z0-9][a-z0-9_-]*`) and `MAX_TENANT_ID_LENGTH` (64),
98
+ the same rule as `@zudojs/tenancy`
85
99
  - `Limits`, `Defaults`, `Sentinel` and sentinels `NONE`, `UNINITIALIZED`, `EMPTY`
86
100
  - Lifecycle state machine: `LifecycleState`, `LifecyclePhase`, `LIFECYCLE_VALID_TRANSITIONS`, timeouts/retries
87
101
  - Schema constants: `SchemaIssueCode`, `SCHEMA_FORBIDDEN_KEYS` (immutable at runtime), `SCHEMA_STRING_FORMATS`
@@ -92,6 +106,9 @@ formatDuration(4_500_000); // "1h 15m"
92
106
  - `ValidationPattern` — the single source of truth for regexes (EMAIL, UUID,
93
107
  UUID_V4, IPV4, IPV6, ISO_DATE_TIME, URL, SEMVER, PHONE, FILE_NAME, ...).
94
108
  `SCHEMA_STRING_FORMATS` re-exports these rather than redefining them.
109
+ `EMAIL` accepts exactly what `isEmail` in `@zudojs/types` accepts
110
+ (structural, at most 254 characters, no `..`; `o'brien@example.com` and
111
+ `user@host.123` pass).
95
112
  - `ValidationLength`, `ValidationRange`
96
113
 
97
114
  ```typescript
@@ -116,6 +133,9 @@ ValidationLength.EMAIL; // 254
116
133
  - Injectable `Clock` / `Random` interfaces with `systemClock` / `systemRandom`
117
134
  (fully `node:crypto`-backed, safe for tokens and salts) and deterministic
118
135
  `createMockClock()` / `createMockRandom(seed)` for tests
136
+ - `Random` is branded: `createMockRandom` returns a `MockRandom`
137
+ (`deterministic: true`), which the compiler refuses wherever a `Random` is
138
+ required, so a predictable generator cannot mint tokens by accident
119
139
  - `MockClock` adds `advance(ms)` and `set(timestampOrDate)`
120
140
 
121
141
  ```typescript
@@ -138,5 +158,6 @@ systemRandom.randomString(32); // CSPRNG-backed
138
158
 
139
159
  ### Errors
140
160
 
141
- - `InvalidConstantError`, `ConstantContextError` (built on `@zudojs/errors`;
142
- error codes themselves live in `@zudojs/errors`)
161
+ - `InvalidConstantError`, `ConstantContextError`, re-exported from
162
+ `@zudojs/errors` (which owns them), so `instanceof` matches either import
163
+ path
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * @module common/common
6
6
  */
7
- import { type UserId, type EventId, type RequestId, type CorrelationId, type SessionId, type TenantId, type MessageId, type MessageCausationId, type TokenId, type Timestamp, type Url, type EmailAddress, type HexString, type Base64String, type JsonString } from "./common.type.js";
7
+ import { type UserId, type EventId, type RequestId, type CorrelationId, type SessionId, type MessageId, type MessageCausationId, type TokenId, type Url, type HexString, type Base64String, type JsonString } from "./common.type.js";
8
8
  /** Sentinel value indicating absence of a value. */
9
9
  export declare const NONE: "NONE";
10
10
  /** Sentinel value indicating an uninitialized state. */
@@ -109,10 +109,6 @@ export declare function createCorrelationId(id: string): CorrelationId;
109
109
  * Create a branded SessionId from a raw string.
110
110
  */
111
111
  export declare function createSessionId(id: string): SessionId;
112
- /**
113
- * Create a branded TenantId from a raw string.
114
- */
115
- export declare function createTenantId(id: string): TenantId;
116
112
  /**
117
113
  * Create a branded MessageId from a raw string.
118
114
  */
@@ -125,25 +121,12 @@ export declare function createMessageCausationId(id: string): MessageCausationId
125
121
  * Create a branded TokenId from a raw string.
126
122
  */
127
123
  export declare function createTokenId(id: string): TokenId;
128
- /**
129
- * Create a branded Timestamp from an ISO 8601 string.
130
- *
131
- * @throws {InvalidConstantError} if the input is not a valid ISO 8601
132
- * date-time string (e.g. `2024-01-01T00:00:00.000Z`).
133
- */
134
- export declare function createTimestamp(iso: string): Timestamp;
135
124
  /**
136
125
  * Create a branded Url from a raw string.
137
126
  *
138
127
  * @throws {InvalidConstantError} if the input is not a parseable URL.
139
128
  */
140
129
  export declare function createUrl(url: string): Url;
141
- /**
142
- * Create a branded EmailAddress from a raw string.
143
- *
144
- * @throws {InvalidConstantError} if the input is not a valid email address.
145
- */
146
- export declare function createEmailAddress(email: string): EmailAddress;
147
130
  /**
148
131
  * Create a branded HexString from a raw string.
149
132
  *
@@ -6,7 +6,6 @@
6
6
  */
7
7
  import {} from "./common.type.js";
8
8
  import { ContentTypes, Charset } from "../http/httpContentType.type.js";
9
- import { ValidationPattern } from "../validation/validation.pattern.type.js";
10
9
  import { InvalidConstantError } from "../constantsErrors/constantsError.base.js";
11
10
  /** Sentinel value indicating absence of a value. */
12
11
  export const NONE = "NONE";
@@ -122,12 +121,6 @@ export function createCorrelationId(id) {
122
121
  export function createSessionId(id) {
123
122
  return id;
124
123
  }
125
- /**
126
- * Create a branded TenantId from a raw string.
127
- */
128
- export function createTenantId(id) {
129
- return id;
130
- }
131
124
  /**
132
125
  * Create a branded MessageId from a raw string.
133
126
  */
@@ -146,19 +139,6 @@ export function createMessageCausationId(id) {
146
139
  export function createTokenId(id) {
147
140
  return id;
148
141
  }
149
- /**
150
- * Create a branded Timestamp from an ISO 8601 string.
151
- *
152
- * @throws {InvalidConstantError} if the input is not a valid ISO 8601
153
- * date-time string (e.g. `2024-01-01T00:00:00.000Z`).
154
- */
155
- export function createTimestamp(iso) {
156
- if (!ValidationPattern.ISO_DATE_TIME.test(iso) ||
157
- Number.isNaN(Date.parse(iso))) {
158
- throw new InvalidConstantError(`Invalid ISO 8601 timestamp: ${JSON.stringify(iso)}`);
159
- }
160
- return iso;
161
- }
162
142
  /**
163
143
  * Create a branded Url from a raw string.
164
144
  *
@@ -170,17 +150,6 @@ export function createUrl(url) {
170
150
  }
171
151
  return url;
172
152
  }
173
- /**
174
- * Create a branded EmailAddress from a raw string.
175
- *
176
- * @throws {InvalidConstantError} if the input is not a valid email address.
177
- */
178
- export function createEmailAddress(email) {
179
- if (email.length > 254 || !ValidationPattern.EMAIL.test(email)) {
180
- throw new InvalidConstantError(`Invalid email address: ${JSON.stringify(email)}`);
181
- }
182
- return email;
183
- }
184
153
  /**
185
154
  * Create a branded HexString from a raw string.
186
155
  *
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Validating branded-type factories: tenant ids, timestamps and email
3
+ * addresses. Each rejects input that the brand promises is impossible.
4
+ *
5
+ * @module common/factory
6
+ */
7
+ import type { EmailAddress, TenantId, Timestamp } from "./common.type.js";
8
+ /**
9
+ * Characters a tenant id may contain, after normalization.
10
+ *
11
+ * A tenant id is concatenated into cache keys, log lines, schema names and
12
+ * file paths, so anything that could act as a separator or a path segment is
13
+ * rejected here rather than escaped at every use site. Matches the rule in
14
+ * `@zudojs/tenancy`.
15
+ */
16
+ export declare const TENANT_ID_PATTERN: RegExp;
17
+ /** Maximum accepted tenant id length. */
18
+ export declare const MAX_TENANT_ID_LENGTH = 64;
19
+ /**
20
+ * Create a validated, normalized TenantId.
21
+ *
22
+ * Input is NFKC-normalized, trimmed and lowercased, then checked against
23
+ * {@link TENANT_ID_PATTERN} and {@link MAX_TENANT_ID_LENGTH}, so two
24
+ * spellings of one tenant cannot become two tenants and no id can carry a
25
+ * separator or path segment.
26
+ *
27
+ * @throws {InvalidConstantError} when the value is not a valid tenant id.
28
+ */
29
+ export declare function createTenantId(id: string): TenantId;
30
+ /**
31
+ * Create a branded Timestamp from an ISO 8601 string.
32
+ *
33
+ * @throws {InvalidConstantError} if the input is not a valid ISO 8601
34
+ * date-time string (e.g. `2024-01-01T00:00:00.000Z`) naming a real calendar
35
+ * date and time.
36
+ */
37
+ export declare function createTimestamp(iso: string): Timestamp;
38
+ /**
39
+ * Create a branded EmailAddress from a raw string.
40
+ *
41
+ * Accepts exactly what `ValidationPattern.EMAIL` accepts, which is the same
42
+ * set as `isEmail` in `@zudojs/types`, bounded at 254 characters.
43
+ *
44
+ * @throws {InvalidConstantError} if the input is not a valid email address.
45
+ */
46
+ export declare function createEmailAddress(email: string): EmailAddress;
47
+ //# sourceMappingURL=common.factory.d.ts.map
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Validating branded-type factories: tenant ids, timestamps and email
3
+ * addresses. Each rejects input that the brand promises is impossible.
4
+ *
5
+ * @module common/factory
6
+ */
7
+ import { ValidationPattern } from "../validation/validation.pattern.type.js";
8
+ import { ValidationLength } from "../validation/validation.constant.js";
9
+ import { InvalidConstantError } from "../constantsErrors/constantsError.base.js";
10
+ /**
11
+ * Characters a tenant id may contain, after normalization.
12
+ *
13
+ * A tenant id is concatenated into cache keys, log lines, schema names and
14
+ * file paths, so anything that could act as a separator or a path segment is
15
+ * rejected here rather than escaped at every use site. Matches the rule in
16
+ * `@zudojs/tenancy`.
17
+ */
18
+ export const TENANT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/u;
19
+ /** Maximum accepted tenant id length. */
20
+ export const MAX_TENANT_ID_LENGTH = 64;
21
+ /**
22
+ * Create a validated, normalized TenantId.
23
+ *
24
+ * Input is NFKC-normalized, trimmed and lowercased, then checked against
25
+ * {@link TENANT_ID_PATTERN} and {@link MAX_TENANT_ID_LENGTH}, so two
26
+ * spellings of one tenant cannot become two tenants and no id can carry a
27
+ * separator or path segment.
28
+ *
29
+ * @throws {InvalidConstantError} when the value is not a valid tenant id.
30
+ */
31
+ export function createTenantId(id) {
32
+ const normalized = typeof id === "string" ? id.normalize("NFKC").trim().toLowerCase() : "";
33
+ if (normalized.length === 0 ||
34
+ normalized.length > MAX_TENANT_ID_LENGTH ||
35
+ !TENANT_ID_PATTERN.test(normalized)) {
36
+ throw new InvalidConstantError(`Invalid tenant id: ${JSON.stringify(id)}`);
37
+ }
38
+ return normalized;
39
+ }
40
+ const ISO_PARTS = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-](\d{2}):(\d{2}))?$/;
41
+ /**
42
+ * Whether an ISO 8601 date-time names an instant that exists.
43
+ *
44
+ * `Date.parse` rolls day overflow into the next month (`2024-02-30` becomes
45
+ * March 1), so the calendar date is round-tripped through `Date.UTC` and the
46
+ * time and offset fields are range-checked explicitly.
47
+ */
48
+ function isRealIsoDateTime(iso) {
49
+ const match = ISO_PARTS.exec(iso);
50
+ if (match === null)
51
+ return false;
52
+ const [year, month, day, hour, minute, second, offH, offM] = match
53
+ .slice(1)
54
+ .map((part) => (part === undefined ? 0 : Number(part)));
55
+ const date = new Date(Date.UTC(year, month - 1, day));
56
+ return (date.getUTCFullYear() === year &&
57
+ date.getUTCMonth() === month - 1 &&
58
+ date.getUTCDate() === day &&
59
+ hour <= 23 &&
60
+ minute <= 59 &&
61
+ second <= 59 &&
62
+ offH <= 23 &&
63
+ offM <= 59);
64
+ }
65
+ /**
66
+ * Create a branded Timestamp from an ISO 8601 string.
67
+ *
68
+ * @throws {InvalidConstantError} if the input is not a valid ISO 8601
69
+ * date-time string (e.g. `2024-01-01T00:00:00.000Z`) naming a real calendar
70
+ * date and time.
71
+ */
72
+ export function createTimestamp(iso) {
73
+ if (typeof iso !== "string" ||
74
+ !ValidationPattern.ISO_DATE_TIME.test(iso) ||
75
+ !isRealIsoDateTime(iso)) {
76
+ throw new InvalidConstantError(`Invalid ISO 8601 timestamp: ${JSON.stringify(iso)}`);
77
+ }
78
+ return iso;
79
+ }
80
+ /**
81
+ * Create a branded EmailAddress from a raw string.
82
+ *
83
+ * Accepts exactly what `ValidationPattern.EMAIL` accepts, which is the same
84
+ * set as `isEmail` in `@zudojs/types`, bounded at 254 characters.
85
+ *
86
+ * @throws {InvalidConstantError} if the input is not a valid email address.
87
+ */
88
+ export function createEmailAddress(email) {
89
+ if (typeof email !== "string" ||
90
+ email.length > ValidationLength.EMAIL ||
91
+ !ValidationPattern.EMAIL.test(email)) {
92
+ throw new InvalidConstantError(`Invalid email address: ${JSON.stringify(email)}`);
93
+ }
94
+ return email;
95
+ }
96
+ //# sourceMappingURL=common.factory.js.map
@@ -40,6 +40,12 @@ export declare const SerializationLimits: Readonly<{
40
40
  readonly MAX_TRANSFORMERS: 256;
41
41
  /** Maximum length of a type tag string. */
42
42
  readonly MAX_TYPE_TAG_LENGTH: 128;
43
+ /**
44
+ * Maximum number of decimal digits accepted when decoding or coercing a
45
+ * BigInt from text. `BigInt()` parsing is quadratic in the digit count, so
46
+ * an unbounded string is a CPU denial-of-service vector.
47
+ */
48
+ readonly MAX_BIGINT_DIGITS: 4096;
43
49
  }>;
44
50
  /** Type-tag sentinel keys for JSON representation. */
45
51
  export declare const SerializationTags: Readonly<{
@@ -42,6 +42,12 @@ export const SerializationLimits = Object.freeze({
42
42
  MAX_TRANSFORMERS: 256,
43
43
  /** Maximum length of a type tag string. */
44
44
  MAX_TYPE_TAG_LENGTH: 128,
45
+ /**
46
+ * Maximum number of decimal digits accepted when decoding or coercing a
47
+ * BigInt from text. `BigInt()` parsing is quadratic in the digit count, so
48
+ * an unbounded string is a CPU denial-of-service vector.
49
+ */
50
+ MAX_BIGINT_DIGITS: 4096,
45
51
  });
46
52
  /** Type-tag sentinel keys for JSON representation. */
47
53
  export const SerializationTags = Object.freeze({
@@ -4,7 +4,8 @@
4
4
  * @module common
5
5
  */
6
6
  export { type Brand, type EntityId, type UserId, type EventId, type RequestId, type CorrelationId, type SessionId, type TenantId, type MessageId, type MessageCausationId, type TokenId, type Timestamp, type Url, type EmailAddress, type HexString, type Base64String, type JsonString, } from "./common.type.js";
7
- export { NONE, UNINITIALIZED, EMPTY, Limits, Defaults, Sentinel, createUserId, createEventId, createRequestId, createCorrelationId, createSessionId, createTenantId, createMessageId, createMessageCausationId, createTokenId, createTimestamp, createUrl, createEmailAddress, createHexString, createBase64String, createJsonString, } from "./common.constant.js";
7
+ export { NONE, UNINITIALIZED, EMPTY, Limits, Defaults, Sentinel, createUserId, createEventId, createRequestId, createCorrelationId, createSessionId, createMessageId, createMessageCausationId, createTokenId, createUrl, createHexString, createBase64String, createJsonString, } from "./common.constant.js";
8
+ export { createTenantId, createTimestamp, createEmailAddress, TENANT_ID_PATTERN, MAX_TENANT_ID_LENGTH, } from "./common.factory.js";
8
9
  export { SerializationFormat, SerializationContentType, SerializationLimits, SerializationTags, SERIALIZATION_SCHEMA_VERSION, } from "./common.serialization.js";
9
10
  export { SchemaIssueCode, SCHEMA_DEFAULT_MAX_DEPTH, SCHEMA_DEFAULT_MAX_STRING_LENGTH, SCHEMA_DEFAULT_MAX_ARRAY_LENGTH, SCHEMA_DEFAULT_MAX_OBJECT_KEYS, SCHEMA_FORBIDDEN_KEYS, SCHEMA_STRING_FORMATS, } from "./common.schema.js";
10
11
  export { LifecycleState, LifecyclePhase, LIFECYCLE_VALID_TRANSITIONS, LIFECYCLE_DEFAULT_TIMEOUT, LIFECYCLE_DEFAULT_START_TIMEOUT, LIFECYCLE_DEFAULT_STOP_TIMEOUT, LIFECYCLE_DEFAULT_SHUTDOWN_TIMEOUT, LIFECYCLE_DEFAULT_CONCURRENCY, LIFECYCLE_DEFAULT_RETRY_ATTEMPTS, LIFECYCLE_DEFAULT_RETRY_DELAY, LIFECYCLE_DEFAULT_RETRY_MAX_DELAY, } from "./common.lifecycle.js";
@@ -4,7 +4,8 @@
4
4
  * @module common
5
5
  */
6
6
  export {} from "./common.type.js";
7
- export { NONE, UNINITIALIZED, EMPTY, Limits, Defaults, Sentinel, createUserId, createEventId, createRequestId, createCorrelationId, createSessionId, createTenantId, createMessageId, createMessageCausationId, createTokenId, createTimestamp, createUrl, createEmailAddress, createHexString, createBase64String, createJsonString, } from "./common.constant.js";
7
+ export { NONE, UNINITIALIZED, EMPTY, Limits, Defaults, Sentinel, createUserId, createEventId, createRequestId, createCorrelationId, createSessionId, createMessageId, createMessageCausationId, createTokenId, createUrl, createHexString, createBase64String, createJsonString, } from "./common.constant.js";
8
+ export { createTenantId, createTimestamp, createEmailAddress, TENANT_ID_PATTERN, MAX_TENANT_ID_LENGTH, } from "./common.factory.js";
8
9
  export { SerializationFormat, SerializationContentType, SerializationLimits, SerializationTags, SERIALIZATION_SCHEMA_VERSION, } from "./common.serialization.js";
9
10
  export { SchemaIssueCode, SCHEMA_DEFAULT_MAX_DEPTH, SCHEMA_DEFAULT_MAX_STRING_LENGTH, SCHEMA_DEFAULT_MAX_ARRAY_LENGTH, SCHEMA_DEFAULT_MAX_OBJECT_KEYS, SCHEMA_FORBIDDEN_KEYS, SCHEMA_STRING_FORMATS, } from "./common.schema.js";
10
11
  export { LifecycleState, LifecyclePhase, LIFECYCLE_VALID_TRANSITIONS, LIFECYCLE_DEFAULT_TIMEOUT, LIFECYCLE_DEFAULT_START_TIMEOUT, LIFECYCLE_DEFAULT_STOP_TIMEOUT, LIFECYCLE_DEFAULT_SHUTDOWN_TIMEOUT, LIFECYCLE_DEFAULT_CONCURRENCY, LIFECYCLE_DEFAULT_RETRY_ATTEMPTS, LIFECYCLE_DEFAULT_RETRY_DELAY, LIFECYCLE_DEFAULT_RETRY_MAX_DELAY, } from "./common.lifecycle.js";
@@ -1,24 +1,10 @@
1
1
  /**
2
- * Error classes specific to the constants package.
2
+ * Error classes used by the constants package.
3
+ *
4
+ * Owned by `@zudojs/errors` and re-exported here, so `instanceof` checks
5
+ * against either import path match the same errors.
3
6
  *
4
7
  * @module constantsErrors
5
8
  */
6
- import { BaseError, ErrorCode, type ErrorMetadata } from "@zudojs/errors";
7
- /**
8
- * Error thrown when an invalid constant value is used.
9
- */
10
- export declare class InvalidConstantError extends BaseError {
11
- constructor(message: string, options?: {
12
- readonly code?: ErrorCode;
13
- readonly metadata?: ErrorMetadata;
14
- });
15
- }
16
- /**
17
- * Error thrown when a constant is used outside its valid context.
18
- */
19
- export declare class ConstantContextError extends BaseError {
20
- constructor(message: string, options?: {
21
- readonly metadata?: ErrorMetadata;
22
- });
23
- }
9
+ export { InvalidConstantError, ConstantContextError } from "@zudojs/errors";
24
10
  //# sourceMappingURL=constantsError.base.d.ts.map
@@ -1,33 +1,10 @@
1
1
  /**
2
- * Error classes specific to the constants package.
2
+ * Error classes used by the constants package.
3
+ *
4
+ * Owned by `@zudojs/errors` and re-exported here, so `instanceof` checks
5
+ * against either import path match the same errors.
3
6
  *
4
7
  * @module constantsErrors
5
8
  */
6
- import { BaseError, ErrorCode, ErrorCategory, ErrorSeverity, } from "@zudojs/errors";
7
- /**
8
- * Error thrown when an invalid constant value is used.
9
- */
10
- export class InvalidConstantError extends BaseError {
11
- constructor(message, options) {
12
- super(message, {
13
- code: options?.code ?? ErrorCode.CONFIGURATION_INVALID,
14
- category: ErrorCategory.VALIDATION,
15
- severity: ErrorSeverity.ERROR,
16
- metadata: options?.metadata,
17
- });
18
- }
19
- }
20
- /**
21
- * Error thrown when a constant is used outside its valid context.
22
- */
23
- export class ConstantContextError extends BaseError {
24
- constructor(message, options) {
25
- super(message, {
26
- code: ErrorCode.INVALID_INPUT,
27
- category: ErrorCategory.VALIDATION,
28
- severity: ErrorSeverity.ERROR,
29
- metadata: options?.metadata,
30
- });
31
- }
32
- }
9
+ export { InvalidConstantError, ConstantContextError } from "@zudojs/errors";
33
10
  //# sourceMappingURL=constantsError.base.js.map
@@ -3,24 +3,42 @@
3
3
  *
4
4
  * `Object.freeze` does not protect the internal slots of a `Set`, so a
5
5
  * "frozen" Set still allows `.add()`, `.delete()`, and `.clear()` at runtime.
6
- * This subclass hard-disables all mutators, making it safe to expose
7
- * security-sensitive sets (e.g. forbidden schema keys) as `ReadonlySet`.
6
+ * Subclassing `Set` and overriding the mutators is not enough either:
7
+ * `Set.prototype.clear.call(instance)` reaches the internal slot directly.
8
+ * This class therefore holds its values in a private `Set` that no outside
9
+ * code can reference, and exposes only the `ReadonlySet` surface.
8
10
  *
9
11
  * @module internal/immutableSet
10
12
  */
11
13
  /**
12
- * A `Set` whose mutating methods (`add`, `delete`, `clear`) always throw.
14
+ * A read-only set whose mutating methods (`add`, `delete`, `clear`) always
15
+ * throw, and whose backing storage is unreachable from outside the instance.
13
16
  *
14
- * Values are inserted via `super.add` during construction only; afterwards
15
- * the collection is permanently immutable.
17
+ * It is not a `Set` subclass, so `instanceof Set` is `false`; iterate it or
18
+ * copy it with `new Set(value)` when a mutable `Set` is needed.
16
19
  */
17
- export declare class ImmutableSet<T> extends Set<T> {
20
+ export declare class ImmutableSet<T> implements ReadonlySet<T> {
21
+ #private;
18
22
  constructor(values?: Iterable<T>);
23
+ /** Number of values in the set. */
24
+ get size(): number;
25
+ /** Whether `value` is in the set. */
26
+ has(value: T): boolean;
27
+ /** Calls `callbackfn` once per value, in insertion order. */
28
+ forEach(callbackfn: (value: T, value2: T, set: ReadonlySet<T>) => void, thisArg?: unknown): void;
29
+ /** Iterates `[value, value]` pairs, mirroring `Set.prototype.entries`. */
30
+ entries(): SetIterator<[T, T]>;
31
+ /** Iterates the values. */
32
+ keys(): SetIterator<T>;
33
+ /** Iterates the values. */
34
+ values(): SetIterator<T>;
35
+ /** Iterates the values. */
36
+ [Symbol.iterator](): SetIterator<T>;
19
37
  /** @throws {TypeError} always — this set is immutable. */
20
- add(_value: T): this;
38
+ add(_value: T): never;
21
39
  /** @throws {TypeError} always — this set is immutable. */
22
- delete(_value: T): boolean;
40
+ delete(_value: T): never;
23
41
  /** @throws {TypeError} always — this set is immutable. */
24
- clear(): void;
42
+ clear(): never;
25
43
  }
26
44
  //# sourceMappingURL=immutableSet.d.ts.map
@@ -3,27 +3,57 @@
3
3
  *
4
4
  * `Object.freeze` does not protect the internal slots of a `Set`, so a
5
5
  * "frozen" Set still allows `.add()`, `.delete()`, and `.clear()` at runtime.
6
- * This subclass hard-disables all mutators, making it safe to expose
7
- * security-sensitive sets (e.g. forbidden schema keys) as `ReadonlySet`.
6
+ * Subclassing `Set` and overriding the mutators is not enough either:
7
+ * `Set.prototype.clear.call(instance)` reaches the internal slot directly.
8
+ * This class therefore holds its values in a private `Set` that no outside
9
+ * code can reference, and exposes only the `ReadonlySet` surface.
8
10
  *
9
11
  * @module internal/immutableSet
10
12
  */
13
+ const setHas = Set.prototype.has;
11
14
  /**
12
- * A `Set` whose mutating methods (`add`, `delete`, `clear`) always throw.
15
+ * A read-only set whose mutating methods (`add`, `delete`, `clear`) always
16
+ * throw, and whose backing storage is unreachable from outside the instance.
13
17
  *
14
- * Values are inserted via `super.add` during construction only; afterwards
15
- * the collection is permanently immutable.
18
+ * It is not a `Set` subclass, so `instanceof Set` is `false`; iterate it or
19
+ * copy it with `new Set(value)` when a mutable `Set` is needed.
16
20
  */
17
- export class ImmutableSet extends Set {
21
+ export class ImmutableSet {
22
+ #values;
18
23
  constructor(values) {
19
- super();
20
- if (values !== undefined) {
21
- for (const value of values) {
22
- super.add(value);
23
- }
24
- }
24
+ this.#values = new Set(values);
25
25
  Object.freeze(this);
26
26
  }
27
+ /** Number of values in the set. */
28
+ get size() {
29
+ return this.#values.size;
30
+ }
31
+ /** Whether `value` is in the set. */
32
+ has(value) {
33
+ return setHas.call(this.#values, value);
34
+ }
35
+ /** Calls `callbackfn` once per value, in insertion order. */
36
+ forEach(callbackfn, thisArg) {
37
+ for (const value of this.#values) {
38
+ callbackfn.call(thisArg, value, value, this);
39
+ }
40
+ }
41
+ /** Iterates `[value, value]` pairs, mirroring `Set.prototype.entries`. */
42
+ entries() {
43
+ return this.#values.entries();
44
+ }
45
+ /** Iterates the values. */
46
+ keys() {
47
+ return this.#values.keys();
48
+ }
49
+ /** Iterates the values. */
50
+ values() {
51
+ return this.#values.values();
52
+ }
53
+ /** Iterates the values. */
54
+ [Symbol.iterator]() {
55
+ return this.#values.values();
56
+ }
27
57
  /** @throws {TypeError} always — this set is immutable. */
28
58
  add(_value) {
29
59
  throw new TypeError("Cannot add to an immutable Set");
@@ -37,4 +67,5 @@ export class ImmutableSet extends Set {
37
67
  throw new TypeError("Cannot clear an immutable Set");
38
68
  }
39
69
  }
70
+ Object.freeze(ImmutableSet.prototype);
40
71
  //# sourceMappingURL=immutableSet.js.map
@@ -6,5 +6,5 @@
6
6
  export { systemClock, createMockClock } from "./clock.js";
7
7
  export type { Clock, MockClock } from "./clock.js";
8
8
  export { systemRandom, createMockRandom } from "./random.js";
9
- export type { Random } from "./random.js";
9
+ export type { Random, RandomSource, MockRandom } from "./random.js";
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -4,9 +4,15 @@
4
4
  * @module runtime/random
5
5
  */
6
6
  /**
7
- * Provides deterministic randomness for testing.
7
+ * Brand marking an implementation as cryptographically secure.
8
+ *
9
+ * Without it any object with the four method names would satisfy
10
+ * {@link Random}, including the seeded {@link createMockRandom} generator,
11
+ * so a predictable test double could be injected where tokens are minted.
8
12
  */
9
- export interface Random {
13
+ declare const SecureRandomBrand: unique symbol;
14
+ /** The randomness operations shared by {@link Random} and {@link MockRandom}. */
15
+ export interface RandomSource {
10
16
  /**
11
17
  * Returns a random float between 0 (inclusive) and 1 (exclusive).
12
18
  */
@@ -24,6 +30,29 @@ export interface Random {
24
30
  */
25
31
  randomBytes(length: number): Uint8Array;
26
32
  }
33
+ /**
34
+ * Cryptographically secure randomness, safe for tokens, session ids and
35
+ * salts. Branded: only {@link systemRandom} (or an implementation cast
36
+ * deliberately) satisfies it, and {@link MockRandom} never does.
37
+ *
38
+ * The richer, separately-branded `Random` in `@zudojs/types` is the
39
+ * long-term owner of this contract.
40
+ */
41
+ export interface Random extends RandomSource {
42
+ /** @internal Marks the implementation as unpredictable. */
43
+ readonly [SecureRandomBrand]: true;
44
+ }
45
+ /**
46
+ * A seeded, fully predictable generator for tests.
47
+ *
48
+ * Structurally distinct from {@link Random} (it lacks the security brand and
49
+ * carries `deterministic: true`), so the compiler rejects it wherever a
50
+ * secure generator is required.
51
+ */
52
+ export interface MockRandom extends RandomSource {
53
+ /** Marks this as a deterministic generator, not a secure one. */
54
+ readonly deterministic: true;
55
+ }
27
56
  /**
28
57
  * Default random backed by `node:crypto` (CSPRNG).
29
58
  *
@@ -40,5 +69,6 @@ export declare const systemRandom: Random;
40
69
  * with `Math.imul` for exact 32-bit arithmetic. Outputs are always in
41
70
  * `[0, 1)`. Not cryptographically secure — tests only.
42
71
  */
43
- export declare function createMockRandom(seed?: number): Random;
72
+ export declare function createMockRandom(seed?: number): MockRandom;
73
+ export {};
44
74
  //# sourceMappingURL=random.d.ts.map
@@ -50,6 +50,7 @@ export function createMockRandom(seed = 1) {
50
50
  return state / 0x100000000;
51
51
  }
52
52
  return {
53
+ deterministic: true,
53
54
  random: next,
54
55
  randomInt: (min, max) => Math.floor(next() * (max - min + 1)) + min,
55
56
  randomString: (length) => {
@@ -12,11 +12,15 @@
12
12
  */
13
13
  export declare const ValidationPattern: Readonly<{
14
14
  /**
15
- * Simplified email pattern (pragmatic subset of RFC 5322).
15
+ * Structural email pattern — the monorepo's one acceptance set.
16
16
  *
17
- * The domain part rejects consecutive dots, leading/trailing dots, and
18
- * labels that start or end with a hyphen. Combine with
19
- * `ValidationLength.EMAIL` (254) for a length bound.
17
+ * Accepts exactly what `isEmail` in `@zudojs/types` accepts: at most 254
18
+ * characters (RFC 5321), no `..` anywhere, a local part free of
19
+ * whitespace, `@`, `,`, `;`, `<`, `>`, `"`, `[`, `]` and `\\`, and a
20
+ * domain of two or more hyphen-safe labels. Deliberately structural, not a
21
+ * full RFC 5322 parser: `o'brien@example.com` and `user@host.123` pass,
22
+ * deliverability is a verification step's job. The length bound is a
23
+ * lookahead, so it runs before the label pattern can backtrack.
20
24
  */
21
25
  readonly EMAIL: RegExp;
22
26
  /** UUID — any version/variant nibble (use UUID_V4 for strict v4). */
@@ -12,13 +12,17 @@
12
12
  */
13
13
  export const ValidationPattern = Object.freeze({
14
14
  /**
15
- * Simplified email pattern (pragmatic subset of RFC 5322).
15
+ * Structural email pattern — the monorepo's one acceptance set.
16
16
  *
17
- * The domain part rejects consecutive dots, leading/trailing dots, and
18
- * labels that start or end with a hyphen. Combine with
19
- * `ValidationLength.EMAIL` (254) for a length bound.
17
+ * Accepts exactly what `isEmail` in `@zudojs/types` accepts: at most 254
18
+ * characters (RFC 5321), no `..` anywhere, a local part free of
19
+ * whitespace, `@`, `,`, `;`, `<`, `>`, `"`, `[`, `]` and `\\`, and a
20
+ * domain of two or more hyphen-safe labels. Deliberately structural, not a
21
+ * full RFC 5322 parser: `o'brien@example.com` and `user@host.123` pass,
22
+ * deliverability is a verification step's job. The length bound is a
23
+ * lookahead, so it runs before the label pattern can backtrack.
20
24
  */
21
- EMAIL: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/,
25
+ EMAIL: /^(?=[\s\S]{1,254}$)(?![\s\S]*\.\.)[^\s@,;<>"[\]\\]+@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/i,
22
26
  /** UUID — any version/variant nibble (use UUID_V4 for strict v4). */
23
27
  UUID: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
24
28
  /** UUID v4 (strict: version nibble 4, RFC 4122 variant). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/constants",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Shared constants, enums, and type-safe literals for the Zudojs framework.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -26,7 +26,7 @@
26
26
  "!dist/.tsbuildinfo"
27
27
  ],
28
28
  "dependencies": {
29
- "@zudojs/errors": "1.0.1"
29
+ "@zudojs/errors": "1.2.0"
30
30
  },
31
31
  "engines": {
32
32
  "node": ">=24.0.0"