functionalscript 0.17.0 → 0.19.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.
- package/fs/asn.1/module.f.js +7 -8
- package/fs/asn.1/test.f.js +11 -12
- package/fs/ci/common/module.f.d.ts +4 -5
- package/fs/ci/common/module.f.js +4 -4
- package/fs/ci/config/module.f.d.ts +5 -5
- package/fs/ci/config/module.f.js +5 -5
- package/fs/ci/test.f.js +2 -4
- package/fs/crypto/sign/module.f.js +3 -3
- package/fs/dev/module.f.d.ts +3 -3
- package/fs/dev/module.f.js +12 -9
- package/fs/dev/tf/module.d.ts +1 -1
- package/fs/dev/tf/module.f.d.ts +76 -7
- package/fs/dev/tf/module.f.js +166 -87
- package/fs/dev/tf/module.js +3 -53
- package/fs/dev/tf/scenarios/all.d.ts +1 -0
- package/fs/dev/tf/scenarios/all.js +3 -0
- package/fs/dev/tf/scenarios/fail.fail.f.d.ts +1 -0
- package/fs/dev/tf/scenarios/fail.fail.f.js +1 -0
- package/fs/dev/tf/scenarios/return-value.pass.f.d.ts +1 -0
- package/fs/dev/tf/scenarios/return-value.pass.f.js +2 -0
- package/fs/dev/tf/scenarios/throw.pass.f.d.ts +6 -0
- package/fs/dev/tf/scenarios/throw.pass.f.js +3 -0
- package/fs/dev/tf/test.f.d.ts +21 -20
- package/fs/dev/tf/test.f.js +254 -31
- package/fs/djs/module.f.d.ts +2 -2
- package/fs/djs/module.f.js +8 -5
- package/fs/djs/test.f.js +5 -6
- package/fs/djs/tokenizer-new/test.f.js +126 -78
- package/fs/djs/transpiler/module.f.js +2 -2
- package/fs/djs/transpiler/test.f.js +11 -12
- package/fs/fjs/module.f.d.ts +2 -7
- package/fs/fjs/module.f.js +16 -22
- package/fs/fjs/module.js +2 -2
- package/fs/io/module.d.ts +3 -3
- package/fs/io/module.f.d.ts +9 -2
- package/fs/io/module.f.js +13 -3
- package/fs/io/module.js +68 -17
- package/fs/path/module.f.d.ts +6 -0
- package/fs/path/module.f.js +6 -0
- package/fs/path/test.f.d.ts +3 -5
- package/fs/path/test.f.js +67 -49
- package/fs/sul/id/module.f.js +3 -4
- package/fs/sul/level/literal/module.f.js +3 -3
- package/fs/text/sgr/module.f.d.ts +9 -1
- package/fs/text/sgr/module.f.js +16 -5
- package/fs/text/sgr/test.f.js +16 -1
- package/fs/types/bit_vec/module.f.d.ts +9 -4
- package/fs/types/bit_vec/module.f.js +7 -9
- package/fs/types/btree/remove/module.f.d.ts +1 -1
- package/fs/types/btree/remove/module.f.js +7 -2
- package/fs/types/btree/set/module.f.d.ts +1 -1
- package/fs/types/btree/set/module.f.js +7 -2
- package/fs/types/btree/types/module.f.d.ts +1 -0
- package/fs/types/btree/types/module.f.js +1 -1
- package/fs/types/effects/node/module.f.d.ts +30 -17
- package/fs/types/effects/node/module.f.js +21 -2
- package/fs/types/effects/node/test.f.js +8 -5
- package/fs/types/effects/node/virtual/module.f.d.ts +12 -2
- package/fs/types/effects/node/virtual/module.f.js +31 -17
- package/fs/types/function/compare/module.f.d.ts +12 -0
- package/fs/types/function/compare/module.f.js +33 -0
- package/fs/types/function/operator/test.f.d.ts +10 -0
- package/fs/types/function/operator/test.f.js +81 -0
- package/fs/types/nullable/test.f.js +10 -1
- package/fs/types/range_map/module.f.js +3 -18
- package/fs/types/result/module.f.d.ts +4 -0
- package/fs/types/result/module.f.js +4 -0
- package/fs/types/result/test.f.d.ts +2 -4
- package/fs/types/result/test.f.js +24 -16
- package/fs/types/rtti/common/module.f.d.ts +10 -1
- package/fs/types/rtti/common/module.f.js +7 -2
- package/fs/types/rtti/parse/module.f.js +35 -46
- package/fs/types/rtti/validate/module.f.js +9 -12
- package/fs/types/sorted_list/module.f.d.ts +1 -2
- package/fs/types/sorted_list/module.f.js +8 -21
- package/fs/types/sorted_set/module.f.d.ts +1 -3
- package/fs/types/ts/test.f.d.ts +18 -0
- package/fs/types/ts/test.f.js +111 -0
- package/fs/types/uint8array/module.f.js +7 -1
- package/fs/types/uint8array/test.f.d.ts +1 -0
- package/fs/types/uint8array/test.f.js +5 -1
- package/package.json +4 -4
|
@@ -64,11 +64,11 @@ export default {
|
|
|
64
64
|
throw result;
|
|
65
65
|
}
|
|
66
66
|
const tmp = state.root.tmp;
|
|
67
|
-
if (tmp
|
|
67
|
+
if (typeof tmp !== 'object') {
|
|
68
68
|
throw state.root;
|
|
69
69
|
}
|
|
70
70
|
const cache = tmp.cache;
|
|
71
|
-
if (cache
|
|
71
|
+
if (typeof cache !== 'object') {
|
|
72
72
|
throw tmp;
|
|
73
73
|
}
|
|
74
74
|
},
|
|
@@ -294,7 +294,7 @@ export default {
|
|
|
294
294
|
throw result;
|
|
295
295
|
}
|
|
296
296
|
const tmp = state.root.tmp;
|
|
297
|
-
if (tmp
|
|
297
|
+
if (typeof tmp !== 'object') {
|
|
298
298
|
throw state.root;
|
|
299
299
|
}
|
|
300
300
|
if (tmp.cache !== undefined) {
|
|
@@ -333,8 +333,11 @@ export default {
|
|
|
333
333
|
}
|
|
334
334
|
},
|
|
335
335
|
sandbox: {
|
|
336
|
+
// Virtual `sandbox` is now a pass-through: the function is expected
|
|
337
|
+
// to return a `SandboxResult` directly. Fixtures dictate the result
|
|
338
|
+
// (and `duration`) instead of the runner measuring.
|
|
336
339
|
ok: () => {
|
|
337
|
-
const [_, { result, duration }] = virtual(emptyState)(sandbox(() => 42));
|
|
340
|
+
const [_, { result, duration }] = virtual(emptyState)(sandbox(() => ({ result: ['ok', 42], duration: 0 })));
|
|
338
341
|
if (result[0] !== 'ok') {
|
|
339
342
|
throw result;
|
|
340
343
|
}
|
|
@@ -347,7 +350,7 @@ export default {
|
|
|
347
350
|
},
|
|
348
351
|
error: () => {
|
|
349
352
|
const err = new Error('fail');
|
|
350
|
-
const [_, { result }] = virtual(emptyState)(sandbox(() => {
|
|
353
|
+
const [_, { result }] = virtual(emptyState)(sandbox(() => ({ result: ['error', err], duration: 0 })));
|
|
351
354
|
if (result[0] !== 'error') {
|
|
352
355
|
throw result;
|
|
353
356
|
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { type Vec } from '../../../bit_vec/module.f.ts';
|
|
2
2
|
import { type RunInstance } from '../../mock/module.f.ts';
|
|
3
|
-
import type { NodeOp } from '../module.f.ts';
|
|
3
|
+
import type { Module, NodeOp } from '../module.f.ts';
|
|
4
|
+
/**
|
|
5
|
+
* In-memory JS module entry. When `import_` is called on the path, the
|
|
6
|
+
* function is invoked and its return value is the module value (with a
|
|
7
|
+
* `default` export and optional named exports). Using a function (not a
|
|
8
|
+
* plain value) lets the entry be distinguished from `Vec`/`Dir` at runtime
|
|
9
|
+
* via `typeof === 'function'`, and lets the fixture compute the module on
|
|
10
|
+
* each import for closures/state.
|
|
11
|
+
*/
|
|
12
|
+
export type JsModule = () => Module;
|
|
13
|
+
export type Entity = Vec | Dir | JsModule;
|
|
4
14
|
export type Dir = {
|
|
5
|
-
readonly [name in string]?:
|
|
15
|
+
readonly [name in string]?: Entity;
|
|
6
16
|
};
|
|
7
17
|
export type State = {
|
|
8
18
|
stdout: string;
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import { todo } from "../../../../dev/module.f.js";
|
|
6
|
+
import { assert, todo } from "../../../../dev/module.f.js";
|
|
7
7
|
import { parse } from "../../../../path/module.f.js";
|
|
8
|
+
import { utf8ToString } from "../../../../text/module.f.js";
|
|
8
9
|
import { isVec } from "../../../bit_vec/module.f.js";
|
|
9
10
|
import { error, ok } from "../../../result/module.f.js";
|
|
10
11
|
import { run } from "../../mock/module.f.js";
|
|
@@ -22,7 +23,7 @@ const operation = (op) => {
|
|
|
22
23
|
}
|
|
23
24
|
const [first, ...rest] = path;
|
|
24
25
|
const subDir = dir[first];
|
|
25
|
-
if (subDir
|
|
26
|
+
if (typeof subDir !== 'object') {
|
|
26
27
|
return op(dir, path);
|
|
27
28
|
}
|
|
28
29
|
const [newSubDir, r] = f(subDir, rest);
|
|
@@ -54,11 +55,24 @@ const readFile = readOperation((dir, path) => {
|
|
|
54
55
|
return readFileError;
|
|
55
56
|
}
|
|
56
57
|
const file = dir[path[0]];
|
|
58
|
+
if (typeof file === 'function') {
|
|
59
|
+
throw new Error(`'${path[0]}' is a JsModule; readFile not supported`);
|
|
60
|
+
}
|
|
57
61
|
if (!isVec(file)) {
|
|
58
62
|
return error(`'${path[0]}' is not a file`);
|
|
59
63
|
}
|
|
60
64
|
return ok(file);
|
|
61
65
|
});
|
|
66
|
+
const import_ = readOperation((dir, path) => {
|
|
67
|
+
if (path.length !== 1) {
|
|
68
|
+
return error('no such file');
|
|
69
|
+
}
|
|
70
|
+
const entry = dir[path[0]];
|
|
71
|
+
if (typeof entry !== 'function') {
|
|
72
|
+
return error(`'${path[0]}' is not a JsModule`);
|
|
73
|
+
}
|
|
74
|
+
return ok(entry());
|
|
75
|
+
});
|
|
62
76
|
const writeFileError = error('invalid file');
|
|
63
77
|
const writeFile = (payload) => operation((dir, path) => {
|
|
64
78
|
if (path.length !== 1) {
|
|
@@ -85,7 +99,7 @@ const readdir = (base, recursive) => readOperation((dir, path) => {
|
|
|
85
99
|
if (content === undefined) {
|
|
86
100
|
continue;
|
|
87
101
|
}
|
|
88
|
-
const isFile =
|
|
102
|
+
const isFile = typeof content !== 'object';
|
|
89
103
|
result = [...result, { name, parentPath, isFile }];
|
|
90
104
|
if (!isFile && recursive) {
|
|
91
105
|
result = [...result, ...f(`${parentPath}/${name}`, content)];
|
|
@@ -113,13 +127,12 @@ const rm = operation((dir, path) => {
|
|
|
113
127
|
if (entry === undefined) {
|
|
114
128
|
return [dir, error('no such file')];
|
|
115
129
|
}
|
|
116
|
-
if (
|
|
130
|
+
if (typeof entry === 'object') {
|
|
117
131
|
return [dir, error('is a directory')];
|
|
118
132
|
}
|
|
119
133
|
const { [name]: _, ...rest } = dir;
|
|
120
134
|
return [rest, okVoid];
|
|
121
135
|
});
|
|
122
|
-
const console = (name) => (state, payload) => [{ ...state, [name]: `${state[name]}${payload}\n` }, undefined];
|
|
123
136
|
const map = {
|
|
124
137
|
all: (state, ...a) => {
|
|
125
138
|
let e = [];
|
|
@@ -130,8 +143,6 @@ const map = {
|
|
|
130
143
|
}
|
|
131
144
|
return [state, e];
|
|
132
145
|
},
|
|
133
|
-
error: console('stderr'),
|
|
134
|
-
log: console('stdout'),
|
|
135
146
|
fetch: (state, url) => {
|
|
136
147
|
const result = state.internet[url];
|
|
137
148
|
return result === undefined ? [state, error('not found')] : [state, ok(result)];
|
|
@@ -141,22 +152,25 @@ const map = {
|
|
|
141
152
|
readdir: (state, path, { recursive }) => readdir(path, recursive === true)(state, path),
|
|
142
153
|
writeFile: (state, path, payload) => writeFile(payload)(state, path),
|
|
143
154
|
access,
|
|
144
|
-
import:
|
|
155
|
+
import: import_,
|
|
145
156
|
rm,
|
|
146
157
|
exec: todo,
|
|
147
158
|
createServer: todo,
|
|
148
159
|
listen: todo,
|
|
149
160
|
forever: todo,
|
|
150
161
|
now: (state) => [state, state.epochNs],
|
|
151
|
-
sandbox:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
162
|
+
// Virtual sandbox is a pass-through: the fixture's test function is
|
|
163
|
+
// expected to return a `SandboxResult` directly (encoding pass/fail and a
|
|
164
|
+
// chosen duration), so the handler invokes it without try/catch or clock
|
|
165
|
+
// reads. This makes test outcomes deterministic — fixtures dictate the
|
|
166
|
+
// result instead of the runner measuring real execution. A genuine
|
|
167
|
+
// exception in a fixture propagates loudly as a bug in the fixture.
|
|
168
|
+
// See: issues/156-tf-virtual-tests.md
|
|
169
|
+
sandbox: (state, f) => [state, f()],
|
|
170
|
+
test: todo,
|
|
171
|
+
write: (state, stream, data) => {
|
|
172
|
+
const s = utf8ToString(data);
|
|
173
|
+
return [{ ...state, [stream]: `${state[stream]}${s}` }, undefined];
|
|
160
174
|
},
|
|
161
175
|
};
|
|
162
176
|
export const virtual = run(map);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { Index3, Index5, Array2 } from '../../array/module.f.ts';
|
|
7
7
|
export type Sign = -1 | 0 | 1;
|
|
8
8
|
export type Compare<T> = (_: T) => Sign;
|
|
9
|
+
export type Cmp<T> = (a: T) => Compare<T>;
|
|
9
10
|
export declare const index3: <T>(cmp: Compare<T>) => (value: T) => Index3;
|
|
10
11
|
export declare const index5: <T>(cmp: Compare<T>) => (v2: Array2<T>) => Index5;
|
|
11
12
|
export type Cmp1 = boolean | string | number | bigint;
|
|
@@ -23,3 +24,14 @@ export type Cmp2<A, B> = [
|
|
|
23
24
|
B
|
|
24
25
|
] extends [bigint, bigint] ? bigint : never;
|
|
25
26
|
export declare const cmp: <A extends Cmp1>(a: A) => <B extends Cmp2<A, B>>(b: B) => Sign;
|
|
27
|
+
/**
|
|
28
|
+
* Binary search over `[0, len)`. `probe(mid)` returns the sign of the search
|
|
29
|
+
* key relative to the element at `mid` (`-1` before, `0` at, `1` after). On a
|
|
30
|
+
* hit it returns the matching index; on a miss it returns the converged lower
|
|
31
|
+
* bound `b` (the insertion point), which may equal `len`.
|
|
32
|
+
*
|
|
33
|
+
* `probe` must be monotonic over `[0, len)`: scanning indices left to right its
|
|
34
|
+
* result is non-increasing — a run of `1`s, then `0`s, then `-1`s. A
|
|
35
|
+
* non-monotonic probe yields an undefined position.
|
|
36
|
+
*/
|
|
37
|
+
export declare const bsearch: (len: number) => (probe: (mid: number) => Sign) => number;
|
|
@@ -4,3 +4,36 @@ export const index5 = cmp => ([v0, v1]) => {
|
|
|
4
4
|
return (_0 <= 0 ? _0 + 1 : cmp(v1) + 3);
|
|
5
5
|
};
|
|
6
6
|
export const cmp = (a) => (b) => a < b ? -1 : a > b ? 1 : 0;
|
|
7
|
+
/**
|
|
8
|
+
* Binary search over `[0, len)`. `probe(mid)` returns the sign of the search
|
|
9
|
+
* key relative to the element at `mid` (`-1` before, `0` at, `1` after). On a
|
|
10
|
+
* hit it returns the matching index; on a miss it returns the converged lower
|
|
11
|
+
* bound `b` (the insertion point), which may equal `len`.
|
|
12
|
+
*
|
|
13
|
+
* `probe` must be monotonic over `[0, len)`: scanning indices left to right its
|
|
14
|
+
* result is non-increasing — a run of `1`s, then `0`s, then `-1`s. A
|
|
15
|
+
* non-monotonic probe yields an undefined position.
|
|
16
|
+
*/
|
|
17
|
+
export const bsearch = (len) => (probe) => {
|
|
18
|
+
let b = 0;
|
|
19
|
+
let e = len - 1;
|
|
20
|
+
while (true) {
|
|
21
|
+
if (e < b) {
|
|
22
|
+
return b;
|
|
23
|
+
}
|
|
24
|
+
const mid = b + (e - b >> 1);
|
|
25
|
+
switch (probe(mid)) {
|
|
26
|
+
case -1: {
|
|
27
|
+
e = mid - 1;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
case 0: {
|
|
31
|
+
return mid;
|
|
32
|
+
}
|
|
33
|
+
case 1: {
|
|
34
|
+
b = mid + 1;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const joinTest: () => void;
|
|
2
|
+
export declare const concatTest: () => void;
|
|
3
|
+
export declare const logicalNotTest: () => void;
|
|
4
|
+
export declare const strictEqualTest: () => void;
|
|
5
|
+
export declare const additionTest: () => void;
|
|
6
|
+
export declare const minTest: () => void;
|
|
7
|
+
export declare const maxTest: () => void;
|
|
8
|
+
export declare const incrementTest: () => void;
|
|
9
|
+
export declare const foldToScanTest: () => void;
|
|
10
|
+
export declare const reduceToScanTest: () => void;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { join, concat, logicalNot, strictEqual, addition, min, max, increment, foldToScan, reduceToScan, } from "./module.f.js";
|
|
2
|
+
export const joinTest = () => {
|
|
3
|
+
const result = join(', ')('world')('hello');
|
|
4
|
+
if (result !== 'hello, world') {
|
|
5
|
+
throw result;
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
export const concatTest = () => {
|
|
9
|
+
const result = concat('world')('hello');
|
|
10
|
+
if (result !== 'helloworld') {
|
|
11
|
+
throw result;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const logicalNotTest = () => {
|
|
15
|
+
if (logicalNot(true) !== false) {
|
|
16
|
+
throw 'expected false';
|
|
17
|
+
}
|
|
18
|
+
if (logicalNot(false) !== true) {
|
|
19
|
+
throw 'expected true';
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const strictEqualTest = () => {
|
|
23
|
+
if (!strictEqual(1)(1)) {
|
|
24
|
+
throw 'expected true';
|
|
25
|
+
}
|
|
26
|
+
if (strictEqual(1)(2)) {
|
|
27
|
+
throw 'expected false';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export const additionTest = () => {
|
|
31
|
+
const result = addition(3)(4);
|
|
32
|
+
if (result !== 7) {
|
|
33
|
+
throw result;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export const minTest = () => {
|
|
37
|
+
if (min(3)(5) !== 3) {
|
|
38
|
+
throw 'min(3)(5)';
|
|
39
|
+
}
|
|
40
|
+
if (min(7)(2) !== 2) {
|
|
41
|
+
throw 'min(7)(2)';
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
export const maxTest = () => {
|
|
45
|
+
if (max(3)(5) !== 5) {
|
|
46
|
+
throw 'max(3)(5)';
|
|
47
|
+
}
|
|
48
|
+
if (max(7)(2) !== 7) {
|
|
49
|
+
throw 'max(7)(2)';
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
export const incrementTest = () => {
|
|
53
|
+
if (increment(4) !== 5) {
|
|
54
|
+
throw 'increment(4)';
|
|
55
|
+
}
|
|
56
|
+
if (increment(0) !== 1) {
|
|
57
|
+
throw 'increment(0)';
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
export const foldToScanTest = () => {
|
|
61
|
+
const scan = foldToScan(addition)(0);
|
|
62
|
+
const [v1, scan2] = scan(3);
|
|
63
|
+
if (v1 !== 3) {
|
|
64
|
+
throw v1;
|
|
65
|
+
}
|
|
66
|
+
const [v2] = scan2(4);
|
|
67
|
+
if (v2 !== 7) {
|
|
68
|
+
throw v2;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
export const reduceToScanTest = () => {
|
|
72
|
+
const scan = reduceToScan(addition);
|
|
73
|
+
const [v0, scan2] = scan(10);
|
|
74
|
+
if (v0 !== 10) {
|
|
75
|
+
throw v0;
|
|
76
|
+
}
|
|
77
|
+
const [v1] = scan2(5);
|
|
78
|
+
if (v1 !== 15) {
|
|
79
|
+
throw v1;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { map, toOption } from "./module.f.js";
|
|
1
|
+
import { map, match, toOption } from "./module.f.js";
|
|
2
2
|
export default [
|
|
3
3
|
() => {
|
|
4
4
|
const optionSq = map((v) => v * v);
|
|
@@ -20,5 +20,14 @@ export default [
|
|
|
20
20
|
if (opt2.length !== 0) {
|
|
21
21
|
throw opt2;
|
|
22
22
|
}
|
|
23
|
+
},
|
|
24
|
+
() => {
|
|
25
|
+
const double = match((v) => v * 2)(() => -1);
|
|
26
|
+
if (double(3) !== 6) {
|
|
27
|
+
throw double(3);
|
|
28
|
+
}
|
|
29
|
+
if (double(null) !== -1) {
|
|
30
|
+
throw double(null);
|
|
31
|
+
}
|
|
23
32
|
}
|
|
24
33
|
];
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
import { genericMerge } from "../sorted_list/module.f.js";
|
|
38
38
|
import { next } from "../list/module.f.js";
|
|
39
39
|
import { cmp } from "../number/module.f.js";
|
|
40
|
+
import { bsearch } from "../function/compare/module.f.js";
|
|
40
41
|
const reduceOp = ({ union, equal }) => state => ([aItem, aMax]) => ([bItem, bMax]) => {
|
|
41
42
|
const sign = cmp(aMax)(bMax);
|
|
42
43
|
const min = sign === 1 ? bMax : aMax;
|
|
@@ -59,24 +60,8 @@ const tailReduce = equal => state => tail => {
|
|
|
59
60
|
};
|
|
60
61
|
export const merge = op => genericMerge({ reduceOp: reduceOp(op), tailReduce: tailReduce(op.equal) })(null);
|
|
61
62
|
export const get = def => value => rm => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
let e = len - 1;
|
|
65
|
-
while (true) {
|
|
66
|
-
if (b >= len) {
|
|
67
|
-
return def;
|
|
68
|
-
}
|
|
69
|
-
if (e - b < 0) {
|
|
70
|
-
return rm[b][0];
|
|
71
|
-
}
|
|
72
|
-
const mid = b + (e - b >> 1);
|
|
73
|
-
if (value <= rm[mid][1]) {
|
|
74
|
-
e = mid - 1;
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
b = mid + 1;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
63
|
+
const pos = bsearch(rm.length)(mid => value <= rm[mid][1] ? -1 : 1);
|
|
64
|
+
return pos < rm.length ? rm[pos][0] : def;
|
|
80
65
|
};
|
|
81
66
|
export const fromRange = def => ([a, b]) => v => [[def, a - 1], [v, b]];
|
|
82
67
|
/**
|
|
@@ -51,3 +51,7 @@ export declare const error: <E>(e: E) => Error<E>;
|
|
|
51
51
|
* @returns The value if the result is successful. Otherwise, throws the error.
|
|
52
52
|
*/
|
|
53
53
|
export declare const unwrap: <T, E>([kind, v]: Result<T, E>) => T;
|
|
54
|
+
/**
|
|
55
|
+
* Swaps the `ok` and `error` cases of a result.
|
|
56
|
+
*/
|
|
57
|
+
export declare const invert: <T, E>([k, v]: Result<T, E>) => Result<E, T>;
|
|
@@ -1,18 +1,26 @@
|
|
|
1
|
-
import { error, ok, unwrap } from "./module.f.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { error, ok, unwrap, invert } from "./module.f.js";
|
|
2
|
+
export const example = () => {
|
|
3
|
+
const success = ok(42);
|
|
4
|
+
const failure = error('Something went wrong');
|
|
5
|
+
if (unwrap(success) !== 42) {
|
|
6
|
+
throw 'error';
|
|
7
|
+
}
|
|
8
|
+
const [kind, v] = failure;
|
|
9
|
+
if (kind !== 'error') {
|
|
10
|
+
throw 'error';
|
|
11
|
+
}
|
|
12
|
+
// `v` is inferred as `string` here
|
|
13
|
+
if (v !== 'Something went wrong') {
|
|
14
|
+
throw 'error';
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export const invertTest = () => {
|
|
18
|
+
const [k0, v0] = invert(ok(42));
|
|
19
|
+
if (k0 !== 'error' || v0 !== 42) {
|
|
20
|
+
throw [k0, v0];
|
|
21
|
+
}
|
|
22
|
+
const [k1, v1] = invert(error('oops'));
|
|
23
|
+
if (k1 !== 'ok' || v1 !== 'oops') {
|
|
24
|
+
throw [k1, v1];
|
|
17
25
|
}
|
|
18
26
|
};
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
* @module
|
|
24
24
|
*/
|
|
25
25
|
import type { Primitive, Unknown } from '../../../djs/module.f.ts';
|
|
26
|
-
import { type Info0, type Primitive0, type Struct, type Tuple, type Type } from '../module.f.ts';
|
|
26
|
+
import { type Info0, type Primitive0, type Struct, type Tag1, type Tuple, type Type } from '../module.f.ts';
|
|
27
27
|
import { type Error, type Result as CommonResult } from '../../result/module.f.ts';
|
|
28
28
|
import type { Ts } from '../ts/module.f.ts';
|
|
29
|
+
import { type ReadonlyRecord } from '../../object/module.f.ts';
|
|
29
30
|
/** A path to a sub-value within the validated structure. Each step is an object key or stringified array index. */
|
|
30
31
|
export type Path = readonly string[];
|
|
31
32
|
/** Detailed validation failure: the offending `path` plus a short `message`. */
|
|
@@ -65,6 +66,14 @@ export type Visitor<R> = {
|
|
|
65
66
|
readonly primitive0: (tag: Primitive0) => R;
|
|
66
67
|
readonly unknown: () => R;
|
|
67
68
|
};
|
|
69
|
+
/** Type guard narrowing `Unknown` to a specific container type `C`. */
|
|
70
|
+
export type IsContainer<C extends Unknown> = (value: Unknown) => value is C;
|
|
71
|
+
/** Maps a `Tag1` to its runtime container type. */
|
|
72
|
+
export type Container<K extends Tag1> = K extends 'array' ? ReadonlyArray<Unknown> : ReadonlyRecord<string, Unknown>;
|
|
73
|
+
/** `IsContainer` guard for arrays, shared by `validate` and `parse`. */
|
|
74
|
+
export declare const isArray: IsContainer<ReadonlyArray<Unknown>>;
|
|
75
|
+
/** `IsContainer` guard for records/structs, shared by `validate` and `parse`. */
|
|
76
|
+
export declare const isObject: IsContainer<ReadonlyRecord<string, Unknown>>;
|
|
68
77
|
/**
|
|
69
78
|
* Visits a schema `Type` by dispatching to the matching handler in `v`.
|
|
70
79
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {} from "../module.f.js";
|
|
2
2
|
import { error, ok } from "../../result/module.f.js";
|
|
3
|
-
import { isArray } from "../../array/module.f.js";
|
|
3
|
+
import { isArray as commonIsArray } from "../../array/module.f.js";
|
|
4
|
+
import { isObject as commonIsObject } from "../../object/module.f.js";
|
|
4
5
|
/** Builds an error result with empty path and the given message. */
|
|
5
6
|
export const verror = (message) => error({ path: [], message });
|
|
6
7
|
/** Prepends `key` to the error's path, used to build the path bottom-up. */
|
|
@@ -18,8 +19,12 @@ export const constPrimitiveValidate = (rtti) => value => Object.is(rtti, value)
|
|
|
18
19
|
? ok(value)
|
|
19
20
|
: verror('unexpected value');
|
|
20
21
|
const visitConst = (v) => (c) => typeof c === 'object' && c !== null
|
|
21
|
-
? (
|
|
22
|
+
? (commonIsArray(c) ? v.tuple(c) : v.struct(c))
|
|
22
23
|
: v.constPrimitive(c);
|
|
24
|
+
/** `IsContainer` guard for arrays, shared by `validate` and `parse`. */
|
|
25
|
+
export const isArray = value => commonIsArray(value);
|
|
26
|
+
/** `IsContainer` guard for records/structs, shared by `validate` and `parse`. */
|
|
27
|
+
export const isObject = value => commonIsObject(value);
|
|
23
28
|
/**
|
|
24
29
|
* Visits a schema `Type` by dispatching to the matching handler in `v`.
|
|
25
30
|
*
|
|
@@ -1,71 +1,60 @@
|
|
|
1
1
|
import {} from "../module.f.js";
|
|
2
2
|
import { ok } from "../../result/module.f.js";
|
|
3
|
-
import {
|
|
4
|
-
import { isObject as commonIsObject } from "../../object/module.f.js";
|
|
3
|
+
import {} from "../../object/module.f.js";
|
|
5
4
|
import { find, map as listMap } from "../../list/module.f.js";
|
|
6
|
-
import { constPrimitiveValidate, prependPath, primitive0Validate, verror, visit, } from "../common/module.f.js";
|
|
5
|
+
import { constPrimitiveValidate, isArray, isObject, prependPath, primitive0Validate, verror, visit, } from "../common/module.f.js";
|
|
7
6
|
export {} from "../common/module.f.js";
|
|
8
|
-
const
|
|
9
|
-
// TODO: findIndex breaks type inference,
|
|
10
|
-
// we should replace it with something else.
|
|
11
|
-
const i = results.findIndex(r => r[0] === 'error');
|
|
12
|
-
return i < 0 ? null : [i, results[i]];
|
|
13
|
-
};
|
|
7
|
+
const { entries } = Object;
|
|
14
8
|
const keyedFirstError = (results) => {
|
|
15
9
|
const e = results.find(([, r]) => r[0] === 'error');
|
|
16
10
|
return e === undefined ? null : [e[0], e[1]];
|
|
17
11
|
};
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
? ok(results.map(r => r[1]))
|
|
32
|
-
: prependPath(String(err[0]), err[1]));
|
|
33
|
-
};
|
|
34
|
-
const recordParse = (item) => value => {
|
|
35
|
-
if (!commonIsObject(value)) {
|
|
12
|
+
const arrayRebuild = entries => entries.map(([, v]) => v);
|
|
13
|
+
const recordRebuild = entries => Object.fromEntries(entries);
|
|
14
|
+
/** Drops the `'ok'` tag from each result, yielding the rebuild's `[key, value]` entries. */
|
|
15
|
+
const okEntries = (results) => results.map(([k, r]) => [k, r[1]]);
|
|
16
|
+
/**
|
|
17
|
+
* Builds a parser for `array` or `record` schemas. Mirrors `validate`'s
|
|
18
|
+
* `containerValidate`, but rebuilds a fresh container from each item's parsed
|
|
19
|
+
* result instead of returning the value unchanged. The inner item parser is
|
|
20
|
+
* instantiated lazily (only when the container is non-empty) so recursive
|
|
21
|
+
* schemas don't recurse forever on empty containers.
|
|
22
|
+
*/
|
|
23
|
+
const containerParse = (isContainer, rebuild) => (item) => value => {
|
|
24
|
+
if (!isContainer(value)) {
|
|
36
25
|
return verror('unexpected value');
|
|
37
26
|
}
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
return ok(
|
|
27
|
+
const e = entries(value);
|
|
28
|
+
if (e.length === 0) {
|
|
29
|
+
return ok(rebuild([]));
|
|
41
30
|
}
|
|
42
31
|
const itemParse = parse(item);
|
|
43
|
-
const results =
|
|
32
|
+
const results = e.map(([k, v]) => [k, itemParse(v)]);
|
|
44
33
|
const err = keyedFirstError(results);
|
|
45
34
|
return (err === null
|
|
46
|
-
? ok(
|
|
35
|
+
? ok(rebuild(okEntries(results)))
|
|
47
36
|
: prependPath(err[0], err[1]));
|
|
48
37
|
};
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const structParse = (rtti) => value => {
|
|
60
|
-
if (!commonIsObject(value)) {
|
|
38
|
+
const arrayParse = containerParse(isArray, arrayRebuild);
|
|
39
|
+
const recordParse = containerParse(isObject, recordRebuild);
|
|
40
|
+
/**
|
|
41
|
+
* Builds a parser for `Tuple` or `Struct` const schemas. Mirrors `validate`'s
|
|
42
|
+
* `constContainerValidate`: it iterates the schema's entries (so extra tuple
|
|
43
|
+
* elements and undeclared struct keys are dropped) and rebuilds the result
|
|
44
|
+
* from each parsed item.
|
|
45
|
+
*/
|
|
46
|
+
const constContainerParse = (isContainer, getItem, rebuild) => (rtti) => value => {
|
|
47
|
+
if (!isContainer(value)) {
|
|
61
48
|
return verror('unexpected value');
|
|
62
49
|
}
|
|
63
|
-
const results =
|
|
50
|
+
const results = entries(rtti).map(([k, t]) => [k, parse(t)(getItem(value, k))]);
|
|
64
51
|
const err = keyedFirstError(results);
|
|
65
52
|
return (err === null
|
|
66
|
-
? ok(
|
|
53
|
+
? ok(rebuild(okEntries(results)))
|
|
67
54
|
: prependPath(err[0], err[1]));
|
|
68
55
|
};
|
|
56
|
+
const tupleParse = constContainerParse(isArray, (value, k) => value[Number(k)], arrayRebuild);
|
|
57
|
+
const structParse = constContainerParse(isObject, (value, k) => value[k], recordRebuild);
|
|
69
58
|
const findFirst = find(verror('no match'))((k) => k[0] === 'ok');
|
|
70
59
|
const orParse = (rtti) => value => findFirst(listMap(t => parse(t)(value))(rtti));
|
|
71
60
|
/**
|