@reventlessdev/reventless-local 3.0.0-alpha.115 → 3.0.0-alpha.116
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 +6 -6
- 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
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
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.116 (2026-07-03)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **local:** decode plugin events from the bus envelope in subscribeToPluginEvents ([0f24dc1](https://github.com/ReventlessDev/reventless-core/commit/0f24dc129bf2631b94c300d7d5f6ceba3a9bb9f7))
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **core,local,aws:** EventLog snapshot storage surface, all backends (aggregate-snapshotting plan, steps 3+4) ([b6e50e2](https://github.com/ReventlessDev/reventless-core/commit/b6e50e2e5fe50b7372e54811f883fa91f6758dd1))
|
|
14
|
+
* **local:** DCB-axis projection checkpoints + startup catch-up (plan B5) ([8f9b11d](https://github.com/ReventlessDev/reventless-core/commit/8f9b11d24627bb6cc105d37af4974745444b80ba))
|
|
15
|
+
* **local:** projection checkpoints + startup catch-up under SQLite (plan B5) ([d28f30e](https://github.com/ReventlessDev/reventless-core/commit/d28f30e25d43cbf378b3aab091b1979dbb449a4f))
|
|
16
|
+
### Performance Improvements
|
|
17
|
+
|
|
18
|
+
* **local:** DCB in-memory posting lists for tag/type queries (plan B4) ([7859fed](https://github.com/ReventlessDev/reventless-core/commit/7859fede8ad6ab44f657d4d9dd44fba08e85e3aa))
|
|
19
|
+
* **local:** DCB SQLite batched tag reads + EXISTS conflict check (plan B4) ([c9d647b](https://github.com/ReventlessDev/reventless-core/commit/c9d647b73adba4e054edbeba83dfb9c8111e4dfb))
|
|
20
|
+
* **local:** EventLog SQLite MAX(seq_nr)+1 + batched appendStream (plan B4) ([7b24fb1](https://github.com/ReventlessDev/reventless-core/commit/7b24fb12b78b79854d3c345a74f1eab68a28e745))
|
|
21
|
+
* **local:** SQLite connection-list push-down with parity harness (plan B4 complete) ([d78d428](https://github.com/ReventlessDev/reventless-core/commit/d78d4282c923c64cbde3432cd906845705a1e97b))
|
|
22
|
+
* **local:** SQLite pragmas, lazy in-memory scan, indexed lookup push-down (plan B4) ([b0ff78a](https://github.com/ReventlessDev/reventless-core/commit/b0ff78a5c02030eef9fc5bb1ee8b92c65114fa45))
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
# 3.0.0-alpha.115 (2026-07-02)
|
|
7
26
|
|
|
8
27
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-local",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.116",
|
|
4
4
|
"description": "Local platform for Reventless (in-memory or SQLite backend, for development and testing without AWS)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -36,15 +36,15 @@
|
|
|
36
36
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.5",
|
|
37
37
|
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.16",
|
|
38
38
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
|
|
39
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.
|
|
40
|
-
"@reventlessdev/reventless-gwt": "1.0.0-alpha.
|
|
41
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
42
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
39
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.137",
|
|
40
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.77",
|
|
41
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.84",
|
|
42
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.64"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"rescript": "^12.3.0",
|
|
46
46
|
"sury-ppx": "11.0.0-alpha.2",
|
|
47
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
47
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.50"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"rescript": "^12.3.0"
|
package/src/Platform.res
CHANGED
|
@@ -28,6 +28,22 @@ let getPlatformGraphQL = () => platformGraphQLRef.contents
|
|
|
28
28
|
// Populated by makePlatform when splitApi=true.
|
|
29
29
|
let platformMCPRef: ref<option<MCP_ServerInstance.t>> = ref(None)
|
|
30
30
|
|
|
31
|
+
// Decode a Plugin aggregate event from the published bus envelope.
|
|
32
|
+
// The bus delivers the EventTopic envelope {id, meta, event} — the event
|
|
33
|
+
// payload lives under "event". (The previous code fed the WHOLE envelope to
|
|
34
|
+
// the event schema in convert mode, which "succeeded" and then threw a
|
|
35
|
+
// TypeError on every variant's payload access, so the Source-C status /
|
|
36
|
+
// UI-fragment subscription emissions never fired.) Top-level, not inside the
|
|
37
|
+
// functor, so the decode is unit-testable.
|
|
38
|
+
let decodePluginEventEnvelope = (eventJson: JSON.t): option<ReventlessCore.PluginSpec.event> =>
|
|
39
|
+
switch eventJson
|
|
40
|
+
->JSON.Decode.object
|
|
41
|
+
->Option.flatMap(d => d->Dict.get("event"))
|
|
42
|
+
->Option.map(payload => payload->S.parseJsonOrThrow(ReventlessCore.PluginSpec.eventSchema)) {
|
|
43
|
+
| result => result
|
|
44
|
+
| exception _ => None
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
// Configurable platform — set silent=true to suppress diagnostic warnings in tests,
|
|
32
48
|
// splitApi=true to serve core and plugin APIs on separate ports,
|
|
33
49
|
// backend=Backend.Sqlite({...}) to opt into file-backed SQLite persistence.
|
|
@@ -76,6 +92,29 @@ module MakeWithConfig = (
|
|
|
76
92
|
LocalBus.seedEventTapSeq(
|
|
77
93
|
EventLogStorage_Sqlite.countAll(db) + DcbEventLogStorage_Sqlite.countAll(db),
|
|
78
94
|
)
|
|
95
|
+
// Projection checkpoints (plan B5): the event-log storage tracks each
|
|
96
|
+
// appended batch as pending; this hook resolves it once the publish cycle
|
|
97
|
+
// completes (publishEvent returns only after every subscriber processed the
|
|
98
|
+
// events), advancing the persisted per-read-model checkpoints to the
|
|
99
|
+
// low-watermark. Startup catch-up in makePlatform replays anything below a
|
|
100
|
+
// read model's checkpoint that a crash left unprojected.
|
|
101
|
+
ProjectionPending.enableTracking()
|
|
102
|
+
ReventlessCore.EventPublish_Callback.registerAfterPublish(async publishedEvent => {
|
|
103
|
+
// Aggregate EventLog batches carry flat stored events (msgId at the top
|
|
104
|
+
// level of each eventsJson entry); DcbEventLog batches carry bare event
|
|
105
|
+
// payloads and expose the per-event metas separately.
|
|
106
|
+
let msgIds = switch publishedEvent.metas {
|
|
107
|
+
| Some(metas) => metas->Array.map(m => m.msgId)
|
|
108
|
+
| None =>
|
|
109
|
+
publishedEvent.eventsJson->Array.filterMap(json =>
|
|
110
|
+
json
|
|
111
|
+
->JSON.Decode.object
|
|
112
|
+
->Option.flatMap(d => d->Dict.get("msgId"))
|
|
113
|
+
->Option.flatMap(JSON.Decode.string)
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
ProjectionCheckpoint.completePublished(db, msgIds)
|
|
117
|
+
})
|
|
79
118
|
}
|
|
80
119
|
|
|
81
120
|
module Bus = LocalBus.Impl({
|
|
@@ -860,36 +899,36 @@ module MakeWithConfig = (
|
|
|
860
899
|
let manifestJson = (m: Reventless.Plugin.uiFragmentManifest) =>
|
|
861
900
|
m->S.reverseConvertToJsonOrThrow(Reventless.Plugin.uiFragmentManifestSchema)
|
|
862
901
|
Bus.subscribeToEvents(pluginEventTopicKey, async (_service, _meta, eventJson) => {
|
|
863
|
-
switch eventJson
|
|
864
|
-
| VersionConnected(def)
|
|
865
|
-
| VersionActivated(def)
|
|
866
|
-
| VersionPromoted(def) =>
|
|
902
|
+
switch decodePluginEventEnvelope(eventJson) {
|
|
903
|
+
| Some(VersionConnected(def))
|
|
904
|
+
| Some(VersionActivated(def))
|
|
905
|
+
| Some(VersionPromoted(def)) =>
|
|
867
906
|
publishStatus(~name=def.name, ~status="Connected")
|
|
868
|
-
| VersionDisconnected(def) => publishStatus(~name=def.name, ~status="Disconnected")
|
|
869
|
-
| VersionDeactivated(def) => publishStatus(~name=def.name, ~status="Inactive")
|
|
870
|
-
| VersionRetired(def) => publishStatus(~name=def.name, ~status="Retired")
|
|
871
|
-
| UIFragmentRegistered(data) =>
|
|
907
|
+
| Some(VersionDisconnected(def)) => publishStatus(~name=def.name, ~status="Disconnected")
|
|
908
|
+
| Some(VersionDeactivated(def)) => publishStatus(~name=def.name, ~status="Inactive")
|
|
909
|
+
| Some(VersionRetired(def)) => publishStatus(~name=def.name, ~status="Retired")
|
|
910
|
+
| Some(UIFragmentRegistered(data)) =>
|
|
872
911
|
publishUIFragment(
|
|
873
912
|
~name=ReventlessCore.Plugin.name(data.pluginId),
|
|
874
913
|
~changeKind="Registered",
|
|
875
914
|
~manifest=manifestJson(data.manifest),
|
|
876
915
|
)
|
|
877
|
-
| UIFragmentUpdated(data) =>
|
|
916
|
+
| Some(UIFragmentUpdated(data)) =>
|
|
878
917
|
publishUIFragment(
|
|
879
918
|
~name=ReventlessCore.Plugin.name(data.pluginId),
|
|
880
919
|
~changeKind="Updated",
|
|
881
920
|
~manifest=manifestJson(data.newManifest),
|
|
882
921
|
)
|
|
883
|
-
| UIFragmentDeregistered(data) =>
|
|
922
|
+
| Some(UIFragmentDeregistered(data)) =>
|
|
884
923
|
publishUIFragment(
|
|
885
924
|
~name=ReventlessCore.Plugin.name(data.pluginId),
|
|
886
925
|
~changeKind="Deregistered",
|
|
887
926
|
~manifest=JSON.Encode.null,
|
|
888
927
|
)
|
|
889
|
-
| VersionDetected(_)
|
|
890
|
-
| VersionSuperseded(_)
|
|
891
|
-
| IncompatiblePluginDetected(_)
|
|
892
|
-
|
|
|
928
|
+
| Some(VersionDetected(_))
|
|
929
|
+
| Some(VersionSuperseded(_))
|
|
930
|
+
| Some(IncompatiblePluginDetected(_))
|
|
931
|
+
| None => ()
|
|
893
932
|
}
|
|
894
933
|
})
|
|
895
934
|
}
|
|
@@ -1239,6 +1278,16 @@ module MakeWithConfig = (
|
|
|
1239
1278
|
`backend: sqlite (${path}${resetOnStart ? ", resetOnStart" : ""})`
|
|
1240
1279
|
},
|
|
1241
1280
|
)
|
|
1281
|
+
// Projection catch-up bound (plan B5): the highest persisted event position
|
|
1282
|
+
// BEFORE this session appends anything. Catch-up replays only (checkpoint,
|
|
1283
|
+
// bound] — this session's own events (Connect dispatches, user commands)
|
|
1284
|
+
// are live-delivered and must not be redelivered.
|
|
1285
|
+
let projectionCatchup = BackendState.getDb()->Option.map(db => (
|
|
1286
|
+
db,
|
|
1287
|
+
ProjectionCheckpoint.maxPosition(db, ProjectionPending.Aggregate),
|
|
1288
|
+
ProjectionCheckpoint.maxPosition(db, ProjectionPending.Dcb),
|
|
1289
|
+
))
|
|
1290
|
+
|
|
1242
1291
|
// Create scheduler and populate platform context refs.
|
|
1243
1292
|
let scheduler = makeScheduler()
|
|
1244
1293
|
hooks.scheduler := Some(scheduler)
|
|
@@ -1328,9 +1377,34 @@ module MakeWithConfig = (
|
|
|
1328
1377
|
// Drive the admin Plugin read models via synthetic Connect dispatch through
|
|
1329
1378
|
// LocalPluginAggregate (replaces the old direct-write seed). The real
|
|
1330
1379
|
// PluginsProjection folds the emitted events into the "Plugins" QueryDb store.
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1380
|
+
//
|
|
1381
|
+
// Under SQLite this trio is sequenced AFTER the projection catch-up: the
|
|
1382
|
+
// catch-up replays PRIOR sessions' stored events (e.g. the admin plugin
|
|
1383
|
+
// lifecycle history) into the projections, and this session's Connect
|
|
1384
|
+
// events must fold on top of — never race and be overwritten by — that
|
|
1385
|
+
// replay. All three calls are internally fire-and-forget Output.apply
|
|
1386
|
+
// chains, so deferring their invocation changes no synchronous contract.
|
|
1387
|
+
let seedAdminStores = () => {
|
|
1388
|
+
connectPlugin(~pluginComponents=plugins)
|
|
1389
|
+
seedUIFragmentRegistryQueryDb(~pluginComponents=plugins)
|
|
1390
|
+
seedPluginStructuresStore(~pluginComponents=plugins)
|
|
1391
|
+
}
|
|
1392
|
+
switch projectionCatchup {
|
|
1393
|
+
| Some((db, upperBound, dcbUpperBound)) =>
|
|
1394
|
+
let _ =
|
|
1395
|
+
ProjectionCheckpoint.startupCatchup(~db, ~upperBound, ~dcbUpperBound, ~handlers=() =>
|
|
1396
|
+
Bus.projectionCatchupHandlers()
|
|
1397
|
+
)
|
|
1398
|
+
->Promise.catch(e => {
|
|
1399
|
+
Console.error2("[Platform] projection catch-up failed:", e)
|
|
1400
|
+
Promise.resolve()
|
|
1401
|
+
})
|
|
1402
|
+
->Promise.then(() => {
|
|
1403
|
+
seedAdminStores()
|
|
1404
|
+
Promise.resolve()
|
|
1405
|
+
})
|
|
1406
|
+
| None => seedAdminStores()
|
|
1407
|
+
}
|
|
1334
1408
|
|
|
1335
1409
|
// Seed the built-in admin plugin's structure so its Auto UI (Plugin list with
|
|
1336
1410
|
// Activate/Deactivate buttons) renders alongside the user plugins. Admin.construct
|
package/src/Platform.res.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
11
11
|
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
12
12
|
import * as Effect from "effect/Effect";
|
|
13
13
|
import * as Pulumi from "@pulumi/pulumi";
|
|
14
|
+
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
14
15
|
import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
|
|
15
16
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
16
17
|
import * as Plugin$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin.res.mjs";
|
|
@@ -45,12 +46,15 @@ import * as Scheduler_Builder$ReventlessCore from "@reventlessdev/reventless-cor
|
|
|
45
46
|
import * as Aggregate_Builder$ReventlessLocal from "./components/Aggregate_Builder.res.mjs";
|
|
46
47
|
import * as LocalClonerRunner$ReventlessLocal from "./adapter/Cloner/LocalClonerRunner.res.mjs";
|
|
47
48
|
import * as PluginBaseFragment$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/api/PluginBaseFragment.res.mjs";
|
|
49
|
+
import * as ProjectionPending$ReventlessLocal from "./adapter/ProjectionPending.res.mjs";
|
|
48
50
|
import * as ReadModel_Builder$ReventlessLocal from "./components/ReadModel_Builder.res.mjs";
|
|
49
51
|
import * as PlatformMCP_Server$ReventlessLocal from "./adapter/PlatformMCP_Server.res.mjs";
|
|
50
52
|
import * as Auth_GraphqlContext$ReventlessLocal from "./adapter/Auth/Auth_GraphqlContext.res.mjs";
|
|
51
53
|
import * as PluginsReadModelSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs";
|
|
52
54
|
import * as DomainGraphQL_Server$ReventlessLocal from "./adapter/DomainGraphQL_Server.res.mjs";
|
|
55
|
+
import * as EventPublish_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventLog/EventPublish_Callback.res.mjs";
|
|
53
56
|
import * as LocalGraphQL_Adapter$ReventlessLocal from "./adapter/Api/LocalGraphQL_Adapter.res.mjs";
|
|
57
|
+
import * as ProjectionCheckpoint$ReventlessLocal from "./adapter/ProjectionCheckpoint.res.mjs";
|
|
54
58
|
import * as ExtensionPointMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionPointMapping.res.mjs";
|
|
55
59
|
import * as EventLogStorage_Sqlite$ReventlessLocal from "./adapter/EventLog/EventLogStorage_Sqlite.res.mjs";
|
|
56
60
|
import * as ExtensionPoint_Builder$ReventlessLocal from "./components/ExtensionPoint_Builder.res.mjs";
|
|
@@ -93,6 +97,14 @@ let platformMCPRef = {
|
|
|
93
97
|
contents: undefined
|
|
94
98
|
};
|
|
95
99
|
|
|
100
|
+
function decodePluginEventEnvelope(eventJson) {
|
|
101
|
+
try {
|
|
102
|
+
return Stdlib_Option.map(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(eventJson), d => d["event"]), payload => S.parseJsonOrThrow(payload, PluginSpec$ReventlessCore.eventSchema));
|
|
103
|
+
} catch (exn) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
96
108
|
function MakeWithConfig(Config) {
|
|
97
109
|
TestRunner$ReventlessLocal.setup();
|
|
98
110
|
let match = Config.backend;
|
|
@@ -106,6 +118,12 @@ function MakeWithConfig(Config) {
|
|
|
106
118
|
let db = SqliteDriver$ReventlessLocal.openDb(path);
|
|
107
119
|
BackendState$ReventlessLocal.setSqlite(db, path);
|
|
108
120
|
LocalBus$ReventlessLocal.seedEventTapSeq(EventLogStorage_Sqlite$ReventlessLocal.countAll(db) + DcbEventLogStorage_Sqlite$ReventlessLocal.countAll(db) | 0);
|
|
121
|
+
ProjectionPending$ReventlessLocal.enableTracking();
|
|
122
|
+
EventPublish_Callback$ReventlessCore.registerAfterPublish(async publishedEvent => {
|
|
123
|
+
let metas = publishedEvent.metas;
|
|
124
|
+
let msgIds = metas !== undefined ? metas.map(m => m.msgId) : Stdlib_Array.filterMap(publishedEvent.eventsJson, json => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), d => d["msgId"]), Stdlib_JSON.Decode.string));
|
|
125
|
+
return ProjectionCheckpoint$ReventlessLocal.completePublished(db, msgIds);
|
|
126
|
+
});
|
|
109
127
|
}
|
|
110
128
|
let Bus = LocalBus$ReventlessLocal.Impl({
|
|
111
129
|
capacity: undefined,
|
|
@@ -637,6 +655,7 @@ function MakeWithConfig(Config) {
|
|
|
637
655
|
initialState: PluginBehavior$ReventlessCore.initialState,
|
|
638
656
|
evolve: PluginBehavior$ReventlessCore.evolve,
|
|
639
657
|
decide: PluginBehavior$ReventlessCore.decide,
|
|
658
|
+
snapshot: undefined,
|
|
640
659
|
moduleUrl: PluginBehavior$ReventlessCore.moduleUrl
|
|
641
660
|
})(NoEventMappings$ReventlessInfra.Make({
|
|
642
661
|
name: PluginSpec$ReventlessCore.name,
|
|
@@ -787,33 +806,31 @@ function MakeWithConfig(Config) {
|
|
|
787
806
|
]
|
|
788
807
|
]));
|
|
789
808
|
Bus.subscribeToEvents(pluginEventTopicKey, async (_service, _meta, eventJson) => {
|
|
790
|
-
let
|
|
791
|
-
|
|
792
|
-
def = S.convertOrThrow(eventJson, PluginSpec$ReventlessCore.eventSchema);
|
|
793
|
-
} catch (exn) {
|
|
809
|
+
let match = decodePluginEventEnvelope(eventJson);
|
|
810
|
+
if (match === undefined) {
|
|
794
811
|
return;
|
|
795
812
|
}
|
|
796
|
-
switch (
|
|
813
|
+
switch (match.TAG) {
|
|
797
814
|
case "VersionDisconnected" :
|
|
798
|
-
return publishStatus(
|
|
815
|
+
return publishStatus(match._0.name, "Disconnected");
|
|
799
816
|
case "VersionDeactivated" :
|
|
800
|
-
return publishStatus(
|
|
817
|
+
return publishStatus(match._0.name, "Inactive");
|
|
801
818
|
case "VersionRetired" :
|
|
802
|
-
return publishStatus(
|
|
819
|
+
return publishStatus(match._0.name, "Retired");
|
|
803
820
|
case "VersionDetected" :
|
|
804
821
|
case "VersionSuperseded" :
|
|
805
822
|
case "IncompatiblePluginDetected" :
|
|
806
823
|
return;
|
|
807
824
|
case "UIFragmentRegistered" :
|
|
808
|
-
let data =
|
|
825
|
+
let data = match._0;
|
|
809
826
|
return publishUIFragment(Plugin$ReventlessCore.name(data.pluginId), "Registered", S.reverseConvertToJsonOrThrow(data.manifest, Plugin$Reventless.uiFragmentManifestSchema));
|
|
810
827
|
case "UIFragmentUpdated" :
|
|
811
|
-
let data$1 =
|
|
828
|
+
let data$1 = match._0;
|
|
812
829
|
return publishUIFragment(Plugin$ReventlessCore.name(data$1.pluginId), "Updated", S.reverseConvertToJsonOrThrow(data$1.newManifest, Plugin$Reventless.uiFragmentManifestSchema));
|
|
813
830
|
case "UIFragmentDeregistered" :
|
|
814
|
-
return publishUIFragment(Plugin$ReventlessCore.name(
|
|
831
|
+
return publishUIFragment(Plugin$ReventlessCore.name(match._0.pluginId), "Deregistered", null);
|
|
815
832
|
default:
|
|
816
|
-
return publishStatus(
|
|
833
|
+
return publishStatus(match._0.name, "Connected");
|
|
817
834
|
}
|
|
818
835
|
});
|
|
819
836
|
};
|
|
@@ -1081,6 +1098,11 @@ function MakeWithConfig(Config) {
|
|
|
1081
1098
|
match.resetOnStart ? ", resetOnStart" : ""
|
|
1082
1099
|
) + `)`;
|
|
1083
1100
|
log.info("Platform", undefined, tmp);
|
|
1101
|
+
let projectionCatchup = Stdlib_Option.map(BackendState$ReventlessLocal.getDb(), db => [
|
|
1102
|
+
db,
|
|
1103
|
+
ProjectionCheckpoint$ReventlessLocal.maxPosition(db, "Aggregate"),
|
|
1104
|
+
ProjectionCheckpoint$ReventlessLocal.maxPosition(db, "Dcb")
|
|
1105
|
+
]);
|
|
1084
1106
|
let scheduler = makeScheduler();
|
|
1085
1107
|
hooks_scheduler.contents = scheduler;
|
|
1086
1108
|
hooks_api.contents = {
|
|
@@ -1139,9 +1161,22 @@ function MakeWithConfig(Config) {
|
|
|
1139
1161
|
adminResources: []
|
|
1140
1162
|
});
|
|
1141
1163
|
subscribeToPluginEvents();
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1164
|
+
let seedAdminStores = () => {
|
|
1165
|
+
connectPlugin(plugins$1);
|
|
1166
|
+
seedUIFragmentRegistryQueryDb(plugins$1);
|
|
1167
|
+
seedPluginStructuresStore(plugins$1);
|
|
1168
|
+
};
|
|
1169
|
+
if (projectionCatchup !== undefined) {
|
|
1170
|
+
Stdlib_Promise.$$catch(ProjectionCheckpoint$ReventlessLocal.startupCatchup(projectionCatchup[0], projectionCatchup[1], projectionCatchup[2], () => Bus.projectionCatchupHandlers()), e => {
|
|
1171
|
+
console.error("[Platform] projection catch-up failed:", e);
|
|
1172
|
+
return Promise.resolve();
|
|
1173
|
+
}).then(() => {
|
|
1174
|
+
seedAdminStores();
|
|
1175
|
+
return Promise.resolve();
|
|
1176
|
+
});
|
|
1177
|
+
} else {
|
|
1178
|
+
seedAdminStores();
|
|
1179
|
+
}
|
|
1145
1180
|
pluginStructuresStore.contents[Platform_Admin_Structure$ReventlessCore.pluginId] = Platform_Admin_Structure$ReventlessCore.structure;
|
|
1146
1181
|
CommandGeneratorResolvers_GraphQL$ReventlessLocal.setPluginStatusGate(field => {
|
|
1147
1182
|
let parts = field.split("_");
|
|
@@ -1698,6 +1733,12 @@ function Make($star) {
|
|
|
1698
1733
|
let db = SqliteDriver$ReventlessLocal.openDb(path);
|
|
1699
1734
|
BackendState$ReventlessLocal.setSqlite(db, path);
|
|
1700
1735
|
LocalBus$ReventlessLocal.seedEventTapSeq(EventLogStorage_Sqlite$ReventlessLocal.countAll(db) + DcbEventLogStorage_Sqlite$ReventlessLocal.countAll(db) | 0);
|
|
1736
|
+
ProjectionPending$ReventlessLocal.enableTracking();
|
|
1737
|
+
EventPublish_Callback$ReventlessCore.registerAfterPublish(async publishedEvent => {
|
|
1738
|
+
let metas = publishedEvent.metas;
|
|
1739
|
+
let msgIds = metas !== undefined ? metas.map(m => m.msgId) : Stdlib_Array.filterMap(publishedEvent.eventsJson, json => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), d => d["msgId"]), Stdlib_JSON.Decode.string));
|
|
1740
|
+
return ProjectionCheckpoint$ReventlessLocal.completePublished(db, msgIds);
|
|
1741
|
+
});
|
|
1701
1742
|
}
|
|
1702
1743
|
let Bus = LocalBus$ReventlessLocal.Impl({
|
|
1703
1744
|
capacity: undefined,
|
|
@@ -2222,6 +2263,7 @@ function Make($star) {
|
|
|
2222
2263
|
initialState: PluginBehavior$ReventlessCore.initialState,
|
|
2223
2264
|
evolve: PluginBehavior$ReventlessCore.evolve,
|
|
2224
2265
|
decide: PluginBehavior$ReventlessCore.decide,
|
|
2266
|
+
snapshot: undefined,
|
|
2225
2267
|
moduleUrl: PluginBehavior$ReventlessCore.moduleUrl
|
|
2226
2268
|
})(NoEventMappings$ReventlessInfra.Make({
|
|
2227
2269
|
name: PluginSpec$ReventlessCore.name,
|
|
@@ -2372,33 +2414,31 @@ function Make($star) {
|
|
|
2372
2414
|
]
|
|
2373
2415
|
]));
|
|
2374
2416
|
Bus.subscribeToEvents(pluginEventTopicKey, async (_service, _meta, eventJson) => {
|
|
2375
|
-
let
|
|
2376
|
-
|
|
2377
|
-
def = S.convertOrThrow(eventJson, PluginSpec$ReventlessCore.eventSchema);
|
|
2378
|
-
} catch (exn) {
|
|
2417
|
+
let match = decodePluginEventEnvelope(eventJson);
|
|
2418
|
+
if (match === undefined) {
|
|
2379
2419
|
return;
|
|
2380
2420
|
}
|
|
2381
|
-
switch (
|
|
2421
|
+
switch (match.TAG) {
|
|
2382
2422
|
case "VersionDisconnected" :
|
|
2383
|
-
return publishStatus(
|
|
2423
|
+
return publishStatus(match._0.name, "Disconnected");
|
|
2384
2424
|
case "VersionDeactivated" :
|
|
2385
|
-
return publishStatus(
|
|
2425
|
+
return publishStatus(match._0.name, "Inactive");
|
|
2386
2426
|
case "VersionRetired" :
|
|
2387
|
-
return publishStatus(
|
|
2427
|
+
return publishStatus(match._0.name, "Retired");
|
|
2388
2428
|
case "VersionDetected" :
|
|
2389
2429
|
case "VersionSuperseded" :
|
|
2390
2430
|
case "IncompatiblePluginDetected" :
|
|
2391
2431
|
return;
|
|
2392
2432
|
case "UIFragmentRegistered" :
|
|
2393
|
-
let data =
|
|
2433
|
+
let data = match._0;
|
|
2394
2434
|
return publishUIFragment(Plugin$ReventlessCore.name(data.pluginId), "Registered", S.reverseConvertToJsonOrThrow(data.manifest, Plugin$Reventless.uiFragmentManifestSchema));
|
|
2395
2435
|
case "UIFragmentUpdated" :
|
|
2396
|
-
let data$1 =
|
|
2436
|
+
let data$1 = match._0;
|
|
2397
2437
|
return publishUIFragment(Plugin$ReventlessCore.name(data$1.pluginId), "Updated", S.reverseConvertToJsonOrThrow(data$1.newManifest, Plugin$Reventless.uiFragmentManifestSchema));
|
|
2398
2438
|
case "UIFragmentDeregistered" :
|
|
2399
|
-
return publishUIFragment(Plugin$ReventlessCore.name(
|
|
2439
|
+
return publishUIFragment(Plugin$ReventlessCore.name(match._0.pluginId), "Deregistered", null);
|
|
2400
2440
|
default:
|
|
2401
|
-
return publishStatus(
|
|
2441
|
+
return publishStatus(match._0.name, "Connected");
|
|
2402
2442
|
}
|
|
2403
2443
|
});
|
|
2404
2444
|
};
|
|
@@ -2661,6 +2701,11 @@ function Make($star) {
|
|
|
2661
2701
|
backend.resetOnStart ? ", resetOnStart" : ""
|
|
2662
2702
|
) + `)`;
|
|
2663
2703
|
log.info("Platform", undefined, tmp);
|
|
2704
|
+
let projectionCatchup = Stdlib_Option.map(BackendState$ReventlessLocal.getDb(), db => [
|
|
2705
|
+
db,
|
|
2706
|
+
ProjectionCheckpoint$ReventlessLocal.maxPosition(db, "Aggregate"),
|
|
2707
|
+
ProjectionCheckpoint$ReventlessLocal.maxPosition(db, "Dcb")
|
|
2708
|
+
]);
|
|
2664
2709
|
let scheduler = makeScheduler();
|
|
2665
2710
|
hooks_scheduler.contents = scheduler;
|
|
2666
2711
|
hooks_api.contents = {
|
|
@@ -2719,9 +2764,22 @@ function Make($star) {
|
|
|
2719
2764
|
adminResources: []
|
|
2720
2765
|
});
|
|
2721
2766
|
subscribeToPluginEvents();
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2767
|
+
let seedAdminStores = () => {
|
|
2768
|
+
connectPlugin(plugins$1);
|
|
2769
|
+
seedUIFragmentRegistryQueryDb(plugins$1);
|
|
2770
|
+
seedPluginStructuresStore(plugins$1);
|
|
2771
|
+
};
|
|
2772
|
+
if (projectionCatchup !== undefined) {
|
|
2773
|
+
Stdlib_Promise.$$catch(ProjectionCheckpoint$ReventlessLocal.startupCatchup(projectionCatchup[0], projectionCatchup[1], projectionCatchup[2], () => Bus.projectionCatchupHandlers()), e => {
|
|
2774
|
+
console.error("[Platform] projection catch-up failed:", e);
|
|
2775
|
+
return Promise.resolve();
|
|
2776
|
+
}).then(() => {
|
|
2777
|
+
seedAdminStores();
|
|
2778
|
+
return Promise.resolve();
|
|
2779
|
+
});
|
|
2780
|
+
} else {
|
|
2781
|
+
seedAdminStores();
|
|
2782
|
+
}
|
|
2725
2783
|
pluginStructuresStore.contents[Platform_Admin_Structure$ReventlessCore.pluginId] = Platform_Admin_Structure$ReventlessCore.structure;
|
|
2726
2784
|
CommandGeneratorResolvers_GraphQL$ReventlessLocal.setPluginStatusGate(field => {
|
|
2727
2785
|
let parts = field.split("_");
|
|
@@ -3256,6 +3314,7 @@ export {
|
|
|
3256
3314
|
platformGraphQLRef,
|
|
3257
3315
|
getPlatformGraphQL,
|
|
3258
3316
|
platformMCPRef,
|
|
3317
|
+
decodePluginEventEnvelope,
|
|
3259
3318
|
MakeWithConfig,
|
|
3260
3319
|
Make,
|
|
3261
3320
|
}
|
|
@@ -25,18 +25,108 @@ let matchesQuery = (event: DcbEventLog_Adapter.rawSequencedEvent, query: Reventl
|
|
|
25
25
|
})
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Composite key for the per-(key,value) tag posting list. NUL can't appear in a
|
|
29
|
+
// tag key or value, so it's an unambiguous separator.
|
|
30
|
+
let tagPostingKey = (key: string, value: string) => key ++ "\x00" ++ value
|
|
31
|
+
|
|
32
|
+
// Intersect two ascending, duplicate-free index arrays (two-pointer merge).
|
|
33
|
+
let intersectSorted = (a: array<int>, b: array<int>): array<int> => {
|
|
34
|
+
let out = []
|
|
35
|
+
let i = ref(0)
|
|
36
|
+
let j = ref(0)
|
|
37
|
+
while i.contents < a->Array.length && j.contents < b->Array.length {
|
|
38
|
+
let av = a->Array.getUnsafe(i.contents)
|
|
39
|
+
let bv = b->Array.getUnsafe(j.contents)
|
|
40
|
+
if av == bv {
|
|
41
|
+
out->Array.push(av)
|
|
42
|
+
i := i.contents + 1
|
|
43
|
+
j := j.contents + 1
|
|
44
|
+
} else if av < bv {
|
|
45
|
+
i := i.contents + 1
|
|
46
|
+
} else {
|
|
47
|
+
j := j.contents + 1
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
out
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
let makeStorage = (~name as _name, ~indexes as _, ~partitionTag as _, ~opts as _) => {
|
|
29
54
|
let events: ref<array<DcbEventLog_Adapter.rawSequencedEvent>> = ref([])
|
|
30
55
|
let position = ref(0)
|
|
56
|
+
// Posting lists: tag "key\0value" / eventType → ascending array indices into
|
|
57
|
+
// `events`. Events are only ever appended (never removed) in position order, so
|
|
58
|
+
// every list stays sorted and duplicate-free by construction. They turn the
|
|
59
|
+
// per-read / per-conditional-append full scan (O(n) → O(n²) over a session)
|
|
60
|
+
// into a lookup + intersection over the (usually tiny) candidate set.
|
|
61
|
+
let byTag: Dict.t<array<int>> = Dict.make()
|
|
62
|
+
let byType: Dict.t<array<int>> = Dict.make()
|
|
63
|
+
let pushInto = (dict: Dict.t<array<int>>, key: string, idx: int) =>
|
|
64
|
+
switch dict->Dict.get(key) {
|
|
65
|
+
| Some(list) => list->Array.push(idx)
|
|
66
|
+
| None => dict->Dict.set(key, [idx])
|
|
67
|
+
}
|
|
68
|
+
let indexEvent = (idx: int, ev: DcbEventLog_Adapter.rawSequencedEvent) => {
|
|
69
|
+
pushInto(byType, ev.eventType, idx)
|
|
70
|
+
ev.tags->Array.forEach(t => pushInto(byTag, tagPostingKey(t.key, t.value), idx))
|
|
71
|
+
}
|
|
31
72
|
|
|
32
|
-
|
|
33
|
-
|
|
73
|
+
// Ascending, duplicate-free superset of the indices that could satisfy the
|
|
74
|
+
// query (an OR of clauses). Narrowing only — the authoritative `matchesQuery`
|
|
75
|
+
// predicate is still applied to each candidate, so the result is identical to
|
|
76
|
+
// the old linear scan even if this over-includes. Callers guarantee query≠[].
|
|
77
|
+
let candidateIndices = (query: Reventless.DcbTag.query): array<int> => {
|
|
78
|
+
let acc: Dict.t<int> = Dict.make()
|
|
79
|
+
let addAll = (arr: array<int>) => arr->Array.forEach(i => acc->Dict.set(Int.toString(i), i))
|
|
80
|
+
query->Array.forEach(clause => {
|
|
81
|
+
switch clause.tags {
|
|
82
|
+
| Some(tags) if tags->Array.length > 0 =>
|
|
83
|
+
// Events carrying ALL of the clause's tags = intersection of the tags'
|
|
84
|
+
// posting lists; a missing tag ⇒ empty ⇒ this clause adds nothing.
|
|
85
|
+
let lists = tags->Array.map(t =>
|
|
86
|
+
byTag->Dict.get(tagPostingKey(t.key, t.value))->Option.getOr([])
|
|
87
|
+
)
|
|
88
|
+
if lists->Array.some(l => l->Array.length == 0) {
|
|
89
|
+
()
|
|
90
|
+
} else {
|
|
91
|
+
let sorted = lists->Array.toSorted((a, b) => Int.toFloat(a->Array.length - b->Array.length))
|
|
92
|
+
let acc0 = sorted->Array.getUnsafe(0)
|
|
93
|
+
let folded =
|
|
94
|
+
sorted->Array.slice(~start=1, ~end=sorted->Array.length)->Array.reduce(acc0, intersectSorted)
|
|
95
|
+
addAll(folded)
|
|
96
|
+
}
|
|
97
|
+
| _ =>
|
|
98
|
+
// No tag constraint: narrow by event type when present (union of the
|
|
99
|
+
// types' posting lists — disjoint since an event has one type), else the
|
|
100
|
+
// clause is unconstrained and matches everything.
|
|
101
|
+
switch clause.eventTypes {
|
|
102
|
+
| Some(types) => types->Array.forEach(t => addAll(byType->Dict.get(t)->Option.getOr([])))
|
|
103
|
+
| None => events.contents->Array.forEachWithIndex((_, i) => acc->Dict.set(Int.toString(i), i))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
acc->Dict.valuesToArray->Array.toSorted((a, b) => Int.toFloat(a - b))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let matchingEvents = (~query, ~after): array<DcbEventLog_Adapter.rawSequencedEvent> => {
|
|
111
|
+
let pass = (ev: DcbEventLog_Adapter.rawSequencedEvent) => {
|
|
34
112
|
let afterMatch = switch after {
|
|
35
|
-
| Some(afterPos) =>
|
|
113
|
+
| Some(afterPos) => ev.position->posToInt > afterPos->posToInt
|
|
36
114
|
| None => true
|
|
37
115
|
}
|
|
38
|
-
afterMatch && matchesQuery(
|
|
39
|
-
}
|
|
116
|
+
afterMatch && matchesQuery(ev, query)
|
|
117
|
+
}
|
|
118
|
+
if query->Array.length == 0 {
|
|
119
|
+
events.contents->Array.filter(pass)
|
|
120
|
+
} else {
|
|
121
|
+
candidateIndices(query)->Array.filterMap(i => {
|
|
122
|
+
let ev = events.contents->Array.getUnsafe(i)
|
|
123
|
+
pass(ev) ? Some(ev) : None
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let read = async (~query, ~after=?) => {
|
|
129
|
+
let filtered = matchingEvents(~query, ~after)
|
|
40
130
|
let headPosition =
|
|
41
131
|
events.contents
|
|
42
132
|
->Array.get(events.contents->Array.length - 1)
|
|
@@ -50,30 +140,27 @@ let makeStorage = (~name as _name, ~indexes as _, ~partitionTag as _, ~opts as _
|
|
|
50
140
|
let append = async (newEvents, ~condition=?) => {
|
|
51
141
|
let conflictDetected = switch condition {
|
|
52
142
|
| Some(cond: Reventless.DcbTag.appendCondition) =>
|
|
53
|
-
|
|
54
|
-
let afterMatch = switch cond.after {
|
|
55
|
-
| Some(pos) => event.position->posToInt > pos->posToInt
|
|
56
|
-
| None => true
|
|
57
|
-
}
|
|
58
|
-
afterMatch && matchesQuery(event, cond.query)
|
|
59
|
-
})
|
|
143
|
+
matchingEvents(~query=cond.query, ~after=cond.after)->Array.length > 0
|
|
60
144
|
| None => false
|
|
61
145
|
}
|
|
62
146
|
if conflictDetected {
|
|
63
147
|
Error("conflict: condition check failed")
|
|
64
148
|
} else {
|
|
149
|
+
let base = events.contents->Array.length
|
|
65
150
|
let storedEvents =
|
|
66
|
-
newEvents->Array.
|
|
151
|
+
newEvents->Array.mapWithIndex((event: DcbEventLog_Adapter.rawStoredEvent, j) => {
|
|
67
152
|
position := position.contents + 1
|
|
68
153
|
let pos = position.contents->Int.toString
|
|
69
|
-
{
|
|
70
|
-
|
|
154
|
+
let stored: DcbEventLog_Adapter.rawSequencedEvent = {
|
|
155
|
+
position: pos,
|
|
71
156
|
eventType: event.eventType,
|
|
72
157
|
data: event.data,
|
|
73
158
|
tags: event.tags,
|
|
74
159
|
meta: event.meta,
|
|
75
160
|
recordedAt: Message.nowAsISOString(),
|
|
76
161
|
}
|
|
162
|
+
indexEvent(base + j, stored)
|
|
163
|
+
stored
|
|
77
164
|
})
|
|
78
165
|
events := events.contents->Array.concat(storedEvents)
|
|
79
166
|
Ok(position.contents->Int.toString)
|
|
@@ -113,8 +200,6 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
113
200
|
| Some(db) =>
|
|
114
201
|
let (storageName, read, storage) = DcbEventLogStorage_Sqlite.makeStorage(
|
|
115
202
|
~db,
|
|
116
|
-
~bus=(_, _) => (),
|
|
117
|
-
~publishToTopic=(~topicName as _, ~json as _) => (),
|
|
118
203
|
~name,
|
|
119
204
|
~indexes,
|
|
120
205
|
~partitionTag,
|