@reventlessdev/reventless-infra 3.0.0-alpha.81

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 (73) hide show
  1. package/CHANGELOG.md +709 -0
  2. package/LICENSE +202 -0
  3. package/package.json +35 -0
  4. package/rescript.json +28 -0
  5. package/src/adapter/Adapter.res +88 -0
  6. package/src/adapter/Adapter.res.mjs +29 -0
  7. package/src/components/Aggregate.res +73 -0
  8. package/src/components/Aggregate.res.mjs +2 -0
  9. package/src/components/Api.res +124 -0
  10. package/src/components/Api.res.mjs +31 -0
  11. package/src/components/Api_Adapter.res +39 -0
  12. package/src/components/Api_Adapter.res.mjs +2 -0
  13. package/src/components/AutomationSlice.res +48 -0
  14. package/src/components/AutomationSlice.res.mjs +2 -0
  15. package/src/components/CommandGenerator.res +8 -0
  16. package/src/components/CommandGenerator.res.mjs +2 -0
  17. package/src/components/CommandTopic.res +70 -0
  18. package/src/components/CommandTopic.res.mjs +2 -0
  19. package/src/components/Component.js +56 -0
  20. package/src/components/Component.mjs +10 -0
  21. package/src/components/Component.res +90 -0
  22. package/src/components/Component.res.mjs +57 -0
  23. package/src/components/Component.resi +21 -0
  24. package/src/components/Counter.res +81 -0
  25. package/src/components/Counter.res.mjs +2 -0
  26. package/src/components/DcbEventLog.res +115 -0
  27. package/src/components/DcbEventLog.res.mjs +2 -0
  28. package/src/components/EventCollector.res +19 -0
  29. package/src/components/EventCollector.res.mjs +2 -0
  30. package/src/components/EventLog.res +24 -0
  31. package/src/components/EventLog.res.mjs +2 -0
  32. package/src/components/EventMapper.res +37 -0
  33. package/src/components/EventMapper.res.mjs +2 -0
  34. package/src/components/EventTopic.res +51 -0
  35. package/src/components/EventTopic.res.mjs +2 -0
  36. package/src/components/Extension.res +64 -0
  37. package/src/components/Extension.res.mjs +2 -0
  38. package/src/components/ExtensionPoint.res +62 -0
  39. package/src/components/ExtensionPoint.res.mjs +2 -0
  40. package/src/components/Heartbeat.res +7 -0
  41. package/src/components/Heartbeat.res.mjs +2 -0
  42. package/src/components/InboundTranslationSlice.res +41 -0
  43. package/src/components/InboundTranslationSlice.res.mjs +2 -0
  44. package/src/components/OutboundTranslationSlice.res +44 -0
  45. package/src/components/OutboundTranslationSlice.res.mjs +2 -0
  46. package/src/components/Plugin.res +84 -0
  47. package/src/components/Plugin.res.mjs +2 -0
  48. package/src/components/QueryDb.res +48 -0
  49. package/src/components/QueryDb.res.mjs +33 -0
  50. package/src/components/ReadModel.res +50 -0
  51. package/src/components/ReadModel.res.mjs +2 -0
  52. package/src/components/Scheduler.res +32 -0
  53. package/src/components/Scheduler.res.mjs +2 -0
  54. package/src/components/StateChangeSlice.res +45 -0
  55. package/src/components/StateChangeSlice.res.mjs +2 -0
  56. package/src/components/StateViewSlice.res +38 -0
  57. package/src/components/StateViewSlice.res.mjs +2 -0
  58. package/src/components/Task.res +89 -0
  59. package/src/components/Task.res.mjs +2 -0
  60. package/src/types/ExtensionMapping.res +448 -0
  61. package/src/types/ExtensionMapping.res.mjs +252 -0
  62. package/src/types/ExtensionPointMapping.res +303 -0
  63. package/src/types/ExtensionPointMapping.res.mjs +116 -0
  64. package/src/types/Message.res +3 -0
  65. package/src/types/Message.res.mjs +2 -0
  66. package/src/types/NoEventMappings.res +25 -0
  67. package/src/types/NoEventMappings.res.mjs +17 -0
  68. package/src/types/Platform.res +279 -0
  69. package/src/types/Platform.res.mjs +2 -0
  70. package/src/types/PluginExtensionPointSpec.res +43 -0
  71. package/src/types/PluginExtensionPointSpec.res.mjs +204 -0
  72. package/src/types/ResourceNaming.res +12 -0
  73. package/src/types/ResourceNaming.res.mjs +2 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ Derives the name used to query a task's S3 bucket.
3
+
4
+ - `~taskName` — the task's logical name
5
+ - `~bucketName` — optional override; defaults to the task's configured bucket name
6
+ */
7
+ type queryBucketName = (~taskName: string, ~bucketName: string=?) => string
8
+
9
+ /**
10
+ The `setup` function signature for a `Task.Spec`.
11
+
12
+ Called at deploy time to configure the task's infrastructure.
13
+ Receives the query engine, bucket name helper, and Pulumi options.
14
+ Returns the task's runtime `config`.
15
+ */
16
+ type setup = (
17
+ Reventless.QueryEngine.operations,
18
+ queryBucketName,
19
+ Pulumi.ComponentResource.options,
20
+ ) => Reventless.Task.config
21
+
22
+ /**
23
+ Module type for a task's specification.
24
+
25
+ A `Task` is a serverless handler triggered by S3 events or schedules.
26
+ It can publish commands, manage schedules, and execute side effects.
27
+
28
+ @example
29
+ ```rescript
30
+ module CatalogImportTask: Task.Spec = {
31
+ let name = "CatalogImport"
32
+ let setup = (_queryEngine, _queryBucketName, _opts) => {
33
+ buckets: [{bucketMode: Read}],
34
+ sideEffects: [],
35
+ }
36
+ }
37
+ ```
38
+ */
39
+ module type Spec = {
40
+ /** Logical task name (used as a Lambda function name prefix). */
41
+ let name: string
42
+ /** ESM specifier for this Spec module — populated by `@@reventless.task` PPX. */
43
+ let moduleUrl: string
44
+ let setup: setup
45
+ }
46
+
47
+ /**
48
+ Deploy-time outputs produced when a `Task` is provisioned.
49
+
50
+ - `name` — the task's logical name
51
+ - `bucketNames` — resolved S3 bucket names keyed by bucket spec index
52
+ - `sideEffectSources` — names of aggregate event sources wired to side effects
53
+ */
54
+ type outputs = {
55
+ name: string,
56
+ bucketNames?: dict<Pulumi.Output.t<string>>,
57
+ sideEffectSources?: array<string>,
58
+ }
59
+
60
+ /**
61
+ Runtime operations exposed by a `Task` component.
62
+ Allows the task handler to publish commands to aggregates by name.
63
+ */
64
+ type operations = {
65
+ publishCommands: (string, array<Reventless.Message.commandJson>) => promise<unit>,
66
+ }
67
+
68
+ /**
69
+ Module type produced by `Platform.Task.Make(Spec)`.
70
+ */
71
+ module type T = {
72
+ module Spec: Spec
73
+ type component
74
+ let make: (
75
+ ~queryBucketName: queryBucketName,
76
+ ~scheduler: Scheduler.operations,
77
+ /** URN identifying the principal/role the scheduler invokes targets as.
78
+ Bundled adapters thread this into the runtime config so the deployed
79
+ handler can call the underlying scheduler service. Adapters without
80
+ a real scheduler (e.g. in-memory) ignore it. */
81
+ ~schedulerRoleUrn: Pulumi.Output.t<string>,
82
+ ~publishToAggregates: dict<CommandTopic.publishJsons>,
83
+ ~queryEngine: Reventless.QueryEngine.operations,
84
+ ~resourceNaming: ResourceNaming.operations,
85
+ ~allAggregates: dict<Aggregate.outputs>,
86
+ ~opts: option<Pulumi.ComponentResource.options>,
87
+ ) => component
88
+ let outputs: component => outputs
89
+ }
@@ -0,0 +1,2 @@
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. */
@@ -0,0 +1,448 @@
1
+ /**
2
+ A pre-serialized command routed to a foreign extension point.
3
+ Used when an extension needs to dispatch to an extension point it does not
4
+ own — the command JSON is forwarded opaquely without re-encoding.
5
+ */
6
+ type forwardCommand = {
7
+ extensionPointName: string,
8
+ id: string,
9
+ commandJson: JSON.t,
10
+ }
11
+
12
+ type id = string
13
+
14
+ /**
15
+ Actions returned by an extension's `mapIncomingEvent` function.
16
+
17
+ When an extension point emits an event, the extension's mapping can:
18
+ - publish a command to the aggregate it wraps (`PublishAggregateCommand`)
19
+ - publish a command to the StateChangeSlice it wraps (`PublishStateChangeSliceCommand`)
20
+ — no id argument; the framework derives the FIFO grouping id from the command's
21
+ `@partitionTag` (or `@compositePartitionTag`) field.
22
+ - publish a command back to the extension point (`PublishExtensionPointCommand`)
23
+ - forward a command to another extension point opaquely (`ForwardCommand`)
24
+ - handle an extension-point-defined directive via an async handler (`HandleDirective`)
25
+ */
26
+ type incomingCommandAction<'aggregateCommand, 'extensionPointCommand, 'directive> =
27
+ | PublishAggregateCommand(id, 'aggregateCommand)
28
+ | PublishAggregateCommandAsync(promise<(id, 'aggregateCommand)>)
29
+ | PublishAggregateCommandsAsync(promise<array<(id, 'aggregateCommand)>>)
30
+ | PublishStateChangeSliceCommand('aggregateCommand)
31
+ | PublishStateChangeSliceCommandAsync(promise<'aggregateCommand>)
32
+ | PublishStateChangeSliceCommandsAsync(promise<array<'aggregateCommand>>)
33
+ | PublishExtensionPointCommand(id, 'extensionPointCommand)
34
+ | ForwardCommand(forwardCommand)
35
+ | HandleDirective(Reventless.Handler.handler<'directive>, 'directive)
36
+
37
+ /**
38
+ Actions returned by an extension's `mapOutgoingEvent` function.
39
+
40
+ When the wrapped aggregate emits an event, the extension can:
41
+ - publish a command to the extension point (`PublishExtensionPointCommand`)
42
+ - forward a command to another extension point opaquely (`ForwardCommand`)
43
+ - handle an extension-point-defined directive via an async handler (`HandleDirective`)
44
+ */
45
+ type outgoingCommandAction<'extensionPointCommand, 'directive> =
46
+ | PublishExtensionPointCommand(id, 'extensionPointCommand)
47
+ | ForwardCommand(forwardCommand)
48
+ | HandleDirective(Reventless.Handler.handler<'directive>, 'directive)
49
+
50
+ /**
51
+ Maps an extension point event to actions on the wrapped aggregate or extension point.
52
+
53
+ Called when the extension point emits an event that this extension handles.
54
+ Receives the entity ID, the event, message metadata, the plugin definition,
55
+ and the query engine.
56
+ */
57
+ type mapIncomingEvent<
58
+ 'extensionPointEvent,
59
+ 'aggregateCommand,
60
+ 'extensionPointCommand,
61
+ 'extensionPointDirective,
62
+ > = (
63
+ string,
64
+ 'extensionPointEvent,
65
+ Reventless.Message.meta,
66
+ Reventless.Plugin.pluginDefinition,
67
+ Reventless.QueryEngine.operations,
68
+ ) => array<
69
+ incomingCommandAction<'aggregateCommand, 'extensionPointCommand, 'extensionPointDirective>,
70
+ >
71
+
72
+ /**
73
+ Maps an aggregate event to actions on the extension point.
74
+
75
+ Optionally defined — return `None` if the aggregate's outgoing events do not
76
+ need to be reflected back through the extension point.
77
+ */
78
+ type mapOutgoingEvent<'aggregateEvent, 'extensionPointCommand, 'extensionPointDirective> = (
79
+ string,
80
+ 'aggregateEvent,
81
+ Reventless.Message.meta,
82
+ Reventless.Plugin.pluginDefinition,
83
+ ) => array<outgoingCommandAction<'extensionPointCommand, 'extensionPointDirective>>
84
+
85
+ /**
86
+ The extension point protocol that this `Extension` connects to.
87
+ Mirrors `ExtensionPoint.Spec` so the extension can be type-checked
88
+ against the extension point's command / event / directive types.
89
+ */
90
+ module type Spec = {
91
+ let name: string
92
+ let moduleUrl: string
93
+
94
+ @schema
95
+ type command
96
+ @schema
97
+ type event
98
+ @schema
99
+ type directive
100
+ }
101
+
102
+ /**
103
+ Application-level implementation of an extension's bidirectional mapping.
104
+
105
+ - `ExtensionPoint` — the extension point protocol this extension connects to
106
+ - `Delegate` — the component this extension delegates to (aggregate or DCB slice)
107
+ - `mapIncomingEvent` — routes extension point events to delegate commands
108
+ - `mapOutgoingEvent` — optionally routes delegate events back to the EP
109
+ */
110
+ module type Mapping = {
111
+ module ExtensionPoint: Spec
112
+ module Delegate: Reventless.Aggregate.Spec
113
+
114
+ // npm-style specifier of the user extension file (the module exporting this
115
+ // Mapping). Used by Plugin_Helpers + the bundled Plugin EventCollector entry
116
+ // point to dynamic-import the user mapping for runtime reconstruction of
117
+ // mapIncomingEvent / mapOutgoingEvent. PPX-injected on @@reventless.extension
118
+ // files as the same specifier as the file-level moduleUrl.
119
+ let moduleUrl: string
120
+
121
+ // npm-style specifier of the Delegate's source module. Used by the bundled
122
+ // Plugin EventCollector entry point to dynamic-import the Delegate spec at
123
+ // cold start (Mapping.ExtensionPoint and Mapping.Delegate are erased in the
124
+ // compiled .res.mjs export). PPX-injected on @@reventless.extension files
125
+ // as `Delegate.moduleUrl`.
126
+ let delegateModuleUrl: string
127
+
128
+ let mapIncomingEvent: mapIncomingEvent<
129
+ ExtensionPoint.event,
130
+ Delegate.command,
131
+ ExtensionPoint.command,
132
+ ExtensionPoint.directive,
133
+ >
134
+
135
+ let mapOutgoingEvent: option<
136
+ mapOutgoingEvent<Delegate.event, ExtensionPoint.command, ExtensionPoint.directive>,
137
+ >
138
+ }
139
+
140
+ /**
141
+ A dummy target used when an extension does not wrap a real aggregate or slice.
142
+ Satisfies `Aggregate.Spec` with unit command / event / error types.
143
+ */
144
+ module NoDelegate = {
145
+ let name = "NoDelegate"
146
+
147
+ module Id = {
148
+ @schema
149
+ type t = string
150
+ type input = string
151
+ external make: t => t = "%identity"
152
+ external makeFromString: string => t = "%identity"
153
+ external toString: t => t = "%identity"
154
+ let cmp = String.compare
155
+ }
156
+
157
+ @schema
158
+ type command = unit
159
+
160
+ @schema
161
+ type event = unit
162
+
163
+ @schema
164
+ type error = unit
165
+
166
+ let commandSchema = S.unit
167
+ let moduleUrl: string = %raw(`import.meta.url`)
168
+ let commandAuthorization = (_: command): Reventless.Authorization.permission =>
169
+ AllowAuthenticated
170
+ }
171
+
172
+
173
+ open PluginExtensionPointSpec
174
+
175
+ type extensionPointName = string
176
+
177
+ /* these actions are internal to the Mapping Functor */
178
+ type abstractIncomingCommandAction =
179
+ | AbstractPublishAggregateCommand(string, Reventless.Message.commandJson)
180
+ | AbstractPublishAggregateCommandAsync(promise<(string, Reventless.Message.commandJson)>)
181
+ | AbstractPublishAggregateCommandsAsync(promise<array<(string, Reventless.Message.commandJson)>>)
182
+ | AbstractPublishPluginExtensionPointCommand(Reventless.Message.commandJson)
183
+ | AbstractPublishExtensionPointCommand(extensionPointName, Reventless.Message.commandJson)
184
+ | AbstractHandleDirective(Reventless.Handler.handler<unit>)
185
+
186
+ type abstractOutgoingCommandAction =
187
+ | AbstractPublishPluginExtensionPointCommand(Reventless.Message.commandJson)
188
+ | AbstractPublishExtensionPointCommand(extensionPointName, Reventless.Message.commandJson)
189
+ | AbstractHandleDirective(Reventless.Handler.handler<unit>)
190
+
191
+ module type T = {
192
+ module ExtensionPoint: Spec
193
+
194
+ let delegateName: string
195
+
196
+ let mapIncomingEvent: (
197
+ Reventless.Message.event'<string, ExtensionPoint.event>,
198
+ pluginDefinition,
199
+ Reventless.QueryEngine.operations,
200
+ ) => array<abstractIncomingCommandAction>
201
+
202
+ let mapOutgoingEvent: option<(JSON.t, pluginDefinition) => array<abstractOutgoingCommandAction>>
203
+ }
204
+
205
+ module type Mappings = {
206
+ module Spec: Spec
207
+ module type Mapping = T with module ExtensionPoint := Spec
208
+ let name: string
209
+ let moduleUrl: string
210
+ let mappings: array<module(Mapping)>
211
+ }
212
+
213
+ module Make = (MappingImpl: Mapping): (
214
+ T with module ExtensionPoint := MappingImpl.ExtensionPoint
215
+ ) => {
216
+ module Spec = MappingImpl.ExtensionPoint
217
+ module Delegate = MappingImpl.Delegate
218
+ let delegateName = Delegate.name
219
+ let extensionPointName = Spec.name
220
+
221
+ // Variant TAGs the Delegate cares about — derived from the Delegate's event
222
+ // schema at functor instantiation. Used to pre-filter incoming envelopes:
223
+ // sibling variants on the source log (events the Delegate did not declare)
224
+ // are silently skipped without any decode attempt.
225
+ // extractAllVariantNames keeps payload-less variants (sury-compiled bare
226
+ // `S.literal("Name")` strings) — the JSON envelope's `event` TAG still
227
+ // carries the literal name, so the filter would otherwise drop them.
228
+ let acceptedTags = Reventless.DcbTag.extractAllVariantNames(Delegate.eventSchema)
229
+
230
+ // Lazily-computed partition-tag derivation for `PublishStateChangeSliceCommand*`.
231
+ // Derived from the Delegate's command schema; throws if no `@partitionTag` /
232
+ // `@compositePartitionTag` annotation exists (i.e. the Delegate is an Aggregate,
233
+ // not a StateChangeSlice — in which case the user should use
234
+ // `PublishAggregateCommand` instead).
235
+ let derivedPartitionTagLazy = ref(None)
236
+ let getDerivedPartitionTag = () =>
237
+ switch derivedPartitionTagLazy.contents {
238
+ | Some(d) => d
239
+ | None =>
240
+ let d = Reventless.DcbTag.derivePartitionTag([
241
+ (Delegate.name, Delegate.moduleUrl, Delegate.commandSchema->S.castToUnknown),
242
+ ])
243
+ derivedPartitionTagLazy := Some(d)
244
+ d
245
+ }
246
+ let derivePartitionId = (targetCmd: Delegate.command): string =>
247
+ switch getDerivedPartitionTag() {
248
+ | Simple(pt) =>
249
+ let tags = Reventless.DcbTag.extractTags(Delegate.commandSchema, targetCmd)
250
+ Reventless.DcbTag.getPartitionTagValue([{tags: tags}], pt)->Option.getOr("")
251
+ | Composite(spec) =>
252
+ let tags = Reventless.DcbTag.extractTags(Delegate.commandSchema, targetCmd)
253
+ Reventless.DcbTag.getCompositePartitionKeyValue(tags, spec)
254
+ }
255
+
256
+ // Carry `comp` as a structured Effect log annotation — EffectLogger.install
257
+ // (in reventless-core, wired at Lambda startup) lifts it to the top-level
258
+ // JSON `comp` field. Without the unified logger installed, Effect's default
259
+ // logger still renders the annotation, just less prettily.
260
+ let compLog = (comp, msg) =>
261
+ Effect.logInfo(msg)->Effect.annotateLogs("comp", comp)->Effect.runSync
262
+
263
+ let encodeMeta = (meta: Reventless.Message.meta, service) => {
264
+ ...meta,
265
+ service,
266
+ msgId: Message.uuid(),
267
+ }
268
+
269
+ let doMapIncomingEvent = (
270
+ mapIncomingEventImpl,
271
+ {Reventless.Message.id: id, event, meta},
272
+ pluginDef,
273
+ queryEngine,
274
+ ) => {
275
+ let encodeTargetCommandJson = (targetCmd, targetId) => {
276
+ let cmdJson = targetCmd->Reventless.Message.encode(Delegate.commandSchema)
277
+ {
278
+ Reventless.Message.id: targetId,
279
+ meta: encodeMeta(meta, delegateName),
280
+ commandJson: cmdJson,
281
+ }
282
+ }
283
+
284
+ let encodeExtensionPointCommandJson = (commandJson, ~id, ~extensionPointName, ~action) => {
285
+ compLog(`Extension(${extensionPointName})`, `${action}: ${commandJson->Reventless.Message.variantNameOfJson->Reventless.AnsiStyle.bold}(${id})`)
286
+ {
287
+ Reventless.Message.id,
288
+ meta: encodeMeta(meta, extensionPointName),
289
+ commandJson,
290
+ }
291
+ }
292
+
293
+ let encodeExtensionPointCommand = (command, ~id, ~extensionPointName, ~action) =>
294
+ command
295
+ ->Reventless.Message.encode(Spec.commandSchema)
296
+ ->encodeExtensionPointCommandJson(~id, ~extensionPointName, ~action)
297
+
298
+ mapIncomingEventImpl(id, event, meta, pluginDef, queryEngine)->Array.map(x =>
299
+ switch x {
300
+ | PublishAggregateCommand(targetId, targetCmd) =>
301
+ AbstractPublishAggregateCommand(
302
+ delegateName,
303
+ targetCmd->encodeTargetCommandJson(targetId),
304
+ )
305
+ | PublishAggregateCommandAsync(promise) =>
306
+ let toCommandJson = async promise => {
307
+ let (targetId, targetCmd) = await promise
308
+ (delegateName, targetCmd->encodeTargetCommandJson(targetId))
309
+ }
310
+ AbstractPublishAggregateCommandAsync(promise->toCommandJson)
311
+ | PublishAggregateCommandsAsync(promise) =>
312
+ let toCommandJsons = async promise =>
313
+ (await promise)->Array.map(((targetId, targetCmd)) => (
314
+ delegateName,
315
+ targetCmd->encodeTargetCommandJson(targetId),
316
+ ))
317
+ AbstractPublishAggregateCommandsAsync(promise->toCommandJsons)
318
+ | PublishStateChangeSliceCommand(targetCmd) =>
319
+ AbstractPublishAggregateCommand(
320
+ delegateName,
321
+ targetCmd->encodeTargetCommandJson(derivePartitionId(targetCmd)),
322
+ )
323
+ | PublishStateChangeSliceCommandAsync(promise) =>
324
+ let toCommandJson = async promise => {
325
+ let targetCmd = await promise
326
+ (delegateName, targetCmd->encodeTargetCommandJson(derivePartitionId(targetCmd)))
327
+ }
328
+ AbstractPublishAggregateCommandAsync(promise->toCommandJson)
329
+ | PublishStateChangeSliceCommandsAsync(promise) =>
330
+ let toCommandJsons = async promise =>
331
+ (await promise)->Array.map(targetCmd => (
332
+ delegateName,
333
+ targetCmd->encodeTargetCommandJson(derivePartitionId(targetCmd)),
334
+ ))
335
+ AbstractPublishAggregateCommandsAsync(promise->toCommandJsons)
336
+ | PublishExtensionPointCommand(id, command)
337
+ if Spec.name == PluginExtensionPointSpec.name =>
338
+ AbstractPublishPluginExtensionPointCommand(
339
+ command->encodeExtensionPointCommand(
340
+ ~id,
341
+ ~extensionPointName,
342
+ ~action="Publish PluginExtensionPoint command",
343
+ ),
344
+ )
345
+ | PublishExtensionPointCommand(id, command) =>
346
+ AbstractPublishExtensionPointCommand(
347
+ extensionPointName,
348
+ command->encodeExtensionPointCommand(
349
+ ~id,
350
+ ~extensionPointName,
351
+ ~action="Publish ExtensionPoint command",
352
+ ),
353
+ )
354
+ | ForwardCommand({extensionPointName, id, commandJson}) =>
355
+ AbstractPublishExtensionPointCommand(
356
+ extensionPointName,
357
+ commandJson->encodeExtensionPointCommandJson(
358
+ ~id,
359
+ ~extensionPointName,
360
+ ~action="Forward ExtensionPoint command",
361
+ ),
362
+ )
363
+ | HandleDirective(handler, directive) =>
364
+ compLog(`Extension(${extensionPointName})`, "incoming directive")
365
+ AbstractHandleDirective(() => handler(directive))
366
+ }
367
+ )
368
+ }
369
+
370
+ let mapIncomingEvent = doMapIncomingEvent(MappingImpl.mapIncomingEvent, ...)
371
+
372
+ let variantTagOfEnvelope = json =>
373
+ json
374
+ ->JSON.Decode.object
375
+ ->Option.flatMap(d => d->Dict.get("event"))
376
+ ->Option.map(Reventless.Message.variantNameOfJson)
377
+ ->Option.getOr("unknown")
378
+
379
+ let doMapOutgoingEvent = (mapOutgoingEventImpl, targetEvent'Json, pluginDef) => {
380
+ let tag = variantTagOfEnvelope(targetEvent'Json)
381
+ // Pre-filter by TAG: sibling variants from the source log that the Delegate
382
+ // did not declare are not this mapping's concern — skip silently with no
383
+ // decode attempt.
384
+ if !(acceptedTags->Array.includes(tag)) {
385
+ []
386
+ } else {
387
+ let {id, meta, event} =
388
+ targetEvent'Json->Reventless.Message.decodeEvent'(
389
+ Delegate.Id.schema,
390
+ Delegate.eventSchema,
391
+ )
392
+ let encodeExtensionPointCommandJson = (commandJson, ~id, ~extensionPointName, ~action) => {
393
+ compLog(`Extension(${delegateName})`, `${action}: ${commandJson->Reventless.Message.variantNameOfJson->Reventless.AnsiStyle.bold}(${id})`)
394
+ {
395
+ Reventless.Message.id,
396
+ meta: encodeMeta(meta, extensionPointName),
397
+ commandJson,
398
+ }
399
+ }
400
+
401
+ let encodeExtensionPointCommand = (command, ~id, ~extensionPointName, ~action) =>
402
+ command
403
+ ->Reventless.Message.encode(Spec.commandSchema)
404
+ ->encodeExtensionPointCommandJson(~id, ~extensionPointName, ~action)
405
+
406
+ mapOutgoingEventImpl(id->Delegate.Id.toString, event, meta, pluginDef)->Array.map(x =>
407
+ switch x {
408
+ | PublishExtensionPointCommand(id, command)
409
+ if Spec.name == PluginExtensionPointSpec.name =>
410
+ AbstractPublishPluginExtensionPointCommand(
411
+ command->encodeExtensionPointCommand(
412
+ ~id,
413
+ ~extensionPointName,
414
+ ~action="Publish PluginExtensionPoint command",
415
+ ),
416
+ )
417
+
418
+ | PublishExtensionPointCommand(id, command) =>
419
+ AbstractPublishExtensionPointCommand(
420
+ extensionPointName,
421
+ command->encodeExtensionPointCommand(
422
+ ~id,
423
+ ~extensionPointName,
424
+ ~action="Publish ExtensionPoint command",
425
+ ),
426
+ )
427
+ | ForwardCommand({extensionPointName, id, commandJson}) =>
428
+ AbstractPublishExtensionPointCommand(
429
+ extensionPointName,
430
+ commandJson->encodeExtensionPointCommandJson(
431
+ ~id,
432
+ ~extensionPointName,
433
+ ~action="Forward ExtensionPoint command",
434
+ ),
435
+ )
436
+ | HandleDirective(handler, directive) =>
437
+ compLog(`Extension(${delegateName})`, "outgoing directive")
438
+ AbstractHandleDirective(() => handler(directive))
439
+ }
440
+ )
441
+ }
442
+ }
443
+
444
+ let mapOutgoingEvent =
445
+ MappingImpl.mapOutgoingEvent->Option.map(mapOutgoingEventImpl =>
446
+ doMapOutgoingEvent(mapOutgoingEventImpl, ...)
447
+ )
448
+ }