@reventlessdev/reventless-spec 3.0.0-alpha.61

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 (86) hide show
  1. package/CHANGELOG.md +625 -0
  2. package/LICENSE +202 -0
  3. package/README.md +114 -0
  4. package/package.json +34 -0
  5. package/rescript.json +25 -0
  6. package/run-generator.mjs +2 -0
  7. package/src/AnsiStyle.res +40 -0
  8. package/src/AnsiStyle.res.mjs +54 -0
  9. package/src/LogPrefix.res +168 -0
  10. package/src/LogPrefix.res.mjs +148 -0
  11. package/src/PackageVersion.res +73 -0
  12. package/src/PackageVersion.res.mjs +81 -0
  13. package/src/components/Aggregate.res +64 -0
  14. package/src/components/Aggregate.res.mjs +2 -0
  15. package/src/components/AutomationSlice.res +279 -0
  16. package/src/components/AutomationSlice.res.mjs +30 -0
  17. package/src/components/Counter.res +24 -0
  18. package/src/components/Counter.res.mjs +2 -0
  19. package/src/components/DcbDecode.res +128 -0
  20. package/src/components/DcbDecode.res.mjs +117 -0
  21. package/src/components/DcbTag.res +1256 -0
  22. package/src/components/DcbTag.res.mjs +869 -0
  23. package/src/components/DcbValidation.res +362 -0
  24. package/src/components/DcbValidation.res.mjs +360 -0
  25. package/src/components/DisplayName.res +40 -0
  26. package/src/components/DisplayName.res.mjs +26 -0
  27. package/src/components/ExtensionPoint.res +27 -0
  28. package/src/components/ExtensionPoint.res.mjs +2 -0
  29. package/src/components/InboundTranslationSlice.res +79 -0
  30. package/src/components/InboundTranslationSlice.res.mjs +2 -0
  31. package/src/components/OutboundTranslationSlice.res +108 -0
  32. package/src/components/OutboundTranslationSlice.res.mjs +2 -0
  33. package/src/components/Plugin.res +373 -0
  34. package/src/components/Plugin.res.mjs +249 -0
  35. package/src/components/ReadModel.res +199 -0
  36. package/src/components/ReadModel.res.mjs +18 -0
  37. package/src/components/Reference.res +51 -0
  38. package/src/components/Reference.res.mjs +37 -0
  39. package/src/components/StateAnnotations.res +65 -0
  40. package/src/components/StateAnnotations.res.mjs +15 -0
  41. package/src/components/StateChangeSlice.res +131 -0
  42. package/src/components/StateChangeSlice.res.mjs +2 -0
  43. package/src/components/StateViewSlice.res +99 -0
  44. package/src/components/StateViewSlice.res.mjs +2 -0
  45. package/src/components/Task.res +62 -0
  46. package/src/components/Task.res.mjs +2 -0
  47. package/src/generator/Codegen.res +703 -0
  48. package/src/generator/Codegen.res.mjs +485 -0
  49. package/src/generator/Config.res +85 -0
  50. package/src/generator/Config.res.mjs +78 -0
  51. package/src/generator/Discovery.res +204 -0
  52. package/src/generator/Discovery.res.mjs +213 -0
  53. package/src/generator/Generator_Node.res +49 -0
  54. package/src/generator/Generator_Node.res.mjs +18 -0
  55. package/src/generator/Pairing.res +386 -0
  56. package/src/generator/Pairing.res.mjs +353 -0
  57. package/src/generator/PluginGenerator.res +53 -0
  58. package/src/generator/PluginGenerator.res.mjs +70 -0
  59. package/src/types/Authorization.res +23 -0
  60. package/src/types/Authorization.res.mjs +33 -0
  61. package/src/types/Behavior.res +78 -0
  62. package/src/types/Behavior.res.mjs +2 -0
  63. package/src/types/EventMapping.res +100 -0
  64. package/src/types/EventMapping.res.mjs +15 -0
  65. package/src/types/Handler.res +30 -0
  66. package/src/types/Handler.res.mjs +2 -0
  67. package/src/types/Id.res +75 -0
  68. package/src/types/Id.res.mjs +37 -0
  69. package/src/types/Identity.res +46 -0
  70. package/src/types/Identity.res.mjs +51 -0
  71. package/src/types/Message.res +197 -0
  72. package/src/types/Message.res.mjs +108 -0
  73. package/src/types/Projection.res +220 -0
  74. package/src/types/Projection.res.mjs +44 -0
  75. package/src/types/QueryEngine.res +123 -0
  76. package/src/types/QueryEngine.res.mjs +12 -0
  77. package/src/types/ReadConsistency.res +38 -0
  78. package/src/types/ReadConsistency.res.mjs +29 -0
  79. package/src/types/Schedule.res +65 -0
  80. package/src/types/Schedule.res.mjs +68 -0
  81. package/src/types/SideEffect.res +45 -0
  82. package/src/types/SideEffect.res.mjs +2 -0
  83. package/src/types/StoredEvent.res +46 -0
  84. package/src/types/StoredEvent.res.mjs +32 -0
  85. package/src/types/Visibility.res +24 -0
  86. package/src/types/Visibility.res.mjs +25 -0
@@ -0,0 +1,220 @@
1
+ /**
2
+ The aggregate whose events drive this projection.
3
+ Used to subscribe to the correct event topic.
4
+ */
5
+ module type Source = {
6
+ module Id: Id.T
7
+ let name: string
8
+ @schema
9
+ type event
10
+ }
11
+
12
+ /**
13
+ The read model table that stores the projected state.
14
+ `subIdConfig` enables composite-key tables (id + sub-id).
15
+ */
16
+ module type Target = {
17
+ module Id: Id.T
18
+ let name: string
19
+ @schema
20
+ type state
21
+ let subIdConfig: option<ReadModel.subIdConfig<state>>
22
+ }
23
+
24
+ /**
25
+ The outcome of a projection mapping — what to do with the read model state
26
+ after processing a source event.
27
+
28
+ Returned from a `Projection.Mapping` `project` function.
29
+ Return `Ignore` (or `[]`) when an event should not affect the read model.
30
+
31
+ @example
32
+ ```rescript
33
+ // CategoriesProjections.res
34
+ let project = ({event, id, _}) => switch event {
35
+ | CategoryAdded({categoryId, name}) =>
36
+ Set(id, {CategoriesReadModel.categoryId, name, archived: false})
37
+ | CategoryRenamed({name}) => Update(id, state => {...state, name})
38
+ | CategoryArchived(_) => Update(id, state => {...state, archived: true})
39
+ }
40
+ ```
41
+ */
42
+ type action<'id, 'state> =
43
+ /** Create a new state entry. The entry must not already exist. */
44
+ | Create('id, 'state)
45
+ /** Create many new state entries. Entries must not already exist. */
46
+ | CreateMany(array<('id, 'state)>)
47
+ /** Update an existing entry by applying a transform function. */
48
+ | Update('id, 'state => 'state)
49
+ /** Update many existing entries. */
50
+ | UpdateMany(array<'id>, ('id, 'state) => 'state)
51
+ /** Update an existing entry, or create it with `'state` if it does not exist. */
52
+ | UpdateWithDefault('id, 'state, 'state => 'state)
53
+ /** Update many entries or create them with a default derived from their ID. */
54
+ | UpdateManyWithDefault(array<'id>, 'id => 'state, ('id, 'state) => 'state)
55
+ /** Overwrite the state for an entry, creating it if it does not exist. */
56
+ | Set('id, 'state)
57
+ /** Overwrite the state for many entries. */
58
+ | SetMany(array<'id>, 'id => 'state)
59
+ /** Delete an existing entry. */
60
+ | Delete('id)
61
+ /** Delete many entries. */
62
+ | DeleteMany(array<'id>)
63
+ /** Delete an entry only if the predicate returns true. */
64
+ | DeleteIf('id, 'state => bool)
65
+ /** Delete many entries conditionally. */
66
+ | DeleteManyIf(array<'id>, ('id, 'state) => bool)
67
+ /**
68
+ Create multiple sub-state rows under the same primary ID.
69
+ Used when one event produces several independent sub-entries.
70
+ */
71
+ | CreateMultiState('id, array<'state>)
72
+ /**
73
+ Replace the entire set of sub-state rows for a primary ID.
74
+ The transform receives the current rows and returns the new rows.
75
+ */
76
+ | UpdateMultiState('id, array<'state> => array<'state>)
77
+ /** Update sub-state rows for multiple primary IDs. */
78
+ | UpdateManyMultiStates(array<'id>, ('id, array<'state>) => array<'state>)
79
+ /** No-op. Return this when an event should not affect the read model. */
80
+ | Ignore
81
+
82
+ /**
83
+ A compiled single-source-to-single-target mapping.
84
+
85
+ Created by `Projection.Mapping.Make(Source, Target, MappingImpl)`.
86
+ The `project` function receives a full `Message.event'` envelope and returns
87
+ one `action` value.
88
+ */
89
+ module type Mapping = {
90
+ //module Source: Source
91
+ //module Target: Target // NOTE: to be destructive substituted
92
+ module SourceId: Id.T
93
+ @schema
94
+ type sourceEvent
95
+ @schema
96
+ type targetState
97
+
98
+ let project: Message.event'<string, sourceEvent> => action<string, targetState>
99
+ let sourceEventSchema: S.t<sourceEvent>
100
+ let sourceName: string
101
+ let subIdConfig: option<ReadModel.subIdConfig<targetState>>
102
+ let targetStateSchema: S.t<targetState>
103
+ }
104
+
105
+ /**
106
+ A collection of `Mapping` modules for a single read model target.
107
+
108
+ Pass a `Mappings` module to `Platform.ReadModel.Make` to register all
109
+ source-to-target projections for a read model.
110
+ */
111
+ module type Mappings = {
112
+ module Target: Target // to be removed via destructive replace in functor call
113
+ module type Mapping = Mapping with type targetState = Target.state
114
+ let moduleUrl: string
115
+ let mappings: array<module(Mapping)>
116
+ }
117
+
118
+ module type MappingImpl = {
119
+ type sourceEvent
120
+ type targetState
121
+ let project: Message.event'<string, sourceEvent> => action<string, targetState>
122
+ }
123
+
124
+ /**
125
+ Builds a `Projection.Mapping` from a `Source`, `Target`, and `MappingImpl`.
126
+
127
+ @example
128
+ ```rescript
129
+ // CategoriesProjections.res
130
+ module CategoryMapping = Projection.Mapping.Make(
131
+ Category,
132
+ CategoriesReadModel,
133
+ {
134
+ let project = ({event, id, _}) => switch event {
135
+ | CategoryAdded({categoryId, name}) =>
136
+ Set(id, {CategoriesReadModel.categoryId, name, archived: false})
137
+ | CategoryRenamed({name}) => Update(id, state => {...state, name})
138
+ | CategoryArchived(_) => Update(id, state => {...state, archived: true})
139
+ }
140
+ },
141
+ )
142
+ ```
143
+ */
144
+ module Mapping = {
145
+ module Make = (
146
+ Source: Source,
147
+ Target: Target,
148
+ MappingImpl: MappingImpl with type sourceEvent := Source.event and type targetState := Target.state,
149
+ ): (Mapping with type targetState = Target.state and type sourceEvent = Source.event and module SourceId = Source.Id) => {
150
+ module SourceId = Source.Id
151
+ @schema
152
+ type sourceEvent = Source.event
153
+ @schema
154
+ type targetState = Target.state
155
+ let project = MappingImpl.project
156
+ let sourceName = Source.name
157
+ let subIdConfig = Target.subIdConfig
158
+ }
159
+ }
160
+
161
+ module Mappings = {
162
+ module Make = (Target: Target) => {
163
+ module type Mapping = Mapping with type targetState = Target.state
164
+ }
165
+ }
166
+
167
+ /**
168
+ Builds a `Projection.Source` module for a DCB EventLog.
169
+
170
+ A DCB-source `Mapping.Make` takes a `Source` whose `name` matches the key under
171
+ which `Plugin_Builder` registers the DCB topic in `allEventTopics`
172
+ (`<pluginName>DcbEventLog`). This helper is a thin convenience that asserts the
173
+ `Source` shape over an inline DCB event subset — the `event` type only needs to
174
+ enumerate the variants this consumer projects (others fall through as decode
175
+ errors and the runtime treats them as `Ignore`).
176
+
177
+ @example
178
+ ```rescript
179
+ // catalog/src/Product/ReadModel/CatalogDcbSource.res
180
+ module CatalogDcbSource = Reventless.Projection.DcbSource.Make({
181
+ let name = "CatalogDcbEventLog"
182
+ @schema
183
+ type event =
184
+ | ProductAdded({productId: string, name: string, price: float})
185
+ | ProductRenamed({productId: string, name: string})
186
+ })
187
+ ```
188
+
189
+ Then in your projections file:
190
+ ```rescript
191
+ module ProductsFromDcb = Reventless.Projection.Mapping.Make(
192
+ CatalogDcbSource,
193
+ ProductsReadModel,
194
+ { let project = ({event, id, _}) => switch event {
195
+ | ProductAdded({name}) => Set(id, {ProductsReadModel.name})
196
+ | ProductRenamed({name}) => Update(id, s => {...s, name})
197
+ } },
198
+ )
199
+ ```
200
+
201
+ `module Id` is `Reventless.Id.String` for DCB sources — DCB events are
202
+ content-addressed and have no aggregate-style stream ID.
203
+ */
204
+ module DcbSource = {
205
+ module type Definition = {
206
+ let name: string
207
+ @schema
208
+ type event
209
+ }
210
+ // Returned signature is left structurally inferred (NOT sealed under `Source`)
211
+ // so that variant constructors of `D.event` remain accessible to callers
212
+ // writing `switch msg.event { | ProductAdded(_) => ... }`. The result still
213
+ // satisfies `Source` structurally and is accepted by `Mapping.Make` as such.
214
+ module Make = (D: Definition) => {
215
+ module Id = Id.String
216
+ let name = D.name
217
+ type event = D.event
218
+ let eventSchema = D.eventSchema
219
+ }
220
+ }
@@ -0,0 +1,44 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ function Make(Source) {
5
+ return Target => (MappingImpl => ({
6
+ SourceId: Source.Id,
7
+ project: MappingImpl.project,
8
+ sourceEventSchema: Source.eventSchema,
9
+ sourceName: Source.name,
10
+ subIdConfig: Target.subIdConfig,
11
+ targetStateSchema: Target.stateSchema
12
+ }));
13
+ }
14
+
15
+ let Mapping = {
16
+ Make: Make
17
+ };
18
+
19
+ function Make$1(Target) {
20
+ return {};
21
+ }
22
+
23
+ let Mappings = {
24
+ Make: Make$1
25
+ };
26
+
27
+ function Make$2(D) {
28
+ return {
29
+ Id: undefined,
30
+ name: D.name,
31
+ eventSchema: D.eventSchema
32
+ };
33
+ }
34
+
35
+ let DcbSource = {
36
+ Make: Make$2
37
+ };
38
+
39
+ export {
40
+ Mapping,
41
+ Mappings,
42
+ DcbSource,
43
+ }
44
+ /* No side effect */
@@ -0,0 +1,123 @@
1
+ /**
2
+ A typed query value used as a filter operand.
3
+
4
+ Wraps the three primitive types supported by the query engine so that
5
+ filter comparisons are type-safe without requiring a raw JSON representation.
6
+ */
7
+ type value =
8
+ | String(string)
9
+ | Int(int)
10
+ | Bool(bool)
11
+
12
+ /**
13
+ Filter operations for read model queries.
14
+
15
+ `Filter.config` is a triple `(fieldName, comparator, value)` applied on top of
16
+ the primary key lookup to narrow the result set.
17
+
18
+ @example
19
+ ```rescript
20
+ // Only return categories where archived = false
21
+ let activeFilter: QueryEngine.Filter.config = ("archived", Equal, Bool(false))
22
+ ```
23
+ */
24
+ module Filter = {
25
+ /** Comparison operators available for field-level filters. */
26
+ type comparator =
27
+ | Equal
28
+ | Unequal
29
+ | LessOrEqual
30
+ | Less
31
+ | GreaterOrEqual
32
+ | Greater
33
+ /** Field must exist (have a value). */
34
+ | Exists
35
+ /** Field must not exist. */
36
+ | NotExists
37
+ /** String / set membership: field contains the given substring or element. */
38
+ | Contains
39
+ | NotContains
40
+ | BeginsWith
41
+
42
+ /** A filter specification: `(fieldName, comparator, value)`. */
43
+ type config = (string, comparator, value)
44
+ }
45
+
46
+ /**
47
+ Sub-ID filter operations for composite-key read model queries.
48
+
49
+ `SubId.config` narrows results within a partition already selected by the primary ID.
50
+
51
+ @example
52
+ ```rescript
53
+ // Only return rows where subId begins with "cat-"
54
+ let categoryFilter: QueryEngine.SubId.config = ("categoryId", BeginsWith, String("cat-"))
55
+ ```
56
+ */
57
+ module SubId = {
58
+ /** Comparison operators available for sub-ID range conditions. */
59
+ type comparator =
60
+ | Equal
61
+ | Unequal
62
+ | LessOrEqual
63
+ | Less
64
+ | GreaterOrEqual
65
+ | Greater
66
+ | BeginsWith
67
+
68
+ /** A sub-ID filter specification: `(subIdField, comparator, value)`. */
69
+ type config = (string, comparator, value)
70
+ }
71
+
72
+ /**
73
+ Fetches rows from a read model by primary key, with optional sub-ID range and
74
+ additional attribute filters.
75
+
76
+ @example
77
+ ```rescript
78
+ let rows = await queryEngine.query(
79
+ ~readModelName="Categories",
80
+ ~id=String("cat-1"),
81
+ ~filterConfigs=[("archived", Filter.Equal, Bool(false))],
82
+ )
83
+ ```
84
+ */
85
+ type query = (
86
+ ~readModelName: string,
87
+ ~key: string=?,
88
+ ~id: value,
89
+ ~subIdConfig: SubId.config=?,
90
+ ~filterConfigs: array<Filter.config>=?,
91
+ ~ascending: bool=?,
92
+ ~limit: int=?,
93
+ ) => promise<array<JSON.t>>
94
+
95
+ /**
96
+ Scans an entire read model table with attribute filters (no primary key).
97
+
98
+ Use only when no primary key is available; scans are more expensive than queries.
99
+
100
+ @example
101
+ ```rescript
102
+ let allActive = await queryEngine.scan(
103
+ ~readModelName="Categories",
104
+ ~filterConfigs=[("archived", Filter.Equal, Bool(false))],
105
+ ~limit=100,
106
+ )
107
+ ```
108
+ */
109
+ type scan = (
110
+ ~readModelName: string,
111
+ ~filterConfigs: array<Filter.config>,
112
+ ~limit: int,
113
+ ) => promise<array<JSON.t>>
114
+
115
+ /**
116
+ Runtime query engine injected into task handlers, side effects, and extension point mappings.
117
+
118
+ Provides provider-agnostic read access to all read models in the plugin.
119
+ */
120
+ type operations = {
121
+ scan: scan,
122
+ query: query,
123
+ }
@@ -0,0 +1,12 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ let Filter = {};
5
+
6
+ let SubId = {};
7
+
8
+ export {
9
+ Filter,
10
+ SubId,
11
+ }
12
+ /* No side effect */
@@ -0,0 +1,38 @@
1
+ // Per-slice decision-read consistency mode for DCB StateChangeSlices.
2
+ //
3
+ // Controls how a slice's decision-model read (the conditional read that
4
+ // rebuilds `state` before `decide`) chooses DynamoDB read consistency across
5
+ // the optimistic-concurrency retry loop in `StateChangeSlice_Callback`.
6
+ //
7
+ // Correctness is identical in every mode. A DynamoDB conditional write is
8
+ // always evaluated against the latest committed data, so a stale (eventually
9
+ // consistent) read can only ever cause a *rejected* append — which the retry
10
+ // loop re-reads and resolves — never a wrong write. Strong reads are therefore
11
+ // purely a cost/latency lever against the *replica-lag* class of conflicts;
12
+ // they do nothing for genuine concurrent-writer conflicts (those serialize at
13
+ // the fence regardless). See `docs/analysis/dcb-high-contention-handling.md`.
14
+ //
15
+ // The variant ships with three cases; further cases can be added without
16
+ // breaking existing `@@reventless.consistency(...)` declarations.
17
+
18
+ @schema
19
+ type t =
20
+ | // Default: eventual read on the first attempt (cheaper RCU), then strong on
21
+ // every retry so a replica-lag conflict self-heals deterministically instead
22
+ // of burning further retries.
23
+ EscalateOnRetry
24
+ | // Always strong. For known-hot slices where replica-lag conflicts dominate
25
+ // and the first eventual read is not worth the wasted attempt.
26
+ AlwaysStrong
27
+ | // Always eventual, even on retry. For very cost-sensitive, low-contention
28
+ // slices that never want to pay strong-read RCU.
29
+ AlwaysEventual
30
+
31
+ let default = EscalateOnRetry
32
+
33
+ let toString = (v: t) =>
34
+ switch v {
35
+ | EscalateOnRetry => "EscalateOnRetry"
36
+ | AlwaysStrong => "AlwaysStrong"
37
+ | AlwaysEventual => "AlwaysEventual"
38
+ }
@@ -0,0 +1,29 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+
5
+ let schema = S.union([
6
+ S.literal("EscalateOnRetry"),
7
+ S.literal("AlwaysStrong"),
8
+ S.literal("AlwaysEventual")
9
+ ]);
10
+
11
+ function toString(v) {
12
+ switch (v) {
13
+ case "EscalateOnRetry" :
14
+ return "EscalateOnRetry";
15
+ case "AlwaysStrong" :
16
+ return "AlwaysStrong";
17
+ case "AlwaysEventual" :
18
+ return "AlwaysEventual";
19
+ }
20
+ }
21
+
22
+ let $$default = "EscalateOnRetry";
23
+
24
+ export {
25
+ schema,
26
+ $$default as default,
27
+ toString,
28
+ }
29
+ /* schema Not a pure module */
@@ -0,0 +1,65 @@
1
+ @schema
2
+ type year = int
3
+ @schema
4
+ type month = int
5
+ @schema
6
+ type day = int
7
+ @schema
8
+ type hour = int
9
+ @schema
10
+ type minute = int
11
+
12
+ /**
13
+ The repetition rule for a scheduled event.
14
+
15
+ - `Single(year, month, day, hour, minute)` — fires once at an absolute UTC time
16
+ - `Minutes(n)` / `Hours(n)` / `Days(n)` — fires every n minutes / hours / days
17
+ - `Daily(hour, minute)` — fires every day at the given UTC time
18
+ - `Weekdays(hour, minute)` — fires Monday–Friday at the given UTC time
19
+ - `WeekdaysAndSaturday(hour, minute)` — fires Monday–Saturday at the given UTC time
20
+
21
+ @example
22
+ ```rescript
23
+ // Fire every 15 minutes
24
+ let rate = Schedule.Minutes(15)
25
+
26
+ // Fire once on 2024-12-31 at 23:59 UTC
27
+ let oneTime = Schedule.Single(2024, 12, 31, 23, 59)
28
+ ```
29
+ */
30
+ @schema
31
+ type rate =
32
+ | Single(year, month, day, hour, minute)
33
+ | Minutes(int)
34
+ | Hours(int)
35
+ | Days(int)
36
+ | Daily(hour, minute)
37
+ | Weekdays(hour, minute)
38
+ | WeekdaysAndSaturday(hour, minute)
39
+
40
+ /**
41
+ A named recurring or one-time schedule.
42
+
43
+ `payload` is an opaque string forwarded to the task handler when the schedule fires.
44
+
45
+ @example
46
+ ```rescript
47
+ let dailySync: Schedule.schedule = {
48
+ name: "DailyCatalogSync",
49
+ rate: Daily(2, 0), // 02:00 UTC every day
50
+ payload: `{"action": "sync"}`,
51
+ }
52
+ ```
53
+ */
54
+ @schema
55
+ type schedule = {
56
+ name: string,
57
+ rate: rate,
58
+ payload: string,
59
+ }
60
+
61
+ /** Creates a schedule in the underlying infrastructure (e.g. EventBridge Scheduler). */
62
+ type create = schedule => promise<unit>
63
+
64
+ /** Deletes a schedule by name from the underlying infrastructure. */
65
+ type delete = /* ~name: */ string => promise<unit>
@@ -0,0 +1,68 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+
5
+ let rateSchema = S.union([
6
+ S.schema(s => ({
7
+ TAG: "Single",
8
+ _0: s.m(S.int),
9
+ _1: s.m(S.int),
10
+ _2: s.m(S.int),
11
+ _3: s.m(S.int),
12
+ _4: s.m(S.int)
13
+ })),
14
+ S.schema(s => ({
15
+ TAG: "Minutes",
16
+ _0: s.m(S.int)
17
+ })),
18
+ S.schema(s => ({
19
+ TAG: "Hours",
20
+ _0: s.m(S.int)
21
+ })),
22
+ S.schema(s => ({
23
+ TAG: "Days",
24
+ _0: s.m(S.int)
25
+ })),
26
+ S.schema(s => ({
27
+ TAG: "Daily",
28
+ _0: s.m(S.int),
29
+ _1: s.m(S.int)
30
+ })),
31
+ S.schema(s => ({
32
+ TAG: "Weekdays",
33
+ _0: s.m(S.int),
34
+ _1: s.m(S.int)
35
+ })),
36
+ S.schema(s => ({
37
+ TAG: "WeekdaysAndSaturday",
38
+ _0: s.m(S.int),
39
+ _1: s.m(S.int)
40
+ }))
41
+ ]);
42
+
43
+ let scheduleSchema = S.schema(s => ({
44
+ name: s.m(S.string),
45
+ rate: s.m(rateSchema),
46
+ payload: s.m(S.string)
47
+ }));
48
+
49
+ let yearSchema = S.int;
50
+
51
+ let monthSchema = S.int;
52
+
53
+ let daySchema = S.int;
54
+
55
+ let hourSchema = S.int;
56
+
57
+ let minuteSchema = S.int;
58
+
59
+ export {
60
+ yearSchema,
61
+ monthSchema,
62
+ daySchema,
63
+ hourSchema,
64
+ minuteSchema,
65
+ rateSchema,
66
+ scheduleSchema,
67
+ }
68
+ /* rateSchema Not a pure module */
@@ -0,0 +1,45 @@
1
+ /**
2
+ The event source that a `SideEffect.T` module listens to.
3
+
4
+ Mirrors the aggregate `EventLog.T` shape — the adapter uses `name` and `Id`
5
+ to subscribe to the correct event topic.
6
+ */
7
+ module type Source = {
8
+ let name: string
9
+ module Id: Id.T
10
+ /** The event type whose emissions trigger this side effect. */
11
+ @schema
12
+ type event
13
+ }
14
+
15
+ /**
16
+ Module type for an imperative side effect triggered by aggregate events.
17
+
18
+ Side effects are registered on a `Task` and executed after the event is stored.
19
+ They have read access to the query engine but do NOT emit new events or commands.
20
+
21
+ @example
22
+ ```rescript
23
+ module NotifyOnCategoryAdded: SideEffect.T = {
24
+ module Source = Category
25
+ let execute = async (id, _meta, event, _queryEngine) => switch event {
26
+ | CategoryAdded({name}) =>
27
+ Console.log2("Category added:", name)
28
+ | _ => ()
29
+ }
30
+ }
31
+ ```
32
+ */
33
+ module type T = {
34
+ module Source: Source
35
+ let moduleUrl: string
36
+ /**
37
+ Called once for each event emitted by `Source`.
38
+
39
+ - `id` — the aggregate ID that emitted the event
40
+ - `meta` — the message envelope metadata
41
+ - `event` — the domain event payload
42
+ - `queryEngine` — read-only access to all plugin read models
43
+ */
44
+ let execute: (Source.Id.t, Message.meta, Source.event, QueryEngine.operations) => promise<unit>
45
+ }
@@ -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,46 @@
1
+ // Logical envelope for an event as it lives in storage (EventLog or DcbEventLog).
2
+ // The DynamoDB adapters flatten `meta` to top-level item attributes so meta keys
3
+ // stay GSI-projectable; the in-memory adapter stores the nested record as-is.
4
+ //
5
+ // This is the single source of truth for the on-disk shape — both
6
+ // EventLog_Operations and DcbEventLogStorage_DynamoDb_Runtime go through it
7
+ // rather than building Dict items by hand.
8
+
9
+ /**
10
+ A stored event record. Used by both backends:
11
+ - Aggregate EventLog: `id` is the aggregate id, `position` is the zero-padded sequence string, `tags` is absent.
12
+ - DcbEventLog: `id` is the synthesised partition key (derived from tags), `position` is "<unixMs>-<uuid>", `tags` is present.
13
+
14
+ `event` is the variant constructor name (the on-disk "event" column).
15
+ `data` is the sury-encoded variant payload with the TAG discriminator stripped.
16
+ `recordedAt` is the storage timestamp, set by the storage adapter at append.
17
+ */
18
+ type storedEvent<'id> = {
19
+ id: 'id,
20
+ position: string,
21
+ event: string,
22
+ data: JSON.t,
23
+ meta: Message.meta,
24
+ recordedAt: string,
25
+ tags?: array<DcbTag.tag>,
26
+ }
27
+
28
+ /** Build a sury schema for `storedEvent<'id>` given the `'id` schema. */
29
+ let toStoredEventSchema = (idSchema: S.t<'id>): S.t<storedEvent<'id>> =>
30
+ S.object(s => {
31
+ id: s.field("id", idSchema),
32
+ position: s.field("position", S.string),
33
+ event: s.field("event", S.string),
34
+ data: s.field("data", S.json),
35
+ meta: s.field("meta", Message.metaSchema),
36
+ recordedAt: s.field("recordedAt", S.string),
37
+ tags: ?s.field("tags", S.option(S.array(DcbTag.tagSchema))),
38
+ })
39
+
40
+ /** Decode a `storedEvent<'id>` from JSON. */
41
+ let decode = (json, idSchema) =>
42
+ json->S.parseJsonOrThrow(toStoredEventSchema(idSchema))
43
+
44
+ /** Encode a `storedEvent<'id>` to JSON. */
45
+ let encode = (stored, idSchema) =>
46
+ stored->S.reverseConvertToJsonOrThrow(toStoredEventSchema(idSchema))