@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.416",
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.324",
34
- "@reventlessdev/reventless-infra": "3.0.0-alpha.219",
35
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.188",
36
- "@reventlessdev/reventless-spec": "3.0.0-alpha.190",
37
- "@reventlessdev/reventless-interop": "3.0.0-alpha.42"
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
- function buildAggregateParts(specModule, decisionModule, eventLogTableName, queueUrl, pgConnection) {
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 = makeStorageOps(eventLogTableName, pgConnection);
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 */