@reventlessdev/reventless-local 3.0.0-alpha.249 → 3.0.0-alpha.250
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 +8 -0
- package/package.json +13 -13
- package/src/BakedManifest.res +5 -6
- package/src/EmitCapabilities.res +3 -1
- package/src/LocalPlatformRegistry.res +3 -1
- package/src/LocalPlatformStart.res +7 -6
- package/src/LocalSeedTarget.res +3 -1
- package/src/Platform.res +291 -215
- package/src/ShellConfig.res +10 -17
- package/src/UiHints.res +8 -14
- package/src/UiSlots.res +10 -17
- package/src/adapter/Api/LocalEvents_Server.res +5 -3
- package/src/adapter/Api/LocalGraphQL_Adapter.res +4 -4
- package/src/adapter/Auth/LocalAuth.res +11 -15
- package/src/adapter/Auth/UserStore.res +5 -7
- package/src/adapter/Backend.res +1 -2
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +42 -26
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +13 -3
- package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +15 -17
- package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +18 -22
- package/src/adapter/Counter/LocalCounterHandler.res +1 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +33 -22
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +57 -57
- package/src/adapter/DomainGraphQL_Server.res +35 -34
- package/src/adapter/DomainMCP_Server.res +45 -41
- package/src/adapter/EventCollector/LocalEventCollectorChannel.res +47 -36
- package/src/adapter/EventHistory/EventHistoryResolvers_GraphQL.res +15 -12
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +9 -4
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +36 -24
- package/src/adapter/EventTopic/LocalEventTopicPublisher.res +9 -3
- package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +9 -14
- package/src/adapter/LocalBus.res +12 -12
- package/src/adapter/LocalCapabilities.res.mjs +1 -1
- package/src/adapter/LocalGeocodeResolvers.res +1 -4
- package/src/adapter/LocalSideEffectHandler.res +1 -1
- package/src/adapter/LocalStateChangeDescriptor.res +2 -1
- package/src/adapter/MCP_ServerInstance.res +51 -39
- package/src/adapter/ObjectStore/LocalObjectStore.res +5 -2
- package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -2
- package/src/adapter/PlatformGraphQL_Server.res +9 -5
- package/src/adapter/ProjectionCheckpoint.res +8 -1
- package/src/adapter/ProjectionPending.res +1 -3
- package/src/adapter/QueryDb/LocalQueryDbStorage.res +2 -1
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +367 -269
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +5 -3
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +63 -40
- package/src/adapter/QueryDb/SortKey_Filter.res +14 -6
- package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +12 -10
- package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +8 -2
- package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +1 -4
- package/src/adapter/Scheduler/LocalScheduledPublisher.res +12 -12
- package/src/adapter/SqliteDriver.res +16 -4
- package/src/adapter/Task/TaskBucket_Sqlite.res +5 -6
- package/src/components/Aggregate_Builder.res +6 -1
- package/src/components/Counter_Builder.res +1 -5
- package/src/components/DcbEventLog_Builder.res +5 -3
- package/src/components/ExtensionPoint_Builder.res +13 -15
- package/src/components/InboundTranslationSlice_Builder.res +3 -2
- package/src/components/OutboundTranslationSlice_Builder.res +3 -2
- package/src/components/ReadModel_Builder.res +18 -20
- package/src/components/StateViewSlice_Builder.res +3 -2
- package/src/components/Task_Builder.res +12 -13
- package/src/reset/LocalSeedReset.res +2 -5
- package/src/test/Mocks/MockDcbEventLogStorage.res +5 -1
- package/src/test/Mocks/MockEventLogStorage.res +8 -3
- package/src/test/Mocks/MockQueryDbStorage.res +3 -2
- package/src/test/TestRunner.res +1 -1
- package/tests/BakedManifestFilesTest.res +7 -10
- package/tests/LocalEventTapTest.res +26 -21
- package/tests/LocalPlatformStartTest.res +33 -28
- package/tests/PluginEventDecodeTest.res +3 -3
- package/tests/ShellConfigTest.res +30 -21
- package/tests/SplitApiFixtures.res +34 -10
- package/tests/SplitApiTest.res +92 -62
- package/tests/adapter/BackendParityTest.res +180 -130
- package/tests/adapter/CollectNEventsTest.res +6 -4
- package/tests/adapter/CommandAuthorizationTest.res +47 -41
- package/tests/adapter/CommandTopicChannelTest.res +163 -139
- package/tests/adapter/CounterHandlerTest.res +95 -77
- package/tests/adapter/DcbEventLogStorageSqliteTest.res +171 -168
- package/tests/adapter/DcbEventLogStorageTest.res +264 -207
- package/tests/adapter/DomainGraphQL_ServerTest.res +53 -43
- package/tests/adapter/EventCollectorChannelTest.res +153 -124
- package/tests/adapter/EventHistoryResolverTest.res +46 -18
- package/tests/adapter/EventLogSnapshotParityTest.res +57 -45
- package/tests/adapter/EventLogStorageSqliteTest.res +9 -10
- package/tests/adapter/EventTapTest.res +37 -20
- package/tests/adapter/EventTopicPublisherTest.res +74 -50
- package/tests/adapter/GraphQLSubscriptionBridgeTest.res +4 -3
- package/tests/adapter/GraphQL_SchemaInspectorTest.res +666 -568
- package/tests/adapter/GraphQL_SubscriptionResolversTest.res +105 -96
- package/tests/adapter/HeartbeatRunnerTest.res +99 -81
- package/tests/adapter/InboundTranslationMutationTest.res +4 -1
- package/tests/adapter/LocalAuthHttpTest.res +79 -85
- package/tests/adapter/LocalAuthLoginTest.res +42 -50
- package/tests/adapter/LocalAuthTest.res +8 -16
- package/tests/adapter/LocalAuthUserStoreTest.res +2 -7
- package/tests/adapter/LocalBusBoundedTest.res +187 -117
- package/tests/adapter/LocalBusPubSubTest.res +118 -76
- package/tests/adapter/LocalDispatchContextTest.res +1 -2
- package/tests/adapter/LocalEvents_ServerTest.res +242 -187
- package/tests/adapter/PgProjectionCatchupTest.res +44 -10
- package/tests/adapter/ProjectionCheckpointTest.res +261 -206
- package/tests/adapter/QueryDbGsiTtlTest.res +123 -83
- package/tests/adapter/QueryDbListPushdownParityTest.res +107 -67
- package/tests/adapter/QueryDbListResolverTest.res +225 -208
- package/tests/adapter/QueryDbResolvedFieldsTest.res +17 -14
- package/tests/adapter/QueryDbStorageSqliteTest.res +33 -49
- package/tests/adapter/QueryDbStorageTest.res +163 -117
- package/tests/adapter/QueryDbUnionRoundTripTest.res +8 -10
- package/tests/adapter/QueryEngineTest.res +179 -126
- package/tests/adapter/ScheduledPublisherTest.res +171 -135
- package/tests/adapter/ServedBucketHttpTest.res +65 -59
- package/tests/adapter/SortKey_FilterTest.res +19 -3
- package/tests/adapter/SqliteDriverTest.res +13 -10
- package/tests/adapter/TaskBucketTest.res +94 -80
- package/tests/components/aggregate/AggregateTest.res +31 -18
- package/tests/components/automationslice/AutomationSliceCallbackTest.res +259 -222
- package/tests/components/automationslice/AutomationSliceFixtures.res +4 -2
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +38 -38
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +12 -14
- package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +8 -32
- package/tests/components/commandgenerator/CommandGeneratorFixtures.res +7 -5
- package/tests/components/commandgenerator/CommandGeneratorTest.res +40 -31
- package/tests/components/commandtopic/CommandTopicFixtures.res +3 -4
- package/tests/components/commandtopic/CommandTopicStreamFixtures.res +7 -5
- package/tests/components/commandtopic/CommandTopicTest.res +24 -15
- package/tests/components/counter/CounterFixtures.res +6 -8
- package/tests/components/dcb/DcbCrossPartitionFixtures.res +8 -2
- package/tests/components/dcb/DcbCrossPartitionReadTest.res +20 -13
- package/tests/components/dcb/DcbEventLogAppendStreamTest.res +12 -8
- package/tests/components/dcb/DcbEventLogStreamTest.res +88 -61
- package/tests/components/dcb/DcbFixtures.res +4 -1
- package/tests/components/dcb/DcbTest.res +9 -6
- package/tests/components/eventcollector/EventCollectorFixtures.res +0 -1
- package/tests/components/eventcollector/EventCollectorTest.res +6 -3
- package/tests/components/eventlog/EventLogAppendStreamTest.res +88 -68
- package/tests/components/eventlog/EventLogConflictTest.res +8 -4
- package/tests/components/eventlog/EventLogFixtures.res +3 -3
- package/tests/components/eventlog/EventLogProvisioningSeamTest.res +26 -13
- package/tests/components/eventlog/EventLogStreamTest.res +119 -56
- package/tests/components/eventlog/EventLogTest.res +20 -4
- package/tests/components/eventtopic/EventTopicFixtures.res +3 -3
- package/tests/components/eventtopic/EventTopicStreamFixtures.res +1 -2
- package/tests/components/eventtopic/EventTopicStreamTest.res +7 -1
- package/tests/components/extensionpoint/ExtensionPointFixtures.res +14 -9
- package/tests/components/extensionpoint/ExtensionPointTest.res +3 -2
- package/tests/components/heartbeat/HeartbeatFixtures.res +5 -6
- package/tests/components/heartbeat/HeartbeatTest.res +56 -47
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +271 -249
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -2
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +361 -276
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -10
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +37 -43
- package/tests/components/querydb/QueryDbFixtures.res +1 -5
- package/tests/components/querydb/QueryDbTest.res +2 -4
- package/tests/components/readmodel/DcbReadModelE2EFixtures.res +15 -18
- package/tests/components/readmodel/MixedSourceReadModelFixtures.res +5 -7
- package/tests/components/readmodel/ReadModelFixtures.res +8 -8
- package/tests/components/scheduler/SchedulerTest.res +76 -58
- package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +4 -4
- package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +5 -7
- package/tests/components/stateviewslice/StateViewSliceFixtures.res +8 -6
- package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +17 -11
- package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +12 -2
- package/tests/components/task/TaskFixtures.res +5 -5
- package/tests/components/task/TaskTest.res +95 -80
- package/tests/plugin/PluginsProjection_GWT.res +3 -1
- package/tests/plugin/UiFragments_GWT.res +6 -1
- package/tests/reset/LocalSeedResetTest.res +1 -0
|
@@ -444,11 +444,8 @@ let build = (db, ~root: string, ~scope: scope): plan => {
|
|
|
444
444
|
// structure mentions, which only a `domain` scope reaches.
|
|
445
445
|
let unattributed = switch scope {
|
|
446
446
|
| OnePlugin(_) =>
|
|
447
|
-
let claimedByAnyPlugin =
|
|
448
|
-
|
|
449
|
-
qdbTables(db)->Array.filter(t =>
|
|
450
|
-
!isPlatformQdb(t) && !(claimedByAnyPlugin->Array.includes(t))
|
|
451
|
-
)
|
|
447
|
+
let claimedByAnyPlugin = plugins->Array.flatMap(p => p.queryables->Array.map(qdbTableName))
|
|
448
|
+
qdbTables(db)->Array.filter(t => !isPlatformQdb(t) && !(claimedByAnyPlugin->Array.includes(t)))
|
|
452
449
|
| Domain | Platform | Everything => []
|
|
453
450
|
}
|
|
454
451
|
|
|
@@ -41,7 +41,11 @@ let matchesQuery = (event: DcbEventLog_Adapter.rawSequencedEvent, query: Reventl
|
|
|
41
41
|
})
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
let make = (
|
|
44
|
+
let make = (
|
|
45
|
+
~name="mock-dcb-log",
|
|
46
|
+
~indexes: array<string>=[],
|
|
47
|
+
~opts: Pulumi.CustomResourceOptions.t={},
|
|
48
|
+
) => {
|
|
45
49
|
let _ = (name, indexes, opts)
|
|
46
50
|
let events: ref<array<DcbEventLog_Adapter.rawSequencedEvent>> = ref([])
|
|
47
51
|
let position = ref(0)
|
|
@@ -36,15 +36,20 @@ let make = (~name as _="mock-event-log", ~opts as _: Pulumi.CustomResourceOption
|
|
|
36
36
|
|
|
37
37
|
let snapshots: dict<ReventlessCore.EventLog.snapshot> = Dict.make()
|
|
38
38
|
|
|
39
|
-
let latestSnapshot: ReventlessCore.EventLog.latestSnapshot<string> = async id =>
|
|
40
|
-
|
|
39
|
+
let latestSnapshot: ReventlessCore.EventLog.latestSnapshot<string> = async id => Ok(
|
|
40
|
+
snapshots->Dict.get(id),
|
|
41
|
+
)
|
|
41
42
|
|
|
42
43
|
let writeSnapshot: ReventlessCore.EventLog.writeSnapshot<string> = async (id, snap) => {
|
|
43
44
|
snapshots->Dict.set(id, snap)
|
|
44
45
|
Ok()
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
let appendStream: ReventlessCore.EventLog.appendStream<string, JSON.t> = (
|
|
48
|
+
let appendStream: ReventlessCore.EventLog.appendStream<string, JSON.t> = (
|
|
49
|
+
_startingSeqNr,
|
|
50
|
+
id,
|
|
51
|
+
stream,
|
|
52
|
+
) =>
|
|
48
53
|
stream->Stream.runForEach(json =>
|
|
49
54
|
Effect.sync(() => {
|
|
50
55
|
let existing = events.contents->Dict.get(id)->Option.getOr([])
|
|
@@ -28,8 +28,9 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
28
28
|
allItems.contents = store.contents->Dict.valuesToArray->Array.flatMap(v => v)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
let load: QueryDb.load<string, JSON.t> = async id =>
|
|
32
|
-
|
|
31
|
+
let load: QueryDb.load<string, JSON.t> = async id => Ok(
|
|
32
|
+
store.contents->Dict.get(id)->Option.getOr([]),
|
|
33
|
+
)
|
|
33
34
|
|
|
34
35
|
let loadStream: QueryDb.loadStream<string, JSON.t> = id =>
|
|
35
36
|
store.contents->Dict.get(id)->Option.getOr([])->Stream.fromIterable
|
package/src/test/TestRunner.res
CHANGED
|
@@ -15,7 +15,7 @@ type mockResult = {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
@module("@pulumi/pulumi") @scope("runtime")
|
|
18
|
-
external setMocks:
|
|
18
|
+
external setMocks: {"newResource": mockArgs => mockResult, "call": mockArgs => JSON.t} => unit =
|
|
19
19
|
"setMocks"
|
|
20
20
|
|
|
21
21
|
// Activate Pulumi mock mode. Must be called once before any Platform.Make() or component creation.
|
|
@@ -16,7 +16,6 @@ let keysOf = (config: ReventlessInfra.Platform.bakedManifest) =>
|
|
|
16
16
|
BakedManifest.files(~config)->Array.map(((key, _)) => key)
|
|
17
17
|
|
|
18
18
|
describe("BakedManifest.files", () => {
|
|
19
|
-
|
|
20
19
|
// The regression line: one declaration, one file, under the name every
|
|
21
20
|
// existing deployment's config.json already points at.
|
|
22
21
|
testSync("a declaration with no journeys produces exactly one file", () => {
|
|
@@ -39,11 +38,7 @@ describe("BakedManifest.files", () => {
|
|
|
39
38
|
{group: "Fulfilment", components: [sel("Ordering")], key: "fulfil.json"},
|
|
40
39
|
],
|
|
41
40
|
}),
|
|
42
|
-
)->toEqual([
|
|
43
|
-
"component-manifest.json",
|
|
44
|
-
"component-manifest-shopper.json",
|
|
45
|
-
"fulfil.json",
|
|
46
|
-
])
|
|
41
|
+
)->toEqual(["component-manifest.json", "component-manifest-shopper.json", "fulfil.json"])
|
|
47
42
|
})
|
|
48
43
|
|
|
49
44
|
// A group name is a Cognito identifier and a key is part of a URL, so the
|
|
@@ -55,10 +50,12 @@ describe("BakedManifest.files", () => {
|
|
|
55
50
|
})
|
|
56
51
|
|
|
57
52
|
testSync("carries each journey's own selections", () => {
|
|
58
|
-
let files = BakedManifest.files(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
let files = BakedManifest.files(
|
|
54
|
+
~config={
|
|
55
|
+
components: [sel("Catalog")],
|
|
56
|
+
journeys: [{group: "Fulfilment", components: [sel("Ordering")]}],
|
|
57
|
+
},
|
|
58
|
+
)
|
|
62
59
|
expect(files->Array.map(((_, sels)) => sels->Array.map(s => s.plugin)))->toEqual([
|
|
63
60
|
["Catalog"],
|
|
64
61
|
["Ordering"],
|
|
@@ -47,8 +47,8 @@ describe("LocalEventTap.settingFromEnv", () => {
|
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
testSync("switches the socket off on request", () =>
|
|
50
|
-
["off", "false", "none"]->Array.forEach(
|
|
51
|
-
expect(LocalEventTap.settingFromEnv(~env=envWith(Some(v))))->toEqual(LocalEventTap.Off)
|
|
50
|
+
["off", "false", "none"]->Array.forEach(
|
|
51
|
+
v => expect(LocalEventTap.settingFromEnv(~env=envWith(Some(v))))->toEqual(LocalEventTap.Off),
|
|
52
52
|
)
|
|
53
53
|
)
|
|
54
54
|
})
|
|
@@ -113,8 +113,8 @@ describe("LocalEventTap.start", () => {
|
|
|
113
113
|
// picks, reported only once it is actually bound.
|
|
114
114
|
test("binds an ephemeral port by default and reports the one it got", async () => {
|
|
115
115
|
LocalEventTap.resetForTests()
|
|
116
|
-
let bound = await Promise.make(
|
|
117
|
-
LocalEventTap.start(~env=envWith(None), ~onBound=p => resolve(p), ())
|
|
116
|
+
let bound = await Promise.make(
|
|
117
|
+
(resolve, _reject) => LocalEventTap.start(~env=envWith(None), ~onBound=p => resolve(p), ()),
|
|
118
118
|
)
|
|
119
119
|
expect(bound > 0)->toEqual(true)
|
|
120
120
|
// What the registry entry will carry — the real port, not a hoped-for one.
|
|
@@ -127,26 +127,31 @@ describe("LocalEventTap.start", () => {
|
|
|
127
127
|
test("serves the lines it is sent on the port it was named", async () => {
|
|
128
128
|
LocalEventTap.resetForTests()
|
|
129
129
|
let port = 47311
|
|
130
|
-
let bound = await Promise.make(
|
|
131
|
-
|
|
130
|
+
let bound = await Promise.make(
|
|
131
|
+
(resolve, _reject) =>
|
|
132
|
+
LocalEventTap.start(~env=envWith(Some(port->Int.toString)), ~onBound=p => resolve(p), ()),
|
|
132
133
|
)
|
|
133
134
|
expect(bound)->toEqual(port)
|
|
134
135
|
|
|
135
|
-
let received = await Promise.make(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
136
|
+
let received = await Promise.make(
|
|
137
|
+
(resolve, _reject) => {
|
|
138
|
+
let socket = NodeNet.connect(port, "127.0.0.1", () => ())
|
|
139
|
+
socket->NodeNet.setEncoding("utf8")
|
|
140
|
+
socket->NodeNet.onSocketError(
|
|
141
|
+
e => resolve("connect failed: " ++ e->JsExn.message->Option.getOr("unknown")),
|
|
142
|
+
)
|
|
143
|
+
// Re-sent until it lands: the server's accept and the client's connect are
|
|
144
|
+
// separate events, so a single broadcast could beat the connection.
|
|
145
|
+
let ticker = ref(None)
|
|
146
|
+
socket->NodeNet.onSocketData(
|
|
147
|
+
chunk => {
|
|
148
|
+
ticker.contents->Option.forEach(clearInterval)
|
|
149
|
+
resolve(chunk)
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
ticker := Some(setInterval(() => LocalEventTap.broadcast(`@@RVLESS_EVT@@ {"seq":7}`), 5))
|
|
153
|
+
},
|
|
154
|
+
)
|
|
150
155
|
|
|
151
156
|
expect(received)->toEqual(`@@RVLESS_EVT@@ {"seq":7}\n`)
|
|
152
157
|
await LocalEventTap.stopForTests()
|
|
@@ -122,42 +122,47 @@ describe("LocalPlatformStart.orAddressRunning", () => {
|
|
|
122
122
|
|
|
123
123
|
describe("LocalPlatformStart.decide", () => {
|
|
124
124
|
testSync("starts where nothing is running", () =>
|
|
125
|
-
withoutBypass(
|
|
126
|
-
|
|
125
|
+
withoutBypass(
|
|
126
|
+
() =>
|
|
127
|
+
expect(LocalPlatformStart.decide(~cwd=tempRoot(), ()))->toEqual(LocalPlatformStart.Start),
|
|
127
128
|
)
|
|
128
129
|
)
|
|
129
130
|
|
|
130
131
|
testSync("addresses the one platform already serving this directory", () =>
|
|
131
|
-
withoutBypass(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
withoutBypass(
|
|
133
|
+
() => {
|
|
134
|
+
let cwd = tempRoot()
|
|
135
|
+
let _ = writeAt(~cwd, ~port=4000, ~store=sqliteStore(NodePath.join([cwd, "local.db"])))
|
|
136
|
+
|
|
137
|
+
switch LocalPlatformStart.decide(~cwd, ()) {
|
|
138
|
+
| Start => fail("expected the running platform to be addressed")
|
|
139
|
+
| AlreadyRunning(entries) =>
|
|
140
|
+
expect(entries->Array.map(e => e.port))->toEqual([4000])
|
|
141
|
+
let lines = LocalPlatformStart.report(entries)
|
|
142
|
+
expect(lines->Array.length)->toEqual(2)
|
|
143
|
+
expect(lines->Array.join("\n")->String.includes(":4000"))->toEqual(true)
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
)
|
|
144
147
|
)
|
|
145
148
|
|
|
146
149
|
// The state this is trying to make impossible: say so rather than pick one.
|
|
147
150
|
testSync("names all of them when two are running", () =>
|
|
148
|
-
withoutBypass(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
151
|
+
withoutBypass(
|
|
152
|
+
() => {
|
|
153
|
+
let cwd = tempRoot()
|
|
154
|
+
let _ = writeAt(~cwd, ~port=4000, ~store=sqliteStore(NodePath.join([cwd, "local.db"])))
|
|
155
|
+
let _ = writeAt(~cwd, ~port=4010, ~store=memoryStore)
|
|
156
|
+
|
|
157
|
+
switch LocalPlatformStart.decide(~cwd, ()) {
|
|
158
|
+
| Start => fail("expected both running platforms to be reported")
|
|
159
|
+
| AlreadyRunning(entries) =>
|
|
160
|
+
let printed = LocalPlatformStart.report(entries)->Array.join("\n")
|
|
161
|
+
expect(printed->String.includes(":4000"))->toEqual(true)
|
|
162
|
+
expect(printed->String.includes(":4010"))->toEqual(true)
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
)
|
|
161
166
|
)
|
|
162
167
|
|
|
163
168
|
// The escape hatch the e2e suites and the VS Code runner already take.
|
|
@@ -93,9 +93,9 @@ describe("Platform.decodeUiFragmentRegistryEventEnvelope", () => {
|
|
|
93
93
|
})
|
|
94
94
|
|
|
95
95
|
testSync("returns None for malformed input", () => {
|
|
96
|
-
expect(
|
|
97
|
-
|
|
98
|
-
)
|
|
96
|
+
expect(Platform.decodeUiFragmentRegistryEventEnvelope(JSON.Encode.string("junk")))->toEqual(
|
|
97
|
+
None,
|
|
98
|
+
)
|
|
99
99
|
expect(
|
|
100
100
|
Platform.decodeUiFragmentRegistryEventEnvelope(
|
|
101
101
|
JSON.Encode.object(Dict.fromArray([("event", JSON.Encode.object(Dict.make()))])),
|
|
@@ -11,7 +11,7 @@ let _ = TestRunner.setup()
|
|
|
11
11
|
|
|
12
12
|
let manifest = (~key: option<string>=?): ReventlessInfra.Platform.bakedManifest => {
|
|
13
13
|
components: [{plugin: "Catalog", views: ["Products"], commands: []}],
|
|
14
|
-
|
|
14
|
+
?key,
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
let str = (d: dict<JSON.t>, k: string): option<string> =>
|
|
@@ -65,13 +65,14 @@ describe("ShellConfig.overlay", () => {
|
|
|
65
65
|
// did not write, with nothing in the diff to say so.
|
|
66
66
|
testSync("refuses a shellConfig key the platform computes", () =>
|
|
67
67
|
expect(
|
|
68
|
-
threw(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
threw(
|
|
69
|
+
() =>
|
|
70
|
+
ShellConfig.overlay(
|
|
71
|
+
~bakedManifest=Some(manifest()),
|
|
72
|
+
~shellConfig=Some(
|
|
73
|
+
Dict.fromArray([("manifestUrl", JSON.Encode.string("/elsewhere.json"))]),
|
|
74
|
+
),
|
|
75
|
+
)->ignore,
|
|
75
76
|
),
|
|
76
77
|
)->toBe(true)
|
|
77
78
|
)
|
|
@@ -107,12 +108,15 @@ describe("ShellConfig.overlay — uiSlotsFile", () => {
|
|
|
107
108
|
|
|
108
109
|
testSync("refuses a shellConfig key redirecting the computed module", () =>
|
|
109
110
|
expect(
|
|
110
|
-
threw(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
threw(
|
|
112
|
+
() =>
|
|
113
|
+
ShellConfig.overlay(
|
|
114
|
+
~bakedManifest=None,
|
|
115
|
+
~uiSlotsFile="/src/storefront-slots.js",
|
|
116
|
+
~shellConfig=Some(
|
|
117
|
+
Dict.fromArray([("uiSlotsUrl", JSON.Encode.string("/elsewhere.js"))]),
|
|
118
|
+
),
|
|
119
|
+
)->ignore,
|
|
116
120
|
),
|
|
117
121
|
)->toBe(true)
|
|
118
122
|
)
|
|
@@ -208,7 +212,6 @@ let fulfilment: ReventlessInfra.Platform.bakedJourney = {
|
|
|
208
212
|
}
|
|
209
213
|
|
|
210
214
|
describe("ShellConfig.overlay — journeys", () => {
|
|
211
|
-
|
|
212
215
|
// The regression line. A shell that has never heard of journeys must not
|
|
213
216
|
// suddenly find a key it does not know.
|
|
214
217
|
testSync("a bake declaring no journeys writes no map", () => {
|
|
@@ -217,7 +220,10 @@ describe("ShellConfig.overlay — journeys", () => {
|
|
|
217
220
|
})
|
|
218
221
|
|
|
219
222
|
testSync("an empty journeys array is the same as none", () => {
|
|
220
|
-
let out = ShellConfig.overlay(
|
|
223
|
+
let out = ShellConfig.overlay(
|
|
224
|
+
~bakedManifest=Some(withJourneys(~journeys=[])),
|
|
225
|
+
~shellConfig=None,
|
|
226
|
+
)
|
|
221
227
|
expect(out->Dict.get("journeyManifestUrls"))->toEqual(None)
|
|
222
228
|
})
|
|
223
229
|
|
|
@@ -250,11 +256,14 @@ describe("ShellConfig.overlay — journeys", () => {
|
|
|
250
256
|
// `manifestUrl` already carries, extended to the map it now writes beside it.
|
|
251
257
|
testSync("refuses a shellConfig that sets the journey map itself", () =>
|
|
252
258
|
expect(
|
|
253
|
-
threw(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
259
|
+
threw(
|
|
260
|
+
() =>
|
|
261
|
+
ShellConfig.overlay(
|
|
262
|
+
~bakedManifest=Some(withJourneys(~journeys=[shopper])),
|
|
263
|
+
~shellConfig=Some(
|
|
264
|
+
Dict.fromArray([("journeyManifestUrls", JSON.Encode.object(Dict.make()))]),
|
|
265
|
+
),
|
|
266
|
+
)->ignore,
|
|
258
267
|
),
|
|
259
268
|
)->toBe(true)
|
|
260
269
|
)
|
|
@@ -31,7 +31,9 @@ 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(
|
|
34
|
+
let baseParts = ReventlessCore.GraphQL_Stitcher.decode(
|
|
35
|
+
ReventlessCore.Platform_AdminApi.baseFragment(~cloner=true),
|
|
36
|
+
)
|
|
35
37
|
let () = adminGraphQL.registerTypes(~sdlTypes=baseParts.types)
|
|
36
38
|
|
|
37
39
|
// Derive query/mutation field names directly from SDL so that any SDL
|
|
@@ -45,16 +47,23 @@ let singleQueryField = adminQueryEntry.singleFieldName
|
|
|
45
47
|
let listQueryField = adminQueryEntry.listFieldName
|
|
46
48
|
|
|
47
49
|
let adminQueryResolvers = Dict.make()
|
|
48
|
-
let () =
|
|
49
|
-
|
|
50
|
-
)
|
|
50
|
+
let () =
|
|
51
|
+
adminQueryFieldNames->Array.forEach(field =>
|
|
52
|
+
adminQueryResolvers->Dict.set(field, async (_root, _args, _ctx): JSON.t => JSON.Encode.null)
|
|
53
|
+
)
|
|
51
54
|
let () = adminGraphQL.registerQueries(~sdlFields=baseParts.queries, ~resolvers=adminQueryResolvers)
|
|
52
55
|
|
|
53
56
|
let adminMutationResolvers = Dict.make()
|
|
54
|
-
let () =
|
|
55
|
-
|
|
57
|
+
let () =
|
|
58
|
+
adminMutationFieldNames->Array.forEach(field =>
|
|
59
|
+
adminMutationResolvers->Dict.set(field, async (_root, _args, _ctx): JSON.t =>
|
|
60
|
+
JSON.Encode.string("ok")
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
let () = adminGraphQL.registerMutations(
|
|
64
|
+
~sdlFields=baseParts.mutations,
|
|
65
|
+
~resolvers=adminMutationResolvers,
|
|
56
66
|
)
|
|
57
|
-
let () = adminGraphQL.registerMutations(~sdlFields=baseParts.mutations, ~resolvers=adminMutationResolvers)
|
|
58
67
|
|
|
59
68
|
// ─────────────────────────────────────────────────────────────
|
|
60
69
|
// Register fake plugin schema into the singleton (plugin server)
|
|
@@ -66,8 +75,18 @@ let pluginTypes = [`type SplitTestPlugin_SplitTestItem { id: ID!, name: String!
|
|
|
66
75
|
let () = GraphQL_Server.registerTypes(~sdlTypes=pluginTypes)
|
|
67
76
|
|
|
68
77
|
let pluginQueryResolvers = Dict.make()
|
|
69
|
-
let () =
|
|
70
|
-
|
|
78
|
+
let () =
|
|
79
|
+
pluginQueryResolvers->Dict.set("SplitTestPlugin_SplitTestItem", async (
|
|
80
|
+
_root,
|
|
81
|
+
_args,
|
|
82
|
+
_ctx,
|
|
83
|
+
): JSON.t => JSON.Encode.null)
|
|
84
|
+
let () =
|
|
85
|
+
pluginQueryResolvers->Dict.set("SplitTestPlugin_SplitTestItems", async (
|
|
86
|
+
_root,
|
|
87
|
+
_args,
|
|
88
|
+
_ctx,
|
|
89
|
+
): JSON.t => JSON.Encode.null)
|
|
71
90
|
let () = GraphQL_Server.registerQueries(
|
|
72
91
|
~sdlFields=[
|
|
73
92
|
`SplitTestPlugin_SplitTestItem(id: ID!): SplitTestPlugin_SplitTestItem`,
|
|
@@ -77,7 +96,12 @@ let () = GraphQL_Server.registerQueries(
|
|
|
77
96
|
)
|
|
78
97
|
|
|
79
98
|
let pluginMutationResolvers = Dict.make()
|
|
80
|
-
let () =
|
|
99
|
+
let () =
|
|
100
|
+
pluginMutationResolvers->Dict.set("SplitTestPlugin_SplitTestItem_CreateItem", async (
|
|
101
|
+
_root,
|
|
102
|
+
_args,
|
|
103
|
+
_ctx,
|
|
104
|
+
): JSON.t => JSON.Encode.string("ok"))
|
|
81
105
|
let () = GraphQL_Server.registerMutations(
|
|
82
106
|
~sdlFields=[`SplitTestPlugin_SplitTestItem_CreateItem(id: ID!, name: String!): String`],
|
|
83
107
|
~resolvers=pluginMutationResolvers,
|
package/tests/SplitApiTest.res
CHANGED
|
@@ -25,78 +25,108 @@ let pluginQueryPrefix = "SplitTestPlugin_SplitTestItem"
|
|
|
25
25
|
|
|
26
26
|
describe("Split API Mode — Schema Separation", () => {
|
|
27
27
|
describe("Plugin GraphQL singleton", () => {
|
|
28
|
-
testPromise(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
d
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
testPromise(
|
|
29
|
+
"contains plugin mutation fields",
|
|
30
|
+
async () => {
|
|
31
|
+
let d = GraphQL_Server.diagnostics()
|
|
32
|
+
let hasPluginMutation =
|
|
33
|
+
d.registeredMutationFields->Array.some(f => f->String.startsWith(pluginMutationPrefix))
|
|
34
|
+
expect(hasPluginMutation)->toBe(true)
|
|
35
|
+
},
|
|
36
|
+
)
|
|
34
37
|
|
|
35
|
-
testPromise(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
d
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
testPromise(
|
|
39
|
+
"contains plugin query fields",
|
|
40
|
+
async () => {
|
|
41
|
+
let d = GraphQL_Server.diagnostics()
|
|
42
|
+
let hasPluginQuery =
|
|
43
|
+
d.registeredQueryFields->Array.some(f => f->String.startsWith(pluginQueryPrefix))
|
|
44
|
+
expect(hasPluginQuery)->toBe(true)
|
|
45
|
+
},
|
|
46
|
+
)
|
|
41
47
|
|
|
42
|
-
testPromise(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
d.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
testPromise(
|
|
49
|
+
"does NOT contain admin mutation fields",
|
|
50
|
+
async () => {
|
|
51
|
+
let d = GraphQL_Server.diagnostics()
|
|
52
|
+
let hasAdminField =
|
|
53
|
+
d.registeredMutationFields->Array.some(f => adminMutationFields->Array.includes(f))
|
|
54
|
+
expect(hasAdminField)->toBe(false)
|
|
55
|
+
},
|
|
56
|
+
)
|
|
50
57
|
|
|
51
|
-
testPromise(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
d.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
testPromise(
|
|
59
|
+
"does NOT contain admin query fields",
|
|
60
|
+
async () => {
|
|
61
|
+
let d = GraphQL_Server.diagnostics()
|
|
62
|
+
let hasAdminQuery =
|
|
63
|
+
d.registeredQueryFields->Array.some(f => adminQueryFields->Array.includes(f))
|
|
64
|
+
expect(hasAdminQuery)->toBe(false)
|
|
65
|
+
},
|
|
66
|
+
)
|
|
59
67
|
})
|
|
60
68
|
|
|
61
69
|
describe("Admin GraphQL instance", () => {
|
|
62
|
-
testPromise(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
let
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
testPromise(
|
|
71
|
+
"contains admin mutation fields",
|
|
72
|
+
async () => {
|
|
73
|
+
let d = adminGraphQL.diagnostics()
|
|
74
|
+
adminMutationFields->Array.forEach(
|
|
75
|
+
field => {
|
|
76
|
+
let hasField = d.registeredMutationFields->Array.includes(field)
|
|
77
|
+
expect(hasField)->toBe(true)
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
},
|
|
81
|
+
)
|
|
69
82
|
|
|
70
|
-
testPromise(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
let
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
testPromise(
|
|
84
|
+
"contains admin query fields",
|
|
85
|
+
async () => {
|
|
86
|
+
let d = adminGraphQL.diagnostics()
|
|
87
|
+
adminQueryFields->Array.forEach(
|
|
88
|
+
field => {
|
|
89
|
+
let hasField = d.registeredQueryFields->Array.includes(field)
|
|
90
|
+
expect(hasField)->toBe(true)
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
},
|
|
94
|
+
)
|
|
77
95
|
|
|
78
|
-
testPromise(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
d
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
testPromise(
|
|
97
|
+
"does NOT contain plugin mutation fields",
|
|
98
|
+
async () => {
|
|
99
|
+
let d = adminGraphQL.diagnostics()
|
|
100
|
+
let hasPluginMutation =
|
|
101
|
+
d.registeredMutationFields->Array.some(f => f->String.startsWith(pluginMutationPrefix))
|
|
102
|
+
expect(hasPluginMutation)->toBe(false)
|
|
103
|
+
},
|
|
104
|
+
)
|
|
84
105
|
|
|
85
|
-
testPromise(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
d
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
testPromise(
|
|
107
|
+
"does NOT contain plugin query fields",
|
|
108
|
+
async () => {
|
|
109
|
+
let d = adminGraphQL.diagnostics()
|
|
110
|
+
let hasPluginQuery =
|
|
111
|
+
d.registeredQueryFields->Array.some(f => f->String.startsWith(pluginQueryPrefix))
|
|
112
|
+
expect(hasPluginQuery)->toBe(false)
|
|
113
|
+
},
|
|
114
|
+
)
|
|
91
115
|
|
|
92
|
-
testPromise(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
116
|
+
testPromise(
|
|
117
|
+
"has admin type definitions registered",
|
|
118
|
+
async () => {
|
|
119
|
+
let d = adminGraphQL.diagnostics()
|
|
120
|
+
expect(d.typeCount > 0)->toBe(true)
|
|
121
|
+
},
|
|
122
|
+
)
|
|
96
123
|
|
|
97
|
-
testPromise(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
124
|
+
testPromise(
|
|
125
|
+
"has no SDL mismatches",
|
|
126
|
+
async () => {
|
|
127
|
+
let d = adminGraphQL.diagnostics()
|
|
128
|
+
expect(d.mismatches->Array.length)->toBe(0)
|
|
129
|
+
},
|
|
130
|
+
)
|
|
101
131
|
})
|
|
102
132
|
})
|