@reventlessdev/reventless-infra 3.0.0-alpha.100
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 +853 -0
- package/LICENSE +202 -0
- package/README.md +92 -0
- package/package.json +46 -0
- package/rescript.json +34 -0
- package/src/adapter/Adapter.res +88 -0
- package/src/adapter/Adapter.res.mjs +29 -0
- package/src/components/Aggregate.res +73 -0
- package/src/components/Aggregate.res.mjs +2 -0
- package/src/components/Api.res +120 -0
- package/src/components/Api.res.mjs +31 -0
- package/src/components/Api_Adapter.res +29 -0
- package/src/components/Api_Adapter.res.mjs +2 -0
- package/src/components/AutomationSlice.res +48 -0
- package/src/components/AutomationSlice.res.mjs +2 -0
- package/src/components/CommandGenerator.res +8 -0
- package/src/components/CommandGenerator.res.mjs +2 -0
- package/src/components/CommandTopic.res +70 -0
- package/src/components/CommandTopic.res.mjs +2 -0
- package/src/components/Component.js +56 -0
- package/src/components/Component.mjs +10 -0
- package/src/components/Component.res +90 -0
- package/src/components/Component.res.mjs +57 -0
- package/src/components/Component.resi +21 -0
- package/src/components/Counter.res +81 -0
- package/src/components/Counter.res.mjs +2 -0
- package/src/components/DcbEventLog.res +130 -0
- package/src/components/DcbEventLog.res.mjs +2 -0
- package/src/components/DeployBootstrap.res +83 -0
- package/src/components/DeployBootstrap.res.mjs +69 -0
- package/src/components/EventCollector.res +19 -0
- package/src/components/EventCollector.res.mjs +2 -0
- package/src/components/EventLog.res +24 -0
- package/src/components/EventLog.res.mjs +2 -0
- package/src/components/EventMapper.res +37 -0
- package/src/components/EventMapper.res.mjs +2 -0
- package/src/components/EventTopic.res +51 -0
- package/src/components/EventTopic.res.mjs +2 -0
- package/src/components/Extension.res +64 -0
- package/src/components/Extension.res.mjs +2 -0
- package/src/components/ExtensionPoint.res +62 -0
- package/src/components/ExtensionPoint.res.mjs +2 -0
- package/src/components/Heartbeat.res +7 -0
- package/src/components/Heartbeat.res.mjs +2 -0
- package/src/components/InboundTranslationSlice.res +41 -0
- package/src/components/InboundTranslationSlice.res.mjs +2 -0
- package/src/components/OutboundTranslationSlice.res +44 -0
- package/src/components/OutboundTranslationSlice.res.mjs +2 -0
- package/src/components/Plugin.res +93 -0
- package/src/components/Plugin.res.mjs +2 -0
- package/src/components/QueryDb.res +48 -0
- package/src/components/QueryDb.res.mjs +33 -0
- package/src/components/ReadModel.res +56 -0
- package/src/components/ReadModel.res.mjs +2 -0
- package/src/components/Scheduler.res +32 -0
- package/src/components/Scheduler.res.mjs +2 -0
- package/src/components/StateChangeSlice.res +45 -0
- package/src/components/StateChangeSlice.res.mjs +2 -0
- package/src/components/StateViewSlice.res +38 -0
- package/src/components/StateViewSlice.res.mjs +2 -0
- package/src/components/Task.res +89 -0
- package/src/components/Task.res.mjs +2 -0
- package/src/types/ExtensionMapping.res +448 -0
- package/src/types/ExtensionMapping.res.mjs +252 -0
- package/src/types/ExtensionPointMapping.res +303 -0
- package/src/types/ExtensionPointMapping.res.mjs +116 -0
- package/src/types/Message.res +3 -0
- package/src/types/Message.res.mjs +2 -0
- package/src/types/NoEventMappings.res +25 -0
- package/src/types/NoEventMappings.res.mjs +17 -0
- package/src/types/Platform.res +269 -0
- package/src/types/Platform.res.mjs +2 -0
- package/src/types/PluginExtensionPointSpec.res +55 -0
- package/src/types/PluginExtensionPointSpec.res.mjs +209 -0
- package/src/types/ResourceNaming.res +12 -0
- package/src/types/ResourceNaming.res.mjs +2 -0
- package/tests/DeployBootstrapTest.res +72 -0
- package/tests/DeployBootstrapTest.res.mjs +100 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Deploy-time outputs produced when a `DcbEventLog` is provisioned.
|
|
3
|
+
|
|
4
|
+
- `resources` — the underlying storage infrastructure (e.g. DynamoDB table)
|
|
5
|
+
- `eventTopic` — the SNS topic for downstream subscribers (state view slices)
|
|
6
|
+
*/
|
|
7
|
+
type outputs = {resources: array<Adapter.resource>, eventTopic: EventTopic.outputs}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
A raw event ready to be stored in the DCB log.
|
|
11
|
+
Produced by slice callbacks after encoding with their eventSchema.
|
|
12
|
+
|
|
13
|
+
`meta` is the envelope metadata for this event (causation, correlation, tracing).
|
|
14
|
+
Slice callbacks derive it from the triggering message's context.
|
|
15
|
+
*/
|
|
16
|
+
type rawEvent = {
|
|
17
|
+
eventType: string,
|
|
18
|
+
data: JSON.t,
|
|
19
|
+
tags: array<Reventless.DcbTag.tag>,
|
|
20
|
+
meta: Reventless.Message.meta,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
A raw event read from the DCB log at a known position.
|
|
25
|
+
Consumed by slice callbacks for decoding with their consumedEventSchema.
|
|
26
|
+
|
|
27
|
+
`meta` is the envelope metadata that was written at append time.
|
|
28
|
+
`recordedAt` is the storage timestamp, set by the storage adapter at append.
|
|
29
|
+
*/
|
|
30
|
+
type rawSequencedEvent = {
|
|
31
|
+
position: Reventless.DcbTag.sequencePosition,
|
|
32
|
+
eventType: string,
|
|
33
|
+
data: JSON.t,
|
|
34
|
+
tags: array<Reventless.DcbTag.tag>,
|
|
35
|
+
meta: Reventless.Message.meta,
|
|
36
|
+
recordedAt: string,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
The result of a DCB `read` operation.
|
|
41
|
+
|
|
42
|
+
- `events` — the matching events in sequence order
|
|
43
|
+
- `headPosition` — the sequence position of the last event read (use as the
|
|
44
|
+
`after` cursor in a subsequent `appendCondition` to detect conflicts)
|
|
45
|
+
*/
|
|
46
|
+
type readResult = {
|
|
47
|
+
events: array<rawSequencedEvent>,
|
|
48
|
+
headPosition?: Reventless.DcbTag.sequencePosition,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
Reads events from the DCB log matching the given query.
|
|
53
|
+
|
|
54
|
+
- `~query` — content-based filter (event types + tags)
|
|
55
|
+
- `~after` — optional cursor; only events after this position are returned
|
|
56
|
+
*/
|
|
57
|
+
type read = (
|
|
58
|
+
~query: Reventless.DcbTag.query,
|
|
59
|
+
~after: Reventless.DcbTag.sequencePosition=?,
|
|
60
|
+
) => promise<readResult>
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
Typed DCB append outcome — mirrors `EventLog.appendError`. `Conflict` is the
|
|
64
|
+
optimistic-concurrency sentinel (the append-condition failed: a matching event
|
|
65
|
+
was written since `condition.after`), which the slice callback retries by
|
|
66
|
+
re-reading and re-deciding. Every other failure is a `StorageFailure` carrying
|
|
67
|
+
its message. Replaces the former string sentinel that conflated the two — a
|
|
68
|
+
storage error whose text happened to contain "conflict" would have been retried
|
|
69
|
+
forever, and backends disagreed on the sentinel's casing so conflict metrics were
|
|
70
|
+
misclassified across backends.
|
|
71
|
+
*/
|
|
72
|
+
type appendError =
|
|
73
|
+
| Conflict
|
|
74
|
+
| StorageFailure(string)
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
Appends raw events to the DCB log with optional optimistic-concurrency checking.
|
|
78
|
+
|
|
79
|
+
Returns `Ok(position)` on success or `Error(Conflict)` if the append condition
|
|
80
|
+
was violated (i.e. the log was modified since `condition.after`), or
|
|
81
|
+
`Error(StorageFailure(_))` on any other failure.
|
|
82
|
+
*/
|
|
83
|
+
type append = (
|
|
84
|
+
array<rawEvent>,
|
|
85
|
+
~condition: Reventless.DcbTag.appendCondition=?,
|
|
86
|
+
) => promise<result<Reventless.DcbTag.sequencePosition, appendError>>
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
Streams events from the DCB log matching the given query.
|
|
90
|
+
Use for large result sets that should not be loaded into memory at once.
|
|
91
|
+
*/
|
|
92
|
+
type readStream = (
|
|
93
|
+
~query: Reventless.DcbTag.query,
|
|
94
|
+
~after: Reventless.DcbTag.sequencePosition=?,
|
|
95
|
+
/** Opt into strongly-consistent single-tag reads. Defaults to eventually
|
|
96
|
+
consistent: a stale read can only cause a rejected append (then a retry),
|
|
97
|
+
never a wrong write, because the append fence is always evaluated strongly.
|
|
98
|
+
The slice callback sets this on retries (eventual-first, strong-on-retry). */
|
|
99
|
+
~strongConsistency: bool=?,
|
|
100
|
+
) => Stream.t<rawSequencedEvent, string, unit>
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
Appends a stream of raw events to the DCB log as an `Effect.t`.
|
|
104
|
+
Use for high-throughput batch imports.
|
|
105
|
+
*/
|
|
106
|
+
type appendStream = (
|
|
107
|
+
Stream.t<rawEvent, string, unit>,
|
|
108
|
+
~condition: Reventless.DcbTag.appendCondition=?,
|
|
109
|
+
) => Effect.t<result<Reventless.DcbTag.sequencePosition, appendError>, string, unit>
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
Runtime operations exposed by a `DcbEventLog` component.
|
|
113
|
+
Works with raw events — encode/decode is handled by each slice's callback.
|
|
114
|
+
|
|
115
|
+
Obtained via `Component.operations(dcbEventLog)`. Available inside Lambda handlers.
|
|
116
|
+
*/
|
|
117
|
+
type operations = {
|
|
118
|
+
read: read,
|
|
119
|
+
append: append,
|
|
120
|
+
readStream: readStream,
|
|
121
|
+
appendStream: appendStream,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
type t
|
|
125
|
+
type component = Component.t<t, outputs, operations>
|
|
126
|
+
|
|
127
|
+
module type T = {
|
|
128
|
+
type component = component
|
|
129
|
+
let make: (~name: string, ~indexes: array<string>=?, ~opts: Pulumi.ComponentResource.options=?) => component
|
|
130
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Deploy-time bootstrap seam: a named choke point through which a deploy program
|
|
3
|
+
runs cross-cutting activations at fixed phases, without hand-editing the
|
|
4
|
+
generated `Main.res`.
|
|
5
|
+
|
|
6
|
+
No-op by default. Zero registrations ⇒ `run` does nothing ⇒ existing generated
|
|
7
|
+
and hand-written deploy programs are byte-identical and preview with no resource
|
|
8
|
+
diff. An extension (a backend, an operational package) registers a contribution
|
|
9
|
+
via `register` — at module-load time as an import side effect, or explicitly —
|
|
10
|
+
and the generated program's `run(PreDeploy)` / `run(PostDeploy)` calls fire it in
|
|
11
|
+
registration order.
|
|
12
|
+
|
|
13
|
+
Same shape as `ReventlessCore.Monitoring.use`: a module-level registry consulted
|
|
14
|
+
by an emitted call, so deploy-time extension becomes *registration*, not *file
|
|
15
|
+
editing*. See `docs/plans/deploy-bootstrap-seam.md`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
Ordered phases at which bootstrap contributions run during a deploy program.
|
|
20
|
+
*/
|
|
21
|
+
type phase =
|
|
22
|
+
| /** before `deployPlatform` / `deployPlugin` — seam registration and other
|
|
23
|
+
ordering-sensitive activations that must precede the platform/plugin
|
|
24
|
+
graph build */
|
|
25
|
+
PreDeploy
|
|
26
|
+
| /** after the platform/plugin graph is registered — exports, cross-stack
|
|
27
|
+
output emission */
|
|
28
|
+
PostDeploy
|
|
29
|
+
|
|
30
|
+
type contribution = unit => unit
|
|
31
|
+
|
|
32
|
+
let preContributions: ref<array<contribution>> = ref([])
|
|
33
|
+
let postContributions: ref<array<contribution>> = ref([])
|
|
34
|
+
let preRan = ref(false)
|
|
35
|
+
let postRan = ref(false)
|
|
36
|
+
|
|
37
|
+
let registryFor = phase =>
|
|
38
|
+
switch phase {
|
|
39
|
+
| PreDeploy => preContributions
|
|
40
|
+
| PostDeploy => postContributions
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let ranFor = phase =>
|
|
44
|
+
switch phase {
|
|
45
|
+
| PreDeploy => preRan
|
|
46
|
+
| PostDeploy => postRan
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
Register a contribution. Contributions run in registration order within a phase.
|
|
51
|
+
Safe to call at module-load time (as an import side effect) or explicitly.
|
|
52
|
+
Defaults to `PreDeploy`.
|
|
53
|
+
*/
|
|
54
|
+
let register = (~phase=PreDeploy, contribution: contribution) => {
|
|
55
|
+
let registry = registryFor(phase)
|
|
56
|
+
registry := registry.contents->Array.concat([contribution])
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
Run all contributions registered for a phase, in registration order. Emitted by
|
|
61
|
+
generated deploy programs around the platform/plugin build. Idempotent per phase:
|
|
62
|
+
running an already-run phase is a no-op, and running a phase with no
|
|
63
|
+
registrations does nothing.
|
|
64
|
+
*/
|
|
65
|
+
let run = (phase: phase) => {
|
|
66
|
+
let ran = ranFor(phase)
|
|
67
|
+
if !ran.contents {
|
|
68
|
+
ran := true
|
|
69
|
+
registryFor(phase).contents->Array.forEach(contribution => contribution())
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
Reset all registries and run-flags. Test-support only — deploy programs never
|
|
75
|
+
call this. Lets a test exercise registration order, phase isolation, and
|
|
76
|
+
idempotency from a clean slate.
|
|
77
|
+
*/
|
|
78
|
+
let reset = () => {
|
|
79
|
+
preContributions := []
|
|
80
|
+
postContributions := []
|
|
81
|
+
preRan := false
|
|
82
|
+
postRan := false
|
|
83
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
let preContributions = {
|
|
5
|
+
contents: []
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
let postContributions = {
|
|
9
|
+
contents: []
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let preRan = {
|
|
13
|
+
contents: false
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let postRan = {
|
|
17
|
+
contents: false
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function registryFor(phase) {
|
|
21
|
+
if (phase === "PreDeploy") {
|
|
22
|
+
return preContributions;
|
|
23
|
+
} else {
|
|
24
|
+
return postContributions;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ranFor(phase) {
|
|
29
|
+
if (phase === "PreDeploy") {
|
|
30
|
+
return preRan;
|
|
31
|
+
} else {
|
|
32
|
+
return postRan;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function register(phaseOpt, contribution) {
|
|
37
|
+
let phase = phaseOpt !== undefined ? phaseOpt : "PreDeploy";
|
|
38
|
+
let registry = registryFor(phase);
|
|
39
|
+
registry.contents = registry.contents.concat([contribution]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function run(phase) {
|
|
43
|
+
let ran = ranFor(phase);
|
|
44
|
+
if (!ran.contents) {
|
|
45
|
+
ran.contents = true;
|
|
46
|
+
registryFor(phase).contents.forEach(contribution => contribution());
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function reset() {
|
|
52
|
+
preContributions.contents = [];
|
|
53
|
+
postContributions.contents = [];
|
|
54
|
+
preRan.contents = false;
|
|
55
|
+
postRan.contents = false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
preContributions,
|
|
60
|
+
postContributions,
|
|
61
|
+
preRan,
|
|
62
|
+
postRan,
|
|
63
|
+
registryFor,
|
|
64
|
+
ranFor,
|
|
65
|
+
register,
|
|
66
|
+
run,
|
|
67
|
+
reset,
|
|
68
|
+
}
|
|
69
|
+
/* No side effect */
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enqueues an event for processing by a downstream component (e.g. a read model).
|
|
3
|
+
*
|
|
4
|
+
* - `delay` — delivery delay in seconds (0 for immediate delivery)
|
|
5
|
+
* - `id` — the aggregate ID string the event belongs to
|
|
6
|
+
* - `message` — the serialized event payload (JSON string)
|
|
7
|
+
*
|
|
8
|
+
* This function is exposed by read model `operations` so that the aggregate
|
|
9
|
+
* runtime can push events without knowing the read model's infrastructure details.
|
|
10
|
+
*/
|
|
11
|
+
type enqueueEvent = (/* ~delay: */ int, /* ~id: */ string, /* ~message: */ string) => promise<unit>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Deploy-time outputs produced when an `EventCollector` is provisioned.
|
|
15
|
+
*
|
|
16
|
+
* An `EventCollector` is the inbound queue of a read model — events are collected
|
|
17
|
+
* here and then processed in order by the read model's projection function.
|
|
18
|
+
*/
|
|
19
|
+
type outputs = {name: string, resources: array<Adapter.resource>}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Module type for an aggregate's event log specification.
|
|
3
|
+
|
|
4
|
+
`EventLog.T` is used by the framework to configure the event storage backend
|
|
5
|
+
(e.g. a DynamoDB table) and the associated event topic for downstream subscribers.
|
|
6
|
+
*/
|
|
7
|
+
module type T = {
|
|
8
|
+
module Id: Reventless.Id.T
|
|
9
|
+
|
|
10
|
+
/** Logical name of the aggregate / event stream (used as a table-name prefix). */
|
|
11
|
+
let name: string
|
|
12
|
+
|
|
13
|
+
/** The event type stored in this log. Must carry `@schema` for serialization. */
|
|
14
|
+
@schema
|
|
15
|
+
type event
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
Deploy-time outputs produced when an `EventLog` is provisioned.
|
|
20
|
+
|
|
21
|
+
- `resources` — the underlying infrastructure resources (e.g. DynamoDB table)
|
|
22
|
+
- `eventTopic` — the event topic outputs for downstream subscribers
|
|
23
|
+
*/
|
|
24
|
+
type outputs = {resources: array<Adapter.resource>, eventTopic: EventTopic.outputs}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Deploy-time outputs produced when an `EventMapper` is provisioned.
|
|
3
|
+
|
|
4
|
+
An `EventMapper` subscribes to an aggregate's event topic and routes events
|
|
5
|
+
to one or more target aggregate command topics, optionally via a `Counter`.
|
|
6
|
+
*/
|
|
7
|
+
type outputs = {
|
|
8
|
+
name: string,
|
|
9
|
+
eventCollector: Pulumi.Output.t<EventCollector.outputs>,
|
|
10
|
+
counter?: Counter.outputs,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
A collection of `EventMapping.T` modules that route events from one aggregate
|
|
15
|
+
to commands on one or more target aggregates.
|
|
16
|
+
|
|
17
|
+
Pass a `Mappings` module to `Platform.Aggregate.Make` as the third argument.
|
|
18
|
+
Use `NoEventMappings.Make(TargetSpec)` when no routing is needed.
|
|
19
|
+
|
|
20
|
+
@example
|
|
21
|
+
```rescript
|
|
22
|
+
// CatalogPlugin.res
|
|
23
|
+
module CategoryMappings: Mappings with module Target := CategoriesReadModel = {
|
|
24
|
+
module CategoryMappings = Mappings.Make(CategoriesReadModel)
|
|
25
|
+
module type Mapping = CategoryMappings.Mapping
|
|
26
|
+
let mappings = CategoriesProjections.mappings
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
*/
|
|
30
|
+
module type Mappings = {
|
|
31
|
+
module Target: Reventless.EventMapping.Target
|
|
32
|
+
module type Mapping = Reventless.EventMapping.T with module Target := Target
|
|
33
|
+
let moduleUrl: string
|
|
34
|
+
let mappings: array<module(Mapping)>
|
|
35
|
+
/** Optional counter component for threshold-based command triggers. */
|
|
36
|
+
let counter: option<module(Counter.T)>
|
|
37
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Module type for an aggregate's event topic specification.
|
|
3
|
+
|
|
4
|
+
`EventTopic.T` is used by the framework to identify the event channel
|
|
5
|
+
(e.g. an SNS topic) and validate that published events match the
|
|
6
|
+
aggregate's event schema.
|
|
7
|
+
*/
|
|
8
|
+
module type T = {
|
|
9
|
+
module Id: Reventless.Id.T
|
|
10
|
+
|
|
11
|
+
/** Logical name of the aggregate or component owning this event topic. */
|
|
12
|
+
let name: string
|
|
13
|
+
|
|
14
|
+
/** The event type published to this topic. Must carry `@schema` for serialization. */
|
|
15
|
+
@schema
|
|
16
|
+
type event
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
Deploy-time outputs produced when an `EventTopic` is provisioned.
|
|
21
|
+
Contains the underlying messaging infrastructure resources.
|
|
22
|
+
*/
|
|
23
|
+
type outputs = {resources: array<Adapter.resource>}
|
|
24
|
+
|
|
25
|
+
/** A dictionary of event topic outputs keyed by aggregate name. */
|
|
26
|
+
type allOutputs = dict<outputs>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
Publishes a single event as raw JSON to the event topic.
|
|
30
|
+
|
|
31
|
+
- `string` — the aggregate ID (as a plain string)
|
|
32
|
+
- `Message.meta` — the event envelope metadata
|
|
33
|
+
- `JSON.t` — the serialized event payload
|
|
34
|
+
*/
|
|
35
|
+
type publishJson = (string, Reventless.Message.meta, JSON.t) => promise<unit>
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
An item in a streaming event publication batch.
|
|
39
|
+
Passed to `publishJsonStream` for high-throughput event pipelines.
|
|
40
|
+
*/
|
|
41
|
+
type publishJsonStreamItem = {
|
|
42
|
+
service: string,
|
|
43
|
+
meta: Reventless.Message.meta,
|
|
44
|
+
json: JSON.t,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
Publishes a stream of event items as an `Effect.t`.
|
|
49
|
+
Use this for high-throughput or streaming event pipelines.
|
|
50
|
+
*/
|
|
51
|
+
type publishJsonStream = Stream.t<publishJsonStreamItem, string, unit> => Effect.t<unit, string, unit>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Deploy-time outputs produced when an `Extension` is provisioned.
|
|
3
|
+
|
|
4
|
+
- `name` — the extension's logical name
|
|
5
|
+
- `extensionPointName` — the extension point this extension connects to
|
|
6
|
+
- `aggregateNames` — names of aggregates wired through this extension
|
|
7
|
+
*/
|
|
8
|
+
type outputs = {
|
|
9
|
+
name: string,
|
|
10
|
+
extensionPointName: string,
|
|
11
|
+
aggregateNames: array<string>,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// eventHandler type is defined in reventless (references Plugin.pluginDefinition which
|
|
15
|
+
// would create a circular dependency: Extension → Plugin → Extension).
|
|
16
|
+
// The spec-level T uses abstract `type operations` to avoid the cycle.
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
Module type for a provisioned extension component.
|
|
20
|
+
|
|
21
|
+
An `Extension` connects a host plugin's extension point to one or more aggregates,
|
|
22
|
+
translating commands and events in both directions via `ExtensionMapping.Mapping`.
|
|
23
|
+
|
|
24
|
+
`operations` is left abstract at the spec level to avoid a circular dependency.
|
|
25
|
+
The concrete type is defined in the `reventless` package.
|
|
26
|
+
*/
|
|
27
|
+
module type T = {
|
|
28
|
+
type operations
|
|
29
|
+
type component
|
|
30
|
+
let make: (
|
|
31
|
+
~publishToPluginExtensionPoint: CommandTopic.publishJsons,
|
|
32
|
+
~publishToAggregates: dict<CommandTopic.publishJsons>,
|
|
33
|
+
~readModelNamesForSourceName: dict<array<string>>,
|
|
34
|
+
~publishToReadModels: dict<EventCollector.enqueueEvent>,
|
|
35
|
+
~queryEngine: Reventless.QueryEngine.operations,
|
|
36
|
+
~opts: option<Pulumi.ComponentResource.options>,
|
|
37
|
+
) => component
|
|
38
|
+
let outputs: component => outputs
|
|
39
|
+
let operations: component => Pulumi.Output.t<operations>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
Pre-build blueprint for an extension — compiled mappings that have not yet been
|
|
44
|
+
instantiated as a Pulumi component.
|
|
45
|
+
|
|
46
|
+
`Plugin.make` receives blueprints, groups them by extension point name,
|
|
47
|
+
auto-merges mappings for the same EP, sets the component name to the plugin
|
|
48
|
+
name, and builds the actual `Extension` component.
|
|
49
|
+
*/
|
|
50
|
+
module type Blueprint = {
|
|
51
|
+
module Spec: ExtensionMapping.Spec
|
|
52
|
+
module type Mapping = ExtensionMapping.T with module ExtensionPoint := Spec
|
|
53
|
+
let name: string
|
|
54
|
+
// npm-style specifier of the user extension file (the one declaring the
|
|
55
|
+
// `module Mapping`). Threaded from the user-facing Mapping.moduleUrl by
|
|
56
|
+
// Platform.Extension.Make so deploy-side helpers can emit HANDLER_CONFIG
|
|
57
|
+
// entries that let the runtime dynamic-import the user mapping at cold
|
|
58
|
+
// start.
|
|
59
|
+
let moduleUrl: string
|
|
60
|
+
// npm-style specifier of the Delegate (aggregate / slice) the extension
|
|
61
|
+
// delegates to. Threaded from Mapping.delegateModuleUrl by Platform.Extension.Make.
|
|
62
|
+
let delegateModuleUrl: string
|
|
63
|
+
let mappings: array<module(Mapping)>
|
|
64
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Deploy-time outputs produced when an `ExtensionPoint` is provisioned.
|
|
3
|
+
|
|
4
|
+
- `name` — the extension point's logical name
|
|
5
|
+
- `aggregateNames` — names of aggregates connected via `ExtensionPointMapping`
|
|
6
|
+
- `commandTopic` — the inbound command queue for extensions to publish to
|
|
7
|
+
- `eventTopic` — the outbound event topic extensions subscribe to
|
|
8
|
+
*/
|
|
9
|
+
type outputs = {
|
|
10
|
+
name: string,
|
|
11
|
+
aggregateNames: array<string>,
|
|
12
|
+
commandTopic: Pulumi.Output.t<CommandTopic.outputs>,
|
|
13
|
+
eventTopic: Pulumi.Output.t<EventTopic.outputs>,
|
|
14
|
+
// Module URLs preserved from the Spec and Mappings packed into Make. The
|
|
15
|
+
// EventCollector runtime needs them at HANDLER_CONFIG-build time to wire
|
|
16
|
+
// outgoing event publication for this extension point — see
|
|
17
|
+
// `Plugin_Helpers.registerEventCollectorContext` and the `extensionPointEntry`
|
|
18
|
+
// type that's serialised into the Lambda's HANDLER_CONFIG.
|
|
19
|
+
specModule: string,
|
|
20
|
+
mappingsModule: string,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// eventHandler type is defined in reventless (references Plugin.pluginDefinition which
|
|
24
|
+
// would create a circular dependency: ExtensionPoint → Plugin → ExtensionPoint).
|
|
25
|
+
// The infra-level T uses abstract `type operations` to avoid the cycle.
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
A collection of `ExtensionPointMapping.T` modules connecting aggregates to
|
|
29
|
+
this extension point.
|
|
30
|
+
|
|
31
|
+
Pass a `Mappings` module to `Platform.ExtensionPoint.Make` to register all
|
|
32
|
+
aggregate-to-extension-point connections.
|
|
33
|
+
*/
|
|
34
|
+
module type Mappings = {
|
|
35
|
+
module Spec: ExtensionPointMapping.Spec
|
|
36
|
+
module type Mapping = ExtensionPointMapping.T with module ExtensionPoint := Spec
|
|
37
|
+
let name: string
|
|
38
|
+
let moduleUrl: string
|
|
39
|
+
let mappings: array<module(Mapping)>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
Module type for a provisioned extension point component.
|
|
44
|
+
|
|
45
|
+
`operations` is left abstract to avoid a circular dependency between
|
|
46
|
+
`ExtensionPoint`, `Plugin`, and back. The concrete type is defined in
|
|
47
|
+
the `reventless` package.
|
|
48
|
+
*/
|
|
49
|
+
module type T = {
|
|
50
|
+
type operations
|
|
51
|
+
type component
|
|
52
|
+
let make: (
|
|
53
|
+
~aggregateResources: dict<array<Adapter.resource>>,
|
|
54
|
+
~publishToAggregates: dict<CommandTopic.publishJsons>,
|
|
55
|
+
~scheduler: Scheduler.operations,
|
|
56
|
+
~queryEngine: Reventless.QueryEngine.operations,
|
|
57
|
+
~resourceNaming: ResourceNaming.operations,
|
|
58
|
+
~opts: option<Pulumi.ComponentResource.options>,
|
|
59
|
+
) => component
|
|
60
|
+
let outputs: component => outputs
|
|
61
|
+
let operations: component => Pulumi.Output.t<operations>
|
|
62
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy-time outputs produced when a `Heartbeat` component is provisioned.
|
|
3
|
+
*
|
|
4
|
+
* The heartbeat is an internal health-check component that periodically fires
|
|
5
|
+
* to verify that the plugin's Lambda handlers are reachable and warm.
|
|
6
|
+
*/
|
|
7
|
+
type outputs = {name: string, resources: array<Adapter.resource>}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Deploy-time outputs produced when an `InboundTranslationSlice` is provisioned.
|
|
3
|
+
|
|
4
|
+
- `resources` -- the underlying infrastructure
|
|
5
|
+
- `queryDb` -- the DynamoDB table for the audit log
|
|
6
|
+
*/
|
|
7
|
+
type outputs = {
|
|
8
|
+
resources: array<Adapter.resource>,
|
|
9
|
+
queryDb: QueryDb.outputs,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
Runtime operations exposed by an `InboundTranslationSlice` component.
|
|
14
|
+
|
|
15
|
+
- `receive` -- accept external input, translate it, and publish a command
|
|
16
|
+
*/
|
|
17
|
+
type operations = {
|
|
18
|
+
receive: JSON.t => promise<result<array<string>, string>>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
Module type produced by `Platform.InboundTranslationSlice.Make(Spec)`.
|
|
23
|
+
|
|
24
|
+
@example
|
|
25
|
+
```rescript
|
|
26
|
+
module PaymentWebhookSlice = Platform.InboundTranslationSlice.Make(PaymentWebhook)
|
|
27
|
+
let slice = PaymentWebhookSlice.make(~publishJsons=publishJsonsOutput)
|
|
28
|
+
```
|
|
29
|
+
*/
|
|
30
|
+
type t
|
|
31
|
+
|
|
32
|
+
module type T = {
|
|
33
|
+
module Spec: Reventless.InboundTranslationSlice.Spec
|
|
34
|
+
module Translation: Reventless.InboundTranslationSlice.Translation with module Spec := Spec
|
|
35
|
+
type component = Component.t<t, outputs, operations>
|
|
36
|
+
let queryDbName: string
|
|
37
|
+
let make: (
|
|
38
|
+
~publishJsons: Pulumi.Output.t<CommandTopic.publishJsons>,
|
|
39
|
+
~opts: Pulumi.ComponentResource.options=?,
|
|
40
|
+
) => component
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Deploy-time outputs produced when an `OutboundTranslationSlice` is provisioned.
|
|
3
|
+
|
|
4
|
+
- `resources` -- the underlying infrastructure (e.g. SQS subscription)
|
|
5
|
+
- `queryDb` -- the DynamoDB table for the TODO list
|
|
6
|
+
*/
|
|
7
|
+
type outputs = {
|
|
8
|
+
resources: array<Adapter.resource>,
|
|
9
|
+
queryDb: QueryDb.outputs,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
Runtime operations exposed by an `OutboundTranslationSlice` component.
|
|
14
|
+
|
|
15
|
+
- `enqueueEvent` -- push events into the slice's projection queue
|
|
16
|
+
- `translatePending` -- manually trigger Phase 2 (useful in tests and for heartbeat)
|
|
17
|
+
*/
|
|
18
|
+
type operations = {
|
|
19
|
+
enqueueEvent: EventCollector.enqueueEvent,
|
|
20
|
+
translatePending: unit => promise<unit>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
Module type produced by `Platform.OutboundTranslationSlice.Make(Spec)`.
|
|
25
|
+
|
|
26
|
+
@example
|
|
27
|
+
```rescript
|
|
28
|
+
module SendTrackingEmailSlice = Platform.OutboundTranslationSlice.Make(SendTrackingEmail)
|
|
29
|
+
let slice = SendTrackingEmailSlice.make(~dcbEventLog=log, ~publishJsons=publishJsonsOutput)
|
|
30
|
+
```
|
|
31
|
+
*/
|
|
32
|
+
type t
|
|
33
|
+
|
|
34
|
+
module type T = {
|
|
35
|
+
module Spec: Reventless.OutboundTranslationSlice.Spec
|
|
36
|
+
module Translation: Reventless.OutboundTranslationSlice.Translation with module Spec := Spec
|
|
37
|
+
type component = Component.t<t, outputs, operations>
|
|
38
|
+
let queryDbName: string
|
|
39
|
+
let make: (
|
|
40
|
+
~dcbEventLog: DcbEventLog.component,
|
|
41
|
+
~publishJsons: Pulumi.Output.t<CommandTopic.publishJsons>,
|
|
42
|
+
~opts: Pulumi.ComponentResource.options=?,
|
|
43
|
+
) => component
|
|
44
|
+
}
|