@reventlessdev/reventless-core 3.0.0-alpha.135 → 3.0.0-alpha.137
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 +19 -0
- package/package.json +8 -8
- package/src/Message.res +43 -10
- package/src/Message.res.mjs +21 -4
- package/src/Projection.res +83 -55
- package/src/Projection.res.mjs +69 -57
- package/src/components/Aggregate/Aggregate_Callback.res +67 -9
- package/src/components/Aggregate/Aggregate_Callback.res.mjs +42 -13
- package/src/components/DcbEventLog/DcbEventLog_Operations.res +3 -0
- package/src/components/DcbEventLog/DcbEventLog_Operations.res.mjs +3 -1
- package/src/components/EventLog/EventLog.res +33 -2
- package/src/components/EventLog/EventLog_Adapter.res +3 -1
- package/src/components/EventLog/EventLog_Builder.res +4 -0
- package/src/components/EventLog/EventLog_Builder.res.mjs +3 -1
- package/src/components/EventLog/EventLog_Operations.res +48 -36
- package/src/components/EventLog/EventLog_Operations.res.mjs +36 -15
- package/src/components/EventLog/EventPublish_Callback.res +7 -0
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +26 -6
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +21 -6
- package/src/plugin/component/SchemaWalker.res +13 -1
- package/src/plugin/component/SchemaWalker.res.mjs +5 -4
- package/src/plugin/lifecycle/PluginBehavior.res +6 -0
- package/src/plugin/lifecycle/PluginBehavior.res.mjs +3 -0
- package/src/util/CommandPublisher.res +20 -10
- package/src/util/CommandPublisher.res.mjs +6 -10
- package/src/util/Logger.res +10 -0
- package/src/util/Logger.res.mjs +12 -2
- package/src/util/Util_Array.res +1 -1
- package/src/util/Util_Array.res.mjs +1 -1
- package/tests/aggregate/AggregateCacheTest.res +271 -0
- package/tests/aggregate/AggregateCacheTest.res.mjs +433 -0
- package/tests/aggregate/AggregateCallbackTest.res +4 -1
- package/tests/aggregate/AggregateCallbackTest.res.mjs +4 -1
- package/tests/aggregate/AggregateCausationTest.res +4 -1
- package/tests/aggregate/AggregateCausationTest.res.mjs +4 -1
- package/tests/aggregate/AggregateConflictTest.res +17 -6
- package/tests/aggregate/AggregateConflictTest.res.mjs +27 -4
- package/tests/aggregate/AggregateFixtures.res +13 -5
- package/tests/aggregate/AggregateFixtures.res.mjs +26 -3
- package/tests/aggregate/AggregateRejectionTest.res +4 -1
- package/tests/aggregate/AggregateRejectionTest.res.mjs +4 -1
- package/tests/eventlog/EventLogFixtures.res +15 -4
- package/tests/eventlog/EventLogFixtures.res.mjs +35 -5
- package/tests/eventlog/EventLogRetryTest.res +12 -11
- package/tests/eventlog/EventLogRetryTest.res.mjs +31 -7
- package/tests/logger/LoggerLazyTest.res +36 -0
- package/tests/logger/LoggerLazyTest.res.mjs +40 -0
- package/tests/projection/ProjectionOptimizeTest.res +38 -0
- package/tests/projection/ProjectionOptimizeTest.res.mjs +75 -0
- package/tests/util/CommandPublisherTest.res +56 -0
- package/tests/util/CommandPublisherTest.res.mjs +78 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Regression test for Projection.optimizeActions (plan A7 / D2).
|
|
2
|
+
//
|
|
3
|
+
// optimizeActions merges consecutive per-id actions. A non-merging pair (Delete
|
|
4
|
+
// then Update) grows the accumulator to length 2; the next merge (Update+Set)
|
|
5
|
+
// must fold only the last action and keep everything before it exactly once.
|
|
6
|
+
// Before the `~end=count-1` fix, `previousActions` still contained the
|
|
7
|
+
// just-merged action, so the earlier action was duplicated — applied twice.
|
|
8
|
+
|
|
9
|
+
open JestGlobals
|
|
10
|
+
open Reventless.Projection
|
|
11
|
+
|
|
12
|
+
describe("Projection.optimizeActions", () => {
|
|
13
|
+
testSync("does not duplicate the pre-merge action (off-by-one regression)", () => {
|
|
14
|
+
let optimized = Projection.optimizeActions([Delete("a"), Update("a", x => x + 1), Set("a", 99)])
|
|
15
|
+
// Was 3 (with a duplicated Update("a")) before the fix.
|
|
16
|
+
expect(optimized->Array.length)->toBe(2)
|
|
17
|
+
let sets =
|
|
18
|
+
optimized->Array.filterMap(a =>
|
|
19
|
+
switch a {
|
|
20
|
+
| Set(id, s) => Some((id, s))
|
|
21
|
+
| _ => None
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
expect(sets)->toEqual([("a", 99)])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
testSync("merges a same-id Create+Update chain into a single Create", () => {
|
|
28
|
+
let optimized = Projection.optimizeActions([Create("a", 0), Update("a", x => x + 1), Update("a", x => x + 10)])
|
|
29
|
+
let creates =
|
|
30
|
+
optimized->Array.filterMap(a =>
|
|
31
|
+
switch a {
|
|
32
|
+
| Create(id, s) => Some((id, s))
|
|
33
|
+
| _ => None
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
expect(creates)->toEqual([("a", 11)])
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
|
+
import * as Projection$ReventlessCore from "../../src/Projection.res.mjs";
|
|
5
|
+
|
|
6
|
+
globalThis.describe("Projection.optimizeActions", () => {
|
|
7
|
+
globalThis.test("does not duplicate the pre-merge action (off-by-one regression)", () => {
|
|
8
|
+
let optimized = Projection$ReventlessCore.optimizeActions([
|
|
9
|
+
{
|
|
10
|
+
TAG: "Delete",
|
|
11
|
+
_0: "a"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
TAG: "Update",
|
|
15
|
+
_0: "a",
|
|
16
|
+
_1: x => x + 1 | 0
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
TAG: "Set",
|
|
20
|
+
_0: "a",
|
|
21
|
+
_1: 99
|
|
22
|
+
}
|
|
23
|
+
]);
|
|
24
|
+
globalThis.expect(optimized.length).toBe(2);
|
|
25
|
+
let sets = Stdlib_Array.filterMap(optimized, a => {
|
|
26
|
+
if (typeof a !== "object" || a.TAG !== "Set") {
|
|
27
|
+
return;
|
|
28
|
+
} else {
|
|
29
|
+
return [
|
|
30
|
+
a._0,
|
|
31
|
+
a._1
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
globalThis.expect(sets).toEqual([[
|
|
36
|
+
"a",
|
|
37
|
+
99
|
|
38
|
+
]]);
|
|
39
|
+
});
|
|
40
|
+
globalThis.test("merges a same-id Create+Update chain into a single Create", () => {
|
|
41
|
+
let optimized = Projection$ReventlessCore.optimizeActions([
|
|
42
|
+
{
|
|
43
|
+
TAG: "Create",
|
|
44
|
+
_0: "a",
|
|
45
|
+
_1: 0
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
TAG: "Update",
|
|
49
|
+
_0: "a",
|
|
50
|
+
_1: x => x + 1 | 0
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
TAG: "Update",
|
|
54
|
+
_0: "a",
|
|
55
|
+
_1: x => x + 10 | 0
|
|
56
|
+
}
|
|
57
|
+
]);
|
|
58
|
+
let creates = Stdlib_Array.filterMap(optimized, a => {
|
|
59
|
+
if (typeof a !== "object" || a.TAG !== "Create") {
|
|
60
|
+
return;
|
|
61
|
+
} else {
|
|
62
|
+
return [
|
|
63
|
+
a._0,
|
|
64
|
+
a._1
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
globalThis.expect(creates).toEqual([[
|
|
69
|
+
"a",
|
|
70
|
+
11
|
|
71
|
+
]]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Regression tests for CommandPublisher (plan A7).
|
|
2
|
+
//
|
|
3
|
+
// Two bugs made the buffered publisher silently lose commands:
|
|
4
|
+
// - send() used the non-mutating `toSpliced`, so the buffer never drained and
|
|
5
|
+
// the wrong slice was published.
|
|
6
|
+
// - flush()'s in-flight arm looped on a dead condition and never sent.
|
|
7
|
+
// - clear() removed only the first buffered command instead of truncating.
|
|
8
|
+
//
|
|
9
|
+
// These tests drive the functor with a capturing publishCommands and assert the
|
|
10
|
+
// buffer flushes in order and clear() empties it.
|
|
11
|
+
|
|
12
|
+
open JestGlobals
|
|
13
|
+
|
|
14
|
+
S.enableJson()
|
|
15
|
+
|
|
16
|
+
let published: ref<array<Reventless.Message.commandJson>> = ref([])
|
|
17
|
+
|
|
18
|
+
module TestSpec = {
|
|
19
|
+
@schema let name = "TestCmd"
|
|
20
|
+
@schema type command = {value: int}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module TestConfig = {
|
|
24
|
+
let user = "tester"
|
|
25
|
+
let publishCommands: Task.publishCommands = (_name, cmds) => {
|
|
26
|
+
published := published.contents->Array.concat(cmds)
|
|
27
|
+
Promise.resolve()
|
|
28
|
+
}
|
|
29
|
+
let mode = CommandPublisher.SendAllInOneChunk
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module Pub = CommandPublisher.Make(TestSpec, TestConfig)
|
|
33
|
+
|
|
34
|
+
describe("CommandPublisher", () => {
|
|
35
|
+
testPromise("flush sends all buffered commands in order and drains the buffer", async () => {
|
|
36
|
+
published := []
|
|
37
|
+
Pub.publish("id1", {value: 1})
|
|
38
|
+
Pub.publish("id2", {value: 2})
|
|
39
|
+
await Pub.flush()
|
|
40
|
+
let ids = published.contents->Array.map((c: Reventless.Message.commandJson) => c.id)
|
|
41
|
+
expect(ids)->toEqual(["id1", "id2"])
|
|
42
|
+
// Buffer drained: a second flush sends nothing more.
|
|
43
|
+
published := []
|
|
44
|
+
await Pub.flush()
|
|
45
|
+
expect(published.contents->Array.length)->toBe(0)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
testPromise("clear truncates the whole buffer so nothing is later published", async () => {
|
|
49
|
+
published := []
|
|
50
|
+
Pub.publish("id3", {value: 3})
|
|
51
|
+
Pub.publish("id4", {value: 4})
|
|
52
|
+
Pub.clear()
|
|
53
|
+
await Pub.flush()
|
|
54
|
+
expect(published.contents->Array.length)->toBe(0)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as CommandPublisher$ReventlessCore from "../../src/util/CommandPublisher.res.mjs";
|
|
5
|
+
|
|
6
|
+
S.enableJson();
|
|
7
|
+
|
|
8
|
+
let published = {
|
|
9
|
+
contents: []
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let commandSchema = S.schema(s => ({
|
|
13
|
+
value: s.m(S.int)
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
function commandAuthorization(param) {
|
|
17
|
+
return "AllowAuthenticated";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let TestSpec = {
|
|
21
|
+
name: "TestCmd",
|
|
22
|
+
commandSchema: commandSchema,
|
|
23
|
+
commandAuthorization: commandAuthorization
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function publishCommands(_name, cmds) {
|
|
27
|
+
published.contents = published.contents.concat(cmds);
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let TestConfig = {
|
|
32
|
+
user: "tester",
|
|
33
|
+
publishCommands: publishCommands,
|
|
34
|
+
mode: "SendAllInOneChunk"
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let Pub = CommandPublisher$ReventlessCore.Make(TestSpec)(TestConfig);
|
|
38
|
+
|
|
39
|
+
globalThis.describe("CommandPublisher", () => {
|
|
40
|
+
globalThis.test("flush sends all buffered commands in order and drains the buffer", async () => {
|
|
41
|
+
published.contents = [];
|
|
42
|
+
Pub.publish("id1", {
|
|
43
|
+
value: 1
|
|
44
|
+
});
|
|
45
|
+
Pub.publish("id2", {
|
|
46
|
+
value: 2
|
|
47
|
+
});
|
|
48
|
+
await Pub.flush();
|
|
49
|
+
let ids = published.contents.map(c => c.id);
|
|
50
|
+
globalThis.expect(ids).toEqual([
|
|
51
|
+
"id1",
|
|
52
|
+
"id2"
|
|
53
|
+
]);
|
|
54
|
+
published.contents = [];
|
|
55
|
+
await Pub.flush();
|
|
56
|
+
globalThis.expect(published.contents.length).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
globalThis.test("clear truncates the whole buffer so nothing is later published", async () => {
|
|
59
|
+
published.contents = [];
|
|
60
|
+
Pub.publish("id3", {
|
|
61
|
+
value: 3
|
|
62
|
+
});
|
|
63
|
+
Pub.publish("id4", {
|
|
64
|
+
value: 4
|
|
65
|
+
});
|
|
66
|
+
Pub.clear();
|
|
67
|
+
await Pub.flush();
|
|
68
|
+
globalThis.expect(published.contents.length).toBe(0);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
published,
|
|
74
|
+
TestSpec,
|
|
75
|
+
TestConfig,
|
|
76
|
+
Pub,
|
|
77
|
+
}
|
|
78
|
+
/* Not a pure module */
|