functionalscript 0.11.10 → 0.11.11
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/html/module.f.js +5 -2
- package/package.json +1 -1
- package/types/array/module.f.d.ts +1 -0
- package/types/array/module.f.js +1 -0
- package/types/rtti/module.f.d.ts +48 -0
- package/types/rtti/module.f.js +53 -0
- package/types/rtti/test.f.d.ts +54 -0
- package/types/rtti/test.f.js +89 -0
package/html/module.f.js
CHANGED
|
@@ -2,6 +2,7 @@ import { map, flatMap, flat, concat as listConcat } from "../types/list/module.f
|
|
|
2
2
|
import { concat, concat as stringConcat } from "../types/string/module.f.js";
|
|
3
3
|
import { compose } from "../types/function/module.f.js";
|
|
4
4
|
import { stringToList } from "../text/utf16/module.f.js";
|
|
5
|
+
import { includes } from "../types/array/module.f.js";
|
|
5
6
|
const { fromCharCode } = String;
|
|
6
7
|
const { entries } = Object;
|
|
7
8
|
/**
|
|
@@ -62,6 +63,8 @@ const parseElement = (e) => {
|
|
|
62
63
|
[tag, item1, list] :
|
|
63
64
|
[tag, {}, [item1, ...list]];
|
|
64
65
|
};
|
|
66
|
+
const isVoidTag = includes(voidTagList);
|
|
67
|
+
const isRawText = includes(rawText);
|
|
65
68
|
/**
|
|
66
69
|
* Converts a FunctionalScript element into a list of HTML string chunks.
|
|
67
70
|
*
|
|
@@ -71,10 +74,10 @@ const parseElement = (e) => {
|
|
|
71
74
|
export const element = (e) => {
|
|
72
75
|
const [tag, a, n] = parseElement(e);
|
|
73
76
|
const open = flat([[`<`, tag], attributes(a), [`>`]]);
|
|
74
|
-
if (
|
|
77
|
+
if (isVoidTag(tag)) {
|
|
75
78
|
return open;
|
|
76
79
|
}
|
|
77
|
-
return flat([open,
|
|
80
|
+
return flat([open, isRawText(tag) ? [rawMap(n)] : nodes(n), ['</', tag, '>']]);
|
|
78
81
|
};
|
|
79
82
|
/**
|
|
80
83
|
* Builds a complete HTML document by prepending `<!DOCTYPE html>`.
|
package/package.json
CHANGED
|
@@ -37,3 +37,4 @@ export declare const splitLast: <T>(a: readonly T[]) => readonly [readonly T[],
|
|
|
37
37
|
* we may minimize memory a number of memory allocations.
|
|
38
38
|
*/
|
|
39
39
|
export declare const empty: readonly [];
|
|
40
|
+
export declare const includes: <I, T extends readonly I[]>(a: T) => (v: I) => v is T[number];
|
package/types/array/module.f.js
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
/**
|
|
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.
|
|
14
|
+
*/
|
|
15
|
+
export type StructType = object & {
|
|
16
|
+
readonly [K in string]: Type;
|
|
17
|
+
};
|
|
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;
|
|
42
|
+
/**
|
|
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>`.
|
|
46
|
+
*/
|
|
47
|
+
export declare const validate: <T extends Type>(rtti: T) => Validate<T>;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
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));
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
typeof: {
|
|
3
|
+
[k: string]: (() => void)[];
|
|
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
|
+
};
|
|
54
|
+
export default _default;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { validate } from "./module.f.js";
|
|
2
|
+
const tests = {
|
|
3
|
+
undefined: [undefined],
|
|
4
|
+
boolean: [true, false],
|
|
5
|
+
string: ['hello'],
|
|
6
|
+
number: [3],
|
|
7
|
+
bigint: [4n],
|
|
8
|
+
object: [null, {}, []],
|
|
9
|
+
function: [() => undefined]
|
|
10
|
+
};
|
|
11
|
+
const assertOk = ([k]) => { if (k !== 'ok') {
|
|
12
|
+
throw 'expected ok';
|
|
13
|
+
} };
|
|
14
|
+
const assertError = ([k]) => { if (k !== 'error') {
|
|
15
|
+
throw 'expected error';
|
|
16
|
+
} };
|
|
17
|
+
export default {
|
|
18
|
+
typeof: Object.fromEntries(Object.entries(tests).map(([k, a]) => [k, a.map(v => () => {
|
|
19
|
+
if (typeof v !== k) {
|
|
20
|
+
throw `typeof ${v} !== ${k}`;
|
|
21
|
+
}
|
|
22
|
+
})])),
|
|
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
|
+
};
|