functionalscript 0.15.0 → 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.
- package/fs/ci/common/module.f.d.ts +50 -16
- package/fs/ci/common/module.f.js +19 -0
- package/fs/ci/config/module.f.d.ts +1 -1
- package/fs/ci/config/module.f.js +1 -1
- package/fs/ci/module.f.js +1 -1
- package/fs/ci/test.f.js +6 -2
- package/fs/dev/module.f.d.ts +1 -1
- package/fs/dev/module.f.js +25 -16
- package/fs/fjs/module.f.js +5 -3
- package/fs/io/module.d.ts +2 -2
- package/fs/io/module.f.d.ts +3 -15
- package/fs/io/module.f.js +8 -2
- package/fs/io/module.js +2 -2
- package/fs/io/virtual/module.f.js +2 -2
- package/fs/types/effects/node/module.f.d.ts +16 -3
- package/fs/types/effects/node/module.f.js +2 -0
- package/fs/types/effects/node/virtual/module.f.js +11 -0
- package/fs/types/rtti/common/module.f.d.ts +77 -0
- package/fs/types/rtti/common/module.f.js +45 -0
- package/fs/types/rtti/parse/module.f.d.ts +3 -27
- package/fs/types/rtti/parse/module.f.js +12 -20
- package/fs/types/rtti/ts/module.f.d.ts +9 -6
- package/fs/types/rtti/validate/module.f.d.ts +3 -79
- package/fs/types/rtti/validate/module.f.js +13 -36
- package/package.json +1 -1
|
@@ -1,30 +1,63 @@
|
|
|
1
1
|
import { images } from '../config/module.f.ts';
|
|
2
|
+
import { type Ts } from '../../types/rtti/ts/module.f.ts';
|
|
2
3
|
export declare const os: readonly ["ubuntu", "macos", "windows"];
|
|
3
4
|
export type Os = typeof os[number];
|
|
4
5
|
export declare const architecture: readonly ["intel", "arm"];
|
|
5
6
|
export type Architecture = typeof architecture[number];
|
|
6
7
|
export type Image = typeof images[Os][Architecture];
|
|
7
|
-
|
|
8
|
-
readonly run
|
|
9
|
-
readonly uses
|
|
10
|
-
readonly with
|
|
11
|
-
readonly [k: string]: string;
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
export type Job = {
|
|
15
|
-
readonly 'runs-on': Image;
|
|
16
|
-
readonly steps: readonly Step[];
|
|
8
|
+
declare const stepSchema: {
|
|
9
|
+
readonly run: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
10
|
+
readonly uses: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
11
|
+
readonly with: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"record", import("../../types/rtti/module.f.ts").String>, undefined]>;
|
|
17
12
|
};
|
|
18
|
-
|
|
19
|
-
readonly
|
|
13
|
+
declare const jobSchema: {
|
|
14
|
+
readonly 'runs-on': import("../../types/rtti/module.f.ts").String;
|
|
15
|
+
readonly steps: import("../../types/rtti/module.f.ts").Type1<"array", {
|
|
16
|
+
readonly run: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
17
|
+
readonly uses: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
18
|
+
readonly with: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"record", import("../../types/rtti/module.f.ts").String>, undefined]>;
|
|
19
|
+
}>;
|
|
20
20
|
};
|
|
21
|
-
|
|
22
|
-
readonly
|
|
21
|
+
declare const jobsSchema: import("../../types/rtti/module.f.ts").Type1<"record", {
|
|
22
|
+
readonly 'runs-on': import("../../types/rtti/module.f.ts").String;
|
|
23
|
+
readonly steps: import("../../types/rtti/module.f.ts").Type1<"array", {
|
|
24
|
+
readonly run: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
25
|
+
readonly uses: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
26
|
+
readonly with: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"record", import("../../types/rtti/module.f.ts").String>, undefined]>;
|
|
27
|
+
}>;
|
|
28
|
+
}>;
|
|
29
|
+
declare const gitHubActionSchema: {
|
|
30
|
+
readonly name: import("../../types/rtti/module.f.ts").String;
|
|
23
31
|
readonly on: {
|
|
24
|
-
readonly pull_request
|
|
32
|
+
readonly pull_request: import("../../types/rtti/module.f.ts").Or<readonly [{}, undefined]>;
|
|
25
33
|
};
|
|
26
|
-
readonly jobs:
|
|
34
|
+
readonly jobs: import("../../types/rtti/module.f.ts").Type1<"record", {
|
|
35
|
+
readonly 'runs-on': import("../../types/rtti/module.f.ts").String;
|
|
36
|
+
readonly steps: import("../../types/rtti/module.f.ts").Type1<"array", {
|
|
37
|
+
readonly run: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
38
|
+
readonly uses: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
39
|
+
readonly with: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"record", import("../../types/rtti/module.f.ts").String>, undefined]>;
|
|
40
|
+
}>;
|
|
41
|
+
}>;
|
|
27
42
|
};
|
|
43
|
+
export type Step = Ts<typeof stepSchema>;
|
|
44
|
+
export type Job = Ts<typeof jobSchema>;
|
|
45
|
+
export type Jobs = Ts<typeof jobsSchema>;
|
|
46
|
+
export type GitHubAction = Ts<typeof gitHubActionSchema>;
|
|
47
|
+
export declare const parseGitHubAction: import("../../types/rtti/parse/module.f.ts").Parse<{
|
|
48
|
+
readonly name: import("../../types/rtti/module.f.ts").String;
|
|
49
|
+
readonly on: {
|
|
50
|
+
readonly pull_request: import("../../types/rtti/module.f.ts").Or<readonly [{}, undefined]>;
|
|
51
|
+
};
|
|
52
|
+
readonly jobs: import("../../types/rtti/module.f.ts").Type1<"record", {
|
|
53
|
+
readonly 'runs-on': import("../../types/rtti/module.f.ts").String;
|
|
54
|
+
readonly steps: import("../../types/rtti/module.f.ts").Type1<"array", {
|
|
55
|
+
readonly run: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
56
|
+
readonly uses: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
57
|
+
readonly with: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"record", import("../../types/rtti/module.f.ts").String>, undefined]>;
|
|
58
|
+
}>;
|
|
59
|
+
}>;
|
|
60
|
+
}>;
|
|
28
61
|
export type StepType = 'install' | 'test';
|
|
29
62
|
export type MetaStep = {
|
|
30
63
|
readonly type: StepType;
|
|
@@ -42,3 +75,4 @@ export declare const clean: (steps: readonly MetaStep[]) => readonly MetaStep[];
|
|
|
42
75
|
export declare const toSteps: (m: readonly MetaStep[]) => readonly Step[];
|
|
43
76
|
export declare const ubuntu: (ms: readonly MetaStep[]) => Job;
|
|
44
77
|
export declare const findTgz: (v: Os) => "(Get-ChildItem *.tgz).FullName" | "./*.tgz";
|
|
78
|
+
export {};
|
package/fs/ci/common/module.f.js
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { images, rust } from "../config/module.f.js";
|
|
2
|
+
import { option, array, record, string } from "../../types/rtti/module.f.js";
|
|
3
|
+
import {} from "../../types/rtti/ts/module.f.js";
|
|
4
|
+
import { parse as rttiParse } from "../../types/rtti/parse/module.f.js";
|
|
2
5
|
export const os = ['ubuntu', 'macos', 'windows'];
|
|
3
6
|
export const architecture = ['intel', 'arm'];
|
|
7
|
+
const stepSchema = {
|
|
8
|
+
run: option(string),
|
|
9
|
+
uses: option(string),
|
|
10
|
+
with: option(record(string))
|
|
11
|
+
};
|
|
12
|
+
const jobSchema = {
|
|
13
|
+
'runs-on': string,
|
|
14
|
+
steps: array(stepSchema)
|
|
15
|
+
};
|
|
16
|
+
const jobsSchema = record(jobSchema);
|
|
17
|
+
const gitHubActionSchema = {
|
|
18
|
+
name: string,
|
|
19
|
+
on: { pull_request: option({}) },
|
|
20
|
+
jobs: jobsSchema
|
|
21
|
+
};
|
|
22
|
+
export const parseGitHubAction = rttiParse(gitHubActionSchema);
|
|
4
23
|
export const install = (step) => ({ type: 'install', step });
|
|
5
24
|
export const test = (step) => ({ type: 'test', step });
|
|
6
25
|
export const clean = (steps) => [
|
package/fs/ci/config/module.f.js
CHANGED
|
@@ -31,4 +31,4 @@ export const wasmtime = '44.0.1';
|
|
|
31
31
|
// https://github.com/wasmerio/wasmer/releases
|
|
32
32
|
export const wasmer = '7.1.0';
|
|
33
33
|
// https://www.npmjs.com/package/@typescript/native-preview?activeTab=versions
|
|
34
|
-
export const tsgo = '7.0.0-dev.
|
|
34
|
+
export const tsgo = '7.0.0-dev.20260517.1';
|
package/fs/ci/module.f.js
CHANGED
|
@@ -51,7 +51,7 @@ const defaultEffect = ci({
|
|
|
51
51
|
denoExtra: [
|
|
52
52
|
test({ run: 'deno task fjs compile issues/demo/data/tree.json _tree.f.js' }),
|
|
53
53
|
test({ run: 'deno task fjs t' }),
|
|
54
|
-
test({ run: 'deno publish --dry-run' }),
|
|
54
|
+
test({ run: 'deno publish --dry-run --allow-slow-types' }),
|
|
55
55
|
],
|
|
56
56
|
bunExtra: [
|
|
57
57
|
test({ run: 'bun ./fs/fjs/module.ts t' }),
|
package/fs/ci/test.f.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { ci } from "./module.f.js";
|
|
2
2
|
import { utf8ToString } from "../text/module.f.js";
|
|
3
3
|
import { isVec } from "../types/bit_vec/module.f.js";
|
|
4
|
-
import { test } from "./common/module.f.js";
|
|
4
|
+
import { test, parseGitHubAction } from "./common/module.f.js";
|
|
5
5
|
import { assert } from "../dev/module.f.js";
|
|
6
6
|
import { emptyState, virtual } from "../types/effects/node/virtual/module.f.js";
|
|
7
|
+
import {} from "../types/rtti/ts/module.f.js";
|
|
8
|
+
import { parse as jsonParse } from "../json/module.f.js";
|
|
9
|
+
import { unwrap } from "../types/result/module.f.js";
|
|
10
|
+
// type Gha = Ts<typeof gitHubActionSchema>
|
|
7
11
|
const hasRun = (cmd) => (gha) => Object.values(gha.jobs).some(job => job.steps.some(step => step.run?.includes(cmd)));
|
|
8
12
|
const hasRunInJob = (jobId, cmd) => (gha) => gha.jobs[jobId]?.steps.some(step => step.run?.includes(cmd)) ?? false;
|
|
9
13
|
const githubState = {
|
|
@@ -19,7 +23,7 @@ const run = (rust, nodeExtra = () => []) => {
|
|
|
19
23
|
assert(workflows !== undefined && !isVec(workflows), workflows);
|
|
20
24
|
const file = workflows['ci.yml'];
|
|
21
25
|
assert(isVec(file), file);
|
|
22
|
-
return
|
|
26
|
+
return unwrap(parseGitHubAction(jsonParse(utf8ToString(file))));
|
|
23
27
|
};
|
|
24
28
|
export default {
|
|
25
29
|
rust: () => {
|
package/fs/dev/module.f.d.ts
CHANGED
|
@@ -17,6 +17,6 @@ export type ModuleMap = {
|
|
|
17
17
|
readonly [k in string]: Module;
|
|
18
18
|
};
|
|
19
19
|
export declare const env: (io: Io) => (v: string) => string | undefined;
|
|
20
|
-
export declare const
|
|
20
|
+
export declare const allFiles: (s: string) => Effect<Readdir | All, readonly string[]>;
|
|
21
21
|
export declare const loadModuleMap: (io: Io) => Promise<ModuleMap>;
|
|
22
22
|
export declare const index4: NodeProgram;
|
package/fs/dev/module.f.js
CHANGED
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { fromIo } from "../io/module.f.js";
|
|
7
7
|
import { updateVersion } from "./version/module.f.js";
|
|
8
|
-
import { all, both, readdir, readFile, writeFile } from "../types/effects/node/module.f.js";
|
|
8
|
+
import { access, all, both, import_, readdir, readFile, writeFile } from "../types/effects/node/module.f.js";
|
|
9
9
|
import { utf8, utf8ToString } from "../text/module.f.js";
|
|
10
10
|
import { unwrap } from "../types/result/module.f.js";
|
|
11
11
|
import { begin, pure } from "../types/effects/module.f.js";
|
|
12
|
+
import { parse as jsonParse } from "../json/module.f.js";
|
|
13
|
+
import { record, unknown as rttiUnknown } from "../types/rtti/module.f.js";
|
|
14
|
+
import { parse as rttiParse } from "../types/rtti/parse/module.f.js";
|
|
12
15
|
export const todo = () => { throw 'not implemented'; };
|
|
13
16
|
export const assert = (v, msg = 'assertion failed') => {
|
|
14
17
|
if (!v)
|
|
@@ -22,7 +25,7 @@ export const env = ({ process: { env } }) => a => {
|
|
|
22
25
|
typeof r.get === 'function' ? r.get() :
|
|
23
26
|
r.value;
|
|
24
27
|
};
|
|
25
|
-
export const
|
|
28
|
+
export const allFiles = (s) => {
|
|
26
29
|
const load = (p) => begin
|
|
27
30
|
.step(() => readdir(p, {}))
|
|
28
31
|
.step(d => {
|
|
@@ -49,28 +52,34 @@ export const allFiles2 = (s) => {
|
|
|
49
52
|
.step(v => pure(v.flat()));
|
|
50
53
|
return load(s);
|
|
51
54
|
};
|
|
52
|
-
const
|
|
55
|
+
const loadFile = (f) => {
|
|
56
|
+
const doImport = import_(f).step(r => pure([[f, unwrap(r)]]));
|
|
57
|
+
if (f.endsWith('.f.js')) {
|
|
58
|
+
return doImport;
|
|
59
|
+
}
|
|
60
|
+
if (f.endsWith('.f.ts')) {
|
|
61
|
+
return access(f.substring(0, f.length - 3) + '.js')
|
|
62
|
+
.step(r => r[0] === 'ok' ? pure([]) : doImport);
|
|
63
|
+
}
|
|
64
|
+
return pure([]);
|
|
65
|
+
};
|
|
53
66
|
export const loadModuleMap = async (io) => {
|
|
54
|
-
const {
|
|
55
|
-
|
|
56
|
-
const initCwd = env(io)('INIT_CWD');
|
|
67
|
+
const { process: { env } } = io;
|
|
68
|
+
const initCwd = env['INIT_CWD'];
|
|
57
69
|
const s = initCwd === undefined ? '.' : `${initCwd.replaceAll('\\', '/')}`;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
map = [...map, [f, source]];
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return Object.fromEntries(map.toSorted(cmp));
|
|
70
|
+
const effect = allFiles(s)
|
|
71
|
+
.step(files => all(...files.map(loadFile)))
|
|
72
|
+
.step(entries => pure(Object.fromEntries(entries.flat().toSorted(cmp))));
|
|
73
|
+
return fromIo(io)(effect);
|
|
66
74
|
};
|
|
67
75
|
const denoJson = './deno.json';
|
|
76
|
+
const parseDenoJson = rttiParse(record(rttiUnknown));
|
|
68
77
|
const index2 = begin
|
|
69
78
|
.step(() => updateVersion)
|
|
70
79
|
.step(() => readFile(denoJson))
|
|
71
|
-
.step(v => pure(
|
|
80
|
+
.step(v => pure(unwrap(parseDenoJson(jsonParse(utf8ToString(unwrap(v)))))));
|
|
72
81
|
const allFiles2aa = begin
|
|
73
|
-
.step(() =>
|
|
82
|
+
.step(() => allFiles('.'))
|
|
74
83
|
.step(files => {
|
|
75
84
|
const list = files.filter(v => v.endsWith('/module.f.ts') || v.endsWith('/module.ts'));
|
|
76
85
|
const exportsA = list.map(v => [v, `./${v.substring(2)}`]);
|
package/fs/fjs/module.f.js
CHANGED
|
@@ -9,7 +9,9 @@ import { main as testMain } from "../dev/tf/module.f.js";
|
|
|
9
9
|
import { main as casMain } from "../cas/module.f.js";
|
|
10
10
|
export const main = async (io) => {
|
|
11
11
|
const { error } = io.console;
|
|
12
|
-
const
|
|
12
|
+
const { process, asyncImport } = io;
|
|
13
|
+
const { env } = process;
|
|
14
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
13
15
|
const eRun = fromIo(io);
|
|
14
16
|
switch (command) {
|
|
15
17
|
case 'test':
|
|
@@ -24,8 +26,8 @@ export const main = async (io) => {
|
|
|
24
26
|
case 'run':
|
|
25
27
|
case 'r':
|
|
26
28
|
const [file, ...args] = rest;
|
|
27
|
-
const m = await
|
|
28
|
-
return eRun(m.default(args));
|
|
29
|
+
const m = await asyncImport(file);
|
|
30
|
+
return eRun(m.default(args, env));
|
|
29
31
|
case undefined:
|
|
30
32
|
error('Error: command is required');
|
|
31
33
|
return Promise.resolve(1);
|
package/fs/io/module.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Io, type
|
|
2
|
-
import type { NodeProgram } from '../types/effects/node/module.f.ts';
|
|
1
|
+
import { type Io, type Run } from './module.f.ts';
|
|
2
|
+
import type { Module, NodeProgram } from '../types/effects/node/module.f.ts';
|
|
3
3
|
export declare const asyncImport: (v: string) => Promise<Module>;
|
|
4
4
|
export declare const io: Io;
|
|
5
5
|
export declare const legacyRun: Run;
|
package/fs/io/module.f.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Effect } from '../types/effects/module.f.ts';
|
|
2
|
-
import type { Headers, NodeOp } from '../types/effects/node/module.f.ts';
|
|
2
|
+
import type { Headers, Module, NodeOp, Env } from '../types/effects/node/module.f.ts';
|
|
3
3
|
import { type Result } from '../types/result/module.f.ts';
|
|
4
4
|
/**
|
|
5
5
|
* Represents a directory entry (file or directory) in the filesystem
|
|
@@ -31,7 +31,6 @@ export type Fs = {
|
|
|
31
31
|
readonly writeSync: (fd: number, s: string) => void;
|
|
32
32
|
readonly writeFileSync: (file: string, data: Uint8Array) => void;
|
|
33
33
|
readonly readFileSync: (path: string) => Uint8Array | null;
|
|
34
|
-
readonly existsSync: (path: string) => boolean;
|
|
35
34
|
readonly promises: {
|
|
36
35
|
readonly readFile: (path: string) => Promise<Uint8Array>;
|
|
37
36
|
readonly writeFile: (path: string, data: Uint8Array) => Promise<void>;
|
|
@@ -39,6 +38,7 @@ export type Fs = {
|
|
|
39
38
|
readonly rm: (path: string, options?: RmOptions) => Promise<void>;
|
|
40
39
|
readonly mkdir: (path: string, options?: MakeDirectoryOptions) => Promise<string | undefined>;
|
|
41
40
|
readonly copyFile: (src: string, dest: string) => Promise<void>;
|
|
41
|
+
readonly access: (path: string) => Promise<void>;
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
44
|
/**
|
|
@@ -49,12 +49,6 @@ export type Console = {
|
|
|
49
49
|
readonly log: (...d: unknown[]) => void;
|
|
50
50
|
readonly error: (...d: unknown[]) => void;
|
|
51
51
|
};
|
|
52
|
-
/**
|
|
53
|
-
* Represents an ES module with a default export
|
|
54
|
-
*/
|
|
55
|
-
export type Module = {
|
|
56
|
-
readonly default: unknown;
|
|
57
|
-
};
|
|
58
52
|
/**
|
|
59
53
|
* High-resolution time measurement interface
|
|
60
54
|
* @see https://nodejs.org/api/perf_hooks.html#performance-now
|
|
@@ -118,12 +112,6 @@ export type Io = {
|
|
|
118
112
|
readonly http: Http;
|
|
119
113
|
readonly childProcess: ChildProcess;
|
|
120
114
|
};
|
|
121
|
-
/**
|
|
122
|
-
* The environment variables.
|
|
123
|
-
*/
|
|
124
|
-
export type Env = {
|
|
125
|
-
readonly [k: string]: string | undefined;
|
|
126
|
-
};
|
|
127
115
|
export type App = (io: Io) => Promise<number>;
|
|
128
116
|
export type Run = (f: App) => Promise<never>;
|
|
129
117
|
/**
|
|
@@ -132,4 +120,4 @@ export type Run = (f: App) => Promise<never>;
|
|
|
132
120
|
*/
|
|
133
121
|
export declare const run: (io: Io) => Run;
|
|
134
122
|
export type EffectToPromise = <T>(effect: Effect<NodeOp, T>) => Promise<T>;
|
|
135
|
-
export declare const fromIo: ({ console: { error, log }, fs: { promises: { mkdir, readFile, readdir, writeFile, rm } }, fetch, http: { createServer }, childProcess, }: Io) => EffectToPromise;
|
|
123
|
+
export declare const fromIo: ({ console: { error, log }, fs: { promises: { mkdir, readFile, readdir, writeFile, rm, access } }, fetch, http: { createServer }, childProcess, asyncImport, }: Io) => EffectToPromise;
|
package/fs/io/module.f.js
CHANGED
|
@@ -35,7 +35,7 @@ const collect = async (v) => {
|
|
|
35
35
|
}
|
|
36
36
|
return result;
|
|
37
37
|
};
|
|
38
|
-
export const fromIo = ({ console: { error, log }, fs: { promises: { mkdir, readFile, readdir, writeFile, rm } }, fetch, http: { createServer }, childProcess, }) => {
|
|
38
|
+
export const fromIo = ({ console: { error, log }, fs: { promises: { mkdir, readFile, readdir, writeFile, rm, access } }, fetch, http: { createServer }, childProcess, asyncImport, }) => {
|
|
39
39
|
const result = asyncRun({
|
|
40
40
|
all: async (...effects) => await Promise.all(effects.map(result)),
|
|
41
41
|
error: async (message) => error(message),
|
|
@@ -50,9 +50,15 @@ export const fromIo = ({ console: { error, log }, fs: { promises: { mkdir, readF
|
|
|
50
50
|
mkdir: (...p) => tc(async () => { await mkdir(...p); }),
|
|
51
51
|
readFile: path => tc(async () => toVec(await readFile(path))),
|
|
52
52
|
readdir: (path, r) => tc(async () => (await readdir(path, { ...r, withFileTypes: true }))
|
|
53
|
-
.map(v => ({
|
|
53
|
+
.map(v => ({
|
|
54
|
+
name: v.name,
|
|
55
|
+
parentPath: normalize(v.parentPath),
|
|
56
|
+
isFile: v.isFile()
|
|
57
|
+
}))),
|
|
54
58
|
writeFile: (path, data) => tc(() => writeFile(path, fromVec(data))),
|
|
55
59
|
rm: path => tc(() => rm(path)),
|
|
60
|
+
access: path => tc(() => access(path)),
|
|
61
|
+
import: path => tc(() => asyncImport(path)),
|
|
56
62
|
exec: (command, stdin) => new Promise(resolve => {
|
|
57
63
|
const child = childProcess.exec(command, (e, stdout, stderr) => resolve(e !== null ? ['error', e] : ok({ stdout, stderr })));
|
|
58
64
|
child.stdin?.end(stdin);
|
package/fs/io/module.js
CHANGED
|
@@ -48,8 +48,8 @@ export const io = {
|
|
|
48
48
|
export const legacyRun = run(io);
|
|
49
49
|
export const ioRun = (io) => {
|
|
50
50
|
const r = fromIo(io);
|
|
51
|
-
const { argv } = io.process;
|
|
52
|
-
return p => r(p(argv));
|
|
51
|
+
const { argv, env } = io.process;
|
|
52
|
+
return p => r(p(argv, env));
|
|
53
53
|
};
|
|
54
54
|
const effectRun = ioRun(io);
|
|
55
55
|
export default effectRun;
|
|
@@ -6,10 +6,9 @@ export const createVirtualIo = (files) => ({
|
|
|
6
6
|
error: (..._d) => { }
|
|
7
7
|
},
|
|
8
8
|
fs: {
|
|
9
|
-
writeSync: (
|
|
9
|
+
writeSync: (_fd, _s) => { },
|
|
10
10
|
writeFileSync: (_file, _data) => { },
|
|
11
11
|
readFileSync: (path) => { return at(path)(files); },
|
|
12
|
-
existsSync: (path) => { return at(path)(files) !== null; },
|
|
13
12
|
promises: {
|
|
14
13
|
readdir: (_path) => Promise.resolve([]),
|
|
15
14
|
readFile: (_path) => Promise.resolve(new Uint8Array()),
|
|
@@ -17,6 +16,7 @@ export const createVirtualIo = (files) => ({
|
|
|
17
16
|
rm: (_path, _options) => Promise.resolve(),
|
|
18
17
|
mkdir: (_path, _options) => Promise.resolve(undefined),
|
|
19
18
|
copyFile: (_src, _dest) => Promise.resolve(),
|
|
19
|
+
access: (_path) => Promise.resolve(),
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
process: {
|
|
@@ -46,7 +46,9 @@ export type ExecResult = {
|
|
|
46
46
|
};
|
|
47
47
|
export type Exec = readonly ['exec', (command: string, stdin?: string) => IoResult<ExecResult>];
|
|
48
48
|
export declare const exec: Func<Exec>;
|
|
49
|
-
export type
|
|
49
|
+
export type Access = readonly ['access', (path: string) => IoResult<void>];
|
|
50
|
+
export declare const access: Func<Access>;
|
|
51
|
+
export type Fs = Mkdir | ReadFile | Readdir | WriteFile | Rm | Exec | Access;
|
|
50
52
|
export type Error = ['error', (message: string) => void];
|
|
51
53
|
export declare const error: Func<Error>;
|
|
52
54
|
export type Log = ['log', (message: string) => void];
|
|
@@ -75,7 +77,18 @@ export declare const listen: Func<Listen>;
|
|
|
75
77
|
export type Http = CreateServer | Listen;
|
|
76
78
|
export type Forever = ['forever', () => never];
|
|
77
79
|
export declare const forever: Func<Forever>;
|
|
78
|
-
export type
|
|
80
|
+
export type Module = {
|
|
81
|
+
readonly default: unknown;
|
|
82
|
+
};
|
|
83
|
+
export type Import = ['import', (path: string) => IoResult<Module>];
|
|
84
|
+
export declare const import_: Func<Import>;
|
|
85
|
+
export type NodeOp = All | Fetch | Console | Fs | Http | Forever | Import;
|
|
79
86
|
export type NodeEffect<T> = Effect<NodeOp, T>;
|
|
80
87
|
export type NodeOperationMap = ToAsyncOperationMap<NodeOp>;
|
|
81
|
-
|
|
88
|
+
/**
|
|
89
|
+
* The environment variables.
|
|
90
|
+
*/
|
|
91
|
+
export type Env = {
|
|
92
|
+
readonly [k: string]: string | undefined;
|
|
93
|
+
};
|
|
94
|
+
export type NodeProgram = (argv: readonly string[], env: Env) => Effect<NodeOp, number>;
|
|
@@ -16,8 +16,10 @@ export const readdir = do_('readdir');
|
|
|
16
16
|
export const writeFile = do_('writeFile');
|
|
17
17
|
export const rm = do_('rm');
|
|
18
18
|
export const exec = do_('exec');
|
|
19
|
+
export const access = do_('access');
|
|
19
20
|
export const error = do_('error');
|
|
20
21
|
export const log = do_('log');
|
|
21
22
|
export const createServer = do_('createServer');
|
|
22
23
|
export const listen = do_('listen');
|
|
23
24
|
export const forever = do_('forever');
|
|
25
|
+
export const import_ = do_('import');
|
|
@@ -94,6 +94,15 @@ const readdir = (base, recursive) => readOperation((dir, path) => {
|
|
|
94
94
|
};
|
|
95
95
|
return ok(f(base, dir));
|
|
96
96
|
});
|
|
97
|
+
const access = readOperation((dir, path) => {
|
|
98
|
+
if (path.length === 0) {
|
|
99
|
+
return okVoid;
|
|
100
|
+
}
|
|
101
|
+
if (path.length !== 1) {
|
|
102
|
+
return error('no such file or directory');
|
|
103
|
+
}
|
|
104
|
+
return dir[path[0]] !== undefined ? okVoid : error('no such file or directory');
|
|
105
|
+
});
|
|
97
106
|
const rm = operation((dir, path) => {
|
|
98
107
|
if (path.length !== 1) {
|
|
99
108
|
return [dir, error('invalid path')];
|
|
@@ -130,6 +139,8 @@ const map = {
|
|
|
130
139
|
readFile,
|
|
131
140
|
readdir: (state, path, { recursive }) => readdir(path, recursive === true)(state, path),
|
|
132
141
|
writeFile: (state, path, payload) => writeFile(payload)(state, path),
|
|
142
|
+
access,
|
|
143
|
+
import: todo,
|
|
133
144
|
rm,
|
|
134
145
|
exec: todo,
|
|
135
146
|
createServer: todo,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared kernel for RTTI consumers (`validate`, `parse`).
|
|
3
|
+
*
|
|
4
|
+
* Both consumers traverse the same schema shape and produce the same
|
|
5
|
+
* `Result<T, ValidationError>` outcome. Only the per-variant handling differs
|
|
6
|
+
* — `validate` keeps the original value, `parse` constructs a fresh one.
|
|
7
|
+
*
|
|
8
|
+
* This module hosts the parts that do not differ:
|
|
9
|
+
*
|
|
10
|
+
* - The error shape (`ValidationError`, `Path`) and path bookkeeping
|
|
11
|
+
* (`verror`, `prependPath`).
|
|
12
|
+
* - Primitive checks (`primitive0Validate`, `constPrimitiveValidate`).
|
|
13
|
+
* - The `Validate<T>`/`Result<T>` signatures — `parse` uses the same shape.
|
|
14
|
+
* - `visit`: a visitor over the `Type` ADT. Callers supply a `Visitor<R>`
|
|
15
|
+
* with one handler per variant; `visit(v)(rtti)` recognizes `rtti` and
|
|
16
|
+
* calls the matching handler. Both consumers compose their top-level
|
|
17
|
+
* function from a visitor.
|
|
18
|
+
*
|
|
19
|
+
* Keeping the kernel here also removes `parse`'s incidental dependency on
|
|
20
|
+
* `validate` and gives future schema-driven consumers (e.g. the data form
|
|
21
|
+
* sketched in [i143](../../../../issues/README.md)) a stable shared base.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
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';
|
|
27
|
+
import { type Error, type Result as CommonResult } from '../../result/module.f.ts';
|
|
28
|
+
import type { Ts } from '../ts/module.f.ts';
|
|
29
|
+
/** A path to a sub-value within the validated structure. Each step is an object key or stringified array index. */
|
|
30
|
+
export type Path = readonly string[];
|
|
31
|
+
/** Detailed validation failure: the offending `path` plus a short `message`. */
|
|
32
|
+
export type ValidationError = {
|
|
33
|
+
readonly path: Path;
|
|
34
|
+
readonly message: string;
|
|
35
|
+
};
|
|
36
|
+
/** Validation/parse result: either the typed value or a `ValidationError`. */
|
|
37
|
+
export type Result<T extends Type> = CommonResult<Ts<T>, ValidationError>;
|
|
38
|
+
/** A function that checks an unknown value against schema `T`. Shared by `validate` and `parse`. */
|
|
39
|
+
export type Validate<T extends Type> = (value: Unknown) => Result<T>;
|
|
40
|
+
/** Builds an error result with empty path and the given message. */
|
|
41
|
+
export declare const verror: (message: string) => Error<ValidationError>;
|
|
42
|
+
/** Prepends `key` to the error's path, used to build the path bottom-up. */
|
|
43
|
+
export declare const prependPath: (key: string, r: Error<ValidationError>) => Error<ValidationError>;
|
|
44
|
+
/** Validates a `Tag0` primitive schema using `typeof`. */
|
|
45
|
+
export declare const primitive0Validate: <K extends Primitive0, T extends Info0<K>>(tag: K) => Validate<T>;
|
|
46
|
+
/**
|
|
47
|
+
* Validates a primitive `Const` schema using `Object.is` (SameValue).
|
|
48
|
+
*
|
|
49
|
+
* `Object.is` is used instead of `===` so that:
|
|
50
|
+
* - `NaN` const schemas match `NaN` values (`===` would always fail because `NaN !== NaN`).
|
|
51
|
+
* - `+0` and `-0` are treated as distinct const values.
|
|
52
|
+
*/
|
|
53
|
+
export declare const constPrimitiveValidate: <T extends Primitive>(rtti: T) => Validate<T>;
|
|
54
|
+
/**
|
|
55
|
+
* One handler per `Type` variant. Both `validate` and `parse` provide a
|
|
56
|
+
* `Visitor<R>` where `R` is the per-variant output (e.g. a `Validate` function).
|
|
57
|
+
*/
|
|
58
|
+
export type Visitor<R> = {
|
|
59
|
+
readonly tuple: (rtti: Tuple) => R;
|
|
60
|
+
readonly struct: (rtti: Struct) => R;
|
|
61
|
+
readonly array: (item: Type) => R;
|
|
62
|
+
readonly record: (item: Type) => R;
|
|
63
|
+
readonly or: (variants: readonly Type[]) => R;
|
|
64
|
+
readonly constPrimitive: (p: Primitive) => R;
|
|
65
|
+
readonly primitive0: (tag: Primitive0) => R;
|
|
66
|
+
readonly unknown: () => R;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Visits a schema `Type` by dispatching to the matching handler in `v`.
|
|
70
|
+
*
|
|
71
|
+
* - `Thunk` schemas are evaluated once to read the `Info` descriptor, then
|
|
72
|
+
* routed by tag (`'const'`, `'array'`, `'record'`, `'unknown'`, `'or'`,
|
|
73
|
+
* or a `Tag0` primitive).
|
|
74
|
+
* - `Const` schemas (primitives, tuples, structs) are routed directly to
|
|
75
|
+
* `tuple`, `struct`, or `constPrimitive`.
|
|
76
|
+
*/
|
|
77
|
+
export declare const visit: <R>(v: Visitor<R>) => (rtti: Type) => R;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {} from "../module.f.js";
|
|
2
|
+
import { error, ok } from "../../result/module.f.js";
|
|
3
|
+
import { isArray } from "../../array/module.f.js";
|
|
4
|
+
/** Builds an error result with empty path and the given message. */
|
|
5
|
+
export const verror = (message) => error({ path: [], message });
|
|
6
|
+
/** Prepends `key` to the error's path, used to build the path bottom-up. */
|
|
7
|
+
export const prependPath = (key, r) => error({ path: [key, ...r[1].path], message: r[1].message });
|
|
8
|
+
/** Validates a `Tag0` primitive schema using `typeof`. */
|
|
9
|
+
export const primitive0Validate = (tag) => value => typeof value === tag ? ok(value) : verror('unexpected value');
|
|
10
|
+
/**
|
|
11
|
+
* Validates a primitive `Const` schema using `Object.is` (SameValue).
|
|
12
|
+
*
|
|
13
|
+
* `Object.is` is used instead of `===` so that:
|
|
14
|
+
* - `NaN` const schemas match `NaN` values (`===` would always fail because `NaN !== NaN`).
|
|
15
|
+
* - `+0` and `-0` are treated as distinct const values.
|
|
16
|
+
*/
|
|
17
|
+
export const constPrimitiveValidate = (rtti) => value => Object.is(rtti, value)
|
|
18
|
+
? ok(value)
|
|
19
|
+
: verror('unexpected value');
|
|
20
|
+
const visitConst = (v) => (c) => typeof c === 'object' && c !== null
|
|
21
|
+
? (isArray(c) ? v.tuple(c) : v.struct(c))
|
|
22
|
+
: v.constPrimitive(c);
|
|
23
|
+
/**
|
|
24
|
+
* Visits a schema `Type` by dispatching to the matching handler in `v`.
|
|
25
|
+
*
|
|
26
|
+
* - `Thunk` schemas are evaluated once to read the `Info` descriptor, then
|
|
27
|
+
* routed by tag (`'const'`, `'array'`, `'record'`, `'unknown'`, `'or'`,
|
|
28
|
+
* or a `Tag0` primitive).
|
|
29
|
+
* - `Const` schemas (primitives, tuples, structs) are routed directly to
|
|
30
|
+
* `tuple`, `struct`, or `constPrimitive`.
|
|
31
|
+
*/
|
|
32
|
+
export const visit = (v) => (rtti) => {
|
|
33
|
+
if (typeof rtti === 'function') {
|
|
34
|
+
const [tag, ...value] = rtti();
|
|
35
|
+
switch (tag) {
|
|
36
|
+
case 'const': return visitConst(v)(value[0]);
|
|
37
|
+
case 'array': return v.array(value[0]);
|
|
38
|
+
case 'record': return v.record(value[0]);
|
|
39
|
+
case 'unknown': return v.unknown();
|
|
40
|
+
case 'or': return v.or(value);
|
|
41
|
+
}
|
|
42
|
+
return v.primitive0(tag);
|
|
43
|
+
}
|
|
44
|
+
return visitConst(v)(rtti);
|
|
45
|
+
};
|
|
@@ -1,32 +1,8 @@
|
|
|
1
1
|
import { type Type } from '../module.f.ts';
|
|
2
|
-
import { type Result as
|
|
3
|
-
export type
|
|
2
|
+
import { type Result as CommonValidateResult, type Validate } from '../common/module.f.ts';
|
|
3
|
+
export { type Path, type ValidationError } from '../common/module.f.ts';
|
|
4
4
|
/** Parse result: either the freshly constructed typed value or a `ValidationError`. */
|
|
5
|
-
export type Result<T extends Type> =
|
|
5
|
+
export type Result<T extends Type> = CommonValidateResult<T>;
|
|
6
6
|
/** A function that parses an unknown value into the schema `T`. */
|
|
7
7
|
export type Parse<T extends Type> = Validate<T>;
|
|
8
|
-
/**
|
|
9
|
-
* Creates a parser function for the given RTTI schema.
|
|
10
|
-
*
|
|
11
|
-
* The returned function takes an unknown value and returns either
|
|
12
|
-
* `['ok', newValue]` containing a freshly constructed value matching the schema,
|
|
13
|
-
* or `['error', { path, message }]` describing the failure location.
|
|
14
|
-
*
|
|
15
|
-
* @param rtti - A schema `Type`: a `Thunk` for tag-based schemas, or a `Const`
|
|
16
|
-
* (primitive literal, tuple, or struct) for exact-value schemas.
|
|
17
|
-
* @returns A `Parse<T>` function.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```ts
|
|
21
|
-
* const p = parse(array(number))
|
|
22
|
-
* p([1, 2, 3]) // ['ok', [1, 2, 3]] (a new array)
|
|
23
|
-
* p([1, 'two']) // ['error', { path: ['1'], message: 'unexpected value' }]
|
|
24
|
-
*
|
|
25
|
-
* // tuples are closed: extra elements are dropped
|
|
26
|
-
* parse([number, number] as const)([1, 2, 3]) // ['ok', [1, 2]]
|
|
27
|
-
*
|
|
28
|
-
* // structs drop undeclared keys
|
|
29
|
-
* parse({ a: number } as const)({ a: 1, b: 2 }) // ['ok', { a: 1 }]
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
8
|
export declare const parse: <T extends Type>(rtti: T) => Parse<T>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {} from "../module.f.js";
|
|
2
2
|
import { ok } from "../../result/module.f.js";
|
|
3
|
-
import { constPrimitiveValidate, prependPath, primitive0Validate, verror, } from "../validate/module.f.js";
|
|
4
3
|
import { isArray as commonIsArray } from "../../array/module.f.js";
|
|
5
4
|
import { isObject as commonIsObject } from "../../object/module.f.js";
|
|
6
5
|
import { find, map as listMap } from "../../list/module.f.js";
|
|
6
|
+
import { constPrimitiveValidate, prependPath, primitive0Validate, verror, visit, } from "../common/module.f.js";
|
|
7
|
+
export {} from "../common/module.f.js";
|
|
7
8
|
const indexedFirstError = (results) => {
|
|
8
9
|
// TODO: findIndex breaks type inference,
|
|
9
10
|
// we should replace it with something else.
|
|
@@ -65,12 +66,6 @@ const structParse = (rtti) => value => {
|
|
|
65
66
|
? ok(Object.fromEntries(results.map(([k, r]) => [k, r[1]])))
|
|
66
67
|
: prependPath(err[0], err[1]));
|
|
67
68
|
};
|
|
68
|
-
const constObjectParse = (rtti) => commonIsArray(rtti)
|
|
69
|
-
? tupleParse(rtti)
|
|
70
|
-
: structParse(rtti);
|
|
71
|
-
const constParse = (rtti) => typeof rtti === 'object' && rtti !== null
|
|
72
|
-
? constObjectParse(rtti)
|
|
73
|
-
: constPrimitiveValidate(rtti);
|
|
74
69
|
const findFirst = find(verror('no match'))((k) => k[0] === 'ok');
|
|
75
70
|
const orParse = (rtti) => value => findFirst(listMap(t => parse(t)(value))(rtti));
|
|
76
71
|
/**
|
|
@@ -97,17 +92,14 @@ const orParse = (rtti) => value => findFirst(listMap(t => parse(t)(value))(rtti)
|
|
|
97
92
|
* parse({ a: number } as const)({ a: 1, b: 2 }) // ['ok', { a: 1 }]
|
|
98
93
|
* ```
|
|
99
94
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
return primitive0Validate(tag);
|
|
111
|
-
}
|
|
112
|
-
return constParse(rtti);
|
|
95
|
+
const parseVisitor = {
|
|
96
|
+
tuple: tupleParse,
|
|
97
|
+
struct: structParse,
|
|
98
|
+
array: arrayParse,
|
|
99
|
+
record: recordParse,
|
|
100
|
+
or: orParse,
|
|
101
|
+
constPrimitive: constPrimitiveValidate,
|
|
102
|
+
primitive0: primitive0Validate,
|
|
103
|
+
unknown: () => ok,
|
|
113
104
|
};
|
|
105
|
+
export const parse = (rtti) => visit(parseVisitor)(rtti);
|
|
@@ -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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
6
|
-
export
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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);
|