@reventlessdev/reventless-spec 3.0.0-alpha.143 → 3.0.0-alpha.145
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/CHANGELOG.md +20 -0
- package/package.json +2 -2
- package/run-certify-trait.mjs +2 -1
- package/run-generator.mjs +2 -1
- package/run-graft-trait.mjs +2 -1
- package/run-platform-generator.mjs +2 -1
- package/run-trait-manifest.mjs +2 -1
- package/src/CliArgs.res +209 -0
- package/src/CliArgs.res.mjs +325 -0
- package/src/components/DcbScopeInference.res +39 -1
- package/src/components/DcbScopeInference.res.mjs +20 -0
- package/src/components/DcbTag.res +68 -3
- package/src/components/DcbTag.res.mjs +76 -12
- package/src/components/DcbValidation.res +42 -1
- package/src/components/DcbValidation.res.mjs +19 -2
- package/src/generator/CertifyTrait.res +96 -93
- package/src/generator/CertifyTrait.res.mjs +92 -75
- package/src/generator/GraftTrait.res +147 -148
- package/src/generator/GraftTrait.res.mjs +131 -128
- package/src/generator/PlatformGenerator.res +50 -31
- package/src/generator/PlatformGenerator.res.mjs +107 -76
- package/src/generator/PluginGenerator.res +29 -29
- package/src/generator/PluginGenerator.res.mjs +87 -55
- package/src/generator/TraitManifestCli.res +69 -68
- package/src/generator/TraitManifestCli.res.mjs +67 -51
- package/src/lifecycle/CheckLifecycleModel.res +203 -189
- package/src/lifecycle/CheckLifecycleModel.res.mjs +146 -141
- package/src/types/PrepareAccounts.res +38 -57
- package/src/types/PrepareAccounts.res.mjs +28 -82
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.145 (2026-09-24)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **dcb:** a command reference no decision reads by stays out of the query ([3ae149a](https://github.com/ReventlessDev/reventless-core/commit/3ae149ae91ebab63413be57df5827c5fd4390160))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.144 (2026-09-23)
|
|
14
|
+
|
|
15
|
+
* feat(node)!: every command-line tool reads its arguments through parseArgs ([2e41979](https://github.com/ReventlessDev/reventless-core/commit/2e41979b73646c46356e197e17eb4a3da0154c73))
|
|
16
|
+
|
|
17
|
+
### BREAKING CHANGES
|
|
18
|
+
|
|
19
|
+
* NodeProcess.onSignal takes the new NodeProcess.signal
|
|
20
|
+
variant (SIGINT | SIGTERM | SIGHUP) instead of a polymorphic variant:
|
|
21
|
+
write onSignal(SIGINT, …) for onSignal(#SIGINT, …). Node receives the
|
|
22
|
+
same string.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
# 3.0.0-alpha.143 (2026-09-23)
|
|
7
27
|
|
|
8
28
|
**Note:** Version bump only for package @reventlessdev/reventless-spec
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-spec",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.145",
|
|
4
4
|
"description": "Specifications for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"sury": "11.0.0",
|
|
27
27
|
"sury-ppx": "11.0.0",
|
|
28
28
|
"yaml": "^2.8.3",
|
|
29
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.
|
|
29
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.15"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"rescript": "12.3.1",
|
package/run-certify-trait.mjs
CHANGED
package/run-generator.mjs
CHANGED
package/run-graft-trait.mjs
CHANGED
package/run-trait-manifest.mjs
CHANGED
package/src/CliArgs.res
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/***
|
|
2
|
+
How every command-line tool in this repository reads `process.argv`: through
|
|
3
|
+
Node's `parseArgs`, with mistakes worded the way these tools word them, and one
|
|
4
|
+
behaviour on `--help`, on a usage mistake and on a bare invocation (see [run]).
|
|
5
|
+
Non-strict with tokens rather than strict mode, because strict mode names the
|
|
6
|
+
offending argument only inside English prose.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** An undeclared `--key` under `~pairs=true`: with a value, or on its own. */
|
|
10
|
+
type value =
|
|
11
|
+
| Text(string)
|
|
12
|
+
| Present
|
|
13
|
+
|
|
14
|
+
type t = {
|
|
15
|
+
values: NodeUtil.values,
|
|
16
|
+
positionals: array<string>,
|
|
17
|
+
pairs: array<(string, value)>,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A usage mistake. A tool that ran and failed exits 1, so a caller can tell a
|
|
21
|
+
misinvocation from a run whose answer was no. */
|
|
22
|
+
let usageMistakeExit = 2
|
|
23
|
+
|
|
24
|
+
let unknown = (arg: string) => `unknown argument "${arg}"`
|
|
25
|
+
|
|
26
|
+
/** A positional the tool has no place for, worded like any other unknown argument. */
|
|
27
|
+
let extra = (arg: string): result<'a, string> => Error(unknown(arg))
|
|
28
|
+
|
|
29
|
+
/** `-` alone is a value (stdin, by convention); anything else starting with a
|
|
30
|
+
dash is the next flag, which a string option must not swallow. */
|
|
31
|
+
let flagShaped = (value: string) => value->String.startsWith("-") && value != "-"
|
|
32
|
+
|
|
33
|
+
/** Package managers can forward a bare `--` ahead of a script's own arguments.
|
|
34
|
+
Anywhere else it ends the options, as POSIX says. */
|
|
35
|
+
let withoutLeadingTerminator = (argv: array<string>) =>
|
|
36
|
+
argv->Array.get(0) == Some("--") ? argv->Array.slice(~start=1, ~end=Array.length(argv)) : argv
|
|
37
|
+
|
|
38
|
+
/** Parse against the declared options. `-h`/`--help` is declared for every
|
|
39
|
+
tool. The first mistake in argv order is the one reported. Under
|
|
40
|
+
`~pairs=true` an undeclared long option is kept as a `(key, value)` pair
|
|
41
|
+
instead of refused, taking the argument after it as its value when that
|
|
42
|
+
argument is not itself an option. */
|
|
43
|
+
let parse = (
|
|
44
|
+
~strings: array<string>=[],
|
|
45
|
+
~bools: array<string>=[],
|
|
46
|
+
~lists: array<string>=[],
|
|
47
|
+
~pairs: bool=false,
|
|
48
|
+
argv: array<string>,
|
|
49
|
+
): result<t, string> => {
|
|
50
|
+
let args = withoutLeadingTerminator(argv)
|
|
51
|
+
let options: dict<NodeUtil.optionConfig> = Dict.make()
|
|
52
|
+
options->Dict.set("help", {type_: Boolean, short: "h"})
|
|
53
|
+
bools->Array.forEach(name => options->Dict.set(name, {type_: Boolean}))
|
|
54
|
+
strings->Array.forEach(name => options->Dict.set(name, {type_: String}))
|
|
55
|
+
lists->Array.forEach(name => options->Dict.set(name, {type_: String, multiple: true}))
|
|
56
|
+
|
|
57
|
+
let parsed = NodeUtil.parseArgs({
|
|
58
|
+
args,
|
|
59
|
+
options,
|
|
60
|
+
strict: false,
|
|
61
|
+
allowPositionals: true,
|
|
62
|
+
tokens: true,
|
|
63
|
+
})
|
|
64
|
+
let tokens = parsed.tokens->Option.getOr([])
|
|
65
|
+
let positionals = []
|
|
66
|
+
let found = []
|
|
67
|
+
|
|
68
|
+
// Non-strict `parseArgs` types an undeclared option as a boolean and emits
|
|
69
|
+
// the argument after it as the next positional token, so a pair's value is
|
|
70
|
+
// read back from token order.
|
|
71
|
+
let rec walk = i =>
|
|
72
|
+
switch tokens->Array.get(i) {
|
|
73
|
+
| None => Ok()
|
|
74
|
+
| Some(token) =>
|
|
75
|
+
switch token.kind {
|
|
76
|
+
| Terminator => walk(i + 1)
|
|
77
|
+
| Positional =>
|
|
78
|
+
positionals->Array.push(token.value->Option.getOr(""))
|
|
79
|
+
walk(i + 1)
|
|
80
|
+
| Flag =>
|
|
81
|
+
let name = token.name->Option.getOr("")
|
|
82
|
+
let rawName = token.rawName->Option.getOr(name)
|
|
83
|
+
let inline = token.inlineValue->Option.getOr(false)
|
|
84
|
+
switch (options->Dict.get(name), token.value) {
|
|
85
|
+
| (Some({type_: String}), None) => Error(`${rawName} needs a value`)
|
|
86
|
+
| (Some({type_: String}), Some(v)) if !inline && flagShaped(v) =>
|
|
87
|
+
Error(`${rawName} needs a value`)
|
|
88
|
+
| (Some({type_: Boolean}), Some(_)) => Error(`${rawName} takes no value`)
|
|
89
|
+
| (Some(_), _) => walk(i + 1)
|
|
90
|
+
| (None, _) if !pairs || !(rawName->String.startsWith("--")) => Error(unknown(rawName))
|
|
91
|
+
| (None, Some(v)) =>
|
|
92
|
+
found->Array.push((name, Text(v)))
|
|
93
|
+
walk(i + 1)
|
|
94
|
+
| (None, None) =>
|
|
95
|
+
switch tokens->Array.get(i + 1) {
|
|
96
|
+
| Some({kind: Positional, index, value: ?Some(v)}) if index == token.index + 1 =>
|
|
97
|
+
found->Array.push((name, Text(v)))
|
|
98
|
+
walk(i + 2)
|
|
99
|
+
| _ =>
|
|
100
|
+
found->Array.push((name, Present))
|
|
101
|
+
walk(i + 1)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
walk(0)->Result.map(() => {values: parsed.values, positionals, pairs: found})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let string = (t: t, name: string): option<string> => t.values->NodeUtil.string(name)
|
|
111
|
+
let bool = (t: t, name: string): bool => t.values->NodeUtil.bool(name)->Option.getOr(false)
|
|
112
|
+
let strings = (t: t, name: string): array<string> =>
|
|
113
|
+
t.values->NodeUtil.strings(name)->Option.getOr([])
|
|
114
|
+
let positionals = (t: t): array<string> => t.positionals
|
|
115
|
+
let pairs = (t: t): array<(string, value)> => t.pairs
|
|
116
|
+
let help = (t: t): bool => t->bool("help")
|
|
117
|
+
|
|
118
|
+
/** For a tool that takes at most `max` positionals: the first beyond them is
|
|
119
|
+
refused as [extra]. */
|
|
120
|
+
let atMost = (t: t, max: int): result<t, string> =>
|
|
121
|
+
switch t.positionals->Array.get(max) {
|
|
122
|
+
| None => Ok(t)
|
|
123
|
+
| Some(arg) => extra(arg)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let noPositionals = (t: t): result<t, string> => t->atMost(0)
|
|
127
|
+
|
|
128
|
+
/** Whether `-h` or `--help` appears before any `--` that ends the options.
|
|
129
|
+
Read off argv rather than a parse, so it answers even when the other
|
|
130
|
+
arguments are wrong. */
|
|
131
|
+
let asksForHelp = (argv: array<string>): bool => {
|
|
132
|
+
let argv = withoutLeadingTerminator(argv)
|
|
133
|
+
let options = switch argv->Array.indexOf("--") {
|
|
134
|
+
| -1 => argv
|
|
135
|
+
| end => argv->Array.slice(~start=0, ~end)
|
|
136
|
+
}
|
|
137
|
+
options->Array.some(arg => arg == "-h" || arg == "--help")
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** A tool's command line: its name, its usage (first line `Usage: <bin> …`),
|
|
141
|
+
and how it turns argv into its arguments. */
|
|
142
|
+
type cli<'args> = {
|
|
143
|
+
bin: string,
|
|
144
|
+
usage: string,
|
|
145
|
+
parse: array<string> => result<'args, string>,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Where [run] writes and how it ends the process, so a test can watch it. */
|
|
149
|
+
type io = {
|
|
150
|
+
out: string => unit,
|
|
151
|
+
err: string => unit,
|
|
152
|
+
exit: int => unit,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let processIo = {
|
|
156
|
+
out: text => Console.log(text),
|
|
157
|
+
err: text => Console.error(text),
|
|
158
|
+
exit: NodeProcess.exit,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Help anywhere prints the usage to stdout and exits 0, whatever else is
|
|
162
|
+
wrong. A usage mistake prints `<bin>: <message>`, a blank line and the usage
|
|
163
|
+
to stderr and exits 2. Otherwise `main` gets the arguments, and its own
|
|
164
|
+
failures keep their exit 1. */
|
|
165
|
+
let run = async (
|
|
166
|
+
cli: cli<'args>,
|
|
167
|
+
~argv: option<array<string>>=?,
|
|
168
|
+
~io: io=processIo,
|
|
169
|
+
main: 'args => promise<unit>,
|
|
170
|
+
): unit => {
|
|
171
|
+
let argv =
|
|
172
|
+
argv->Option.getOr(NodeProcess.argv->Array.slice(~start=2, ~end=NodeProcess.argv->Array.length))
|
|
173
|
+
let usage = cli.usage->String.trim
|
|
174
|
+
if asksForHelp(argv) {
|
|
175
|
+
io.out(usage)
|
|
176
|
+
io.exit(0)
|
|
177
|
+
} else {
|
|
178
|
+
switch cli.parse(argv) {
|
|
179
|
+
| Error(message) =>
|
|
180
|
+
io.err(`${cli.bin}: ${message}\n\n${usage}`)
|
|
181
|
+
io.exit(usageMistakeExit)
|
|
182
|
+
| Ok(args) => await main(args)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** What [run] did with one argv, for a tool's own tests. */
|
|
188
|
+
type observed = {
|
|
189
|
+
stdout: array<string>,
|
|
190
|
+
stderr: array<string>,
|
|
191
|
+
exitCode: option<int>,
|
|
192
|
+
reachedMain: bool,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Run a tool's command line without a process: nothing is printed, nothing
|
|
196
|
+
exits, and `main` is not called — only whether it would have been. */
|
|
197
|
+
let observe = async (cli: cli<'args>, argv: array<string>): observed => {
|
|
198
|
+
let stdout = []
|
|
199
|
+
let stderr = []
|
|
200
|
+
let exitCode = ref(None)
|
|
201
|
+
let reachedMain = ref(false)
|
|
202
|
+
let io = {
|
|
203
|
+
out: text => stdout->Array.push(text),
|
|
204
|
+
err: text => stderr->Array.push(text),
|
|
205
|
+
exit: code => exitCode := Some(code),
|
|
206
|
+
}
|
|
207
|
+
await run(cli, ~argv, ~io, async _ => reachedMain := true)
|
|
208
|
+
{stdout, stderr, exitCode: exitCode.contents, reachedMain: reachedMain.contents}
|
|
209
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Nodeutil from "node:util";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
6
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
7
|
+
|
|
8
|
+
function unknown(arg) {
|
|
9
|
+
return `unknown argument "` + arg + `"`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function extra(arg) {
|
|
13
|
+
return {
|
|
14
|
+
TAG: "Error",
|
|
15
|
+
_0: unknown(arg)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function flagShaped(value) {
|
|
20
|
+
if (value.startsWith("-")) {
|
|
21
|
+
return value !== "-";
|
|
22
|
+
} else {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function withoutLeadingTerminator(argv) {
|
|
28
|
+
if (Primitive_object.equal(argv[0], "--")) {
|
|
29
|
+
return argv.slice(1, argv.length);
|
|
30
|
+
} else {
|
|
31
|
+
return argv;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parse(stringsOpt, boolsOpt, listsOpt, pairsOpt, argv) {
|
|
36
|
+
let strings = stringsOpt !== undefined ? stringsOpt : [];
|
|
37
|
+
let bools = boolsOpt !== undefined ? boolsOpt : [];
|
|
38
|
+
let lists = listsOpt !== undefined ? listsOpt : [];
|
|
39
|
+
let pairs = pairsOpt !== undefined ? pairsOpt : false;
|
|
40
|
+
let args = withoutLeadingTerminator(argv);
|
|
41
|
+
let options = {};
|
|
42
|
+
options["help"] = {
|
|
43
|
+
type: "boolean",
|
|
44
|
+
short: "h"
|
|
45
|
+
};
|
|
46
|
+
bools.forEach(name => {
|
|
47
|
+
options[name] = {
|
|
48
|
+
type: "boolean"
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
strings.forEach(name => {
|
|
52
|
+
options[name] = {
|
|
53
|
+
type: "string"
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
lists.forEach(name => {
|
|
57
|
+
options[name] = {
|
|
58
|
+
type: "string",
|
|
59
|
+
multiple: true
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
let parsed = Nodeutil.parseArgs({
|
|
63
|
+
args: args,
|
|
64
|
+
options: options,
|
|
65
|
+
strict: false,
|
|
66
|
+
allowPositionals: true,
|
|
67
|
+
tokens: true
|
|
68
|
+
});
|
|
69
|
+
let tokens = Stdlib_Option.getOr(parsed.tokens, []);
|
|
70
|
+
let positionals = [];
|
|
71
|
+
let found = [];
|
|
72
|
+
let walk = _i => {
|
|
73
|
+
while (true) {
|
|
74
|
+
let i = _i;
|
|
75
|
+
let token = tokens[i];
|
|
76
|
+
if (token === undefined) {
|
|
77
|
+
return {
|
|
78
|
+
TAG: "Ok",
|
|
79
|
+
_0: undefined
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
let match = token.kind;
|
|
83
|
+
switch (match) {
|
|
84
|
+
case "option" :
|
|
85
|
+
let name = Stdlib_Option.getOr(token.name, "");
|
|
86
|
+
let rawName = Stdlib_Option.getOr(token.rawName, name);
|
|
87
|
+
let inline = Stdlib_Option.getOr(token.inlineValue, false);
|
|
88
|
+
let match$1 = options[name];
|
|
89
|
+
let match$2 = token.value;
|
|
90
|
+
if (match$1 !== undefined) {
|
|
91
|
+
if (match$1.type === "string") {
|
|
92
|
+
if (match$2 === undefined) {
|
|
93
|
+
return {
|
|
94
|
+
TAG: "Error",
|
|
95
|
+
_0: rawName + ` needs a value`
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (!inline && flagShaped(match$2)) {
|
|
99
|
+
return {
|
|
100
|
+
TAG: "Error",
|
|
101
|
+
_0: rawName + ` needs a value`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
_i = i + 1 | 0;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (match$2 !== undefined) {
|
|
108
|
+
return {
|
|
109
|
+
TAG: "Error",
|
|
110
|
+
_0: rawName + ` takes no value`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
_i = i + 1 | 0;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!pairs || !rawName.startsWith("--")) {
|
|
117
|
+
return {
|
|
118
|
+
TAG: "Error",
|
|
119
|
+
_0: unknown(rawName)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (match$2 !== undefined) {
|
|
123
|
+
found.push([
|
|
124
|
+
name,
|
|
125
|
+
{
|
|
126
|
+
TAG: "Text",
|
|
127
|
+
_0: match$2
|
|
128
|
+
}
|
|
129
|
+
]);
|
|
130
|
+
_i = i + 1 | 0;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
let match$3 = tokens[i + 1 | 0];
|
|
134
|
+
if (match$3 !== undefined) {
|
|
135
|
+
switch (match$3.kind) {
|
|
136
|
+
case "positional" :
|
|
137
|
+
let v = match$3.value;
|
|
138
|
+
if (v !== undefined && match$3.index === (token.index + 1 | 0)) {
|
|
139
|
+
found.push([
|
|
140
|
+
name,
|
|
141
|
+
{
|
|
142
|
+
TAG: "Text",
|
|
143
|
+
_0: v
|
|
144
|
+
}
|
|
145
|
+
]);
|
|
146
|
+
_i = i + 2 | 0;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case "option" :
|
|
151
|
+
case "option-terminator" :
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
found.push([
|
|
156
|
+
name,
|
|
157
|
+
"Present"
|
|
158
|
+
]);
|
|
159
|
+
_i = i + 1 | 0;
|
|
160
|
+
continue;
|
|
161
|
+
break;
|
|
162
|
+
case "positional" :
|
|
163
|
+
positionals.push(Stdlib_Option.getOr(token.value, ""));
|
|
164
|
+
_i = i + 1 | 0;
|
|
165
|
+
continue;
|
|
166
|
+
case "option-terminator" :
|
|
167
|
+
_i = i + 1 | 0;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
return Stdlib_Result.map(walk(0), () => ({
|
|
173
|
+
values: parsed.values,
|
|
174
|
+
positionals: positionals,
|
|
175
|
+
pairs: found
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function string(t, name) {
|
|
180
|
+
return t.values[name];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function bool(t, name) {
|
|
184
|
+
return Stdlib_Option.getOr(t.values[name], false);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function strings(t, name) {
|
|
188
|
+
return Stdlib_Option.getOr(t.values[name], []);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function positionals(t) {
|
|
192
|
+
return t.positionals;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function pairs(t) {
|
|
196
|
+
return t.pairs;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function help(t) {
|
|
200
|
+
return bool(t, "help");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function atMost(t, max) {
|
|
204
|
+
let arg = t.positionals[max];
|
|
205
|
+
if (arg !== undefined) {
|
|
206
|
+
return {
|
|
207
|
+
TAG: "Error",
|
|
208
|
+
_0: unknown(arg)
|
|
209
|
+
};
|
|
210
|
+
} else {
|
|
211
|
+
return {
|
|
212
|
+
TAG: "Ok",
|
|
213
|
+
_0: t
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function noPositionals(t) {
|
|
219
|
+
return atMost(t, 0);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function asksForHelp(argv) {
|
|
223
|
+
let argv$1 = withoutLeadingTerminator(argv);
|
|
224
|
+
let end = argv$1.indexOf("--");
|
|
225
|
+
let options = end !== -1 ? argv$1.slice(0, end) : argv$1;
|
|
226
|
+
return options.some(arg => {
|
|
227
|
+
if (arg === "-h") {
|
|
228
|
+
return true;
|
|
229
|
+
} else {
|
|
230
|
+
return arg === "--help";
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function processIo_out(text) {
|
|
236
|
+
console.log(text);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function processIo_err(text) {
|
|
240
|
+
console.error(text);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function processIo_exit(prim) {
|
|
244
|
+
process.exit(prim);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let processIo = {
|
|
248
|
+
out: processIo_out,
|
|
249
|
+
err: processIo_err,
|
|
250
|
+
exit: processIo_exit
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
async function run(cli, argv, ioOpt, main) {
|
|
254
|
+
let io = ioOpt !== undefined ? ioOpt : processIo;
|
|
255
|
+
let argv$1 = Stdlib_Option.getOr(argv, process.argv.slice(2, process.argv.length));
|
|
256
|
+
let usage = cli.usage.trim();
|
|
257
|
+
if (asksForHelp(argv$1)) {
|
|
258
|
+
io.out(usage);
|
|
259
|
+
return io.exit(0);
|
|
260
|
+
}
|
|
261
|
+
let message = cli.parse(argv$1);
|
|
262
|
+
if (message.TAG === "Ok") {
|
|
263
|
+
return await main(message._0);
|
|
264
|
+
}
|
|
265
|
+
io.err(cli.bin + `: ` + message._0 + `\n\n` + usage);
|
|
266
|
+
return io.exit(2);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function observe(cli, argv) {
|
|
270
|
+
let stdout = [];
|
|
271
|
+
let stderr = [];
|
|
272
|
+
let exitCode = {
|
|
273
|
+
contents: undefined
|
|
274
|
+
};
|
|
275
|
+
let reachedMain = {
|
|
276
|
+
contents: false
|
|
277
|
+
};
|
|
278
|
+
let io_out = text => {
|
|
279
|
+
stdout.push(text);
|
|
280
|
+
};
|
|
281
|
+
let io_err = text => {
|
|
282
|
+
stderr.push(text);
|
|
283
|
+
};
|
|
284
|
+
let io_exit = code => {
|
|
285
|
+
exitCode.contents = code;
|
|
286
|
+
};
|
|
287
|
+
let io = {
|
|
288
|
+
out: io_out,
|
|
289
|
+
err: io_err,
|
|
290
|
+
exit: io_exit
|
|
291
|
+
};
|
|
292
|
+
await run(cli, argv, io, async param => {
|
|
293
|
+
reachedMain.contents = true;
|
|
294
|
+
});
|
|
295
|
+
return {
|
|
296
|
+
stdout: stdout,
|
|
297
|
+
stderr: stderr,
|
|
298
|
+
exitCode: exitCode.contents,
|
|
299
|
+
reachedMain: reachedMain.contents
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let usageMistakeExit = 2;
|
|
304
|
+
|
|
305
|
+
export {
|
|
306
|
+
usageMistakeExit,
|
|
307
|
+
unknown,
|
|
308
|
+
extra,
|
|
309
|
+
flagShaped,
|
|
310
|
+
withoutLeadingTerminator,
|
|
311
|
+
parse,
|
|
312
|
+
string,
|
|
313
|
+
bool,
|
|
314
|
+
strings,
|
|
315
|
+
positionals,
|
|
316
|
+
pairs,
|
|
317
|
+
help,
|
|
318
|
+
atMost,
|
|
319
|
+
noPositionals,
|
|
320
|
+
asksForHelp,
|
|
321
|
+
processIo,
|
|
322
|
+
run,
|
|
323
|
+
observe,
|
|
324
|
+
}
|
|
325
|
+
/* node:util Not a pure module */
|
|
@@ -10,7 +10,7 @@ a tag-metadata flag. That is precisely what we are replacing — so both the run
|
|
|
10
10
|
VS Code tooling (building shapes from parsed `.res` source) can feed the same
|
|
11
11
|
`infer`. See `docs/plans/done/dcb-tag-scope-inference.md` § "Phase 1 design".
|
|
12
12
|
|
|
13
|
-
The
|
|
13
|
+
The four rules (over the representation):
|
|
14
14
|
|
|
15
15
|
1. **Owner / partition.** A slice's partition key is the key its *own* emitted
|
|
16
16
|
events are identified by — computed as `producedKeys(S)` minus the keys `S`
|
|
@@ -33,6 +33,12 @@ The three rules (over the representation):
|
|
|
33
33
|
producing slice's own partition key. Foreign reference keys (e.g. `categoryId`
|
|
34
34
|
on `ProductAdded`) are payload ⇒ not indexed ⇒ the sibling-leak GSI write never
|
|
35
35
|
happens.
|
|
36
|
+
|
|
37
|
+
4. **Command scope.** A scalar key on a *command* is a query tag iff it is the
|
|
38
|
+
slice's partition or a key the slice reads off a foreign event (rule 2's
|
|
39
|
+
candidates). `ShipOrder({orderId, carrierId})` names the carrier as information:
|
|
40
|
+
AND-ing it into the clause would hide every order event that does not carry it,
|
|
41
|
+
so it is payload. See `commandPayloadKeys`.
|
|
36
42
|
*/
|
|
37
43
|
/** A field the slice graph treats as an entity identity.
|
|
38
44
|
|
|
@@ -55,6 +61,9 @@ type idField = {
|
|
|
55
61
|
byTag?: bool,
|
|
56
62
|
/** The tag key, when the field's type says it (an identity) rather than its name. */
|
|
57
63
|
key?: string,
|
|
64
|
+
/** The author asked for the tag (`@dcbTag`, `@partitionTag`, `@crossPartition`,
|
|
65
|
+
`@compositePartitionTag`), so a command keeps it in its query (rule 4). */
|
|
66
|
+
declared?: bool,
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
/** One variant arm: its constructor name and the `*Id` fields it carries. */
|
|
@@ -397,6 +406,35 @@ let crossPartitionForSlice = (s: sliceShape): array<string> => {
|
|
|
397
406
|
foreign->Array.filter(k => Some(k) != partition && scalar->Array.includes(k))
|
|
398
407
|
}
|
|
399
408
|
|
|
409
|
+
/**
|
|
410
|
+
Rule 4: the scalar command keys that stay out of the slice's decision query.
|
|
411
|
+
|
|
412
|
+
A key stays in when it is the partition, when the slice reads it off a foreign
|
|
413
|
+
event, when the boundary reads it across partitions, when the author declared it
|
|
414
|
+
on the command or on one of the slice's own arms (an M:N capacity read's
|
|
415
|
+
`@crossPartition` sits on the event), or when the command also carries it as a
|
|
416
|
+
list. Anything else only names another thing, and the fence is the partition.
|
|
417
|
+
*/
|
|
418
|
+
let commandPayloadKeys = (s: sliceShape, ~partition: string, ~crossPartition: array<string>): array<
|
|
419
|
+
string,
|
|
420
|
+
> => {
|
|
421
|
+
let foreign = foreignConsumedKeys(s)
|
|
422
|
+
let declared = (f: idField) => f.declared->Option.getOr(false)
|
|
423
|
+
let pinned =
|
|
424
|
+
s.command
|
|
425
|
+
->Array.filter(f => f.isList || declared(f))
|
|
426
|
+
->Array.concat(
|
|
427
|
+
Array.concat(s.consumed, s.produced)->Array.flatMap(e => e.idFields->Array.filter(declared)),
|
|
428
|
+
)
|
|
429
|
+
->Array.map(tagKeyOf)
|
|
430
|
+
commandScalarKeys(s)->Array.filter(k =>
|
|
431
|
+
k != partition &&
|
|
432
|
+
!(foreign->Array.includes(k)) &&
|
|
433
|
+
!(crossPartition->Array.includes(k)) &&
|
|
434
|
+
!(pinned->Array.includes(k))
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
|
|
400
438
|
let infer = (slices: array<sliceShape>): derived => {
|
|
401
439
|
let {partitionBySlice, ambiguities} = resolvePartitions(slices)
|
|
402
440
|
|