@reventlessdev/reventless-infra 3.0.0-alpha.207 → 3.0.0-alpha.208

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 CHANGED
@@ -3,6 +3,22 @@
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.208 (2026-10-29)
7
+
8
+ * refactor!: a read model hears another plugin only through an Extension and a slice ([15c7cae](https://github.com/ReventlessDev/reventless-core/commit/15c7cae214d5aa04404779fcd87a8429aad1965c))
9
+ ### Features
10
+
11
+ * **core:** a handler drops a command it has already handled ([605862b](https://github.com/ReventlessDev/reventless-core/commit/605862bba765b1501eb19d08f3924bf619159e21))
12
+ * **infra:** a message a mapping derives gets a stable id and names its parent ([a72a08f](https://github.com/ReventlessDev/reventless-core/commit/a72a08f9af4440eff5fa36e9a364a426bc70918c))
13
+
14
+ ### BREAKING CHANGES
15
+
16
+ * `ReventlessInfra.Extension.T.make` and the core
17
+ `Extension_Builder.make` no longer take `~readModelNamesForSourceName` and
18
+ `~publishToReadModels`. Both arguments did nothing; drop them at the call site.
19
+
20
+
21
+
6
22
  # 3.0.0-alpha.207 (2026-10-25)
7
23
 
8
24
  **Note:** Version bump only for package @reventlessdev/reventless-infra
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-infra",
3
- "version": "3.0.0-alpha.207",
3
+ "version": "3.0.0-alpha.208",
4
4
  "description": "Infrastructure types for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -17,9 +17,9 @@
17
17
  "sury-ppx": "11.0.0",
18
18
  "uuid": "^13.0.0",
19
19
  "@reventlessdev/rescript-effect": "1.0.0-alpha.1",
20
- "@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
20
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.179",
21
21
  "@reventlessdev/rescript-pulumi-pulumi": "3.0.0-alpha.0",
22
- "@reventlessdev/reventless-spec": "3.0.0-alpha.178"
22
+ "@reventlessdev/rescript-uuid": "2.0.0-alpha.1"
23
23
  },
24
24
  "devDependencies": {
25
25
  "rescript": "12.3.1",
@@ -30,8 +30,6 @@ module type T = {
30
30
  let make: (
31
31
  ~publishToPluginExtensionPoint: CommandTopic.publishJsons,
32
32
  ~publishToAggregates: dict<CommandTopic.publishJsons>,
33
- ~readModelNamesForSourceName: dict<array<string>>,
34
- ~publishToReadModels: dict<EventCollector.enqueueEvent>,
35
33
  ~queryEngine: Reventless.QueryEngine.operations,
36
34
  ~opts: option<Pulumi.ComponentResource.options>,
37
35
  ) => component
@@ -257,28 +257,32 @@ module Make = (MappingImpl: Mapping): (
257
257
  let compLog = (comp, msg) =>
258
258
  Effect.logInfo(msg)->Effect.annotateLogs("comp", comp)->Effect.runSync
259
259
 
260
- let encodeMeta = (meta: Reventless.Message.meta, service) => {
261
- ...meta,
262
- service,
263
- msgId: Message.uuid(),
264
- }
265
-
266
260
  let doMapIncomingEvent = (
267
261
  mapIncomingEventImpl,
268
262
  {Reventless.Message.id: id, event, meta},
269
263
  pluginDef,
270
264
  queryEngine,
271
265
  ) => {
272
- let encodeTargetCommandJson = (targetCmd, targetId) => {
266
+ // Every command here is derived from the published event: `index` is the
267
+ // action's position in the mapping's result, `item` a command's position
268
+ // inside a multi-command action, so a redelivered event yields the same
269
+ // `msgId`s and the handler can drop the repeat.
270
+ let encodeTargetCommandJson = (targetCmd, targetId, ~index, ~item) => {
273
271
  let cmdJson = targetCmd->Reventless.Message.encode(Delegate.commandSchema)
274
272
  {
275
273
  Reventless.Message.id: targetId,
276
- meta: encodeMeta(meta, delegateName),
274
+ meta: Message.deriveStableMeta(~parent=meta, ~service=delegateName, ~index, ~item),
277
275
  commandJson: cmdJson,
278
276
  }
279
277
  }
280
278
 
281
- let encodeExtensionPointCommandJson = (commandJson, ~id, ~extensionPointName, ~action) => {
279
+ let encodeExtensionPointCommandJson = (
280
+ commandJson,
281
+ ~id,
282
+ ~extensionPointName,
283
+ ~action,
284
+ ~index,
285
+ ) => {
282
286
  compLog(
283
287
  `Extension(${extensionPointName})`,
284
288
  `${action}: ${commandJson
@@ -287,49 +291,55 @@ module Make = (MappingImpl: Mapping): (
287
291
  )
288
292
  {
289
293
  Reventless.Message.id,
290
- meta: encodeMeta(meta, extensionPointName),
294
+ meta: Message.deriveStableMeta(~parent=meta, ~service=extensionPointName, ~index),
291
295
  commandJson,
292
296
  }
293
297
  }
294
298
 
295
- let encodeExtensionPointCommand = (command, ~id, ~extensionPointName, ~action) =>
299
+ let encodeExtensionPointCommand = (command, ~id, ~extensionPointName, ~action, ~index) =>
296
300
  command
297
301
  ->Reventless.Message.encode(Spec.commandSchema)
298
- ->encodeExtensionPointCommandJson(~id, ~extensionPointName, ~action)
302
+ ->encodeExtensionPointCommandJson(~id, ~extensionPointName, ~action, ~index)
299
303
 
300
- mapIncomingEventImpl(id, event, meta, pluginDef, queryEngine)->Array.map(x =>
304
+ mapIncomingEventImpl(id, event, meta, pluginDef, queryEngine)->Array.mapWithIndex((x, index) =>
301
305
  switch x {
302
306
  | PublishAggregateCommand(targetId, targetCmd) =>
303
- AbstractPublishAggregateCommand(delegateName, targetCmd->encodeTargetCommandJson(targetId))
307
+ AbstractPublishAggregateCommand(
308
+ delegateName,
309
+ targetCmd->encodeTargetCommandJson(targetId, ~index, ~item=0),
310
+ )
304
311
  | PublishAggregateCommandAsync(promise) =>
305
312
  let toCommandJson = async promise => {
306
313
  let (targetId, targetCmd) = await promise
307
- (delegateName, targetCmd->encodeTargetCommandJson(targetId))
314
+ (delegateName, targetCmd->encodeTargetCommandJson(targetId, ~index, ~item=0))
308
315
  }
309
316
  AbstractPublishAggregateCommandAsync(promise->toCommandJson)
310
317
  | PublishAggregateCommandsAsync(promise) =>
311
318
  let toCommandJsons = async promise =>
312
- (await promise)->Array.map(((targetId, targetCmd)) => (
319
+ (await promise)->Array.mapWithIndex(((targetId, targetCmd), item) => (
313
320
  delegateName,
314
- targetCmd->encodeTargetCommandJson(targetId),
321
+ targetCmd->encodeTargetCommandJson(targetId, ~index, ~item),
315
322
  ))
316
323
  AbstractPublishAggregateCommandsAsync(promise->toCommandJsons)
317
324
  | PublishStateChangeSliceCommand(targetCmd) =>
318
325
  AbstractPublishAggregateCommand(
319
326
  delegateName,
320
- targetCmd->encodeTargetCommandJson(derivePartitionId(targetCmd)),
327
+ targetCmd->encodeTargetCommandJson(derivePartitionId(targetCmd), ~index, ~item=0),
321
328
  )
322
329
  | PublishStateChangeSliceCommandAsync(promise) =>
323
330
  let toCommandJson = async promise => {
324
331
  let targetCmd = await promise
325
- (delegateName, targetCmd->encodeTargetCommandJson(derivePartitionId(targetCmd)))
332
+ (
333
+ delegateName,
334
+ targetCmd->encodeTargetCommandJson(derivePartitionId(targetCmd), ~index, ~item=0),
335
+ )
326
336
  }
327
337
  AbstractPublishAggregateCommandAsync(promise->toCommandJson)
328
338
  | PublishStateChangeSliceCommandsAsync(promise) =>
329
339
  let toCommandJsons = async promise =>
330
- (await promise)->Array.map(targetCmd => (
340
+ (await promise)->Array.mapWithIndex((targetCmd, item) => (
331
341
  delegateName,
332
- targetCmd->encodeTargetCommandJson(derivePartitionId(targetCmd)),
342
+ targetCmd->encodeTargetCommandJson(derivePartitionId(targetCmd), ~index, ~item),
333
343
  ))
334
344
  AbstractPublishAggregateCommandsAsync(promise->toCommandJsons)
335
345
  | PublishExtensionPointCommand(id, command) if Spec.name == PluginExtensionPointSpec.name =>
@@ -338,6 +348,7 @@ module Make = (MappingImpl: Mapping): (
338
348
  ~id,
339
349
  ~extensionPointName,
340
350
  ~action="Publish PluginExtensionPoint command",
351
+ ~index,
341
352
  ),
342
353
  )
343
354
  | PublishExtensionPointCommand(id, command) =>
@@ -347,6 +358,7 @@ module Make = (MappingImpl: Mapping): (
347
358
  ~id,
348
359
  ~extensionPointName,
349
360
  ~action="Publish ExtensionPoint command",
361
+ ~index,
350
362
  ),
351
363
  )
352
364
  | ForwardCommand({extensionPointName, id, commandJson}) =>
@@ -356,6 +368,7 @@ module Make = (MappingImpl: Mapping): (
356
368
  ~id,
357
369
  ~extensionPointName,
358
370
  ~action="Forward ExtensionPoint command",
371
+ ~index,
359
372
  ),
360
373
  )
361
374
  | HandleDirective(handler, directive) =>
@@ -383,7 +396,13 @@ module Make = (MappingImpl: Mapping): (
383
396
  } else {
384
397
  let {id, meta, event} =
385
398
  targetEvent'Json->Reventless.Message.decodeEvent'(Delegate.Id.schema, Delegate.eventSchema)
386
- let encodeExtensionPointCommandJson = (commandJson, ~id, ~extensionPointName, ~action) => {
399
+ let encodeExtensionPointCommandJson = (
400
+ commandJson,
401
+ ~id,
402
+ ~extensionPointName,
403
+ ~action,
404
+ ~index,
405
+ ) => {
387
406
  compLog(
388
407
  `Extension(${delegateName})`,
389
408
  `${action}: ${commandJson
@@ -392,17 +411,20 @@ module Make = (MappingImpl: Mapping): (
392
411
  )
393
412
  {
394
413
  Reventless.Message.id,
395
- meta: encodeMeta(meta, extensionPointName),
414
+ meta: Message.deriveStableMeta(~parent=meta, ~service=extensionPointName, ~index),
396
415
  commandJson,
397
416
  }
398
417
  }
399
418
 
400
- let encodeExtensionPointCommand = (command, ~id, ~extensionPointName, ~action) =>
419
+ let encodeExtensionPointCommand = (command, ~id, ~extensionPointName, ~action, ~index) =>
401
420
  command
402
421
  ->Reventless.Message.encode(Spec.commandSchema)
403
- ->encodeExtensionPointCommandJson(~id, ~extensionPointName, ~action)
422
+ ->encodeExtensionPointCommandJson(~id, ~extensionPointName, ~action, ~index)
404
423
 
405
- mapOutgoingEventImpl(id->Delegate.Id.toString, event, meta, pluginDef)->Array.map(x =>
424
+ mapOutgoingEventImpl(id->Delegate.Id.toString, event, meta, pluginDef)->Array.mapWithIndex((
425
+ x,
426
+ index,
427
+ ) =>
406
428
  switch x {
407
429
  | PublishExtensionPointCommand(id, command) if Spec.name == PluginExtensionPointSpec.name =>
408
430
  AbstractPublishPluginExtensionPointCommand(
@@ -410,6 +432,7 @@ module Make = (MappingImpl: Mapping): (
410
432
  ~id,
411
433
  ~extensionPointName,
412
434
  ~action="Publish PluginExtensionPoint command",
435
+ ~index,
413
436
  ),
414
437
  )
415
438
 
@@ -420,6 +443,7 @@ module Make = (MappingImpl: Mapping): (
420
443
  ~id,
421
444
  ~extensionPointName,
422
445
  ~action="Publish ExtensionPoint command",
446
+ ~index,
423
447
  ),
424
448
  )
425
449
  | ForwardCommand({extensionPointName, id, commandJson}) =>
@@ -429,6 +453,7 @@ module Make = (MappingImpl: Mapping): (
429
453
  ~id,
430
454
  ~extensionPointName,
431
455
  ~action="Forward ExtensionPoint command",
456
+ ~index,
432
457
  ),
433
458
  )
434
459
  | HandleDirective(handler, directive) =>
@@ -1,7 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Sury from "sury";
4
- import * as Uuid from "uuid";
5
4
  import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
6
5
  import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
7
6
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
@@ -11,6 +10,7 @@ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/component
11
10
  import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
12
11
  import * as AnsiStyle$Reventless from "@reventlessdev/reventless-spec/src/AnsiStyle.res.mjs";
13
12
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
13
+ import * as Message$ReventlessInfra from "./Message.res.mjs";
14
14
  import * as PluginExtensionPointSpec$ReventlessInfra from "./PluginExtensionPointSpec.res.mjs";
15
15
 
16
16
  let schema = Sury.string;
@@ -98,46 +98,40 @@ function Make(MappingImpl) {
98
98
  };
99
99
  let derivePartitionId = targetCmd => DcbTag$Reventless.partitionValueOfTags(DcbTag$Reventless.extractTags(Delegate.commandSchema, targetCmd), getDerivedPartitionTag());
100
100
  let compLog = (comp, msg) => Effect.runSync(Effect.annotateLogs(Effect.logInfo(msg), "comp", comp));
101
- let encodeMeta = (meta, service) => {
102
- let newrecord = {...meta};
103
- newrecord.msgId = Uuid.v4();
104
- newrecord.service = service;
105
- return newrecord;
106
- };
107
101
  let mapIncomingEvent = (extra, extra$1, extra$2) => {
108
102
  let mapIncomingEventImpl = MappingImpl.mapIncomingEvent;
109
103
  let meta = extra.meta;
110
- let encodeTargetCommandJson = (targetCmd, targetId) => {
104
+ let encodeTargetCommandJson = (targetCmd, targetId, index, item) => {
111
105
  let cmdJson = Message$Reventless.encode(targetCmd, Delegate.commandSchema);
112
106
  return {
113
107
  id: targetId,
114
- meta: encodeMeta(meta, delegateName),
108
+ meta: Message$ReventlessInfra.deriveStableMeta(meta, delegateName, index, item),
115
109
  commandJson: cmdJson
116
110
  };
117
111
  };
118
- let encodeExtensionPointCommandJson = (commandJson, id, extensionPointName, action) => {
112
+ let encodeExtensionPointCommandJson = (commandJson, id, extensionPointName, action, index) => {
119
113
  compLog(`Extension(` + extensionPointName + `)`, action + `: ` + AnsiStyle$Reventless.bold(Message$Reventless.variantNameOfJson(commandJson)) + `(` + id + `)`);
120
114
  return {
121
115
  id: id,
122
- meta: encodeMeta(meta, extensionPointName),
116
+ meta: Message$ReventlessInfra.deriveStableMeta(meta, extensionPointName, index, undefined),
123
117
  commandJson: commandJson
124
118
  };
125
119
  };
126
- let encodeExtensionPointCommand = (command, id, extensionPointName, action) => encodeExtensionPointCommandJson(Message$Reventless.encode(command, Spec.commandSchema), id, extensionPointName, action);
127
- return mapIncomingEventImpl(extra.id, extra.event, meta, extra$1, extra$2).map(x => {
120
+ let encodeExtensionPointCommand = (command, id, extensionPointName, action, index) => encodeExtensionPointCommandJson(Message$Reventless.encode(command, Spec.commandSchema), id, extensionPointName, action, index);
121
+ return mapIncomingEventImpl(extra.id, extra.event, meta, extra$1, extra$2).map((x, index) => {
128
122
  switch (x.TAG) {
129
123
  case "PublishAggregateCommand" :
130
124
  return {
131
125
  TAG: "AbstractPublishAggregateCommand",
132
126
  _0: delegateName,
133
- _1: encodeTargetCommandJson(x._1, x._0)
127
+ _1: encodeTargetCommandJson(x._1, x._0, index, 0)
134
128
  };
135
129
  case "PublishAggregateCommandAsync" :
136
130
  let toCommandJson = async promise => {
137
131
  let match = await promise;
138
132
  return [
139
133
  delegateName,
140
- encodeTargetCommandJson(match[1], match[0])
134
+ encodeTargetCommandJson(match[1], match[0], index, 0)
141
135
  ];
142
136
  };
143
137
  return {
@@ -145,9 +139,9 @@ function Make(MappingImpl) {
145
139
  _0: toCommandJson(x._0)
146
140
  };
147
141
  case "PublishAggregateCommandsAsync" :
148
- let toCommandJsons = async promise => (await promise).map(param => [
142
+ let toCommandJsons = async promise => (await promise).map((param, item) => [
149
143
  delegateName,
150
- encodeTargetCommandJson(param[1], param[0])
144
+ encodeTargetCommandJson(param[1], param[0], index, item)
151
145
  ]);
152
146
  return {
153
147
  TAG: "AbstractPublishAggregateCommandsAsync",
@@ -158,14 +152,14 @@ function Make(MappingImpl) {
158
152
  return {
159
153
  TAG: "AbstractPublishAggregateCommand",
160
154
  _0: delegateName,
161
- _1: encodeTargetCommandJson(targetCmd, derivePartitionId(targetCmd))
155
+ _1: encodeTargetCommandJson(targetCmd, derivePartitionId(targetCmd), index, 0)
162
156
  };
163
157
  case "PublishStateChangeSliceCommandAsync" :
164
158
  let toCommandJson$1 = async promise => {
165
159
  let targetCmd = await promise;
166
160
  return [
167
161
  delegateName,
168
- encodeTargetCommandJson(targetCmd, derivePartitionId(targetCmd))
162
+ encodeTargetCommandJson(targetCmd, derivePartitionId(targetCmd), index, 0)
169
163
  ];
170
164
  };
171
165
  return {
@@ -173,9 +167,9 @@ function Make(MappingImpl) {
173
167
  _0: toCommandJson$1(x._0)
174
168
  };
175
169
  case "PublishStateChangeSliceCommandsAsync" :
176
- let toCommandJsons$1 = async promise => (await promise).map(targetCmd => [
170
+ let toCommandJsons$1 = async promise => (await promise).map((targetCmd, item) => [
177
171
  delegateName,
178
- encodeTargetCommandJson(targetCmd, derivePartitionId(targetCmd))
172
+ encodeTargetCommandJson(targetCmd, derivePartitionId(targetCmd), index, item)
179
173
  ]);
180
174
  return {
181
175
  TAG: "AbstractPublishAggregateCommandsAsync",
@@ -187,13 +181,13 @@ function Make(MappingImpl) {
187
181
  if (Spec.name === PluginExtensionPointSpec$ReventlessInfra.name) {
188
182
  return {
189
183
  TAG: "AbstractPublishPluginExtensionPointCommand",
190
- _0: encodeExtensionPointCommand(command, id, extensionPointName, "Publish PluginExtensionPoint command")
184
+ _0: encodeExtensionPointCommand(command, id, extensionPointName, "Publish PluginExtensionPoint command", index)
191
185
  };
192
186
  } else {
193
187
  return {
194
188
  TAG: "AbstractPublishExtensionPointCommand",
195
189
  _0: extensionPointName,
196
- _1: encodeExtensionPointCommand(command, id, extensionPointName, "Publish ExtensionPoint command")
190
+ _1: encodeExtensionPointCommand(command, id, extensionPointName, "Publish ExtensionPoint command", index)
197
191
  };
198
192
  }
199
193
  case "ForwardCommand" :
@@ -202,7 +196,7 @@ function Make(MappingImpl) {
202
196
  return {
203
197
  TAG: "AbstractPublishExtensionPointCommand",
204
198
  _0: extensionPointName$1,
205
- _1: encodeExtensionPointCommandJson(match.commandJson, match.id, extensionPointName$1, "Forward ExtensionPoint command")
199
+ _1: encodeExtensionPointCommandJson(match.commandJson, match.id, extensionPointName$1, "Forward ExtensionPoint command", index)
206
200
  };
207
201
  case "HandleDirective" :
208
202
  let directive = x._1;
@@ -223,16 +217,16 @@ function Make(MappingImpl) {
223
217
  }
224
218
  let match = Message$Reventless.decodeEvent$p(extra, Delegate.Id.schema, Delegate.eventSchema);
225
219
  let meta = match.meta;
226
- let encodeExtensionPointCommandJson = (commandJson, id, extensionPointName, action) => {
220
+ let encodeExtensionPointCommandJson = (commandJson, id, extensionPointName, action, index) => {
227
221
  compLog(`Extension(` + delegateName + `)`, action + `: ` + AnsiStyle$Reventless.bold(Message$Reventless.variantNameOfJson(commandJson)) + `(` + id + `)`);
228
222
  return {
229
223
  id: id,
230
- meta: encodeMeta(meta, extensionPointName),
224
+ meta: Message$ReventlessInfra.deriveStableMeta(meta, extensionPointName, index, undefined),
231
225
  commandJson: commandJson
232
226
  };
233
227
  };
234
- let encodeExtensionPointCommand = (command, id, extensionPointName, action) => encodeExtensionPointCommandJson(Message$Reventless.encode(command, Spec.commandSchema), id, extensionPointName, action);
235
- return mapOutgoingEventImpl(Delegate.Id.toString(match.id), match.event, meta, extra$1).map(x => {
228
+ let encodeExtensionPointCommand = (command, id, extensionPointName, action, index) => encodeExtensionPointCommandJson(Message$Reventless.encode(command, Spec.commandSchema), id, extensionPointName, action, index);
229
+ return mapOutgoingEventImpl(Delegate.Id.toString(match.id), match.event, meta, extra$1).map((x, index) => {
236
230
  switch (x.TAG) {
237
231
  case "PublishExtensionPointCommand" :
238
232
  let command = x._1;
@@ -240,13 +234,13 @@ function Make(MappingImpl) {
240
234
  if (Spec.name === PluginExtensionPointSpec$ReventlessInfra.name) {
241
235
  return {
242
236
  TAG: "AbstractPublishPluginExtensionPointCommand",
243
- _0: encodeExtensionPointCommand(command, id, extensionPointName, "Publish PluginExtensionPoint command")
237
+ _0: encodeExtensionPointCommand(command, id, extensionPointName, "Publish PluginExtensionPoint command", index)
244
238
  };
245
239
  } else {
246
240
  return {
247
241
  TAG: "AbstractPublishExtensionPointCommand",
248
242
  _0: extensionPointName,
249
- _1: encodeExtensionPointCommand(command, id, extensionPointName, "Publish ExtensionPoint command")
243
+ _1: encodeExtensionPointCommand(command, id, extensionPointName, "Publish ExtensionPoint command", index)
250
244
  };
251
245
  }
252
246
  case "ForwardCommand" :
@@ -255,7 +249,7 @@ function Make(MappingImpl) {
255
249
  return {
256
250
  TAG: "AbstractPublishExtensionPointCommand",
257
251
  _0: extensionPointName$1,
258
- _1: encodeExtensionPointCommandJson(match.commandJson, match.id, extensionPointName$1, "Forward ExtensionPoint command")
252
+ _1: encodeExtensionPointCommandJson(match.commandJson, match.id, extensionPointName$1, "Forward ExtensionPoint command", index)
259
253
  };
260
254
  case "HandleDirective" :
261
255
  let directive = x._1;
@@ -170,7 +170,10 @@ module Make = (MappingImpl: Mapping): (
170
170
  CommandTopic.reference: reference,
171
171
  command: {Reventless.Message.id: id, command, meta},
172
172
  }) =>
173
- mapIncomingEventImpl(id->Reventless.Id.String.toString, command, meta)->Array.map(x =>
173
+ mapIncomingEventImpl(id->Reventless.Id.String.toString, command, meta)->Array.mapWithIndex((
174
+ x,
175
+ index,
176
+ ) =>
174
177
  switch x {
175
178
  | PublishCommand(targetId, targetCmd) =>
176
179
  let cmdJson = targetCmd->Reventless.Message.encode(Delegate.commandSchema)
@@ -186,11 +189,7 @@ module Make = (MappingImpl: Mapping): (
186
189
  reference,
187
190
  {
188
191
  id: targetId,
189
- meta: {
190
- ...meta,
191
- service: Delegate.name,
192
- msgId: Message.uuid(),
193
- },
192
+ meta: Message.deriveStableMeta(~parent=meta, ~service=Delegate.name, ~index),
194
193
  commandJson: targetCmd->Reventless.Message.encode(Delegate.commandSchema),
195
194
  },
196
195
  )
@@ -230,12 +229,15 @@ module Make = (MappingImpl: Mapping): (
230
229
  } else {
231
230
  let {id, meta, event} =
232
231
  targetEventJson'->Reventless.Message.decodeEvent'(Delegate.Id.schema, Delegate.eventSchema)
232
+ // The published event is derived from the Delegate's: `index` is the
233
+ // action's position, so a stream redelivery of the same event publishes
234
+ // the same `msgId`s.
233
235
  mapOutgoingEventImpl(
234
236
  id->Delegate.Id.toString,
235
237
  event,
236
238
  meta,
237
239
  queryEngine,
238
- )->Array.map(eventAction =>
240
+ )->Array.mapWithIndex((eventAction, index) =>
239
241
  switch eventAction {
240
242
  | PublishEvent(id, event) =>
241
243
  let eventJson = event->Reventless.Message.encode(Spec.eventSchema)
@@ -245,11 +247,7 @@ module Make = (MappingImpl: Mapping): (
245
247
  ->Reventless.Message.variantNameOfJson
246
248
  ->Reventless.AnsiStyle.bold}(${id})`,
247
249
  )
248
- let meta = {
249
- ...meta,
250
- service: Spec.name,
251
- msgId: Message.uuid(),
252
- }
250
+ let meta = Message.deriveStableMeta(~parent=meta, ~service=Spec.name, ~index)
253
251
  let eventJson' = Reventless.Message.composeEventJson'(id, meta, eventJson)
254
252
  AbstractPublishEvent(id, meta, eventJson')
255
253
  | PublishEventAsync(promise) =>
@@ -262,7 +260,8 @@ module Make = (MappingImpl: Mapping): (
262
260
  ->Reventless.Message.variantNameOfJson
263
261
  ->Reventless.AnsiStyle.bold}(${id}) (async)`,
264
262
  )
265
- let eventJson' = Reventless.Message.composeEventJson'(id, meta, eventJson) // TODO: check if meta is correct
263
+ let meta = Message.deriveStableMeta(~parent=meta, ~service=Spec.name, ~index)
264
+ let eventJson' = Reventless.Message.composeEventJson'(id, meta, eventJson)
266
265
  (id, meta, eventJson')
267
266
  }
268
267
  AbstractPublishEventAsync(promise->toEvent')
@@ -1,6 +1,5 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Uuid from "uuid";
4
3
  import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
4
  import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
6
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
@@ -8,6 +7,7 @@ import * as Effect from "effect/Effect";
8
7
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
9
8
  import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
10
9
  import * as AnsiStyle$Reventless from "@reventlessdev/reventless-spec/src/AnsiStyle.res.mjs";
10
+ import * as Message$ReventlessInfra from "./Message.res.mjs";
11
11
 
12
12
  function Make(MappingImpl) {
13
13
  let Spec = MappingImpl.ExtensionPoint;
@@ -22,20 +22,19 @@ function Make(MappingImpl) {
22
22
  let reference = param.reference;
23
23
  let match = param.command;
24
24
  let meta = match.meta;
25
- return mapIncomingEventImpl(Id$Reventless.$$String.toString(match.id), match.command, meta).map(x => {
25
+ return mapIncomingEventImpl(Id$Reventless.$$String.toString(match.id), match.command, meta).map((x, index) => {
26
26
  if (x.TAG === "PublishCommand") {
27
27
  let targetCmd = x._1;
28
28
  let targetId = x._0;
29
29
  let cmdJson = Message$Reventless.encode(targetCmd, Delegate.commandSchema);
30
30
  compLog(`ExtensionPoint(` + extensionPointName + `)`, `EP→` + delegateName + `: ` + AnsiStyle$Reventless.bold(Message$Reventless.variantNameOfJson(cmdJson)) + `(` + targetId + `)`);
31
- let newrecord = {...meta};
32
31
  return {
33
32
  TAG: "AbstractPublishCommand",
34
33
  _0: delegateName,
35
34
  _1: reference,
36
35
  _2: {
37
36
  id: targetId,
38
- meta: (newrecord.msgId = Uuid.v4(), newrecord.service = Delegate.name, newrecord),
37
+ meta: Message$ReventlessInfra.deriveStableMeta(meta, Delegate.name, index, undefined),
39
38
  commandJson: Message$Reventless.encode(targetCmd, Delegate.commandSchema)
40
39
  }
41
40
  };
@@ -59,20 +58,18 @@ function Make(MappingImpl) {
59
58
  }
60
59
  let match = Message$Reventless.decodeEvent$p(extra, Delegate.Id.schema, Delegate.eventSchema);
61
60
  let meta = match.meta;
62
- return mapOutgoingEventImpl(Delegate.Id.toString(match.id), match.event, meta, extra$3).map(eventAction => {
61
+ return mapOutgoingEventImpl(Delegate.Id.toString(match.id), match.event, meta, extra$3).map((eventAction, index) => {
63
62
  switch (eventAction.TAG) {
64
63
  case "PublishEvent" :
65
64
  let id = eventAction._0;
66
65
  let eventJson = Message$Reventless.encode(eventAction._1, Spec.eventSchema);
67
66
  compLog(`ExtensionPoint(` + extensionPointName + `)`, `mapped ` + delegateName + ` → ` + AnsiStyle$Reventless.bold(Message$Reventless.variantNameOfJson(eventJson)) + `(` + id + `)`);
68
- let newrecord = {...meta};
69
- newrecord.msgId = Uuid.v4();
70
- newrecord.service = Spec.name;
71
- let eventJson$p = Message$Reventless.composeEventJson$p(id, newrecord, eventJson);
67
+ let meta$1 = Message$ReventlessInfra.deriveStableMeta(meta, Spec.name, index, undefined);
68
+ let eventJson$p = Message$Reventless.composeEventJson$p(id, meta$1, eventJson);
72
69
  return {
73
70
  TAG: "AbstractPublishEvent",
74
71
  _0: id,
75
- _1: newrecord,
72
+ _1: meta$1,
76
73
  _2: eventJson$p
77
74
  };
78
75
  case "PublishEventAsync" :
@@ -81,10 +78,11 @@ function Make(MappingImpl) {
81
78
  let id = match[0];
82
79
  let eventJson = Message$Reventless.encode(match[1], Spec.eventSchema);
83
80
  compLog(`ExtensionPoint(` + extensionPointName + `)`, `mapped ` + delegateName + ` → ` + AnsiStyle$Reventless.bold(Message$Reventless.variantNameOfJson(eventJson)) + `(` + id + `) (async)`);
84
- let eventJson$p = Message$Reventless.composeEventJson$p(id, meta, eventJson);
81
+ let meta$2 = Message$ReventlessInfra.deriveStableMeta(meta, Spec.name, index, undefined);
82
+ let eventJson$p = Message$Reventless.composeEventJson$p(id, meta$2, eventJson);
85
83
  return [
86
84
  id,
87
- meta,
85
+ meta$2,
88
86
  eventJson$p
89
87
  ];
90
88
  };
@@ -113,4 +111,4 @@ function Make(MappingImpl) {
113
111
  export {
114
112
  Make,
115
113
  }
116
- /* uuid Not a pure module */
114
+ /* Id-Reventless Not a pure module */
@@ -1,3 +1,66 @@
1
1
  /** Generates a new UUID v4 string. */
2
2
  @module("uuid") @val
3
3
  external uuid: unit => string = "v4"
4
+
5
+ /** The namespace `deriveStableMeta` mints ids in: one fixed value, so the same
6
+ input gives the same id in every process. */
7
+ let derivedIdNamespace = Uuid.Namespace.custom("c4e9c2e6-8b3f-4f0e-9a52-6d2b4c7e1a90")
8
+
9
+ /** The header `deriveStableMeta` sets, which marks a message whose `msgId` is a
10
+ function of its input. A handler reads it as "a repeat of this command can be
11
+ recognized", and `deriveMeta` strips it so the mark never outlives the id. */
12
+ let stableIdHeader = "reventless-stable-id"
13
+
14
+ /** Whether this message's `msgId` was derived from its input, so that a second
15
+ delivery carries the same id. */
16
+ let hasStableMsgId = (meta: Reventless.Message.meta): bool =>
17
+ meta.headers->Option.flatMap(h => h->Dict.get(stableIdHeader))->Option.isSome
18
+
19
+ let withStableIdHeader = (headers: option<dict<string>>): dict<string> => {
20
+ let out = headers->Option.mapOr(Dict.make(), Dict.copy)
21
+ out->Dict.set(stableIdHeader, "true")
22
+ out
23
+ }
24
+
25
+ /** The inherited headers without the stable-id mark: a message whose id is fresh
26
+ must not claim otherwise. `None` when nothing is left. */
27
+ let withoutStableIdHeader = (headers: option<dict<string>>): option<dict<string>> =>
28
+ switch headers {
29
+ | Some(h) if h->Dict.has(stableIdHeader) =>
30
+ let out = h->Dict.copy
31
+ out->Dict.delete(stableIdHeader)
32
+ out->Dict.keysToArray->Array.length == 0 ? None : Some(out)
33
+ | other => other
34
+ }
35
+
36
+ /**
37
+ The meta of a message a mapping derives from `parent`.
38
+
39
+ The envelope is the one `deriveMeta` documents: `causationId` is the parent's
40
+ `msgId`, `correlationId` and the tracing fields are inherited, `service` is the
41
+ emitter. The `msgId` is a function of the input: the parent's `msgId`, the
42
+ emitter and the action's `index` in the mapping's result (`item` for one command
43
+ of a multi-command action). A mapping run again on the same input produces the
44
+ same ids, which is what lets a handler recognize a redelivered fact; two inputs
45
+ or two actions of one input never collide. The `stableIdHeader` marks the result.
46
+ */
47
+ let deriveStableMeta = (
48
+ ~parent: Reventless.Message.meta,
49
+ ~service,
50
+ ~index,
51
+ ~item=0,
52
+ ): Reventless.Message.meta => {
53
+ service,
54
+ time: Date.make()->Date.toISOString,
55
+ msgId: Uuid.v5(
56
+ ~name=`${parent.msgId}/${service}/${index->Int.toString}/${item->Int.toString}`,
57
+ ~namespace=derivedIdNamespace,
58
+ ),
59
+ correlationId: parent.correlationId,
60
+ causationId: parent.msgId,
61
+ ip: ?parent.ip,
62
+ user: ?parent.user,
63
+ traceparent: ?parent.traceparent,
64
+ schemaVersion: ?parent.schemaVersion,
65
+ headers: withStableIdHeader(parent.headers),
66
+ }
@@ -1,2 +1,61 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
- /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
2
+
3
+ import * as Uuid from "uuid";
4
+ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+
7
+ let derivedIdNamespace = "c4e9c2e6-8b3f-4f0e-9a52-6d2b4c7e1a90";
8
+
9
+ let stableIdHeader = "reventless-stable-id";
10
+
11
+ function hasStableMsgId(meta) {
12
+ return Stdlib_Option.isSome(Stdlib_Option.flatMap(meta.headers, h => h[stableIdHeader]));
13
+ }
14
+
15
+ function withStableIdHeader(headers) {
16
+ let out = Stdlib_Option.mapOr(headers, {}, prim => Object.assign({}, prim));
17
+ out[stableIdHeader] = "true";
18
+ return out;
19
+ }
20
+
21
+ function withoutStableIdHeader(headers) {
22
+ if (headers === undefined) {
23
+ return headers;
24
+ }
25
+ if (!(stableIdHeader in headers)) {
26
+ return headers;
27
+ }
28
+ let out = Object.assign({}, headers);
29
+ Stdlib_Dict.$$delete(out, stableIdHeader);
30
+ if (Object.keys(out).length === 0) {
31
+ return;
32
+ } else {
33
+ return out;
34
+ }
35
+ }
36
+
37
+ function deriveStableMeta(parent, service, index, itemOpt) {
38
+ let item = itemOpt !== undefined ? itemOpt : 0;
39
+ return {
40
+ service: service,
41
+ time: new Date().toISOString(),
42
+ ip: parent.ip,
43
+ user: parent.user,
44
+ msgId: Uuid.v5(parent.msgId + `/` + service + `/` + index.toString() + `/` + item.toString(), derivedIdNamespace),
45
+ correlationId: parent.correlationId,
46
+ causationId: parent.msgId,
47
+ traceparent: parent.traceparent,
48
+ schemaVersion: parent.schemaVersion,
49
+ headers: withStableIdHeader(parent.headers)
50
+ };
51
+ }
52
+
53
+ export {
54
+ derivedIdNamespace,
55
+ stableIdHeader,
56
+ hasStableMsgId,
57
+ withStableIdHeader,
58
+ withoutStableIdHeader,
59
+ deriveStableMeta,
60
+ }
61
+ /* uuid Not a pure module */