functionalscript 0.14.8 → 0.16.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.
Files changed (38) hide show
  1. package/fs/ci/common/module.f.d.ts +50 -16
  2. package/fs/ci/common/module.f.js +19 -0
  3. package/fs/ci/config/module.f.d.ts +2 -2
  4. package/fs/ci/config/module.f.js +2 -2
  5. package/fs/ci/module.f.js +1 -1
  6. package/fs/ci/test.f.js +6 -2
  7. package/fs/dev/module.f.d.ts +2 -1
  8. package/fs/dev/module.f.js +25 -16
  9. package/fs/dev/tf/module.f.js +10 -0
  10. package/fs/dev/tf/test.f.d.ts +5 -0
  11. package/fs/dev/tf/test.f.js +14 -0
  12. package/fs/fjs/module.f.js +5 -3
  13. package/fs/io/module.d.ts +2 -2
  14. package/fs/io/module.f.d.ts +3 -15
  15. package/fs/io/module.f.js +14 -8
  16. package/fs/io/module.js +2 -2
  17. package/fs/io/virtual/module.f.js +2 -2
  18. package/fs/types/effects/mock/module.f.d.ts +1 -1
  19. package/fs/types/effects/mock/module.f.js +1 -1
  20. package/fs/types/effects/module.f.d.ts +5 -7
  21. package/fs/types/effects/module.f.js +1 -2
  22. package/fs/types/effects/module.js +1 -1
  23. package/fs/types/effects/node/module.f.d.ts +34 -25
  24. package/fs/types/effects/node/module.f.js +9 -7
  25. package/fs/types/effects/node/test.f.js +1 -1
  26. package/fs/types/effects/node/virtual/module.f.js +16 -5
  27. package/fs/types/rtti/common/module.f.d.ts +77 -0
  28. package/fs/types/rtti/common/module.f.js +45 -0
  29. package/fs/types/rtti/module.f.d.ts +4 -8
  30. package/fs/types/rtti/module.f.js +5 -71
  31. package/fs/types/rtti/parse/module.f.d.ts +3 -27
  32. package/fs/types/rtti/parse/module.f.js +12 -20
  33. package/fs/types/rtti/test.f.d.ts +0 -28
  34. package/fs/types/rtti/test.f.js +0 -107
  35. package/fs/types/rtti/ts/module.f.d.ts +9 -6
  36. package/fs/types/rtti/validate/module.f.d.ts +3 -79
  37. package/fs/types/rtti/validate/module.f.js +13 -36
  38. package/package.json +2 -2
@@ -1,5 +1,3 @@
1
- import { boolean, number, string, bigint, unknown, or, never } from "./module.f.js";
2
- import { assertEq } from "../../dev/module.f.js";
3
1
  const tests = {
4
2
  undefined: [undefined],
5
3
  boolean: [true, false],
@@ -9,115 +7,10 @@ const tests = {
9
7
  object: [null, {}, []],
10
8
  function: [() => undefined]
11
9
  };
12
- const variantsOf = (t) => {
13
- if (typeof t !== 'function') {
14
- throw 'expected an or thunk';
15
- }
16
- const info = t();
17
- if (info[0] !== 'or') {
18
- throw `expected an or thunk, got ${info[0]}`;
19
- }
20
- return info.slice(1);
21
- };
22
10
  export default {
23
11
  typeof: Object.fromEntries(Object.entries(tests).map(([k, a]) => [k, a.map(v => () => {
24
12
  if (typeof v !== k) {
25
13
  throw `typeof ${v} !== ${k}`;
26
14
  }
27
15
  })])),
28
- or: {
29
- flatten: {
30
- shallow: () => {
31
- const v = variantsOf(or(or(boolean, number), string));
32
- assertEq(v.length, 3);
33
- assertEq(v[0], boolean);
34
- assertEq(v[1], number);
35
- assertEq(v[2], string);
36
- },
37
- deep: () => {
38
- assertEq(variantsOf(or(or(or(boolean, number), string), bigint)).length, 4);
39
- },
40
- manualOrThunk: () => {
41
- // Manually-constructed `() => ['or', ...]` thunks should also flatten.
42
- const inner = () => ['or', boolean, number];
43
- assertEq(variantsOf(or(inner, string)).length, 3);
44
- },
45
- selfReferential: () => {
46
- // A self-referential `or` thunk terminates: it is expanded once,
47
- // then kept as-is on the second encounter (via the visited set).
48
- const t = (() => ['or', boolean, t]);
49
- // expansion: t -> [boolean, t]; t (second) kept as-is; number kept.
50
- assertEq(variantsOf(or(t, number)).length, 3);
51
- },
52
- },
53
- unknownCollapse: {
54
- withConst: () => {
55
- const v = variantsOf(or(unknown, 42));
56
- assertEq(v.length, 1);
57
- assertEq(v[0], unknown);
58
- },
59
- withThunk: () => {
60
- const v = variantsOf(or(number, unknown, string));
61
- assertEq(v.length, 1);
62
- assertEq(v[0], unknown);
63
- },
64
- nested: () => {
65
- // nested `or` whose flattening surfaces an `unknown`
66
- const v = variantsOf(or(or(boolean, unknown), 42));
67
- assertEq(v.length, 1);
68
- assertEq(v[0], unknown);
69
- },
70
- },
71
- dropPrimitiveSubset: {
72
- number: () => {
73
- const v = variantsOf(or(42, number));
74
- assertEq(v.length, 1);
75
- assertEq(v[0], number);
76
- },
77
- boolean: () => {
78
- const v = variantsOf(or(true, false, boolean));
79
- assertEq(v.length, 1);
80
- assertEq(v[0], boolean);
81
- },
82
- string: () => {
83
- const v = variantsOf(or('hi', 'bye', string));
84
- assertEq(v.length, 1);
85
- assertEq(v[0], string);
86
- },
87
- bigint: () => {
88
- const v = variantsOf(or(7n, bigint));
89
- assertEq(v.length, 1);
90
- assertEq(v[0], bigint);
91
- },
92
- keepIfThunkAbsent: () => {
93
- // Without a matching primitive thunk, consts are kept.
94
- assertEq(variantsOf(or(42, 'hello')).length, 2);
95
- },
96
- mixed: () => {
97
- // `null`/`undefined` consts have no matching primitive thunk and remain.
98
- assertEq(variantsOf(or(null, undefined, 42, number)).length, 3);
99
- },
100
- },
101
- dedup: {
102
- sameThunkReference: () => {
103
- assertEq(variantsOf(or(boolean, boolean)).length, 1);
104
- },
105
- samePrimitive: () => {
106
- assertEq(variantsOf(or(42, 42)).length, 1);
107
- },
108
- nanCollapses: () => {
109
- // `Object.is(NaN, NaN)` is true, so duplicate `NaN` variants
110
- // collapse — matching `constPrimitiveValidate` semantics.
111
- assertEq(variantsOf(or(NaN, NaN)).length, 1);
112
- },
113
- signedZeroDistinct: () => {
114
- // `Object.is(+0, -0)` is false, so they remain distinct.
115
- assertEq(variantsOf(or(0, -0)).length, 2);
116
- },
117
- },
118
- emptyStillNever: () => {
119
- assertEq(variantsOf(or()).length, 0);
120
- assertEq(variantsOf(never).length, 0);
121
- },
122
- },
123
16
  };
@@ -6,9 +6,7 @@ export type Info0Ts<T extends Tag0> = T extends 'boolean' ? boolean : T extends
6
6
  /** Maps a `Const` schema to its TypeScript type. */
7
7
  export type ConstTs<T> = T extends readonly Type[] ? TupleTs<T> : T extends {
8
8
  readonly [k in string]: Type;
9
- } ? {
10
- readonly [K in keyof T]: Ts<T[K]>;
11
- } : T;
9
+ } ? StructTs<T> : T;
12
10
  /** Maps a `Tag1` and inner type to its TypeScript type. */
13
11
  export type Info1Ts<K extends Tag1, T extends Type> = K extends 'array' ? ArrayTs<T> : K extends 'record' ? RecordTs<T> : never;
14
12
  /** Maps an array schema `T` to `readonly Ts<T>[]`. */
@@ -19,10 +17,14 @@ export type RecordTs<T extends Type> = ReadonlyRecord<string, Ts<T>>;
19
17
  export type TupleTs<T extends Tuple> = {
20
18
  readonly [K in keyof T]: Ts<T[K]>;
21
19
  };
22
- /** Maps a struct schema to a readonly object of resolved types. */
23
- export type StructTs<T extends Struct> = {
24
- readonly [K in keyof T]: Ts<T[K]>;
20
+ type OptionalFields<T extends Struct> = {
21
+ readonly [K in keyof T as undefined extends Ts<T[K]> ? K : never]?: Ts<T[K]>;
22
+ };
23
+ type RequiredFields<T extends Struct> = {
24
+ readonly [K in keyof T as undefined extends Ts<T[K]> ? never : K]: Ts<T[K]>;
25
25
  };
26
+ /** Maps a struct schema to a readonly object of resolved types, with optional fields for schemas that include `undefined`. */
27
+ export type StructTs<T extends Struct> = (keyof OptionalFields<T> extends never ? unknown : OptionalFields<T>) & (keyof RequiredFields<T> extends never ? unknown : RequiredFields<T>);
26
28
  /**
27
29
  * Converts a schema `Type` to its corresponding TypeScript type.
28
30
  *
@@ -70,3 +72,4 @@ export type Ts<T extends Type> = T extends () => infer I ? (I extends readonly [
70
72
  * ```
71
73
  */
72
74
  export declare const printer: (mut?: true) => (rtti: Type) => string;
75
+ export {};
@@ -1,80 +1,4 @@
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', { path, message }]`.
7
- *
8
- * ## Error path
9
- *
10
- * On failure, the error carries a `path` pointing at the offending sub-value:
11
- * each step is the string property name (for structs/records) or the stringified
12
- * index (for tuples/arrays). The path is empty when the failure is at the root.
13
- *
14
- * ## Dispatch strategy
15
- *
16
- * - **`Thunk`** schemas are evaluated lazily: the thunk is called once to obtain an
17
- * `Info` descriptor, then dispatched by tag:
18
- * - `'const'` — delegates to `constValidate`
19
- * - `'unknown'` — always succeeds (any DJS value is valid)
20
- * - `Tag1` (`'array'`, `'record'`) — delegates to `containerValidate`
21
- * - `Tag0` (`'boolean'`, `'number'`, `'string'`, `'bigint'`) — uses `typeof` check
22
- * - `'or'` — tries each variant; reports `'no match'` at the current location if all fail
23
- * - **`Const`** schemas (primitives, tuples, structs) validate by exact equality or
24
- * recursive field/element checking.
25
- *
26
- * ## Recursion safety
27
- *
28
- * For `array` and `record` schemas, the inner item validator is instantiated lazily —
29
- * only after confirming the container is non-empty. This prevents infinite recursion
30
- * when validating recursive schemas like `const list = () => ['array', list]`.
31
- *
32
- * @module
33
- */
34
- import type { Unknown } from '../../../djs/module.f.ts';
35
- import { type Info0, type Primitive0, type Type } from '../module.f.ts';
36
- import { type Error, type Result as CommonResult } from '../../result/module.f.ts';
37
- import type { Ts } from '../ts/module.f.ts';
38
- import type { Primitive } from '../../../djs/module.f.ts';
39
- /** A path to a sub-value within the validated structure. Each step is an object key or stringified array index. */
40
- export type Path = readonly string[];
41
- /** Detailed validation failure: the offending `path` plus a short `message`. */
42
- export type ValidationError = {
43
- readonly path: Path;
44
- readonly message: string;
45
- };
46
- /** Validation result: either the typed value or a `ValidationError`. */
47
- export type Result<T extends Type> = CommonResult<Ts<T>, ValidationError>;
48
- /** A function that validates an unknown value against schema `T`. */
49
- export type Validate<T extends Type> = (value: Unknown) => Result<T>;
50
- /** Builds an error result with empty path and the given message. */
51
- export declare const verror: (message: string) => Error<ValidationError>;
52
- /** Prepends `key` to the error's path, used to build the path bottom-up. */
53
- export declare const prependPath: (key: string, r: Error<ValidationError>) => Error<ValidationError>;
54
- /** Validates a `Tag0` primitive schema using `typeof`. */
55
- export declare const primitive0Validate: <K extends Primitive0, T extends Info0<K>>(tag: K) => Validate<T>;
56
- /**
57
- * Validates a primitive `Const` schema using `Object.is` (SameValue).
58
- *
59
- * `Object.is` is used instead of `===` so that:
60
- * - `NaN` const schemas match `NaN` values (`===` would always fail because `NaN !== NaN`).
61
- * - `+0` and `-0` are treated as distinct const values.
62
- */
63
- export declare const constPrimitiveValidate: <T extends Primitive>(rtti: T) => Validate<T>;
64
- /**
65
- * Creates a validator function for the given RTTI schema.
66
- *
67
- * @param rtti - A schema `Type`: a `Thunk` for tag-based schemas, or a `Const`
68
- * (primitive literal, tuple, or struct) for exact-value schemas.
69
- * @returns A `Validate<T>` function that checks an unknown value and returns
70
- * `['ok', value]` or `['error', { path, message }]`.
71
- *
72
- * @example
73
- * ```ts
74
- * const v = validate(array(number))
75
- * v([1, 2, 3]) // ['ok', [1, 2, 3]]
76
- * v([1, 'two']) // ['error', { path: ['1'], message: 'unexpected value' }]
77
- * v(['a']) // ['error', { path: ['0'], message: 'unexpected value' }]
78
- * ```
79
- */
1
+ import { type Type } from '../module.f.ts';
2
+ import { type Validate } from '../common/module.f.ts';
3
+ export { constPrimitiveValidate, prependPath, primitive0Validate, verror, type Path, type Result, type Validate, type ValidationError, } from '../common/module.f.ts';
80
4
  export declare const validate: <T extends Type>(rtti: T) => Validate<T>;
@@ -1,11 +1,9 @@
1
1
  import {} from "../module.f.js";
2
- import { error, ok } from "../../result/module.f.js";
2
+ import { ok } from "../../result/module.f.js";
3
3
  import { isArray as commonIsArray } from "../../array/module.f.js";
4
4
  import { isObject as commonIsObject } from "../../object/module.f.js";
5
- /** Builds an error result with empty path and the given message. */
6
- export const verror = (message) => error({ path: [], message });
7
- /** Prepends `key` to the error's path, used to build the path bottom-up. */
8
- export const prependPath = (key, r) => error({ path: [key, ...r[1].path], message: r[1].message });
5
+ import { constPrimitiveValidate, prependPath, primitive0Validate, verror, visit, } from "../common/module.f.js";
6
+ export { constPrimitiveValidate, prependPath, primitive0Validate, verror, } from "../common/module.f.js";
9
7
  /**
10
8
  * Builds a validator for `array` or `record` schemas.
11
9
  * The inner item validator is instantiated lazily (only when the container is
@@ -35,8 +33,6 @@ const arrayEntries = (value) => value.map((v, i) => [String(i), v]);
35
33
  const arrayValidate = containerValidate(isArray, arrayEntries);
36
34
  const isObject = value => commonIsObject(value);
37
35
  const recordValidate = containerValidate(isObject, Object.entries);
38
- /** Validates a `Tag0` primitive schema using `typeof`. */
39
- export const primitive0Validate = (tag) => value => typeof value === tag ? ok(value) : verror('unexpected value');
40
36
  /**
41
37
  * Builds a validator for `Tuple` or `Struct` const schemas.
42
38
  * Iterates over the schema's entries and validates each corresponding
@@ -57,22 +53,6 @@ const constContainerValidate = (isContainer, getItem) => (rtti) => value => {
57
53
  };
58
54
  const tupleValidate = constContainerValidate(isArray, (value, k) => value[Number(k)]);
59
55
  const structValidate = constContainerValidate(isObject, (value, k) => value[k]);
60
- const constObjectValidate = (rtti) => commonIsArray(rtti)
61
- ? tupleValidate(rtti)
62
- : structValidate(rtti);
63
- /**
64
- * Validates a primitive `Const` schema using `Object.is` (SameValue).
65
- *
66
- * `Object.is` is used instead of `===` so that:
67
- * - `NaN` const schemas match `NaN` values (`===` would always fail because `NaN !== NaN`).
68
- * - `+0` and `-0` are treated as distinct const values.
69
- */
70
- export const constPrimitiveValidate = (rtti) => value => Object.is(rtti, value)
71
- ? ok(value)
72
- : verror('unexpected value');
73
- const constValidate = (rtti) => typeof rtti === 'object' && rtti !== null
74
- ? constObjectValidate(rtti)
75
- : constPrimitiveValidate(rtti);
76
56
  const orValidate = (rtti) => {
77
57
  const all = rtti.map(r => validate(r));
78
58
  return value => {
@@ -101,17 +81,14 @@ const orValidate = (rtti) => {
101
81
  * v(['a']) // ['error', { path: ['0'], message: 'unexpected value' }]
102
82
  * ```
103
83
  */
104
- export const validate = (rtti) => {
105
- if (typeof rtti === 'function') {
106
- const [tag, ...value] = rtti();
107
- switch (tag) {
108
- case 'const': return constValidate(value[0]);
109
- case 'array': return arrayValidate(value[0]);
110
- case 'record': return recordValidate(value[0]);
111
- case 'unknown': return ok;
112
- case 'or': return orValidate(value);
113
- }
114
- return primitive0Validate(tag);
115
- }
116
- return constValidate(rtti);
84
+ const validateVisitor = {
85
+ tuple: tupleValidate,
86
+ struct: structValidate,
87
+ array: arrayValidate,
88
+ record: recordValidate,
89
+ or: orValidate,
90
+ constPrimitive: constPrimitiveValidate,
91
+ primitive0: primitive0Validate,
92
+ unknown: () => ok,
117
93
  };
94
+ export const validate = (rtti) => visit(validateVisitor)(rtti);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.14.8",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",
@@ -18,7 +18,7 @@
18
18
  "website": "node --experimental-strip-types ./fs/fjs/module.ts r ./fs/website/module.f.ts"
19
19
  },
20
20
  "engines": {
21
- "node": ">=20"
21
+ "node": ">=22"
22
22
  },
23
23
  "bin": {
24
24
  "fjs": "fs/fjs/module.js"