@reventlessdev/reventless-local 3.0.0-alpha.138 → 3.0.0-alpha.140
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 +15 -0
- package/package.json +10 -9
- package/rescript.json +1 -0
- package/src/Platform.res +3 -3
- package/src/adapter/Api/LocalGraphQL_SubscriptionResolvers.res +27 -54
- package/src/adapter/Api/LocalGraphQL_SubscriptionResolvers.res.mjs +13 -26
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +5 -5
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +2 -2
- package/src/adapter/DomainGraphQL_Server.res +6 -6
- package/src/adapter/PlatformGraphQL_Server.res +6 -6
- package/src/adapter/PlatformGraphQL_Server.res.mjs +2 -2
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +11 -11
- package/tests/SplitApiFixtures.res +4 -4
- package/tests/SplitApiFixtures.res.mjs +2 -2
- package/tests/SplitApiTest.res +2 -2
- package/tests/adapter/GraphQLSubscriptionBridgeTest.res +120 -0
- package/tests/adapter/GraphQLSubscriptionBridgeTest.res.mjs +96 -0
- package/src/adapter/GraphQL_ServerInstance.res +0 -310
- package/src/adapter/GraphQL_ServerInstance.res.mjs +0 -257
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
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.140 (2026-07-11)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @reventlessdev/reventless-local
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 3.0.0-alpha.139 (2026-07-11)
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **graphql-server:** promote reusable GraphQL server runtime into shared package ([53dd0d5](https://github.com/ReventlessDev/reventless-core/commit/53dd0d54585dcc10d75322a76050aa8c06cec4fb))
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.138 (2026-07-11)
|
|
7
22
|
|
|
8
23
|
**Note:** Version bump only for package @reventlessdev/reventless-local
|
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.140",
|
|
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,16 +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.
|
|
35
|
-
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.
|
|
34
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.26",
|
|
35
|
+
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.19",
|
|
36
36
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.6",
|
|
37
|
-
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.17",
|
|
38
37
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
|
|
39
|
-
"@reventlessdev/
|
|
40
|
-
"@reventlessdev/reventless-
|
|
41
|
-
"@reventlessdev/reventless-
|
|
42
|
-
"@reventlessdev/reventless-
|
|
43
|
-
"@reventlessdev/reventless-
|
|
38
|
+
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.17",
|
|
39
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.155",
|
|
40
|
+
"@reventlessdev/reventless-graphql-server": "1.0.0-alpha.2",
|
|
41
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.100",
|
|
42
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.96",
|
|
43
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.19",
|
|
44
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.74"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"rescript": "^12.3.0",
|
package/rescript.json
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@reventlessdev/rescript-pulumi-pulumi",
|
|
30
30
|
"@reventlessdev/rescript-graphql-yoga",
|
|
31
31
|
"@reventlessdev/rescript-mcp-sdk",
|
|
32
|
+
"@reventlessdev/reventless-graphql-server",
|
|
32
33
|
"@reventlessdev/reventless-infra",
|
|
33
34
|
"@reventlessdev/reventless-spec",
|
|
34
35
|
"@reventlessdev/reventless-core",
|
package/src/Platform.res
CHANGED
|
@@ -21,7 +21,7 @@ let log = ReventlessCore.Logger.fromEnv()
|
|
|
21
21
|
|
|
22
22
|
// Module-level ref to hold the platform GraphQL server instance in split mode.
|
|
23
23
|
// Populated by makePlatform when splitApi=true.
|
|
24
|
-
let platformGraphQLRef: ref<option<GraphQL_ServerInstance.t>> = ref(None)
|
|
24
|
+
let platformGraphQLRef: ref<option<ReventlessGraphqlServer.GraphQL_ServerInstance.t>> = ref(None)
|
|
25
25
|
let getPlatformGraphQL = () => platformGraphQLRef.contents
|
|
26
26
|
|
|
27
27
|
// Module-level ref to hold the platform MCP server instance in split mode.
|
|
@@ -144,7 +144,7 @@ module MakeWithConfig = (
|
|
|
144
144
|
|
|
145
145
|
// Track which GraphQL servers have had admin schema (Plugin queries/mutations) registered.
|
|
146
146
|
// Prevents double-registration when makePlatform and deployPlugin both target the same server.
|
|
147
|
-
let adminRegisteredServers: ref<array<GraphQL_ServerInstance.t>> = ref([])
|
|
147
|
+
let adminRegisteredServers: ref<array<ReventlessGraphqlServer.GraphQL_ServerInstance.t>> = ref([])
|
|
148
148
|
|
|
149
149
|
// Relay support record for domain QueryDbs (platform QueryDbs pass None).
|
|
150
150
|
let domainRelaySupport: QueryDbResolvers_GraphQL.relaySupport = {
|
|
@@ -157,7 +157,7 @@ module MakeWithConfig = (
|
|
|
157
157
|
// Resolve the active GraphQL server based on current deploy target.
|
|
158
158
|
// In unified mode always routes to DomainGraphQL_Server (single server).
|
|
159
159
|
// In split mode routes based on currentDeployTarget.
|
|
160
|
-
let resolveTargetGraphQL = (): GraphQL_ServerInstance.t =>
|
|
160
|
+
let resolveTargetGraphQL = (): ReventlessGraphqlServer.GraphQL_ServerInstance.t =>
|
|
161
161
|
if !Config.splitApi {
|
|
162
162
|
DomainGraphQL_Server.asInterface
|
|
163
163
|
} else {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
// LocalGraphQL_SubscriptionResolvers.res
|
|
2
|
-
//
|
|
2
|
+
// In-process wiring of the local event bus into the promoted, transport-neutral
|
|
3
|
+
// GraphQL subscription bridge (`ReventlessGraphqlServer.GraphQL_SubscriptionBridge`).
|
|
4
|
+
//
|
|
5
|
+
// The generic pieces — topic naming, the field-resolver builder, `registerAll`,
|
|
6
|
+
// and the `@aws_subscribe` stripping — live in core-adjacent
|
|
7
|
+
// `GraphQL_SubscriptionBridge`, parameterized over a PubSub instance. This
|
|
8
|
+
// module supplies the in-process `GraphqlYoga.createPubSub()` singleton and the
|
|
9
|
+
// Bus→PubSub glue that is specific to the local in-process backend:
|
|
3
10
|
//
|
|
4
11
|
// Source A (raw event stream): subscribes to Bus.subscribeToEvents for each event log
|
|
5
12
|
// topic and publishes the event envelope to a yoga PubSub channel.
|
|
@@ -11,15 +18,17 @@
|
|
|
11
18
|
// after each successful command. This is left as future work — currently mutations do
|
|
12
19
|
// not cross-post to the subscription PubSub in-memory.
|
|
13
20
|
//
|
|
14
|
-
// Usage:
|
|
15
|
-
// server instance. Both must be called
|
|
16
|
-
//
|
|
17
|
-
// Note: `scalar AWSJSON` is injected here so yoga accepts the AppSync scalar used in
|
|
18
|
-
// Source A event log types. It serialises as a plain string.
|
|
21
|
+
// Usage: wire the bridges (bridgeSourceA / bridgeSourceB), then `registerAll` to
|
|
22
|
+
// wire subscriptions into the GraphQL server instance. Both must be called
|
|
23
|
+
// before the server starts.
|
|
19
24
|
|
|
20
25
|
module YG = GraphqlYoga
|
|
26
|
+
module Bridge = ReventlessGraphqlServer.GraphQL_SubscriptionBridge
|
|
21
27
|
|
|
22
28
|
// ── PubSub instance ───────────────────────────────────────────────────────────
|
|
29
|
+
// The single in-process PubSub used by every local server. Cross-process
|
|
30
|
+
// delivery is not a concern in-memory, so this is byte-identical to the pre-
|
|
31
|
+
// promotion behavior: one process, one PubSub, topics disambiguate channels.
|
|
23
32
|
|
|
24
33
|
let pubSub: ref<option<YG.pubSub>> = ref(None)
|
|
25
34
|
|
|
@@ -32,13 +41,10 @@ let getPubSub = () =>
|
|
|
32
41
|
ps
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
// ── Topic naming
|
|
36
|
-
// Matches the field names generated by Plugin_SubscriptionSchema:
|
|
37
|
-
// Source A: on{DisplayName}EventLog_eventAppended
|
|
38
|
-
// Source B: on{ReturnTypeName}_stateChanged
|
|
44
|
+
// ── Topic naming (re-exported from the promoted bridge) ────────────────────────
|
|
39
45
|
|
|
40
|
-
let sourceATopic =
|
|
41
|
-
let sourceBTopic =
|
|
46
|
+
let sourceATopic = Bridge.sourceATopic
|
|
47
|
+
let sourceBTopic = Bridge.sourceBTopic
|
|
42
48
|
|
|
43
49
|
// ── Bridge: Bus → PubSub ──────────────────────────────────────────────────────
|
|
44
50
|
|
|
@@ -73,26 +79,14 @@ let bridgeSourceB = (
|
|
|
73
79
|
})
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
// ── Subscription resolver builder
|
|
77
|
-
//
|
|
78
|
-
// Builds the yoga subscription resolver object for one field:
|
|
79
|
-
// { subscribe: (_root, _args) => pubSub.subscribe(topic), resolve: x => x }
|
|
80
|
-
//
|
|
81
|
-
// The resolver dict value is typed as JSON.t (the generic resolverFn) and cast via
|
|
82
|
-
// Obj.magic — yoga's createSchema accepts { subscribe, resolve } objects for the
|
|
83
|
-
// Subscription type, not plain functions. This is safe at runtime.
|
|
82
|
+
// ── Subscription resolver builder (delegates to the promoted bridge) ───────────
|
|
84
83
|
|
|
85
|
-
let makeFieldResolver = (topic: string): YG.resolverFn =>
|
|
86
|
-
|
|
87
|
-
Obj.magic({
|
|
88
|
-
"subscribe": (_root: JSON.t, _args: JSON.t, _ctx: JSON.t) => ps->YG.pubSubSubscribe(topic),
|
|
89
|
-
"resolve": (payload: JSON.t) => payload,
|
|
90
|
-
})
|
|
91
|
-
}
|
|
84
|
+
let makeFieldResolver = (topic: string): YG.resolverFn =>
|
|
85
|
+
Bridge.makeFieldResolver(~pubSub=getPubSub(), topic)
|
|
92
86
|
|
|
93
|
-
// ── Public API
|
|
87
|
+
// ── Public API (delegates to the promoted bridge, binding the local PubSub) ────
|
|
94
88
|
|
|
95
|
-
type subscriptionEntry = {
|
|
89
|
+
type subscriptionEntry = Bridge.subscriptionEntry = {
|
|
96
90
|
fieldName: string,
|
|
97
91
|
topic: string,
|
|
98
92
|
}
|
|
@@ -110,36 +104,15 @@ type subscriptionEntry = {
|
|
|
110
104
|
* (after stripping `@aws_subscribe` directives — not valid in yoga).
|
|
111
105
|
*/
|
|
112
106
|
let registerAll = (
|
|
113
|
-
~server: GraphQL_ServerInstance.t,
|
|
107
|
+
~server: ReventlessGraphqlServer.GraphQL_ServerInstance.t,
|
|
114
108
|
~sdlFields: array<string>,
|
|
115
109
|
~sourceAEntries: array<subscriptionEntry>,
|
|
116
110
|
~sourceBEntries: array<subscriptionEntry>,
|
|
117
|
-
) =>
|
|
118
|
-
// Strip @aws_subscribe directives — only valid in AppSync, not yoga
|
|
119
|
-
let cleanedFields =
|
|
120
|
-
sdlFields->Array.map(field =>
|
|
121
|
-
field->String.split("\n")->Array.filter(line => !(line->String.includes("@aws_subscribe")))->Array.join("\n")
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
let resolvers: dict<YG.resolverFn> = Dict.make()
|
|
125
|
-
|
|
126
|
-
sourceAEntries->Array.forEach(({fieldName, topic}) => {
|
|
127
|
-
resolvers->Dict.set(fieldName, makeFieldResolver(topic))
|
|
128
|
-
})
|
|
129
|
-
sourceBEntries->Array.forEach(({fieldName, topic}) => {
|
|
130
|
-
resolvers->Dict.set(fieldName, makeFieldResolver(topic))
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
server.registerSubscriptions(~sdlFields=cleanedFields, ~resolvers)
|
|
134
|
-
|
|
135
|
-
// Inject scalar AWSJSON so yoga accepts it in Source A event log types
|
|
136
|
-
server.registerTypes(~sdlTypes=["scalar AWSJSON"])
|
|
137
|
-
}
|
|
111
|
+
) => Bridge.registerAll(~server, ~sdlFields, ~sourceAEntries, ~sourceBEntries, ~pubSub=getPubSub())
|
|
138
112
|
|
|
139
113
|
/** Publish a payload to a named PubSub topic (for Source C mutation resolvers). */
|
|
140
|
-
let publish = (topic: string, payload: JSON.t) =>
|
|
141
|
-
getPubSub()
|
|
142
|
-
}
|
|
114
|
+
let publish = (topic: string, payload: JSON.t) =>
|
|
115
|
+
Bridge.publish(~pubSub=getPubSub(), topic, payload)
|
|
143
116
|
|
|
144
117
|
let reset = () => {
|
|
145
118
|
pubSub.contents = None
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as GraphqlYoga from "graphql-yoga";
|
|
4
4
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
5
|
+
import * as GraphQL_SubscriptionBridge$ReventlessGraphqlServer from "@reventlessdev/reventless-graphql-server/src/GraphQL_SubscriptionBridge.res.mjs";
|
|
5
6
|
|
|
6
7
|
let pubSub = {
|
|
7
8
|
contents: undefined
|
|
@@ -17,17 +18,9 @@ function getPubSub() {
|
|
|
17
18
|
return ps$1;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
function sourceATopic(displayName) {
|
|
21
|
-
return `on` + displayName + `EventLog_eventAppended`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function sourceBTopic(returnTypeName) {
|
|
25
|
-
return `on` + returnTypeName + `_stateChanged`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
function bridgeSourceA(subscribeToEvents, displayName, busTopicName) {
|
|
29
22
|
let ps = getPubSub();
|
|
30
|
-
let yogaTopic = sourceATopic(displayName);
|
|
23
|
+
let yogaTopic = GraphQL_SubscriptionBridge$ReventlessGraphqlServer.sourceATopic(displayName);
|
|
31
24
|
subscribeToEvents(busTopicName, async (_service, _meta, json) => {
|
|
32
25
|
ps.publish(yogaTopic, json);
|
|
33
26
|
});
|
|
@@ -35,35 +28,22 @@ function bridgeSourceA(subscribeToEvents, displayName, busTopicName) {
|
|
|
35
28
|
|
|
36
29
|
function bridgeSourceB(subscribeToStateChanges, readModelName, returnTypeName) {
|
|
37
30
|
let ps = getPubSub();
|
|
38
|
-
let yogaTopic = sourceBTopic(returnTypeName);
|
|
31
|
+
let yogaTopic = GraphQL_SubscriptionBridge$ReventlessGraphqlServer.sourceBTopic(returnTypeName);
|
|
39
32
|
subscribeToStateChanges(readModelName, state => {
|
|
40
33
|
ps.publish(yogaTopic, state);
|
|
41
34
|
});
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
function makeFieldResolver(topic) {
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
subscribe: (_root, _args, _ctx) => ps.subscribe(topic),
|
|
48
|
-
resolve: payload => payload
|
|
49
|
-
};
|
|
38
|
+
return GraphQL_SubscriptionBridge$ReventlessGraphqlServer.makeFieldResolver(getPubSub(), topic);
|
|
50
39
|
}
|
|
51
40
|
|
|
52
41
|
function registerAll(server, sdlFields, sourceAEntries, sourceBEntries) {
|
|
53
|
-
|
|
54
|
-
let resolvers = {};
|
|
55
|
-
sourceAEntries.forEach(param => {
|
|
56
|
-
resolvers[param.fieldName] = makeFieldResolver(param.topic);
|
|
57
|
-
});
|
|
58
|
-
sourceBEntries.forEach(param => {
|
|
59
|
-
resolvers[param.fieldName] = makeFieldResolver(param.topic);
|
|
60
|
-
});
|
|
61
|
-
server.registerSubscriptions(cleanedFields, resolvers);
|
|
62
|
-
server.registerTypes(["scalar AWSJSON"]);
|
|
42
|
+
GraphQL_SubscriptionBridge$ReventlessGraphqlServer.registerAll(server, sdlFields, sourceAEntries, sourceBEntries, getPubSub());
|
|
63
43
|
}
|
|
64
44
|
|
|
65
45
|
function publish(topic, payload) {
|
|
66
|
-
getPubSub()
|
|
46
|
+
GraphQL_SubscriptionBridge$ReventlessGraphqlServer.publish(getPubSub(), topic, payload);
|
|
67
47
|
}
|
|
68
48
|
|
|
69
49
|
function reset() {
|
|
@@ -72,8 +52,15 @@ function reset() {
|
|
|
72
52
|
|
|
73
53
|
let YG;
|
|
74
54
|
|
|
55
|
+
let Bridge;
|
|
56
|
+
|
|
57
|
+
let sourceATopic = GraphQL_SubscriptionBridge$ReventlessGraphqlServer.sourceATopic;
|
|
58
|
+
|
|
59
|
+
let sourceBTopic = GraphQL_SubscriptionBridge$ReventlessGraphqlServer.sourceBTopic;
|
|
60
|
+
|
|
75
61
|
export {
|
|
76
62
|
YG,
|
|
63
|
+
Bridge,
|
|
77
64
|
pubSub,
|
|
78
65
|
getPubSub,
|
|
79
66
|
sourceATopic,
|
|
@@ -104,7 +104,7 @@ let capitalize = s =>
|
|
|
104
104
|
|
|
105
105
|
let commandResultSdl = GraphQL_FragmentGenerator.commandResultSdlTypes->Array.join("\n\n")
|
|
106
106
|
|
|
107
|
-
let ensureCommandResultTypes = (server: GraphQL_ServerInstance.t) => {
|
|
107
|
+
let ensureCommandResultTypes = (server: ReventlessGraphqlServer.GraphQL_ServerInstance.t) => {
|
|
108
108
|
if !(server.buildSdl()->String.includes("CommandAccepted")) {
|
|
109
109
|
server.registerTypes(~sdlTypes=[commandResultSdl])
|
|
110
110
|
}
|
|
@@ -167,7 +167,7 @@ let register = (
|
|
|
167
167
|
~fields: array<string>,
|
|
168
168
|
~commandSchema: S.t<unknown>,
|
|
169
169
|
~commandAuthorization: unknown => Reventless.Authorization.permission,
|
|
170
|
-
~server: GraphQL_ServerInstance.t,
|
|
170
|
+
~server: ReventlessGraphqlServer.GraphQL_ServerInstance.t,
|
|
171
171
|
) => {
|
|
172
172
|
ensureCommandResultTypes(server)
|
|
173
173
|
// Aggregate commands target a specific instance — prepend id: ID!
|
|
@@ -193,7 +193,7 @@ let register = (
|
|
|
193
193
|
| _ => false
|
|
194
194
|
}
|
|
195
195
|
let commandName = extractCommandName(field)
|
|
196
|
-
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
196
|
+
let resolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
197
197
|
switch handlerRef.contents {
|
|
198
198
|
| Some(generateCommand) =>
|
|
199
199
|
switch checkPluginStatus(~field) {
|
|
@@ -234,7 +234,7 @@ let registerDcb = (
|
|
|
234
234
|
~fieldName: string,
|
|
235
235
|
~commandSchema: S.t<unknown>,
|
|
236
236
|
~commandAuthorization: unknown => Reventless.Authorization.permission,
|
|
237
|
-
~server: GraphQL_ServerInstance.t,
|
|
237
|
+
~server: ReventlessGraphqlServer.GraphQL_ServerInstance.t,
|
|
238
238
|
) => {
|
|
239
239
|
ensureCommandResultTypes(server)
|
|
240
240
|
// Derive the constructor from the field name's trailing `_` segment (mirrors the
|
|
@@ -256,7 +256,7 @@ let registerDcb = (
|
|
|
256
256
|
let handlerRef = ref(None)
|
|
257
257
|
handlerRefs->Dict.set(fieldName, handlerRef)
|
|
258
258
|
|
|
259
|
-
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
259
|
+
let resolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
260
260
|
switch handlerRef.contents {
|
|
261
261
|
| Some(generateCommand) =>
|
|
262
262
|
switch checkPluginStatus(~field=fieldName) {
|
|
@@ -23,7 +23,7 @@ let pendingQueueRegistry: dict<ref<array<pendingCall>>> = Dict.make()
|
|
|
23
23
|
// Phase 1: Register SDL + resolver stub synchronously (before server starts).
|
|
24
24
|
// Also pre-registers a queuing forwarder in receiveRegistry so callers can
|
|
25
25
|
// invoke receive immediately; calls are parked until bindReceive drains them.
|
|
26
|
-
let register = (~fieldName: string, ~externalInputSchema: S.t<unknown>, ~server: GraphQL_ServerInstance.t) => {
|
|
26
|
+
let register = (~fieldName: string, ~externalInputSchema: S.t<unknown>, ~server: ReventlessGraphqlServer.GraphQL_ServerInstance.t) => {
|
|
27
27
|
let sdlFields = switch GraphQL_FragmentGenerator.deriveMutationFieldFromObject(
|
|
28
28
|
~fieldName,
|
|
29
29
|
~collectedTypes=[],
|
|
@@ -45,7 +45,7 @@ let register = (~fieldName: string, ~externalInputSchema: S.t<unknown>, ~server:
|
|
|
45
45
|
})
|
|
46
46
|
receiveRegistry->Dict.set(fieldName, queuingReceive)
|
|
47
47
|
|
|
48
|
-
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, _ctx) => {
|
|
48
|
+
let resolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (_root, args, _ctx) => {
|
|
49
49
|
let inputJson: JSON.t = args->Obj.magic
|
|
50
50
|
let receive = receiveRegistry->Dict.getUnsafe(fieldName)
|
|
51
51
|
let result = await receive(inputJson)
|
|
@@ -153,7 +153,7 @@ let _dispatch = (req: nodeRequest, res: nodeResponse, yoga: YG.yoga, getSdl: uni
|
|
|
153
153
|
// `Auth_GraphqlContext.buildAuthContext` flattens headers into a lowercase
|
|
154
154
|
// dict, runs them through `LocalAuth.authenticate`, and attaches the
|
|
155
155
|
// resolved identity to the resolver context. Lifted into its own module so
|
|
156
|
-
// the split-mode admin server (`GraphQL_ServerInstance`) enforces the same
|
|
156
|
+
// the split-mode admin server (`ReventlessGraphqlServer.GraphQL_ServerInstance`) enforces the same
|
|
157
157
|
// bearer-token rules instead of leaving admin queries wide open.
|
|
158
158
|
|
|
159
159
|
let buildAuthContext = Auth_GraphqlContext.buildAuthContext
|
|
@@ -301,7 +301,7 @@ ${mutations}
|
|
|
301
301
|
base ++ subscriptionsSdl
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
// `~contextFactory` is accepted to satisfy the `GraphQL_ServerInstance.t`
|
|
304
|
+
// `~contextFactory` is accepted to satisfy the `ReventlessGraphqlServer.GraphQL_ServerInstance.t`
|
|
305
305
|
// interface but ignored — the data server always wires `buildAuthContext`
|
|
306
306
|
// internally so the bearer-token rules + /__inmemory/login + /sdl dispatch
|
|
307
307
|
// stay consistent across callers.
|
|
@@ -444,7 +444,7 @@ let printLiveSdl = () =>
|
|
|
444
444
|
| None => log.info(~comp="GraphQL:Domain", "no active schema")
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
-
type diagnostics = GraphQL_ServerInstance.diagnostics
|
|
447
|
+
type diagnostics = ReventlessGraphqlServer.GraphQL_ServerInstance.diagnostics
|
|
448
448
|
|
|
449
449
|
let extractFieldName = (sdlField: string): string => {
|
|
450
450
|
let trimmed = sdlField->String.trim
|
|
@@ -544,10 +544,10 @@ let printDiagnostics = () => {
|
|
|
544
544
|
}
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
-
// -- GraphQL_ServerInstance.t interface ----------------------------------
|
|
548
|
-
// Exposes this singleton as a GraphQL_ServerInstance.t for use by
|
|
547
|
+
// -- ReventlessGraphqlServer.GraphQL_ServerInstance.t interface ----------------------------------
|
|
548
|
+
// Exposes this singleton as a ReventlessGraphqlServer.GraphQL_ServerInstance.t for use by
|
|
549
549
|
// resolveTargetGraphQL() in Platform.res (mirrors AWS resolveTargetApi() pattern).
|
|
550
|
-
let asInterface: GraphQL_ServerInstance.t = {
|
|
550
|
+
let asInterface: ReventlessGraphqlServer.GraphQL_ServerInstance.t = {
|
|
551
551
|
registerMutations,
|
|
552
552
|
registerQueries,
|
|
553
553
|
registerSubscriptions,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Platform (admin-facing) in-memory GraphQL server — singleton.
|
|
2
2
|
// Serves admin/core operations on a separate port in split mode (port 4001).
|
|
3
3
|
//
|
|
4
|
-
// Uses the same GraphQL_ServerInstance factory as the generic instance,
|
|
4
|
+
// Uses the same ReventlessGraphqlServer.GraphQL_ServerInstance factory as the generic instance,
|
|
5
5
|
// but is exposed as a named singleton so Platform.res can reference it
|
|
6
6
|
// directly (mirroring the AWS PlatformAPI resource pattern).
|
|
7
7
|
//
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
// — without the wrapper, anonymous callers could trigger admin mutations
|
|
23
23
|
// (Plugin_Activate / _Deactivate, etc.) just by hitting the platform port.
|
|
24
24
|
|
|
25
|
-
let instance: GraphQL_ServerInstance.t = GraphQL_ServerInstance.make(~label="GraphQL:Platform")
|
|
25
|
+
let instance: ReventlessGraphqlServer.GraphQL_ServerInstance.t = ReventlessGraphqlServer.GraphQL_ServerInstance.make(~label="GraphQL:Platform")
|
|
26
26
|
|
|
27
27
|
// Wrap a resolver dict so every value enforces the Admin group. Keys are
|
|
28
28
|
// preserved; resolvers fall through to the underlying instance once the
|
|
29
29
|
// group check passes.
|
|
30
30
|
let wrapAdmin = (
|
|
31
|
-
resolvers: dict<GraphQL_ServerInstance.resolverFn>,
|
|
32
|
-
): dict<GraphQL_ServerInstance.resolverFn> => {
|
|
31
|
+
resolvers: dict<ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn>,
|
|
32
|
+
): dict<ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn> => {
|
|
33
33
|
let wrapped = Dict.make()
|
|
34
34
|
resolvers->Dict.toArray->Array.forEach(((k, v)) =>
|
|
35
35
|
wrapped->Dict.set(k, Auth_GraphqlContext.requireGroup(~group="Admin", v))
|
|
@@ -54,10 +54,10 @@ let getSchema = instance.getSchema
|
|
|
54
54
|
let diagnostics = instance.diagnostics
|
|
55
55
|
let printDiagnostics = instance.printDiagnostics
|
|
56
56
|
|
|
57
|
-
// Expose as GraphQL_ServerInstance.t for resolveTargetGraphQL() in Platform.res.
|
|
57
|
+
// Expose as ReventlessGraphqlServer.GraphQL_ServerInstance.t for resolveTargetGraphQL() in Platform.res.
|
|
58
58
|
// `registerQueries` is the wrapped version so admin auth fires regardless of
|
|
59
59
|
// whether callers reach the singleton directly or through `asInterface`.
|
|
60
|
-
let asInterface: GraphQL_ServerInstance.t = {
|
|
60
|
+
let asInterface: ReventlessGraphqlServer.GraphQL_ServerInstance.t = {
|
|
61
61
|
registerMutations,
|
|
62
62
|
registerQueries,
|
|
63
63
|
registerSubscriptions,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Auth_GraphqlContext$ReventlessLocal from "./Auth/Auth_GraphqlContext.res.mjs";
|
|
4
|
-
import * as GraphQL_ServerInstance$
|
|
4
|
+
import * as GraphQL_ServerInstance$ReventlessGraphqlServer from "@reventlessdev/reventless-graphql-server/src/GraphQL_ServerInstance.res.mjs";
|
|
5
5
|
|
|
6
|
-
let instance = GraphQL_ServerInstance$
|
|
6
|
+
let instance = GraphQL_ServerInstance$ReventlessGraphqlServer.make("GraphQL:Platform");
|
|
7
7
|
|
|
8
8
|
function wrapAdmin(resolvers) {
|
|
9
9
|
let wrapped = {};
|
|
@@ -57,7 +57,7 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
57
57
|
// -- Module-level server and relay refs ------------------------------------
|
|
58
58
|
// Set by Platform.res before component construction runs so the make function
|
|
59
59
|
// can pick up the correct target server and relay support (mirrors AWS ~api pattern).
|
|
60
|
-
let serverRef: ref<GraphQL_ServerInstance.t> = ref(DomainGraphQL_Server.asInterface)
|
|
60
|
+
let serverRef: ref<ReventlessGraphqlServer.GraphQL_ServerInstance.t> = ref(DomainGraphQL_Server.asInterface)
|
|
61
61
|
let relayRef: ref<option<relaySupport>> = ref(Some({
|
|
62
62
|
encodeGlobalId: DomainGraphQL_Server.encodeGlobalId,
|
|
63
63
|
registerNodeType: DomainGraphQL_Server.registerNodeType,
|
|
@@ -183,7 +183,7 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
183
183
|
} else {
|
|
184
184
|
` ${singleQueryName}: ${returnTypeName}`
|
|
185
185
|
}
|
|
186
|
-
let byIdResolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
186
|
+
let byIdResolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
187
187
|
switch await runInterceptor(~ctx, ~args) {
|
|
188
188
|
| Deny(_) => JSON.Encode.null
|
|
189
189
|
| Allow =>
|
|
@@ -222,9 +222,9 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
222
222
|
} else {
|
|
223
223
|
[]
|
|
224
224
|
}
|
|
225
|
-
let byIdsResolverEntry: option<(string, GraphQL_ServerInstance.resolverFn)> =
|
|
225
|
+
let byIdsResolverEntry: option<(string, ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn)> =
|
|
226
226
|
if includeIdParam && subIdField === None {
|
|
227
|
-
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
227
|
+
let resolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
228
228
|
switch await runInterceptor(~ctx, ~args) {
|
|
229
229
|
| Deny(_) => []->JSON.Encode.array
|
|
230
230
|
| Allow =>
|
|
@@ -295,7 +295,7 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
let (listSdl, listResolver): (array<string>, GraphQL_ServerInstance.resolverFn) = if connectionSpec {
|
|
298
|
+
let (listSdl, listResolver): (array<string>, ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn) = if connectionSpec {
|
|
299
299
|
// Relay Connection spec format
|
|
300
300
|
let filterTypeName = returnTypeName ++ "Filter"
|
|
301
301
|
let orderByTypes = GraphQL_FragmentGenerator.deriveConnectionOrderByType(
|
|
@@ -315,7 +315,7 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
315
315
|
~hasOrderBy,
|
|
316
316
|
),
|
|
317
317
|
]
|
|
318
|
-
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
318
|
+
let resolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
319
319
|
switch await runInterceptor(~ctx, ~args) {
|
|
320
320
|
| Deny(_) =>
|
|
321
321
|
Obj.magic({
|
|
@@ -355,7 +355,7 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
355
355
|
} else {
|
|
356
356
|
// Legacy AppSync-style format
|
|
357
357
|
let sdl = [` ${listQueryName}(nextToken: String, limit: Int): ${pluralTypeName}!`]
|
|
358
|
-
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
358
|
+
let resolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
359
359
|
switch await runInterceptor(~ctx, ~args) {
|
|
360
360
|
| Deny(_) => Obj.magic({"nextToken": Nullable.null, "scannedCount": 0, "items": []})
|
|
361
361
|
| Allow =>
|
|
@@ -383,9 +383,9 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
383
383
|
[` ${singleQueryName}Items(id: ID!, filter: ${filterTypeName}, first: Int, after: String, last: Int, before: String): ${connectionTypeName}!`]
|
|
384
384
|
| None => []
|
|
385
385
|
}
|
|
386
|
-
let itemsResolvers: array<(string, GraphQL_ServerInstance.resolverFn)> = switch subIdField {
|
|
386
|
+
let itemsResolvers: array<(string, ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn)> = switch subIdField {
|
|
387
387
|
| Some(sf) =>
|
|
388
|
-
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
388
|
+
let resolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
389
389
|
let emptyConn = Obj.magic({
|
|
390
390
|
"edges": [],
|
|
391
391
|
"pageInfo": {
|
|
@@ -498,12 +498,12 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
498
498
|
let indexSdlFields = indexes->Array.map((ic: Reventless.ReadModel.indexConfig) =>
|
|
499
499
|
` ${singleQueryName}By${cap(ic.index)}(${ic.index}: String!): [String]`
|
|
500
500
|
)
|
|
501
|
-
let indexResolvers: array<(string, GraphQL_ServerInstance.resolverFn)> = indexes->Array.map(
|
|
501
|
+
let indexResolvers: array<(string, ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn)> = indexes->Array.map(
|
|
502
502
|
(ic: Reventless.ReadModel.indexConfig) => {
|
|
503
503
|
let index = ic.index
|
|
504
504
|
let resolverName = singleQueryName ++ "By" ++ cap(index)
|
|
505
505
|
let filterField = ic.idField->Option.getOr(index)
|
|
506
|
-
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
506
|
+
let resolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
507
507
|
switch await runInterceptor(~ctx, ~args) {
|
|
508
508
|
| Deny(_) => []->JSON.Encode.array
|
|
509
509
|
| Allow =>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// Fixtures for split API mode integration tests.
|
|
2
2
|
// Tests the split routing logic directly using GraphQL_Server (singleton)
|
|
3
|
-
// and GraphQL_ServerInstance (factory) without importing Platform or MCP,
|
|
3
|
+
// and ReventlessGraphqlServer.GraphQL_ServerInstance (factory) without importing Platform or MCP,
|
|
4
4
|
// which avoids ESM compatibility issues with Jest.
|
|
5
5
|
//
|
|
6
6
|
// This mirrors what Platform.makePlatform does in split mode:
|
|
7
|
-
// - Admin schema → GraphQL_ServerInstance (isolated admin server)
|
|
7
|
+
// - Admin schema → ReventlessGraphqlServer.GraphQL_ServerInstance (isolated admin server)
|
|
8
8
|
// - Plugin schema → GraphQL_Server singleton (plugin server)
|
|
9
9
|
|
|
10
10
|
let _ = TestRunner.setup()
|
|
11
11
|
|
|
12
12
|
// Helper to extract resolver key from an SDL field string.
|
|
13
|
-
// Mirrors GraphQL_ServerInstance.extractFieldName.
|
|
13
|
+
// Mirrors ReventlessGraphqlServer.GraphQL_ServerInstance.extractFieldName.
|
|
14
14
|
let extractSdlFieldName = (sdlField: string): string => {
|
|
15
15
|
let trimmed = sdlField->String.trim
|
|
16
16
|
trimmed
|
|
@@ -28,7 +28,7 @@ let extractSdlFieldName = (sdlField: string): string => {
|
|
|
28
28
|
// Create admin GraphQL instance (mirrors Platform split mode)
|
|
29
29
|
// ─────────────────────────────────────────────────────────────
|
|
30
30
|
|
|
31
|
-
let adminGraphQL = GraphQL_ServerInstance.make(~label="GraphQL:Admin")
|
|
31
|
+
let adminGraphQL = ReventlessGraphqlServer.GraphQL_ServerInstance.make(~label="GraphQL:Admin")
|
|
32
32
|
|
|
33
33
|
// Build admin SDL — authoritative source for field names.
|
|
34
34
|
let baseParts = ReventlessCore.GraphQL_Stitcher.decode(ReventlessCore.AdminApi.baseFragment(~cloner=true))
|
|
@@ -6,7 +6,7 @@ import * as TestRunner$ReventlessLocal from "../src/test/TestRunner.res.mjs";
|
|
|
6
6
|
import * as GraphQL_Server$ReventlessLocal from "../src/adapter/GraphQL_Server.res.mjs";
|
|
7
7
|
import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
8
8
|
import * as PluginBaseFragment$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/api/PluginBaseFragment.res.mjs";
|
|
9
|
-
import * as GraphQL_ServerInstance$
|
|
9
|
+
import * as GraphQL_ServerInstance$ReventlessGraphqlServer from "@reventlessdev/reventless-graphql-server/src/GraphQL_ServerInstance.res.mjs";
|
|
10
10
|
|
|
11
11
|
TestRunner$ReventlessLocal.setup();
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ function extractSdlFieldName(sdlField) {
|
|
|
15
15
|
return Stdlib_Option.getOr(Stdlib_Option.getOr(trimmed.split("(")[0], "").trim().split(":")[0], "").trim();
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
let adminGraphQL = GraphQL_ServerInstance$
|
|
18
|
+
let adminGraphQL = GraphQL_ServerInstance$ReventlessGraphqlServer.make("GraphQL:Admin");
|
|
19
19
|
|
|
20
20
|
let baseParts = GraphQL_Stitcher$ReventlessCore.decode(AdminApi$ReventlessCore.baseFragment(true));
|
|
21
21
|
|
package/tests/SplitApiTest.res
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// with no cross-contamination.
|
|
4
4
|
//
|
|
5
5
|
// Tests the split routing pattern used by Platform.makePlatform(splitApi=true):
|
|
6
|
-
// - Admin schema → GraphQL_ServerInstance (dedicated admin server)
|
|
6
|
+
// - Admin schema → ReventlessGraphqlServer.GraphQL_ServerInstance (dedicated admin server)
|
|
7
7
|
// - Plugin schema → GraphQL_Server singleton (default plugin server)
|
|
8
8
|
//
|
|
9
9
|
// MCP split is structurally identical (MCP_ServerInstance mirrors
|
|
10
|
-
// GraphQL_ServerInstance) but is not tested here due to Jest ESM
|
|
10
|
+
// ReventlessGraphqlServer.GraphQL_ServerInstance) but is not tested here due to Jest ESM
|
|
11
11
|
// compatibility issues with the MCP SDK dependency chain.
|
|
12
12
|
|
|
13
13
|
open JestGlobals
|