functionalscript 0.11.11 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,5 +17,4 @@ export declare const serialize: (mapEntries: MapEntries) => (value: Unknown) =>
17
17
  */
18
18
  export declare const stringify: (mapEntries: MapEntries) => (value: Unknown) => string;
19
19
  export declare const parse: (value: string) => Unknown;
20
- export declare const isObject: (value: Unknown) => value is Object;
21
20
  export {};
package/json/module.f.js CHANGED
@@ -60,4 +60,7 @@ export const serialize = sort => {
60
60
  */
61
61
  export const stringify = sort => compose(serialize(sort))(concat);
62
62
  export const parse = JSON.parse;
63
- export const isObject = (value) => typeof value === 'object' && value !== null && !(value instanceof Array);
63
+ /*
64
+ export const isObject = (value: Unknown): value is Object =>
65
+ typeof value === 'object' && value !== null && !(value instanceof Array)
66
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.11.11",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * @module
5
5
  */
6
+ export declare const isArray: (value: unknown) => value is readonly unknown[];
6
7
  export type Array1<T> = readonly [T];
7
8
  export type Index1 = 0;
8
9
  export type Array2<T> = readonly [T, T];
@@ -37,4 +38,5 @@ export declare const splitLast: <T>(a: readonly T[]) => readonly [readonly T[],
37
38
  * we may minimize memory a number of memory allocations.
38
39
  */
39
40
  export declare const empty: readonly [];
41
+ export type Includes<I, T extends readonly I[]> = (v: I) => v is T[number];
40
42
  export declare const includes: <I, T extends readonly I[]>(a: T) => (v: I) => v is T[number];
@@ -4,6 +4,7 @@
4
4
  * @module
5
5
  */
6
6
  import { map } from "../nullable/module.f.js";
7
+ export const isArray = (value) => value instanceof Array;
7
8
  export const isArray2 = (a) => a.length === 2;
8
9
  const uncheckTail = (a) => a.slice(1);
9
10
  const uncheckHead = (a) => a.slice(0, -1);
@@ -15,7 +15,7 @@ export declare const fromMap: <T>(m: OrderedMap<T>) => Map<T>;
15
15
  * https://stackoverflow.com/questions/57571664/typescript-type-for-an-object-with-only-one-key-no-union-type-allowed-as-a-key
16
16
  */
17
17
  export type OneKey<K extends string, V> = {
18
- [P in K]: (Record<P, V> & Partial<Record<Exclude<K, P>, never>>) extends infer O ? {
18
+ [P in K]: (ReadonlyRecord<P, V> & Partial<ReadonlyRecord<Exclude<K, P>, never>>) extends infer O ? {
19
19
  [Q in keyof O]: O[Q];
20
20
  } : never;
21
21
  }[K];
@@ -25,4 +25,8 @@ export type OneKey<K extends string, V> = {
25
25
  export type NotUnion<T, U = T> = T extends unknown ? [
26
26
  U
27
27
  ] extends [T] ? T : never : never;
28
- export type SingleProperty<T extends Record<string, never>> = keyof T extends NotUnion<keyof T> ? T : never;
28
+ export type SingleProperty<T extends ReadonlyRecord<string, never>> = keyof T extends NotUnion<keyof T> ? T : never;
29
+ export declare const isObject: (value: unknown) => value is { readonly [k in string]: unknown; };
30
+ export type ReadonlyRecord<S extends string, T> = {
31
+ readonly [K in S]: T;
32
+ };
@@ -1,3 +1,4 @@
1
+ import { isArray } from "../array/module.f.js";
1
2
  import { iterable } from "../list/module.f.js";
2
3
  import { entries as mapEntries, fromEntries as mapFromEntries } from "../ordered_map/module.f.js";
3
4
  const { getOwnPropertyDescriptor, fromEntries: objectFromEntries } = Object;
@@ -8,3 +9,4 @@ export const at = name => object => {
8
9
  export const sort = e => mapEntries(mapFromEntries(e));
9
10
  export const fromEntries = e => objectFromEntries(iterable(e));
10
11
  export const fromMap = m => fromEntries(mapEntries(m));
12
+ export const isObject = (value) => typeof value === 'object' && !isArray(value) && value !== null;
@@ -1,48 +1,107 @@
1
- import { type Result as CommonResult } from "../result/module.f.ts";
2
- /** Validation result: either the typed value or an error message. */
3
- export type Result<T extends Type> = CommonResult<Ts<T>, string>;
4
- declare const objectTypeList: readonly ["null", "array", "record"];
5
- /** String tags for non-object primitive types. */
6
- export type NonObjectType = 'undefined' | 'boolean' | 'string' | 'number' | 'bigint' | 'function';
7
- /** String tags for object types: `'null'`, `'array'`, `'record'`. */
8
- export type ObjectType = typeof objectTypeList[number];
9
- /** Union of all base type string tags. */
10
- export type BaseType = NonObjectType | ObjectType;
11
1
  /**
12
- * A struct schema: an object whose keys are field names and values are nested `Type`s.
13
- * Used to validate plain objects with specific typed fields.
2
+ * Runtime type information (RTTI) a type-safe schema system for describing and
3
+ * converting TypeScript types.
4
+ *
5
+ * ## Core concepts
6
+ *
7
+ * A `Type` is either a `Const` (used directly as its own schema) or a `Thunk`
8
+ * (a zero-argument function returning an `Info` descriptor). Thunks are the
9
+ * primary building block: they enable lazy evaluation and recursive type definitions.
10
+ *
11
+ * ```
12
+ * Type = Const | Thunk
13
+ * Thunk = () => Info
14
+ * Info = ['const', Const] | Info0<Tag0> | Info1<Tag1, Type>
15
+ * ```
16
+ *
17
+ * ## Nullary schemas (no type parameter)
18
+ *
19
+ * `boolean`, `number`, `string`, `bigint`, `unknown` are pre-built `Thunk` values
20
+ * that describe primitive types. Each is a `Type0<Tag0>` — a thunk returning a
21
+ * single-element tag tuple.
22
+ *
23
+ * ## Unary schemas (one type parameter)
24
+ *
25
+ * `array(t)` and `record(t)` construct `Thunk` values parameterized by an inner
26
+ * `Type`. They return `Type1` thunks wrapping an `Info1` tuple.
27
+ *
28
+ * ## Const schemas
29
+ *
30
+ * Any `Primitive`, `Struct` (plain object), or `Tuple` (readonly array) can be
31
+ * used directly as a schema — it describes exactly the shape of that value.
32
+ * Inside a recursive `Thunk`-based definition, wrap consts with `() => ['const', c]`
33
+ * to keep the schema uniform.
34
+ *
35
+ * ## Converting to TypeScript types
36
+ *
37
+ * See `./ts/module.f.ts` for `Ts<T>` and the `*Ts` transformer types.
14
38
  */
15
- export type StructType = object & {
39
+ import type { Primitive } from '../../djs/module.f.ts';
40
+ import { type Includes } from '../array/module.f.ts';
41
+ export type ConstObject = Struct | Tuple;
42
+ /** A constant schema: a primitive literal, a struct object, or a tuple. */
43
+ export type Const = Primitive | ConstObject;
44
+ /** A struct schema: plain object whose values are nested `Type`s. */
45
+ export type Struct = {
16
46
  readonly [K in string]: Type;
17
47
  };
18
- /** A thunk returning a `NonLazyType`, used for recursive type definitions. */
19
- export type LazyType = () => NonLazyType;
20
- /** A `Type` that is not wrapped in a thunk. */
21
- export type NonLazyType = BaseType | StructType;
22
- /** Any RTTI type: a base tag string, a struct schema object, or a lazy thunk. */
23
- export type Type = NonLazyType | LazyType;
24
- type NonObjectTs<T extends NonObjectType> = T extends 'undefined' ? undefined : T extends 'boolean' ? boolean : T extends 'string' ? string : T extends 'number' ? number : T extends 'bigint' ? bigint : T extends 'function' ? Function : never;
25
- type ArrayTs = readonly unknown[];
26
- type RecordTs = object & {
27
- readonly [K in string]: unknown;
28
- };
29
- type ObjectTs<T extends ObjectType> = T extends 'null' ? null : T extends 'array' ? ArrayTs : T extends 'record' ? RecordTs : never;
30
- type BaseTs<T extends BaseType> = T extends NonObjectType ? NonObjectTs<T> : T extends ObjectType ? ObjectTs<T> : never;
31
- type NonLazyTs<T extends NonLazyType> = T extends BaseType ? BaseTs<T> : T extends StructType ? object & {
32
- readonly [K in keyof T]: Ts<T[K]>;
33
- } : never;
34
- /**
35
- * Converts an RTTI `Type` to its corresponding TypeScript type.
36
- * @example `Ts<'string'>` → `string`, `Ts<{ x: 'number' }>` → `{ readonly x: number }`
37
- */
38
- export type Ts<T extends Type> = NonLazyTs<ToNonLazy<T>>;
39
- /** A function that validates an unknown value against type `T`. */
40
- export type Validate<T extends Type> = (value: unknown) => Result<T>;
41
- type ToNonLazy<T extends Type> = T extends () => infer R ? R : T;
48
+ /** A tuple schema: readonly array whose elements are nested `Type`s. */
49
+ export type Tuple = readonly Type[];
50
+ declare const primitive0List: readonly ["bigint", "boolean", "number", "string"];
51
+ export type Primitive0 = typeof primitive0List[number];
52
+ export declare const tag0List: readonly ["bigint", "boolean", "number", "string", "unknown"];
53
+ /** Tags for nullary (zero-parameter) type schemas. */
54
+ export type Tag0 = typeof tag0List[number];
55
+ /** Info tuple for a nullary tag: `readonly[tag]`. */
56
+ export type Info0<T extends Tag0> = readonly [T];
42
57
  /**
43
- * Creates a validator function for the given RTTI schema.
44
- * @param rtti - A base type tag, struct schema, or lazy thunk.
45
- * @returns A function `(value: unknown) => Result<T>`.
58
+ * The descriptor returned by a `Thunk`. One of:
59
+ * - `['const', Const]` a constant/literal schema (used in recursive thunks)
60
+ * - `Info0<Tag0>` a nullary primitive tag
61
+ * - `Info1<Tag1, Type>` — a unary parametric tag with an inner type
46
62
  */
47
- export declare const validate: <T extends Type>(rtti: T) => Validate<T>;
63
+ export type Info = readonly ['const', Const] | Info0<Tag0> | Info1<Tag1, Type>;
64
+ /** A lazy schema: a zero-argument function returning an `Info` descriptor. */
65
+ export type Thunk = () => Info;
66
+ /** Any schema: a `Const` used directly, or a `Thunk` for tag-based/recursive schemas. */
67
+ export type Type = Const | Thunk;
68
+ /** The type of a nullary thunk for `Tag0`. */
69
+ type Type0<T extends Tag0> = () => Info0<T>;
70
+ /** Schema type for `boolean`. */
71
+ export type Boolean = Type0<'boolean'>;
72
+ /** Schema that validates `boolean` values. */
73
+ export declare const boolean: Boolean;
74
+ /** Schema type for `number`. */
75
+ export type Number = Type0<'number'>;
76
+ /** Schema that validates `number` values. */
77
+ export declare const number: Number;
78
+ /** Schema type for `string`. */
79
+ export type String = Type0<'string'>;
80
+ /** Schema that validates `string` values. */
81
+ export declare const string: String;
82
+ /** Schema type for `bigint`. */
83
+ export type Bigint = Type0<'bigint'>;
84
+ /** Schema that validates `bigint` values. */
85
+ export declare const bigint: Bigint;
86
+ /** Schema type for any DJS value (`Primitive | UnknownRecord | UnknownArray`). */
87
+ export type Unknown = Type0<'unknown'>;
88
+ /** Schema that validates any DJS-compatible value. */
89
+ export declare const unknown: Unknown;
90
+ declare const tag1List: readonly ["array", "record"];
91
+ export declare const isTag1: Includes<string, typeof tag1List>;
92
+ /** Tags for unary (one-parameter) type schemas. */
93
+ export type Tag1 = typeof tag1List[number];
94
+ /** Info tuple for a unary tag: `readonly[tag, innerType]`. */
95
+ export type Info1<K extends Tag1, T extends Type> = readonly [K, T];
96
+ /** The type of a unary thunk for `Tag1` with inner type `T`. */
97
+ export type Type1<K extends Tag1, T extends Type> = () => Info1<K, T>;
98
+ type MakeType1<K extends Tag1> = <T extends Type>(t: T) => Type1<K, T>;
99
+ /** Schema type for a readonly array with element type `T`. */
100
+ export type Array<T extends Type> = Type1<'array', T>;
101
+ /** Constructs a schema that validates `readonly Ts<T>[]`. */
102
+ export declare const array: MakeType1<'array'>;
103
+ /** Schema type for a record (index signature) with value type `T`. */
104
+ export type Record<T extends Type> = Type1<'record', T>;
105
+ /** Constructs a schema that validates `{ readonly[K in string]: Ts<T> }`. */
106
+ export declare const record: MakeType1<'record'>;
48
107
  export {};
@@ -1,53 +1,21 @@
1
- /**
2
- * Runtime type information (RTTI) for validating unknown values against typed schemas.
3
- *
4
- * A `Type` is either a `BaseType` string tag (e.g. `'string'`, `'number'`, `'record'`),
5
- * a `StructType` object mapping field names to nested `Type`s, or a `LazyType` thunk
6
- * for recursive definitions. Use {@link validate} to produce a type-safe validator.
7
- *
8
- * @module
9
- */
10
1
  import { includes } from "../array/module.f.js";
11
- import { ok, error } from "../result/module.f.js";
12
- // object
13
- const objectTypeList = ['null', 'array', 'record'];
14
- const isObjectType = includes(objectTypeList);
15
- const nonObjectValidate = (rtti) => value => typeof value === rtti ? ok(value) : error(rtti);
16
- const isNull = (v) => v === null;
17
- const isArray = (v) => v instanceof Array;
18
- const isRecord = (v) => typeof v === 'object' && !isNull(v) && !isArray(v);
19
- const wrap = (f) => value => f(value) ? ok(value) : error(`unexpected value: ${value}`);
20
- const objectSwitch = {
21
- null: wrap(isNull),
22
- array: wrap(isArray),
23
- record: wrap(isRecord),
24
- };
25
- const objectValidate = (rtti) => objectSwitch[rtti];
26
- const baseValidate = (rtti) => isObjectType(rtti) ? objectValidate(rtti) : nonObjectValidate(rtti);
27
- const recordValidate = (rtti) => value => {
28
- if (!isRecord(value)) {
29
- return error('record is expected');
30
- }
31
- for (const [k, t] of Object.entries(rtti)) {
32
- const r = validate(t)(value[k]);
33
- if (r[0] === 'error') {
34
- return r;
35
- }
36
- }
37
- return ok(value);
38
- };
39
- const nonLazyValidate = (rtti) => {
40
- switch (typeof rtti) {
41
- case 'string':
42
- return baseValidate(rtti);
43
- case 'object':
44
- return recordValidate(rtti);
45
- }
46
- };
47
- const nonLazy = (rtti) => (typeof rtti === 'function' ? rtti() : rtti);
48
- /**
49
- * Creates a validator function for the given RTTI schema.
50
- * @param rtti - A base type tag, struct schema, or lazy thunk.
51
- * @returns A function `(value: unknown) => Result<T>`.
52
- */
53
- export const validate = (rtti) => nonLazyValidate(nonLazy(rtti));
2
+ const primitive0List = ['bigint', 'boolean', 'number', 'string'];
3
+ export const tag0List = [...primitive0List, 'unknown'];
4
+ const type0 = (tag) => () => [tag];
5
+ /** Schema that validates `boolean` values. */
6
+ export const boolean = type0('boolean');
7
+ /** Schema that validates `number` values. */
8
+ export const number = type0('number');
9
+ /** Schema that validates `string` values. */
10
+ export const string = type0('string');
11
+ /** Schema that validates `bigint` values. */
12
+ export const bigint = type0('bigint');
13
+ /** Schema that validates any DJS-compatible value. */
14
+ export const unknown = type0('unknown');
15
+ const tag1List = ['array', 'record'];
16
+ export const isTag1 = includes(tag1List);
17
+ const type1 = (key) => t => () => [key, t];
18
+ /** Constructs a schema that validates `readonly Ts<T>[]`. */
19
+ export const array = type1('array');
20
+ /** Constructs a schema that validates `{ readonly[K in string]: Ts<T> }`. */
21
+ export const record = type1('record');
@@ -2,53 +2,5 @@ declare const _default: {
2
2
  typeof: {
3
3
  [k: string]: (() => void)[];
4
4
  };
5
- validate: {
6
- undefined: {
7
- ok: () => void;
8
- error: () => void;
9
- };
10
- boolean: {
11
- ok: () => void;
12
- error: () => void;
13
- };
14
- string: {
15
- ok: () => void;
16
- error: () => void;
17
- };
18
- number: {
19
- ok: () => void;
20
- error: () => void;
21
- };
22
- bigint: {
23
- ok: () => void;
24
- error: () => void;
25
- };
26
- null: {
27
- ok: () => void;
28
- error: () => void;
29
- };
30
- array: {
31
- ok: () => void;
32
- error: () => void;
33
- };
34
- record: {
35
- ok: () => void;
36
- error: () => void;
37
- };
38
- function: {
39
- ok: () => void;
40
- error: () => void;
41
- };
42
- lazy: {
43
- ok: () => void;
44
- error: () => void;
45
- };
46
- struct: {
47
- ok: () => void;
48
- missingField: () => void;
49
- wrongFieldType: () => void;
50
- notObject: () => void;
51
- };
52
- };
53
5
  };
54
6
  export default _default;
@@ -1,4 +1,3 @@
1
- import { validate } from "./module.f.js";
2
1
  const tests = {
3
2
  undefined: [undefined],
4
3
  boolean: [true, false],
@@ -20,70 +19,4 @@ export default {
20
19
  throw `typeof ${v} !== ${k}`;
21
20
  }
22
21
  })])),
23
- validate: {
24
- undefined: {
25
- ok: () => assertOk(validate('undefined')(undefined)),
26
- error: () => assertError(validate('undefined')(null)),
27
- },
28
- boolean: {
29
- ok: () => {
30
- assertOk(validate('boolean')(true));
31
- assertOk(validate('boolean')(false));
32
- },
33
- error: () => assertError(validate('boolean')(0)),
34
- },
35
- string: {
36
- ok: () => assertOk(validate('string')('hello')),
37
- error: () => assertError(validate('string')(42)),
38
- },
39
- number: {
40
- ok: () => assertOk(validate('number')(3)),
41
- error: () => assertError(validate('number')('hello')),
42
- },
43
- bigint: {
44
- ok: () => assertOk(validate('bigint')(4n)),
45
- error: () => assertError(validate('bigint')(4)),
46
- },
47
- null: {
48
- ok: () => assertOk(validate('null')(null)),
49
- error: () => {
50
- assertError(validate('null')(undefined));
51
- assertError(validate('null')({}));
52
- },
53
- },
54
- array: {
55
- ok: () => {
56
- assertOk(validate('array')([]));
57
- assertOk(validate('array')([1, 2]));
58
- },
59
- error: () => {
60
- assertError(validate('array')({}));
61
- assertError(validate('array')(null));
62
- },
63
- },
64
- record: {
65
- ok: () => {
66
- assertOk(validate('record')({}));
67
- assertOk(validate('record')({ x: 1 }));
68
- },
69
- error: () => {
70
- assertError(validate('record')([]));
71
- assertError(validate('record')(null));
72
- },
73
- },
74
- function: {
75
- ok: () => assertOk(validate('function')(() => { })),
76
- error: () => assertError(validate('function')(null)),
77
- },
78
- lazy: {
79
- ok: () => assertOk(validate(() => 'string')('hello')),
80
- error: () => assertError(validate(() => 'string')(42)),
81
- },
82
- struct: {
83
- ok: () => assertOk(validate({ name: 'string', age: 'number' })({ name: 'alice', age: 30 })),
84
- missingField: () => assertError(validate({ name: 'string' })({})),
85
- wrongFieldType: () => assertError(validate({ name: 'string' })({ name: 42 })),
86
- notObject: () => assertError(validate({ name: 'string' })(null)),
87
- },
88
- }
89
22
  };
@@ -0,0 +1,39 @@
1
+ import type { Primitive, Unknown as DjsUnknown } from '../../../djs/module.f.ts';
2
+ import type { Tag0, Tag1, Const, Struct, Tuple, Info0, Info1, Info, Type } from '../module.f.ts';
3
+ import type { ReadonlyRecord } from '../../object/module.f.ts';
4
+ /** Maps a `Tag0` to its TypeScript type. */
5
+ export type Info0Ts<T extends Tag0> = T extends 'boolean' ? boolean : T extends 'number' ? number : T extends 'string' ? string : T extends 'bigint' ? bigint : T extends 'unknown' ? DjsUnknown : never;
6
+ /** Maps a `Const` schema to its TypeScript type. */
7
+ export type ConstTs<T extends Const> = T extends Primitive ? T : T extends Tuple ? TupleTs<T> : T extends Struct ? StructTs<T> : never;
8
+ /** Maps a `Tag1` and inner type to its TypeScript type. */
9
+ export type Info1Ts<K extends Tag1, T extends Type> = K extends 'array' ? ArrayTs<T> : K extends 'record' ? RecordTs<T> : never;
10
+ /** Maps an `Info` descriptor to its TypeScript type. */
11
+ export type InfoTs<T extends Info> = T extends readonly ['const', infer C extends Const] ? ConstTs<C> : T extends Info0<infer K extends Tag0> ? Info0Ts<K> : T extends Info1<infer K extends Tag1, infer I extends Type> ? Info1Ts<K, I> : never;
12
+ /** Maps an array schema `T` to `readonly Ts<T>[]`. */
13
+ export type ArrayTs<T extends Type> = ReadonlyArray<Ts<T>>;
14
+ /** Maps a record schema `T` to `{ readonly[K in string]: Ts<T> }`. */
15
+ export type RecordTs<T extends Type> = ReadonlyRecord<string, Ts<T>>;
16
+ export type ClosedTupleTs<T extends Tuple> = {
17
+ readonly [K in keyof T]: Ts<T[K]>;
18
+ };
19
+ /** Maps a tuple schema to a readonly tuple of resolved types. */
20
+ export type TupleTs<T extends Tuple> = ClosedTupleTs<T>;
21
+ /** Maps a struct schema to a readonly object of resolved types. */
22
+ export type StructTs<T extends Struct> = {
23
+ readonly [K in keyof T]: Ts<T[K]>;
24
+ };
25
+ /**
26
+ * Converts a schema `Type` to its corresponding TypeScript type.
27
+ *
28
+ * - `Thunk` → evaluates the returned `Info` via `InfoTs`
29
+ * - `Const` → resolves via `ConstTs` (primitives map to themselves; structs/tuples recurse)
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * type A = Ts<typeof string> // string
34
+ * type B = Ts<4> // 4
35
+ * type C = Ts<Array<typeof number>> // readonly number[]
36
+ * type D = Ts<{ x: typeof boolean }> // { readonly x: boolean }
37
+ * ```
38
+ */
39
+ export type Ts<T extends Type> = T extends () => (infer I extends Info) ? InfoTs<I> : T extends Const ? ConstTs<T> : never;
@@ -0,0 +1 @@
1
+ import { string, unknown } from "../module.f.js";
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Runtime validation of unknown values against RTTI schemas.
3
+ *
4
+ * The main entry point is `validate(rtti)`, which takes a schema `Type` and returns
5
+ * a `Validate<T>` function. When called with an unknown value, it returns a `Result`
6
+ * that is either `['ok', typedValue]` or `['error', message]`.
7
+ *
8
+ * ## Dispatch strategy
9
+ *
10
+ * - **`Thunk`** schemas are evaluated lazily: the thunk is called once to obtain an
11
+ * `Info` descriptor, then dispatched by tag:
12
+ * - `'const'` — delegates to `constValidate`
13
+ * - `'unknown'` — always succeeds (any DJS value is valid)
14
+ * - `Tag1` (`'array'`, `'record'`) — delegates to `containerValidate`
15
+ * - `Tag0` (`'boolean'`, `'number'`, `'string'`, `'bigint'`) — uses `typeof` check
16
+ * - **`Const`** schemas (primitives, tuples, structs) validate by exact equality or
17
+ * recursive field/element checking.
18
+ *
19
+ * ## Recursion safety
20
+ *
21
+ * For `array` and `record` schemas, the inner item validator is instantiated lazily —
22
+ * only after confirming the container is non-empty. This prevents infinite recursion
23
+ * when validating recursive schemas like `const list = () => ['array', list]`.
24
+ *
25
+ * @module
26
+ */
27
+ import type { Unknown } from '../../../djs/module.f.ts';
28
+ import { type Type } from '../module.f.ts';
29
+ import { type Result as CommonResult } from '../../result/module.f.ts';
30
+ import type { Ts } from '../ts/module.f.ts';
31
+ /** Validation result: either the typed value or an error message. */
32
+ export type Result<T extends Type> = CommonResult<Ts<T>, string>;
33
+ /** A function that validates an unknown value against schema `T`. */
34
+ export type Validate<T extends Type> = (value: Unknown) => Result<T>;
35
+ /**
36
+ * Creates a validator function for the given RTTI schema.
37
+ *
38
+ * @param rtti - A schema `Type`: a `Thunk` for tag-based schemas, or a `Const`
39
+ * (primitive literal, tuple, or struct) for exact-value schemas.
40
+ * @returns A `Validate<T>` function that checks an unknown value and returns
41
+ * `['ok', value]` or `['error', message]`.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const v = validate(array(number))
46
+ * v([1, 2, 3]) // ['ok', [1, 2, 3]]
47
+ * v(['a', 'b']) // ['error', 'unexpected value']
48
+ * ```
49
+ */
50
+ export declare const validate: <T extends Type>(rtti: T) => Validate<T>;
@@ -0,0 +1,100 @@
1
+ import { isTag1 } from "../module.f.js";
2
+ import { error, ok } from "../../result/module.f.js";
3
+ import { isArray as commonIsArray } from "../../array/module.f.js";
4
+ import { isObject as commonIsObject } from "../../object/module.f.js";
5
+ import { identity } from "../../function/module.f.js";
6
+ /**
7
+ * Builds a validator for `array` or `record` schemas.
8
+ * The inner item validator is instantiated lazily (only when the container is
9
+ * non-empty) to avoid infinite recursion with recursive schemas.
10
+ */
11
+ const containerValidate = (isContainer, getItems) => (item) => value => {
12
+ if (!isContainer(value)) {
13
+ return error('unexpected value');
14
+ }
15
+ const items = getItems(value);
16
+ if (items.length === 0) {
17
+ return ok(value);
18
+ }
19
+ // Note: we shouldn't instantiate `itemValidate` until we make sure `items` is not empty.
20
+ // Otherwise, we can get infinite recursion on empty arrays and objects
21
+ const itemValidate = validate(item);
22
+ for (const i of items) {
23
+ const r = itemValidate(i);
24
+ if (r[0] === 'error') {
25
+ return r;
26
+ }
27
+ }
28
+ return ok(value);
29
+ };
30
+ const isArray = value => commonIsArray(value);
31
+ const arrayValidate = containerValidate(isArray, identity);
32
+ const isObject = value => commonIsObject(value);
33
+ const recordValidate = containerValidate(isObject, Object.values);
34
+ const tag1Validate = ([tag, item]) => tag === 'array'
35
+ ? arrayValidate(item)
36
+ : recordValidate(item);
37
+ /** Validates a `Tag0` primitive schema using `typeof`. */
38
+ const primitive0Validate = (tag) => value => typeof value === tag ? ok(value) : error('unexpected value');
39
+ /** Validates a `Thunk` schema by evaluating it once and dispatching on the resulting `Info` tag. */
40
+ const thunkValidate = (rtti) => {
41
+ const info = rtti();
42
+ const [tag, value] = info;
43
+ switch (tag) {
44
+ case 'const':
45
+ return constValidate(value);
46
+ case 'unknown':
47
+ return ok;
48
+ }
49
+ return isTag1(tag)
50
+ ? tag1Validate(info)
51
+ : primitive0Validate(tag);
52
+ };
53
+ /**
54
+ * Builds a validator for `Tuple` or `Struct` const schemas.
55
+ * Iterates over the schema's entries and validates each corresponding
56
+ * element/property of the value.
57
+ */
58
+ const constContainerValidate = (isContainer, getItem) => (rtti) => value => {
59
+ if (!isContainer(value)) {
60
+ return error('unexpected value');
61
+ }
62
+ for (const [k, v] of Object.entries(rtti)) {
63
+ const item = getItem(value, k);
64
+ const r = validate(v)(item);
65
+ if (r[0] === 'error') {
66
+ return r;
67
+ }
68
+ }
69
+ return ok(value);
70
+ };
71
+ const tupleValidate = constContainerValidate(isArray, (value, k) => value[Number(k)]);
72
+ const structValidate = constContainerValidate(isObject, (value, k) => value[k]);
73
+ const constObjectValidate = (rtti) => commonIsArray(rtti)
74
+ ? tupleValidate(rtti)
75
+ : structValidate(rtti);
76
+ /** Validates a primitive `Const` schema using strict equality (`===`). */
77
+ const constPrimitiveValidate = (rtti) => value => rtti === value
78
+ ? ok(value)
79
+ : error('unexpected value');
80
+ const constValidate = (rtti) => typeof rtti === 'object' && rtti !== null
81
+ ? constObjectValidate(rtti)
82
+ : constPrimitiveValidate(rtti);
83
+ /**
84
+ * Creates a validator function for the given RTTI schema.
85
+ *
86
+ * @param rtti - A schema `Type`: a `Thunk` for tag-based schemas, or a `Const`
87
+ * (primitive literal, tuple, or struct) for exact-value schemas.
88
+ * @returns A `Validate<T>` function that checks an unknown value and returns
89
+ * `['ok', value]` or `['error', message]`.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const v = validate(array(number))
94
+ * v([1, 2, 3]) // ['ok', [1, 2, 3]]
95
+ * v(['a', 'b']) // ['error', 'unexpected value']
96
+ * ```
97
+ */
98
+ export const validate = (rtti) => typeof rtti === 'function'
99
+ ? thunkValidate(rtti)
100
+ : constValidate(rtti);
@@ -0,0 +1,76 @@
1
+ declare const _default: {
2
+ boolean: {
3
+ ok: () => void;
4
+ error: () => void;
5
+ };
6
+ number: {
7
+ ok: () => void;
8
+ error: () => void;
9
+ };
10
+ string: {
11
+ ok: () => void;
12
+ error: () => void;
13
+ };
14
+ bigint: {
15
+ ok: () => void;
16
+ error: () => void;
17
+ };
18
+ unknown: {
19
+ ok: () => void;
20
+ };
21
+ const: {
22
+ null: {
23
+ ok: () => void;
24
+ error: () => void;
25
+ };
26
+ undefined: {
27
+ ok: () => void;
28
+ error: () => void;
29
+ };
30
+ number: {
31
+ ok: () => void;
32
+ error: () => void;
33
+ };
34
+ string: {
35
+ ok: () => void;
36
+ error: () => void;
37
+ };
38
+ bigint: {
39
+ ok: () => void;
40
+ error: () => void;
41
+ };
42
+ boolean: {
43
+ ok: () => void;
44
+ error: () => void;
45
+ };
46
+ tuple: {
47
+ ok: () => void;
48
+ extraItems: () => void;
49
+ error: () => void;
50
+ };
51
+ struct: {
52
+ ok: () => void;
53
+ extraKeys: () => void;
54
+ error: () => void;
55
+ };
56
+ };
57
+ array: {
58
+ empty: () => void;
59
+ ok: () => void;
60
+ error: () => void;
61
+ nested: () => void;
62
+ };
63
+ record: {
64
+ empty: () => void;
65
+ ok: () => void;
66
+ error: () => void;
67
+ };
68
+ constThunk: {
69
+ primitive: () => void;
70
+ };
71
+ recursive: {
72
+ arrayOfArrays: () => void;
73
+ recordOfRecords: () => void;
74
+ };
75
+ };
76
+ export default _default;
@@ -0,0 +1,143 @@
1
+ import { validate } from "./module.f.js";
2
+ import { boolean, number, string, bigint, unknown, array, record } from "../module.f.js";
3
+ const assertOk = ([k]) => { if (k !== 'ok') {
4
+ throw 'expected ok';
5
+ } };
6
+ const assertError = ([k]) => { if (k !== 'error') {
7
+ throw 'expected error';
8
+ } };
9
+ export default {
10
+ boolean: {
11
+ ok: () => {
12
+ assertOk(validate(boolean)(true));
13
+ assertOk(validate(boolean)(false));
14
+ },
15
+ error: () => {
16
+ assertError(validate(boolean)(0));
17
+ assertError(validate(boolean)('true'));
18
+ assertError(validate(boolean)(null));
19
+ },
20
+ },
21
+ number: {
22
+ ok: () => assertOk(validate(number)(42)),
23
+ error: () => {
24
+ assertError(validate(number)('42'));
25
+ assertError(validate(number)(42n));
26
+ },
27
+ },
28
+ string: {
29
+ ok: () => assertOk(validate(string)('hello')),
30
+ error: () => {
31
+ assertError(validate(string)(42));
32
+ assertError(validate(string)(null));
33
+ },
34
+ },
35
+ bigint: {
36
+ ok: () => assertOk(validate(bigint)(4n)),
37
+ error: () => {
38
+ assertError(validate(bigint)(4));
39
+ assertError(validate(bigint)('4'));
40
+ },
41
+ },
42
+ unknown: {
43
+ ok: () => {
44
+ assertOk(validate(unknown)(null));
45
+ assertOk(validate(unknown)(42));
46
+ assertOk(validate(unknown)('hello'));
47
+ assertOk(validate(unknown)(true));
48
+ assertOk(validate(unknown)({}));
49
+ assertOk(validate(unknown)([]));
50
+ },
51
+ },
52
+ const: {
53
+ null: {
54
+ ok: () => assertOk(validate(null)(null)),
55
+ error: () => {
56
+ assertError(validate(null)(undefined));
57
+ assertError(validate(null)(0));
58
+ },
59
+ },
60
+ undefined: {
61
+ ok: () => assertOk(validate(undefined)(undefined)),
62
+ error: () => assertError(validate(undefined)(null)),
63
+ },
64
+ number: {
65
+ ok: () => assertOk(validate(42)(42)),
66
+ error: () => assertError(validate(42)(43)),
67
+ },
68
+ string: {
69
+ ok: () => assertOk(validate('hello')('hello')),
70
+ error: () => assertError(validate('hello')('world')),
71
+ },
72
+ bigint: {
73
+ ok: () => assertOk(validate(7n)(7n)),
74
+ error: () => assertError(validate(7n)(8n)),
75
+ },
76
+ boolean: {
77
+ ok: () => assertOk(validate(true)(true)),
78
+ error: () => assertError(validate(true)(false)),
79
+ },
80
+ tuple: {
81
+ ok: () => assertOk(validate([42, 'hello'])([42, 'hello'])),
82
+ extraItems: () => assertOk(validate([42])([42, 'extra'])),
83
+ error: () => {
84
+ assertError(validate([42])([99]));
85
+ assertError(validate([42])({}));
86
+ },
87
+ },
88
+ struct: {
89
+ ok: () => assertOk(validate({ a: 42, b: 'hello' })({ a: 42, b: 'hello' })),
90
+ extraKeys: () => assertOk(validate({ a: 42 })({ a: 42, b: 'extra' })),
91
+ error: () => {
92
+ assertError(validate({ a: 42 })({ a: 99 }));
93
+ assertError(validate({ a: 42 })([]));
94
+ },
95
+ },
96
+ },
97
+ array: {
98
+ empty: () => assertOk(validate(array(number))([])),
99
+ ok: () => assertOk(validate(array(number))([1, 2, 3])),
100
+ error: () => {
101
+ assertError(validate(array(number))([1, 'two', 3]));
102
+ assertError(validate(array(number))({}));
103
+ assertError(validate(array(number))(null));
104
+ },
105
+ nested: () => {
106
+ assertOk(validate(array(array(boolean)))([[true, false], [false]]));
107
+ assertError(validate(array(array(boolean)))([[true, 42]]));
108
+ },
109
+ },
110
+ record: {
111
+ empty: () => assertOk(validate(record(number))({})),
112
+ ok: () => assertOk(validate(record(string))({ a: 'hello', b: 'world' })),
113
+ error: () => {
114
+ assertError(validate(record(number))({ a: 1, b: 'two' }));
115
+ assertError(validate(record(number))(null));
116
+ assertError(validate(record(number))([]));
117
+ },
118
+ },
119
+ constThunk: {
120
+ primitive: () => {
121
+ assertOk(validate(() => ['const', 7n])(7n));
122
+ assertError(validate(() => ['const', 7n])(8n));
123
+ },
124
+ },
125
+ recursive: {
126
+ arrayOfArrays: () => {
127
+ // self-referential schema: an array whose elements are also arrays of the same type
128
+ const list = () => ['array', list];
129
+ // @ts-ignore see issue # 127
130
+ assertOk(validate(list)([]));
131
+ assertOk(validate(list)([[], []]));
132
+ assertOk(validate(list)([[[], []], []]));
133
+ assertError(validate(list)([42]));
134
+ assertError(validate(list)(null));
135
+ },
136
+ recordOfRecords: () => {
137
+ const tree = () => ['record', tree];
138
+ assertOk(validate(tree)({}));
139
+ assertOk(validate(tree)({ a: {}, b: { c: {} } }));
140
+ assertError(validate(tree)({ a: 42 }));
141
+ },
142
+ },
143
+ };
@@ -0,0 +1,2 @@
1
+ export type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
2
+ export type Assert<T extends true> = T;
@@ -0,0 +1 @@
1
+ export {};