functionalscript 0.46.0 → 0.47.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/fjs/basen/cbase32/module.f.mjs +18 -10
- package/fjs/basen/cbase32/proof.f.d.mts +3 -0
- package/fjs/basen/cbase32/proof.f.mjs +24 -2
- package/fjs/basen/proof.f.mjs +8 -16
- package/fjs/bnf/ll1/proof.f.d.mts +2 -0
- package/fjs/bnf/ll1/proof.f.mjs +33 -18
- package/fjs/bnf/module.f.d.mts +2 -2
- package/fjs/bnf/module.f.mjs +2 -2
- package/fjs/cas/proof.f.mjs +4 -26
- package/fjs/ci/common/module.f.mjs +6 -0
- package/fjs/ci/config/module.f.d.mts +7 -7
- package/fjs/ci/config/module.f.mjs +7 -7
- package/fjs/cli/module.f.mjs +14 -10
- package/fjs/crypto/vdf/module.f.d.mts +0 -1
- package/fjs/crypto/vdf/module.f.mjs +3 -14
- package/fjs/dev/module.f.mjs +10 -20
- package/fjs/djs/parser/module.f.d.mts +101 -53
- package/fjs/djs/parser/module.f.mjs +732 -525
- package/fjs/djs/parser/proof.f.d.mts +6 -0
- package/fjs/djs/parser/proof.f.mjs +315 -11
- package/fjs/djs/parser/types.d.ts +29 -2
- package/fjs/djs/tokenizer/module.f.mjs +122 -105
- package/fjs/djs/tokenizer/proof.f.mjs +17 -0
- package/fjs/djs/transpiler/module.f.mjs +4 -5
- package/fjs/djs/transpiler/proof.f.d.mts +2 -0
- package/fjs/djs/transpiler/proof.f.mjs +25 -0
- package/fjs/edag/amnesia/module.f.d.mts +20 -0
- package/fjs/edag/amnesia/module.f.mjs +303 -0
- package/fjs/edag/amnesia/proof.f.d.mts +79 -0
- package/fjs/edag/amnesia/proof.f.mjs +573 -0
- package/fjs/edag/amnesia/types.d.ts +15 -0
- package/fjs/edag/module.f.d.mts +410 -0
- package/fjs/edag/module.f.mjs +559 -0
- package/fjs/edag/proof.f.d.mts +116 -0
- package/fjs/edag/proof.f.mjs +686 -0
- package/fjs/edag/types.d.ts +55 -0
- package/fjs/effects/node/memory/module.d.mts +37 -3
- package/fjs/effects/node/memory/module.mjs +38 -4
- package/fjs/effects/node/memory/proof.d.mts +5 -2
- package/fjs/effects/node/memory/proof.mjs +36 -6
- package/fjs/effects/node/module.d.mts +9 -1
- package/fjs/effects/node/module.f.d.mts +39 -0
- package/fjs/effects/node/module.f.mjs +46 -0
- package/fjs/effects/node/module.mjs +234 -32
- package/fjs/effects/node/types.d.ts +28 -2
- package/fjs/effects/node/virtual/module.f.d.mts +19 -10
- package/fjs/effects/node/virtual/module.f.mjs +246 -32
- package/fjs/effects/node/virtual/proof.f.d.mts +17 -2
- package/fjs/effects/node/virtual/proof.f.mjs +232 -8
- package/fjs/effects/node/virtual/types.d.ts +57 -1
- package/fjs/emergent_testing/browser/proof.d.mts +55 -0
- package/fjs/emergent_testing/browser/proof.mjs +352 -0
- package/fjs/emergent_testing/browser/species.proof.d.mts +4 -0
- package/fjs/emergent_testing/browser/species.proof.mjs +45 -0
- package/fjs/emergent_testing/browser.d.mts +79 -0
- package/fjs/emergent_testing/browser.mjs +432 -0
- package/fjs/emergent_testing/proof.f.d.mts +1 -1
- package/fjs/emergent_testing/proof.f.mjs +2 -2
- package/fjs/js/string_escape/module.f.d.mts +47 -0
- package/fjs/js/string_escape/module.f.mjs +95 -0
- package/fjs/js/string_escape/proof.f.d.mts +8 -0
- package/fjs/js/string_escape/proof.f.mjs +68 -0
- package/fjs/js/tokenizer/module.f.mjs +14 -12
- package/fjs/mcp/cas/module.f.d.mts +1 -7
- package/fjs/mcp/cas/module.f.mjs +24 -15
- package/fjs/mcp/evo/module.f.mjs +9 -24
- package/fjs/mcp/module.f.d.mts +3 -1
- package/fjs/mcp/module.f.mjs +4 -2
- package/fjs/media/json/rtti/module.f.d.mts +2 -2
- package/fjs/media/json/rtti/module.f.mjs +1 -1
- package/fjs/media/json/schema/module.f.d.mts +15 -5
- package/fjs/media/json/schema/module.f.mjs +42 -15
- package/fjs/media/json/schema/proof.f.d.mts +10 -1
- package/fjs/media/json/schema/proof.f.mjs +88 -16
- package/fjs/media/json/serializer/module.f.mjs +14 -26
- package/fjs/media/json/types.d.ts +1 -1
- package/fjs/media/lock/module.f.d.mts +8 -3
- package/fjs/media/lock/module.f.mjs +9 -3
- package/fjs/media/lock/proof.f.d.mts +1 -1
- package/fjs/media/lock/proof.f.mjs +2 -2
- package/fjs/media/lock/types.d.ts +1 -3
- package/fjs/media/module.f.d.mts +20 -13
- package/fjs/media/module.f.mjs +29 -16
- package/fjs/media/note/module.f.d.mts +14 -7
- package/fjs/media/note/module.f.mjs +14 -7
- package/fjs/media/note/proof.f.mjs +1 -1
- package/fjs/media/proof.f.mjs +5 -5
- package/fjs/media/revision/module.f.d.mts +8 -2
- package/fjs/media/revision/module.f.mjs +9 -3
- package/fjs/media/revision/proof.f.mjs +1 -1
- package/fjs/media/revision/types.d.ts +1 -1
- package/fjs/media/type/module.f.d.mts +20 -0
- package/fjs/media/type/module.f.mjs +74 -0
- package/fjs/media/type/proof.f.d.mts +5 -0
- package/fjs/media/type/proof.f.mjs +24 -1
- package/fjs/module.f.mjs +6 -0
- package/fjs/path/module.f.d.mts +60 -3
- package/fjs/path/module.f.mjs +138 -14
- package/fjs/path/proof.f.d.mts +3 -0
- package/fjs/path/proof.f.mjs +298 -2
- package/fjs/protocol/json_rpc/module.f.d.mts +65 -28
- package/fjs/protocol/json_rpc/module.f.mjs +49 -13
- package/fjs/protocol/json_rpc/proof.f.d.mts +4 -0
- package/fjs/protocol/json_rpc/proof.f.mjs +29 -0
- package/fjs/protocol/json_rpc/types.d.ts +12 -2
- package/fjs/protocol/mcp/module.f.d.mts +89 -54
- package/fjs/protocol/mcp/module.f.mjs +110 -56
- package/fjs/protocol/mcp/proof.f.d.mts +13 -0
- package/fjs/protocol/mcp/proof.f.mjs +107 -7
- package/fjs/protocol/mcp/stdio/module.f.mjs +3 -4
- package/fjs/protocol/mcp/stdio/proof.f.mjs +2 -2
- package/fjs/protocol/mcp/types.d.ts +14 -2
- package/fjs/text/utf8/module.f.mjs +27 -1
- package/fjs/text/utf8/proof.f.mjs +37 -0
- package/fjs/types/array/module.f.mjs +18 -11
- package/fjs/types/array/proof.f.mjs +27 -1
- package/fjs/types/bigfloat/module.f.d.mts +48 -9
- package/fjs/types/bigfloat/module.f.mjs +147 -59
- package/fjs/types/bigfloat/proof.f.d.mts +2 -0
- package/fjs/types/bigfloat/proof.f.mjs +136 -4
- package/fjs/types/bigfloat/types.d.ts +17 -0
- package/fjs/types/function/module.f.d.mts +11 -2
- package/fjs/types/function/module.f.mjs +20 -2
- package/fjs/types/function/proof.f.mjs +5 -1
- package/fjs/types/phantom/types.d.ts +21 -0
- package/fjs/types/result/module.f.d.mts +4 -4
- package/fjs/types/result/module.f.mjs +2 -2
- package/fjs/types/result/proof.f.d.mts +6 -2
- package/fjs/types/result/proof.f.mjs +17 -1
- package/fjs/types/rtti/common/module.f.d.mts +78 -5
- package/fjs/types/rtti/common/module.f.mjs +168 -3
- package/fjs/types/rtti/common/proof.f.d.mts +14 -0
- package/fjs/types/rtti/common/proof.f.mjs +61 -2
- package/fjs/types/rtti/common/types.d.ts +23 -1
- package/fjs/types/rtti/data/module.f.d.mts +57 -1
- package/fjs/types/rtti/data/module.f.mjs +241 -47
- package/fjs/types/rtti/data/proof.f.d.mts +10 -0
- package/fjs/types/rtti/data/proof.f.mjs +272 -39
- package/fjs/types/rtti/data/types.d.ts +14 -7
- package/fjs/types/rtti/host.proof.d.mts +33 -0
- package/fjs/types/rtti/host.proof.mjs +170 -0
- package/fjs/types/rtti/module.f.d.mts +51 -6
- package/fjs/types/rtti/module.f.mjs +50 -3
- package/fjs/types/rtti/parse/module.f.d.mts +31 -24
- package/fjs/types/rtti/parse/module.f.mjs +197 -59
- package/fjs/types/rtti/parse/proof.f.d.mts +37 -2
- package/fjs/types/rtti/parse/proof.f.mjs +171 -47
- package/fjs/types/rtti/proof.f.d.mts +6 -0
- package/fjs/types/rtti/proof.f.mjs +42 -1
- package/fjs/types/rtti/ts/module.f.d.mts +9 -3
- package/fjs/types/rtti/ts/module.f.mjs +68 -15
- package/fjs/types/rtti/ts/proof.f.d.mts +42 -2
- package/fjs/types/rtti/ts/proof.f.mjs +139 -6
- package/fjs/types/rtti/ts/types.d.ts +164 -16
- package/fjs/types/rtti/types.d.ts +31 -3
- package/fjs/types/rtti/validate/module.f.d.mts +120 -0
- package/fjs/types/rtti/validate/module.f.mjs +351 -0
- package/fjs/types/rtti/validate/proof.f.d.mts +154 -0
- package/fjs/types/rtti/validate/proof.f.mjs +781 -0
- package/fjs/types/ts/types.d.ts +4 -0
- package/fjs/web/module.f.d.mts +115 -0
- package/fjs/web/module.f.mjs +581 -0
- package/fjs/web/proof.f.d.mts +39 -0
- package/fjs/web/proof.f.mjs +413 -0
- package/fjs/web/types.d.ts +39 -0
- package/fjs/website/browser-prepare.d.mts +5 -0
- package/fjs/website/browser-prepare.mjs +115 -0
- package/fjs/website/browser-source.d.mts +37 -0
- package/fjs/website/browser-source.mjs +202 -0
- package/fjs/website/browser-source.proof.d.mts +31 -0
- package/fjs/website/browser-source.proof.mjs +162 -0
- package/fjs/website/browser.d.mts +5 -0
- package/fjs/website/browser.mjs +8 -0
- package/fjs/website/module.f.d.mts +3 -3
- package/fjs/website/module.f.mjs +62 -9
- package/fjs/website/proof.f.d.mts +4 -0
- package/fjs/website/proof.f.mjs +18 -3
- package/package.json +2 -3
package/fjs/path/proof.f.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { concat, isProperPrefix, join, normalize, relativize, toPosix } from "./module.f.mjs"
|
|
1
|
+
import { concat, escapes, isProperPrefix, join, normalize, parse, relativize, root, toPosix } from "./module.f.mjs"
|
|
2
2
|
import { assertEq } from '../asserts/module.f.mjs'
|
|
3
3
|
|
|
4
4
|
const normalizeTest = [
|
|
@@ -18,6 +18,193 @@ const normalizeTest = [
|
|
|
18
18
|
const norm = normalize("../../dir/../file.json")
|
|
19
19
|
assertEq(norm, "../../file.json")
|
|
20
20
|
},
|
|
21
|
+
// A root survives normalization: it is not a segment, so it is not one of
|
|
22
|
+
// the empty ones the fold drops.
|
|
23
|
+
() => {
|
|
24
|
+
const norm = normalize("/a/b")
|
|
25
|
+
assertEq(norm, "/a/b")
|
|
26
|
+
},
|
|
27
|
+
() => {
|
|
28
|
+
const norm = normalize("//srv/share")
|
|
29
|
+
assertEq(norm, "//srv/share")
|
|
30
|
+
},
|
|
31
|
+
() => {
|
|
32
|
+
const norm = normalize("/")
|
|
33
|
+
assertEq(norm, "/")
|
|
34
|
+
},
|
|
35
|
+
// `..` has no parent of the root to name, so it is dropped — while the
|
|
36
|
+
// same input without a root keeps it.
|
|
37
|
+
() => {
|
|
38
|
+
const norm = normalize("/a/../..")
|
|
39
|
+
assertEq(norm, "/")
|
|
40
|
+
},
|
|
41
|
+
() => {
|
|
42
|
+
const norm = normalize("a/../..")
|
|
43
|
+
assertEq(norm, "..")
|
|
44
|
+
},
|
|
45
|
+
// A drive letter is an ordinary segment and needs no case of its own.
|
|
46
|
+
() => {
|
|
47
|
+
const norm = normalize("C:/a/b")
|
|
48
|
+
assertEq(norm, "C:/a/b")
|
|
49
|
+
},
|
|
50
|
+
() => {
|
|
51
|
+
const norm = normalize("C:\\a\\b")
|
|
52
|
+
assertEq(norm, "C:/a/b")
|
|
53
|
+
},
|
|
54
|
+
// An *interior* empty segment is still noise: only the leading one is a root.
|
|
55
|
+
() => {
|
|
56
|
+
const norm = normalize("a//b")
|
|
57
|
+
assertEq(norm, "a/b")
|
|
58
|
+
},
|
|
59
|
+
() => {
|
|
60
|
+
const norm = normalize("")
|
|
61
|
+
assertEq(norm, "")
|
|
62
|
+
},
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
const escapesTest = [
|
|
66
|
+
// A `..` that cancels a real segment does not escape; one with nothing
|
|
67
|
+
// left to cancel does. `parse` cannot answer this — it folds with the root
|
|
68
|
+
// in place, which is exactly what removes the escaping `..`.
|
|
69
|
+
() => {
|
|
70
|
+
assertEq(escapes("/a/../b"), false)
|
|
71
|
+
},
|
|
72
|
+
() => {
|
|
73
|
+
assertEq(escapes("/../b"), true)
|
|
74
|
+
},
|
|
75
|
+
() => {
|
|
76
|
+
assertEq(escapes("a/../../b"), true)
|
|
77
|
+
},
|
|
78
|
+
() => {
|
|
79
|
+
assertEq(escapes("a/b/../c"), false)
|
|
80
|
+
},
|
|
81
|
+
// Taking the root off is not the same as dropping one leading `/`: these
|
|
82
|
+
// remainders read as rooted again, and a second fold would clamp the very
|
|
83
|
+
// `..` being looked for.
|
|
84
|
+
() => {
|
|
85
|
+
assertEq(escapes("///../secret"), true)
|
|
86
|
+
},
|
|
87
|
+
() => {
|
|
88
|
+
assertEq(escapes("/C:/../../secret"), true)
|
|
89
|
+
},
|
|
90
|
+
() => {
|
|
91
|
+
assertEq(escapes("//../secret"), true)
|
|
92
|
+
},
|
|
93
|
+
() => {
|
|
94
|
+
assertEq(escapes("C:/../secret"), true)
|
|
95
|
+
},
|
|
96
|
+
() => {
|
|
97
|
+
assertEq(escapes(""), false)
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
const rootTest = [
|
|
102
|
+
() => {
|
|
103
|
+
const r = root("a/b")
|
|
104
|
+
assertEq(r, "")
|
|
105
|
+
},
|
|
106
|
+
() => {
|
|
107
|
+
const r = root("/a/b")
|
|
108
|
+
assertEq(r, "/")
|
|
109
|
+
},
|
|
110
|
+
() => {
|
|
111
|
+
const r = root("//srv/share")
|
|
112
|
+
assertEq(r, "//")
|
|
113
|
+
},
|
|
114
|
+
// Three or more leading slashes are an ordinary root followed by empty
|
|
115
|
+
// segments, which is what POSIX requires; exactly two are the UNC case.
|
|
116
|
+
() => {
|
|
117
|
+
const r = root("///a")
|
|
118
|
+
assertEq(r, "/")
|
|
119
|
+
},
|
|
120
|
+
() => {
|
|
121
|
+
const norm = normalize("///a")
|
|
122
|
+
assertEq(norm, "/a")
|
|
123
|
+
},
|
|
124
|
+
// Windows spells the UNC root with backslashes.
|
|
125
|
+
() => {
|
|
126
|
+
const r = root("\\\\srv\\share")
|
|
127
|
+
assertEq(r, "//")
|
|
128
|
+
},
|
|
129
|
+
// A Windows drive is a root, so `..` cannot climb off it — `C:\\..` is
|
|
130
|
+
// `C:\\` on Windows, not the parent of the drive.
|
|
131
|
+
() => {
|
|
132
|
+
const r = root("C:/a")
|
|
133
|
+
assertEq(r, "C:/")
|
|
134
|
+
},
|
|
135
|
+
() => {
|
|
136
|
+
const r = root("c:/a")
|
|
137
|
+
assertEq(r, "c:/")
|
|
138
|
+
},
|
|
139
|
+
() => {
|
|
140
|
+
const norm = normalize("C:/..")
|
|
141
|
+
assertEq(norm, "C:/")
|
|
142
|
+
},
|
|
143
|
+
() => {
|
|
144
|
+
const norm = normalize("C:/a/../../..")
|
|
145
|
+
assertEq(norm, "C:/")
|
|
146
|
+
},
|
|
147
|
+
() => {
|
|
148
|
+
const norm = normalize("C:/")
|
|
149
|
+
assertEq(norm, "C:/")
|
|
150
|
+
},
|
|
151
|
+
// Only the absolute spelling roots. A bare `C:` and the drive-relative
|
|
152
|
+
// `C:foo` — the current directory *on* drive C — stay ordinary segments,
|
|
153
|
+
// and a prefix that is not a single letter is not a drive at all.
|
|
154
|
+
() => {
|
|
155
|
+
const r = root("C:")
|
|
156
|
+
assertEq(r, "")
|
|
157
|
+
},
|
|
158
|
+
() => {
|
|
159
|
+
const r = root("C:a/b")
|
|
160
|
+
assertEq(r, "")
|
|
161
|
+
},
|
|
162
|
+
() => {
|
|
163
|
+
const r = root("1:/x")
|
|
164
|
+
assertEq(r, "")
|
|
165
|
+
},
|
|
166
|
+
() => {
|
|
167
|
+
const r = root("ab:/x")
|
|
168
|
+
assertEq(r, "")
|
|
169
|
+
},
|
|
170
|
+
// The UNC root stops at `//`: `server/share` are ordinary segments, so a
|
|
171
|
+
// `..` can still climb past a share. Parsing them into the root would let
|
|
172
|
+
// a `../` fold into it, which is what a root must never allow.
|
|
173
|
+
() => {
|
|
174
|
+
const c = concat("//srv/share")("../..")
|
|
175
|
+
assertEq(c, "//")
|
|
176
|
+
},
|
|
177
|
+
() => {
|
|
178
|
+
const p = parse("//a/b")
|
|
179
|
+
assertEq(join(...p), "a/b")
|
|
180
|
+
},
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
const parseTest = [
|
|
184
|
+
// `parse` answers with segments only, so an absolute and a relative path
|
|
185
|
+
// that name the same segments are indistinguishable through it. `root`
|
|
186
|
+
// is what tells them apart.
|
|
187
|
+
() => {
|
|
188
|
+
const p = parse("/a/b")
|
|
189
|
+
assertEq(join(...p), "a/b")
|
|
190
|
+
},
|
|
191
|
+
() => {
|
|
192
|
+
const p = parse("a/b")
|
|
193
|
+
assertEq(join(...p), "a/b")
|
|
194
|
+
},
|
|
195
|
+
// The root still reaches the fold: a `..` that would escape it is dropped.
|
|
196
|
+
() => {
|
|
197
|
+
const p = parse("/..")
|
|
198
|
+
assertEq(p.length, 0)
|
|
199
|
+
},
|
|
200
|
+
() => {
|
|
201
|
+
const p = parse("..")
|
|
202
|
+
assertEq(join(...p), "..")
|
|
203
|
+
},
|
|
204
|
+
() => {
|
|
205
|
+
const p = parse("../..")
|
|
206
|
+
assertEq(join(...p), "../..")
|
|
207
|
+
},
|
|
21
208
|
]
|
|
22
209
|
|
|
23
210
|
const concatTest = [
|
|
@@ -33,6 +220,115 @@ const concatTest = [
|
|
|
33
220
|
const c = concat("a/../b/..")("c")
|
|
34
221
|
assertEq(c, "c")
|
|
35
222
|
},
|
|
223
|
+
// The root comes from `a`, so `concat` no longer turns an absolute path
|
|
224
|
+
// into a relative one. This is the shape `fjs compile` resolves imports
|
|
225
|
+
// with: `concat(concat(path)('..'))(importPath)`.
|
|
226
|
+
() => {
|
|
227
|
+
const c = concat("/a/b/m.f.js")("..")
|
|
228
|
+
assertEq(c, "/a/b")
|
|
229
|
+
},
|
|
230
|
+
() => {
|
|
231
|
+
const c = concat("//srv/share/m.f.js")("..")
|
|
232
|
+
assertEq(c, "//srv/share")
|
|
233
|
+
},
|
|
234
|
+
() => {
|
|
235
|
+
const c = concat("/a/b")("../c")
|
|
236
|
+
assertEq(c, "/a/c")
|
|
237
|
+
},
|
|
238
|
+
() => {
|
|
239
|
+
const c = concat("/")("y")
|
|
240
|
+
assertEq(c, "/y")
|
|
241
|
+
},
|
|
242
|
+
() => {
|
|
243
|
+
const c = concat("/a")("../..")
|
|
244
|
+
assertEq(c, "/")
|
|
245
|
+
},
|
|
246
|
+
() => {
|
|
247
|
+
const c = concat("C:/a/b/m.f.js")("..")
|
|
248
|
+
assertEq(c, "C:/a/b")
|
|
249
|
+
},
|
|
250
|
+
() => {
|
|
251
|
+
const c = concat(concat("C:/a/m.f.js")(".."))("../../lib.f.js")
|
|
252
|
+
assertEq(c, "C:/lib.f.js")
|
|
253
|
+
},
|
|
254
|
+
// A bare drive is not a root, and `concat` does not make it one: joined
|
|
255
|
+
// with a separator, `C:` and `dir` would be the drive root `C:/dir`
|
|
256
|
+
// instead of the drive-relative `C:dir` — a different place on the disk.
|
|
257
|
+
() => {
|
|
258
|
+
const c = concat("C:")("dir")
|
|
259
|
+
assertEq(c, "C:dir")
|
|
260
|
+
},
|
|
261
|
+
() => {
|
|
262
|
+
const r = root(concat("C:")("dir"))
|
|
263
|
+
assertEq(r, "")
|
|
264
|
+
},
|
|
265
|
+
// The drive-absolute spelling is still reachable, and still a root.
|
|
266
|
+
() => {
|
|
267
|
+
const c = concat("C:/")("dir")
|
|
268
|
+
assertEq(c, "C:/dir")
|
|
269
|
+
},
|
|
270
|
+
() => {
|
|
271
|
+
const r = root(concat("C:/")("dir"))
|
|
272
|
+
assertEq(r, "C:/")
|
|
273
|
+
},
|
|
274
|
+
// Only a real bare drive skips the separator: the letter's case does not
|
|
275
|
+
// matter, a non-letter is not a drive, and `C:foo` is longer than a bare
|
|
276
|
+
// drive — reading it as one would join it to `C:foox`.
|
|
277
|
+
() => {
|
|
278
|
+
const c = concat("ab:")("dir")
|
|
279
|
+
assertEq(c, "ab:/dir")
|
|
280
|
+
},
|
|
281
|
+
() => {
|
|
282
|
+
const c = concat("c:")("dir")
|
|
283
|
+
assertEq(c, "c:dir")
|
|
284
|
+
},
|
|
285
|
+
() => {
|
|
286
|
+
const c = concat("1:")("dir")
|
|
287
|
+
assertEq(c, "1:/dir")
|
|
288
|
+
},
|
|
289
|
+
() => {
|
|
290
|
+
const c = concat("C:foo")("x")
|
|
291
|
+
assertEq(c, "C:foo/x")
|
|
292
|
+
},
|
|
293
|
+
() => {
|
|
294
|
+
const c = concat("C:")("/abs")
|
|
295
|
+
assertEq(c, "/abs")
|
|
296
|
+
},
|
|
297
|
+
// The left side is folded before its root is read, so every spelling of a
|
|
298
|
+
// bare drive is one — testing the unfolded text caught only the literal.
|
|
299
|
+
() => {
|
|
300
|
+
const c = concat("./C:")("dir")
|
|
301
|
+
assertEq(c, "C:dir")
|
|
302
|
+
},
|
|
303
|
+
() => {
|
|
304
|
+
const c = concat("x/../C:")("dir")
|
|
305
|
+
assertEq(c, "C:dir")
|
|
306
|
+
},
|
|
307
|
+
// Whatever `a` spells, appending a relative `b` leaves its root alone.
|
|
308
|
+
() => {
|
|
309
|
+
["C:", "./C:", "x/../C:", "C:/", "./a", "/a", "//a", "", "."].forEach(a =>
|
|
310
|
+
assertEq(root(concat(a)("dir")), root(normalize(a)), a))
|
|
311
|
+
},
|
|
312
|
+
// An absolute `b` names a path on its own, so it replaces `a` rather than
|
|
313
|
+
// being appended to it.
|
|
314
|
+
() => {
|
|
315
|
+
const c = concat("a")("/abs/x")
|
|
316
|
+
assertEq(c, "/abs/x")
|
|
317
|
+
},
|
|
318
|
+
() => {
|
|
319
|
+
const c = concat("/a/b")("//srv/share")
|
|
320
|
+
assertEq(c, "//srv/share")
|
|
321
|
+
},
|
|
322
|
+
// The separator `concat` inserts is not a root: with an empty `a`, a
|
|
323
|
+
// leading `..` in `b` still has nothing to escape.
|
|
324
|
+
() => {
|
|
325
|
+
const c = concat("")("../lib.f.js")
|
|
326
|
+
assertEq(c, "../lib.f.js")
|
|
327
|
+
},
|
|
328
|
+
() => {
|
|
329
|
+
const c = concat("")("./lib.f.js")
|
|
330
|
+
assertEq(c, "lib.f.js")
|
|
331
|
+
},
|
|
36
332
|
]
|
|
37
333
|
|
|
38
334
|
const joinTest = [
|
|
@@ -122,4 +418,4 @@ const isProperPrefixTest = [
|
|
|
122
418
|
},
|
|
123
419
|
]
|
|
124
420
|
|
|
125
|
-
export const proof = { normalizeTest, concatTest, joinTest, relativizeTest, toPosixTest, isProperPrefixTest }
|
|
421
|
+
export const proof = { normalizeTest, escapesTest, rootTest, parseTest, concatTest, joinTest, relativizeTest, toPosixTest, isProperPrefixTest }
|
|
@@ -15,74 +15,79 @@
|
|
|
15
15
|
* @module
|
|
16
16
|
*
|
|
17
17
|
* @import { Unknown } from '../../media/json/types.ts'
|
|
18
|
-
* @import { Id, RpcError, Handlers, Response } from './types.ts'
|
|
18
|
+
* @import { Id, RpcError, Handlers, Response, SuccessResponse, ErrorResponse } from './types.ts'
|
|
19
19
|
*/
|
|
20
20
|
import type { Unknown } from '../../media/json/types.ts';
|
|
21
|
-
import type { RpcError, Handlers, Response } from './types.ts';
|
|
21
|
+
import type { Id, RpcError, Handlers, Response } from './types.ts';
|
|
22
22
|
export declare const jsonrpc: '2.0';
|
|
23
23
|
/** Request/response identifier: a string, a number, or `null`. */
|
|
24
|
-
export declare const _id: import("../../types/rtti/types.ts").Or<[import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>;
|
|
24
|
+
export declare const _id: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>;
|
|
25
25
|
/**
|
|
26
26
|
* A request or notification envelope. `id` present → request (a response is
|
|
27
27
|
* expected); `id` absent → notification (no response). `params` is optional.
|
|
28
28
|
*
|
|
29
|
+
* `open`, as every envelope in this module is: a bare struct is closed, and a
|
|
30
|
+
* peer implementing a later revision of the protocol may send members this one
|
|
31
|
+
* does not name — rejecting those outright is the opposite of what a wire
|
|
32
|
+
* format wants. Do not drop the wrapper.
|
|
33
|
+
*
|
|
29
34
|
* https://www.jsonrpc.org/specification#request_object
|
|
30
35
|
*/
|
|
31
|
-
export declare const request: {
|
|
36
|
+
export declare const request: import("../../types/rtti/types.ts").Rest<{
|
|
32
37
|
readonly jsonrpc: "2.0";
|
|
33
38
|
readonly method: import("../../types/rtti/types.ts")._Type0<"string">;
|
|
34
39
|
readonly params: import("../../types/rtti/types.ts").Or<readonly [() => readonly ['or', typeof import("../../media/json/rtti/module.f.mjs").primitive, typeof import("../../media/json/rtti/module.f.mjs").object, typeof import("../../media/json/rtti/module.f.mjs").array], undefined]>;
|
|
35
|
-
readonly id: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts").Or<[import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>, undefined]>;
|
|
36
|
-
}
|
|
37
|
-
/** The JSON-RPC error object. */
|
|
38
|
-
export declare const error: {
|
|
40
|
+
readonly id: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>, undefined]>;
|
|
41
|
+
}, import("../../types/rtti/types.ts").Unknown>;
|
|
42
|
+
/** The JSON-RPC error object — `open`, for the reason {@link request} gives. */
|
|
43
|
+
export declare const error: import("../../types/rtti/types.ts").Rest<{
|
|
39
44
|
readonly code: import("../../types/rtti/types.ts")._Type0<"number">;
|
|
40
45
|
readonly message: import("../../types/rtti/types.ts")._Type0<"string">;
|
|
41
46
|
readonly data: import("../../types/rtti/types.ts").Or<readonly [() => readonly ['or', typeof import("../../media/json/rtti/module.f.mjs").primitive, typeof import("../../media/json/rtti/module.f.mjs").object, typeof import("../../media/json/rtti/module.f.mjs").array], undefined]>;
|
|
42
|
-
}
|
|
43
|
-
export declare const successResponse: {
|
|
47
|
+
}, import("../../types/rtti/types.ts").Unknown>;
|
|
48
|
+
export declare const successResponse: import("../../types/rtti/types.ts").Rest<{
|
|
44
49
|
readonly jsonrpc: "2.0";
|
|
45
50
|
readonly result: () => readonly ['or', typeof import("../../media/json/rtti/module.f.mjs").primitive, typeof import("../../media/json/rtti/module.f.mjs").object, typeof import("../../media/json/rtti/module.f.mjs").array];
|
|
46
|
-
readonly id: import("../../types/rtti/types.ts").Or<[import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>;
|
|
47
|
-
}
|
|
48
|
-
export declare const errorResponse: {
|
|
51
|
+
readonly id: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>;
|
|
52
|
+
}, import("../../types/rtti/types.ts").Unknown>;
|
|
53
|
+
export declare const errorResponse: import("../../types/rtti/types.ts").Rest<{
|
|
49
54
|
readonly jsonrpc: "2.0";
|
|
50
|
-
readonly error: {
|
|
55
|
+
readonly error: import("../../types/rtti/types.ts").Rest<{
|
|
51
56
|
readonly code: import("../../types/rtti/types.ts")._Type0<"number">;
|
|
52
57
|
readonly message: import("../../types/rtti/types.ts")._Type0<"string">;
|
|
53
58
|
readonly data: import("../../types/rtti/types.ts").Or<readonly [() => readonly ['or', typeof import("../../media/json/rtti/module.f.mjs").primitive, typeof import("../../media/json/rtti/module.f.mjs").object, typeof import("../../media/json/rtti/module.f.mjs").array], undefined]>;
|
|
54
|
-
}
|
|
55
|
-
readonly id: import("../../types/rtti/types.ts").Or<[import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>;
|
|
56
|
-
}
|
|
59
|
+
}, import("../../types/rtti/types.ts").Unknown>;
|
|
60
|
+
readonly id: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>;
|
|
61
|
+
}, import("../../types/rtti/types.ts").Unknown>;
|
|
57
62
|
/**
|
|
58
63
|
* A response envelope: either a success (`result`) or an error (`error`).
|
|
59
64
|
* Derived from the rtti schema via `Ts<>` — the same declaration is the
|
|
60
|
-
* runtime decoder and the static type, with no drift.
|
|
65
|
+
* runtime decoder and the static type, with no drift. Both arms say `open`
|
|
61
66
|
* (extra keys allowed), so "result XOR error" is not enforced at runtime; in
|
|
62
67
|
* practice the dispatcher only ever constructs one or the other.
|
|
63
68
|
*
|
|
64
69
|
* https://www.jsonrpc.org/specification#response_object
|
|
65
70
|
*/
|
|
66
|
-
export declare const response: import("../../types/rtti/types.ts").Or<[{
|
|
71
|
+
export declare const response: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts").Rest<{
|
|
67
72
|
readonly jsonrpc: "2.0";
|
|
68
73
|
readonly result: () => readonly ['or', typeof import("../../media/json/rtti/module.f.mjs").primitive, typeof import("../../media/json/rtti/module.f.mjs").object, typeof import("../../media/json/rtti/module.f.mjs").array];
|
|
69
|
-
readonly id: import("../../types/rtti/types.ts").Or<[import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>;
|
|
70
|
-
}, {
|
|
74
|
+
readonly id: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>;
|
|
75
|
+
}, import("../../types/rtti/types.ts").Unknown>, import("../../types/rtti/types.ts").Rest<{
|
|
71
76
|
readonly jsonrpc: "2.0";
|
|
72
|
-
readonly error: {
|
|
77
|
+
readonly error: import("../../types/rtti/types.ts").Rest<{
|
|
73
78
|
readonly code: import("../../types/rtti/types.ts")._Type0<"number">;
|
|
74
79
|
readonly message: import("../../types/rtti/types.ts")._Type0<"string">;
|
|
75
80
|
readonly data: import("../../types/rtti/types.ts").Or<readonly [() => readonly ['or', typeof import("../../media/json/rtti/module.f.mjs").primitive, typeof import("../../media/json/rtti/module.f.mjs").object, typeof import("../../media/json/rtti/module.f.mjs").array], undefined]>;
|
|
76
|
-
}
|
|
77
|
-
readonly id: import("../../types/rtti/types.ts").Or<[import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>;
|
|
78
|
-
}]>;
|
|
81
|
+
}, import("../../types/rtti/types.ts").Unknown>;
|
|
82
|
+
readonly id: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>;
|
|
83
|
+
}, import("../../types/rtti/types.ts").Unknown>]>;
|
|
79
84
|
/** Decodes an untrusted value as a JSON-RPC request / notification. */
|
|
80
|
-
export declare const decodeRequest: import("../../types/rtti/parse/types.ts").Parse<{
|
|
85
|
+
export declare const decodeRequest: import("../../types/rtti/parse/types.ts").Parse<import("../../types/rtti/types.ts").Rest<{
|
|
81
86
|
readonly jsonrpc: "2.0";
|
|
82
87
|
readonly method: import("../../types/rtti/types.ts")._Type0<"string">;
|
|
83
88
|
readonly params: import("../../types/rtti/types.ts").Or<readonly [() => readonly ['or', typeof import("../../media/json/rtti/module.f.mjs").primitive, typeof import("../../media/json/rtti/module.f.mjs").object, typeof import("../../media/json/rtti/module.f.mjs").array], undefined]>;
|
|
84
|
-
readonly id: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts").Or<[import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>, undefined]>;
|
|
85
|
-
}
|
|
89
|
+
readonly id: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts")._Type0<"string">, import("../../types/rtti/types.ts")._Type0<"number">, null]>, undefined]>;
|
|
90
|
+
}, import("../../types/rtti/types.ts").Unknown>>;
|
|
86
91
|
/**
|
|
87
92
|
* Builds an `RpcError` with the given `code` and `message` (no `data`).
|
|
88
93
|
* @type {(code: number) => (message: string) => RpcError}
|
|
@@ -113,6 +118,38 @@ export declare const internalError: import("../../types/rtti/ts/types.ts").Struc
|
|
|
113
118
|
readonly message: import("../../types/rtti/types.ts")._Type0<"string">;
|
|
114
119
|
readonly data: import("../../types/rtti/types.ts").Or<readonly [() => readonly ['or', typeof import("../../media/json/rtti/module.f.mjs").primitive, typeof import("../../media/json/rtti/module.f.mjs").object, typeof import("../../media/json/rtti/module.f.mjs").array], undefined]>;
|
|
115
120
|
}>;
|
|
121
|
+
/**
|
|
122
|
+
* The error half of the response envelope: `{ jsonrpc, error, id }`.
|
|
123
|
+
*
|
|
124
|
+
* Exported as one of a pair with {@link successResponseOf} — the `Response`
|
|
125
|
+
* schema, `jsonrpc`, `Id` and `RpcError` are all owned here, so the two shapes
|
|
126
|
+
* built from them are too. Every protocol layered on this module needs both
|
|
127
|
+
* (`fjs/protocol/mcp` and its stdio transport are the two consumers today),
|
|
128
|
+
* and a private constructor is what made each of them re-roll its own.
|
|
129
|
+
*
|
|
130
|
+
* It answers the `Response` union rather than {@link ErrorResponse}, the branch
|
|
131
|
+
* it always builds. That is deliberate: every consumer in the tree is a
|
|
132
|
+
* dispatcher answering either arm — `dispatch` here, `mcpStep` and the stdio
|
|
133
|
+
* transport in `fjs/protocol/mcp` — and `Handle` is defined in terms of the
|
|
134
|
+
* union, so the branch type would have to be widened again at each of them.
|
|
135
|
+
* {@link ErrorResponse} is exported for a caller that does want it.
|
|
136
|
+
*
|
|
137
|
+
* @type {(id: Id) => (error: RpcError) => Response}
|
|
138
|
+
*/
|
|
139
|
+
export declare const errorResponseOf: (id: Id) => (error: RpcError) => Response;
|
|
140
|
+
/**
|
|
141
|
+
* The success half of the response envelope: `{ jsonrpc, result, id }`.
|
|
142
|
+
*
|
|
143
|
+
* The `…Of` suffix pairs with {@link errorResponseOf}, and both name the
|
|
144
|
+
* already-exported `successResponse` / `errorResponse` schemas they build a
|
|
145
|
+
* value of.
|
|
146
|
+
*
|
|
147
|
+
* It answers the union for the reason {@link errorResponseOf} does, and
|
|
148
|
+
* {@link SuccessResponse} names the branch for a caller that wants it.
|
|
149
|
+
*
|
|
150
|
+
* @type {(id: Id) => (result: Unknown) => Response}
|
|
151
|
+
*/
|
|
152
|
+
export declare const successResponseOf: (id: Id) => (result: Unknown) => Response;
|
|
116
153
|
/**
|
|
117
154
|
* Dispatches an already-parsed JSON-RPC value against `handlers`.
|
|
118
155
|
*
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
* @module
|
|
16
16
|
*
|
|
17
17
|
* @import { Unknown } from '../../media/json/types.ts'
|
|
18
|
-
* @import { Id, RpcError, Handlers, Response } from './types.ts'
|
|
18
|
+
* @import { Id, RpcError, Handlers, Response, SuccessResponse, ErrorResponse } from './types.ts'
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { at } from '../../types/object/module.f.mjs'
|
|
22
|
-
import { number, string, or, option } from '../../types/rtti/module.f.mjs'
|
|
22
|
+
import { number, open, string, or, option } from '../../types/rtti/module.f.mjs'
|
|
23
23
|
import { parse } from '../../types/rtti/parse/module.f.mjs'
|
|
24
24
|
import { unknown } from '../../media/json/rtti/module.f.mjs'
|
|
25
25
|
|
|
@@ -32,29 +32,34 @@ export const _id = or(string, number, null)
|
|
|
32
32
|
* A request or notification envelope. `id` present → request (a response is
|
|
33
33
|
* expected); `id` absent → notification (no response). `params` is optional.
|
|
34
34
|
*
|
|
35
|
+
* `open`, as every envelope in this module is: a bare struct is closed, and a
|
|
36
|
+
* peer implementing a later revision of the protocol may send members this one
|
|
37
|
+
* does not name — rejecting those outright is the opposite of what a wire
|
|
38
|
+
* format wants. Do not drop the wrapper.
|
|
39
|
+
*
|
|
35
40
|
* https://www.jsonrpc.org/specification#request_object
|
|
36
41
|
*/
|
|
37
|
-
export const request = /** @type {const} */ ({
|
|
42
|
+
export const request = open(/** @type {const} */ ({
|
|
38
43
|
jsonrpc,
|
|
39
44
|
method: string,
|
|
40
45
|
params: option(unknown),
|
|
41
46
|
id: option(_id),
|
|
42
|
-
})
|
|
47
|
+
}))
|
|
43
48
|
|
|
44
|
-
/** The JSON-RPC error object. */
|
|
45
|
-
export const error = /** @type {const} */ ({
|
|
49
|
+
/** The JSON-RPC error object — `open`, for the reason {@link request} gives. */
|
|
50
|
+
export const error = open(/** @type {const} */ ({
|
|
46
51
|
code: number,
|
|
47
52
|
message: string,
|
|
48
53
|
data: option(unknown),
|
|
49
|
-
})
|
|
54
|
+
}))
|
|
50
55
|
|
|
51
|
-
export const successResponse = /** @type {const} */ ({ jsonrpc, result: unknown, id: _id })
|
|
52
|
-
export const errorResponse = /** @type {const} */ ({ jsonrpc, error, id: _id })
|
|
56
|
+
export const successResponse = open(/** @type {const} */ ({ jsonrpc, result: unknown, id: _id }))
|
|
57
|
+
export const errorResponse = open(/** @type {const} */ ({ jsonrpc, error, id: _id }))
|
|
53
58
|
|
|
54
59
|
/**
|
|
55
60
|
* A response envelope: either a success (`result`) or an error (`error`).
|
|
56
61
|
* Derived from the rtti schema via `Ts<>` — the same declaration is the
|
|
57
|
-
* runtime decoder and the static type, with no drift.
|
|
62
|
+
* runtime decoder and the static type, with no drift. Both arms say `open`
|
|
58
63
|
* (extra keys allowed), so "result XOR error" is not enforced at runtime; in
|
|
59
64
|
* practice the dispatcher only ever constructs one or the other.
|
|
60
65
|
*
|
|
@@ -78,8 +83,39 @@ export const methodNotFound = rpcError(-32601)('Method not found')
|
|
|
78
83
|
export const invalidParams = rpcError(-32602)('Invalid params')
|
|
79
84
|
export const internalError = rpcError(-32603)('Internal error')
|
|
80
85
|
|
|
81
|
-
/**
|
|
82
|
-
|
|
86
|
+
/**
|
|
87
|
+
* The error half of the response envelope: `{ jsonrpc, error, id }`.
|
|
88
|
+
*
|
|
89
|
+
* Exported as one of a pair with {@link successResponseOf} — the `Response`
|
|
90
|
+
* schema, `jsonrpc`, `Id` and `RpcError` are all owned here, so the two shapes
|
|
91
|
+
* built from them are too. Every protocol layered on this module needs both
|
|
92
|
+
* (`fjs/protocol/mcp` and its stdio transport are the two consumers today),
|
|
93
|
+
* and a private constructor is what made each of them re-roll its own.
|
|
94
|
+
*
|
|
95
|
+
* It answers the `Response` union rather than {@link ErrorResponse}, the branch
|
|
96
|
+
* it always builds. That is deliberate: every consumer in the tree is a
|
|
97
|
+
* dispatcher answering either arm — `dispatch` here, `mcpStep` and the stdio
|
|
98
|
+
* transport in `fjs/protocol/mcp` — and `Handle` is defined in terms of the
|
|
99
|
+
* union, so the branch type would have to be widened again at each of them.
|
|
100
|
+
* {@link ErrorResponse} is exported for a caller that does want it.
|
|
101
|
+
*
|
|
102
|
+
* @type {(id: Id) => (error: RpcError) => Response}
|
|
103
|
+
*/
|
|
104
|
+
export const errorResponseOf = id => error => ({ jsonrpc, error, id })
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The success half of the response envelope: `{ jsonrpc, result, id }`.
|
|
108
|
+
*
|
|
109
|
+
* The `…Of` suffix pairs with {@link errorResponseOf}, and both name the
|
|
110
|
+
* already-exported `successResponse` / `errorResponse` schemas they build a
|
|
111
|
+
* value of.
|
|
112
|
+
*
|
|
113
|
+
* It answers the union for the reason {@link errorResponseOf} does, and
|
|
114
|
+
* {@link SuccessResponse} names the branch for a caller that wants it.
|
|
115
|
+
*
|
|
116
|
+
* @type {(id: Id) => (result: Unknown) => Response}
|
|
117
|
+
*/
|
|
118
|
+
export const successResponseOf = id => result => ({ jsonrpc, result, id })
|
|
83
119
|
|
|
84
120
|
/**
|
|
85
121
|
* Dispatches an already-parsed JSON-RPC value against `handlers`.
|
|
@@ -110,6 +146,6 @@ export const dispatch = handlers => value => {
|
|
|
110
146
|
}
|
|
111
147
|
const [t2, result] = handler(params)
|
|
112
148
|
return t2 === 'ok'
|
|
113
|
-
?
|
|
149
|
+
? successResponseOf(id)(result)
|
|
114
150
|
: errorResponseOf(id)(result)
|
|
115
151
|
}
|
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
methodNotFound,
|
|
16
16
|
invalidParams,
|
|
17
17
|
internalError,
|
|
18
|
+
response,
|
|
19
|
+
errorResponseOf,
|
|
20
|
+
successResponseOf,
|
|
18
21
|
} from './module.f.mjs'
|
|
19
22
|
|
|
20
23
|
/** @type {(r: readonly [string, unknown]) => boolean} */
|
|
@@ -102,4 +105,30 @@ export const proof = {
|
|
|
102
105
|
assertEq(r, null)
|
|
103
106
|
},
|
|
104
107
|
},
|
|
108
|
+
|
|
109
|
+
// The two envelope constructors are public API, so they are proven
|
|
110
|
+
// directly rather than only through `dispatch`. Each result is parsed back
|
|
111
|
+
// with this module's own `response` schema, which is the fact worth
|
|
112
|
+
// pinning: the constructor and the schema describing the shape it builds
|
|
113
|
+
// cannot drift apart.
|
|
114
|
+
responseOf: {
|
|
115
|
+
success: () => {
|
|
116
|
+
const r = successResponseOf(1)('pong')
|
|
117
|
+
assertEq(r.jsonrpc, '2.0')
|
|
118
|
+
assert('result' in r && r.result === 'pong', r)
|
|
119
|
+
assertEq(r.id, 1)
|
|
120
|
+
assert(isOk(parse(response)(r)), r)
|
|
121
|
+
},
|
|
122
|
+
// A non-`null` id on purpose: an `id` forced to `null` is a mutation
|
|
123
|
+
// this case would not see if it asked for `null` to begin with, and a
|
|
124
|
+
// string exercises the other arm of `Id` besides. `errorResponseOf`'s
|
|
125
|
+
// `null` id is covered by `dispatch.invalidRequest`.
|
|
126
|
+
error: () => {
|
|
127
|
+
const r = errorResponseOf('abc')(parseError)
|
|
128
|
+
assertEq(r.jsonrpc, '2.0')
|
|
129
|
+
assert('error' in r && r.error.code === -32700, r)
|
|
130
|
+
assertEq(r.id, 'abc')
|
|
131
|
+
assert(isOk(parse(response)(r)), r)
|
|
132
|
+
},
|
|
133
|
+
},
|
|
105
134
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Unknown } from '../../media/json/types.ts';
|
|
9
9
|
import type { Result } from '../../types/result/types.ts';
|
|
10
|
-
import type { _id, request, error as errorSchema, response } from './module.f.mjs';
|
|
10
|
+
import type { _id, request, error as errorSchema, response, successResponse, errorResponse } from './module.f.mjs';
|
|
11
11
|
import type { Ts } from '../../types/rtti/ts/types.ts';
|
|
12
12
|
export type Id = Ts<typeof _id>;
|
|
13
13
|
export type Request = Ts<typeof request>;
|
|
@@ -15,13 +15,23 @@ export type RpcError = Ts<typeof errorSchema>;
|
|
|
15
15
|
/**
|
|
16
16
|
* A response envelope: either a success (`result`) or an error (`error`).
|
|
17
17
|
* Derived from the rtti schema via `Ts<>` — the same declaration is the
|
|
18
|
-
* runtime decoder and the static type, with no drift.
|
|
18
|
+
* runtime decoder and the static type, with no drift. Both arms say `open`
|
|
19
19
|
* (extra keys allowed), so "result XOR error" is not enforced at runtime; in
|
|
20
20
|
* practice the dispatcher only ever constructs one or the other.
|
|
21
21
|
*
|
|
22
22
|
* https://www.jsonrpc.org/specification#response_object
|
|
23
23
|
*/
|
|
24
24
|
export type Response = Ts<typeof response>;
|
|
25
|
+
/**
|
|
26
|
+
* The success branch of {@link Response}: `result` is present, `error` is not.
|
|
27
|
+
*
|
|
28
|
+
* `successResponseOf` answers the `Response` union, not this — every consumer
|
|
29
|
+
* in the tree is a dispatcher that returns either arm. Name this where a
|
|
30
|
+
* caller genuinely holds one branch and wants `.result` without an `in` check.
|
|
31
|
+
*/
|
|
32
|
+
export type SuccessResponse = Ts<typeof successResponse>;
|
|
33
|
+
/** The error branch of {@link Response}, the mirror of {@link SuccessResponse}. */
|
|
34
|
+
export type ErrorResponse = Ts<typeof errorResponse>;
|
|
25
35
|
/** A method implementation: maps `params` to a result or an `RpcError`. */
|
|
26
36
|
export type Handler = (params: Unknown | undefined) => Result<Unknown, RpcError>;
|
|
27
37
|
/** A `method` name → `Handler` map. */
|