@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/package.json +9 -9
  3. package/src/adapter/Secrets/Secrets_Node_Backend.res +60 -0
  4. package/src/adapter/Secrets/Secrets_Node_Backend.res.mjs +69 -0
  5. package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
  6. package/src/components/Aggregate/Aggregate_Builder.res.mjs +1 -1
  7. package/src/components/CommandGenerator/CommandGenerator.res +4 -4
  8. package/src/components/CommandGenerator/CommandGenerator_Callback.res +13 -20
  9. package/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs +3 -24
  10. package/src/components/Dcb/Dcb_Builder.res +45 -49
  11. package/src/components/Dcb/Dcb_Builder.res.mjs +31 -40
  12. package/src/components/StateChangeSlice/StateChangeSlice.res +2 -0
  13. package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +6 -0
  14. package/src/components/StateChangeSlice/StateChangeSlice_Builder.res.mjs +4 -4
  15. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +51 -22
  16. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +47 -25
  17. package/src/plugin/component/Plugin_Structure.res +101 -4
  18. package/src/plugin/component/Plugin_Structure.res.mjs +82 -9
  19. package/tests/Secrets_Node_BackendTest.res +141 -0
  20. package/tests/Secrets_Node_BackendTest.res.mjs +152 -0
  21. package/tests/adapter/RuntimeExtensionHookReachTest.res.mjs +1 -1
  22. package/tests/admin/Platform_Admin_StructureTest.res +12 -3
  23. package/tests/admin/Platform_Admin_StructureTest.res.mjs +4 -3
  24. package/tests/commandgenerator/CommandGeneratorCallbackTest.res +1 -1
  25. package/tests/commandgenerator/CommandGeneratorCallbackTest.res.mjs +1 -1
  26. package/tests/commandgenerator/CommandGeneratorFixtures.res +2 -0
  27. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +17 -3
  28. package/tests/commandgenerator/OwnerStampingTest.res.mjs +2 -2
  29. package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +1 -1
  30. package/tests/dcb/DcbFixtures.res +18 -1
  31. package/tests/dcb/DcbFixtures.res.mjs +14 -0
  32. package/tests/dcb/DcbScopeInferenceTest.res +234 -0
  33. package/tests/dcb/DcbScopeInferenceTest.res.mjs +425 -13
  34. package/tests/dcb/DcbStateChangeSliceTest.res.mjs +22 -22
  35. package/tests/dcb/DcbTagTest.res +81 -68
  36. package/tests/dcb/DcbTagTest.res.mjs +88 -89
  37. package/tests/plugin/PluginStructureAccessTest.res +1 -0
  38. package/tests/plugin/PluginStructureAccessTest.res.mjs +1 -1
  39. package/tests/plugin/PluginStructureTest.res +47 -0
  40. 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 partitionTag = DcbTag$Reventless.derivePartitionTag(producedNamed);
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(stateChangeSlices.map(Sc => ({
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] = commandSchema;
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
- byTag$1[param[0]] = make(param[1]);
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
- // Computed once at functor init — used to extract entityId for publishJsonsAndWait outcomes.
78
- let derivedPartitionTag = Reventless.DcbTag.derivePartitionTag([
79
- (Spec.name, Behavior.moduleUrl, Spec.eventSchema->S.castToUnknown),
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 (derived from the produced event), but consumed events from
85
- // other sources are tagged by their own key (e.g. CatalogProductSynced →
86
- // productId, not this slice's orderId), so fall back to the read event's own
87
- // tag value(s) instead of logging no id.
88
- let readEventId = (tags: array<Reventless.DcbTag.tag>): option<string> => {
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 derivedPartitionTag {
97
- | Simple(pt) =>
98
- switch tags->Array.findMap((t: Reventless.DcbTag.tag) =>
99
- t.key == pt.key ? Some(t.value) : None
100
- ) {
101
- | Some(v) => Some(v)
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
- | Composite(spec) => Some(Reventless.DcbTag.getCompositePartitionKeyValue(tags, spec))
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 derivedPartitionTag {
196
- | Simple(pt) => Reventless.DcbTag.getPartitionTagValue(query, pt)
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 => (event, raw.position, raw.eventType, readEventId(raw.tags)))
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 derivedPartitionTag = DcbTag$Reventless.derivePartitionTag([[
26
- Spec.name,
27
- Behavior.moduleUrl,
28
- Spec.eventSchema
29
- ]]);
30
- let readEventId = tags => {
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 (derivedPartitionTag.TAG !== "Simple") {
46
- return DcbTag$Reventless.getCompositePartitionKeyValue(tags, derivedPartitionTag._0);
54
+ if (partitionTag === undefined) {
55
+ return ownTagValues();
47
56
  }
48
- let pt = derivedPartitionTag._0;
49
- let v = Stdlib_Array.findMap(tags, t => {
50
- if (t.key === pt.key) {
51
- return t.value;
52
- }
53
- });
54
- if (v !== undefined) {
55
- return v;
56
- } else {
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 (derivedPartitionTag.TAG === "Simple") {
80
- entityId = DcbTag$Reventless.getPartitionTagValue(query, derivedPartitionTag._0);
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
- let tags = DcbTag$Reventless.extractTags(Spec.commandSchema, command$p.command);
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",