@reventlessdev/reventless-local 3.0.0-alpha.163 → 3.0.0-alpha.165
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 +22 -0
- package/package.json +7 -7
- package/src/Platform.res +25 -22
- package/src/Platform.res.mjs +33 -31
- package/src/adapter/EventLog/LocalEventLogStorage.res +1 -1
- package/src/adapter/EventLog/LocalEventLogStorage.res.mjs +1 -1
- package/tests/PluginEventDecodeTest.res +6 -1
- package/tests/PluginEventDecodeTest.res.mjs +3 -1
- package/tests/SplitApiFixtures.res +1 -1
- package/tests/SplitApiFixtures.res.mjs +2 -2
- package/tests/adapter/DcbEventLogStorageSqliteTest.res +33 -0
- package/tests/adapter/DcbEventLogStorageSqliteTest.res.mjs +51 -0
- package/tests/adapter/DcbEventLogStorageTest.res +26 -0
- package/tests/adapter/DcbEventLogStorageTest.res.mjs +43 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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.165 (2026-07-22)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** keep an empty DCB tag value from breaking the append ([1c0ab40](https://github.com/ReventlessDev/reventless-core/commit/1c0ab40f25b596702a81f42686d77c5e423e9b44))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.164 (2026-07-22)
|
|
14
|
+
|
|
15
|
+
* refactor!: retire the stale "Admin" vocabulary for platform-owned resources ([2770bd9](https://github.com/ReventlessDev/reventless-core/commit/2770bd96f309142bdfc65b9f36bd1a3ef60641bd))
|
|
16
|
+
|
|
17
|
+
### BREAKING CHANGES
|
|
18
|
+
|
|
19
|
+
* platform-owned resources are renamed Admin* -> Platform*
|
|
20
|
+
(PlatformDcbEventLog, PlatformDcbCmdTopic, PlatformEventColl). This replaces
|
|
21
|
+
a DynamoDB table, an SQS queue and an SNS topic — wipe and redeploy rather
|
|
22
|
+
than migrate. Both alpha AdminDcbEventLog tables held 0 items at the time of
|
|
23
|
+
the rename. MCP resource URI templates change from Admin/<field>/{id} to
|
|
24
|
+
Platform/<field>/{id}; MCP tool names are unaffected.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
# 3.0.0-alpha.163 (2026-07-22)
|
|
7
29
|
|
|
8
30
|
* refactor!: retire the stale "core" vocabulary for platform things ([34e7480](https://github.com/ReventlessDev/reventless-core/commit/34e7480992bd58906a250b0a1ce6ff2c5ba45260))
|
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.165",
|
|
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": {
|
|
@@ -31,17 +31,17 @@
|
|
|
31
31
|
"graphql-yoga": "^5.21.0",
|
|
32
32
|
"sury": "11.0.0-alpha.4",
|
|
33
33
|
"ws": "^8.18.0",
|
|
34
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.30",
|
|
35
34
|
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.24",
|
|
35
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.30",
|
|
36
36
|
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.20",
|
|
37
37
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.9",
|
|
38
38
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
|
|
39
|
-
"@reventlessdev/reventless-
|
|
40
|
-
"@reventlessdev/reventless-
|
|
41
|
-
"@reventlessdev/reventless-gwt": "1.0.0-alpha.
|
|
39
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.178",
|
|
40
|
+
"@reventlessdev/reventless-graphql-server": "1.0.0-alpha.25",
|
|
41
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.125",
|
|
42
42
|
"@reventlessdev/reventless-infra": "3.0.0-alpha.104",
|
|
43
|
-
"@reventlessdev/reventless-
|
|
44
|
-
"@reventlessdev/reventless-
|
|
43
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.80",
|
|
44
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.42"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"rescript": "12.3.0",
|
package/src/Platform.res
CHANGED
|
@@ -875,10 +875,10 @@ module MakeWithConfig = (
|
|
|
875
875
|
|
|
876
876
|
// Bus key for the shared admin DCB command topic, derived exactly as
|
|
877
877
|
// Dcb_Builder + CommandTopic_Builder name the channel:
|
|
878
|
-
//
|
|
878
|
+
// pluginId -> +"Dcb" -> +CmdTopic ("PlatformDcbCmdTopic").
|
|
879
879
|
let adminDcbCmdTopicKey = {
|
|
880
880
|
module CT = ReventlessCore.ComponentType
|
|
881
|
-
"
|
|
881
|
+
(ReventlessCore.Platform_Admin_Structure.pluginId ++ "Dcb")->CT.name(CT.CommandTopic)
|
|
882
882
|
}
|
|
883
883
|
|
|
884
884
|
// Dispatch a UiFragmentRegistry slice command in-process via the shared admin
|
|
@@ -909,15 +909,18 @@ module MakeWithConfig = (
|
|
|
909
909
|
let uiFragmentSubTopic = "onUIFragmentChange"
|
|
910
910
|
|
|
911
911
|
// Bus key + service identity of the admin DcbEventLog's event topic. The
|
|
912
|
-
// Platform_Admin construct name is
|
|
913
|
-
// to EventTopic_Builder, whose publisher
|
|
914
|
-
// and DcbEventLog_Operations stamps every
|
|
915
|
-
// `<name>DcbEventLog`.
|
|
912
|
+
// Platform_Admin construct name is `Platform_Admin_Structure.pluginId`;
|
|
913
|
+
// DcbEventLog_Builder hands that name to EventTopic_Builder, whose publisher
|
|
914
|
+
// registers under `<name>EventTopic`, and DcbEventLog_Operations stamps every
|
|
915
|
+
// published event's meta.service with `<name>DcbEventLog`.
|
|
916
916
|
let adminDcbEventTopicKey = {
|
|
917
917
|
module CT = ReventlessCore.ComponentType
|
|
918
|
-
|
|
918
|
+
ReventlessCore.Platform_Admin_Structure.pluginId->CT.name(CT.EventTopic)
|
|
919
|
+
}
|
|
920
|
+
let adminDcbServiceName = {
|
|
921
|
+
module CT = ReventlessCore.ComponentType
|
|
922
|
+
ReventlessCore.Platform_Admin_Structure.pluginId->CT.name(CT.DcbEventLog)
|
|
919
923
|
}
|
|
920
|
-
let adminDcbServiceName = "AdminDcbEventLog"
|
|
921
924
|
|
|
922
925
|
// Relocated Source-C emission: subscribe to the Plugin aggregate's event topic
|
|
923
926
|
// (lifecycle status) and the admin DcbEventLog event topic (UiFragmentRegistry
|
|
@@ -1538,14 +1541,14 @@ module MakeWithConfig = (
|
|
|
1538
1541
|
let adminQueryEntry = ReventlessCore.PluginBaseFragment.queryEntries->Array.getUnsafe(0)
|
|
1539
1542
|
let singleQueryField = adminQueryEntry.singleFieldName
|
|
1540
1543
|
let listQueryField = adminQueryEntry.listFieldName
|
|
1541
|
-
let adminMutationEntries = ReventlessCore.
|
|
1544
|
+
let adminMutationEntries = ReventlessCore.Platform_AdminApi.mutationEntries(~cloner=Config.cloner)
|
|
1542
1545
|
let adminMutationFieldNames = adminMutationEntries->Array.flatMap(entry => entry.fieldNames)
|
|
1543
1546
|
|
|
1544
1547
|
// Register the admin Plugin aggregate's types/queries/mutations to the platform target.
|
|
1545
1548
|
let platformGraphQL = resolveTargetGraphQL()
|
|
1546
1549
|
let platformMCP = resolveTargetMCP()
|
|
1547
1550
|
let baseParts = ReventlessCore.GraphQL_Stitcher.decode(
|
|
1548
|
-
ReventlessCore.
|
|
1551
|
+
ReventlessCore.Platform_AdminApi.baseFragment(~cloner=Config.cloner),
|
|
1549
1552
|
)
|
|
1550
1553
|
platformGraphQL.registerTypes(~sdlTypes=baseParts.types)
|
|
1551
1554
|
|
|
@@ -1576,7 +1579,7 @@ module MakeWithConfig = (
|
|
|
1576
1579
|
connectionResponse(items)
|
|
1577
1580
|
})
|
|
1578
1581
|
// Platform_ComponentDefinitions resolver — SDL is already stitched into baseParts via
|
|
1579
|
-
//
|
|
1582
|
+
// Platform_AdminApi.baseFragment so we register only the resolver here. Encoder is shared
|
|
1580
1583
|
// with the AWS adapter so responses are byte-identical.
|
|
1581
1584
|
//
|
|
1582
1585
|
// Filter by plugin lifecycle status to mirror the AWS DynamoDB filter in
|
|
@@ -1734,9 +1737,9 @@ module MakeWithConfig = (
|
|
|
1734
1737
|
}
|
|
1735
1738
|
switch version {
|
|
1736
1739
|
| Some(version) =>
|
|
1737
|
-
log.info(~comp=
|
|
1740
|
+
log.info(~comp=ReventlessCore.Platform_Admin_Structure.pluginId, `${field}(${pluginName}@${version}): dispatching to aggregate`)
|
|
1738
1741
|
let _ = dispatchPluginCommand(~pluginName, ~command=makeCommand(version))
|
|
1739
|
-
| None => log.warn(~comp=
|
|
1742
|
+
| None => log.warn(~comp=ReventlessCore.Platform_Admin_Structure.pluginId, `${field}(${pluginName}): plugin not found`)
|
|
1740
1743
|
}
|
|
1741
1744
|
commandAccepted(~msgId, ~entityId=pluginName)
|
|
1742
1745
|
}
|
|
@@ -1805,8 +1808,8 @@ module MakeWithConfig = (
|
|
|
1805
1808
|
// The SDL field constants are provider-neutral, shared with the AWS path.
|
|
1806
1809
|
platformGraphQL.registerSubscriptions(
|
|
1807
1810
|
~sdlFields=[
|
|
1808
|
-
ReventlessCore.
|
|
1809
|
-
ReventlessCore.
|
|
1811
|
+
ReventlessCore.Platform_AdminApi.uiFragmentSubscriptionField,
|
|
1812
|
+
ReventlessCore.Platform_AdminApi.pluginStatusSubscriptionField,
|
|
1810
1813
|
],
|
|
1811
1814
|
~resolvers=Dict.fromArray([
|
|
1812
1815
|
(
|
|
@@ -1831,7 +1834,7 @@ module MakeWithConfig = (
|
|
|
1831
1834
|
(listQueryField, pluginQueryDbName),
|
|
1832
1835
|
])
|
|
1833
1836
|
platformMCP.registerResourcesFromEntries(
|
|
1834
|
-
~pluginName=
|
|
1837
|
+
~pluginName=ReventlessCore.Platform_Admin_Structure.pluginId,
|
|
1835
1838
|
~queryEntries=ReventlessCore.PluginBaseFragment.queryEntries,
|
|
1836
1839
|
~queryHandler=async (resourceName, uri) => {
|
|
1837
1840
|
let segments = uri->String.split("/")
|
|
@@ -1859,7 +1862,7 @@ module MakeWithConfig = (
|
|
|
1859
1862
|
|
|
1860
1863
|
// Register admin mutations as MCP tools using the same entry-based path as plugins.
|
|
1861
1864
|
platformMCP.registerToolsFromEntries(
|
|
1862
|
-
~pluginName=
|
|
1865
|
+
~pluginName=ReventlessCore.Platform_Admin_Structure.pluginId,
|
|
1863
1866
|
~mutationEntries=adminMutationEntries,
|
|
1864
1867
|
~commandHandler=async (toolName, args, _identity) => {
|
|
1865
1868
|
switch platformGraphQL.getMutationResolver(toolName) {
|
|
@@ -1939,7 +1942,7 @@ module MakeWithConfig = (
|
|
|
1939
1942
|
let adminMCP = resolveTargetMCP()
|
|
1940
1943
|
|
|
1941
1944
|
let baseParts = ReventlessCore.GraphQL_Stitcher.decode(
|
|
1942
|
-
ReventlessCore.
|
|
1945
|
+
ReventlessCore.Platform_AdminApi.baseFragment(~cloner=Config.cloner),
|
|
1943
1946
|
)
|
|
1944
1947
|
adminGraphQL.registerTypes(~sdlTypes=baseParts.types)
|
|
1945
1948
|
|
|
@@ -1965,7 +1968,7 @@ module MakeWithConfig = (
|
|
|
1965
1968
|
adminGraphQL.registerQueries(~sdlFields=baseParts.queries, ~resolvers=queryResolvers)
|
|
1966
1969
|
|
|
1967
1970
|
let mutationResolvers = Dict.make()
|
|
1968
|
-
let adminMutationEntries = ReventlessCore.
|
|
1971
|
+
let adminMutationEntries = ReventlessCore.Platform_AdminApi.mutationEntries(~cloner=Config.cloner)
|
|
1969
1972
|
let adminMutationFieldNames = adminMutationEntries->Array.flatMap(entry => entry.fieldNames)
|
|
1970
1973
|
adminMutationFieldNames->Array.forEach(field =>
|
|
1971
1974
|
mutationResolvers->Dict.set(field, async (_root, _args, _ctx): JSON.t =>
|
|
@@ -2125,7 +2128,7 @@ module MakeWithConfig = (
|
|
|
2125
2128
|
let adminGraphQL = resolveTargetGraphQL()
|
|
2126
2129
|
if !(adminRegisteredServers.contents->Array.some(s => s === adminGraphQL)) {
|
|
2127
2130
|
let baseParts = ReventlessCore.GraphQL_Stitcher.decode(
|
|
2128
|
-
ReventlessCore.
|
|
2131
|
+
ReventlessCore.Platform_AdminApi.baseFragment(~cloner=Config.cloner),
|
|
2129
2132
|
)
|
|
2130
2133
|
adminGraphQL.registerTypes(~sdlTypes=baseParts.types)
|
|
2131
2134
|
|
|
@@ -2159,7 +2162,7 @@ module MakeWithConfig = (
|
|
|
2159
2162
|
},
|
|
2160
2163
|
)
|
|
2161
2164
|
// Platform_ComponentDefinitions resolver — SDL is already stitched into baseParts via
|
|
2162
|
-
//
|
|
2165
|
+
// Platform_AdminApi.baseFragment so we register only the resolver here. Uses the shared
|
|
2163
2166
|
// encoder so the dynamic-plugin admin server emits the same canonical shape as
|
|
2164
2167
|
// the main platform server (and as AWS).
|
|
2165
2168
|
queryResolvers->Dict.set(
|
|
@@ -2178,7 +2181,7 @@ module MakeWithConfig = (
|
|
|
2178
2181
|
adminGraphQL.registerQueries(~sdlFields=baseParts.queries, ~resolvers=queryResolvers)
|
|
2179
2182
|
|
|
2180
2183
|
let mutationResolvers = Dict.make()
|
|
2181
|
-
let adminMutationEntries = ReventlessCore.
|
|
2184
|
+
let adminMutationEntries = ReventlessCore.Platform_AdminApi.mutationEntries(~cloner=Config.cloner)
|
|
2182
2185
|
let adminMutationFieldNames = adminMutationEntries->Array.flatMap(entry => entry.fieldNames)
|
|
2183
2186
|
adminMutationFieldNames->Array.forEach(field =>
|
|
2184
2187
|
mutationResolvers->Dict.set(field, async (_root, _args, _ctx): JSON.t =>
|
package/src/Platform.res.mjs
CHANGED
|
@@ -16,7 +16,6 @@ import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/component
|
|
|
16
16
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
17
17
|
import * as Plugin$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin.res.mjs";
|
|
18
18
|
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
19
|
-
import * as AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/AdminApi.res.mjs";
|
|
20
19
|
import * as Backend$ReventlessLocal from "./adapter/Backend.res.mjs";
|
|
21
20
|
import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
|
|
22
21
|
import * as LocalBus$ReventlessLocal from "./adapter/LocalBus.res.mjs";
|
|
@@ -42,6 +41,7 @@ import * as NoEventMappings$ReventlessInfra from "@reventlessdev/reventless-infr
|
|
|
42
41
|
import * as DomainMCP_Server$ReventlessLocal from "./adapter/DomainMCP_Server.res.mjs";
|
|
43
42
|
import * as ExtensionMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionMapping.res.mjs";
|
|
44
43
|
import * as LocalQueryEngine$ReventlessLocal from "./adapter/QueryEngine/LocalQueryEngine.res.mjs";
|
|
44
|
+
import * as Platform_AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_AdminApi.res.mjs";
|
|
45
45
|
import * as PluginsProjection$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginsProjection.res.mjs";
|
|
46
46
|
import * as Scheduler_Builder$ReventlessCore from "@reventlessdev/reventless-core/src/components/Scheduler/Scheduler_Builder.res.mjs";
|
|
47
47
|
import * as Aggregate_Builder$ReventlessLocal from "./components/Aggregate_Builder.res.mjs";
|
|
@@ -830,7 +830,7 @@ function MakeWithConfig(Config) {
|
|
|
830
830
|
};
|
|
831
831
|
return Bus.dispatchCommand(pluginCmdTopicKey, CommandTopic$ReventlessCore.encodeCommandJson(cmdJson));
|
|
832
832
|
};
|
|
833
|
-
let adminDcbCmdTopicKey = ComponentType$ReventlessCore.name("
|
|
833
|
+
let adminDcbCmdTopicKey = ComponentType$ReventlessCore.name(Platform_Admin_Structure$ReventlessCore.pluginId + "Dcb", "CommandTopic");
|
|
834
834
|
let dispatchUiFragmentCommand = (pluginName, command) => {
|
|
835
835
|
let cmdJson_meta = Message$ReventlessCore.generateMeta(UiFragmentRegistry$ReventlessCore.name, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
836
836
|
let cmdJson_commandJson = S.reverseConvertToJsonOrThrow(command, UiFragmentRegistry$ReventlessCore.commandSchema);
|
|
@@ -843,7 +843,8 @@ function MakeWithConfig(Config) {
|
|
|
843
843
|
};
|
|
844
844
|
let pluginStatusSubTopic = "onPluginStatusChange";
|
|
845
845
|
let uiFragmentSubTopic = "onUIFragmentChange";
|
|
846
|
-
let adminDcbEventTopicKey = ComponentType$ReventlessCore.name(
|
|
846
|
+
let adminDcbEventTopicKey = ComponentType$ReventlessCore.name(Platform_Admin_Structure$ReventlessCore.pluginId, "EventTopic");
|
|
847
|
+
let adminDcbServiceName = ComponentType$ReventlessCore.name(Platform_Admin_Structure$ReventlessCore.pluginId, "DcbEventLog");
|
|
847
848
|
let pluginEventsSubscribed = {
|
|
848
849
|
contents: false
|
|
849
850
|
};
|
|
@@ -897,7 +898,7 @@ function MakeWithConfig(Config) {
|
|
|
897
898
|
}
|
|
898
899
|
});
|
|
899
900
|
Bus.subscribeToEvents(adminDcbEventTopicKey, async (service, _meta, eventJson) => {
|
|
900
|
-
if (service !==
|
|
901
|
+
if (service !== adminDcbServiceName) {
|
|
901
902
|
return;
|
|
902
903
|
}
|
|
903
904
|
let match = decodeUiFragmentRegistryEventEnvelope(eventJson);
|
|
@@ -1297,11 +1298,11 @@ function MakeWithConfig(Config) {
|
|
|
1297
1298
|
let adminQueryEntry = PluginBaseFragment$ReventlessCore.queryEntries[0];
|
|
1298
1299
|
let singleQueryField = adminQueryEntry.singleFieldName;
|
|
1299
1300
|
let listQueryField = adminQueryEntry.listFieldName;
|
|
1300
|
-
let adminMutationEntries =
|
|
1301
|
+
let adminMutationEntries = Platform_AdminApi$ReventlessCore.mutationEntries(Config.cloner);
|
|
1301
1302
|
let adminMutationFieldNames = adminMutationEntries.flatMap(entry => entry.fieldNames);
|
|
1302
1303
|
let platformGraphQL = resolveTargetGraphQL();
|
|
1303
1304
|
let platformMCP = resolveTargetMCP();
|
|
1304
|
-
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(
|
|
1305
|
+
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(Platform_AdminApi$ReventlessCore.baseFragment(Config.cloner));
|
|
1305
1306
|
platformGraphQL.registerTypes(baseParts.types);
|
|
1306
1307
|
let queryResolvers = {};
|
|
1307
1308
|
queryResolvers[singleQueryField] = async (_root, args, _ctx) => {
|
|
@@ -1419,10 +1420,10 @@ function MakeWithConfig(Config) {
|
|
|
1419
1420
|
let v = argVersion(args);
|
|
1420
1421
|
let version = v !== undefined && v !== "" ? v : currentVersionOf(pluginName);
|
|
1421
1422
|
if (version !== undefined) {
|
|
1422
|
-
log.info(
|
|
1423
|
+
log.info(Platform_Admin_Structure$ReventlessCore.pluginId, undefined, field + `(` + pluginName + `@` + version + `): dispatching to aggregate`);
|
|
1423
1424
|
dispatchPluginCommand(pluginName, makeCommand(version));
|
|
1424
1425
|
} else {
|
|
1425
|
-
log.warn(
|
|
1426
|
+
log.warn(Platform_Admin_Structure$ReventlessCore.pluginId, undefined, field + `(` + pluginName + `): plugin not found`);
|
|
1426
1427
|
}
|
|
1427
1428
|
return commandAccepted(msgId, pluginName);
|
|
1428
1429
|
};
|
|
@@ -1479,8 +1480,8 @@ function MakeWithConfig(Config) {
|
|
|
1479
1480
|
platformGraphQL.registerMutations(adminMutationSdl(baseParts.mutations), mutationResolvers);
|
|
1480
1481
|
adminRegisteredServers.contents.push(platformGraphQL);
|
|
1481
1482
|
platformGraphQL.registerSubscriptions([
|
|
1482
|
-
|
|
1483
|
-
|
|
1483
|
+
Platform_AdminApi$ReventlessCore.uiFragmentSubscriptionField,
|
|
1484
|
+
Platform_AdminApi$ReventlessCore.pluginStatusSubscriptionField
|
|
1484
1485
|
], Object.fromEntries([
|
|
1485
1486
|
[
|
|
1486
1487
|
"onUIFragmentChange",
|
|
@@ -1501,7 +1502,7 @@ function MakeWithConfig(Config) {
|
|
|
1501
1502
|
PluginsReadModelSpec$ReventlessCore.name
|
|
1502
1503
|
]
|
|
1503
1504
|
]);
|
|
1504
|
-
platformMCP.registerResourcesFromEntries(
|
|
1505
|
+
platformMCP.registerResourcesFromEntries(Platform_Admin_Structure$ReventlessCore.pluginId, PluginBaseFragment$ReventlessCore.queryEntries, async (resourceName, uri) => {
|
|
1505
1506
|
let segments = uri.split("/");
|
|
1506
1507
|
let id = Stdlib_Option.getOr(segments.at(-1), "");
|
|
1507
1508
|
let queryDbName = Stdlib_Option.getOr(adminFieldToQueryDb[resourceName], PluginsReadModelSpec$ReventlessCore.name);
|
|
@@ -1520,7 +1521,7 @@ function MakeWithConfig(Config) {
|
|
|
1520
1521
|
return [];
|
|
1521
1522
|
}
|
|
1522
1523
|
});
|
|
1523
|
-
platformMCP.registerToolsFromEntries(
|
|
1524
|
+
platformMCP.registerToolsFromEntries(Platform_Admin_Structure$ReventlessCore.pluginId, adminMutationEntries, async (toolName, args, _identity) => {
|
|
1524
1525
|
let resolver = platformGraphQL.getMutationResolver(toolName);
|
|
1525
1526
|
if (resolver === undefined) {
|
|
1526
1527
|
return `error: no handler found for tool ` + toolName;
|
|
@@ -1559,7 +1560,7 @@ function MakeWithConfig(Config) {
|
|
|
1559
1560
|
currentDeployTarget.contents = "Platform";
|
|
1560
1561
|
let adminGraphQL = resolveTargetGraphQL();
|
|
1561
1562
|
let adminMCP = resolveTargetMCP();
|
|
1562
|
-
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(
|
|
1563
|
+
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(Platform_AdminApi$ReventlessCore.baseFragment(Config.cloner));
|
|
1563
1564
|
adminGraphQL.registerTypes(baseParts.types);
|
|
1564
1565
|
let queryResolvers = {};
|
|
1565
1566
|
let adminQueryEntry = PluginBaseFragment$ReventlessCore.queryEntries[0];
|
|
@@ -1569,7 +1570,7 @@ function MakeWithConfig(Config) {
|
|
|
1569
1570
|
registerAdminItemsAndIndexResolvers(queryResolvers, false);
|
|
1570
1571
|
adminGraphQL.registerQueries(baseParts.queries, queryResolvers);
|
|
1571
1572
|
let mutationResolvers = {};
|
|
1572
|
-
let adminMutationEntries =
|
|
1573
|
+
let adminMutationEntries = Platform_AdminApi$ReventlessCore.mutationEntries(Config.cloner);
|
|
1573
1574
|
let adminMutationFieldNames = adminMutationEntries.flatMap(entry => entry.fieldNames);
|
|
1574
1575
|
adminMutationFieldNames.forEach(field => {
|
|
1575
1576
|
mutationResolvers[field] = async (_root, _args, _ctx) => commandAccepted(Message$ReventlessCore.uuid(), undefined);
|
|
@@ -1678,7 +1679,7 @@ function MakeWithConfig(Config) {
|
|
|
1678
1679
|
Plugin_Helpers$ReventlessCore.onPluginBuiltHook.contents = existingBuiltHook;
|
|
1679
1680
|
let adminGraphQL = resolveTargetGraphQL();
|
|
1680
1681
|
if (!adminRegisteredServers.contents.some(s => s === adminGraphQL)) {
|
|
1681
|
-
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(
|
|
1682
|
+
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(Platform_AdminApi$ReventlessCore.baseFragment(Config.cloner));
|
|
1682
1683
|
adminGraphQL.registerTypes(baseParts.types);
|
|
1683
1684
|
let queryResolvers = {};
|
|
1684
1685
|
let adminQueryEntry = PluginBaseFragment$ReventlessCore.queryEntries[0];
|
|
@@ -1701,7 +1702,7 @@ function MakeWithConfig(Config) {
|
|
|
1701
1702
|
registerAdminItemsAndIndexResolvers(queryResolvers, true);
|
|
1702
1703
|
adminGraphQL.registerQueries(baseParts.queries, queryResolvers);
|
|
1703
1704
|
let mutationResolvers = {};
|
|
1704
|
-
let adminMutationEntries =
|
|
1705
|
+
let adminMutationEntries = Platform_AdminApi$ReventlessCore.mutationEntries(Config.cloner);
|
|
1705
1706
|
let adminMutationFieldNames = adminMutationEntries.flatMap(entry => entry.fieldNames);
|
|
1706
1707
|
adminMutationFieldNames.forEach(field => {
|
|
1707
1708
|
mutationResolvers[field] = async (_root, _args, _ctx) => commandAccepted(Message$ReventlessCore.uuid(), undefined);
|
|
@@ -2499,7 +2500,7 @@ function Make($star) {
|
|
|
2499
2500
|
};
|
|
2500
2501
|
return Bus.dispatchCommand(pluginCmdTopicKey, CommandTopic$ReventlessCore.encodeCommandJson(cmdJson));
|
|
2501
2502
|
};
|
|
2502
|
-
let adminDcbCmdTopicKey = ComponentType$ReventlessCore.name("
|
|
2503
|
+
let adminDcbCmdTopicKey = ComponentType$ReventlessCore.name(Platform_Admin_Structure$ReventlessCore.pluginId + "Dcb", "CommandTopic");
|
|
2503
2504
|
let dispatchUiFragmentCommand = (pluginName, command) => {
|
|
2504
2505
|
let cmdJson_meta = Message$ReventlessCore.generateMeta(UiFragmentRegistry$ReventlessCore.name, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
2505
2506
|
let cmdJson_commandJson = S.reverseConvertToJsonOrThrow(command, UiFragmentRegistry$ReventlessCore.commandSchema);
|
|
@@ -2512,7 +2513,8 @@ function Make($star) {
|
|
|
2512
2513
|
};
|
|
2513
2514
|
let pluginStatusSubTopic = "onPluginStatusChange";
|
|
2514
2515
|
let uiFragmentSubTopic = "onUIFragmentChange";
|
|
2515
|
-
let adminDcbEventTopicKey = ComponentType$ReventlessCore.name(
|
|
2516
|
+
let adminDcbEventTopicKey = ComponentType$ReventlessCore.name(Platform_Admin_Structure$ReventlessCore.pluginId, "EventTopic");
|
|
2517
|
+
let adminDcbServiceName = ComponentType$ReventlessCore.name(Platform_Admin_Structure$ReventlessCore.pluginId, "DcbEventLog");
|
|
2516
2518
|
let pluginEventsSubscribed = {
|
|
2517
2519
|
contents: false
|
|
2518
2520
|
};
|
|
@@ -2566,7 +2568,7 @@ function Make($star) {
|
|
|
2566
2568
|
}
|
|
2567
2569
|
});
|
|
2568
2570
|
Bus.subscribeToEvents(adminDcbEventTopicKey, async (service, _meta, eventJson) => {
|
|
2569
|
-
if (service !==
|
|
2571
|
+
if (service !== adminDcbServiceName) {
|
|
2570
2572
|
return;
|
|
2571
2573
|
}
|
|
2572
2574
|
let match = decodeUiFragmentRegistryEventEnvelope(eventJson);
|
|
@@ -2961,11 +2963,11 @@ function Make($star) {
|
|
|
2961
2963
|
let adminQueryEntry = PluginBaseFragment$ReventlessCore.queryEntries[0];
|
|
2962
2964
|
let singleQueryField = adminQueryEntry.singleFieldName;
|
|
2963
2965
|
let listQueryField = adminQueryEntry.listFieldName;
|
|
2964
|
-
let adminMutationEntries =
|
|
2966
|
+
let adminMutationEntries = Platform_AdminApi$ReventlessCore.mutationEntries(false);
|
|
2965
2967
|
let adminMutationFieldNames = adminMutationEntries.flatMap(entry => entry.fieldNames);
|
|
2966
2968
|
let platformGraphQL = resolveTargetGraphQL();
|
|
2967
2969
|
let platformMCP = resolveTargetMCP();
|
|
2968
|
-
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(
|
|
2970
|
+
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(Platform_AdminApi$ReventlessCore.baseFragment(false));
|
|
2969
2971
|
platformGraphQL.registerTypes(baseParts.types);
|
|
2970
2972
|
let queryResolvers = {};
|
|
2971
2973
|
queryResolvers[singleQueryField] = async (_root, args, _ctx) => {
|
|
@@ -3083,10 +3085,10 @@ function Make($star) {
|
|
|
3083
3085
|
let v = argVersion(args);
|
|
3084
3086
|
let version = v !== undefined && v !== "" ? v : currentVersionOf(pluginName);
|
|
3085
3087
|
if (version !== undefined) {
|
|
3086
|
-
log.info(
|
|
3088
|
+
log.info(Platform_Admin_Structure$ReventlessCore.pluginId, undefined, field + `(` + pluginName + `@` + version + `): dispatching to aggregate`);
|
|
3087
3089
|
dispatchPluginCommand(pluginName, makeCommand(version));
|
|
3088
3090
|
} else {
|
|
3089
|
-
log.warn(
|
|
3091
|
+
log.warn(Platform_Admin_Structure$ReventlessCore.pluginId, undefined, field + `(` + pluginName + `): plugin not found`);
|
|
3090
3092
|
}
|
|
3091
3093
|
return commandAccepted(msgId, pluginName);
|
|
3092
3094
|
};
|
|
@@ -3143,8 +3145,8 @@ function Make($star) {
|
|
|
3143
3145
|
platformGraphQL.registerMutations(adminMutationSdl(baseParts.mutations), mutationResolvers);
|
|
3144
3146
|
adminRegisteredServers.contents.push(platformGraphQL);
|
|
3145
3147
|
platformGraphQL.registerSubscriptions([
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
+
Platform_AdminApi$ReventlessCore.uiFragmentSubscriptionField,
|
|
3149
|
+
Platform_AdminApi$ReventlessCore.pluginStatusSubscriptionField
|
|
3148
3150
|
], Object.fromEntries([
|
|
3149
3151
|
[
|
|
3150
3152
|
"onUIFragmentChange",
|
|
@@ -3165,7 +3167,7 @@ function Make($star) {
|
|
|
3165
3167
|
PluginsReadModelSpec$ReventlessCore.name
|
|
3166
3168
|
]
|
|
3167
3169
|
]);
|
|
3168
|
-
platformMCP.registerResourcesFromEntries(
|
|
3170
|
+
platformMCP.registerResourcesFromEntries(Platform_Admin_Structure$ReventlessCore.pluginId, PluginBaseFragment$ReventlessCore.queryEntries, async (resourceName, uri) => {
|
|
3169
3171
|
let segments = uri.split("/");
|
|
3170
3172
|
let id = Stdlib_Option.getOr(segments.at(-1), "");
|
|
3171
3173
|
let queryDbName = Stdlib_Option.getOr(adminFieldToQueryDb[resourceName], PluginsReadModelSpec$ReventlessCore.name);
|
|
@@ -3184,7 +3186,7 @@ function Make($star) {
|
|
|
3184
3186
|
return [];
|
|
3185
3187
|
}
|
|
3186
3188
|
});
|
|
3187
|
-
platformMCP.registerToolsFromEntries(
|
|
3189
|
+
platformMCP.registerToolsFromEntries(Platform_Admin_Structure$ReventlessCore.pluginId, adminMutationEntries, async (toolName, args, _identity) => {
|
|
3188
3190
|
let resolver = platformGraphQL.getMutationResolver(toolName);
|
|
3189
3191
|
if (resolver === undefined) {
|
|
3190
3192
|
return `error: no handler found for tool ` + toolName;
|
|
@@ -3216,7 +3218,7 @@ function Make($star) {
|
|
|
3216
3218
|
currentDeployTarget.contents = "Platform";
|
|
3217
3219
|
let adminGraphQL = resolveTargetGraphQL();
|
|
3218
3220
|
let adminMCP = resolveTargetMCP();
|
|
3219
|
-
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(
|
|
3221
|
+
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(Platform_AdminApi$ReventlessCore.baseFragment(false));
|
|
3220
3222
|
adminGraphQL.registerTypes(baseParts.types);
|
|
3221
3223
|
let queryResolvers = {};
|
|
3222
3224
|
let adminQueryEntry = PluginBaseFragment$ReventlessCore.queryEntries[0];
|
|
@@ -3226,7 +3228,7 @@ function Make($star) {
|
|
|
3226
3228
|
registerAdminItemsAndIndexResolvers(queryResolvers, false);
|
|
3227
3229
|
adminGraphQL.registerQueries(baseParts.queries, queryResolvers);
|
|
3228
3230
|
let mutationResolvers = {};
|
|
3229
|
-
let adminMutationEntries =
|
|
3231
|
+
let adminMutationEntries = Platform_AdminApi$ReventlessCore.mutationEntries(false);
|
|
3230
3232
|
let adminMutationFieldNames = adminMutationEntries.flatMap(entry => entry.fieldNames);
|
|
3231
3233
|
adminMutationFieldNames.forEach(field => {
|
|
3232
3234
|
mutationResolvers[field] = async (_root, _args, _ctx) => commandAccepted(Message$ReventlessCore.uuid(), undefined);
|
|
@@ -3333,7 +3335,7 @@ function Make($star) {
|
|
|
3333
3335
|
Plugin_Helpers$ReventlessCore.onPluginBuiltHook.contents = existingBuiltHook;
|
|
3334
3336
|
let adminGraphQL = resolveTargetGraphQL();
|
|
3335
3337
|
if (!adminRegisteredServers.contents.some(s => s === adminGraphQL)) {
|
|
3336
|
-
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(
|
|
3338
|
+
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(Platform_AdminApi$ReventlessCore.baseFragment(false));
|
|
3337
3339
|
adminGraphQL.registerTypes(baseParts.types);
|
|
3338
3340
|
let queryResolvers = {};
|
|
3339
3341
|
let adminQueryEntry = PluginBaseFragment$ReventlessCore.queryEntries[0];
|
|
@@ -3356,7 +3358,7 @@ function Make($star) {
|
|
|
3356
3358
|
registerAdminItemsAndIndexResolvers(queryResolvers, true);
|
|
3357
3359
|
adminGraphQL.registerQueries(baseParts.queries, queryResolvers);
|
|
3358
3360
|
let mutationResolvers = {};
|
|
3359
|
-
let adminMutationEntries =
|
|
3361
|
+
let adminMutationEntries = Platform_AdminApi$ReventlessCore.mutationEntries(false);
|
|
3360
3362
|
let adminMutationFieldNames = adminMutationEntries.flatMap(entry => entry.fieldNames);
|
|
3361
3363
|
adminMutationFieldNames.forEach(field => {
|
|
3362
3364
|
mutationResolvers[field] = async (_root, _args, _ctx) => commandAccepted(Message$ReventlessCore.uuid(), undefined);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// the LocalBus event-tap can stream historical events uniformly.
|
|
11
11
|
|
|
12
12
|
module Make = (Bus: LocalBus.T) => {
|
|
13
|
-
let make: ReventlessCore.EventLog_Adapter.storageMaker = (~name, ~owner, ~opts) => {
|
|
13
|
+
let make: ReventlessCore.EventLog_Adapter.storageMaker = (~name, ~owner as _, ~opts) => {
|
|
14
14
|
switch (BackendState.getSqliteDb(), BackendState.getPostgresPool()) {
|
|
15
15
|
| (Some(db), _) =>
|
|
16
16
|
let (storageName, replay, storage) = EventLogStorage_Sqlite.makeStorage(~db, ~name, ~opts)
|
|
@@ -7,7 +7,7 @@ import * as EventLogStorage_InMemory$ReventlessLocal from "./EventLogStorage_InM
|
|
|
7
7
|
import * as EventLogStorage_Postgres$ReventlessPostgres from "@reventlessdev/reventless-postgres/src/EventLogStorage_Postgres.res.mjs";
|
|
8
8
|
|
|
9
9
|
function Make(Bus) {
|
|
10
|
-
let make = (name,
|
|
10
|
+
let make = (name, param, opts) => {
|
|
11
11
|
let match = BackendState$ReventlessLocal.getSqliteDb();
|
|
12
12
|
let match$1 = BackendState$ReventlessLocal.getPostgresPool();
|
|
13
13
|
if (match !== undefined) {
|
|
@@ -55,7 +55,12 @@ describe("Platform.decodePluginEventEnvelope", () => {
|
|
|
55
55
|
// DcbEventLog_Operations produces: composeEventJson'(entityId, meta,
|
|
56
56
|
// combineMessage(eventType, data)).
|
|
57
57
|
describe("Platform.decodeUiFragmentRegistryEventEnvelope", () => {
|
|
58
|
-
let dcbMeta =
|
|
58
|
+
let dcbMeta = {
|
|
59
|
+
module CT = ReventlessCore.ComponentType
|
|
60
|
+
ReventlessCore.Message.generateMeta(
|
|
61
|
+
~service=ReventlessCore.Platform_Admin_Structure.pluginId->CT.name(CT.DcbEventLog),
|
|
62
|
+
)
|
|
63
|
+
}
|
|
59
64
|
|
|
60
65
|
let dcbEnvelopeOf = (event: ReventlessCore.UiFragmentRegistry.event) => {
|
|
61
66
|
let json = event->S.reverseConvertToJsonOrThrow(ReventlessCore.UiFragmentRegistry.eventSchema)
|
|
@@ -5,8 +5,10 @@ import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Mess
|
|
|
5
5
|
import * as Platform$ReventlessLocal from "../src/Platform.res.mjs";
|
|
6
6
|
import * as PluginSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginSpec.res.mjs";
|
|
7
7
|
import * as TestRunner$ReventlessLocal from "../src/test/TestRunner.res.mjs";
|
|
8
|
+
import * as ComponentType$ReventlessCore from "@reventlessdev/reventless-core/src/ComponentType.res.mjs";
|
|
8
9
|
import * as Plugin_Fixtures$ReventlessLocal from "./plugin/Plugin_Fixtures.res.mjs";
|
|
9
10
|
import * as UiFragmentRegistry$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs";
|
|
11
|
+
import * as Platform_Admin_Structure$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_Admin_Structure.res.mjs";
|
|
10
12
|
|
|
11
13
|
TestRunner$ReventlessLocal.setup();
|
|
12
14
|
|
|
@@ -58,7 +60,7 @@ globalThis.describe("Platform.decodePluginEventEnvelope", () => {
|
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
globalThis.describe("Platform.decodeUiFragmentRegistryEventEnvelope", () => {
|
|
61
|
-
let dcbMeta = Message$ReventlessCore.generateMeta("
|
|
63
|
+
let dcbMeta = Message$ReventlessCore.generateMeta(ComponentType$ReventlessCore.name(Platform_Admin_Structure$ReventlessCore.pluginId, "DcbEventLog"), undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
62
64
|
let dcbEnvelopeOf = event => {
|
|
63
65
|
let json = S.reverseConvertToJsonOrThrow(event, UiFragmentRegistry$ReventlessCore.eventSchema);
|
|
64
66
|
let match = Message$ReventlessCore.splitMessage(json);
|
|
@@ -31,7 +31,7 @@ let extractSdlFieldName = (sdlField: string): string => {
|
|
|
31
31
|
let adminGraphQL = ReventlessGraphqlServer.GraphQL_ServerInstance.make(~label="GraphQL:Admin")
|
|
32
32
|
|
|
33
33
|
// Build admin SDL — authoritative source for field names.
|
|
34
|
-
let baseParts = ReventlessCore.GraphQL_Stitcher.decode(ReventlessCore.
|
|
34
|
+
let baseParts = ReventlessCore.GraphQL_Stitcher.decode(ReventlessCore.Platform_AdminApi.baseFragment(~cloner=true))
|
|
35
35
|
let () = adminGraphQL.registerTypes(~sdlTypes=baseParts.types)
|
|
36
36
|
|
|
37
37
|
// Derive query/mutation field names directly from SDL so that any SDL
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
-
import * as AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/AdminApi.res.mjs";
|
|
5
4
|
import * as TestRunner$ReventlessLocal from "../src/test/TestRunner.res.mjs";
|
|
6
5
|
import * as GraphQL_Server$ReventlessLocal from "../src/adapter/GraphQL_Server.res.mjs";
|
|
7
6
|
import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
7
|
+
import * as Platform_AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_AdminApi.res.mjs";
|
|
8
8
|
import * as PluginBaseFragment$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/api/PluginBaseFragment.res.mjs";
|
|
9
9
|
import * as GraphQL_ServerInstance$ReventlessGraphqlServer from "@reventlessdev/reventless-graphql-server/src/GraphQL_ServerInstance.res.mjs";
|
|
10
10
|
|
|
@@ -17,7 +17,7 @@ function extractSdlFieldName(sdlField) {
|
|
|
17
17
|
|
|
18
18
|
let adminGraphQL = GraphQL_ServerInstance$ReventlessGraphqlServer.make("GraphQL:Admin");
|
|
19
19
|
|
|
20
|
-
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(
|
|
20
|
+
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(Platform_AdminApi$ReventlessCore.baseFragment(true));
|
|
21
21
|
|
|
22
22
|
adminGraphQL.registerTypes(baseParts.types);
|
|
23
23
|
|
|
@@ -91,6 +91,39 @@ describe("DcbEventLogStorage_Sqlite", () => {
|
|
|
91
91
|
})
|
|
92
92
|
})
|
|
93
93
|
|
|
94
|
+
// Backend contract: an empty tag value (an absent composite-partition member) is
|
|
95
|
+
// legitimate — it appends, is recorded verbatim, and keeps matching through both
|
|
96
|
+
// the partition read and a composite read. Held identically by the in-memory and
|
|
97
|
+
// DynamoDB backends. See docs/plans/dcb-empty-tag-values-break-append.md.
|
|
98
|
+
testPromise("an empty tag value appends and stays readable", async () => {
|
|
99
|
+
await runUnderSqlite(async () => {
|
|
100
|
+
module TestBus = LocalBus.Make()
|
|
101
|
+
module Storage = LocalDcbEventLogStorage.Make(TestBus)
|
|
102
|
+
let s = Storage.make(
|
|
103
|
+
~name="dcb-emptytag",
|
|
104
|
+
~indexes=[],
|
|
105
|
+
~partitionTag=Reventless.DcbTag.Simple({key: "itemId"}),
|
|
106
|
+
~opts,
|
|
107
|
+
)
|
|
108
|
+
let ops = await s.operations->TestRunner.resolve
|
|
109
|
+
|
|
110
|
+
let tags = [{Reventless.DcbTag.key: "itemId", value: "i1"}, {key: "variantId", value: ""}]
|
|
111
|
+
let result = await ops.append([stored("ItemAdded", tags, JSON.Encode.string("x"))])
|
|
112
|
+
switch result {
|
|
113
|
+
| Ok(_) => expect(true)->toBe(true)
|
|
114
|
+
| Error(e) => expect(appendErrMsg(e))->toBe("Ok")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let byPartition = await ops.read(~query=[{tags: [{key: "itemId", value: "i1"}]}])
|
|
118
|
+
expect(byPartition.events->Array.length)->toBe(1)
|
|
119
|
+
expect((byPartition.events->Array.getUnsafe(0)).tags->Array.map(t => (t.key, t.value)))
|
|
120
|
+
->toEqual([("itemId", "i1"), ("variantId", "")])
|
|
121
|
+
|
|
122
|
+
let byComposite = await ops.read(~query=[{tags: tags}])
|
|
123
|
+
expect(byComposite.events->Array.length)->toBe(1)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
94
127
|
testPromise("batched tag hydration preserves per-event tags and order", async () => {
|
|
95
128
|
await runUnderSqlite(async () => {
|
|
96
129
|
module TestBus = LocalBus.Make()
|
|
@@ -96,6 +96,57 @@ globalThis.describe("DcbEventLogStorage_Sqlite", () => {
|
|
|
96
96
|
}], undefined);
|
|
97
97
|
globalThis.expect(read.events.length).toBe(2);
|
|
98
98
|
}));
|
|
99
|
+
globalThis.test("an empty tag value appends and stays readable", async () => await runUnderSqlite(async () => {
|
|
100
|
+
let TestBus = LocalBus$ReventlessLocal.Make({});
|
|
101
|
+
let Storage = LocalDcbEventLogStorage$ReventlessLocal.Make(TestBus);
|
|
102
|
+
let s = Storage.make("dcb-emptytag", [], {
|
|
103
|
+
TAG: "Simple",
|
|
104
|
+
_0: {
|
|
105
|
+
key: "itemId"
|
|
106
|
+
}
|
|
107
|
+
}, undefined, opts);
|
|
108
|
+
let ops = await TestRunner$ReventlessLocal.resolve(s.operations);
|
|
109
|
+
let tags = [
|
|
110
|
+
{
|
|
111
|
+
key: "itemId",
|
|
112
|
+
value: "i1"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
key: "variantId",
|
|
116
|
+
value: ""
|
|
117
|
+
}
|
|
118
|
+
];
|
|
119
|
+
let result = await ops.append([stored("ItemAdded", tags, "x")], undefined);
|
|
120
|
+
if (result.TAG === "Ok") {
|
|
121
|
+
globalThis.expect(true).toBe(true);
|
|
122
|
+
} else {
|
|
123
|
+
globalThis.expect(appendErrMsg(result._0)).toBe("Ok");
|
|
124
|
+
}
|
|
125
|
+
let byPartition = await ops.read([{
|
|
126
|
+
tags: [{
|
|
127
|
+
key: "itemId",
|
|
128
|
+
value: "i1"
|
|
129
|
+
}]
|
|
130
|
+
}], undefined);
|
|
131
|
+
globalThis.expect(byPartition.events.length).toBe(1);
|
|
132
|
+
globalThis.expect(byPartition.events[0].tags.map(t => [
|
|
133
|
+
t.key,
|
|
134
|
+
t.value
|
|
135
|
+
])).toEqual([
|
|
136
|
+
[
|
|
137
|
+
"itemId",
|
|
138
|
+
"i1"
|
|
139
|
+
],
|
|
140
|
+
[
|
|
141
|
+
"variantId",
|
|
142
|
+
""
|
|
143
|
+
]
|
|
144
|
+
]);
|
|
145
|
+
let byComposite = await ops.read([{
|
|
146
|
+
tags: tags
|
|
147
|
+
}], undefined);
|
|
148
|
+
globalThis.expect(byComposite.events.length).toBe(1);
|
|
149
|
+
}));
|
|
99
150
|
globalThis.test("batched tag hydration preserves per-event tags and order", async () => await runUnderSqlite(async () => {
|
|
100
151
|
let TestBus = LocalBus$ReventlessLocal.Make({});
|
|
101
152
|
let Storage = LocalDcbEventLogStorage$ReventlessLocal.Make(TestBus);
|
|
@@ -142,6 +142,32 @@ describe("DcbEventLogStorage_InMemory", () => {
|
|
|
142
142
|
expect((result.events->Array.getUnsafe(1)).eventType)->toBe("B")
|
|
143
143
|
})
|
|
144
144
|
|
|
145
|
+
// An empty tag value is a legitimate model state — an absent member of a
|
|
146
|
+
// composite partition key. Every backend must accept it, record it verbatim,
|
|
147
|
+
// and keep matching the event through its partition and composite reads. See
|
|
148
|
+
// docs/plans/dcb-empty-tag-values-break-append.md.
|
|
149
|
+
testPromise("an empty tag value appends and stays readable", async () => {
|
|
150
|
+
let storage = makeStorage()
|
|
151
|
+
let ops = await storage.operations->TestRunner.resolve
|
|
152
|
+
let tags = [
|
|
153
|
+
{Reventless.DcbTag.key: "itemId", value: "i1"},
|
|
154
|
+
{Reventless.DcbTag.key: "variantId", value: ""},
|
|
155
|
+
]
|
|
156
|
+
let appended = await ops.append([makeEvent(~eventType="ItemAdded", ~data=JSON.Null, ~tags)])
|
|
157
|
+
expect(appended)->toEqual(Ok("1"))
|
|
158
|
+
|
|
159
|
+
let byPartition = await ops.read(
|
|
160
|
+
~query=[{Reventless.DcbTag.tags: [{Reventless.DcbTag.key: "itemId", value: "i1"}]}],
|
|
161
|
+
)
|
|
162
|
+
expect(byPartition.events->Array.length)->toBe(1)
|
|
163
|
+
// The empty value is recorded verbatim, not dropped or substituted.
|
|
164
|
+
expect((byPartition.events->Array.getUnsafe(0)).tags->Array.map(t => (t.key, t.value)))
|
|
165
|
+
->toEqual([("itemId", "i1"), ("variantId", "")])
|
|
166
|
+
|
|
167
|
+
let byComposite = await ops.read(~query=[{Reventless.DcbTag.tags: tags}])
|
|
168
|
+
expect(byComposite.events->Array.length)->toBe(1)
|
|
169
|
+
})
|
|
170
|
+
|
|
145
171
|
testPromise("after parameter skips events at or before that position", async () => {
|
|
146
172
|
let storage = makeStorage()
|
|
147
173
|
let ops = await storage.operations->TestRunner.resolve
|
|
@@ -165,6 +165,49 @@ globalThis.describe("DcbEventLogStorage_InMemory", () => {
|
|
|
165
165
|
globalThis.expect(result.events[0].eventType).toBe("A");
|
|
166
166
|
globalThis.expect(result.events[1].eventType).toBe("B");
|
|
167
167
|
});
|
|
168
|
+
globalThis.test("an empty tag value appends and stays readable", async () => {
|
|
169
|
+
let storage = makeStorage();
|
|
170
|
+
let ops = await TestRunner$ReventlessLocal.resolve(storage.operations);
|
|
171
|
+
let tags = [
|
|
172
|
+
{
|
|
173
|
+
key: "itemId",
|
|
174
|
+
value: "i1"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
key: "variantId",
|
|
178
|
+
value: ""
|
|
179
|
+
}
|
|
180
|
+
];
|
|
181
|
+
let appended = await ops.append([makeEvent("ItemAdded", null, tags)], undefined);
|
|
182
|
+
globalThis.expect(appended).toEqual({
|
|
183
|
+
TAG: "Ok",
|
|
184
|
+
_0: "1"
|
|
185
|
+
});
|
|
186
|
+
let byPartition = await ops.read([{
|
|
187
|
+
tags: [{
|
|
188
|
+
key: "itemId",
|
|
189
|
+
value: "i1"
|
|
190
|
+
}]
|
|
191
|
+
}], undefined);
|
|
192
|
+
globalThis.expect(byPartition.events.length).toBe(1);
|
|
193
|
+
globalThis.expect(byPartition.events[0].tags.map(t => [
|
|
194
|
+
t.key,
|
|
195
|
+
t.value
|
|
196
|
+
])).toEqual([
|
|
197
|
+
[
|
|
198
|
+
"itemId",
|
|
199
|
+
"i1"
|
|
200
|
+
],
|
|
201
|
+
[
|
|
202
|
+
"variantId",
|
|
203
|
+
""
|
|
204
|
+
]
|
|
205
|
+
]);
|
|
206
|
+
let byComposite = await ops.read([{
|
|
207
|
+
tags: tags
|
|
208
|
+
}], undefined);
|
|
209
|
+
globalThis.expect(byComposite.events.length).toBe(1);
|
|
210
|
+
});
|
|
168
211
|
globalThis.test("after parameter skips events at or before that position", async () => {
|
|
169
212
|
let storage = makeStorage();
|
|
170
213
|
let ops = await TestRunner$ReventlessLocal.resolve(storage.operations);
|