@orkestrel/contract 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2026 @@
1
+ //#region src/core/constants.ts
2
+ /**
3
+ * The seven standard JSON Schema `type` names, frozen.
4
+ *
5
+ * @remarks
6
+ * The runtime source of truth for the {@link JSONSchemaType} vocabulary. Compose
7
+ * it with the shipped primitives instead of reaching for a bespoke guard:
8
+ * `literalOf(...JSON_SCHEMA_TYPES)` is the guard, and
9
+ * `parseEnum(value, JSON_SCHEMA_TYPES)` / `parseEnumField(record, path, JSON_SCHEMA_TYPES)`
10
+ * is the parser.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { JSON_SCHEMA_TYPES, literalOf, parseEnumField } from '@src/core'
15
+ *
16
+ * const isSchemaType = literalOf(...JSON_SCHEMA_TYPES) // Guard<JSONSchemaType>
17
+ * parseEnumField(schema, 'type', JSON_SCHEMA_TYPES) // JSONSchemaType | undefined
18
+ * ```
19
+ */
20
+ var JSON_SCHEMA_TYPES = Object.freeze([
21
+ "null",
22
+ "boolean",
23
+ "object",
24
+ "array",
25
+ "number",
26
+ "integer",
27
+ "string"
28
+ ]);
29
+ //#endregion
30
+ //#region src/core/validators.ts
31
+ /** Determine whether a value is `null`. */
32
+ function isNull(value) {
33
+ return value === null;
34
+ }
35
+ /** Determine whether a value is `undefined`. */
36
+ function isUndefined(value) {
37
+ return value === void 0;
38
+ }
39
+ /** Determine whether a value is defined (neither `null` nor `undefined`). */
40
+ function isDefined(value) {
41
+ return value !== null && value !== void 0;
42
+ }
43
+ /** Determine whether a value is a string. */
44
+ function isString(value) {
45
+ return typeof value === "string";
46
+ }
47
+ /**
48
+ * Determine whether a value is a number.
49
+ *
50
+ * @remarks
51
+ * Includes `NaN` and `±Infinity` — use {@link isFiniteNumber} to exclude them.
52
+ */
53
+ function isNumber(value) {
54
+ return typeof value === "number";
55
+ }
56
+ /** Determine whether a value is a finite number (excludes `NaN` and `±Infinity`). */
57
+ function isFiniteNumber(value) {
58
+ return typeof value === "number" && Number.isFinite(value);
59
+ }
60
+ /** Determine whether a value is a finite integer (excludes `NaN`, `±Infinity`, and fractional numbers). */
61
+ function isInteger(value) {
62
+ return Number.isInteger(value);
63
+ }
64
+ /** Determine whether a value is a boolean. */
65
+ function isBoolean(value) {
66
+ return typeof value === "boolean";
67
+ }
68
+ /** Determine whether a value is exactly `true`. */
69
+ function isTrue(value) {
70
+ return value === true;
71
+ }
72
+ /** Determine whether a value is exactly `false`. */
73
+ function isFalse(value) {
74
+ return value === false;
75
+ }
76
+ /** Determine whether a value is a bigint. */
77
+ function isBigInt(value) {
78
+ return typeof value === "bigint";
79
+ }
80
+ /** Determine whether a value is a symbol. */
81
+ function isSymbol(value) {
82
+ return typeof value === "symbol";
83
+ }
84
+ /** Determine whether a value is callable. */
85
+ function isFunction(value) {
86
+ return typeof value === "function";
87
+ }
88
+ /** Determine whether a value is a string or `null`. */
89
+ function isNullableString(value) {
90
+ return value === null || isString(value);
91
+ }
92
+ /** Determine whether a value is a number or `null` (the number may be `NaN` / `±Infinity`). */
93
+ function isNullableNumber(value) {
94
+ return value === null || isNumber(value);
95
+ }
96
+ /** Determine whether a value is a boolean or `null`. */
97
+ function isNullableBoolean(value) {
98
+ return value === null || isBoolean(value);
99
+ }
100
+ /** Determine whether a value is a `Date`. */
101
+ function isDate(value) {
102
+ return value instanceof Date;
103
+ }
104
+ /** Determine whether a value is a `RegExp`. */
105
+ function isRegExp(value) {
106
+ return value instanceof RegExp;
107
+ }
108
+ /** Determine whether a value is an `Error`. */
109
+ function isError(value) {
110
+ return value instanceof Error;
111
+ }
112
+ /** Determine whether a value is a native `Promise` (use {@link isPromiseLike} for any thenable). */
113
+ function isPromise(value) {
114
+ return value instanceof Promise;
115
+ }
116
+ /**
117
+ * Determine whether a value is promise-like — an object exposing callable
118
+ * `then`, `catch`, and `finally` methods.
119
+ *
120
+ * @remarks
121
+ * Accepts any object with all three methods, not only native `Promise`
122
+ * instances. Use {@link isPromise} when you specifically need `instanceof Promise`.
123
+ */
124
+ function isPromiseLike(value) {
125
+ if (!isObject(value)) return false;
126
+ const outcome = attempt(() => {
127
+ const thenValue = Reflect.get(value, "then");
128
+ const catchValue = Reflect.get(value, "catch");
129
+ const finallyValue = Reflect.get(value, "finally");
130
+ return isFunction(thenValue) && isFunction(catchValue) && isFunction(finallyValue);
131
+ });
132
+ return outcome.success && outcome.value;
133
+ }
134
+ /** Determine whether a value is an `ArrayBuffer`. */
135
+ function isArrayBuffer(value) {
136
+ return value instanceof ArrayBuffer;
137
+ }
138
+ /**
139
+ * Determine whether a value is a `SharedArrayBuffer`.
140
+ *
141
+ * @remarks
142
+ * Guards the global existence of `SharedArrayBuffer` first — safe where it is
143
+ * absent or disabled (e.g. a context that is not cross-origin isolated).
144
+ */
145
+ function isSharedArrayBuffer(value) {
146
+ return typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer;
147
+ }
148
+ /**
149
+ * Determine whether a value implements the iterable protocol (`Symbol.iterator`).
150
+ *
151
+ * @remarks
152
+ * Strings are explicitly included: a string has a callable `Symbol.iterator`
153
+ * but is not an object, so the generic object path alone would miss it.
154
+ */
155
+ function isIterable(value) {
156
+ if (isString(value)) return true;
157
+ if (!isObject(value)) return false;
158
+ const outcome = attempt(() => isFunction(Reflect.get(value, Symbol.iterator)));
159
+ return outcome.success && outcome.value;
160
+ }
161
+ /** Determine whether a value implements the async iterable protocol (`Symbol.asyncIterator`). */
162
+ function isAsyncIterable(value) {
163
+ if (!isObject(value)) return false;
164
+ const outcome = attempt(() => isFunction(Reflect.get(value, Symbol.asyncIterator)));
165
+ return outcome.success && outcome.value;
166
+ }
167
+ /**
168
+ * Determine whether a value is a non-null object.
169
+ *
170
+ * @remarks
171
+ * `true` for arrays, class instances, plain objects, `Map`, `Set`, etc. — use
172
+ * {@link isRecord} when you need a plain-record check.
173
+ */
174
+ function isObject(value) {
175
+ return typeof value === "object" && value !== null;
176
+ }
177
+ /**
178
+ * Determine whether a value is a plain record (object literal or null-prototype),
179
+ * not an array or class instance.
180
+ *
181
+ * @remarks
182
+ * Use instead of {@link isObject} to distinguish a plain `{}` /
183
+ * `Object.create(null)` from arrays, `Date`, `Map`, etc. The prototype-chain
184
+ * test is realm-agnostic: rather than comparing against the current realm's
185
+ * `Object.prototype` (which a plain object from another `vm.Context`, iframe,
186
+ * or worker would fail), it accepts any value whose prototype is `null`, OR
187
+ * whose prototype's own prototype is `null` — the shape every plain object
188
+ * has in every realm, since `Object.prototype` itself always sits one step
189
+ * above `null`. Arrays and class instances are still rejected: an array's
190
+ * prototype chain runs through `Array.prototype` before `null`, and a class
191
+ * instance's runs through the class's own prototype. The whole body runs
192
+ * inside `attempt` (AGENTS §14) so a revoked `Proxy` or a hostile
193
+ * `getPrototypeOf` trap cannot escape as a thrown error.
194
+ */
195
+ function isRecord(value) {
196
+ const outcome = attempt(() => {
197
+ if (!isObject(value) || isArray(value)) return false;
198
+ const prototype = Object.getPrototypeOf(value);
199
+ return prototype === null || Object.getPrototypeOf(prototype) === null;
200
+ });
201
+ return outcome.success && outcome.value;
202
+ }
203
+ /** Determine whether a value is a `Map`. */
204
+ function isMap(value) {
205
+ return value instanceof Map;
206
+ }
207
+ /** Determine whether a value is a `Set`. */
208
+ function isSet(value) {
209
+ return value instanceof Set;
210
+ }
211
+ /** Determine whether a value is a `WeakMap`. */
212
+ function isWeakMap(value) {
213
+ return value instanceof WeakMap;
214
+ }
215
+ /** Determine whether a value is a `WeakSet`. */
216
+ function isWeakSet(value) {
217
+ return value instanceof WeakSet;
218
+ }
219
+ /** Determine whether a value is an array. */
220
+ function isArray(value) {
221
+ return Array.isArray(value);
222
+ }
223
+ /** Determine whether a value is a `DataView`. */
224
+ function isDataView(value) {
225
+ return value instanceof DataView;
226
+ }
227
+ /** Determine whether a value is an `ArrayBufferView` (any typed array or `DataView`). */
228
+ function isArrayBufferView(value) {
229
+ return ArrayBuffer.isView(value);
230
+ }
231
+ /** Determine whether a value is an `Int8Array`. */
232
+ function isInt8Array(value) {
233
+ return value instanceof Int8Array;
234
+ }
235
+ /** Determine whether a value is a `Uint8Array`. */
236
+ function isUint8Array(value) {
237
+ return value instanceof Uint8Array;
238
+ }
239
+ /** Determine whether a value is a `Uint8ClampedArray`. */
240
+ function isUint8ClampedArray(value) {
241
+ return value instanceof Uint8ClampedArray;
242
+ }
243
+ /** Determine whether a value is an `Int16Array`. */
244
+ function isInt16Array(value) {
245
+ return value instanceof Int16Array;
246
+ }
247
+ /** Determine whether a value is a `Uint16Array`. */
248
+ function isUint16Array(value) {
249
+ return value instanceof Uint16Array;
250
+ }
251
+ /** Determine whether a value is an `Int32Array`. */
252
+ function isInt32Array(value) {
253
+ return value instanceof Int32Array;
254
+ }
255
+ /** Determine whether a value is a `Uint32Array`. */
256
+ function isUint32Array(value) {
257
+ return value instanceof Uint32Array;
258
+ }
259
+ /** Determine whether a value is a `Float32Array`. */
260
+ function isFloat32Array(value) {
261
+ return value instanceof Float32Array;
262
+ }
263
+ /** Determine whether a value is a `Float64Array`. */
264
+ function isFloat64Array(value) {
265
+ return value instanceof Float64Array;
266
+ }
267
+ /**
268
+ * Determine whether a value is a `BigInt64Array`.
269
+ *
270
+ * @remarks
271
+ * Guards the global existence of `BigInt64Array` first — safe in environments
272
+ * that pre-date the BigInt typed-array additions.
273
+ */
274
+ function isBigInt64Array(value) {
275
+ return typeof BigInt64Array !== "undefined" && value instanceof BigInt64Array;
276
+ }
277
+ /**
278
+ * Determine whether a value is a `BigUint64Array`.
279
+ *
280
+ * @remarks
281
+ * Guards the global existence of `BigUint64Array` first — safe in environments
282
+ * that pre-date the BigInt typed-array additions.
283
+ */
284
+ function isBigUint64Array(value) {
285
+ return typeof BigUint64Array !== "undefined" && value instanceof BigUint64Array;
286
+ }
287
+ /** Determine whether a value is the empty string `''`. */
288
+ function isEmptyString(value) {
289
+ return isString(value) && value.length === 0;
290
+ }
291
+ /** Determine whether a value is an empty array. */
292
+ function isEmptyArray(value) {
293
+ return isArray(value) && value.length === 0;
294
+ }
295
+ /** Determine whether a value is an empty plain object (no own string or enumerable symbol keys). */
296
+ function isEmptyObject(value) {
297
+ if (!isRecord(value)) return false;
298
+ return Object.keys(value).length === 0 && enumerableSymbolCount(value) === 0;
299
+ }
300
+ /** Determine whether a value is an empty `Map`. */
301
+ function isEmptyMap(value) {
302
+ return value instanceof Map && value.size === 0;
303
+ }
304
+ /** Determine whether a value is an empty `Set`. */
305
+ function isEmptySet(value) {
306
+ return value instanceof Set && value.size === 0;
307
+ }
308
+ /** Determine whether a value is a non-empty string (at least one character). */
309
+ function isNonEmptyString(value) {
310
+ return isString(value) && value.length > 0;
311
+ }
312
+ /** Determine whether a value is a non-empty array (at least one element). */
313
+ function isNonEmptyArray(value) {
314
+ return isArray(value) && value.length > 0;
315
+ }
316
+ /** Determine whether a value is a non-empty plain object (at least one own string or enumerable symbol key). */
317
+ function isNonEmptyObject(value) {
318
+ if (!isRecord(value)) return false;
319
+ return Object.keys(value).length > 0 || enumerableSymbolCount(value) > 0;
320
+ }
321
+ /** Determine whether a value is a non-empty `Map` (at least one entry). */
322
+ function isNonEmptyMap(value) {
323
+ return value instanceof Map && value.size > 0;
324
+ }
325
+ /** Determine whether a value is a non-empty `Set` (at least one element). */
326
+ function isNonEmptySet(value) {
327
+ return value instanceof Set && value.size > 0;
328
+ }
329
+ /** Determine whether a value is a function that declares zero parameters (`Function.length === 0`). */
330
+ function isZeroArg(value) {
331
+ return isFunction(value) && value.length === 0;
332
+ }
333
+ /**
334
+ * Determine whether a value is a native `async function`.
335
+ *
336
+ * @remarks
337
+ * Uses `constructor.name === 'AsyncFunction'` — not `instanceof`, which is
338
+ * unreliable across realms. The `?.` keeps the guard total (§14): a function
339
+ * whose `constructor` was nulled yields `undefined`, never a thrown `null.name`.
340
+ */
341
+ function isAsyncFunction(value) {
342
+ return isFunction(value) && value.constructor?.name === "AsyncFunction";
343
+ }
344
+ /** Determine whether a value is a generator function (`function*`). */
345
+ function isGeneratorFunction(value) {
346
+ return isFunction(value) && value.constructor?.name === "GeneratorFunction";
347
+ }
348
+ /** Determine whether a value is an async generator function (`async function*`). */
349
+ function isAsyncGeneratorFunction(value) {
350
+ return isFunction(value) && value.constructor?.name === "AsyncGeneratorFunction";
351
+ }
352
+ /** Determine whether a value is a zero-argument async function. */
353
+ function isZeroArgAsync(value) {
354
+ return isZeroArg(value) && isAsyncFunction(value);
355
+ }
356
+ /** Determine whether a value is a zero-argument generator function. */
357
+ function isZeroArgGenerator(value) {
358
+ return isZeroArg(value) && isGeneratorFunction(value);
359
+ }
360
+ /** Determine whether a value is a zero-argument async generator function. */
361
+ function isZeroArgAsyncGenerator(value) {
362
+ return isZeroArg(value) && isAsyncGeneratorFunction(value);
363
+ }
364
+ /**
365
+ * Determine whether a value can be used as a `new`-target constructor.
366
+ *
367
+ * @remarks
368
+ * Probes with `Reflect.construct(String, [], value)`: a real constructor
369
+ * succeeds, while arrow functions, plain functions, and non-functions throw
370
+ * and yield `false`. Never throws. Backs the `instanceOf` combinator.
371
+ */
372
+ function isConstructor(value) {
373
+ if (!isFunction(value)) return false;
374
+ try {
375
+ Reflect.construct(String, [], value);
376
+ return true;
377
+ } catch {
378
+ return false;
379
+ }
380
+ }
381
+ /**
382
+ * Determine whether a value is a cycle-safe JSON value.
383
+ *
384
+ * @remarks
385
+ * Total guard: never throws, returns `false` for cycles, functions, `Date`
386
+ * instances, class instances, `NaN`, and `±Infinity`. Arrays and plain records
387
+ * are walked with an ancestor set so recursive input fails instead of hanging.
388
+ * The whole walk runs inside `attempt` (AGENTS §14): a hostile getter on a
389
+ * record property, or a revoked `Proxy` anywhere in the structure, is caught
390
+ * and yields `false` instead of escaping as a thrown error.
391
+ *
392
+ * @param value - The value to test
393
+ * @returns `true` when the value has a JSON representation
394
+ *
395
+ * @example
396
+ * ```ts
397
+ * isJSONValue({ nested: [1, 'x', null] }) // true
398
+ * isJSONValue(Number.NaN) // false
399
+ * ```
400
+ */
401
+ function isJSONValue(value) {
402
+ const ancestors = /* @__PURE__ */ new WeakSet();
403
+ const check = (entry) => {
404
+ if (entry === null || isString(entry) || isBoolean(entry) || isFiniteNumber(entry)) return true;
405
+ if (Array.isArray(entry)) {
406
+ if (ancestors.has(entry)) return false;
407
+ ancestors.add(entry);
408
+ const valid = entry.every(check);
409
+ ancestors.delete(entry);
410
+ return valid;
411
+ }
412
+ if (!isRecord(entry)) return false;
413
+ if (ancestors.has(entry)) return false;
414
+ ancestors.add(entry);
415
+ const valid = Object.values(entry).every(check);
416
+ ancestors.delete(entry);
417
+ return valid;
418
+ };
419
+ const outcome = attempt(() => check(value));
420
+ return outcome.success && outcome.value;
421
+ }
422
+ /**
423
+ * Determine whether a value is a primitive JSON value.
424
+ *
425
+ * @remarks
426
+ * The flat leaf of any JSON document: `null`, a string, a **finite** number, or
427
+ * a boolean. Uses {@link isFiniteNumber} (not {@link isNumber}) because real JSON
428
+ * carries no `NaN` / `±Infinity` — `JSON.stringify(NaN)` is `'null'`.
429
+ *
430
+ * The recursive {@link isJSONValue} guard is shipped and stays total with
431
+ * cycle-safe walking. Dedicated `isJSONObject` / `isJSONSchema` validators and
432
+ * the broad `JSONSchemaDefinition` remain omitted; compose narrower shapes with
433
+ * the combinators and gate untrusted strings with `parseJSON` / `parseJSONAs`.
434
+ *
435
+ * @param value - The value to test
436
+ * @returns `true` when `value` is `null`, a string, a finite number, or a boolean
437
+ *
438
+ * @example
439
+ * ```ts
440
+ * isJSONPrimitive(null) // true
441
+ * isJSONPrimitive('hi') // true
442
+ * isJSONPrimitive(42) // true
443
+ * isJSONPrimitive(Number.NaN) // false — not representable in JSON
444
+ * isJSONPrimitive({}) // false
445
+ * ```
446
+ */
447
+ function isJSONPrimitive(value) {
448
+ return isNull(value) || isString(value) || isFiniteNumber(value) || isBoolean(value);
449
+ }
450
+ //#endregion
451
+ //#region src/core/helpers.ts
452
+ /**
453
+ * Invoke a callback and capture its outcome as a {@link Result}, never letting
454
+ * a throw escape.
455
+ *
456
+ * @remarks
457
+ * The single sanctioned never-throw boundary for the guards (AGENTS §14). The
458
+ * `whereOf`, `lazyOf`, and `transformOf` combinators invoke caller-supplied
459
+ * callbacks *inside* a guard body, yet a guard must NEVER throw — it returns a
460
+ * `boolean`. This converts a throwing callback into a `Failure` so the
461
+ * surrounding guard can treat it as a non-match instead of propagating the
462
+ * exception, written once and shared rather than copy-pasted as ad-hoc
463
+ * `try`/`catch`.
464
+ *
465
+ * @param callback - The callback to invoke with no arguments
466
+ * @returns A `Success` carrying the return value, or a `Failure` carrying the
467
+ * thrown reason normalised to an `Error`
468
+ *
469
+ * @example
470
+ * ```ts
471
+ * const outcome = attempt(() => predicate(value))
472
+ * return outcome.success && outcome.value
473
+ * ```
474
+ */
475
+ function attempt(callback) {
476
+ try {
477
+ return {
478
+ success: true,
479
+ value: callback()
480
+ };
481
+ } catch (reason) {
482
+ if (reason instanceof Error) return {
483
+ success: false,
484
+ error: reason
485
+ };
486
+ let message = "Unknown thrown value";
487
+ try {
488
+ message = String(reason);
489
+ } catch {}
490
+ return {
491
+ success: false,
492
+ error: new Error(message)
493
+ };
494
+ }
495
+ }
496
+ /**
497
+ * Resolve a (possibly nested) field value from a record by a key or key path.
498
+ *
499
+ * @remarks
500
+ * A single `string` is ONE key (never split on `.`, so dotted keys are safe); a
501
+ * string array descends left-to-right through nested objects. Intermediates may
502
+ * be any object — records, class instances, or arrays indexed by string. Returns
503
+ * `undefined` the moment a segment is missing or lands on a non-object, so the
504
+ * lookup is total — even against a hostile getter or Proxy trap that throws on
505
+ * read, contained via {@link attempt} so the throw never escapes.
506
+ *
507
+ * @param record - The source record
508
+ * @param path - A property key, or a key path descending into nested objects
509
+ * @returns The resolved value, or `undefined`
510
+ *
511
+ * @example
512
+ * ```ts
513
+ * resolveField({ user: { name: 'Ada' } }, ['user', 'name']) // 'Ada'
514
+ * resolveField({ 'a.b': 1 }, 'a.b') // 1 (one key)
515
+ * resolveField({ a: 1 }, ['a', 'b']) // undefined
516
+ * ```
517
+ */
518
+ function resolveField(record, path) {
519
+ const keys = isString(path) ? [path] : path;
520
+ let current = record;
521
+ for (const key of keys) {
522
+ if (!isObject(current)) return void 0;
523
+ const container = current;
524
+ const outcome = attempt(() => Reflect.get(container, key));
525
+ if (!outcome.success) return void 0;
526
+ current = outcome.value;
527
+ }
528
+ return current;
529
+ }
530
+ /**
531
+ * Build a deterministic pseudo-random source seeded from a single number.
532
+ *
533
+ * @remarks
534
+ * A mulberry32 generator — the same seed always yields the same sequence, so
535
+ * generated seed data is reproducible across runs. Used as the default random
536
+ * source for {@link compileGenerator}, seeded from the wall clock so casual
537
+ * callers still get varied output without passing a source themselves.
538
+ *
539
+ * @param seed - The seed for the sequence
540
+ * @returns A {@link RandomFunction} returning values in `[0, 1)`
541
+ *
542
+ * @example
543
+ * ```ts
544
+ * const random = seededRandom(42)
545
+ * random() // always the same first value for seed 42
546
+ * ```
547
+ */
548
+ function seededRandom(seed) {
549
+ let state = seed >>> 0;
550
+ return () => {
551
+ state = state + 1831565813 >>> 0;
552
+ let t = state;
553
+ t = Math.imul(t ^ t >>> 15, t | 1);
554
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
555
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
556
+ };
557
+ }
558
+ /**
559
+ * Count the enumerable own-symbol keys on a value.
560
+ *
561
+ * @remarks
562
+ * String keys are ignored — only `Object.getOwnPropertySymbols` entries whose
563
+ * descriptor is `enumerable` are counted. Backs the object-emptiness guards
564
+ * (`isEmptyObject` / `isNonEmptyObject`) so a record keyed only by an
565
+ * enumerable symbol is not mistaken for empty.
566
+ *
567
+ * @param value - The object to inspect
568
+ * @returns The number of enumerable own-symbol keys
569
+ *
570
+ * @example
571
+ * ```ts
572
+ * const flag = Symbol('flag')
573
+ * enumerableSymbolCount(Object.defineProperty({}, flag, { value: 1, enumerable: true })) // 1
574
+ * enumerableSymbolCount({}) // 0
575
+ * ```
576
+ */
577
+ function enumerableSymbolCount(value) {
578
+ let count = 0;
579
+ for (const symbol of Object.getOwnPropertySymbols(value)) if (Object.getOwnPropertyDescriptor(value, symbol)?.enumerable) count += 1;
580
+ return count;
581
+ }
582
+ /**
583
+ * Narrow a compiled {@link JSONSchema} down to the open `Readonly<Record<string, unknown>>` shape
584
+ * tool definitions advertise as `parameters` — through the {@link isRecord} boundary guard, never
585
+ * an assertion (AGENTS §14).
586
+ *
587
+ * @remarks
588
+ * A `JSONSchema` is the closed contract-compiler fragment (it has no index signature), whereas a
589
+ * tool advertises its `parameters` as an open record. The two are structurally compatible but not
590
+ * assignable, so the schema crosses that boundary through `isRecord` — a compiled contract schema
591
+ * is always a record, so the guard passes; the `undefined` fallback only satisfies the type's
592
+ * optionality. This is the single sanctioned narrowing from a compiled contract schema to the open
593
+ * tool-parameters record, so the crossing lives once rather than being copy-pasted per call site.
594
+ *
595
+ * @param schema - The compiled JSON Schema (a contract's `schema`)
596
+ * @returns The schema as the open tool-parameters record, or `undefined` when it is not a record
597
+ *
598
+ * @example
599
+ * ```ts
600
+ * import { createContract, schemaToParameters } from '@src/core'
601
+ *
602
+ * const contract = createContract(shape)
603
+ * const parameters = schemaToParameters(contract.schema) // the open record a tool advertises
604
+ * ```
605
+ */
606
+ function schemaToParameters(schema) {
607
+ return isRecord(schema) ? schema : void 0;
608
+ }
609
+ //#endregion
610
+ //#region src/core/combinators.ts
611
+ function arrayOf(elementGuard) {
612
+ return (value) => {
613
+ if (!isArray(value)) return false;
614
+ const outcome = attempt(() => value.every(elementGuard));
615
+ return outcome.success && outcome.value;
616
+ };
617
+ }
618
+ function tupleOf(...guards) {
619
+ return (value) => {
620
+ if (!isArray(value)) return false;
621
+ const outcome = attempt(() => {
622
+ if (value.length !== guards.length) return false;
623
+ for (let index = 0; index < guards.length; index += 1) {
624
+ const guard = guards[index];
625
+ if (!guard?.(value[index])) return false;
626
+ }
627
+ return true;
628
+ });
629
+ return outcome.success && outcome.value;
630
+ };
631
+ }
632
+ /**
633
+ * Build a guard that accepts values identical (via `Object.is`) to one of the
634
+ * provided literal primitives.
635
+ *
636
+ * @example
637
+ * ```ts
638
+ * const isRole = literalOf('admin', 'member', 'guest')
639
+ * isRole('admin') // true
640
+ * isRole('owner') // false
641
+ * ```
642
+ */
643
+ function literalOf(...literals) {
644
+ return (value) => literals.some((literal) => Object.is(literal, value));
645
+ }
646
+ /**
647
+ * Build a guard that accepts instances of the provided constructor.
648
+ *
649
+ * @remarks
650
+ * Verifies that `ctor` is a real constructor (via {@link isConstructor}) first,
651
+ * so passing an arrow function does not silently produce a broken guard.
652
+ *
653
+ * @example
654
+ * ```ts
655
+ * const isDateValue = instanceOf(Date)
656
+ * isDateValue(new Date()) // true
657
+ * isDateValue({}) // false
658
+ * ```
659
+ */
660
+ function instanceOf(ctor) {
661
+ return (value) => isConstructor(ctor) && isObject(value) && value instanceof ctor;
662
+ }
663
+ /**
664
+ * Build a guard from a native `enum` or any object whose values are strings or
665
+ * numbers.
666
+ *
667
+ * @example
668
+ * ```ts
669
+ * enum Direction { Up = 'up', Down = 'down' }
670
+ * const isDirection = enumOf(Direction)
671
+ * isDirection('up') // true
672
+ * isDirection('left') // false
673
+ * ```
674
+ */
675
+ function enumOf(enumeration) {
676
+ const values = new Set(Object.values(enumeration));
677
+ return (value) => (isString(value) || isNumber(value)) && values.has(value);
678
+ }
679
+ function setOf(elementGuard) {
680
+ return (value) => {
681
+ if (!isSet(value)) return false;
682
+ const outcome = attempt(() => {
683
+ for (const entry of value) if (!elementGuard(entry)) return false;
684
+ return true;
685
+ });
686
+ return outcome.success && outcome.value;
687
+ };
688
+ }
689
+ function mapOf(keyGuard, valueGuard) {
690
+ return (value) => {
691
+ if (!isMap(value)) return false;
692
+ const outcome = attempt(() => {
693
+ for (const [key, entryValue] of value) if (!keyGuard(key) || !valueGuard(entryValue)) return false;
694
+ return true;
695
+ });
696
+ return outcome.success && outcome.value;
697
+ };
698
+ }
699
+ /**
700
+ * Build a guard that accepts plain records matching a guard shape.
701
+ *
702
+ * @remarks
703
+ * Three calling modes depending on the `optional` argument:
704
+ * - **No `optional`** — all shape keys required; extra keys rejected.
705
+ * - **`optional: K[]`** — the listed keys are optional; all others required.
706
+ * - **`optional: true`** — every shape key is optional.
707
+ *
708
+ * Key presence is tested with `Object.hasOwn`, so a shape key satisfied only by
709
+ * an inherited prototype member (`toString`, `constructor`, …) counts as absent.
710
+ * A non-object / `null` / array input returns `false` rather than throwing. The
711
+ * extra-key check only inspects `Object.keys` (string keys), so an extra
712
+ * enumerable SYMBOL key is never rejected — intentional, for JSON fidelity, and
713
+ * matches the compiled guard.
714
+ *
715
+ * @example
716
+ * ```ts
717
+ * const isUser = recordOf({ name: isString, age: isNumber })
718
+ * isUser({ name: 'Ada', age: 36 }) // true
719
+ * isUser({ name: 'Ada' }) // false — age missing
720
+ *
721
+ * const isPartial = recordOf({ name: isString, age: isNumber }, ['age'])
722
+ * isPartial({ name: 'Ada' }) // true
723
+ * ```
724
+ */
725
+ function recordOf(shape, optional) {
726
+ const allowed = /* @__PURE__ */ new Set();
727
+ for (const key in shape) if (Object.prototype.hasOwnProperty.call(shape, key)) allowed.add(key);
728
+ const optionalSet = new Set(optional === true ? [...allowed] : isArray(optional) ? optional.map((key) => String(key)) : []);
729
+ return (value) => {
730
+ if (!isRecord(value)) return false;
731
+ const outcome = attempt(() => {
732
+ for (const key of Object.keys(value)) if (!allowed.has(key)) return false;
733
+ for (const key in shape) {
734
+ if (!Object.prototype.hasOwnProperty.call(shape, key)) continue;
735
+ const present = Object.hasOwn(value, key);
736
+ if (!optionalSet.has(key) && !present) return false;
737
+ if (present) {
738
+ const guard = shape[key];
739
+ if (!guard(value[key])) return false;
740
+ }
741
+ }
742
+ return true;
743
+ });
744
+ return outcome.success && outcome.value;
745
+ };
746
+ }
747
+ function iterableOf(elementGuard) {
748
+ return (value) => {
749
+ if (!isIterable(value)) return false;
750
+ const outcome = attempt(() => {
751
+ for (const entry of value) if (!elementGuard(entry)) return false;
752
+ return true;
753
+ });
754
+ return outcome.success && outcome.value;
755
+ };
756
+ }
757
+ /**
758
+ * Build a guard that accepts values that are own keys of the provided object.
759
+ *
760
+ * @remarks
761
+ * Membership is tested with `Object.hasOwn`, so inherited prototype-chain keys
762
+ * (`toString`, `constructor`, …) are rejected. An own property that shadows a
763
+ * prototype name is accepted.
764
+ *
765
+ * @example
766
+ * ```ts
767
+ * const COLORS = { red: '#f00', green: '#0f0', blue: '#00f' } as const
768
+ * const isColorKey = keyOf(COLORS)
769
+ * isColorKey('red') // true
770
+ * isColorKey('purple') // false
771
+ * isColorKey('toString') // false — inherited, not an own key
772
+ * ```
773
+ */
774
+ function keyOf(value) {
775
+ return (entry) => (isString(entry) || isSymbol(entry) || isNumber(entry)) && Object.hasOwn(value, entry);
776
+ }
777
+ /**
778
+ * Build a new guard shape by keeping only the listed keys — the structural
779
+ * equivalent of `Pick<T, K>`. Produces a shape for {@link recordOf}, not a guard.
780
+ *
781
+ * @example
782
+ * ```ts
783
+ * const full = { name: isString, age: isNumber, role: isString }
784
+ * const isName = recordOf(pickOf(full, ['name']))
785
+ * isName({ name: 'Ada' }) // true
786
+ * ```
787
+ */
788
+ function pickOf(shape, keys) {
789
+ const result = Object.create(null);
790
+ for (const key of keys) if (Object.prototype.hasOwnProperty.call(shape, key)) result[key] = shape[key];
791
+ return result;
792
+ }
793
+ /**
794
+ * Build a new guard shape by removing the listed keys — the structural
795
+ * equivalent of `Omit<T, K>`. Produces a shape for {@link recordOf}, not a guard.
796
+ *
797
+ * @example
798
+ * ```ts
799
+ * const full = { name: isString, age: isNumber, role: isString }
800
+ * const isPublic = recordOf(omitOf(full, ['role']))
801
+ * isPublic({ name: 'Ada', age: 36 }) // true
802
+ * ```
803
+ */
804
+ function omitOf(shape, keys) {
805
+ const skipped = /* @__PURE__ */ new Set();
806
+ for (const key of keys) skipped.add(key);
807
+ const result = Object.create(null);
808
+ for (const key in shape) {
809
+ if (!Object.prototype.hasOwnProperty.call(shape, key)) continue;
810
+ if (!skipped.has(key)) result[key] = shape[key];
811
+ }
812
+ return result;
813
+ }
814
+ function andOf(left, right) {
815
+ return (value) => left(value) && right(value);
816
+ }
817
+ function orOf(left, right) {
818
+ return (value) => left(value) || right(value);
819
+ }
820
+ /**
821
+ * Negate a guard or predicate — passes when `guard` returns `false`.
822
+ *
823
+ * @remarks
824
+ * Typed as `Guard<unknown>` because `Exclude<unknown, T>` is not useful; use
825
+ * {@link complementOf} when you need the narrowed `Exclude<TBase, TExcluded>`.
826
+ *
827
+ * @example
828
+ * ```ts
829
+ * const isNotNull = notOf(isNull)
830
+ * ```
831
+ */
832
+ function notOf(guard) {
833
+ return (value) => !guard(value);
834
+ }
835
+ /**
836
+ * Build a guard for `Exclude<TBase, TExcluded>` — accepts values that pass
837
+ * `base` but not `excluded`.
838
+ *
839
+ * @example
840
+ * ```ts
841
+ * const isNonEmpty = complementOf(isString, isEmptyString)
842
+ * isNonEmpty('hi') // true
843
+ * isNonEmpty('') // false
844
+ * ```
845
+ */
846
+ function complementOf(base, excluded) {
847
+ return (value) => {
848
+ if (!base(value)) return false;
849
+ return !excluded(value);
850
+ };
851
+ }
852
+ function unionOf(...guards) {
853
+ return (value) => guards.some((guard) => guard(value));
854
+ }
855
+ function intersectionOf(...guards) {
856
+ return (value) => guards.every((guard) => guard(value));
857
+ }
858
+ function whereOf(base, predicate) {
859
+ return (value) => {
860
+ if (!base(value)) return false;
861
+ const outcome = attempt(() => predicate(value));
862
+ return outcome.success && outcome.value;
863
+ };
864
+ }
865
+ /**
866
+ * Defer guard creation until first use by calling `thunk()` on every
867
+ * invocation.
868
+ *
869
+ * @remarks
870
+ * `thunk` is called on every guard call, not cached — this lets it close over a
871
+ * binding assigned *after* `lazyOf` is called, the primary use case for
872
+ * self-referential recursive guards. Per §14 a throw from `thunk` (or the guard
873
+ * it resolves to) is contained and reported as a non-match.
874
+ *
875
+ * A recursive guard built this way has no cycle/depth detection: a cyclic or
876
+ * pathologically deep input is stack-bounded — the overflow is contained and the
877
+ * guard returns `false` rather than throwing, but it is not validated correctly
878
+ * past that bound.
879
+ *
880
+ * @example
881
+ * ```ts
882
+ * type Tree = { value: number; children: Tree[] }
883
+ * let isTree: Guard<Tree>
884
+ * isTree = recordOf({ value: isNumber, children: arrayOf(lazyOf(() => isTree)) })
885
+ * ```
886
+ */
887
+ function lazyOf(thunk) {
888
+ return (value) => {
889
+ const outcome = attempt(() => thunk()(value));
890
+ return outcome.success && outcome.value;
891
+ };
892
+ }
893
+ function transformOf(base, project, target) {
894
+ return (value) => {
895
+ if (!base(value)) return false;
896
+ const outcome = attempt(() => project(value));
897
+ return outcome.success && target(outcome.value);
898
+ };
899
+ }
900
+ /**
901
+ * Build a guard that accepts finite numbers within an inclusive `[min, max]`
902
+ * range.
903
+ *
904
+ * @remarks
905
+ * Refines {@link isFiniteNumber} with the bound comparison, so `NaN` /
906
+ * `±Infinity` are rejected before any comparison runs. An absent bound never
907
+ * constrains that side. Reused for a number's own value AND, applied to a
908
+ * `.length`, for string and array length refinements — the single source of the
909
+ * bound logic shared by the compiled guard and parser (compilers.ts).
910
+ *
911
+ * @example
912
+ * ```ts
913
+ * const inRange = boundsOf(1, 5)
914
+ * inRange(3) // true
915
+ * inRange(0) // false — below min
916
+ * inRange(6) // false — above max
917
+ *
918
+ * const atLeastTwo = boundsOf(2)
919
+ * atLeastTwo(2) // true — unbounded above
920
+ * ```
921
+ */
922
+ function boundsOf(min, max) {
923
+ return whereOf(isFiniteNumber, (value) => (min === void 0 || value >= min) && (max === void 0 || value <= max));
924
+ }
925
+ /**
926
+ * Build a guard that accepts strings matching a regular expression.
927
+ *
928
+ * @example
929
+ * ```ts
930
+ * const isHex = matchOf(/^[0-9a-f]+$/)
931
+ * isHex('1a2f') // true
932
+ * isHex('xyz') // false
933
+ * ```
934
+ */
935
+ function matchOf(pattern) {
936
+ return whereOf(isString, (value) => pattern.test(value));
937
+ }
938
+ /**
939
+ * Build a guard that accepts strings satisfying optional length and pattern
940
+ * refinements — `min` / `max` length and a `pattern`.
941
+ *
942
+ * @remarks
943
+ * Composes {@link isString} with {@link boundsOf} on the string's `.length` and
944
+ * an inline `pattern.test` (the same refinement {@link matchOf} performs). When all three options are absent it returns
945
+ * the bare {@link isString} guard (the unconstrained fast path), so an
946
+ * unrefined string leaf pays no wrapping cost. The single source of the string
947
+ * refinement shared by the compiled guard and parser (compilers.ts).
948
+ *
949
+ * @example
950
+ * ```ts
951
+ * const isSlug = stringOf({ min: 1, max: 32, pattern: /^[a-z-]+$/ })
952
+ * isSlug('hello-world') // true
953
+ * isSlug('') // false — below min
954
+ * isSlug('Hello') // false — pattern miss
955
+ *
956
+ * stringOf() // identical to isString
957
+ * ```
958
+ */
959
+ function stringOf(options) {
960
+ const min = options?.min;
961
+ const max = options?.max;
962
+ const pattern = options?.pattern;
963
+ if (min === void 0 && max === void 0 && pattern === void 0) return isString;
964
+ const withinLength = boundsOf(min, max);
965
+ return whereOf(isString, (value) => withinLength(value.length) && (pattern === void 0 || pattern.test(value)));
966
+ }
967
+ /**
968
+ * Extend a guard to also allow `null`.
969
+ *
970
+ * @example
971
+ * ```ts
972
+ * const isNullableString = nullableOf(isString)
973
+ * isNullableString('hi') // true
974
+ * isNullableString(null) // true
975
+ * isNullableString(42) // false
976
+ * ```
977
+ */
978
+ function nullableOf(guard) {
979
+ return (value) => value === null || guard(value);
980
+ }
981
+ /**
982
+ * Extend a guard to also allow `undefined` — the optional counterpart of
983
+ * {@link nullableOf}.
984
+ *
985
+ * @example
986
+ * ```ts
987
+ * const isOptionalString = optionalOf(isString)
988
+ * isOptionalString('hi') // true
989
+ * isOptionalString(undefined) // true
990
+ * isOptionalString(null) // false
991
+ * ```
992
+ */
993
+ function optionalOf(guard) {
994
+ return (value) => value === void 0 || guard(value);
995
+ }
996
+ //#endregion
997
+ //#region src/core/parsers.ts
998
+ /**
999
+ * Parse an unknown value to a string.
1000
+ *
1001
+ * @remarks
1002
+ * A string is returned unchanged; a finite number is coerced to its decimal
1003
+ * string (`42` → `'42'`). `NaN`, `±Infinity`, and every other type → `undefined`.
1004
+ *
1005
+ * @param value - The value to parse
1006
+ * @returns A string, or `undefined`
1007
+ */
1008
+ function parseString(value) {
1009
+ if (isString(value)) return value;
1010
+ if (isFiniteNumber(value)) return String(value);
1011
+ }
1012
+ /**
1013
+ * Parse an unknown value to a finite number.
1014
+ *
1015
+ * @remarks
1016
+ * A finite number is returned unchanged; a non-blank numeric string is parsed
1017
+ * via `Number(...)`. `NaN`, `±Infinity`, blank/non-numeric strings, and every
1018
+ * other type → `undefined`.
1019
+ *
1020
+ * @param value - The value to parse
1021
+ * @returns A finite number, or `undefined`
1022
+ */
1023
+ function parseNumber(value) {
1024
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
1025
+ if (isString(value)) {
1026
+ if (value.trim() === "") return void 0;
1027
+ const parsed = Number(value);
1028
+ return Number.isFinite(parsed) ? parsed : void 0;
1029
+ }
1030
+ }
1031
+ /**
1032
+ * Parse an unknown value to a finite integer.
1033
+ *
1034
+ * @remarks
1035
+ * Accepts whatever {@link parseNumber} accepts, then requires the result to have
1036
+ * no fractional part. `3.14` / `'3.14'` → `undefined`.
1037
+ *
1038
+ * @param value - The value to parse
1039
+ * @returns A finite integer, or `undefined`
1040
+ */
1041
+ function parseInteger(value) {
1042
+ const parsed = parseNumber(value);
1043
+ if (parsed === void 0) return void 0;
1044
+ return Number.isInteger(parsed) ? parsed : void 0;
1045
+ }
1046
+ /**
1047
+ * Parse an unknown value to a boolean.
1048
+ *
1049
+ * @remarks
1050
+ * A boolean is returned unchanged. The strings `'true'` / `'false'` / `'1'` /
1051
+ * `'0'` and the numbers `1` / `0` coerce to the matching boolean. Everything
1052
+ * else → `undefined`.
1053
+ *
1054
+ * @param value - The value to parse
1055
+ * @returns A boolean, or `undefined`
1056
+ */
1057
+ function parseBoolean(value) {
1058
+ if (typeof value === "boolean") return value;
1059
+ if (value === "true" || value === "1" || value === 1) return true;
1060
+ if (value === "false" || value === "0" || value === 0) return false;
1061
+ }
1062
+ /**
1063
+ * Parse an unknown value to `null`.
1064
+ *
1065
+ * @remarks
1066
+ * A successful parse returns `null` itself — distinct from the `undefined`
1067
+ * failure sentinel every other parser in this file uses. Only `null` passes;
1068
+ * every other value (including `undefined`) → `undefined`.
1069
+ *
1070
+ * @param value - The value to parse
1071
+ * @returns `null` on a successful parse, or `undefined`
1072
+ */
1073
+ function parseNull(value) {
1074
+ return isNull(value) ? value : void 0;
1075
+ }
1076
+ /**
1077
+ * Parse an unknown value to a plain record — the input reference, never cloned.
1078
+ *
1079
+ * @param value - The value to parse
1080
+ * @returns The record, or `undefined`
1081
+ */
1082
+ function parseRecord(value) {
1083
+ return isRecord(value) ? value : void 0;
1084
+ }
1085
+ /**
1086
+ * Parse an unknown value to an array — the input reference, never cloned —
1087
+ * optionally guarding every element.
1088
+ *
1089
+ * @remarks
1090
+ * Without a `guard`, element types are NOT verified; let `T` default to
1091
+ * `unknown` rather than asserting a specific element type.
1092
+ *
1093
+ * @param value - The value to parse
1094
+ * @param guard - Optional element guard
1095
+ * @returns The array, or `undefined`
1096
+ */
1097
+ function parseArray(value, guard) {
1098
+ if (!isArray(value)) return void 0;
1099
+ if (guard !== void 0 && !value.every(guard)) return void 0;
1100
+ return value;
1101
+ }
1102
+ /**
1103
+ * Parse an unknown value to a cycle-safe JSON value — the input reference,
1104
+ * never cloned.
1105
+ *
1106
+ * @remarks
1107
+ * Unlike {@link parseRecord} / {@link parseArray}, this is a DEEP gate: it
1108
+ * walks the entire tree via {@link isJSONValue} rather than checking only the
1109
+ * top-level shape. That walk is cycle-safe and total (never throws) because
1110
+ * `isJSONValue` runs its own probe inside a guard, so an adversarial
1111
+ * structure (a cycle, a hostile getter) yields `undefined` instead of hanging
1112
+ * or throwing.
1113
+ *
1114
+ * @param value - The value to parse
1115
+ * @returns The value, or `undefined` when it is not a valid JSON value
1116
+ */
1117
+ function parseJSONValue(value) {
1118
+ return isJSONValue(value) ? value : void 0;
1119
+ }
1120
+ /**
1121
+ * Parse an unknown value as one of the allowed literal primitives.
1122
+ *
1123
+ * @remarks
1124
+ * Pairs with {@link literalOf} — both match by `Object.is`, so the
1125
+ * `parseEnum ↔ literalOf(...allowed)` pairing covers every literal primitive
1126
+ * (string, number, or boolean), not only strings. Matching is identity, never
1127
+ * cross-type coercion: `parseEnum('1', [1])` stays `undefined`.
1128
+ *
1129
+ * @param value - The value to parse
1130
+ * @param allowed - The permitted literal values
1131
+ * @returns The matched literal (by identity), or `undefined`
1132
+ */
1133
+ function parseEnum(value, allowed) {
1134
+ for (const option of allowed) if (Object.is(value, option)) return option;
1135
+ }
1136
+ /**
1137
+ * Read and parse a string field from a record by key or nested key path.
1138
+ *
1139
+ * @param record - The source record
1140
+ * @param path - A property key, or a key path descending into nested objects
1141
+ * @returns A string, or `undefined`
1142
+ */
1143
+ function parseStringField(record, path) {
1144
+ return parseString(resolveField(record, path));
1145
+ }
1146
+ /**
1147
+ * Read and parse a finite-number field from a record by key or nested key path.
1148
+ *
1149
+ * @param record - The source record
1150
+ * @param path - A property key, or a key path descending into nested objects
1151
+ * @returns A finite number, or `undefined`
1152
+ */
1153
+ function parseNumberField(record, path) {
1154
+ return parseNumber(resolveField(record, path));
1155
+ }
1156
+ /**
1157
+ * Read and parse a finite-integer field from a record by key or nested key path.
1158
+ *
1159
+ * @param record - The source record
1160
+ * @param path - A property key, or a key path descending into nested objects
1161
+ * @returns A finite integer, or `undefined`
1162
+ */
1163
+ function parseIntegerField(record, path) {
1164
+ return parseInteger(resolveField(record, path));
1165
+ }
1166
+ /**
1167
+ * Read and parse a boolean field from a record by key or nested key path.
1168
+ *
1169
+ * @param record - The source record
1170
+ * @param path - A property key, or a key path descending into nested objects
1171
+ * @returns A boolean, or `undefined`
1172
+ */
1173
+ function parseBooleanField(record, path) {
1174
+ return parseBoolean(resolveField(record, path));
1175
+ }
1176
+ /**
1177
+ * Read and parse a `null` field from a record by key or nested key path.
1178
+ *
1179
+ * @remarks
1180
+ * A successful parse returns `null` itself — distinct from the `undefined`
1181
+ * failure sentinel, which also covers a missing field.
1182
+ *
1183
+ * @param record - The source record
1184
+ * @param path - A property key, or a key path descending into nested objects
1185
+ * @returns `null` on a successful parse, or `undefined`
1186
+ */
1187
+ function parseNullField(record, path) {
1188
+ return parseNull(resolveField(record, path));
1189
+ }
1190
+ /**
1191
+ * Read and parse a nested record field from a record by key or nested key path.
1192
+ *
1193
+ * @param record - The source record
1194
+ * @param path - A property key, or a key path descending into nested objects
1195
+ * @returns A plain record, or `undefined`
1196
+ */
1197
+ function parseRecordField(record, path) {
1198
+ return parseRecord(resolveField(record, path));
1199
+ }
1200
+ /**
1201
+ * Read and parse an array field from a record by key or nested key path,
1202
+ * optionally guarding elements.
1203
+ *
1204
+ * @param record - The source record
1205
+ * @param path - A property key, or a key path descending into nested objects
1206
+ * @param guard - Optional element guard
1207
+ * @returns An array, or `undefined`
1208
+ */
1209
+ function parseArrayField(record, path, guard) {
1210
+ return parseArray(resolveField(record, path), guard);
1211
+ }
1212
+ /**
1213
+ * Read and parse an enum field from a record by key or nested key path.
1214
+ *
1215
+ * @param record - The source record
1216
+ * @param path - A property key, or a key path descending into nested objects
1217
+ * @param allowed - The permitted literal values
1218
+ * @returns The matched literal, or `undefined`
1219
+ */
1220
+ function parseEnumField(record, path, allowed) {
1221
+ return parseEnum(resolveField(record, path), allowed);
1222
+ }
1223
+ /**
1224
+ * Read and parse a JSON-value field from a record by key or nested key path.
1225
+ *
1226
+ * @remarks
1227
+ * Deep-gates the field's whole subtree via {@link parseJSONValue} — see that
1228
+ * function's remarks for why this differs from the shallow
1229
+ * {@link parseRecordField} / {@link parseArrayField}.
1230
+ *
1231
+ * @param record - The source record
1232
+ * @param path - A property key, or a key path descending into nested objects
1233
+ * @returns The value, or `undefined`
1234
+ */
1235
+ function parseJSONValueField(record, path) {
1236
+ return parseJSONValue(resolveField(record, path));
1237
+ }
1238
+ /**
1239
+ * Parse a JSON string, returning `undefined` instead of throwing.
1240
+ *
1241
+ * @remarks
1242
+ * The safe boundary for untrusted JSON text: a malformed string yields
1243
+ * `undefined`, never an exception. Returns `unknown` — a successful parse proves
1244
+ * nothing about shape, so narrow the result with a guard (or use
1245
+ * {@link parseJSONAs}). A large document is not walked here; parsing is shallow
1246
+ * and lazy validation is the caller's to compose.
1247
+ *
1248
+ * @param value - The JSON string to parse
1249
+ * @returns The parsed value, or `undefined` when `value` is not valid JSON
1250
+ */
1251
+ function parseJSON(value) {
1252
+ try {
1253
+ return JSON.parse(value);
1254
+ } catch {
1255
+ return;
1256
+ }
1257
+ }
1258
+ /**
1259
+ * Parse a JSON string and validate the result against a guard.
1260
+ *
1261
+ * @remarks
1262
+ * The lazy, safe path from an untrusted string to a typed `T`: parse, then check
1263
+ * the parsed value with the guard you bring — typically one composed from the
1264
+ * combinators (`recordOf`, `arrayOf`, …). Only the shape the guard inspects is
1265
+ * validated, so a large document is never walked in full unless the guard does.
1266
+ *
1267
+ * @param value - The JSON string to parse
1268
+ * @param guard - The guard for the expected shape
1269
+ * @returns The parsed value when it satisfies `guard`, otherwise `undefined`
1270
+ *
1271
+ * @example
1272
+ * ```ts
1273
+ * const isConfig = recordOf({ host: isString, tags: arrayOf(isString) })
1274
+ * parseJSONAs('{"host":"localhost","tags":["a"]}', isConfig) // { host: 'localhost', tags: ['a'] }
1275
+ * parseJSONAs('{"host":"localhost"}', isConfig) // undefined — guard fails
1276
+ * parseJSONAs('not json', isConfig) // undefined — never throws
1277
+ * ```
1278
+ */
1279
+ function parseJSONAs(value, guard) {
1280
+ const parsed = parseJSON(value);
1281
+ if (parsed === void 0) return void 0;
1282
+ return guard(parsed) ? parsed : void 0;
1283
+ }
1284
+ //#endregion
1285
+ //#region src/core/compilers.ts
1286
+ /**
1287
+ * Validate that a {@link ContractShape} tree is well-formed — a pure recursive
1288
+ * prepass run before compilation.
1289
+ *
1290
+ * @remarks
1291
+ * Fail-fast, per AGENTS §12: a malformed shape is a programmer error, so this
1292
+ * throws a plain `Error` immediately rather than surfacing as a silently-wrong
1293
+ * guard, parser, schema, or generator later. Checks, recursively:
1294
+ *
1295
+ * - An {@link OptionalShape} is only legal as a direct object-property value —
1296
+ * `optionalShape` wrapping an array item, a union variant, another
1297
+ * optional/nullable's inner shape, `additionalProperties`, or the top-level
1298
+ * shape all throw. An object property IS the one legal placement: its value
1299
+ * is unwrapped to `.inner` before recursing, so `.inner` itself is validated
1300
+ * as a normal (non-optional-wrapping) shape.
1301
+ * - A {@link UnionShape} needs at least one variant; a {@link LiteralShape}
1302
+ * needs at least one value and rejects non-finite (`NaN` / `Infinity` /
1303
+ * `-Infinity`) number values.
1304
+ * - A bounded {@link StringShape} / {@link NumberShape} / {@link ArrayShape}
1305
+ * needs `min <= max` when both are set.
1306
+ * - An integer {@link NumberShape} (`integer: true`) needs a non-empty integer
1307
+ * range: `Math.ceil(min ?? -Infinity) <= Math.floor(max ?? Infinity)`.
1308
+ * - `null` / `json` / `raw` / `boolean` are always-valid leaves. Recursion
1309
+ * continues into array items, object properties (and `additionalProperties`
1310
+ * when it is a shape), union variants, and optional/nullable inner shapes.
1311
+ *
1312
+ * @param shape - The shape to validate
1313
+ * @throws {Error} When the shape is malformed
1314
+ */
1315
+ function validateShape(shape) {
1316
+ switch (shape.type) {
1317
+ case "string":
1318
+ if (shape.min !== void 0 && shape.max !== void 0 && shape.min > shape.max) throw new Error("validateShape: a string shape has min greater than max");
1319
+ return;
1320
+ case "number":
1321
+ if (shape.min !== void 0 && shape.max !== void 0 && shape.min > shape.max) throw new Error("validateShape: a number shape has min greater than max");
1322
+ if (shape.integer === true) {
1323
+ if (Math.ceil(shape.min ?? Number.NEGATIVE_INFINITY) > Math.floor(shape.max ?? Number.POSITIVE_INFINITY)) throw new Error("validateShape: an integer number shape has an empty integer range");
1324
+ }
1325
+ return;
1326
+ case "boolean":
1327
+ case "null":
1328
+ case "json":
1329
+ case "raw": return;
1330
+ case "literal":
1331
+ if (shape.values.length === 0) throw new Error("validateShape: a literal shape needs at least one value");
1332
+ for (const value of shape.values) if (typeof value === "number" && !Number.isFinite(value)) throw new Error("validateShape: a literal shape may not contain non-finite number values");
1333
+ return;
1334
+ case "array":
1335
+ if (shape.min !== void 0 && shape.max !== void 0 && shape.min > shape.max) throw new Error("validateShape: an array shape has min greater than max");
1336
+ validateShape(shape.items);
1337
+ return;
1338
+ case "object": {
1339
+ for (const key of Object.keys(shape.properties)) {
1340
+ const child = shape.properties[key];
1341
+ if (child === void 0) continue;
1342
+ validateShape(child.type === "optional" ? child.inner : child);
1343
+ }
1344
+ const extra = shape.additionalProperties;
1345
+ if (extra !== void 0 && extra !== true && extra !== false) validateShape(extra);
1346
+ return;
1347
+ }
1348
+ case "union":
1349
+ if (shape.variants.length === 0) throw new Error("validateShape: a union shape needs at least one variant");
1350
+ for (const variant of shape.variants) validateShape(variant);
1351
+ return;
1352
+ case "optional": throw new Error("validateShape: an optional shape may only appear as a direct object-property value");
1353
+ case "nullable":
1354
+ validateShape(shape.inner);
1355
+ return;
1356
+ }
1357
+ }
1358
+ /**
1359
+ * Compile a {@link ContractShape} into a JSON Schema document.
1360
+ *
1361
+ * @remarks
1362
+ * Object shapes emit `additionalProperties: false` (unless opened) and list only
1363
+ * required keys in `required`; nullable shapes emit an `anyOf` with `{ type:
1364
+ * 'null' }`. Emission only — it never inspects a runtime value.
1365
+ *
1366
+ * @param shape - The shape to compile
1367
+ * @returns The emitted JSON Schema
1368
+ */
1369
+ function compileSchema(shape) {
1370
+ switch (shape.type) {
1371
+ case "string": return {
1372
+ type: "string",
1373
+ ...shape.min !== void 0 ? { minLength: shape.min } : {},
1374
+ ...shape.max !== void 0 ? { maxLength: shape.max } : {},
1375
+ ...shape.pattern !== void 0 ? { pattern: shape.pattern.source } : {},
1376
+ ...shape.description !== void 0 ? { description: shape.description } : {}
1377
+ };
1378
+ case "number": return {
1379
+ type: shape.integer === true ? "integer" : "number",
1380
+ ...shape.min !== void 0 ? { minimum: shape.min } : {},
1381
+ ...shape.max !== void 0 ? { maximum: shape.max } : {},
1382
+ ...shape.description !== void 0 ? { description: shape.description } : {}
1383
+ };
1384
+ case "boolean": return {
1385
+ type: "boolean",
1386
+ ...shape.description !== void 0 ? { description: shape.description } : {}
1387
+ };
1388
+ case "null": return {
1389
+ type: "null",
1390
+ ...shape.description !== void 0 ? { description: shape.description } : {}
1391
+ };
1392
+ case "json": return { ...shape.description !== void 0 ? { description: shape.description } : {} };
1393
+ case "literal": return {
1394
+ enum: [...shape.values],
1395
+ ...shape.description !== void 0 ? { description: shape.description } : {}
1396
+ };
1397
+ case "array": return {
1398
+ type: "array",
1399
+ items: compileSchema(shape.items),
1400
+ ...shape.min !== void 0 ? { minItems: shape.min } : {},
1401
+ ...shape.max !== void 0 ? { maxItems: shape.max } : {},
1402
+ ...shape.description !== void 0 ? { description: shape.description } : {}
1403
+ };
1404
+ case "object": {
1405
+ const properties = {};
1406
+ const required = [];
1407
+ for (const key of Object.keys(shape.properties)) {
1408
+ const child = shape.properties[key];
1409
+ if (child === void 0) continue;
1410
+ properties[key] = compileSchema(child);
1411
+ if (child.type !== "optional") required.push(key);
1412
+ }
1413
+ const extra = shape.additionalProperties;
1414
+ const additionalProperties = extra === true ? true : extra !== void 0 && extra !== false ? compileSchema(extra) : false;
1415
+ return {
1416
+ type: "object",
1417
+ ...Object.keys(properties).length > 0 ? { properties } : {},
1418
+ ...required.length > 0 ? { required } : {},
1419
+ additionalProperties,
1420
+ ...shape.description !== void 0 ? { description: shape.description } : {}
1421
+ };
1422
+ }
1423
+ case "union": return {
1424
+ ...shape.mode === "oneOf" ? { oneOf: shape.variants.map((variant) => compileSchema(variant)) } : { anyOf: shape.variants.map((variant) => compileSchema(variant)) },
1425
+ ...shape.description !== void 0 ? { description: shape.description } : {}
1426
+ };
1427
+ case "optional": return compileSchema(shape.inner);
1428
+ case "nullable": return { anyOf: [compileSchema(shape.inner), { type: "null" }] };
1429
+ case "raw": return shape.schema;
1430
+ }
1431
+ }
1432
+ /**
1433
+ * Compile a {@link ContractShape} into a runtime type guard.
1434
+ *
1435
+ * @remarks
1436
+ * Reuses the combinators: `literalOf` for literals, `arrayOf` for arrays,
1437
+ * `recordOf` for closed objects, `unionOf` for unions, `nullableOf` for nullable,
1438
+ * and `whereOf` for constraint refinement. Like every guard it is total — it
1439
+ * never throws (AGENTS §14).
1440
+ *
1441
+ * @param shape - The shape to compile
1442
+ * @returns A guard narrowing to the shape's inferred type
1443
+ */
1444
+ function compileGuard(shape) {
1445
+ switch (shape.type) {
1446
+ case "string": return stringOf({
1447
+ min: shape.min,
1448
+ max: shape.max,
1449
+ pattern: shape.pattern
1450
+ });
1451
+ case "number": {
1452
+ const base = shape.integer === true ? isInteger : isFiniteNumber;
1453
+ if (shape.min === void 0 && shape.max === void 0) return base;
1454
+ return shape.integer === true ? intersectionOf(isInteger, boundsOf(shape.min, shape.max)) : boundsOf(shape.min, shape.max);
1455
+ }
1456
+ case "boolean": return isBoolean;
1457
+ case "null": return isNull;
1458
+ case "json": return isJSONValue;
1459
+ case "literal": return literalOf(...shape.values);
1460
+ case "array": {
1461
+ const base = arrayOf(compileGuard(shape.items));
1462
+ if (shape.min === void 0 && shape.max === void 0) return base;
1463
+ const withinLength = boundsOf(shape.min, shape.max);
1464
+ return whereOf(base, (value) => withinLength(value.length));
1465
+ }
1466
+ case "object": {
1467
+ const map = Object.create(null);
1468
+ const optionalKeys = [];
1469
+ for (const key of Object.keys(shape.properties)) {
1470
+ const child = shape.properties[key];
1471
+ if (child === void 0) continue;
1472
+ if (child.type === "optional") {
1473
+ map[key] = compileGuard(child.inner);
1474
+ optionalKeys.push(key);
1475
+ } else map[key] = compileGuard(child);
1476
+ }
1477
+ const extra = shape.additionalProperties;
1478
+ if (extra === void 0 || extra === false) return optionalKeys.length > 0 ? recordOf(map, optionalKeys) : recordOf(map);
1479
+ const additional = extra === true ? void 0 : compileGuard(extra);
1480
+ const required = Object.keys(map).filter((key) => !optionalKeys.includes(key));
1481
+ return (value) => {
1482
+ if (!isRecord(value)) return false;
1483
+ for (const key of required) if (!Object.hasOwn(value, key)) return false;
1484
+ const outcome = attempt(() => {
1485
+ for (const key of Object.keys(value)) {
1486
+ const guard = Object.hasOwn(map, key) ? map[key] : void 0;
1487
+ if (guard !== void 0) {
1488
+ if (!guard(value[key])) return false;
1489
+ } else if (additional !== void 0 && !additional(value[key])) return false;
1490
+ }
1491
+ return true;
1492
+ });
1493
+ return outcome.success && outcome.value;
1494
+ };
1495
+ }
1496
+ case "union": return unionOf(...shape.variants.map((variant) => compileGuard(variant)));
1497
+ case "optional": return orOf(isUndefined, compileGuard(shape.inner));
1498
+ case "nullable": return nullableOf(compileGuard(shape.inner));
1499
+ case "raw": return (_value) => true;
1500
+ }
1501
+ }
1502
+ /**
1503
+ * Compile a {@link ContractShape} into an input parser.
1504
+ *
1505
+ * @remarks
1506
+ * Reuses the leaf parsers (`parseString` / `parseInteger` / `parseNumber` /
1507
+ * `parseBoolean` / `parseRecord`) and coerces structurally. An object fails as a
1508
+ * whole on any required-field failure; a union returns a guard-valid value
1509
+ * unchanged, otherwise the first variant that both parses and guards wins.
1510
+ *
1511
+ * After coercing a leaf, it re-applies that leaf's REFINEMENTS through the same
1512
+ * combinators `compileGuard` uses — `stringOf` for a string's length/pattern and
1513
+ * `boundsOf` for a number's value and an array's length — so a value that coerces
1514
+ * but violates a bound parses to `undefined`. The result is full parse↔guard
1515
+ * soundness (AGENTS §14): a non-`undefined` parse always satisfies the contract's
1516
+ * `is`, refinements included.
1517
+ *
1518
+ * @param shape - The shape to compile
1519
+ * @returns A parser yielding the shape's inferred type or `undefined`
1520
+ */
1521
+ function compileParser(shape) {
1522
+ switch (shape.type) {
1523
+ case "string": {
1524
+ if (shape.min === void 0 && shape.max === void 0 && shape.pattern === void 0) return parseString;
1525
+ const guard = stringOf({
1526
+ min: shape.min,
1527
+ max: shape.max,
1528
+ pattern: shape.pattern
1529
+ });
1530
+ return (value) => {
1531
+ const parsed = parseString(value);
1532
+ return parsed !== void 0 && guard(parsed) ? parsed : void 0;
1533
+ };
1534
+ }
1535
+ case "number": {
1536
+ const base = shape.integer === true ? parseInteger : parseNumber;
1537
+ if (shape.min === void 0 && shape.max === void 0) return base;
1538
+ const within = boundsOf(shape.min, shape.max);
1539
+ return (value) => {
1540
+ const parsed = base(value);
1541
+ return parsed !== void 0 && within(parsed) ? parsed : void 0;
1542
+ };
1543
+ }
1544
+ case "boolean": return parseBoolean;
1545
+ case "null": return (value) => value === null ? null : void 0;
1546
+ case "json": return (value) => isJSONValue(value) ? value : void 0;
1547
+ case "literal": {
1548
+ const allowed = new Set(shape.values);
1549
+ return (value) => {
1550
+ if (allowed.has(value)) return value;
1551
+ if (isString(value)) {
1552
+ const trimmed = value.trim();
1553
+ if (allowed.has(trimmed)) return trimmed;
1554
+ }
1555
+ };
1556
+ }
1557
+ case "array": {
1558
+ const item = compileParser(shape.items);
1559
+ const unbounded = shape.min === void 0 && shape.max === void 0;
1560
+ const withinLength = boundsOf(shape.min, shape.max);
1561
+ return (value) => {
1562
+ if (!isArray(value)) return void 0;
1563
+ const result = [];
1564
+ for (const entry of value) {
1565
+ const parsed = item(entry);
1566
+ if (parsed === void 0) return void 0;
1567
+ result.push(parsed);
1568
+ }
1569
+ return unbounded || withinLength(result.length) ? result : void 0;
1570
+ };
1571
+ }
1572
+ case "object": {
1573
+ const entries = [];
1574
+ for (const key of Object.keys(shape.properties)) {
1575
+ const child = shape.properties[key];
1576
+ if (child === void 0) continue;
1577
+ const optional = child.type === "optional";
1578
+ entries.push({
1579
+ key,
1580
+ parse: compileParser(optional ? child.inner : child),
1581
+ optional
1582
+ });
1583
+ }
1584
+ const known = new Set(entries.map((entry) => entry.key));
1585
+ const extra = shape.additionalProperties;
1586
+ const additional = extra === void 0 || extra === false || extra === true ? void 0 : compileParser(extra);
1587
+ const open = extra === true || additional !== void 0;
1588
+ return (value) => {
1589
+ const record = parseRecord(value);
1590
+ if (record === void 0) return void 0;
1591
+ const outcome = attempt(() => {
1592
+ const result = Object.create(null);
1593
+ for (const entry of entries) {
1594
+ const raw = record[entry.key];
1595
+ if (raw === void 0) {
1596
+ if (entry.optional) continue;
1597
+ return;
1598
+ }
1599
+ const parsed = entry.parse(raw);
1600
+ if (parsed === void 0) return void 0;
1601
+ result[entry.key] = parsed;
1602
+ }
1603
+ if (open) for (const key of Object.keys(record)) {
1604
+ if (known.has(key)) continue;
1605
+ if (additional === void 0) result[key] = record[key];
1606
+ else {
1607
+ const parsed = additional(record[key]);
1608
+ if (parsed === void 0) return void 0;
1609
+ result[key] = parsed;
1610
+ }
1611
+ }
1612
+ return result;
1613
+ });
1614
+ return outcome.success ? outcome.value : void 0;
1615
+ };
1616
+ }
1617
+ case "union": {
1618
+ const variants = shape.variants.map((variant) => ({
1619
+ parse: compileParser(variant),
1620
+ guard: compileGuard(variant)
1621
+ }));
1622
+ return (value) => {
1623
+ for (const variant of variants) if (variant.guard(value)) return value;
1624
+ for (const variant of variants) {
1625
+ const parsed = variant.parse(value);
1626
+ if (parsed !== void 0 && variant.guard(parsed)) return parsed;
1627
+ }
1628
+ };
1629
+ }
1630
+ case "optional": {
1631
+ const inner = compileParser(shape.inner);
1632
+ return (value) => value === void 0 ? void 0 : inner(value);
1633
+ }
1634
+ case "nullable": {
1635
+ const inner = compileParser(shape.inner);
1636
+ return (value) => value === null ? null : inner(value);
1637
+ }
1638
+ case "raw": return (value) => value;
1639
+ }
1640
+ }
1641
+ /**
1642
+ * Compile a {@link ContractShape} into a deterministic seed value.
1643
+ *
1644
+ * @remarks
1645
+ * The same shape and the same `random` source always produce the same value, so
1646
+ * seed data is reproducible. Defaults to a {@link seededRandom} source seeded
1647
+ * from the wall clock when none is supplied. Throws on a degenerate empty
1648
+ * `literalShape` / `unionShape`, on a pattern-constrained `stringShape` whose
1649
+ * generated sample cannot satisfy the pattern, or on a `rawShape` (its embedded
1650
+ * schema is arbitrary and cannot be auto-generated) — a programmer error that
1651
+ * cannot generate a value (AGENTS §12). `createContract` runs
1652
+ * {@link validateShape} first, so a degenerate `literalShape` / `unionShape` /
1653
+ * bounded shape is normally caught there; these throws remain here as defense
1654
+ * for standalone `compileGenerator` use.
1655
+ *
1656
+ * @param shape - The shape to generate from
1657
+ * @param random - A seeded random source (defaults to `seededRandom(Date.now())`)
1658
+ * @returns A value matching the shape
1659
+ */
1660
+ function compileGenerator(shape, random = seededRandom(Date.now())) {
1661
+ switch (shape.type) {
1662
+ case "string": {
1663
+ const min = shape.min ?? 0;
1664
+ const max = shape.max ?? Math.max(min, 12);
1665
+ const length = Math.max(min, Math.min(max, 8));
1666
+ const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
1667
+ let value = "";
1668
+ for (let index = 0; index < length; index += 1) value += alphabet[Math.floor(random() * 36)];
1669
+ if (shape.pattern !== void 0 && !shape.pattern.test(value)) throw new Error("compileGenerator: a pattern-constrained string shape cannot be auto-generated — supply or verify values another way");
1670
+ return value;
1671
+ }
1672
+ case "number": {
1673
+ const min = shape.min ?? 0;
1674
+ const max = shape.max ?? 100;
1675
+ if (shape.integer === true) {
1676
+ const lo = Math.ceil(min);
1677
+ const hi = Math.floor(max);
1678
+ return Math.floor(random() * (hi - lo + 1)) + lo;
1679
+ }
1680
+ return random() * (max - min) + min;
1681
+ }
1682
+ case "boolean": return random() >= .5;
1683
+ case "null": return null;
1684
+ case "json": {
1685
+ const pick = Math.floor(random() * 5);
1686
+ if (pick === 0) return null;
1687
+ if (pick === 1) return random() >= .5;
1688
+ if (pick === 2) return Math.floor(random() * 1e3);
1689
+ if (pick === 3) {
1690
+ const alphabet = "abcdefghijklmnopqrstuvwxyz";
1691
+ let value = "";
1692
+ for (let index = 0; index < 6; index += 1) value += alphabet[Math.floor(random() * 26)];
1693
+ return value;
1694
+ }
1695
+ return { value: Math.floor(random() * 1e3) };
1696
+ }
1697
+ case "literal":
1698
+ if (shape.values.length === 0) throw new Error("compileGenerator: a literal shape needs at least one value");
1699
+ return shape.values[Math.floor(random() * shape.values.length)];
1700
+ case "array": {
1701
+ const lo = shape.min ?? Math.min(1, shape.max ?? 1);
1702
+ const hi = shape.max ?? Math.max(lo, 3);
1703
+ const length = Math.floor(random() * (hi - lo + 1)) + lo;
1704
+ const result = [];
1705
+ for (let index = 0; index < length; index += 1) result.push(compileGenerator(shape.items, random));
1706
+ return result;
1707
+ }
1708
+ case "object": {
1709
+ const result = {};
1710
+ for (const key of Object.keys(shape.properties)) {
1711
+ const child = shape.properties[key];
1712
+ if (child === void 0) continue;
1713
+ if (child.type === "optional" && random() < .3) continue;
1714
+ result[key] = compileGenerator(child, random);
1715
+ }
1716
+ const extra = shape.additionalProperties;
1717
+ if (extra !== void 0 && extra !== true && extra !== false) {
1718
+ const count = 1 + Math.floor(random() * 2);
1719
+ for (let index = 0; index < count; index += 1) {
1720
+ const key = `key${index}`;
1721
+ if (Object.hasOwn(result, key)) continue;
1722
+ result[key] = compileGenerator(extra, random);
1723
+ }
1724
+ }
1725
+ return result;
1726
+ }
1727
+ case "union":
1728
+ if (shape.variants.length === 0) throw new Error("compileGenerator: a union shape needs at least one variant");
1729
+ return compileGenerator(shape.variants[Math.floor(random() * shape.variants.length)], random);
1730
+ case "optional": return compileGenerator(shape.inner, random);
1731
+ case "nullable": return random() < .2 ? null : compileGenerator(shape.inner, random);
1732
+ case "raw": throw new Error("compileGenerator: a raw shape embeds an arbitrary JSON Schema and cannot be auto-generated — supply values another way");
1733
+ }
1734
+ }
1735
+ function createContract(shape) {
1736
+ validateShape(shape);
1737
+ const schema = compileSchema(shape);
1738
+ const guard = compileGuard(shape);
1739
+ const parser = compileParser(shape);
1740
+ return {
1741
+ schema,
1742
+ is: guard,
1743
+ parse(value) {
1744
+ return parser(value);
1745
+ },
1746
+ generate(random) {
1747
+ return compileGenerator(shape, random);
1748
+ }
1749
+ };
1750
+ }
1751
+ //#endregion
1752
+ //#region src/core/shapers.ts
1753
+ /**
1754
+ * Build a string {@link StringShape}.
1755
+ *
1756
+ * @param options - Optional length (`min` / `max`), `pattern`, and `description`
1757
+ * @returns A string shape
1758
+ *
1759
+ * @example
1760
+ * ```ts
1761
+ * const name = stringShape({ min: 1, max: 80, description: 'Display name' })
1762
+ * ```
1763
+ */
1764
+ function stringShape(options) {
1765
+ return {
1766
+ type: "string",
1767
+ min: options?.min,
1768
+ max: options?.max,
1769
+ pattern: options?.pattern,
1770
+ description: options?.description
1771
+ };
1772
+ }
1773
+ /**
1774
+ * Build a numeric {@link NumberShape}.
1775
+ *
1776
+ * @param options - Optional bounds (`min` / `max`), `integer`, and `description`
1777
+ * @returns A number shape
1778
+ */
1779
+ function numberShape(options) {
1780
+ return {
1781
+ type: "number",
1782
+ min: options?.min,
1783
+ max: options?.max,
1784
+ integer: options?.integer,
1785
+ description: options?.description
1786
+ };
1787
+ }
1788
+ /**
1789
+ * Build an integer {@link NumberShape} — forces `integer: true`.
1790
+ *
1791
+ * @remarks
1792
+ * The emitted JSON Schema uses `"type": "integer"` and the guard rejects
1793
+ * fractional numbers.
1794
+ *
1795
+ * @param options - Optional bounds and `description` (no `integer` key)
1796
+ * @returns An integer number shape
1797
+ */
1798
+ function integerShape(options) {
1799
+ return {
1800
+ type: "number",
1801
+ integer: true,
1802
+ min: options?.min,
1803
+ max: options?.max,
1804
+ description: options?.description
1805
+ };
1806
+ }
1807
+ /**
1808
+ * Build a {@link BooleanShape}.
1809
+ *
1810
+ * @param options - Optional `description`
1811
+ * @returns A boolean shape
1812
+ */
1813
+ function booleanShape(options) {
1814
+ return {
1815
+ type: "boolean",
1816
+ description: options?.description
1817
+ };
1818
+ }
1819
+ /**
1820
+ * Build a {@link NullShape}.
1821
+ *
1822
+ * @param options - Optional `description`
1823
+ * @returns A null shape
1824
+ */
1825
+ function nullShape(options) {
1826
+ return {
1827
+ type: "null",
1828
+ description: options?.description
1829
+ };
1830
+ }
1831
+ /**
1832
+ * Build a literal shape from a fixed set of primitive values.
1833
+ *
1834
+ * @param values - The permitted literals
1835
+ * @param options - Optional `description`
1836
+ * @returns A literal shape whose `Infer` is the union of `values`
1837
+ *
1838
+ * @example
1839
+ * ```ts
1840
+ * const role = literalShape(['admin', 'member', 'guest'])
1841
+ * // Infer<typeof role> = 'admin' | 'member' | 'guest'
1842
+ *
1843
+ * const via = literalShape(['function', 'tool', 'agent'], { description: 'How to run the step.' })
1844
+ * ```
1845
+ */
1846
+ function literalShape(values, options) {
1847
+ return {
1848
+ type: "literal",
1849
+ values,
1850
+ description: options?.description
1851
+ };
1852
+ }
1853
+ /**
1854
+ * Build an {@link ArrayShape} from an element shape.
1855
+ *
1856
+ * @param items - The element shape
1857
+ * @param options - Optional length bounds and `description`
1858
+ * @returns An array shape
1859
+ *
1860
+ * @example
1861
+ * ```ts
1862
+ * const tags = arrayShape(stringShape(), { max: 10 })
1863
+ * ```
1864
+ */
1865
+ function arrayShape(items, options) {
1866
+ return {
1867
+ type: "array",
1868
+ items,
1869
+ min: options?.min,
1870
+ max: options?.max,
1871
+ description: options?.description
1872
+ };
1873
+ }
1874
+ /**
1875
+ * Build an {@link ObjectShape} from a property map.
1876
+ *
1877
+ * @remarks
1878
+ * Wrap any property in {@link optionalShape} to allow its absence. By default
1879
+ * the compiled guard rejects unknown keys; pass `additionalProperties` to open
1880
+ * the object.
1881
+ *
1882
+ * @param properties - Map of property names to child shapes
1883
+ * @param options - Optional `additionalProperties` and `description`
1884
+ * @returns An object shape
1885
+ *
1886
+ * @example
1887
+ * ```ts
1888
+ * const user = objectShape({
1889
+ * name: stringShape({ min: 1 }),
1890
+ * age: integerShape({ min: 0, max: 120 }),
1891
+ * bio: optionalShape(stringShape()),
1892
+ * })
1893
+ * ```
1894
+ */
1895
+ function objectShape(properties, options) {
1896
+ return {
1897
+ type: "object",
1898
+ properties,
1899
+ additionalProperties: options?.additionalProperties,
1900
+ description: options?.description
1901
+ };
1902
+ }
1903
+ /**
1904
+ * Build an open {@link ObjectShape} with no fixed properties — a dictionary.
1905
+ *
1906
+ * @remarks
1907
+ * Every value is validated against `values`; keys are unconstrained. Equivalent
1908
+ * to `objectShape({}, { additionalProperties: values })`.
1909
+ *
1910
+ * @param values - The shape every value must match
1911
+ * @param options - Optional `description`
1912
+ * @returns An open object shape
1913
+ *
1914
+ * @example
1915
+ * ```ts
1916
+ * const bindings = recordShape(numberShape()) // ~ Record<string, number>
1917
+ * ```
1918
+ */
1919
+ function recordShape(values, options) {
1920
+ return {
1921
+ type: "object",
1922
+ properties: {},
1923
+ additionalProperties: values,
1924
+ description: options?.description
1925
+ };
1926
+ }
1927
+ /**
1928
+ * Build a {@link UnionShape} from a list of variant shapes (`anyOf` in JSON Schema).
1929
+ *
1930
+ * @param variants - The candidate shapes; the first match wins at runtime
1931
+ * @returns A union shape whose `Infer` is the union of the variants
1932
+ *
1933
+ * @example
1934
+ * ```ts
1935
+ * const id = unionShape(stringShape(), integerShape())
1936
+ * // Infer<typeof id> = string | number
1937
+ * ```
1938
+ */
1939
+ function unionShape(...variants) {
1940
+ return {
1941
+ type: "union",
1942
+ variants
1943
+ };
1944
+ }
1945
+ /**
1946
+ * Build a {@link UnionShape} that emits `oneOf` (exactly one match) in JSON Schema.
1947
+ *
1948
+ * @remarks
1949
+ * Runtime behavior is identical to {@link unionShape} — only the emitted schema
1950
+ * keyword differs (`oneOf` vs `anyOf`).
1951
+ *
1952
+ * @param variants - The candidate shapes
1953
+ * @returns A union shape with `mode: 'oneOf'`
1954
+ */
1955
+ function oneOfShape(...variants) {
1956
+ return {
1957
+ type: "union",
1958
+ variants,
1959
+ mode: "oneOf"
1960
+ };
1961
+ }
1962
+ /**
1963
+ * Wrap a shape so it may be absent (`undefined`).
1964
+ *
1965
+ * @remarks
1966
+ * As an {@link objectShape} property, the field becomes a true optional property
1967
+ * in the inferred type.
1968
+ *
1969
+ * @param inner - The wrapped shape
1970
+ * @returns An optional shape
1971
+ */
1972
+ function optionalShape(inner) {
1973
+ return {
1974
+ type: "optional",
1975
+ inner
1976
+ };
1977
+ }
1978
+ /**
1979
+ * Wrap a shape so it may be `null`.
1980
+ *
1981
+ * @param inner - The wrapped shape
1982
+ * @returns A nullable shape
1983
+ */
1984
+ function nullableShape(inner) {
1985
+ return {
1986
+ type: "nullable",
1987
+ inner
1988
+ };
1989
+ }
1990
+ /**
1991
+ * Build a {@link JSONShape}.
1992
+ *
1993
+ * @remarks
1994
+ * The sound counterpart of {@link rawShape}: `rawShape` embeds an arbitrary
1995
+ * schema fragment and accepts anything at runtime, while `jsonShape` validates
1996
+ * that a value is real JSON (via {@link isJSONValue}).
1997
+ *
1998
+ * @param options - Optional `description`
1999
+ * @returns A JSON passthrough shape
2000
+ */
2001
+ function jsonShape(options) {
2002
+ return {
2003
+ type: "json",
2004
+ description: options?.description
2005
+ };
2006
+ }
2007
+ /**
2008
+ * Build a {@link RawShape} from a JSON Schema fragment.
2009
+ *
2010
+ * @remarks
2011
+ * For values the shape DSL can't express. The compiled guard accepts any value;
2012
+ * the parser passes it through; the schema is emitted verbatim.
2013
+ *
2014
+ * @param schema - The JSON Schema fragment to embed
2015
+ * @returns A raw shape
2016
+ */
2017
+ function rawShape(schema) {
2018
+ return {
2019
+ type: "raw",
2020
+ schema
2021
+ };
2022
+ }
2023
+ //#endregion
2024
+ export { JSON_SCHEMA_TYPES, andOf, arrayOf, arrayShape, attempt, booleanShape, boundsOf, compileGenerator, compileGuard, compileParser, compileSchema, complementOf, createContract, enumOf, enumerableSymbolCount, instanceOf, integerShape, intersectionOf, isArray, isArrayBuffer, isArrayBufferView, isAsyncFunction, isAsyncGeneratorFunction, isAsyncIterable, isBigInt, isBigInt64Array, isBigUint64Array, isBoolean, isConstructor, isDataView, isDate, isDefined, isEmptyArray, isEmptyMap, isEmptyObject, isEmptySet, isEmptyString, isError, isFalse, isFiniteNumber, isFloat32Array, isFloat64Array, isFunction, isGeneratorFunction, isInt16Array, isInt32Array, isInt8Array, isInteger, isIterable, isJSONPrimitive, isJSONValue, isMap, isNonEmptyArray, isNonEmptyMap, isNonEmptyObject, isNonEmptySet, isNonEmptyString, isNull, isNullableBoolean, isNullableNumber, isNullableString, isNumber, isObject, isPromise, isPromiseLike, isRecord, isRegExp, isSet, isSharedArrayBuffer, isString, isSymbol, isTrue, isUint16Array, isUint32Array, isUint8Array, isUint8ClampedArray, isUndefined, isWeakMap, isWeakSet, isZeroArg, isZeroArgAsync, isZeroArgAsyncGenerator, isZeroArgGenerator, iterableOf, jsonShape, keyOf, lazyOf, literalOf, literalShape, mapOf, matchOf, notOf, nullShape, nullableOf, nullableShape, numberShape, objectShape, omitOf, oneOfShape, optionalOf, optionalShape, orOf, parseArray, parseArrayField, parseBoolean, parseBooleanField, parseEnum, parseEnumField, parseInteger, parseIntegerField, parseJSON, parseJSONAs, parseJSONValue, parseJSONValueField, parseNull, parseNullField, parseNumber, parseNumberField, parseRecord, parseRecordField, parseString, parseStringField, pickOf, rawShape, recordOf, recordShape, resolveField, schemaToParameters, seededRandom, setOf, stringOf, stringShape, transformOf, tupleOf, unionOf, unionShape, validateShape, whereOf };
2025
+
2026
+ //# sourceMappingURL=index.js.map