@reventlessdev/reventless-core 3.0.0-alpha.261 → 3.0.0-alpha.263
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 +31 -0
- package/package.json +9 -9
- package/src/adapter/Secrets/Secrets_Node_Backend.res +60 -0
- package/src/adapter/Secrets/Secrets_Node_Backend.res.mjs +69 -0
- package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
- package/src/components/Aggregate/Aggregate_Builder.res.mjs +1 -1
- package/src/components/CommandGenerator/CommandGenerator.res +4 -4
- package/src/components/CommandGenerator/CommandGenerator_Callback.res +13 -20
- package/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs +3 -24
- package/src/components/Dcb/Dcb_Builder.res +45 -49
- package/src/components/Dcb/Dcb_Builder.res.mjs +31 -40
- package/src/components/StateChangeSlice/StateChangeSlice.res +2 -0
- package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +6 -0
- package/src/components/StateChangeSlice/StateChangeSlice_Builder.res.mjs +4 -4
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +51 -22
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +47 -25
- package/src/plugin/component/Plugin_Structure.res +101 -4
- package/src/plugin/component/Plugin_Structure.res.mjs +82 -9
- package/tests/Secrets_Node_BackendTest.res +141 -0
- package/tests/Secrets_Node_BackendTest.res.mjs +152 -0
- package/tests/adapter/RuntimeExtensionHookReachTest.res.mjs +1 -1
- package/tests/admin/Platform_Admin_StructureTest.res +12 -3
- package/tests/admin/Platform_Admin_StructureTest.res.mjs +4 -3
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res +1 -1
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res.mjs +1 -1
- package/tests/commandgenerator/CommandGeneratorFixtures.res +2 -0
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +17 -3
- package/tests/commandgenerator/OwnerStampingTest.res.mjs +2 -2
- package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +1 -1
- package/tests/dcb/DcbFixtures.res +18 -1
- package/tests/dcb/DcbFixtures.res.mjs +14 -0
- package/tests/dcb/DcbScopeInferenceTest.res +234 -0
- package/tests/dcb/DcbScopeInferenceTest.res.mjs +425 -13
- package/tests/dcb/DcbStateChangeSliceTest.res.mjs +22 -22
- package/tests/dcb/DcbTagTest.res +81 -68
- package/tests/dcb/DcbTagTest.res.mjs +88 -89
- package/tests/plugin/PluginStructureAccessTest.res +1 -0
- package/tests/plugin/PluginStructureAccessTest.res.mjs +1 -1
- package/tests/plugin/PluginStructureTest.res +47 -0
- package/tests/plugin/PluginStructureTest.res.mjs +50 -10
|
@@ -8,6 +8,7 @@ import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output
|
|
|
8
8
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
9
9
|
import * as Effect from "effect/Effect";
|
|
10
10
|
import * as Pulumi from "@pulumi/pulumi";
|
|
11
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
11
12
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
12
13
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
13
14
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
@@ -30,24 +31,6 @@ import * as OutboundTranslationSlice_Callback$ReventlessCore from "../OutboundTr
|
|
|
30
31
|
|
|
31
32
|
let log = Logger$ReventlessCore.fromEnv();
|
|
32
33
|
|
|
33
|
-
let toRelativePath = (function toRelativePath(moduleUrl) {
|
|
34
|
-
try {
|
|
35
|
-
const fs = process.getBuiltinModule('node:fs');
|
|
36
|
-
const path = process.getBuiltinModule('node:path');
|
|
37
|
-
const { fileURLToPath } = process.getBuiltinModule('node:url');
|
|
38
|
-
const builderReal = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
39
|
-
const sliceReal = fs.realpathSync(fileURLToPath(moduleUrl));
|
|
40
|
-
let dir = path.dirname(builderReal);
|
|
41
|
-
while (dir !== path.dirname(dir)) {
|
|
42
|
-
if (fs.existsSync(path.join(dir, 'lerna.json'))) break;
|
|
43
|
-
dir = path.dirname(dir);
|
|
44
|
-
}
|
|
45
|
-
return path.relative(dir, sliceReal).replace(/\.mjs$/, '.res');
|
|
46
|
-
} catch(e) {
|
|
47
|
-
return moduleUrl.replace('file://', '');
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
34
|
function variantTag(variant) {
|
|
52
35
|
switch (variant.type) {
|
|
53
36
|
case "string" :
|
|
@@ -157,11 +140,6 @@ function Make(DcbEventLogStorage) {
|
|
|
157
140
|
Sc.Spec.name,
|
|
158
141
|
Sc.Spec.eventSchema
|
|
159
142
|
]);
|
|
160
|
-
let producedNamed = stateChangeSlices.map(Sc => [
|
|
161
|
-
Sc.Spec.name,
|
|
162
|
-
toRelativePath(Sc.Spec.moduleUrl),
|
|
163
|
-
Sc.Spec.eventSchema
|
|
164
|
-
]);
|
|
165
143
|
let consumed = stateChangeSlices.map(Sc => [
|
|
166
144
|
Sc.Spec.name,
|
|
167
145
|
Sc.Spec.consumedEventSchema
|
|
@@ -191,7 +169,23 @@ function Make(DcbEventLogStorage) {
|
|
|
191
169
|
}
|
|
192
170
|
}).map(tagKey => `tag_` + tagKey);
|
|
193
171
|
let indexes$1 = indexes.length > 1 ? indexes.concat(["tag_composite"]) : indexes;
|
|
194
|
-
let
|
|
172
|
+
let sliceSchemas = stateChangeSlices.map(Sc => ({
|
|
173
|
+
name: Sc.Spec.name,
|
|
174
|
+
commandSchema: Sc.Spec.commandSchema,
|
|
175
|
+
consumedEventSchema: Sc.Spec.consumedEventSchema,
|
|
176
|
+
eventSchema: Sc.Spec.eventSchema,
|
|
177
|
+
moduleUrl: Sc.Spec.moduleUrl
|
|
178
|
+
}));
|
|
179
|
+
let inferenceShapes = sliceSchemas.map(DcbTag$Reventless.sliceShape);
|
|
180
|
+
let hintIssues = DcbValidation$Reventless.validatePartitionHintsVsInference(inferenceShapes);
|
|
181
|
+
hintIssues.redundancies.forEach(e => log.info("Dcb_Builder", undefined, `DCB partition (` + e.sliceName + `): ` + e.message));
|
|
182
|
+
if (hintIssues.contradictions.length !== 0) {
|
|
183
|
+
let detail = hintIssues.contradictions.map(e => e.sliceName + `: ` + e.message).join(" | ");
|
|
184
|
+
log.error("Dcb_Builder", undefined, `DCB partition contradiction (` + name + `): ` + detail);
|
|
185
|
+
Stdlib_JsError.throwWithMessage(`DCB partition contradiction (` + name + `): ` + detail);
|
|
186
|
+
}
|
|
187
|
+
let boundaryPartition = DcbTag$Reventless.deriveBoundaryPartition(sliceSchemas);
|
|
188
|
+
let partitionTag = boundaryPartition.partitionTag;
|
|
195
189
|
let arr$1 = producedSchemas.flatMap(DcbTag$Reventless.extractCrossPartitionTagKeys);
|
|
196
190
|
let seen$1 = new Set();
|
|
197
191
|
let crossPartitionTagKeys = arr$1.filter(f => {
|
|
@@ -209,7 +203,6 @@ function Make(DcbEventLogStorage) {
|
|
|
209
203
|
Sc.Spec.commandSchema,
|
|
210
204
|
Sc.Spec.consumedEventSchema
|
|
211
205
|
]), tagKeysByEventType).forEach(w => log.warn("Dcb_Builder", undefined, `DCB composite-read warning (` + w.sliceName + `): ` + w.message));
|
|
212
|
-
let inferenceShapes = stateChangeSlices.map(Sc => DcbTag$Reventless.sliceShapeFromSchemas(Sc.Spec.name, Sc.Spec.commandSchema, Sc.Spec.consumedEventSchema, Sc.Spec.eventSchema));
|
|
213
206
|
let inferred = DcbScopeInference$Reventless.infer(inferenceShapes);
|
|
214
207
|
if (Primitive_object.notequal(inferred.crossPartitionTagKeys, crossPartitionTagKeys)) {
|
|
215
208
|
log.info("Dcb_Builder", undefined, `DCB scope-inference diff: crossPartitionTagKeys annotated=[` + crossPartitionTagKeys.join(", ") + `] inferred=[` + inferred.crossPartitionTagKeys.join(", ") + `]`);
|
|
@@ -231,12 +224,7 @@ function Make(DcbEventLogStorage) {
|
|
|
231
224
|
let scopeIssues = DcbValidation$Reventless.validateScopeVsInference(scopeAnnotations, inferred);
|
|
232
225
|
scopeIssues.contradictions.forEach(e => log.warn("Dcb_Builder", undefined, `DCB scope contradiction (` + e.sliceName + `): ` + e.message));
|
|
233
226
|
scopeIssues.redundancies.forEach(e => log.info("Dcb_Builder", undefined, `DCB scope (` + e.sliceName + `): ` + e.message));
|
|
234
|
-
let effective = DcbTag$Reventless.deriveEffectiveScope(
|
|
235
|
-
name: Sc.Spec.name,
|
|
236
|
-
commandSchema: Sc.Spec.commandSchema,
|
|
237
|
-
consumedEventSchema: Sc.Spec.consumedEventSchema,
|
|
238
|
-
eventSchema: Sc.Spec.eventSchema
|
|
239
|
-
})));
|
|
227
|
+
let effective = DcbTag$Reventless.deriveEffectiveScope(sliceSchemas);
|
|
240
228
|
let effectiveCrossPartitionTagKeys = effective.crossPartitionTagKeys;
|
|
241
229
|
let effectiveTagKeysByEventType = effective.tagKeysByEventType;
|
|
242
230
|
if (effective.droppedCrossPartitionTagKeys.length !== 0) {
|
|
@@ -276,7 +264,7 @@ function Make(DcbEventLogStorage) {
|
|
|
276
264
|
asyncDcbCommandTopicOpt = undefined;
|
|
277
265
|
}
|
|
278
266
|
let stateChangeSlicesOutputs = Object.fromEntries(syncSlices.map(StateChangeSlice => {
|
|
279
|
-
let ch = StateChangeSlice.make(dcbEventLog, publishJsons, effectiveTagKeysByEventType, effectiveCrossPartitionTagKeys, componentRuntime[StateChangeSlice.Spec.name], opts);
|
|
267
|
+
let ch = StateChangeSlice.make(dcbEventLog, publishJsons, effectiveTagKeysByEventType, effectiveCrossPartitionTagKeys, DcbTag$Reventless.slicePartitionTag(boundaryPartition, StateChangeSlice.Spec.name), componentRuntime[StateChangeSlice.Spec.name], opts);
|
|
280
268
|
return [
|
|
281
269
|
StateChangeSlice.Spec.name,
|
|
282
270
|
Component$ReventlessCore.outputs(ch)
|
|
@@ -286,7 +274,7 @@ function Make(DcbEventLogStorage) {
|
|
|
286
274
|
if (asyncDcbCommandTopicOpt !== undefined) {
|
|
287
275
|
let asyncPublishJsons = Component$ReventlessCore.operations(Primitive_option.valFromOption(asyncDcbCommandTopicOpt)).apply(ops => ops.publishJsons);
|
|
288
276
|
asyncStateChangeSlicesOutputs = Object.fromEntries(asyncSlices.map(StateChangeSlice => {
|
|
289
|
-
let ch = StateChangeSlice.make(dcbEventLog, asyncPublishJsons, effectiveTagKeysByEventType, effectiveCrossPartitionTagKeys, componentRuntime[StateChangeSlice.Spec.name], opts);
|
|
277
|
+
let ch = StateChangeSlice.make(dcbEventLog, asyncPublishJsons, effectiveTagKeysByEventType, effectiveCrossPartitionTagKeys, DcbTag$Reventless.slicePartitionTag(boundaryPartition, StateChangeSlice.Spec.name), componentRuntime[StateChangeSlice.Spec.name], opts);
|
|
290
278
|
return [
|
|
291
279
|
StateChangeSlice.Spec.name,
|
|
292
280
|
Component$ReventlessCore.outputs(ch)
|
|
@@ -318,7 +306,7 @@ function Make(DcbEventLogStorage) {
|
|
|
318
306
|
if (ApiNoApiHelpers$ReventlessCore.isNoApi(commandSchema)) {
|
|
319
307
|
return;
|
|
320
308
|
}
|
|
321
|
-
let generateCommand = CommandGenerator_Callback$ReventlessCore.makeGenerateCommand(ops.publishJsons, ops.publishJsonsAndWait, S.Spec.name, S.Spec.commandSchema, "StateChangeSlice", false);
|
|
309
|
+
let generateCommand = CommandGenerator_Callback$ReventlessCore.makeGenerateCommand(ops.publishJsons, ops.publishJsonsAndWait, S.Spec.name, S.Spec.commandSchema, "StateChangeSlice", false, DcbTag$Reventless.slicePartitionTag(boundaryPartition, S.Spec.name));
|
|
322
310
|
Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).forEach(param => bindHandler(param[0], generateCommand));
|
|
323
311
|
});
|
|
324
312
|
});
|
|
@@ -329,7 +317,7 @@ function Make(DcbEventLogStorage) {
|
|
|
329
317
|
if (ApiNoApiHelpers$ReventlessCore.isNoApi(commandSchema)) {
|
|
330
318
|
return;
|
|
331
319
|
}
|
|
332
|
-
let generateCommand = CommandGenerator_Callback$ReventlessCore.makeGenerateCommand(asyncOps.publishJsons, asyncOps.publishJsonsAndWait, S.Spec.name, S.Spec.commandSchema, "StateChangeSlice", false);
|
|
320
|
+
let generateCommand = CommandGenerator_Callback$ReventlessCore.makeGenerateCommand(asyncOps.publishJsons, asyncOps.publishJsonsAndWait, S.Spec.name, S.Spec.commandSchema, "StateChangeSlice", false, DcbTag$Reventless.slicePartitionTag(boundaryPartition, S.Spec.name));
|
|
333
321
|
Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).forEach(param => bindHandler(param[0], generateCommand));
|
|
334
322
|
});
|
|
335
323
|
});
|
|
@@ -437,7 +425,10 @@ function Make(DcbEventLogStorage) {
|
|
|
437
425
|
if (match !== undefined) {
|
|
438
426
|
return log.error("Dcb_Builder", undefined, `Two DCB slices in "` + name + `" both declare the command "` + tag + `". ` + "Owner stamping resolves a command by that name alone, so it cannot tell them apart. Rename one of the constructors.");
|
|
439
427
|
} else {
|
|
440
|
-
byTag[tag] =
|
|
428
|
+
byTag[tag] = [
|
|
429
|
+
commandSchema,
|
|
430
|
+
DcbTag$Reventless.slicePartitionTag(boundaryPartition, S.Spec.name)
|
|
431
|
+
];
|
|
441
432
|
return;
|
|
442
433
|
}
|
|
443
434
|
});
|
|
@@ -445,14 +436,15 @@ function Make(DcbEventLogStorage) {
|
|
|
445
436
|
}
|
|
446
437
|
});
|
|
447
438
|
let dcbGenerateCommandOutput = Component$ReventlessCore.operations(dcbCommandTopic).apply(ops => {
|
|
448
|
-
let make = commandSchema => CommandGenerator_Callback$ReventlessCore.makeGenerateCommand(ops.publishJsons, ops.publishJsonsAndWait, name, commandSchema, "StateChangeSlice", false);
|
|
439
|
+
let make = (commandSchema, partitionTag) => CommandGenerator_Callback$ReventlessCore.makeGenerateCommand(ops.publishJsons, ops.publishJsonsAndWait, name, commandSchema, "StateChangeSlice", false, partitionTag);
|
|
449
440
|
let byTag$1 = {};
|
|
450
441
|
Object.entries(byTag).forEach(param => {
|
|
451
|
-
|
|
442
|
+
let match = param[1];
|
|
443
|
+
byTag$1[param[0]] = make(match[0], match[1]);
|
|
452
444
|
});
|
|
453
445
|
return [
|
|
454
446
|
byTag$1,
|
|
455
|
-
make(Sury.json)
|
|
447
|
+
make(Sury.json, undefined)
|
|
456
448
|
];
|
|
457
449
|
});
|
|
458
450
|
let dcbHandler = Pulumi.all([
|
|
@@ -693,7 +685,6 @@ function Make(DcbEventLogStorage) {
|
|
|
693
685
|
|
|
694
686
|
export {
|
|
695
687
|
log,
|
|
696
|
-
toRelativePath,
|
|
697
688
|
variantTag,
|
|
698
689
|
mergedEventSchema,
|
|
699
690
|
emptyResult,
|
|
@@ -33,6 +33,8 @@ module type T = {
|
|
|
33
33
|
clause (instead of an AND-ed composite) so the decision read is a real
|
|
34
34
|
secondary-tag read. */
|
|
35
35
|
~crossPartitionTagKeys: array<string>=?,
|
|
36
|
+
/** This slice's partition, as `Dcb_Builder` derives it for the whole boundary. */
|
|
37
|
+
~partitionTag: Reventless.DcbTag.derivedPartitionTag=?,
|
|
36
38
|
~runtime: ReventlessInfra.RuntimeHints.t=?,
|
|
37
39
|
~opts: Pulumi.ComponentResource.options=?,
|
|
38
40
|
) => component
|
|
@@ -13,6 +13,7 @@ module Make = (
|
|
|
13
13
|
let makeJsonHandler = (
|
|
14
14
|
~tagKeysByEventType,
|
|
15
15
|
~crossPartitionTagKeys,
|
|
16
|
+
~partitionTag,
|
|
16
17
|
dcbEventLogOps: DcbEventLog.operations,
|
|
17
18
|
) => {
|
|
18
19
|
let handler: CommandTopic.jsonCommandsHandler = stream => {
|
|
@@ -42,6 +43,7 @@ module Make = (
|
|
|
42
43
|
Callback.handleCommands(
|
|
43
44
|
~tagKeysByEventType,
|
|
44
45
|
~crossPartitionTagKeys,
|
|
46
|
+
~partitionTag?,
|
|
45
47
|
dcbEventLogOps,
|
|
46
48
|
decodedStream,
|
|
47
49
|
)
|
|
@@ -54,6 +56,7 @@ module Make = (
|
|
|
54
56
|
~publishJsons: Pulumi.Output.t<CommandTopic.publishJsons>,
|
|
55
57
|
~tagKeysByEventType,
|
|
56
58
|
~crossPartitionTagKeys,
|
|
59
|
+
~partitionTag,
|
|
57
60
|
self,
|
|
58
61
|
_name,
|
|
59
62
|
) => {
|
|
@@ -67,6 +70,7 @@ module Make = (
|
|
|
67
70
|
let jsonHandler = makeJsonHandler(
|
|
68
71
|
~tagKeysByEventType,
|
|
69
72
|
~crossPartitionTagKeys,
|
|
73
|
+
~partitionTag,
|
|
70
74
|
dcbEventLogOps,
|
|
71
75
|
)
|
|
72
76
|
CommandTopic.registerHandler(
|
|
@@ -98,6 +102,7 @@ module Make = (
|
|
|
98
102
|
~publishJsons,
|
|
99
103
|
~tagKeysByEventType=Dict.make(),
|
|
100
104
|
~crossPartitionTagKeys=[],
|
|
105
|
+
~partitionTag=?,
|
|
101
106
|
~runtime as _=?,
|
|
102
107
|
~opts=?,
|
|
103
108
|
): StateChangeSlice.component =>
|
|
@@ -109,6 +114,7 @@ module Make = (
|
|
|
109
114
|
~publishJsons,
|
|
110
115
|
~tagKeysByEventType,
|
|
111
116
|
~crossPartitionTagKeys,
|
|
117
|
+
~partitionTag,
|
|
112
118
|
...
|
|
113
119
|
),
|
|
114
120
|
~opts,
|
|
@@ -18,7 +18,7 @@ let log = Logger$ReventlessCore.fromEnv();
|
|
|
18
18
|
function Make(Spec) {
|
|
19
19
|
return Behavior => {
|
|
20
20
|
let Callback = StateChangeSlice_Callback$ReventlessCore.Make(Spec)(Behavior);
|
|
21
|
-
let makeJsonHandler = (tagKeysByEventType, crossPartitionTagKeys, dcbEventLogOps) => (stream => {
|
|
21
|
+
let makeJsonHandler = (tagKeysByEventType, crossPartitionTagKeys, partitionTag, dcbEventLogOps) => (stream => {
|
|
22
22
|
let decodedStream = Stream.flatMap(Stream.mapEffect(stream, param => {
|
|
23
23
|
let reference = param.reference;
|
|
24
24
|
let json = param.command;
|
|
@@ -44,16 +44,16 @@ function Make(Spec) {
|
|
|
44
44
|
return Stream.empty;
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
-
return Callback.handleCommands(tagKeysByEventType, crossPartitionTagKeys, dcbEventLogOps, decodedStream);
|
|
47
|
+
return Callback.handleCommands(tagKeysByEventType, crossPartitionTagKeys, partitionTag, dcbEventLogOps, decodedStream);
|
|
48
48
|
});
|
|
49
|
-
let make = (dcbEventLog, publishJsons, tagKeysByEventTypeOpt, crossPartitionTagKeysOpt, param, opts) => {
|
|
49
|
+
let make = (dcbEventLog, publishJsons, tagKeysByEventTypeOpt, crossPartitionTagKeysOpt, partitionTag, param, opts) => {
|
|
50
50
|
let tagKeysByEventType = tagKeysByEventTypeOpt !== undefined ? tagKeysByEventTypeOpt : ({});
|
|
51
51
|
let crossPartitionTagKeys = crossPartitionTagKeysOpt !== undefined ? crossPartitionTagKeysOpt : [];
|
|
52
52
|
return Component$ReventlessCore.make(ComponentType$ReventlessCore.toString(StateChangeSlice$ReventlessCore.componentType), Spec.name, (extra, extra$1) => {
|
|
53
53
|
let commandSchema = Spec.commandSchema;
|
|
54
54
|
let commandTypeNames = CommandTopic$ReventlessCore.extractTypeNamesFromSchema(commandSchema);
|
|
55
55
|
Component$ReventlessCore.operations(dcbEventLog).apply(dcbEventLogOps => {
|
|
56
|
-
let jsonHandler = makeJsonHandler(tagKeysByEventType, crossPartitionTagKeys, dcbEventLogOps);
|
|
56
|
+
let jsonHandler = makeJsonHandler(tagKeysByEventType, crossPartitionTagKeys, partitionTag, dcbEventLogOps);
|
|
57
57
|
CommandTopic$ReventlessCore.registerHandler(commandSchema, jsonHandler, commandTypeNames);
|
|
58
58
|
});
|
|
59
59
|
Component$ReventlessCore.setOperations(extra, publishJsons.apply(publishJsons => ({
|
|
@@ -4,6 +4,8 @@ module type T = {
|
|
|
4
4
|
let handleCommands: (
|
|
5
5
|
~tagKeysByEventType: Dict.t<array<string>>=?,
|
|
6
6
|
~crossPartitionTagKeys: array<string>=?,
|
|
7
|
+
/** This slice's partition within its boundary; inferred from the slice alone when absent. */
|
|
8
|
+
~partitionTag: Reventless.DcbTag.derivedPartitionTag=?,
|
|
7
9
|
DcbEventLog.operations,
|
|
8
10
|
Stream.t<
|
|
9
11
|
CommandTopic.topicItem<Message.command'<Reventless.Id.String.t, Spec.command>>,
|
|
@@ -74,18 +76,32 @@ module Make = (
|
|
|
74
76
|
{eventType, data: JSON.Object(data), tags, meta}
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
// The partition inferred from this slice alone, for callers that do not thread
|
|
80
|
+
// the boundary's. Lazy and non-throwing: it only names ids in logs and outcomes,
|
|
81
|
+
// and a slice that needs its siblings to resolve must not fail to load for that.
|
|
82
|
+
let slicePartitionTag = Lazy.make(() =>
|
|
83
|
+
try Some(
|
|
84
|
+
Reventless.DcbTag.deriveSlicePartition({
|
|
85
|
+
name: Spec.name,
|
|
86
|
+
commandSchema: Spec.commandSchema->S.castToUnknown,
|
|
87
|
+
consumedEventSchema: Spec.consumedEventSchema->S.castToUnknown,
|
|
88
|
+
eventSchema: Spec.eventSchema->S.castToUnknown,
|
|
89
|
+
moduleUrl: Spec.moduleUrl,
|
|
90
|
+
}),
|
|
91
|
+
) catch {
|
|
92
|
+
| _ => None
|
|
93
|
+
}
|
|
94
|
+
)
|
|
81
95
|
|
|
82
96
|
// Extracts the entity id of a read event from its own tags, for logging
|
|
83
97
|
// which events the decision model was built from. Prefers this slice's
|
|
84
|
-
// partition key
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
98
|
+
// partition key, but consumed events from other sources are tagged by their
|
|
99
|
+
// own key (e.g. CatalogProductSynced → productId, not this slice's orderId),
|
|
100
|
+
// so fall back to the read event's own tag value(s) instead of logging no id.
|
|
101
|
+
let readEventId = (
|
|
102
|
+
~partitionTag: option<Reventless.DcbTag.derivedPartitionTag>,
|
|
103
|
+
tags: array<Reventless.DcbTag.tag>,
|
|
104
|
+
): option<string> => {
|
|
89
105
|
let ownTagValues = () => {
|
|
90
106
|
let vals =
|
|
91
107
|
tags->Array.reduce([], (acc, t: Reventless.DcbTag.tag) =>
|
|
@@ -93,15 +109,14 @@ module Make = (
|
|
|
93
109
|
)
|
|
94
110
|
vals->Array.length == 0 ? None : Some(vals->Array.join(","))
|
|
95
111
|
}
|
|
96
|
-
switch
|
|
97
|
-
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
)
|
|
101
|
-
|
|
|
102
|
-
| None => ownTagValues()
|
|
112
|
+
switch partitionTag {
|
|
113
|
+
| Some(Composite(spec)) => Some(Reventless.DcbTag.getCompositePartitionKeyValue(tags, spec))
|
|
114
|
+
| Some(pt) =>
|
|
115
|
+
switch tags->Reventless.DcbTag.partitionValueOfTags(pt) {
|
|
116
|
+
| "" => ownTagValues()
|
|
117
|
+
| v => Some(v)
|
|
103
118
|
}
|
|
104
|
-
|
|
|
119
|
+
| None => ownTagValues()
|
|
105
120
|
}
|
|
106
121
|
}
|
|
107
122
|
|
|
@@ -147,6 +162,7 @@ module Make = (
|
|
|
147
162
|
let handleSingleCommand = (
|
|
148
163
|
~tagKeysByEventType,
|
|
149
164
|
~crossPartitionTagKeys,
|
|
165
|
+
~partitionTag,
|
|
150
166
|
dcbEventLog: DcbEventLog.operations,
|
|
151
167
|
command': Message.command'<Reventless.Id.String.t, Spec.command>,
|
|
152
168
|
) => {
|
|
@@ -192,11 +208,12 @@ module Make = (
|
|
|
192
208
|
EffectLogger.logInfo(~comp, `query: ${queryDetail}`)->Effect.runSync
|
|
193
209
|
|
|
194
210
|
// Extract the entity ID from the command for use in Accepted outcomes.
|
|
195
|
-
let entityId = switch
|
|
196
|
-
|
|
|
197
|
-
| Composite(spec) =>
|
|
211
|
+
let entityId = switch partitionTag {
|
|
212
|
+
| Some(Reventless.DcbTag.Composite(spec)) =>
|
|
198
213
|
let tags = Reventless.DcbTag.extractTags(Spec.commandSchema, command'.command)
|
|
199
214
|
Some(Reventless.DcbTag.getCompositePartitionKeyValue(tags, spec))
|
|
215
|
+
| Some(Simple(pt)) => Reventless.DcbTag.getPartitionTagValue(query, pt)
|
|
216
|
+
| Some(ByEventType(_)) | None => None
|
|
200
217
|
}
|
|
201
218
|
|
|
202
219
|
// Fail closed: a query that can't be serialized yields `None`, not the empty
|
|
@@ -267,7 +284,12 @@ module Make = (
|
|
|
267
284
|
~eventType=raw.eventType,
|
|
268
285
|
~data=raw.data->JSON.Decode.object->Option.getOr(Dict.make()),
|
|
269
286
|
)
|
|
270
|
-
decoded->Option.map(event => (
|
|
287
|
+
decoded->Option.map(event => (
|
|
288
|
+
event,
|
|
289
|
+
raw.position,
|
|
290
|
+
raw.eventType,
|
|
291
|
+
readEventId(~partitionTag, raw.tags),
|
|
292
|
+
))
|
|
271
293
|
})
|
|
272
294
|
->Stream.flatMap(opt =>
|
|
273
295
|
switch opt {
|
|
@@ -474,14 +496,20 @@ module Make = (
|
|
|
474
496
|
let handleCommands = (
|
|
475
497
|
~tagKeysByEventType=Dict.make(),
|
|
476
498
|
~crossPartitionTagKeys=[],
|
|
499
|
+
~partitionTag=?,
|
|
477
500
|
dcbEventLog,
|
|
478
501
|
stream,
|
|
479
|
-
) =>
|
|
502
|
+
) => {
|
|
503
|
+
let partitionTag = switch partitionTag {
|
|
504
|
+
| Some(_) => partitionTag
|
|
505
|
+
| None => slicePartitionTag->Lazy.get
|
|
506
|
+
}
|
|
480
507
|
stream
|
|
481
508
|
->Stream.mapEffect(({ReventlessInfra.CommandTopic.reference: reference, command}) =>
|
|
482
509
|
handleSingleCommand(
|
|
483
510
|
~tagKeysByEventType,
|
|
484
511
|
~crossPartitionTagKeys,
|
|
512
|
+
~partitionTag,
|
|
485
513
|
dcbEventLog,
|
|
486
514
|
command,
|
|
487
515
|
)->Effect.map(result =>
|
|
@@ -492,4 +520,5 @@ module Make = (
|
|
|
492
520
|
)
|
|
493
521
|
)
|
|
494
522
|
->Stream.runCollect
|
|
523
|
+
}
|
|
495
524
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
|
|
4
4
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
|
+
import * as Stdlib_Lazy from "@rescript/runtime/lib/es6/Stdlib_Lazy.js";
|
|
5
6
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
6
7
|
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
7
8
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
@@ -22,12 +23,20 @@ function Make(Spec) {
|
|
|
22
23
|
let comp = `StateChangeSlice(` + Spec.name + `)`;
|
|
23
24
|
let decoder = DcbDecode$Reventless.makeDecoder(Spec.consumedEventSchema);
|
|
24
25
|
let queryEventTypes = decoder.eventTypes;
|
|
25
|
-
let
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
let slicePartitionTag = Stdlib_Lazy.make(() => {
|
|
27
|
+
try {
|
|
28
|
+
return DcbTag$Reventless.deriveSlicePartition({
|
|
29
|
+
name: Spec.name,
|
|
30
|
+
commandSchema: Spec.commandSchema,
|
|
31
|
+
consumedEventSchema: Spec.consumedEventSchema,
|
|
32
|
+
eventSchema: Spec.eventSchema,
|
|
33
|
+
moduleUrl: Spec.moduleUrl
|
|
34
|
+
});
|
|
35
|
+
} catch (exn) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
let readEventId = (partitionTag, tags) => {
|
|
31
40
|
let ownTagValues = () => {
|
|
32
41
|
let vals = Stdlib_Array.reduce(tags, [], (acc, t) => {
|
|
33
42
|
if (acc.includes(t.value)) {
|
|
@@ -42,24 +51,26 @@ function Make(Spec) {
|
|
|
42
51
|
return vals.join(",");
|
|
43
52
|
}
|
|
44
53
|
};
|
|
45
|
-
if (
|
|
46
|
-
return
|
|
54
|
+
if (partitionTag === undefined) {
|
|
55
|
+
return ownTagValues();
|
|
47
56
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
switch (partitionTag.TAG) {
|
|
58
|
+
case "Composite" :
|
|
59
|
+
return DcbTag$Reventless.getCompositePartitionKeyValue(tags, partitionTag._0);
|
|
60
|
+
case "Simple" :
|
|
61
|
+
case "ByEventType" :
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
let v = DcbTag$Reventless.partitionValueOfTags(tags, partitionTag);
|
|
65
|
+
if (v === "") {
|
|
57
66
|
return ownTagValues();
|
|
67
|
+
} else {
|
|
68
|
+
return v;
|
|
58
69
|
}
|
|
59
70
|
};
|
|
60
71
|
let projectionCache = Lru$ReventlessCore.make(100);
|
|
61
72
|
let resetCache = () => Lru$ReventlessCore.clear(projectionCache);
|
|
62
|
-
let handleSingleCommand = (tagKeysByEventType, crossPartitionTagKeys, dcbEventLog, command$p) => {
|
|
73
|
+
let handleSingleCommand = (tagKeysByEventType, crossPartitionTagKeys, partitionTag, dcbEventLog, command$p) => {
|
|
63
74
|
let cmdJson = Message$ReventlessCore.commandJsonOfCommand$p(Id$Reventless.$$String.toString, Spec.commandSchema, command$p);
|
|
64
75
|
Effect.runSync(EffectLogger$ReventlessCore.logInfo(comp, cmdJson.commandJson, `handling command: ` + LogFormat$ReventlessCore.cmdDetailNoId(cmdJson)));
|
|
65
76
|
let query = DcbTag$Reventless.buildQueryFromCommand(queryEventTypes, Spec.commandSchema, command$p.command, tagKeysByEventType, crossPartitionTagKeys);
|
|
@@ -76,11 +87,21 @@ function Make(Spec) {
|
|
|
76
87
|
}).join(" OR ");
|
|
77
88
|
Effect.runSync(EffectLogger$ReventlessCore.logInfo(comp, undefined, `query: ` + queryDetail));
|
|
78
89
|
let entityId;
|
|
79
|
-
if (
|
|
80
|
-
|
|
90
|
+
if (partitionTag !== undefined) {
|
|
91
|
+
switch (partitionTag.TAG) {
|
|
92
|
+
case "Simple" :
|
|
93
|
+
entityId = DcbTag$Reventless.getPartitionTagValue(query, partitionTag._0);
|
|
94
|
+
break;
|
|
95
|
+
case "Composite" :
|
|
96
|
+
let tags = DcbTag$Reventless.extractTags(Spec.commandSchema, command$p.command);
|
|
97
|
+
entityId = DcbTag$Reventless.getCompositePartitionKeyValue(tags, partitionTag._0);
|
|
98
|
+
break;
|
|
99
|
+
case "ByEventType" :
|
|
100
|
+
entityId = undefined;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
81
103
|
} else {
|
|
82
|
-
|
|
83
|
-
entityId = DcbTag$Reventless.getCompositePartitionKeyValue(tags, derivedPartitionTag._0);
|
|
104
|
+
entityId = undefined;
|
|
84
105
|
}
|
|
85
106
|
let cacheKey = JSON.stringify(query);
|
|
86
107
|
let cacheGet = () => {
|
|
@@ -131,7 +152,7 @@ function Make(Spec) {
|
|
|
131
152
|
event,
|
|
132
153
|
raw.position,
|
|
133
154
|
raw.eventType,
|
|
134
|
-
readEventId(raw.tags)
|
|
155
|
+
readEventId(partitionTag, raw.tags)
|
|
135
156
|
]);
|
|
136
157
|
}), opt => {
|
|
137
158
|
if (opt !== undefined) {
|
|
@@ -290,12 +311,13 @@ function Make(Spec) {
|
|
|
290
311
|
};
|
|
291
312
|
return attempt(3);
|
|
292
313
|
};
|
|
293
|
-
let handleCommands = (tagKeysByEventTypeOpt, crossPartitionTagKeysOpt, dcbEventLog, stream) => {
|
|
314
|
+
let handleCommands = (tagKeysByEventTypeOpt, crossPartitionTagKeysOpt, partitionTag, dcbEventLog, stream) => {
|
|
294
315
|
let tagKeysByEventType = tagKeysByEventTypeOpt !== undefined ? tagKeysByEventTypeOpt : ({});
|
|
295
316
|
let crossPartitionTagKeys = crossPartitionTagKeysOpt !== undefined ? crossPartitionTagKeysOpt : [];
|
|
317
|
+
let partitionTag$1 = partitionTag !== undefined ? partitionTag : Stdlib_Lazy.get(slicePartitionTag);
|
|
296
318
|
return Stream.runCollect(Stream$1.mapEffect(stream, param => {
|
|
297
319
|
let reference = param.reference;
|
|
298
|
-
return Effect.map(handleSingleCommand(tagKeysByEventType, crossPartitionTagKeys, dcbEventLog, param.command), result => {
|
|
320
|
+
return Effect.map(handleSingleCommand(tagKeysByEventType, crossPartitionTagKeys, partitionTag$1, dcbEventLog, param.command), result => {
|
|
299
321
|
if (result.TAG === "Ok") {
|
|
300
322
|
return {
|
|
301
323
|
TAG: "Ok",
|