@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.
- package/CHANGELOG.md +625 -0
- package/LICENSE +202 -0
- package/README.md +114 -0
- package/package.json +34 -0
- package/rescript.json +25 -0
- package/run-generator.mjs +2 -0
- package/src/AnsiStyle.res +40 -0
- package/src/AnsiStyle.res.mjs +54 -0
- package/src/LogPrefix.res +168 -0
- package/src/LogPrefix.res.mjs +148 -0
- package/src/PackageVersion.res +73 -0
- package/src/PackageVersion.res.mjs +81 -0
- package/src/components/Aggregate.res +64 -0
- package/src/components/Aggregate.res.mjs +2 -0
- package/src/components/AutomationSlice.res +279 -0
- package/src/components/AutomationSlice.res.mjs +30 -0
- package/src/components/Counter.res +24 -0
- package/src/components/Counter.res.mjs +2 -0
- package/src/components/DcbDecode.res +128 -0
- package/src/components/DcbDecode.res.mjs +117 -0
- package/src/components/DcbTag.res +1256 -0
- package/src/components/DcbTag.res.mjs +869 -0
- package/src/components/DcbValidation.res +362 -0
- package/src/components/DcbValidation.res.mjs +360 -0
- package/src/components/DisplayName.res +40 -0
- package/src/components/DisplayName.res.mjs +26 -0
- package/src/components/ExtensionPoint.res +27 -0
- package/src/components/ExtensionPoint.res.mjs +2 -0
- package/src/components/InboundTranslationSlice.res +79 -0
- package/src/components/InboundTranslationSlice.res.mjs +2 -0
- package/src/components/OutboundTranslationSlice.res +108 -0
- package/src/components/OutboundTranslationSlice.res.mjs +2 -0
- package/src/components/Plugin.res +373 -0
- package/src/components/Plugin.res.mjs +249 -0
- package/src/components/ReadModel.res +199 -0
- package/src/components/ReadModel.res.mjs +18 -0
- package/src/components/Reference.res +51 -0
- package/src/components/Reference.res.mjs +37 -0
- package/src/components/StateAnnotations.res +65 -0
- package/src/components/StateAnnotations.res.mjs +15 -0
- package/src/components/StateChangeSlice.res +131 -0
- package/src/components/StateChangeSlice.res.mjs +2 -0
- package/src/components/StateViewSlice.res +99 -0
- package/src/components/StateViewSlice.res.mjs +2 -0
- package/src/components/Task.res +62 -0
- package/src/components/Task.res.mjs +2 -0
- package/src/generator/Codegen.res +703 -0
- package/src/generator/Codegen.res.mjs +485 -0
- package/src/generator/Config.res +85 -0
- package/src/generator/Config.res.mjs +78 -0
- package/src/generator/Discovery.res +204 -0
- package/src/generator/Discovery.res.mjs +213 -0
- package/src/generator/Generator_Node.res +49 -0
- package/src/generator/Generator_Node.res.mjs +18 -0
- package/src/generator/Pairing.res +386 -0
- package/src/generator/Pairing.res.mjs +353 -0
- package/src/generator/PluginGenerator.res +53 -0
- package/src/generator/PluginGenerator.res.mjs +70 -0
- package/src/types/Authorization.res +23 -0
- package/src/types/Authorization.res.mjs +33 -0
- package/src/types/Behavior.res +78 -0
- package/src/types/Behavior.res.mjs +2 -0
- package/src/types/EventMapping.res +100 -0
- package/src/types/EventMapping.res.mjs +15 -0
- package/src/types/Handler.res +30 -0
- package/src/types/Handler.res.mjs +2 -0
- package/src/types/Id.res +75 -0
- package/src/types/Id.res.mjs +37 -0
- package/src/types/Identity.res +46 -0
- package/src/types/Identity.res.mjs +51 -0
- package/src/types/Message.res +197 -0
- package/src/types/Message.res.mjs +108 -0
- package/src/types/Projection.res +220 -0
- package/src/types/Projection.res.mjs +44 -0
- package/src/types/QueryEngine.res +123 -0
- package/src/types/QueryEngine.res.mjs +12 -0
- package/src/types/ReadConsistency.res +38 -0
- package/src/types/ReadConsistency.res.mjs +29 -0
- package/src/types/Schedule.res +65 -0
- package/src/types/Schedule.res.mjs +68 -0
- package/src/types/SideEffect.res +45 -0
- package/src/types/SideEffect.res.mjs +2 -0
- package/src/types/StoredEvent.res +46 -0
- package/src/types/StoredEvent.res.mjs +32 -0
- package/src/types/Visibility.res +24 -0
- package/src/types/Visibility.res.mjs +25 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
The aggregate whose events will be mapped.
|
|
3
|
+
Identifies the event topic to subscribe to via `name` and `Id`.
|
|
4
|
+
*/
|
|
5
|
+
module type Source = {
|
|
6
|
+
let name: string
|
|
7
|
+
module Id: Id.T
|
|
8
|
+
@schema
|
|
9
|
+
type event
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
The aggregate whose commands will be produced.
|
|
14
|
+
Identifies the command topic to publish to via `name` and `Id`.
|
|
15
|
+
*/
|
|
16
|
+
module type Target = {
|
|
17
|
+
let name: string
|
|
18
|
+
module Id: Id.T
|
|
19
|
+
@schema
|
|
20
|
+
type command
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
The outcome of an event-to-command mapping.
|
|
25
|
+
|
|
26
|
+
A `map` function returns an array of `action` values describing what should
|
|
27
|
+
happen when a source event is observed.
|
|
28
|
+
|
|
29
|
+
- `Publish(id, cmd)` — immediately publish a command to the target aggregate
|
|
30
|
+
- `PublishDelayed(id, cmd, seconds)` — publish a command after a delay
|
|
31
|
+
- `PublishAsync(promise)` — resolve a promise and publish all resulting commands
|
|
32
|
+
- `AddToCounterTarget(target)` — register a counter threshold target
|
|
33
|
+
- `Count(id)` — increment a counter by 1
|
|
34
|
+
- `CountMulti(id, n)` — increment a counter by `n`
|
|
35
|
+
|
|
36
|
+
@example
|
|
37
|
+
```rescript
|
|
38
|
+
// Route CategoryArchived to update the Products read model
|
|
39
|
+
let map = (sourceId, event, _queryEngine) => switch event {
|
|
40
|
+
| CategoryArchived({categoryId}) =>
|
|
41
|
+
[EventMapping.Publish(categoryId, ArchiveProductsInCategory({categoryId}))]
|
|
42
|
+
| _ => []
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
*/
|
|
46
|
+
type action<'id, 'command> =
|
|
47
|
+
/** Immediately publish a command to the target aggregate. */
|
|
48
|
+
| Publish('id, 'command)
|
|
49
|
+
/** Publish a command after `int` seconds. */
|
|
50
|
+
| PublishDelayed('id, 'command, int)
|
|
51
|
+
/** Resolve a promise and publish all resulting `(id, command)` pairs. */
|
|
52
|
+
| PublishAsync(promise<array<('id, 'command)>>)
|
|
53
|
+
/** Register a counter threshold target (used with the Counter component). */
|
|
54
|
+
| AddToCounterTarget(Counter.counterTarget)
|
|
55
|
+
/** Increment a named counter by 1. */
|
|
56
|
+
| Count(Counter.counterId)
|
|
57
|
+
/** Increment a named counter by `n`. */
|
|
58
|
+
| CountMulti(Counter.counterId, int)
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
Module type for an event-to-command mapping.
|
|
62
|
+
|
|
63
|
+
Implement this to route events from one aggregate to commands on another.
|
|
64
|
+
The `map` function is pure — use `PublishAsync` for async lookups.
|
|
65
|
+
|
|
66
|
+
@example
|
|
67
|
+
```rescript
|
|
68
|
+
module CategoryToProduct: EventMapping.T = {
|
|
69
|
+
module Source = Category
|
|
70
|
+
module Target = Product
|
|
71
|
+
let map = (sourceId, event, _q) => switch event {
|
|
72
|
+
| CategoryArchived({categoryId}) =>
|
|
73
|
+
[Publish(categoryId, ArchiveProductsInCategory({categoryId}))]
|
|
74
|
+
| _ => []
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
*/
|
|
79
|
+
module type T = {
|
|
80
|
+
module Source: Source
|
|
81
|
+
module Target: Target
|
|
82
|
+
/**
|
|
83
|
+
Map a source event to zero or more target actions.
|
|
84
|
+
|
|
85
|
+
- `Source.Id.t` — the ID of the aggregate that emitted the event
|
|
86
|
+
- `Source.event` — the domain event payload
|
|
87
|
+
- `QueryEngine.operations` — read-only query engine for async lookups
|
|
88
|
+
*/
|
|
89
|
+
let map: (
|
|
90
|
+
Source.Id.t,
|
|
91
|
+
Source.event,
|
|
92
|
+
QueryEngine.operations,
|
|
93
|
+
) => array<action<Target.Id.t, Target.command>>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module Mappings = {
|
|
97
|
+
module Make = (Target: Target) => {
|
|
98
|
+
module type Mapping = T with module Target := Target
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Handler types for application developers.
|
|
2
|
+
// These define the function signatures for handling commands, events, and errors.
|
|
3
|
+
|
|
4
|
+
/** A generic async handler for any message type `'msg`. */
|
|
5
|
+
type handler<'msg> = 'msg => promise<unit>
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
Handler for a single typed command.
|
|
9
|
+
|
|
10
|
+
@example
|
|
11
|
+
```rescript
|
|
12
|
+
let handleCategory: Handler.commandHandler<string, Category.command> = async msg => {
|
|
13
|
+
Console.log2("Handling command for", msg.id)
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
*/
|
|
17
|
+
type commandHandler<'id, 'command> = Message.command'<'id, 'command> => promise<unit>
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
Handler for a batch of commands sharing the same aggregate ID.
|
|
21
|
+
Batches arrive when multiple commands are enqueued for the same ID.
|
|
22
|
+
*/
|
|
23
|
+
type commandsHandler<'id, 'command> = ('id, array<Message.command'<'id, 'command>>) => promise<unit>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
Handler for a batch of events sharing the same aggregate ID.
|
|
27
|
+
Used by read model projections and extension points.
|
|
28
|
+
*/
|
|
29
|
+
type eventsHandler<'id, 'event> = ('id, array<Message.event'<'id, 'event>>) => promise<unit>
|
|
30
|
+
|
package/src/types/Id.res
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Module type for aggregate and read model identifiers.
|
|
3
|
+
|
|
4
|
+
Every Reventless component declares its own `Id` module satisfying this type.
|
|
5
|
+
The abstract `type t` prevents accidentally mixing identifiers from different
|
|
6
|
+
aggregates at compile time.
|
|
7
|
+
|
|
8
|
+
@example
|
|
9
|
+
```rescript
|
|
10
|
+
// Category.res
|
|
11
|
+
module Id = Id.String
|
|
12
|
+
```
|
|
13
|
+
*/
|
|
14
|
+
module type T = {
|
|
15
|
+
/** The opaque identity type. Sealed so IDs from different aggregates cannot be mixed. */
|
|
16
|
+
@schema
|
|
17
|
+
type t
|
|
18
|
+
|
|
19
|
+
/** The input form accepted by `make` (e.g. `string`, `int`, or a record). */
|
|
20
|
+
type input
|
|
21
|
+
|
|
22
|
+
/** Construct a typed ID from its input representation. */
|
|
23
|
+
let make: input => t
|
|
24
|
+
|
|
25
|
+
/** Construct a typed ID from a plain string (used by adapters and routing layers). */
|
|
26
|
+
let makeFromString: string => t
|
|
27
|
+
|
|
28
|
+
/** Convert a typed ID back to its string representation. */
|
|
29
|
+
let toString: t => string
|
|
30
|
+
|
|
31
|
+
/** Compare two IDs. Required for sorted data structures and range queries. */
|
|
32
|
+
let cmp: (t, t) => Ordering.t
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
A transparent string-based `Id.T` implementation.
|
|
37
|
+
|
|
38
|
+
`StringPure.t = string`, so IDs can be written as plain string literals in
|
|
39
|
+
test code without casting. Use `Id.String` (the sealed version) in production
|
|
40
|
+
module specs.
|
|
41
|
+
|
|
42
|
+
@example
|
|
43
|
+
```rescript
|
|
44
|
+
// In test specs where literal strings are convenient:
|
|
45
|
+
module TestCategorySpec = {
|
|
46
|
+
module Id = Id.StringPure
|
|
47
|
+
let name = "Category"
|
|
48
|
+
// Id.make("cat-1") == "cat-1"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
*/
|
|
52
|
+
module StringPure = {
|
|
53
|
+
@schema
|
|
54
|
+
type t = string
|
|
55
|
+
type input = string
|
|
56
|
+
external make: t => t = "%identity"
|
|
57
|
+
external makeFromString: string => t = "%identity"
|
|
58
|
+
external toString: t => t = "%identity"
|
|
59
|
+
let cmp: (t, t) => Ordering.t = String.compare
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
A sealed string-based `Id.T` implementation for use in production aggregate specs.
|
|
64
|
+
|
|
65
|
+
Unlike `StringPure`, the `t` type is abstract, preventing accidental cross-aggregate
|
|
66
|
+
ID mixing. Use `Id.StringPure` in tests when string literals are needed.
|
|
67
|
+
|
|
68
|
+
@example
|
|
69
|
+
```rescript
|
|
70
|
+
// Category.res
|
|
71
|
+
module Id = Id.String
|
|
72
|
+
let name = "Category"
|
|
73
|
+
```
|
|
74
|
+
*/
|
|
75
|
+
module String: T = StringPure
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
5
|
+
|
|
6
|
+
let cmp = Primitive_string.compare;
|
|
7
|
+
|
|
8
|
+
let StringPure = {
|
|
9
|
+
schema: S.string,
|
|
10
|
+
cmp: cmp
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function String_make(prim) {
|
|
14
|
+
return prim;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function String_makeFromString(prim) {
|
|
18
|
+
return prim;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function String_toString(prim) {
|
|
22
|
+
return prim;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let $$String = {
|
|
26
|
+
schema: S.string,
|
|
27
|
+
make: String_make,
|
|
28
|
+
makeFromString: String_makeFromString,
|
|
29
|
+
toString: String_toString,
|
|
30
|
+
cmp: cmp
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
StringPure,
|
|
35
|
+
$$String,
|
|
36
|
+
}
|
|
37
|
+
/* S Not a pure module */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
S.enableJson()
|
|
2
|
+
|
|
3
|
+
/** Identity provider that authenticated the user. */
|
|
4
|
+
@schema
|
|
5
|
+
type provider = Cognito | InMemory | Custom(string)
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
Per-request identity carried through RequestContext.
|
|
9
|
+
|
|
10
|
+
NOT persisted in events — only `userId` is stored as `meta.user`.
|
|
11
|
+
Full identity lives in-memory for the duration of a single request.
|
|
12
|
+
*/
|
|
13
|
+
@schema
|
|
14
|
+
type t = {
|
|
15
|
+
userId: string,
|
|
16
|
+
username: string,
|
|
17
|
+
groups: array<string>,
|
|
18
|
+
claims?: dict<string>,
|
|
19
|
+
provider: provider,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let anonymous: t = {
|
|
23
|
+
userId: "anonymous",
|
|
24
|
+
username: "anonymous",
|
|
25
|
+
groups: [],
|
|
26
|
+
provider: InMemory,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let hasGroup = (identity: t, group: string): bool =>
|
|
30
|
+
identity.groups->Array.includes(group)
|
|
31
|
+
|
|
32
|
+
let getClaim = (identity: t, key: string): option<string> =>
|
|
33
|
+
switch identity.claims {
|
|
34
|
+
| Some(claims) => claims->Dict.get(key)
|
|
35
|
+
| None => None
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
Outcome of a single `Auth_Adapter.Provider.authenticate` call.
|
|
40
|
+
`AuthError` carries a short human-readable reason (logged, not returned
|
|
41
|
+
verbatim to clients) — useful for diagnosing failed bearer-token validation.
|
|
42
|
+
*/
|
|
43
|
+
type authResult =
|
|
44
|
+
| Authenticated(t)
|
|
45
|
+
| Anonymous
|
|
46
|
+
| AuthError(string)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
|
|
5
|
+
S.enableJson();
|
|
6
|
+
|
|
7
|
+
let providerSchema = S.union([
|
|
8
|
+
S.literal("Cognito"),
|
|
9
|
+
S.literal("InMemory"),
|
|
10
|
+
S.schema(s => ({
|
|
11
|
+
TAG: "Custom",
|
|
12
|
+
_0: s.m(S.string)
|
|
13
|
+
}))
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
let schema = S.schema(s => ({
|
|
17
|
+
userId: s.m(S.string),
|
|
18
|
+
username: s.m(S.string),
|
|
19
|
+
groups: s.m(S.array(S.string)),
|
|
20
|
+
claims: s.m(S.option(S.dict(S.string))),
|
|
21
|
+
provider: s.m(providerSchema)
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
let anonymous_groups = [];
|
|
25
|
+
|
|
26
|
+
let anonymous = {
|
|
27
|
+
userId: "anonymous",
|
|
28
|
+
username: "anonymous",
|
|
29
|
+
groups: anonymous_groups,
|
|
30
|
+
provider: "InMemory"
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function hasGroup(identity, group) {
|
|
34
|
+
return identity.groups.includes(group);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getClaim(identity, key) {
|
|
38
|
+
let claims = identity.claims;
|
|
39
|
+
if (claims !== undefined) {
|
|
40
|
+
return claims[key];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
providerSchema,
|
|
46
|
+
schema,
|
|
47
|
+
anonymous,
|
|
48
|
+
hasGroup,
|
|
49
|
+
getClaim,
|
|
50
|
+
}
|
|
51
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
S.enableJson()
|
|
2
|
+
|
|
3
|
+
/** Identifies the service that produced or is addressed by a message. */
|
|
4
|
+
@schema
|
|
5
|
+
type service = string
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
Envelope metadata attached to every event and command.
|
|
9
|
+
|
|
10
|
+
Every message that flows through Reventless carries this metadata so that
|
|
11
|
+
audit trails, correlation, distributed tracing, and routing are available
|
|
12
|
+
without inspecting the domain payload.
|
|
13
|
+
|
|
14
|
+
Shared by both `event'<>` and `command'<>` envelopes — fields are kept
|
|
15
|
+
generic so they apply equally to commands and events.
|
|
16
|
+
|
|
17
|
+
@example
|
|
18
|
+
```rescript
|
|
19
|
+
let meta: Message.meta = {
|
|
20
|
+
service: "CatalogService",
|
|
21
|
+
time: "2024-01-01T00:00:00Z",
|
|
22
|
+
msgId: "msg-abc",
|
|
23
|
+
correlationId: "cmd-xyz",
|
|
24
|
+
user: "alice",
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
*/
|
|
28
|
+
@schema
|
|
29
|
+
type meta = {
|
|
30
|
+
/** Name of the service that created or is addressed by this message. */
|
|
31
|
+
service: service,
|
|
32
|
+
/** ISO-8601 timestamp of when the message was created (producer time). */
|
|
33
|
+
time: string,
|
|
34
|
+
/** IP address of the service instance that created the message. Absent when unknown
|
|
35
|
+
(e.g. serverless contexts without a caller IP). */
|
|
36
|
+
ip?: string,
|
|
37
|
+
/** Identity of the actor who initiated the action. Absent for system-initiated messages. */
|
|
38
|
+
user?: string,
|
|
39
|
+
/** Unique identifier for this message. */
|
|
40
|
+
msgId: string,
|
|
41
|
+
/** Identifier of the root message of the correlation chain. Always present;
|
|
42
|
+
defaults to `msgId` when this is the root. */
|
|
43
|
+
correlationId: string,
|
|
44
|
+
/** Identifier of the *direct* parent message that caused this one. Absent at the chain root. */
|
|
45
|
+
causationId?: string,
|
|
46
|
+
/** W3C Trace Context `traceparent` header value (opaque pass-through).
|
|
47
|
+
Intentionally all-lowercase: matches the HTTP header name, OpenTelemetry SDKs,
|
|
48
|
+
and the CloudEvents distributed-tracing extension. */
|
|
49
|
+
traceparent?: string,
|
|
50
|
+
/** Schema version stamp for this message's payload variant. Absent = unversioned. */
|
|
51
|
+
schemaVersion?: string,
|
|
52
|
+
/** Extensible header bag for cross-cutting context (tenantId, feature flags, etc.).
|
|
53
|
+
Absent when empty — consumers normalise with `->Option.getOr(Dict.make())`. */
|
|
54
|
+
headers?: dict<string>,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
Context passed into command handlers, carrying the aggregate ID and envelope metadata.
|
|
59
|
+
|
|
60
|
+
Passed as the second argument to `Behavior.T.create` and `Behavior.T.execute`
|
|
61
|
+
so business logic can read the caller identity or propagate correlation IDs.
|
|
62
|
+
*/
|
|
63
|
+
@schema
|
|
64
|
+
type context = {
|
|
65
|
+
id: string,
|
|
66
|
+
meta: meta,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
A domain event envelope pairing an aggregate ID with the event payload and metadata.
|
|
71
|
+
|
|
72
|
+
The generic parameters `'id` and `'event` are fixed by each aggregate's `Spec`:
|
|
73
|
+
- `'id` comes from `Spec.Id.t`
|
|
74
|
+
- `'event` comes from `Spec.event`
|
|
75
|
+
|
|
76
|
+
@example
|
|
77
|
+
```rescript
|
|
78
|
+
let ev: Message.event'<string, Category.event> = {
|
|
79
|
+
id: "cat-1",
|
|
80
|
+
meta,
|
|
81
|
+
event: CategoryAdded({categoryId: "cat-1", name: "Electronics"}),
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
*/
|
|
85
|
+
type event'<'id, 'event> = {
|
|
86
|
+
id: 'id,
|
|
87
|
+
meta: meta,
|
|
88
|
+
event: 'event,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
Records who changed a resource's status and when.
|
|
93
|
+
Useful in audit-trail projections (e.g. "approved by bob at 2024-01-15").
|
|
94
|
+
*/
|
|
95
|
+
@schema
|
|
96
|
+
type statusChange = {
|
|
97
|
+
at: string,
|
|
98
|
+
by: string,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
A command envelope pairing an aggregate ID with the command payload and metadata.
|
|
103
|
+
|
|
104
|
+
@example
|
|
105
|
+
```rescript
|
|
106
|
+
let cmd: Message.command'<string, Category.command> = {
|
|
107
|
+
id: "cat-1",
|
|
108
|
+
meta,
|
|
109
|
+
command: AddCategory({categoryId: "cat-1", name: "Electronics"}),
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
*/
|
|
113
|
+
type command'<'id, 'command> = {
|
|
114
|
+
id: 'id,
|
|
115
|
+
meta: meta,
|
|
116
|
+
command: 'command,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
A serialized command envelope used when routing commands across service boundaries.
|
|
121
|
+
|
|
122
|
+
`commandJson` holds the raw JSON payload encoded by sury. The optional `delay`
|
|
123
|
+
field specifies a delivery delay in seconds (used by the scheduler).
|
|
124
|
+
|
|
125
|
+
@example
|
|
126
|
+
```rescript
|
|
127
|
+
let queued: Message.commandJson = {
|
|
128
|
+
id: "cat-1",
|
|
129
|
+
meta,
|
|
130
|
+
commandJson: json,
|
|
131
|
+
delay: Some(5),
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
*/
|
|
135
|
+
@schema
|
|
136
|
+
type commandJson = {
|
|
137
|
+
id: string,
|
|
138
|
+
meta: meta,
|
|
139
|
+
commandJson: JSON.t,
|
|
140
|
+
delay?: int,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
Decode a JSON value into `'a` using a sury schema. Throws on parse failure.
|
|
145
|
+
|
|
146
|
+
@example
|
|
147
|
+
```rescript
|
|
148
|
+
let event = json->Message.decode(Category.eventSchema)
|
|
149
|
+
```
|
|
150
|
+
*/
|
|
151
|
+
let decode = (json, schema: S.t<'a>) => json->S.parseJsonOrThrow(schema)
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
Encode a value to JSON using a sury schema.
|
|
155
|
+
|
|
156
|
+
@example
|
|
157
|
+
```rescript
|
|
158
|
+
let json = event->Message.encode(Category.eventSchema)
|
|
159
|
+
```
|
|
160
|
+
*/
|
|
161
|
+
let encode = (value, schema: S.t<'a>) => value->S.reverseConvertToJsonOrThrow(schema)
|
|
162
|
+
|
|
163
|
+
/** Raised by adapters when an incoming event JSON cannot be matched to a known event variant. */
|
|
164
|
+
exception InvalidEvent(JSON.t)
|
|
165
|
+
|
|
166
|
+
let toEventSchema' = (idSchema, eventSchema) =>
|
|
167
|
+
S.object(s => {
|
|
168
|
+
id: s.field("id", idSchema),
|
|
169
|
+
meta: s.field("meta", metaSchema),
|
|
170
|
+
event: s.field("event", eventSchema),
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
/** Decode a raw event JSON envelope into a typed `event'<'id, 'event>`. */
|
|
174
|
+
let decodeEvent' = (json, idSchema, eventSchema) =>
|
|
175
|
+
json->S.parseJsonOrThrow(toEventSchema'(idSchema, eventSchema))
|
|
176
|
+
|
|
177
|
+
/** Extract the variant constructor name from a sury-encoded variant JSON. */
|
|
178
|
+
let variantNameOfJson = json =>
|
|
179
|
+
switch json {
|
|
180
|
+
| JSON.String(str) => str
|
|
181
|
+
| Object(dict) =>
|
|
182
|
+
switch dict->Dict.get("TAG") {
|
|
183
|
+
| Some(String(tag)) => tag
|
|
184
|
+
| _ => "unknown"
|
|
185
|
+
}
|
|
186
|
+
| _ => "unknown"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Compose a raw event JSON envelope from id, meta, and event JSON. */
|
|
190
|
+
let composeEventJson' = (id, meta, eventJson) =>
|
|
191
|
+
[
|
|
192
|
+
("id", id->JSON.Encode.string),
|
|
193
|
+
("meta", meta->S.reverseConvertToJsonOrThrow(metaSchema)),
|
|
194
|
+
("event", eventJson),
|
|
195
|
+
]
|
|
196
|
+
->Dict.fromArray
|
|
197
|
+
->JSON.Encode.object
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
5
|
+
|
|
6
|
+
S.enableJson();
|
|
7
|
+
|
|
8
|
+
let metaSchema = S.schema(s => ({
|
|
9
|
+
service: s.m(S.string),
|
|
10
|
+
time: s.m(S.string),
|
|
11
|
+
ip: s.m(S.option(S.string)),
|
|
12
|
+
user: s.m(S.option(S.string)),
|
|
13
|
+
msgId: s.m(S.string),
|
|
14
|
+
correlationId: s.m(S.string),
|
|
15
|
+
causationId: s.m(S.option(S.string)),
|
|
16
|
+
traceparent: s.m(S.option(S.string)),
|
|
17
|
+
schemaVersion: s.m(S.option(S.string)),
|
|
18
|
+
headers: s.m(S.option(S.dict(S.string)))
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
let contextSchema = S.schema(s => ({
|
|
22
|
+
id: s.m(S.string),
|
|
23
|
+
meta: s.m(metaSchema)
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
let statusChangeSchema = S.schema(s => ({
|
|
27
|
+
at: s.m(S.string),
|
|
28
|
+
by: s.m(S.string)
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
let commandJsonSchema = S.schema(s => ({
|
|
32
|
+
id: s.m(S.string),
|
|
33
|
+
meta: s.m(metaSchema),
|
|
34
|
+
commandJson: s.m(S.json),
|
|
35
|
+
delay: s.m(S.option(S.int))
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
let decode = S.parseJsonOrThrow;
|
|
39
|
+
|
|
40
|
+
let encode = S.reverseConvertToJsonOrThrow;
|
|
41
|
+
|
|
42
|
+
let InvalidEvent = /* @__PURE__ */Primitive_exceptions.create("Message-Reventless.InvalidEvent");
|
|
43
|
+
|
|
44
|
+
function toEventSchema$p(idSchema, eventSchema) {
|
|
45
|
+
return S.object(s => ({
|
|
46
|
+
id: s.f("id", idSchema),
|
|
47
|
+
meta: s.f("meta", metaSchema),
|
|
48
|
+
event: s.f("event", eventSchema)
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function decodeEvent$p(json, idSchema, eventSchema) {
|
|
53
|
+
return S.parseJsonOrThrow(json, toEventSchema$p(idSchema, eventSchema));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function variantNameOfJson(json) {
|
|
57
|
+
if (json === null || Array.isArray(json)) {
|
|
58
|
+
return "unknown";
|
|
59
|
+
}
|
|
60
|
+
switch (typeof json) {
|
|
61
|
+
case "string" :
|
|
62
|
+
return json;
|
|
63
|
+
case "object" :
|
|
64
|
+
let match = json["TAG"];
|
|
65
|
+
if (typeof match === "string") {
|
|
66
|
+
return match;
|
|
67
|
+
} else {
|
|
68
|
+
return "unknown";
|
|
69
|
+
}
|
|
70
|
+
default:
|
|
71
|
+
return "unknown";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function composeEventJson$p(id, meta, eventJson) {
|
|
76
|
+
return Object.fromEntries([
|
|
77
|
+
[
|
|
78
|
+
"id",
|
|
79
|
+
id
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
"meta",
|
|
83
|
+
S.reverseConvertToJsonOrThrow(meta, metaSchema)
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
"event",
|
|
87
|
+
eventJson
|
|
88
|
+
]
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let serviceSchema = S.string;
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
serviceSchema,
|
|
96
|
+
metaSchema,
|
|
97
|
+
contextSchema,
|
|
98
|
+
statusChangeSchema,
|
|
99
|
+
commandJsonSchema,
|
|
100
|
+
decode,
|
|
101
|
+
encode,
|
|
102
|
+
InvalidEvent,
|
|
103
|
+
toEventSchema$p,
|
|
104
|
+
decodeEvent$p,
|
|
105
|
+
variantNameOfJson,
|
|
106
|
+
composeEventJson$p,
|
|
107
|
+
}
|
|
108
|
+
/* Not a pure module */
|