functionalscript 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",
@@ -1,46 +1,9 @@
1
- /**
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.
38
- */
39
- import type { Primitive } from '../../djs/module.f.ts';
40
1
  import { type Includes } from '../array/module.f.ts';
41
- export type ConstObject = Struct | Tuple;
42
2
  /** A constant schema: a primitive literal, a struct object, or a tuple. */
43
- export type Const = Primitive | ConstObject;
3
+ export type Const = null | boolean | number | string | undefined | bigint | {
4
+ readonly [K in string]: Type;
5
+ } | readonly Type[];
6
+ export type ConstObject = Struct | Tuple;
44
7
  /** A struct schema: plain object whose values are nested `Type`s. */
45
8
  export type Struct = {
46
9
  readonly [K in string]: Type;
@@ -53,7 +16,7 @@ export declare const tag0List: readonly ["bigint", "boolean", "number", "string"
53
16
  /** Tags for nullary (zero-parameter) type schemas. */
54
17
  export type Tag0 = typeof tag0List[number];
55
18
  /** Info tuple for a nullary tag: `readonly[tag]`. */
56
- export type Info0<T extends Tag0> = readonly [T];
19
+ export type Info0<T extends Tag0> = T extends Tag0 ? readonly [T] : never;
57
20
  /**
58
21
  * The descriptor returned by a `Thunk`. One of:
59
22
  * - `['const', Const]` — a constant/literal schema (used in recursive thunks)
@@ -64,7 +27,7 @@ export type Info = readonly ['const', Const] | Info0<Tag0> | Info1<Tag1, Type>;
64
27
  /** A lazy schema: a zero-argument function returning an `Info` descriptor. */
65
28
  export type Thunk = () => Info;
66
29
  /** Any schema: a `Const` used directly, or a `Thunk` for tag-based/recursive schemas. */
67
- export type Type = Const | Thunk;
30
+ export type Type = (() => (readonly ['const', Const] | readonly ['bigint'] | readonly ['boolean'] | readonly ['number'] | readonly ['string'] | readonly ['unknown'] | readonly ['array', Type] | readonly ['record', Type])) | Const;
68
31
  /** The type of a nullary thunk for `Tag0`. */
69
32
  type Type0<T extends Tag0> = () => Info0<T>;
70
33
  /** Schema type for `boolean`. */
@@ -92,7 +55,7 @@ export declare const isTag1: Includes<string, typeof tag1List>;
92
55
  /** Tags for unary (one-parameter) type schemas. */
93
56
  export type Tag1 = typeof tag1List[number];
94
57
  /** Info tuple for a unary tag: `readonly[tag, innerType]`. */
95
- export type Info1<K extends Tag1, T extends Type> = readonly [K, T];
58
+ export type Info1<K extends Tag1, T extends Type> = K extends Tag1 ? readonly [K, T] : never;
96
59
  /** The type of a unary thunk for `Tag1` with inner type `T`. */
97
60
  export type Type1<K extends Tag1, T extends Type> = () => Info1<K, T>;
98
61
  type MakeType1<K extends Tag1> = <T extends Type>(t: T) => Type1<K, T>;
@@ -1,10 +1,14 @@
1
- import type { Primitive, Unknown as DjsUnknown } from '../../../djs/module.f.ts';
1
+ import type { Unknown as DjsUnknown } from '../../../djs/module.f.ts';
2
2
  import type { Tag0, Tag1, Const, Struct, Tuple, Info0, Info1, Info, Type } from '../module.f.ts';
3
3
  import type { ReadonlyRecord } from '../../object/module.f.ts';
4
4
  /** Maps a `Tag0` to its TypeScript type. */
5
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
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;
7
+ export type ConstTs<T> = T extends readonly Type[] ? TupleTs<T> : T extends {
8
+ readonly [k in string]: Type;
9
+ } ? {
10
+ readonly [K in keyof T]: Ts<T[K]>;
11
+ } : T;
8
12
  /** Maps a `Tag1` and inner type to its TypeScript type. */
9
13
  export type Info1Ts<K extends Tag1, T extends Type> = K extends 'array' ? ArrayTs<T> : K extends 'record' ? RecordTs<T> : never;
10
14
  /** Maps an `Info` descriptor to its TypeScript type. */
@@ -13,11 +17,10 @@ export type InfoTs<T extends Info> = T extends readonly ['const', infer C extend
13
17
  export type ArrayTs<T extends Type> = ReadonlyArray<Ts<T>>;
14
18
  /** Maps a record schema `T` to `{ readonly[K in string]: Ts<T> }`. */
15
19
  export type RecordTs<T extends Type> = ReadonlyRecord<string, Ts<T>>;
16
- export type ClosedTupleTs<T extends Tuple> = {
20
+ /** Maps a tuple schema to a readonly tuple of resolved types. */
21
+ export type TupleTs<T extends Tuple> = {
17
22
  readonly [K in keyof T]: Ts<T[K]>;
18
23
  };
19
- /** Maps a tuple schema to a readonly tuple of resolved types. */
20
- export type TupleTs<T extends Tuple> = ClosedTupleTs<T>;
21
24
  /** Maps a struct schema to a readonly object of resolved types. */
22
25
  export type StructTs<T extends Struct> = {
23
26
  readonly [K in keyof T]: Ts<T[K]>;
@@ -36,4 +39,6 @@ export type StructTs<T extends Struct> = {
36
39
  * type D = Ts<{ x: typeof boolean }> // { readonly x: boolean }
37
40
  * ```
38
41
  */
39
- export type Ts<T extends Type> = T extends () => (infer I extends Info) ? InfoTs<I> : T extends Const ? ConstTs<T> : never;
42
+ export type Ts<T extends Type> = T extends () => infer I ? (I extends readonly ['const', infer C] ? ConstTs<C> : I extends readonly ['boolean'] ? boolean : I extends readonly ['number'] ? number : I extends readonly ['string'] ? string : I extends readonly ['bigint'] ? bigint : I extends readonly ['unknown'] ? DjsUnknown : I extends readonly ['array', infer E extends Type] ? readonly Ts<E>[] : I extends readonly ['record', infer E extends Type] ? {
43
+ readonly [K in string]: Ts<E>;
44
+ } : never) : ConstTs<T>;
@@ -1 +1 @@
1
- import { string, unknown } from "../module.f.js";
1
+ export {};
@@ -95,6 +95,16 @@ const constValidate = (rtti) => typeof rtti === 'object' && rtti !== null
95
95
  * v(['a', 'b']) // ['error', 'unexpected value']
96
96
  * ```
97
97
  */
98
- export const validate = (rtti) => typeof rtti === 'function'
99
- ? thunkValidate(rtti)
100
- : constValidate(rtti);
98
+ export const validate = (rtti) => {
99
+ if (typeof rtti === 'function') {
100
+ const [tag, value] = rtti();
101
+ switch (tag) {
102
+ case 'const': return constValidate(value);
103
+ case 'array': return arrayValidate(value);
104
+ case 'record': return recordValidate(value);
105
+ case 'unknown': return ok;
106
+ }
107
+ return primitive0Validate(tag);
108
+ }
109
+ return constValidate(rtti);
110
+ };
@@ -19,21 +19,27 @@ export default {
19
19
  },
20
20
  },
21
21
  number: {
22
- ok: () => assertOk(validate(number)(42)),
22
+ ok: () => {
23
+ assertOk(validate(number)(42));
24
+ },
23
25
  error: () => {
24
26
  assertError(validate(number)('42'));
25
27
  assertError(validate(number)(42n));
26
28
  },
27
29
  },
28
30
  string: {
29
- ok: () => assertOk(validate(string)('hello')),
31
+ ok: () => {
32
+ assertOk(validate(string)('hello'));
33
+ },
30
34
  error: () => {
31
35
  assertError(validate(string)(42));
32
36
  assertError(validate(string)(null));
33
37
  },
34
38
  },
35
39
  bigint: {
36
- ok: () => assertOk(validate(bigint)(4n)),
40
+ ok: () => {
41
+ assertOk(validate(bigint)(4n));
42
+ },
37
43
  error: () => {
38
44
  assertError(validate(bigint)(4));
39
45
  assertError(validate(bigint)('4'));
@@ -51,34 +57,49 @@ export default {
51
57
  },
52
58
  const: {
53
59
  null: {
54
- ok: () => assertOk(validate(null)(null)),
60
+ ok: () => {
61
+ assertOk(validate(null)(null));
62
+ },
55
63
  error: () => {
56
64
  assertError(validate(null)(undefined));
57
65
  assertError(validate(null)(0));
58
66
  },
59
67
  },
60
68
  undefined: {
61
- ok: () => assertOk(validate(undefined)(undefined)),
69
+ ok: () => {
70
+ assertOk(validate(undefined)(undefined));
71
+ },
62
72
  error: () => assertError(validate(undefined)(null)),
63
73
  },
64
74
  number: {
65
- ok: () => assertOk(validate(42)(42)),
75
+ ok: () => {
76
+ assertOk(validate(42)(42));
77
+ },
66
78
  error: () => assertError(validate(42)(43)),
67
79
  },
68
80
  string: {
69
- ok: () => assertOk(validate('hello')('hello')),
81
+ ok: () => {
82
+ assertOk(validate('hello')('hello'));
83
+ },
70
84
  error: () => assertError(validate('hello')('world')),
71
85
  },
72
86
  bigint: {
73
- ok: () => assertOk(validate(7n)(7n)),
87
+ ok: () => {
88
+ assertOk(validate(7n)(7n));
89
+ },
74
90
  error: () => assertError(validate(7n)(8n)),
75
91
  },
76
92
  boolean: {
77
- ok: () => assertOk(validate(true)(true)),
93
+ ok: () => {
94
+ assertOk(validate(true)(true));
95
+ },
78
96
  error: () => assertError(validate(true)(false)),
79
97
  },
80
98
  tuple: {
81
- ok: () => assertOk(validate([42, 'hello'])([42, 'hello'])),
99
+ ok: () => {
100
+ const t = [42, 'hello'];
101
+ assertOk(validate(t)([42, 'hello']));
102
+ },
82
103
  extraItems: () => assertOk(validate([42])([42, 'extra'])),
83
104
  error: () => {
84
105
  assertError(validate([42])([99]));
@@ -86,7 +107,10 @@ export default {
86
107
  },
87
108
  },
88
109
  struct: {
89
- ok: () => assertOk(validate({ a: 42, b: 'hello' })({ a: 42, b: 'hello' })),
110
+ ok: () => {
111
+ const t = { a: 42, b: 'hello' };
112
+ assertOk(validate(t)({ a: 42, b: 'hello' }));
113
+ },
90
114
  extraKeys: () => assertOk(validate({ a: 42 })({ a: 42, b: 'extra' })),
91
115
  error: () => {
92
116
  assertError(validate({ a: 42 })({ a: 99 }));
@@ -96,20 +120,27 @@ export default {
96
120
  },
97
121
  array: {
98
122
  empty: () => assertOk(validate(array(number))([])),
99
- ok: () => assertOk(validate(array(number))([1, 2, 3])),
123
+ ok: () => {
124
+ const t = array(number);
125
+ assertOk(validate(array(number))([1, 2, 3]));
126
+ },
100
127
  error: () => {
101
128
  assertError(validate(array(number))([1, 'two', 3]));
102
129
  assertError(validate(array(number))({}));
103
130
  assertError(validate(array(number))(null));
104
131
  },
105
132
  nested: () => {
133
+ const t = array(array(boolean));
106
134
  assertOk(validate(array(array(boolean)))([[true, false], [false]]));
107
135
  assertError(validate(array(array(boolean)))([[true, 42]]));
108
136
  },
109
137
  },
110
138
  record: {
111
139
  empty: () => assertOk(validate(record(number))({})),
112
- ok: () => assertOk(validate(record(string))({ a: 'hello', b: 'world' })),
140
+ ok: () => {
141
+ const t = record(string);
142
+ assertOk(validate(t)({ a: 'hello', b: 'world' }));
143
+ },
113
144
  error: () => {
114
145
  assertError(validate(record(number))({ a: 1, b: 'two' }));
115
146
  assertError(validate(record(number))(null));
@@ -118,26 +149,28 @@ export default {
118
149
  },
119
150
  constThunk: {
120
151
  primitive: () => {
121
- assertOk(validate(() => ['const', 7n])(7n));
122
- assertError(validate(() => ['const', 7n])(8n));
152
+ const t = () => ['const', 7n];
153
+ assertOk(validate(t)(7n));
154
+ assertError(validate(t)(8n));
123
155
  },
124
156
  },
125
157
  recursive: {
126
158
  arrayOfArrays: () => {
127
159
  // self-referential schema: an array whose elements are also arrays of the same type
128
160
  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));
161
+ const v = validate(list);
162
+ assertOk(v([]));
163
+ assertOk(v([[], []]));
164
+ assertOk(v([[[], []], []]));
165
+ assertError(v([42]));
166
+ assertError(v(null));
135
167
  },
136
168
  recordOfRecords: () => {
137
169
  const tree = () => ['record', tree];
138
- assertOk(validate(tree)({}));
139
- assertOk(validate(tree)({ a: {}, b: { c: {} } }));
140
- assertError(validate(tree)({ a: 42 }));
170
+ const v = validate(tree);
171
+ assertOk(v({}));
172
+ assertOk(v({ a: {}, b: { c: {} } }));
173
+ assertError(v({ a: 42 }));
141
174
  },
142
175
  },
143
176
  };