@reventlessdev/reventless-spec 3.0.0-alpha.169 → 3.0.0-alpha.171
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 +32 -0
- package/README.md +17 -3
- package/package.json +5 -4
- package/run-rename-to-decision.mjs +3 -0
- package/src/AnsiStyle.res +3 -3
- package/src/CliArgs.res +1 -1
- package/src/LogPrefix.res +1 -1
- package/src/codemod/RenameToDecision.res +207 -0
- package/src/codemod/RenameToDecision.res.mjs +334 -0
- package/src/components/Aggregate.res +1 -1
- package/src/components/CapabilityManifest.res +1 -1
- package/src/components/ComponentKind.res +2 -2
- package/src/components/ComponentKind.res.mjs +1 -1
- package/src/components/DcbScopeInference.res +2 -2
- package/src/components/DcbTag.res +5 -5
- package/src/components/OutboundTranslationSlice.res +2 -2
- package/src/components/Plugin.res +14 -14
- package/src/components/Plugin.res.mjs +8 -8
- package/src/components/Sensitive.res +1 -1
- package/src/components/Snapshot.res +5 -5
- package/src/components/StateAnnotations.res +1 -1
- package/src/components/StateChangeSlice.res +6 -5
- package/src/components/StateViewSlice.res +3 -3
- package/src/components/TaggedUnion.res +1 -1
- package/src/generator/Codegen.res +5 -5
- package/src/generator/Codegen.res.mjs +1 -1
- package/src/generator/Discovery.res +3 -3
- package/src/generator/Pairing.res +47 -18
- package/src/generator/Pairing.res.mjs +41 -10
- package/src/lifecycle/CheckLifecycleModel.res +8 -7
- package/src/lifecycle/CheckLifecycleModel.res.mjs +2 -2
- package/src/semantic/Capabilities.res +2 -2
- package/src/semantic/CapabilityNeed.res +3 -3
- package/src/semantic/Color.res +8 -8
- package/src/semantic/IdentityProvider.res +1 -1
- package/src/semantic/Messaging.res +2 -2
- package/src/types/{Behavior.res → Decision.res} +10 -6
- package/src/types/Message.res +1 -1
- package/src/types/OwnerScope.res +5 -5
- package/src/types/RoleCoverage.res +6 -6
- package/src/types/RoleCoverage.res.mjs +6 -6
- package/src/types/Trait.res +1 -1
- /package/src/types/{Behavior.res.mjs → Decision.res.mjs} +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Nodefs from "node:fs";
|
|
4
|
+
import * as Nodepath from "node:path";
|
|
5
|
+
import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.js";
|
|
6
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
7
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
8
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
9
|
+
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
10
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
11
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
12
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
13
|
+
import * as CliArgs$Reventless from "../CliArgs.res.mjs";
|
|
14
|
+
import * as Nodechild_process from "node:child_process";
|
|
15
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
16
|
+
|
|
17
|
+
let usage = `Usage: reventless-rename-to-decision [--dry-run] [<dir>]
|
|
18
|
+
|
|
19
|
+
<dir> where to start; the default is the current directory
|
|
20
|
+
--dry-run print what would change, change nothing
|
|
21
|
+
|
|
22
|
+
Renames every *_Behavior.res to *_Decision.res (git mv inside a git work tree,
|
|
23
|
+
with its compiled output beside it), and in every .res and .resi file:
|
|
24
|
+
@@reventless.behavior -> @@reventless.decision, Behavior_GWT -> Decision_GWT,
|
|
25
|
+
Reventless.Behavior -> Reventless.Decision, thenRefused -> thenForbidden, and
|
|
26
|
+
<Spec>_Behavior -> <Spec>_Decision. Skips node_modules and lib.`;
|
|
27
|
+
|
|
28
|
+
function parseArgs(argv) {
|
|
29
|
+
return Stdlib_Result.map(Stdlib_Result.flatMap(CliArgs$Reventless.parse(undefined, ["dry-run"], undefined, undefined, argv), a => CliArgs$Reventless.atMost(a, 1)), a => ({
|
|
30
|
+
dir: Stdlib_Option.getOr(CliArgs$Reventless.positionals(a)[0], "."),
|
|
31
|
+
dryRun: CliArgs$Reventless.bool(a, "dry-run")
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let cli = {
|
|
36
|
+
bin: "reventless-rename-to-decision",
|
|
37
|
+
usage: usage,
|
|
38
|
+
parse: parseArgs
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let replacements = [
|
|
42
|
+
[
|
|
43
|
+
/@@reventless\.behavior\b/g,
|
|
44
|
+
"@@reventless.decision"
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
/\bBehavior_GWT\.BehaviorSpec\b/g,
|
|
48
|
+
"Decision_GWT.DecisionSpec"
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
/\bBehavior_GWT\.Behavior\b/g,
|
|
52
|
+
"Decision_GWT.Decision"
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
/\bBehavior_GWT\b/g,
|
|
56
|
+
"Decision_GWT"
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
/\bReventless\.Behavior\b/g,
|
|
60
|
+
"Reventless.Decision"
|
|
61
|
+
],
|
|
62
|
+
[
|
|
63
|
+
/\bStateChangeSlice\.Behavior\b/g,
|
|
64
|
+
"StateChangeSlice.Decision"
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
/\bMapping_GWT\.FromBehavior\b/g,
|
|
68
|
+
"Mapping_GWT.FromDecision"
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
/\bthenRefused\b/g,
|
|
72
|
+
"thenForbidden"
|
|
73
|
+
],
|
|
74
|
+
[
|
|
75
|
+
/\b([A-Z][A-Za-z0-9]*)_Behavior\b/g,
|
|
76
|
+
"$1_Decision"
|
|
77
|
+
]
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
let localBinding = /^(\s*)module Behavior = /m;
|
|
81
|
+
|
|
82
|
+
function rewrite(source) {
|
|
83
|
+
let text = Stdlib_Array.reduce(replacements, source, (text, param) => text.replace(param[0], param[1]));
|
|
84
|
+
if (localBinding.test(text)) {
|
|
85
|
+
return text.replace(/^(\s*)module Behavior = /gm, "$1module Decision = ").replace(/\bBehavior\./g, "Decision.");
|
|
86
|
+
} else {
|
|
87
|
+
return text;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let skippedDirs = [
|
|
92
|
+
"node_modules",
|
|
93
|
+
"lib",
|
|
94
|
+
".git"
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
function isSource(name) {
|
|
98
|
+
if (name.endsWith(".res")) {
|
|
99
|
+
return true;
|
|
100
|
+
} else {
|
|
101
|
+
return name.endsWith(".resi");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let companions = [
|
|
106
|
+
".res",
|
|
107
|
+
".resi",
|
|
108
|
+
".res.mjs",
|
|
109
|
+
".res.js",
|
|
110
|
+
".res.cjs"
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
function walk(dir, files) {
|
|
114
|
+
Nodefs.readdirSync(dir, {
|
|
115
|
+
withFileTypes: true
|
|
116
|
+
}).forEach(entry => {
|
|
117
|
+
let name = entry.name;
|
|
118
|
+
let path = Nodepath.join(dir, name);
|
|
119
|
+
if (entry.isDirectory()) {
|
|
120
|
+
if (!skippedDirs.includes(name)) {
|
|
121
|
+
return walk(path, files);
|
|
122
|
+
} else {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
} else if (entry.isFile()) {
|
|
126
|
+
files.push(path);
|
|
127
|
+
return;
|
|
128
|
+
} else {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let byPath = Primitive_string.compare;
|
|
135
|
+
|
|
136
|
+
function plan(dir) {
|
|
137
|
+
let files = [];
|
|
138
|
+
walk(dir, files);
|
|
139
|
+
files.sort(byPath);
|
|
140
|
+
let present = new Set(files);
|
|
141
|
+
let moves = files.filter(path => path.endsWith("_Behavior.res")).flatMap(source => {
|
|
142
|
+
let stem = source.slice(0, source.length - 13 | 0);
|
|
143
|
+
return Stdlib_Array.filterMap(companions, suffix => {
|
|
144
|
+
let from = stem + "_Behavior" + suffix;
|
|
145
|
+
if (present.has(from)) {
|
|
146
|
+
return {
|
|
147
|
+
from: from,
|
|
148
|
+
to_: stem + "_Decision" + suffix
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
let edits = Stdlib_Array.filterMap(files, path => {
|
|
154
|
+
if (!isSource(Nodepath.basename(path))) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
let contents = Nodefs.readFileSync(path, "utf8");
|
|
158
|
+
let rewritten = rewrite(contents);
|
|
159
|
+
if (rewritten === contents) {
|
|
160
|
+
return;
|
|
161
|
+
} else {
|
|
162
|
+
return {
|
|
163
|
+
path: path,
|
|
164
|
+
contents: rewritten
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
let match = moves.find(param => present.has(param.to_));
|
|
169
|
+
if (match !== undefined) {
|
|
170
|
+
return {
|
|
171
|
+
TAG: "Error",
|
|
172
|
+
_0: match.to_ + ` already exists, so ` + match.from + ` cannot take its name. Nothing was changed.`
|
|
173
|
+
};
|
|
174
|
+
} else {
|
|
175
|
+
return {
|
|
176
|
+
TAG: "Ok",
|
|
177
|
+
_0: {
|
|
178
|
+
moves: moves,
|
|
179
|
+
edits: edits
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function git(cwd, args) {
|
|
186
|
+
return Nodechild_process.spawnSync("git", args, {
|
|
187
|
+
cwd: cwd,
|
|
188
|
+
encoding: "utf8",
|
|
189
|
+
stdio: [
|
|
190
|
+
"ignore",
|
|
191
|
+
"pipe",
|
|
192
|
+
"pipe"
|
|
193
|
+
]
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function succeeded(result) {
|
|
198
|
+
return Primitive_object.equal(Primitive_option.fromNullable(result.status), 0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isTracked(path) {
|
|
202
|
+
return succeeded(git(Nodepath.dirname(path), [
|
|
203
|
+
"ls-files",
|
|
204
|
+
"--error-unmatch",
|
|
205
|
+
"--",
|
|
206
|
+
Nodepath.basename(path)
|
|
207
|
+
]));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function moveFile(param) {
|
|
211
|
+
let to_ = param.to_;
|
|
212
|
+
let from = param.from;
|
|
213
|
+
if (isTracked(from)) {
|
|
214
|
+
let result = git(Nodepath.dirname(from), [
|
|
215
|
+
"mv",
|
|
216
|
+
"--",
|
|
217
|
+
Nodepath.basename(from),
|
|
218
|
+
Nodepath.basename(to_)
|
|
219
|
+
]);
|
|
220
|
+
if (succeeded(result)) {
|
|
221
|
+
return {
|
|
222
|
+
TAG: "Ok",
|
|
223
|
+
_0: undefined
|
|
224
|
+
};
|
|
225
|
+
} else {
|
|
226
|
+
return {
|
|
227
|
+
TAG: "Error",
|
|
228
|
+
_0: `git mv ` + from + `: ` + Stdlib_Option.getOr(Primitive_option.fromNullable(result.stderr), "").trim()
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
Nodefs.renameSync(from, to_);
|
|
234
|
+
return {
|
|
235
|
+
TAG: "Ok",
|
|
236
|
+
_0: undefined
|
|
237
|
+
};
|
|
238
|
+
} catch (raw_e) {
|
|
239
|
+
let e = Primitive_exceptions.internalToException(raw_e);
|
|
240
|
+
if (e.RE_EXN_ID === "JsExn") {
|
|
241
|
+
return {
|
|
242
|
+
TAG: "Error",
|
|
243
|
+
_0: `rename ` + from + `: ` + Stdlib_Option.getOr(Stdlib_JsExn.message(e._1), "failed")
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
throw e;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function apply(param) {
|
|
251
|
+
param.edits.forEach(param => {
|
|
252
|
+
Nodefs.writeFileSync(param.path, param.contents, "utf8");
|
|
253
|
+
});
|
|
254
|
+
return Stdlib_Array.reduce(param.moves, {
|
|
255
|
+
TAG: "Ok",
|
|
256
|
+
_0: undefined
|
|
257
|
+
}, (done, move) => Stdlib_Result.flatMap(done, () => moveFile(move)));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function describe(param, dryRun) {
|
|
261
|
+
let edits = param.edits;
|
|
262
|
+
let moves = param.moves;
|
|
263
|
+
let verb = dryRun ? "would " : "";
|
|
264
|
+
let lines = Belt_Array.concatMany([
|
|
265
|
+
moves.map(param => verb + `move ` + param.from + ` -> ` + param.to_),
|
|
266
|
+
edits.map(param => verb + `edit ` + param.path)
|
|
267
|
+
]);
|
|
268
|
+
let movedSources = moves.filter(param => param.from.endsWith(".res")).length;
|
|
269
|
+
lines.push((
|
|
270
|
+
dryRun ? "Would rename" : "Renamed"
|
|
271
|
+
) + ` ` + movedSources.toString() + ` file(s) and ` + (
|
|
272
|
+
dryRun ? "edit" : "edited"
|
|
273
|
+
) + ` ` + edits.length.toString() + `.`);
|
|
274
|
+
return lines;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function execute(param) {
|
|
278
|
+
let dir = param.dir;
|
|
279
|
+
if (!Nodefs.existsSync(dir)) {
|
|
280
|
+
return {
|
|
281
|
+
TAG: "Error",
|
|
282
|
+
_0: dir + ` does not exist.`
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
let dryRun = param.dryRun;
|
|
286
|
+
return Stdlib_Result.flatMap(plan(dir), plan => {
|
|
287
|
+
if (dryRun) {
|
|
288
|
+
return {
|
|
289
|
+
TAG: "Ok",
|
|
290
|
+
_0: describe(plan, dryRun)
|
|
291
|
+
};
|
|
292
|
+
} else {
|
|
293
|
+
return Stdlib_Result.map(apply(plan), () => describe(plan, dryRun));
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function main() {
|
|
299
|
+
return CliArgs$Reventless.run(cli, undefined, undefined, async args => {
|
|
300
|
+
let lines = execute(args);
|
|
301
|
+
if (lines.TAG === "Ok") {
|
|
302
|
+
lines._0.forEach(line => {
|
|
303
|
+
console.log(line);
|
|
304
|
+
});
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
console.error(`reventless-rename-to-decision: ` + lines._0);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export {
|
|
313
|
+
usage,
|
|
314
|
+
parseArgs,
|
|
315
|
+
cli,
|
|
316
|
+
replacements,
|
|
317
|
+
localBinding,
|
|
318
|
+
rewrite,
|
|
319
|
+
skippedDirs,
|
|
320
|
+
isSource,
|
|
321
|
+
companions,
|
|
322
|
+
walk,
|
|
323
|
+
byPath,
|
|
324
|
+
plan,
|
|
325
|
+
git,
|
|
326
|
+
succeeded,
|
|
327
|
+
isTracked,
|
|
328
|
+
moveFile,
|
|
329
|
+
apply,
|
|
330
|
+
describe,
|
|
331
|
+
execute,
|
|
332
|
+
main,
|
|
333
|
+
}
|
|
334
|
+
/* node:fs Not a pure module */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Module type for an aggregate's identity and schema specification.
|
|
3
3
|
|
|
4
4
|
Every aggregate defines a `Spec` module satisfying this type. The `Spec` is
|
|
5
|
-
used by `Platform.Aggregate.Make` and by `
|
|
5
|
+
used by `Platform.Aggregate.Make` and by `Decision.T` to fix the aggregate's
|
|
6
6
|
command, event, and error types.
|
|
7
7
|
|
|
8
8
|
@example
|
|
@@ -76,7 +76,7 @@ let fromStructure = (structure: Plugin.pluginStructure): t => {
|
|
|
76
76
|
let capabilityKeys =
|
|
77
77
|
needs->Array.map(d => d.capability)->Belt.Set.String.fromArray->Belt.Set.String.toArray
|
|
78
78
|
let capabilities = capabilityKeys->Array.filterMap(key =>
|
|
79
|
-
// An
|
|
79
|
+
// An unrecognized capability is dropped rather than passed through: the
|
|
80
80
|
// generator downstream renders a real `Platform.capability` arm, and a name
|
|
81
81
|
// this build cannot map has no arm to render.
|
|
82
82
|
CapabilityNeed.fromString(key)->Option.map(need => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// (`generator/Discovery`) and the external tooling that classifies files by
|
|
5
5
|
// folder (`reventless-gwt`'s `ComponentMeta`) both derive from this, so the two
|
|
6
6
|
// can't drift — previously the generator accepted plural/short folder names that
|
|
7
|
-
// the gwt discovery silently didn't
|
|
7
|
+
// the gwt discovery silently didn't recognize.
|
|
8
8
|
|
|
9
9
|
type t =
|
|
10
10
|
| StateChangeSlice
|
|
@@ -99,7 +99,7 @@ let bodySuffixes = [
|
|
|
99
99
|
"_Projections",
|
|
100
100
|
"_Projection",
|
|
101
101
|
"_Mappings",
|
|
102
|
-
"
|
|
102
|
+
"_Decision",
|
|
103
103
|
"_Automation",
|
|
104
104
|
"_Translation",
|
|
105
105
|
]
|
|
@@ -46,10 +46,10 @@ The four rules (over the representation):
|
|
|
46
46
|
and this module stays schema-agnostic by taking the name alone.
|
|
47
47
|
|
|
48
48
|
`byTag` marks the exception: an identity the *name* does not declare, which an
|
|
49
|
-
adapter
|
|
49
|
+
adapter recognized from an explicit `@partitionTag`. A domain's own identifier
|
|
50
50
|
is often not suffixed — `sku`, `isbn`, `vin` — and without this the annotation
|
|
51
51
|
naming one would be extracted as a hint and then dropped, because `seedOf`
|
|
52
|
-
only
|
|
52
|
+
only honors a hint already among the produced keys. So the escape hatch would
|
|
53
53
|
have worked for every field except the ones that need it.
|
|
54
54
|
|
|
55
55
|
It is carried rather than folded in because removing the annotation removes
|
|
@@ -100,7 +100,7 @@ let dcbPartitionTagId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(
|
|
|
100
100
|
Internal sury metadata ID marking a tag field as *cross-partition* — readable
|
|
101
101
|
across every partition that carries it (a secondary-tag read), not just the
|
|
102
102
|
partition it is the partition key of. Opt-in; absence = the default
|
|
103
|
-
partition-scoped
|
|
103
|
+
partition-scoped behavior. The scope is a property of the tag *key* and must
|
|
104
104
|
agree across every event type that carries it (the fence-scope half depends on
|
|
105
105
|
it). See `docs/analysis/dcb-consistency-check-issues.md` Issue 13.
|
|
106
106
|
*/
|
|
@@ -497,7 +497,7 @@ tag the catalog's own events write. A key derived from `lineItems` would name a
|
|
|
497
497
|
tag no producer has ever written, and the decision read would come back empty —
|
|
498
498
|
the identical symptom to extracting no tags at all, from a different cause. An
|
|
499
499
|
author who wants another key still writes `DcbTag.stringForKey` on the nested
|
|
500
|
-
field, which `resolveTagKey`
|
|
500
|
+
field, which `resolveTagKey` honors here as it does at the top level.
|
|
501
501
|
*/
|
|
502
502
|
let nestedRecordTags = (fieldSchema: S.t<unknown>, jsonValue: JSON.t): array<tag> =>
|
|
503
503
|
switch nestedRecordProperties(fieldSchema) {
|
|
@@ -655,7 +655,7 @@ let extractVariantNames = (schema: S.t<'a>): array<string> => {
|
|
|
655
655
|
// intentionally excluded here: DCB event-type lookups can't WHERE-clause
|
|
656
656
|
// on bare-string events, so Plugin_Structure.consumedEventTypes and
|
|
657
657
|
// related graph fields would otherwise claim cross-component edges the
|
|
658
|
-
// runtime can't
|
|
658
|
+
// runtime can't honor. Callers that need every constructor (e.g.
|
|
659
659
|
// mapping-mode `acceptedTags` filters that match a JSON envelope's
|
|
660
660
|
// `event` TAG against the Delegate's full constructor set) should call
|
|
661
661
|
// [`extractAllVariantNames`] instead.
|
|
@@ -729,7 +729,7 @@ let isVariantPayloadBearing = (schema: S.t<'a>, name: string): bool => {
|
|
|
729
729
|
Extracts tags from a flat JSON object, expanding array values into per-element tags.
|
|
730
730
|
|
|
731
731
|
For scalar tagged fields, behaves identically to `extractTagsFromProperties`.
|
|
732
|
-
For array tagged fields, produces one tag per element. Tag keys
|
|
732
|
+
For array tagged fields, produces one tag per element. Tag keys honor an optional
|
|
733
733
|
`DcbTag.stringForKey(~key=...)` override on the (inner) schema; otherwise the field
|
|
734
734
|
name is used.
|
|
735
735
|
|
|
@@ -1123,7 +1123,7 @@ let idFieldsOfProperties = (properties: dict<S.t<unknown>>): array<DcbScopeInfer
|
|
|
1123
1123
|
let isIdName = (name: string) => name->String.endsWith("Ids") || name->String.endsWith("Id")
|
|
1124
1124
|
// An explicit `@partitionTag` declares an identity the naming convention cannot
|
|
1125
1125
|
// express — a domain's own identifier is often unsuffixed (`sku`, `isbn`).
|
|
1126
|
-
// Without this the hint is extracted and then ignored, since `seedOf`
|
|
1126
|
+
// Without this the hint is extracted and then ignored, since `seedOf` honors
|
|
1127
1127
|
// only a hint already among the produced keys, so the escape hatch would work
|
|
1128
1128
|
// for every field except the ones that need it. Flagged, not folded in: taking
|
|
1129
1129
|
// the annotation away takes the identity with it, which is what the redundancy
|
|
@@ -76,14 +76,14 @@ module type Spec = {
|
|
|
76
76
|
/**
|
|
77
77
|
Event sources this slice subscribes to, by topic key.
|
|
78
78
|
|
|
79
|
-
`[]` — the default and the historical
|
|
79
|
+
`[]` — the default and the historical behavior — means this plugin's own DCB
|
|
80
80
|
event log. Naming sources explicitly subscribes to them instead: an Aggregate's
|
|
81
81
|
`Spec.name`, or a DCB source name (conventionally `"<pluginName>DcbEventLog"`),
|
|
82
82
|
matching the keys `AutomationSlice` mappings already use.
|
|
83
83
|
|
|
84
84
|
This exists because an outbound slice is the framework's one component for
|
|
85
85
|
*calling an external service and feeding the answer back*, and that job is not
|
|
86
|
-
specific to DCB-
|
|
86
|
+
specific to DCB-modeled entities. An Aggregate whose events should trigger an
|
|
87
87
|
outbound call had no route to one while this list was hard-wired.
|
|
88
88
|
|
|
89
89
|
Unlike `AutomationSlice`, the sources are a flat list rather than per-source
|
|
@@ -173,7 +173,7 @@ type commandDef = {
|
|
|
173
173
|
means the command does not move the row. */
|
|
174
174
|
targetState: option<string>,
|
|
175
175
|
/** Where the from-set came from, ranking an inherited edge against an authored
|
|
176
|
-
one the way `
|
|
176
|
+
one the way `readSideDef.labelFieldSource` does for its field:
|
|
177
177
|
|
|
178
178
|
- `"derived"` — the component's own scenarios.
|
|
179
179
|
- `"declared"` — its `commandTransition` switch.
|
|
@@ -204,7 +204,7 @@ type commandDef = {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
@schema
|
|
207
|
-
type
|
|
207
|
+
type readSideDef = {
|
|
208
208
|
name: string,
|
|
209
209
|
queryField: string,
|
|
210
210
|
schema: string,
|
|
@@ -276,7 +276,7 @@ type errorDef = {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
@schema
|
|
279
|
-
type
|
|
279
|
+
type writeSideDef = {
|
|
280
280
|
name: string,
|
|
281
281
|
commands: array<commandDef>,
|
|
282
282
|
producedEventTypes: array<string>,
|
|
@@ -289,7 +289,7 @@ type writableDef = {
|
|
|
289
289
|
required list still lacks the key — both admin resolvers heal absent ones to
|
|
290
290
|
`[]` on read, and a new one must be added there too. */
|
|
291
291
|
errors: array<errorDef>,
|
|
292
|
-
/** Chapter grouping band — see `
|
|
292
|
+
/** Chapter grouping band — see `readSideDef.chapter`. */
|
|
293
293
|
chapter: option<string>,
|
|
294
294
|
}
|
|
295
295
|
|
|
@@ -299,7 +299,7 @@ type automationSliceDef = {
|
|
|
299
299
|
consumedEventTypes: array<string>,
|
|
300
300
|
producedCommandTypes: array<string>,
|
|
301
301
|
targetName: string,
|
|
302
|
-
/** Chapter grouping band — see `
|
|
302
|
+
/** Chapter grouping band — see `readSideDef.chapter`. */
|
|
303
303
|
chapter: option<string>,
|
|
304
304
|
/** The declared `schedule`, as the JSON of `Schedule.timeSchema`; `None` for an
|
|
305
305
|
automation started by events only, and in a structure from before the field. */
|
|
@@ -314,7 +314,7 @@ type outboundTranslationSliceDef = {
|
|
|
314
314
|
targetName: option<string>,
|
|
315
315
|
// Foreign system this slice publishes to — drives the external box (Event Graph).
|
|
316
316
|
externalSystem: option<string>,
|
|
317
|
-
/** Chapter grouping band — see `
|
|
317
|
+
/** Chapter grouping band — see `readSideDef.chapter`. */
|
|
318
318
|
chapter: option<string>,
|
|
319
319
|
/** The topics this slice subscribes to, as `Spec.sourceNames` declares them —
|
|
320
320
|
an Aggregate's `Spec.name` or a DCB source name. `Some([])` is the declared
|
|
@@ -333,7 +333,7 @@ type inboundTranslationSliceDef = {
|
|
|
333
333
|
targetName: string,
|
|
334
334
|
// Foreign system this slice receives from — drives the external box (Event Graph).
|
|
335
335
|
externalSystem: option<string>,
|
|
336
|
-
/** Chapter grouping band — see `
|
|
336
|
+
/** Chapter grouping band — see `readSideDef.chapter`. */
|
|
337
337
|
chapter: option<string>,
|
|
338
338
|
/** The roles any of its commands' rules name; see `commandDef`. */
|
|
339
339
|
requiredRoles?: array<string>,
|
|
@@ -393,7 +393,7 @@ type extensionDef = {
|
|
|
393
393
|
An extension point owned by a plugin, from the producer side.
|
|
394
394
|
|
|
395
395
|
`sourceEventTypes` are the `Delegate`'s events feeding the published protocol,
|
|
396
|
-
plugin-qualified to match `
|
|
396
|
+
plugin-qualified to match `writeSideDef.producedEventTypes`. `commandTypes` is the
|
|
397
397
|
EP's inbound protocol — None (read as []) for a `command = unit` EP, which routes
|
|
398
398
|
nothing. Definitions persisted before a field was added must be re-emitted.
|
|
399
399
|
*/
|
|
@@ -451,7 +451,7 @@ whole structure.
|
|
|
451
451
|
while it walks the components, because it is the only party that knows which one
|
|
452
452
|
carried the declaration. Nothing in this record is a string a developer typed.
|
|
453
453
|
|
|
454
|
-
It records ORIGIN, not
|
|
454
|
+
It records ORIGIN, not behavior — a grafted file is the host's to edit
|
|
455
455
|
afterwards. What answers "does it still behave like the trait" is the trait's own
|
|
456
456
|
conformance suite, which runs in the consumer's build and is not this field.
|
|
457
457
|
*/
|
|
@@ -474,10 +474,10 @@ is fabricated and warned about, so it is the one shape to avoid. Regression suit
|
|
|
474
474
|
*/
|
|
475
475
|
@schema
|
|
476
476
|
type pluginStructure = {
|
|
477
|
-
readModels: array<
|
|
478
|
-
stateViewSlices: array<
|
|
479
|
-
stateChangeSlices: array<
|
|
480
|
-
aggregates: array<
|
|
477
|
+
readModels: array<readSideDef>,
|
|
478
|
+
stateViewSlices: array<readSideDef>,
|
|
479
|
+
stateChangeSlices: array<writeSideDef>,
|
|
480
|
+
aggregates: array<writeSideDef>,
|
|
481
481
|
automationSlices: array<automationSliceDef>,
|
|
482
482
|
outboundTranslationSlices: array<outboundTranslationSliceDef>,
|
|
483
483
|
inboundTranslationSlices: array<inboundTranslationSliceDef>,
|
|
@@ -499,7 +499,7 @@ type pluginStructure = {
|
|
|
499
499
|
component. Absent → None, read as []. The only signal a graft leaves that
|
|
500
500
|
survives into a deployed plugin — every other one (the dependency, the
|
|
501
501
|
variant spread, the rules alias, the conformance binding) is source-side.
|
|
502
|
-
A claim about origin, never about
|
|
502
|
+
A claim about origin, never about behavior: see `Trait`. */
|
|
503
503
|
traitDeclarations: option<array<traitDeclaration>>,
|
|
504
504
|
}
|
|
505
505
|
|
|
@@ -106,7 +106,7 @@ let commandDefSchema = Sury.$schema(s => ({
|
|
|
106
106
|
ownerField: s.m(Sury.$option(Sury.string))
|
|
107
107
|
}));
|
|
108
108
|
|
|
109
|
-
let
|
|
109
|
+
let readSideDefSchema = Sury.$schema(s => ({
|
|
110
110
|
name: s.m(Sury.string),
|
|
111
111
|
queryField: s.m(Sury.string),
|
|
112
112
|
schema: s.m(Sury.string),
|
|
@@ -141,7 +141,7 @@ let errorDefSchema = Sury.$schema(s => ({
|
|
|
141
141
|
references: s.m(Sury.array(fieldReferenceSchema))
|
|
142
142
|
}));
|
|
143
143
|
|
|
144
|
-
let
|
|
144
|
+
let writeSideDefSchema = Sury.$schema(s => ({
|
|
145
145
|
name: s.m(Sury.string),
|
|
146
146
|
commands: s.m(Sury.array(commandDefSchema)),
|
|
147
147
|
producedEventTypes: s.m(Sury.array(Sury.string)),
|
|
@@ -239,10 +239,10 @@ let traitDeclarationSchema = Sury.$schema(s => ({
|
|
|
239
239
|
}));
|
|
240
240
|
|
|
241
241
|
let pluginStructureSchema = Sury.$schema(s => ({
|
|
242
|
-
readModels: s.m(Sury.array(
|
|
243
|
-
stateViewSlices: s.m(Sury.array(
|
|
244
|
-
stateChangeSlices: s.m(Sury.array(
|
|
245
|
-
aggregates: s.m(Sury.array(
|
|
242
|
+
readModels: s.m(Sury.array(readSideDefSchema)),
|
|
243
|
+
stateViewSlices: s.m(Sury.array(readSideDefSchema)),
|
|
244
|
+
stateChangeSlices: s.m(Sury.array(writeSideDefSchema)),
|
|
245
|
+
aggregates: s.m(Sury.array(writeSideDefSchema)),
|
|
246
246
|
automationSlices: s.m(Sury.array(automationSliceDefSchema)),
|
|
247
247
|
outboundTranslationSlices: s.m(Sury.array(outboundTranslationSliceDefSchema)),
|
|
248
248
|
inboundTranslationSlices: s.m(Sury.array(inboundTranslationSliceDefSchema)),
|
|
@@ -290,10 +290,10 @@ export {
|
|
|
290
290
|
commandLevelSchema,
|
|
291
291
|
fieldReferenceSchema,
|
|
292
292
|
commandDefSchema,
|
|
293
|
-
|
|
293
|
+
readSideDefSchema,
|
|
294
294
|
eventDefSchema,
|
|
295
295
|
errorDefSchema,
|
|
296
|
-
|
|
296
|
+
writeSideDefSchema,
|
|
297
297
|
automationSliceDefSchema,
|
|
298
298
|
outboundTranslationSliceDefSchema,
|
|
299
299
|
inboundTranslationSliceDefSchema,
|
|
@@ -9,7 +9,7 @@ facts about one field.
|
|
|
9
9
|
It is declared where the field is declared, and read by anything that composes
|
|
10
10
|
text somebody will read. That is the whole reason it lives here rather than
|
|
11
11
|
beside whichever consumer happens to need it first: a marking that exists in one
|
|
12
|
-
consumer is a marking the rest of the system cannot
|
|
12
|
+
consumer is a marking the rest of the system cannot honor, and the domain model
|
|
13
13
|
is the only place that knows which values are sensitive.
|
|
14
14
|
|
|
15
15
|
## Absent means "not stated", not "safe"
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// Per-aggregate persisted-snapshot configuration
|
|
2
2
|
// (docs/plans/done/aggregate-snapshotting.md).
|
|
3
3
|
//
|
|
4
|
-
// Snapshotting is opt-in and lives on the
|
|
5
|
-
// defined): `
|
|
6
|
-
// `@@reventless.
|
|
4
|
+
// Snapshotting is opt-in and lives on the Decision module (where `state` is
|
|
5
|
+
// defined): `Decision.T.snapshot = None` (the default, auto-injected by
|
|
6
|
+
// `@@reventless.decision`) keeps full replay; `Some(config)` makes the
|
|
7
7
|
// aggregate write a keep-one snapshot every `interval` events and seed cold
|
|
8
8
|
// replays from the latest one. Snapshots are a read optimization only — the
|
|
9
9
|
// OCC append remains the sole consistency primitive, and a missing, corrupt,
|
|
10
10
|
// or schema-drifted snapshot degrades to full replay.
|
|
11
11
|
//
|
|
12
|
-
// Enable via the file-level attribute on a
|
|
12
|
+
// Enable via the file-level attribute on a decision file:
|
|
13
13
|
//
|
|
14
14
|
// @@reventless.snapshots(100)
|
|
15
15
|
//
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
type config<'state> = {
|
|
21
21
|
/** Write a snapshot after every N appended events. Required — no default. */
|
|
22
22
|
interval: int,
|
|
23
|
-
/** Sury schema for the
|
|
23
|
+
/** Sury schema for the decision's state — serializes the persisted snapshot
|
|
24
24
|
and (hashed) gates staleness across state-shape changes. */
|
|
25
25
|
stateSchema: S.t<'state>,
|
|
26
26
|
}
|
|
@@ -161,7 +161,7 @@ type stateAnnotationSpec = {
|
|
|
161
161
|
command's declared edge is written in terms of, a board draws its columns
|
|
162
162
|
from and a state diagram renders. `Some(name)` when one such annotation
|
|
163
163
|
exists; the PPX errors on duplicate `@lifecycle` annotations within the same
|
|
164
|
-
record. Codegen consumes this to populate `
|
|
164
|
+
record. Codegen consumes this to populate `readSideDef.lifecycleField`, and
|
|
165
165
|
falls back to a field literally named `"lifecycle"` whose shape is an enum
|
|
166
166
|
when this annotation is absent — so a record whose field can honestly be
|
|
167
167
|
called `lifecycle` declares one without ceremony.
|