@reventlessdev/reventless-local 3.0.0-alpha.115 → 3.0.0-alpha.117
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 +27 -0
- package/package.json +8 -8
- package/src/Platform.res +91 -17
- package/src/Platform.res.mjs +89 -30
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +102 -17
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res.mjs +105 -22
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +113 -39
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res.mjs +87 -23
- package/src/adapter/EventCollector/LocalEventCollectorChannel.res +33 -0
- package/src/adapter/EventCollector/LocalEventCollectorChannel.res.mjs +44 -0
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +23 -2
- package/src/adapter/EventLog/EventLogStorage_InMemory.res.mjs +20 -3
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +132 -24
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res.mjs +119 -37
- package/src/adapter/LocalBus.res +62 -0
- package/src/adapter/LocalBus.res.mjs +120 -0
- package/src/adapter/ProjectionCheckpoint.res +350 -0
- package/src/adapter/ProjectionCheckpoint.res.mjs +327 -0
- package/src/adapter/ProjectionPending.res +60 -0
- package/src/adapter/ProjectionPending.res.mjs +64 -0
- package/src/adapter/QueryDb/QueryDbListQuery.res +261 -0
- package/src/adapter/QueryDb/QueryDbListQuery.res.mjs +212 -0
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +54 -286
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +28 -190
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +32 -6
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res.mjs +30 -14
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +163 -11
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res.mjs +101 -3
- package/src/adapter/SqliteDriver.res +11 -1
- package/src/adapter/SqliteDriver.res.mjs +5 -1
- package/src/components/ReadModel_Builder.res +3 -1
- package/src/components/ReadModel_Builder.res.mjs +13 -4
- package/src/components/StateViewSlice_Builder.res +4 -1
- package/src/components/StateViewSlice_Builder.res.mjs +9 -3
- package/src/test/Mocks/MockEventLogStorage.res +19 -2
- package/src/test/Mocks/MockEventLogStorage.res.mjs +21 -2
- package/tests/PluginEventDecodeTest.res +51 -0
- package/tests/PluginEventDecodeTest.res.mjs +63 -0
- package/tests/adapter/DcbEventLogStorageSqliteTest.res +73 -0
- package/tests/adapter/DcbEventLogStorageSqliteTest.res.mjs +86 -0
- package/tests/adapter/DcbEventLogStorageTest.res +46 -0
- package/tests/adapter/DcbEventLogStorageTest.res.mjs +63 -0
- package/tests/adapter/EventLogSnapshotParityTest.res +81 -0
- package/tests/adapter/EventLogSnapshotParityTest.res.mjs +141 -0
- package/tests/adapter/EventLogStorageSqliteTest.res +128 -0
- package/tests/adapter/EventLogStorageSqliteTest.res.mjs +189 -0
- package/tests/adapter/ProjectionCheckpointTest.res +422 -0
- package/tests/adapter/ProjectionCheckpointTest.res.mjs +401 -0
- package/tests/adapter/QueryDbGsiTtlTest.res +78 -0
- package/tests/adapter/QueryDbGsiTtlTest.res.mjs +77 -0
- package/tests/adapter/QueryDbListPushdownParityTest.res +197 -0
- package/tests/adapter/QueryDbListPushdownParityTest.res.mjs +292 -0
- package/tests/adapter/QueryDbListResolverTest.res +76 -0
- package/tests/adapter/QueryDbListResolverTest.res.mjs +84 -0
- package/tests/components/aggregate/AggregateFixtures.res +4 -0
- package/tests/components/aggregate/AggregateFixtures.res.mjs +1 -0
- package/tests/components/eventlog/EventLogAppendStreamTest.res.mjs +4 -4
- package/tests/components/eventlog/EventLogStreamTest.res.mjs +8 -8
- package/tests/plugin/PluginBehavior_GWT.res.mjs +1 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as LocalBus$ReventlessLocal from "../../src/adapter/LocalBus.res.mjs";
|
|
6
|
+
import * as TestRunner$ReventlessLocal from "../../src/test/TestRunner.res.mjs";
|
|
7
|
+
import * as SqliteDriver$ReventlessLocal from "../../src/adapter/SqliteDriver.res.mjs";
|
|
8
|
+
import * as EventLogStorage_Sqlite$ReventlessLocal from "../../src/adapter/EventLog/EventLogStorage_Sqlite.res.mjs";
|
|
9
|
+
import * as EventLogStorage_InMemory$ReventlessLocal from "../../src/adapter/EventLog/EventLogStorage_InMemory.res.mjs";
|
|
10
|
+
|
|
11
|
+
TestRunner$ReventlessLocal.setup();
|
|
12
|
+
|
|
13
|
+
let opts = {};
|
|
14
|
+
|
|
15
|
+
function makeMemoryOps() {
|
|
16
|
+
let s = EventLogStorage_InMemory$ReventlessLocal.make("parity", opts);
|
|
17
|
+
return TestRunner$ReventlessLocal.resolve(s.operations);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function makeSqliteOps() {
|
|
21
|
+
let TestBus = LocalBus$ReventlessLocal.Make({});
|
|
22
|
+
let db = SqliteDriver$ReventlessLocal.openDb(":memory:");
|
|
23
|
+
let DbProvider = {
|
|
24
|
+
db: db
|
|
25
|
+
};
|
|
26
|
+
let Storage = EventLogStorage_Sqlite$ReventlessLocal.Make(TestBus)(DbProvider);
|
|
27
|
+
let s = Storage.make("parity", opts);
|
|
28
|
+
return TestRunner$ReventlessLocal.resolve(s.operations);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let backends = [
|
|
32
|
+
[
|
|
33
|
+
"memory",
|
|
34
|
+
makeMemoryOps
|
|
35
|
+
],
|
|
36
|
+
[
|
|
37
|
+
"sqlite",
|
|
38
|
+
makeSqliteOps
|
|
39
|
+
]
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
globalThis.describe("EventLog snapshot backend parity", () => {
|
|
43
|
+
backends.forEach(param => {
|
|
44
|
+
let makeOps = param[1];
|
|
45
|
+
let label = param[0];
|
|
46
|
+
globalThis.test(label + `: latestSnapshot is None before any write`, async () => {
|
|
47
|
+
let ops = await makeOps();
|
|
48
|
+
globalThis.expect(await ops.latestSnapshot("id-1")).toEqual({
|
|
49
|
+
TAG: "Ok",
|
|
50
|
+
_0: undefined
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
globalThis.test(label + `: snapshot round-trips and keep-one overwrites`, async () => {
|
|
54
|
+
let ops = await makeOps();
|
|
55
|
+
let state = Object.fromEntries([[
|
|
56
|
+
"n",
|
|
57
|
+
1
|
|
58
|
+
]]);
|
|
59
|
+
globalThis.expect(await ops.writeSnapshot("id-1", {
|
|
60
|
+
seqNr: 2,
|
|
61
|
+
state: state,
|
|
62
|
+
schemaHash: "h1"
|
|
63
|
+
})).toEqual({
|
|
64
|
+
TAG: "Ok",
|
|
65
|
+
_0: undefined
|
|
66
|
+
});
|
|
67
|
+
globalThis.expect(await ops.latestSnapshot("id-1")).toEqual({
|
|
68
|
+
TAG: "Ok",
|
|
69
|
+
_0: {
|
|
70
|
+
seqNr: 2,
|
|
71
|
+
state: state,
|
|
72
|
+
schemaHash: "h1"
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
let state2 = Object.fromEntries([[
|
|
76
|
+
"n",
|
|
77
|
+
9
|
|
78
|
+
]]);
|
|
79
|
+
await ops.writeSnapshot("id-1", {
|
|
80
|
+
seqNr: 9,
|
|
81
|
+
state: state2,
|
|
82
|
+
schemaHash: "h2"
|
|
83
|
+
});
|
|
84
|
+
globalThis.expect(await ops.latestSnapshot("id-1")).toEqual({
|
|
85
|
+
TAG: "Ok",
|
|
86
|
+
_0: {
|
|
87
|
+
seqNr: 9,
|
|
88
|
+
state: state2,
|
|
89
|
+
schemaHash: "h2"
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
globalThis.test(label + `: snapshots are per-aggregate-id`, async () => {
|
|
94
|
+
let ops = await makeOps();
|
|
95
|
+
let state = Object.fromEntries([[
|
|
96
|
+
"n",
|
|
97
|
+
1
|
|
98
|
+
]]);
|
|
99
|
+
await ops.writeSnapshot("id-a", {
|
|
100
|
+
seqNr: 1,
|
|
101
|
+
state: state,
|
|
102
|
+
schemaHash: "h"
|
|
103
|
+
});
|
|
104
|
+
globalThis.expect(await ops.latestSnapshot("id-b")).toEqual({
|
|
105
|
+
TAG: "Ok",
|
|
106
|
+
_0: undefined
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
globalThis.test(label + `: replayStream ~fromSeq equals the tail of a full replay`, async () => {
|
|
110
|
+
let ops = await makeOps();
|
|
111
|
+
let ev = i => Object.fromEntries([[
|
|
112
|
+
"i",
|
|
113
|
+
i
|
|
114
|
+
]]);
|
|
115
|
+
await ops.append(0, "id-1", [
|
|
116
|
+
ev(0),
|
|
117
|
+
ev(1),
|
|
118
|
+
ev(2),
|
|
119
|
+
ev(3),
|
|
120
|
+
ev(4)
|
|
121
|
+
]);
|
|
122
|
+
let delta = await Effect.runPromise(Stream.runCollect(ops.replayStream("id-1", 3)));
|
|
123
|
+
globalThis.expect(delta).toEqual([
|
|
124
|
+
ev(3),
|
|
125
|
+
ev(4)
|
|
126
|
+
]);
|
|
127
|
+
let empty = await Effect.runPromise(Stream.runCollect(ops.replayStream("id-1", 5)));
|
|
128
|
+
globalThis.expect(empty).toEqual([]);
|
|
129
|
+
let full = await Effect.runPromise(Stream.runCollect(ops.replayStream("id-1", undefined)));
|
|
130
|
+
globalThis.expect(full.length).toBe(5);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export {
|
|
136
|
+
opts,
|
|
137
|
+
makeMemoryOps,
|
|
138
|
+
makeSqliteOps,
|
|
139
|
+
backends,
|
|
140
|
+
}
|
|
141
|
+
/* Not a pure module */
|
|
@@ -61,6 +61,52 @@ describe("EventLogStorage_Sqlite", () => {
|
|
|
61
61
|
}
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
+
testPromise("multi-event append advances the expected seq via MAX(seq_nr)+1", async () => {
|
|
65
|
+
module TestBus = LocalBus.Make()
|
|
66
|
+
module DbProvider = {
|
|
67
|
+
let db = makeFreshDb()
|
|
68
|
+
}
|
|
69
|
+
module Storage = EventLogStorage_Sqlite.Make(TestBus, DbProvider)
|
|
70
|
+
let s = Storage.make(~name="agg", ~opts)
|
|
71
|
+
let ops = await s.operations->TestRunner.resolve
|
|
72
|
+
|
|
73
|
+
let ev = i => JSON.Encode.object(Dict.fromArray([("i", JSON.Encode.int(i))]))
|
|
74
|
+
// Batch of three at seq 0 → occupies seq 0,1,2.
|
|
75
|
+
let r1 = await ops.append(0, "id-m", [ev(0), ev(1), ev(2)])
|
|
76
|
+
expect(r1)->toEqual(Ok())
|
|
77
|
+
// A stale seq (2) is a conflict; the correct next seq is 3.
|
|
78
|
+
let stale = await ops.append(2, "id-m", [ev(9)])
|
|
79
|
+
switch stale {
|
|
80
|
+
| Error(ReventlessCore.EventLog.Conflict) => expect(true)->toBe(true)
|
|
81
|
+
| _ => expect("expected Conflict")->toBe("other")
|
|
82
|
+
}
|
|
83
|
+
let r2 = await ops.append(3, "id-m", [ev(3)])
|
|
84
|
+
expect(r2)->toEqual(Ok())
|
|
85
|
+
let replayed = await ops.replay("id-m")
|
|
86
|
+
expect(replayed->Array.length)->toBe(4)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
testPromise("appendStream persists the whole batch in order from the starting seq", async () => {
|
|
90
|
+
module TestBus = LocalBus.Make()
|
|
91
|
+
module DbProvider = {
|
|
92
|
+
let db = makeFreshDb()
|
|
93
|
+
}
|
|
94
|
+
module Storage = EventLogStorage_Sqlite.Make(TestBus, DbProvider)
|
|
95
|
+
let s = Storage.make(~name="agg", ~opts)
|
|
96
|
+
let ops = await s.operations->TestRunner.resolve
|
|
97
|
+
|
|
98
|
+
let ev = i => JSON.Encode.object(Dict.fromArray([("i", JSON.Encode.int(i))]))
|
|
99
|
+
// Seed one event, then stream three more starting at seq 1.
|
|
100
|
+
let _ = await ops.append(0, "id-s", [ev(0)])
|
|
101
|
+
let _ =
|
|
102
|
+
await ops.appendStream(1, "id-s", Stream.fromIterable([ev(1), ev(2), ev(3)]))
|
|
103
|
+
->Effect.runPromise
|
|
104
|
+
let replayed = await ops.replay("id-s")
|
|
105
|
+
expect(replayed->Array.length)->toBe(4)
|
|
106
|
+
expect(replayed->Array.getUnsafe(0))->toEqual(ev(0))
|
|
107
|
+
expect(replayed->Array.getUnsafe(3))->toEqual(ev(3))
|
|
108
|
+
})
|
|
109
|
+
|
|
64
110
|
testPromise("replay returns empty array for unknown id", async () => {
|
|
65
111
|
module TestBus = LocalBus.Make()
|
|
66
112
|
module DbProvider = {
|
|
@@ -75,6 +121,88 @@ describe("EventLogStorage_Sqlite", () => {
|
|
|
75
121
|
expect(replayed->Array.length)->toBe(0)
|
|
76
122
|
})
|
|
77
123
|
|
|
124
|
+
testPromise("replayStream ~fromSeq reads only the delta", async () => {
|
|
125
|
+
module TestBus = LocalBus.Make()
|
|
126
|
+
module DbProvider = {
|
|
127
|
+
let db = makeFreshDb()
|
|
128
|
+
}
|
|
129
|
+
module Storage = EventLogStorage_Sqlite.Make(TestBus, DbProvider)
|
|
130
|
+
let s = Storage.make(~name="agg", ~opts)
|
|
131
|
+
let ops = await s.operations->TestRunner.resolve
|
|
132
|
+
|
|
133
|
+
let ev = i => JSON.Encode.object(Dict.fromArray([("i", JSON.Encode.int(i))]))
|
|
134
|
+
let _ = await ops.append(0, "id-d", [ev(0), ev(1), ev(2), ev(3)])
|
|
135
|
+
let delta = await ops.replayStream("id-d", ~fromSeq=2)->Stream.runCollect->Effect.runPromise
|
|
136
|
+
expect(delta)->toEqual([ev(2), ev(3)])
|
|
137
|
+
// Omitted fromSeq still replays from 0 (unchanged default).
|
|
138
|
+
let full = await ops.replayStream("id-d")->Stream.runCollect->Effect.runPromise
|
|
139
|
+
expect(full->Array.length)->toBe(4)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
testPromise("snapshot round-trip with keep-one overwrite", async () => {
|
|
143
|
+
module TestBus = LocalBus.Make()
|
|
144
|
+
module DbProvider = {
|
|
145
|
+
let db = makeFreshDb()
|
|
146
|
+
}
|
|
147
|
+
module Storage = EventLogStorage_Sqlite.Make(TestBus, DbProvider)
|
|
148
|
+
let s = Storage.make(~name="agg", ~opts)
|
|
149
|
+
let ops = await s.operations->TestRunner.resolve
|
|
150
|
+
|
|
151
|
+
let none = await ops.latestSnapshot("id-snap")
|
|
152
|
+
expect(none)->toEqual(Ok(None))
|
|
153
|
+
|
|
154
|
+
let state = JSON.Encode.object(Dict.fromArray([("names", JSON.Encode.int(2))]))
|
|
155
|
+
let w1 = await ops.writeSnapshot(
|
|
156
|
+
"id-snap",
|
|
157
|
+
{ReventlessCore.EventLog.seqNr: 2, state, schemaHash: "h1"},
|
|
158
|
+
)
|
|
159
|
+
expect(w1)->toEqual(Ok())
|
|
160
|
+
let r1 = await ops.latestSnapshot("id-snap")
|
|
161
|
+
expect(r1)->toEqual(Ok(Some({ReventlessCore.EventLog.seqNr: 2, state, schemaHash: "h1"})))
|
|
162
|
+
|
|
163
|
+
// Keep-one: a later write replaces the row, it does not accumulate.
|
|
164
|
+
let state5 = JSON.Encode.object(Dict.fromArray([("names", JSON.Encode.int(5))]))
|
|
165
|
+
let _ = await ops.writeSnapshot(
|
|
166
|
+
"id-snap",
|
|
167
|
+
{ReventlessCore.EventLog.seqNr: 5, state: state5, schemaHash: "h2"},
|
|
168
|
+
)
|
|
169
|
+
let r2 = await ops.latestSnapshot("id-snap")
|
|
170
|
+
expect(r2)->toEqual(
|
|
171
|
+
Ok(Some({ReventlessCore.EventLog.seqNr: 5, state: state5, schemaHash: "h2"})),
|
|
172
|
+
)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
testPromise("snapshots persist across a reopen of the database file", async () => {
|
|
176
|
+
let path = `/tmp/reventless-test-snapshot-${Float.toString(Date.now())}.db`
|
|
177
|
+
let state = JSON.Encode.object(Dict.fromArray([("v", JSON.Encode.int(7))]))
|
|
178
|
+
|
|
179
|
+
{
|
|
180
|
+
module TestBus = LocalBus.Make()
|
|
181
|
+
module DbProvider = {
|
|
182
|
+
let db = SqliteDriver.openDb(~path)
|
|
183
|
+
}
|
|
184
|
+
module Storage = EventLogStorage_Sqlite.Make(TestBus, DbProvider)
|
|
185
|
+
let s = Storage.make(~name="persist", ~opts)
|
|
186
|
+
let ops = await s.operations->TestRunner.resolve
|
|
187
|
+
let _ = await ops.writeSnapshot(
|
|
188
|
+
"id-sp",
|
|
189
|
+
{ReventlessCore.EventLog.seqNr: 3, state, schemaHash: "h"},
|
|
190
|
+
)
|
|
191
|
+
DbProvider.db->SqliteDriver.close
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module TestBus2 = LocalBus.Make()
|
|
195
|
+
module DbProvider2 = {
|
|
196
|
+
let db = SqliteDriver.openDb(~path)
|
|
197
|
+
}
|
|
198
|
+
module Storage2 = EventLogStorage_Sqlite.Make(TestBus2, DbProvider2)
|
|
199
|
+
let s2 = Storage2.make(~name="persist", ~opts)
|
|
200
|
+
let ops2 = await s2.operations->TestRunner.resolve
|
|
201
|
+
let r = await ops2.latestSnapshot("id-sp")
|
|
202
|
+
expect(r)->toEqual(Ok(Some({ReventlessCore.EventLog.seqNr: 3, state, schemaHash: "h"})))
|
|
203
|
+
DbProvider2.db->SqliteDriver.close
|
|
204
|
+
})
|
|
205
|
+
|
|
78
206
|
testPromise("events persist across a reopen of the database file", async () => {
|
|
79
207
|
let path = `/tmp/reventless-test-eventlog-${Float.toString(Date.now())}.db`
|
|
80
208
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Stream$1 from "effect/Stream";
|
|
3
6
|
import * as LocalBus$ReventlessLocal from "../../src/adapter/LocalBus.res.mjs";
|
|
4
7
|
import * as TestRunner$ReventlessLocal from "../../src/test/TestRunner.res.mjs";
|
|
5
8
|
import * as SqliteDriver$ReventlessLocal from "../../src/adapter/SqliteDriver.res.mjs";
|
|
@@ -72,6 +75,71 @@ globalThis.describe("EventLogStorage_Sqlite", () => {
|
|
|
72
75
|
}
|
|
73
76
|
globalThis.expect("expected Conflict, got StorageFailure").toBe(msg._0);
|
|
74
77
|
});
|
|
78
|
+
globalThis.test("multi-event append advances the expected seq via MAX(seq_nr)+1", async () => {
|
|
79
|
+
let TestBus = LocalBus$ReventlessLocal.Make({});
|
|
80
|
+
let db = SqliteDriver$ReventlessLocal.openDb(":memory:");
|
|
81
|
+
let DbProvider = {
|
|
82
|
+
db: db
|
|
83
|
+
};
|
|
84
|
+
let Storage = EventLogStorage_Sqlite$ReventlessLocal.Make(TestBus)(DbProvider);
|
|
85
|
+
let s = Storage.make("agg", opts);
|
|
86
|
+
let ops = await TestRunner$ReventlessLocal.resolve(s.operations);
|
|
87
|
+
let ev = i => Object.fromEntries([[
|
|
88
|
+
"i",
|
|
89
|
+
i
|
|
90
|
+
]]);
|
|
91
|
+
let r1 = await ops.append(0, "id-m", [
|
|
92
|
+
ev(0),
|
|
93
|
+
ev(1),
|
|
94
|
+
ev(2)
|
|
95
|
+
]);
|
|
96
|
+
globalThis.expect(r1).toEqual({
|
|
97
|
+
TAG: "Ok",
|
|
98
|
+
_0: undefined
|
|
99
|
+
});
|
|
100
|
+
let stale = await ops.append(2, "id-m", [ev(9)]);
|
|
101
|
+
if (stale.TAG === "Ok") {
|
|
102
|
+
globalThis.expect("expected Conflict").toBe("other");
|
|
103
|
+
} else {
|
|
104
|
+
let tmp = stale._0;
|
|
105
|
+
if (typeof tmp !== "object") {
|
|
106
|
+
globalThis.expect(true).toBe(true);
|
|
107
|
+
} else {
|
|
108
|
+
globalThis.expect("expected Conflict").toBe("other");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
let r2 = await ops.append(3, "id-m", [ev(3)]);
|
|
112
|
+
globalThis.expect(r2).toEqual({
|
|
113
|
+
TAG: "Ok",
|
|
114
|
+
_0: undefined
|
|
115
|
+
});
|
|
116
|
+
let replayed = await ops.replay("id-m");
|
|
117
|
+
globalThis.expect(replayed.length).toBe(4);
|
|
118
|
+
});
|
|
119
|
+
globalThis.test("appendStream persists the whole batch in order from the starting seq", async () => {
|
|
120
|
+
let TestBus = LocalBus$ReventlessLocal.Make({});
|
|
121
|
+
let db = SqliteDriver$ReventlessLocal.openDb(":memory:");
|
|
122
|
+
let DbProvider = {
|
|
123
|
+
db: db
|
|
124
|
+
};
|
|
125
|
+
let Storage = EventLogStorage_Sqlite$ReventlessLocal.Make(TestBus)(DbProvider);
|
|
126
|
+
let s = Storage.make("agg", opts);
|
|
127
|
+
let ops = await TestRunner$ReventlessLocal.resolve(s.operations);
|
|
128
|
+
let ev = i => Object.fromEntries([[
|
|
129
|
+
"i",
|
|
130
|
+
i
|
|
131
|
+
]]);
|
|
132
|
+
await ops.append(0, "id-s", [ev(0)]);
|
|
133
|
+
await Effect.runPromise(ops.appendStream(1, "id-s", Stream$1.fromIterable([
|
|
134
|
+
ev(1),
|
|
135
|
+
ev(2),
|
|
136
|
+
ev(3)
|
|
137
|
+
])));
|
|
138
|
+
let replayed = await ops.replay("id-s");
|
|
139
|
+
globalThis.expect(replayed.length).toBe(4);
|
|
140
|
+
globalThis.expect(replayed[0]).toEqual(ev(0));
|
|
141
|
+
globalThis.expect(replayed[3]).toEqual(ev(3));
|
|
142
|
+
});
|
|
75
143
|
globalThis.test("replay returns empty array for unknown id", async () => {
|
|
76
144
|
let TestBus = LocalBus$ReventlessLocal.Make({});
|
|
77
145
|
let db = SqliteDriver$ReventlessLocal.openDb(":memory:");
|
|
@@ -84,6 +152,127 @@ globalThis.describe("EventLogStorage_Sqlite", () => {
|
|
|
84
152
|
let replayed = await ops.replay("not-there");
|
|
85
153
|
globalThis.expect(replayed.length).toBe(0);
|
|
86
154
|
});
|
|
155
|
+
globalThis.test("replayStream ~fromSeq reads only the delta", async () => {
|
|
156
|
+
let TestBus = LocalBus$ReventlessLocal.Make({});
|
|
157
|
+
let db = SqliteDriver$ReventlessLocal.openDb(":memory:");
|
|
158
|
+
let DbProvider = {
|
|
159
|
+
db: db
|
|
160
|
+
};
|
|
161
|
+
let Storage = EventLogStorage_Sqlite$ReventlessLocal.Make(TestBus)(DbProvider);
|
|
162
|
+
let s = Storage.make("agg", opts);
|
|
163
|
+
let ops = await TestRunner$ReventlessLocal.resolve(s.operations);
|
|
164
|
+
let ev = i => Object.fromEntries([[
|
|
165
|
+
"i",
|
|
166
|
+
i
|
|
167
|
+
]]);
|
|
168
|
+
await ops.append(0, "id-d", [
|
|
169
|
+
ev(0),
|
|
170
|
+
ev(1),
|
|
171
|
+
ev(2),
|
|
172
|
+
ev(3)
|
|
173
|
+
]);
|
|
174
|
+
let delta = await Effect.runPromise(Stream.runCollect(ops.replayStream("id-d", 2)));
|
|
175
|
+
globalThis.expect(delta).toEqual([
|
|
176
|
+
ev(2),
|
|
177
|
+
ev(3)
|
|
178
|
+
]);
|
|
179
|
+
let full = await Effect.runPromise(Stream.runCollect(ops.replayStream("id-d", undefined)));
|
|
180
|
+
globalThis.expect(full.length).toBe(4);
|
|
181
|
+
});
|
|
182
|
+
globalThis.test("snapshot round-trip with keep-one overwrite", async () => {
|
|
183
|
+
let TestBus = LocalBus$ReventlessLocal.Make({});
|
|
184
|
+
let db = SqliteDriver$ReventlessLocal.openDb(":memory:");
|
|
185
|
+
let DbProvider = {
|
|
186
|
+
db: db
|
|
187
|
+
};
|
|
188
|
+
let Storage = EventLogStorage_Sqlite$ReventlessLocal.Make(TestBus)(DbProvider);
|
|
189
|
+
let s = Storage.make("agg", opts);
|
|
190
|
+
let ops = await TestRunner$ReventlessLocal.resolve(s.operations);
|
|
191
|
+
let none = await ops.latestSnapshot("id-snap");
|
|
192
|
+
globalThis.expect(none).toEqual({
|
|
193
|
+
TAG: "Ok",
|
|
194
|
+
_0: undefined
|
|
195
|
+
});
|
|
196
|
+
let state = Object.fromEntries([[
|
|
197
|
+
"names",
|
|
198
|
+
2
|
|
199
|
+
]]);
|
|
200
|
+
let w1 = await ops.writeSnapshot("id-snap", {
|
|
201
|
+
seqNr: 2,
|
|
202
|
+
state: state,
|
|
203
|
+
schemaHash: "h1"
|
|
204
|
+
});
|
|
205
|
+
globalThis.expect(w1).toEqual({
|
|
206
|
+
TAG: "Ok",
|
|
207
|
+
_0: undefined
|
|
208
|
+
});
|
|
209
|
+
let r1 = await ops.latestSnapshot("id-snap");
|
|
210
|
+
globalThis.expect(r1).toEqual({
|
|
211
|
+
TAG: "Ok",
|
|
212
|
+
_0: {
|
|
213
|
+
seqNr: 2,
|
|
214
|
+
state: state,
|
|
215
|
+
schemaHash: "h1"
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
let state5 = Object.fromEntries([[
|
|
219
|
+
"names",
|
|
220
|
+
5
|
|
221
|
+
]]);
|
|
222
|
+
await ops.writeSnapshot("id-snap", {
|
|
223
|
+
seqNr: 5,
|
|
224
|
+
state: state5,
|
|
225
|
+
schemaHash: "h2"
|
|
226
|
+
});
|
|
227
|
+
let r2 = await ops.latestSnapshot("id-snap");
|
|
228
|
+
globalThis.expect(r2).toEqual({
|
|
229
|
+
TAG: "Ok",
|
|
230
|
+
_0: {
|
|
231
|
+
seqNr: 5,
|
|
232
|
+
state: state5,
|
|
233
|
+
schemaHash: "h2"
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
globalThis.test("snapshots persist across a reopen of the database file", async () => {
|
|
238
|
+
let path = `/tmp/reventless-test-snapshot-` + Date.now().toString() + `.db`;
|
|
239
|
+
let state = Object.fromEntries([[
|
|
240
|
+
"v",
|
|
241
|
+
7
|
|
242
|
+
]]);
|
|
243
|
+
let TestBus = LocalBus$ReventlessLocal.Make({});
|
|
244
|
+
let db = SqliteDriver$ReventlessLocal.openDb(path);
|
|
245
|
+
let DbProvider = {
|
|
246
|
+
db: db
|
|
247
|
+
};
|
|
248
|
+
let Storage = EventLogStorage_Sqlite$ReventlessLocal.Make(TestBus)(DbProvider);
|
|
249
|
+
let s = Storage.make("persist", opts);
|
|
250
|
+
let ops = await TestRunner$ReventlessLocal.resolve(s.operations);
|
|
251
|
+
await ops.writeSnapshot("id-sp", {
|
|
252
|
+
seqNr: 3,
|
|
253
|
+
state: state,
|
|
254
|
+
schemaHash: "h"
|
|
255
|
+
});
|
|
256
|
+
SqliteDriver$ReventlessLocal.close(db);
|
|
257
|
+
let TestBus2 = LocalBus$ReventlessLocal.Make({});
|
|
258
|
+
let db$1 = SqliteDriver$ReventlessLocal.openDb(path);
|
|
259
|
+
let DbProvider2 = {
|
|
260
|
+
db: db$1
|
|
261
|
+
};
|
|
262
|
+
let Storage2 = EventLogStorage_Sqlite$ReventlessLocal.Make(TestBus2)(DbProvider2);
|
|
263
|
+
let s2 = Storage2.make("persist", opts);
|
|
264
|
+
let ops2 = await TestRunner$ReventlessLocal.resolve(s2.operations);
|
|
265
|
+
let r = await ops2.latestSnapshot("id-sp");
|
|
266
|
+
globalThis.expect(r).toEqual({
|
|
267
|
+
TAG: "Ok",
|
|
268
|
+
_0: {
|
|
269
|
+
seqNr: 3,
|
|
270
|
+
state: state,
|
|
271
|
+
schemaHash: "h"
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
return SqliteDriver$ReventlessLocal.close(db$1);
|
|
275
|
+
});
|
|
87
276
|
globalThis.test("events persist across a reopen of the database file", async () => {
|
|
88
277
|
let path = `/tmp/reventless-test-eventlog-` + Date.now().toString() + `.db`;
|
|
89
278
|
let TestBus = LocalBus$ReventlessLocal.Make({});
|