@reventlessdev/reventless-aws 3.0.0-alpha.416 → 3.0.0-alpha.418
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.418 (2026-11-01)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @reventlessdev/reventless-aws
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 3.0.0-alpha.417 (2026-10-30)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @reventlessdev/reventless-aws
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
# 3.0.0-alpha.416 (2026-10-30)
|
|
7
23
|
|
|
8
24
|
**Note:** Version bump only for package @reventlessdev/reventless-aws
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.418",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -22,19 +22,19 @@
|
|
|
22
22
|
"sury": "11.0.0",
|
|
23
23
|
"uuid": "^13.0.0",
|
|
24
24
|
"yaml": "^2.8.3",
|
|
25
|
-
"@reventlessdev/rescript-effect": "1.0.0-alpha.1",
|
|
26
25
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.37",
|
|
26
|
+
"@reventlessdev/rescript-effect": "1.0.0-alpha.1",
|
|
27
27
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.13",
|
|
28
28
|
"@reventlessdev/rescript-node": "2.0.0-alpha.24",
|
|
29
29
|
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.37",
|
|
30
30
|
"@reventlessdev/rescript-pulumi-pulumi": "3.0.0-alpha.0",
|
|
31
|
-
"@reventlessdev/rescript-web": "1.0.0-alpha.8",
|
|
32
31
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
33
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.
|
|
34
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
35
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.
|
|
36
|
-
"@reventlessdev/reventless-
|
|
37
|
-
"@reventlessdev/
|
|
32
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.325",
|
|
33
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.220",
|
|
34
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.189",
|
|
35
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.42",
|
|
36
|
+
"@reventlessdev/rescript-web": "1.0.0-alpha.8",
|
|
37
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.191"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"rescript": "12.3.1",
|
|
@@ -137,12 +137,22 @@ async function checkPluginStatus(event) {
|
|
|
137
137
|
// Postgres EventLog runtime; absence keeps the DynamoDB path byte-identical.
|
|
138
138
|
// `eventLogTableName` doubles as the Postgres `event_log.log_name` discriminator
|
|
139
139
|
// (all aggregates share one Postgres table, unlike DynamoDB's table-per-aggregate).
|
|
140
|
-
|
|
140
|
+
// Exported for tests, which pass `makeStorage` to stand an in-memory storage in for
|
|
141
|
+
// DynamoDB and drive the functor wiring below exactly as a deployed handler does
|
|
142
|
+
// (tests/AggregateEntryPoint_WiringTest.res).
|
|
143
|
+
export function buildAggregateParts(
|
|
144
|
+
specModule,
|
|
145
|
+
decisionModule,
|
|
146
|
+
eventLogTableName,
|
|
147
|
+
queueUrl,
|
|
148
|
+
pgConnection,
|
|
149
|
+
makeStorage = makeStorageOps,
|
|
150
|
+
) {
|
|
141
151
|
const patchedSpec = patchSpecId(specModule);
|
|
142
152
|
// Typed core: backend-specific EventLog storage ops (Postgres when pgConnection
|
|
143
153
|
// is present, else DynamoDB). Returns the full 6-field EventLog_Adapter.operations
|
|
144
154
|
// — the shell previously dropped latestSnapshot/writeSnapshot.
|
|
145
|
-
const rawStorageOps =
|
|
155
|
+
const rawStorageOps = makeStorage(eventLogTableName, pgConnection);
|
|
146
156
|
const eventLogOps = eventLogOperationsMake(patchedSpec)({
|
|
147
157
|
Spec: patchedSpec,
|
|
148
158
|
EventTopic: { Spec: patchedSpec },
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// The deployed aggregate handler, wired exactly as `AggregateEntryPoint.mjs` wires it,
|
|
2
|
+
// over an in-memory storage in place of DynamoDB.
|
|
3
|
+
//
|
|
4
|
+
// The shell is hand-written JS that passes the compiled EventLog operations MODULE
|
|
5
|
+
// where the aggregate callback expects the `EventLog.operations` RECORD, so a field
|
|
6
|
+
// the record has and the module does not export under the same name type-checks
|
|
7
|
+
// everywhere and fails only in a Lambda. That is how a primed field
|
|
8
|
+
// (`replayStream'`, exported as `replayStream$p`) took down every command of the
|
|
9
|
+
// platform's Plugin aggregate on AWS while every in-process test passed. The
|
|
10
|
+
// command that broke was a derived one — it carries the stable-id header, which
|
|
11
|
+
// sends the handler down the cause-reading replay — so that is the command sent
|
|
12
|
+
// here, its redelivery, and a plain command, which between them reach every
|
|
13
|
+
// operation the aggregate callback reads.
|
|
14
|
+
|
|
15
|
+
open JestGlobals
|
|
16
|
+
|
|
17
|
+
module Spec = AggTestAggregate
|
|
18
|
+
|
|
19
|
+
// ── An in-memory storage, typed by the contract the shell's storage satisfies ──
|
|
20
|
+
|
|
21
|
+
let stored: dict<array<JSON.t>> = Dict.make()
|
|
22
|
+
|
|
23
|
+
let storage: ReventlessCore.EventLog_Adapter.operations = {
|
|
24
|
+
append: async (seq, id, events) => {
|
|
25
|
+
let current = stored->Dict.get(id)->Option.getOr([])
|
|
26
|
+
if current->Array.length != seq {
|
|
27
|
+
Error(ReventlessCore.EventLog.Conflict)
|
|
28
|
+
} else {
|
|
29
|
+
stored->Dict.set(id, current->Array.concat(events))
|
|
30
|
+
Ok()
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
replay: async id => stored->Dict.get(id)->Option.getOr([]),
|
|
34
|
+
replayStream: (id, ~fromSeq=0) =>
|
|
35
|
+
Stream.fromIterable(stored->Dict.get(id)->Option.getOr([])->Array.slice(~start=fromSeq)),
|
|
36
|
+
appendStream: (_, _, _) => Effect.succeed(),
|
|
37
|
+
latestSnapshot: async _ => Ok(None),
|
|
38
|
+
writeSnapshot: async (_, _) => Ok(),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── The shell's own wiring ────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
type parts = {handleJsonCommands: ReventlessCore.CommandTopic_Helpers.jsonCommandsHandler}
|
|
44
|
+
|
|
45
|
+
let buildParts: ReventlessCore.EventLog_Adapter.operations => promise<parts> = %raw(`
|
|
46
|
+
async (storage) => {
|
|
47
|
+
const { buildAggregateParts } = await import(
|
|
48
|
+
"@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs"
|
|
49
|
+
);
|
|
50
|
+
const spec = await import("./integration/AggTestAggregate.res.mjs");
|
|
51
|
+
const decision = await import("./integration/AggTestAggregateDecision.res.mjs");
|
|
52
|
+
return buildAggregateParts(
|
|
53
|
+
spec,
|
|
54
|
+
decision,
|
|
55
|
+
"agg-wiring-test",
|
|
56
|
+
"https://sqs.eu-west-1.amazonaws.com/000000000000/agg-wiring-test",
|
|
57
|
+
undefined,
|
|
58
|
+
() => storage,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
`)
|
|
62
|
+
|
|
63
|
+
/** A command a mapping derived: its id is stable across redeliveries, and the
|
|
64
|
+
header says so. */
|
|
65
|
+
let derived = (~id, ~msgId, name): JSON.t => {
|
|
66
|
+
let meta: Reventless.Message.meta = {
|
|
67
|
+
service: "AggWiringTest",
|
|
68
|
+
time: "2026-11-01T00:00:00.000Z",
|
|
69
|
+
msgId,
|
|
70
|
+
correlationId: msgId,
|
|
71
|
+
headers: Dict.fromArray([(ReventlessInfra.Message.stableIdHeader, "true")]),
|
|
72
|
+
}
|
|
73
|
+
ReventlessCore.Message.encodeCommand'(
|
|
74
|
+
{
|
|
75
|
+
Reventless.Message.id: Reventless.Id.String.makeFromString(id),
|
|
76
|
+
meta,
|
|
77
|
+
command: Spec.Add({name: name}),
|
|
78
|
+
},
|
|
79
|
+
Reventless.Id.String.schema,
|
|
80
|
+
Spec.commandSchema,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let send = async (parts: parts, json) =>
|
|
85
|
+
await parts.handleJsonCommands(
|
|
86
|
+
Stream.fromIterable([{ReventlessInfra.CommandTopic.reference: "r", command: json}]),
|
|
87
|
+
)->Effect.runPromise
|
|
88
|
+
|
|
89
|
+
describe("the deployed aggregate handler's wiring", () => {
|
|
90
|
+
beforeEach(() => stored->Dict.keysToArray->Array.forEach(k => stored->Dict.delete(k)))
|
|
91
|
+
|
|
92
|
+
testPromise("handles a derived command, which replays its stream with causes", async () => {
|
|
93
|
+
let parts = await buildParts(storage)
|
|
94
|
+
let results = await send(parts, derived(~id="a-1", ~msgId="m-1", "first"))
|
|
95
|
+
expect(results->Array.every(Result.isOk))->toBe(true)
|
|
96
|
+
expect(stored->Dict.get("a-1")->Option.map(Array.length))->toEqual(Some(1))
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
testPromise("appends nothing for a redelivery of a derived command", async () => {
|
|
100
|
+
let parts = await buildParts(storage)
|
|
101
|
+
let _ = await send(parts, derived(~id="a-2", ~msgId="m-2", "first"))
|
|
102
|
+
let again = await send(parts, derived(~id="a-2", ~msgId="m-2", "first"))
|
|
103
|
+
expect(again->Array.every(Result.isOk))->toBe(true)
|
|
104
|
+
expect(stored->Dict.get("a-2")->Option.map(Array.length))->toEqual(Some(1))
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// An API command: no stable id, so the handler seeds from the snapshot read and
|
|
108
|
+
// replays events alone — the other operations the shell's module must carry.
|
|
109
|
+
testPromise("handles a command with a fresh id after a derived one", async () => {
|
|
110
|
+
let parts = await buildParts(storage)
|
|
111
|
+
let _ = await send(parts, derived(~id="a-3", ~msgId="m-3", "first"))
|
|
112
|
+
let fresh = derived(~id="a-3", ~msgId="m-4", "second")
|
|
113
|
+
switch fresh {
|
|
114
|
+
| Object(d) =>
|
|
115
|
+
d
|
|
116
|
+
->Dict.get("meta")
|
|
117
|
+
->Option.flatMap(JSON.Decode.object)
|
|
118
|
+
->Option.forEach(meta => meta->Dict.delete("headers"))
|
|
119
|
+
| _ => ()
|
|
120
|
+
}
|
|
121
|
+
let results = await send(parts, fresh)
|
|
122
|
+
expect(results->Array.every(Result.isOk))->toBe(true)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
4
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
|
+
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
6
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Stream from "effect/Stream";
|
|
10
|
+
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
11
|
+
import * as Message$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/Message.res.mjs";
|
|
12
|
+
import * as AggTestAggregate$ReventlessAws from "./integration/AggTestAggregate.res.mjs";
|
|
13
|
+
|
|
14
|
+
let stored = {};
|
|
15
|
+
|
|
16
|
+
async function storage_append(seq, id, events) {
|
|
17
|
+
let current = Stdlib_Option.getOr(stored[id], []);
|
|
18
|
+
if (current.length !== seq) {
|
|
19
|
+
return {
|
|
20
|
+
TAG: "Error",
|
|
21
|
+
_0: "Conflict"
|
|
22
|
+
};
|
|
23
|
+
} else {
|
|
24
|
+
stored[id] = current.concat(events);
|
|
25
|
+
return {
|
|
26
|
+
TAG: "Ok",
|
|
27
|
+
_0: undefined
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function storage_replay(id) {
|
|
33
|
+
return Stdlib_Option.getOr(stored[id], []);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function storage_replayStream(id, fromSeqOpt) {
|
|
37
|
+
let fromSeq = fromSeqOpt !== undefined ? fromSeqOpt : 0;
|
|
38
|
+
return Stream.fromIterable(Stdlib_Option.getOr(stored[id], []).slice(fromSeq));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function storage_appendStream(param, param$1, param$2) {
|
|
42
|
+
return Effect.succeed();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function storage_latestSnapshot(param) {
|
|
46
|
+
return {
|
|
47
|
+
TAG: "Ok",
|
|
48
|
+
_0: undefined
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function storage_writeSnapshot(param, param$1) {
|
|
53
|
+
return {
|
|
54
|
+
TAG: "Ok",
|
|
55
|
+
_0: undefined
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let storage = {
|
|
60
|
+
append: storage_append,
|
|
61
|
+
replay: storage_replay,
|
|
62
|
+
replayStream: storage_replayStream,
|
|
63
|
+
appendStream: storage_appendStream,
|
|
64
|
+
latestSnapshot: storage_latestSnapshot,
|
|
65
|
+
writeSnapshot: storage_writeSnapshot
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let buildParts = (async (storage) => {
|
|
69
|
+
const { buildAggregateParts } = await import(
|
|
70
|
+
"@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs"
|
|
71
|
+
);
|
|
72
|
+
const spec = await import("./integration/AggTestAggregate.res.mjs");
|
|
73
|
+
const decision = await import("./integration/AggTestAggregateDecision.res.mjs");
|
|
74
|
+
return buildAggregateParts(
|
|
75
|
+
spec,
|
|
76
|
+
decision,
|
|
77
|
+
"agg-wiring-test",
|
|
78
|
+
"https://sqs.eu-west-1.amazonaws.com/000000000000/agg-wiring-test",
|
|
79
|
+
undefined,
|
|
80
|
+
() => storage,
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
function derived(id, msgId, name) {
|
|
85
|
+
let meta_headers = Object.fromEntries([[
|
|
86
|
+
Message$ReventlessInfra.stableIdHeader,
|
|
87
|
+
"true"
|
|
88
|
+
]]);
|
|
89
|
+
let meta = {
|
|
90
|
+
service: "AggWiringTest",
|
|
91
|
+
time: "2026-11-01T00:00:00.000Z",
|
|
92
|
+
msgId: msgId,
|
|
93
|
+
correlationId: msgId,
|
|
94
|
+
headers: meta_headers
|
|
95
|
+
};
|
|
96
|
+
return Message$ReventlessCore.encodeCommand$p({
|
|
97
|
+
id: Id$Reventless.$$String.makeFromString(id),
|
|
98
|
+
meta: meta,
|
|
99
|
+
command: {
|
|
100
|
+
TAG: "Add",
|
|
101
|
+
name: name
|
|
102
|
+
}
|
|
103
|
+
}, Id$Reventless.$$String.schema, AggTestAggregate$ReventlessAws.commandSchema);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function send(parts, json) {
|
|
107
|
+
return await Effect.runPromise(parts.handleJsonCommands(Stream.fromIterable([{
|
|
108
|
+
command: json,
|
|
109
|
+
reference: "r"
|
|
110
|
+
}])));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
globalThis.describe("the deployed aggregate handler's wiring", () => {
|
|
114
|
+
globalThis.beforeEach(() => {
|
|
115
|
+
Object.keys(stored).forEach(k => Stdlib_Dict.$$delete(stored, k));
|
|
116
|
+
});
|
|
117
|
+
globalThis.test("handles a derived command, which replays its stream with causes", async () => {
|
|
118
|
+
let parts = await buildParts(storage);
|
|
119
|
+
let results = await send(parts, derived("a-1", "m-1", "first"));
|
|
120
|
+
globalThis.expect(results.every(Stdlib_Result.isOk)).toBe(true);
|
|
121
|
+
globalThis.expect(Stdlib_Option.map(stored["a-1"], prim => prim.length)).toEqual(1);
|
|
122
|
+
});
|
|
123
|
+
globalThis.test("appends nothing for a redelivery of a derived command", async () => {
|
|
124
|
+
let parts = await buildParts(storage);
|
|
125
|
+
await send(parts, derived("a-2", "m-2", "first"));
|
|
126
|
+
let again = await send(parts, derived("a-2", "m-2", "first"));
|
|
127
|
+
globalThis.expect(again.every(Stdlib_Result.isOk)).toBe(true);
|
|
128
|
+
globalThis.expect(Stdlib_Option.map(stored["a-2"], prim => prim.length)).toEqual(1);
|
|
129
|
+
});
|
|
130
|
+
globalThis.test("handles a command with a fresh id after a derived one", async () => {
|
|
131
|
+
let parts = await buildParts(storage);
|
|
132
|
+
await send(parts, derived("a-3", "m-3", "first"));
|
|
133
|
+
let fresh = derived("a-3", "m-4", "second");
|
|
134
|
+
if (typeof fresh === "object" && fresh !== null && !Array.isArray(fresh)) {
|
|
135
|
+
Stdlib_Option.forEach(Stdlib_Option.flatMap(fresh["meta"], Stdlib_JSON.Decode.object), meta => Stdlib_Dict.$$delete(meta, "headers"));
|
|
136
|
+
}
|
|
137
|
+
let results = await send(parts, fresh);
|
|
138
|
+
globalThis.expect(results.every(Stdlib_Result.isOk)).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
let Spec;
|
|
143
|
+
|
|
144
|
+
export {
|
|
145
|
+
Spec,
|
|
146
|
+
stored,
|
|
147
|
+
storage,
|
|
148
|
+
buildParts,
|
|
149
|
+
derived,
|
|
150
|
+
send,
|
|
151
|
+
}
|
|
152
|
+
/* buildParts Not a pure module */
|