@reventlessdev/reventless-core 3.0.0-alpha.165 → 3.0.0-alpha.167
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 +7 -7
- package/src/admin/AdminApi.res +9 -62
- package/src/admin/AdminApi.res.mjs +6 -28
- package/src/components/Api/GraphQL_Stitcher.res +41 -123
- package/src/components/Api/GraphQL_Stitcher.res.mjs +15 -94
- package/tests/api/GraphQL_StitcherTest.res +32 -198
- package/tests/api/GraphQL_StitcherTest.res.mjs +32 -0
- package/src/admin/ApiFragmentRegistry/ApiFragmentRegistryBehavior.res +0 -108
- package/src/admin/ApiFragmentRegistry/ApiFragmentRegistryBehavior.res.mjs +0 -190
- package/src/admin/ApiFragmentRegistry/ApiFragmentRegistrySpec.res +0 -71
- package/src/admin/ApiFragmentRegistry/ApiFragmentRegistrySpec.res.mjs +0 -92
- package/src/admin/ApiFragmentRegistry/ApiFragmentsProjection.res +0 -63
- package/src/admin/ApiFragmentRegistry/ApiFragmentsProjection.res.mjs +0 -108
- package/src/admin/ApiFragmentRegistry/ApiFragmentsReadModelSpec.res +0 -23
- package/src/admin/ApiFragmentRegistry/ApiFragmentsReadModelSpec.res.mjs +0 -43
- package/src/admin/Platform_ApiFragmentsApi.res +0 -32
- package/src/admin/Platform_ApiFragmentsApi.res.mjs +0 -52
- package/src/components/Api/Api_Builder.res +0 -40
- package/src/components/Api/Api_Builder.res.mjs +0 -33
- package/src/components/Api/Api_Operations.res +0 -13
- package/src/components/Api/Api_Operations.res.mjs +0 -12
- package/src/components/Api/GraphQL_PushPlanner.res +0 -65
- package/src/components/Api/GraphQL_PushPlanner.res.mjs +0 -46
- package/tests/admin/Platform_ApiFragmentsApiTest.res +0 -66
- package/tests/admin/Platform_ApiFragmentsApiTest.res.mjs +0 -68
- package/tests/api/GraphQL_PushPlannerTest.res +0 -62
- package/tests/api/GraphQL_PushPlannerTest.res.mjs +0 -80
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.167 (2026-07-14)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @reventlessdev/reventless-core
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 3.0.0-alpha.166 (2026-07-14)
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **api:** merge-path codegen — stitchStandalone subgraph SDL + [@canonical](https://github.com/canonical) stamping (Phase 2) ([02ae93b](https://github.com/ReventlessDev/reventless-core/commit/02ae93b1c84a116ae47fb656cab17858a5df2c97))
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.165 (2026-07-13)
|
|
7
22
|
|
|
8
23
|
**Note:** Version bump only for package @reventlessdev/reventless-core
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.167",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -28,17 +28,17 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"sury": "11.0.0-alpha.4",
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.27",
|
|
32
31
|
"@reventlessdev/rescript-fast-csv": "1.2.0-alpha.12",
|
|
32
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.27",
|
|
33
33
|
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.11",
|
|
34
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.7",
|
|
35
34
|
"@reventlessdev/rescript-node-streams": "1.1.0-alpha.11",
|
|
36
|
-
"@reventlessdev/rescript-
|
|
35
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.7",
|
|
37
36
|
"@reventlessdev/rescript-ssh2": "1.1.0-alpha.12",
|
|
37
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.15",
|
|
38
38
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.15",
|
|
39
|
-
"@reventlessdev/reventless-
|
|
40
|
-
"@reventlessdev/reventless-
|
|
41
|
-
"@reventlessdev/reventless-
|
|
39
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.26",
|
|
40
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.99",
|
|
41
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.76"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "^12.3.0",
|
package/src/admin/AdminApi.res
CHANGED
|
@@ -9,46 +9,14 @@ let cloneMutationEntry: mutationSchemaEntry = {
|
|
|
9
9
|
description: "Clone the system to a specific point in time",
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
// ApiFragmentRegistry admin mutations — the real GraphQL surface of the ApiFragmentRegistry
|
|
13
|
-
// singleton aggregate: the plugin / standalone-service deploy calls
|
|
14
|
-
// `Platform_ApiFragmentRegistry_RegisterApiFragment` / `..._DeregisterApiFragment` as a SigV4
|
|
15
|
-
// system caller. Derived from the aggregate's `commandSchema` exactly like the Plugin aggregate's
|
|
16
|
-
// entries (`PluginBaseFragment.pluginAggregateMutationEntries`) — one `Platform_<Spec>_<Command>`
|
|
17
|
-
// field per non-`@noApi` variant (`RecordApiFragmentPush` is `@noApi`, filtered out). The matching
|
|
18
|
-
// resolver wiring is fired by `Plugin_Helpers.registerAdminAggregateMutations` over the same
|
|
19
|
-
// `~aggregates` list in `Platform_Admin.construct`, so the SDL field and the auto-bound resolver
|
|
20
|
-
// carry byte-identical names.
|
|
21
|
-
let apiFragmentRegistryMutationEntries: array<mutationSchemaEntry> = {
|
|
22
|
-
let commandSchema = ApiFragmentRegistrySpec.commandSchema->S.castToUnknown
|
|
23
|
-
let constructorNames = Reventless.DcbTag.extractAllVariantNames(ApiFragmentRegistrySpec.commandSchema)
|
|
24
|
-
let filteredConstructorNames = ApiNoApiHelpers.filterNoApiVariants(constructorNames, commandSchema)
|
|
25
|
-
let fieldNames =
|
|
26
|
-
filteredConstructorNames->Array.map(cname =>
|
|
27
|
-
Api_Naming.adminField(~name=ApiFragmentRegistrySpec.name ++ "_" ++ cname)
|
|
28
|
-
)
|
|
29
|
-
if fieldNames->Array.length === 0 {
|
|
30
|
-
[]
|
|
31
|
-
} else {
|
|
32
|
-
[
|
|
33
|
-
{
|
|
34
|
-
ReventlessInfra.Api.fieldNames,
|
|
35
|
-
commandSchema,
|
|
36
|
-
},
|
|
37
|
-
]
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
12
|
// Aggregate-derived admin mutations come from `PluginBaseFragment.pluginAggregateMutationEntries`
|
|
42
|
-
// (Plugin aggregate)
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
13
|
+
// (Plugin aggregate) so they end up in the admin base document. The corresponding resolver wiring
|
|
14
|
+
// is fired in parallel by `Plugin_Helpers.registerAdminAggregateMutations` from
|
|
15
|
+
// `Platform_Admin.construct` over the same `~aggregates` list.
|
|
16
|
+
// (The ApiFragmentRegistry aggregate's entries were retired with the merged-API cutover —
|
|
17
|
+
// schema composition is push-free, so no deploy-time register/deregister surface exists.)
|
|
47
18
|
let mutationEntries = (~cloner: bool) => {
|
|
48
|
-
let base =
|
|
49
|
-
PluginBaseFragment.pluginAggregateMutationEntries,
|
|
50
|
-
apiFragmentRegistryMutationEntries,
|
|
51
|
-
)
|
|
19
|
+
let base = PluginBaseFragment.pluginAggregateMutationEntries
|
|
52
20
|
if cloner {
|
|
53
21
|
Array.concat(base, [cloneMutationEntry])
|
|
54
22
|
} else {
|
|
@@ -58,17 +26,6 @@ let mutationEntries = (~cloner: bool) => {
|
|
|
58
26
|
|
|
59
27
|
let queryEntries = PluginBaseFragment.queryEntries
|
|
60
28
|
|
|
61
|
-
// Admin fields a deploy-time system caller (machine credentials — SigV4/IAM on AWS) invokes: the
|
|
62
|
-
// two ApiFragmentRegistry mutations (the deploy's register/deregister write path) and
|
|
63
|
-
// `Platform_ApiFragments` (the deploy waiter's push-status poll). Provider-neutral by design — the
|
|
64
|
-
// AWS adapter threads these into `injectAwsAuthAll`'s `~iamFieldNames` so only these fields carry
|
|
65
|
-
// the dual-auth (`@aws_cognito_user_pools @aws_iam`) directive; every other admin field stays
|
|
66
|
-
// Cognito-only. Derived from the same mutation entries so the names never drift from the SDL.
|
|
67
|
-
let systemCallerFieldNames =
|
|
68
|
-
apiFragmentRegistryMutationEntries
|
|
69
|
-
->Array.flatMap(e => e.fieldNames)
|
|
70
|
-
->Array.concat([Platform_ApiFragmentsApi.queryFieldName])
|
|
71
|
-
|
|
72
29
|
// UIFragment lifecycle Source C subscriptions.
|
|
73
30
|
// Mutations are called by the backend when UIFragment events occur; the
|
|
74
31
|
// subscription→mutation fan-in is carried as neutral `subscriptionSource`
|
|
@@ -132,35 +89,25 @@ let baseFragment = (~cloner: bool) => {
|
|
|
132
89
|
// matching SDL fields, AppSync's CreateResolver fails with "Type not found".
|
|
133
90
|
let (pluginAggregateSubscriptionFields, pluginAggregateSubscriptionSources) =
|
|
134
91
|
Plugin_SubscriptionSchema.sourceCFields(~mutationEntries=PluginBaseFragment.pluginAggregateMutationEntries)
|
|
135
|
-
// The ApiFragmentRegistry aggregate's mutations go through the same CommandGenerator
|
|
136
|
-
// auto-flow, which creates an `on<field>` subscription resolver per mutation. On the
|
|
137
|
-
// static-push platform (AWS) those resolvers orphan unless the fields are declared in the
|
|
138
|
-
// pushed baseFragment — so generate them here too, symmetric with the Plugin aggregate.
|
|
139
|
-
let (apiFragmentAggregateSubscriptionFields, apiFragmentAggregateSubscriptionSources) =
|
|
140
|
-
Plugin_SubscriptionSchema.sourceCFields(~mutationEntries=apiFragmentRegistryMutationEntries)
|
|
141
92
|
GraphQL_Stitcher.encode({
|
|
142
93
|
types: parts.types
|
|
143
94
|
->Array.concat(uiFragmentSubscriptionTypes)
|
|
144
95
|
->Array.concat(pluginStatusSubscriptionTypes)
|
|
145
96
|
->Array.concat(Platform_ComponentDefinitionsApi.sdlTypes)
|
|
146
|
-
->Array.concat(Platform_UIFragmentsApi.sdlTypes)
|
|
147
|
-
->Array.concat(Platform_ApiFragmentsApi.sdlTypes),
|
|
97
|
+
->Array.concat(Platform_UIFragmentsApi.sdlTypes),
|
|
148
98
|
queries: Array.concat(
|
|
149
99
|
parts.queries,
|
|
150
100
|
[
|
|
151
101
|
Platform_ComponentDefinitionsApi.sdlQueryField,
|
|
152
102
|
Platform_UIFragmentsApi.sdlQueryField,
|
|
153
|
-
Platform_ApiFragmentsApi.sdlQueryField,
|
|
154
103
|
],
|
|
155
104
|
),
|
|
156
105
|
mutations: parts.mutations
|
|
157
106
|
->Array.concat(uiFragmentMutationFields)
|
|
158
107
|
->Array.concat(pluginStatusMutationFields),
|
|
159
108
|
subscriptions: [uiFragmentSubscriptionField, pluginStatusSubscriptionField]
|
|
160
|
-
->Array.concat(pluginAggregateSubscriptionFields)
|
|
161
|
-
->Array.concat(apiFragmentAggregateSubscriptionFields),
|
|
109
|
+
->Array.concat(pluginAggregateSubscriptionFields),
|
|
162
110
|
subscriptionSources: [uiFragmentSubscriptionSource, pluginStatusSubscriptionSource]
|
|
163
|
-
->Array.concat(pluginAggregateSubscriptionSources)
|
|
164
|
-
->Array.concat(apiFragmentAggregateSubscriptionSources),
|
|
111
|
+
->Array.concat(pluginAggregateSubscriptionSources),
|
|
165
112
|
})
|
|
166
113
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as S from "sury/src/S.res.mjs";
|
|
4
|
-
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
5
4
|
import * as Api_Naming$ReventlessCore from "../components/Api/Api_Naming.res.mjs";
|
|
6
|
-
import * as ApiNoApiHelpers$ReventlessCore from "../components/Api/ApiNoApiHelpers.res.mjs";
|
|
7
5
|
import * as GraphQL_Stitcher$ReventlessCore from "../components/Api/GraphQL_Stitcher.res.mjs";
|
|
8
6
|
import * as PluginBaseFragment$ReventlessCore from "../plugin/api/PluginBaseFragment.res.mjs";
|
|
9
|
-
import * as ApiFragmentRegistrySpec$ReventlessCore from "./ApiFragmentRegistry/ApiFragmentRegistrySpec.res.mjs";
|
|
10
7
|
import * as Platform_UIFragmentsApi$ReventlessCore from "./Platform_UIFragmentsApi.res.mjs";
|
|
11
|
-
import * as Platform_ApiFragmentsApi$ReventlessCore from "./Platform_ApiFragmentsApi.res.mjs";
|
|
12
8
|
import * as GraphQL_FragmentGenerator$ReventlessCore from "../components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
13
9
|
import * as Plugin_SubscriptionSchema$ReventlessCore from "../plugin/component/Plugin_SubscriptionSchema.res.mjs";
|
|
14
10
|
import * as Platform_ComponentDefinitionsApi$ReventlessCore from "./Platform_ComponentDefinitionsApi.res.mjs";
|
|
@@ -27,28 +23,14 @@ let cloneMutationEntry = {
|
|
|
27
23
|
description: cloneMutationEntry_description
|
|
28
24
|
};
|
|
29
25
|
|
|
30
|
-
let constructorNames = DcbTag$Reventless.extractAllVariantNames(ApiFragmentRegistrySpec$ReventlessCore.commandSchema);
|
|
31
|
-
|
|
32
|
-
let filteredConstructorNames = ApiNoApiHelpers$ReventlessCore.filterNoApiVariants(constructorNames, ApiFragmentRegistrySpec$ReventlessCore.commandSchema);
|
|
33
|
-
|
|
34
|
-
let fieldNames = filteredConstructorNames.map(cname => Api_Naming$ReventlessCore.adminField(ApiFragmentRegistrySpec$ReventlessCore.name + "_" + cname));
|
|
35
|
-
|
|
36
|
-
let apiFragmentRegistryMutationEntries = fieldNames.length === 0 ? [] : [{
|
|
37
|
-
fieldNames: fieldNames,
|
|
38
|
-
commandSchema: ApiFragmentRegistrySpec$ReventlessCore.commandSchema
|
|
39
|
-
}];
|
|
40
|
-
|
|
41
26
|
function mutationEntries(cloner) {
|
|
42
|
-
let base = PluginBaseFragment$ReventlessCore.pluginAggregateMutationEntries.concat(apiFragmentRegistryMutationEntries);
|
|
43
27
|
if (cloner) {
|
|
44
|
-
return
|
|
28
|
+
return PluginBaseFragment$ReventlessCore.pluginAggregateMutationEntries.concat([cloneMutationEntry]);
|
|
45
29
|
} else {
|
|
46
|
-
return
|
|
30
|
+
return PluginBaseFragment$ReventlessCore.pluginAggregateMutationEntries;
|
|
47
31
|
}
|
|
48
32
|
}
|
|
49
33
|
|
|
50
|
-
let systemCallerFieldNames = apiFragmentRegistryMutationEntries.flatMap(e => e.fieldNames).concat([Platform_ApiFragmentsApi$ReventlessCore.queryFieldName]);
|
|
51
|
-
|
|
52
34
|
let uiFragmentSubscriptionTypes = [
|
|
53
35
|
`enum UIFragmentChangeKind {\n Registered\n Updated\n Deregistered\n}`,
|
|
54
36
|
`type UIFragmentChangeEvent {\n pluginId: ID!\n changeKind: UIFragmentChangeKind!\n manifest: String\n}`
|
|
@@ -93,23 +75,21 @@ function baseFragment(cloner) {
|
|
|
93
75
|
let base = GraphQL_FragmentGenerator$ReventlessCore.generate(mutationEntries(cloner), PluginBaseFragment$ReventlessCore.queryEntries);
|
|
94
76
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(base);
|
|
95
77
|
let match = Plugin_SubscriptionSchema$ReventlessCore.sourceCFields(PluginBaseFragment$ReventlessCore.pluginAggregateMutationEntries);
|
|
96
|
-
let match$1 = Plugin_SubscriptionSchema$ReventlessCore.sourceCFields(apiFragmentRegistryMutationEntries);
|
|
97
78
|
return GraphQL_Stitcher$ReventlessCore.encode({
|
|
98
|
-
types: parts.types.concat(uiFragmentSubscriptionTypes).concat(pluginStatusSubscriptionTypes).concat(Platform_ComponentDefinitionsApi$ReventlessCore.sdlTypes).concat(Platform_UIFragmentsApi$ReventlessCore.sdlTypes)
|
|
79
|
+
types: parts.types.concat(uiFragmentSubscriptionTypes).concat(pluginStatusSubscriptionTypes).concat(Platform_ComponentDefinitionsApi$ReventlessCore.sdlTypes).concat(Platform_UIFragmentsApi$ReventlessCore.sdlTypes),
|
|
99
80
|
mutations: parts.mutations.concat(uiFragmentMutationFields).concat(pluginStatusMutationFields),
|
|
100
81
|
queries: parts.queries.concat([
|
|
101
82
|
Platform_ComponentDefinitionsApi$ReventlessCore.sdlQueryField,
|
|
102
|
-
Platform_UIFragmentsApi$ReventlessCore.sdlQueryField
|
|
103
|
-
Platform_ApiFragmentsApi$ReventlessCore.sdlQueryField
|
|
83
|
+
Platform_UIFragmentsApi$ReventlessCore.sdlQueryField
|
|
104
84
|
]),
|
|
105
85
|
subscriptions: [
|
|
106
86
|
uiFragmentSubscriptionField,
|
|
107
87
|
pluginStatusSubscriptionField
|
|
108
|
-
].concat(match[0])
|
|
88
|
+
].concat(match[0]),
|
|
109
89
|
subscriptionSources: [
|
|
110
90
|
uiFragmentSubscriptionSource,
|
|
111
91
|
pluginStatusSubscriptionSource
|
|
112
|
-
].concat(match[1])
|
|
92
|
+
].concat(match[1])
|
|
113
93
|
});
|
|
114
94
|
}
|
|
115
95
|
|
|
@@ -118,10 +98,8 @@ let queryEntries = PluginBaseFragment$ReventlessCore.queryEntries;
|
|
|
118
98
|
export {
|
|
119
99
|
cloneArgsSchema,
|
|
120
100
|
cloneMutationEntry,
|
|
121
|
-
apiFragmentRegistryMutationEntries,
|
|
122
101
|
mutationEntries,
|
|
123
102
|
queryEntries,
|
|
124
|
-
systemCallerFieldNames,
|
|
125
103
|
uiFragmentSubscriptionTypes,
|
|
126
104
|
uiFragmentMutationFields,
|
|
127
105
|
uiFragmentSubscriptionField,
|
|
@@ -172,21 +172,23 @@ let relayBaseQueries = [
|
|
|
172
172
|
]
|
|
173
173
|
|
|
174
174
|
/**
|
|
175
|
-
|
|
175
|
+
Assemble fragments into a complete GraphQL SDL string.
|
|
176
176
|
|
|
177
|
-
-
|
|
178
|
-
- Plugin fragments add their own types, mutations, and queries.
|
|
177
|
+
- Fragments are processed in order (first wins on collisions).
|
|
179
178
|
- Collision detection: duplicate type definitions and field names are logged and skipped.
|
|
180
|
-
- Relay base types (Node interface, PageInfo)
|
|
179
|
+
- Relay base types (Node interface, PageInfo) are always injected; the global
|
|
180
|
+
`node` query only when `includeGlobalNodeQuery` (stitched schemas yes,
|
|
181
|
+
standalone subgraph documents no).
|
|
181
182
|
|
|
182
183
|
Returns the full SDL string ready for use with AppSync or graphql-yoga.
|
|
184
|
+
Public entry points: `stitch` (base + plugins, the whole-schema path) and
|
|
185
|
+
`stitchStandalone` (one fragment as a self-contained subgraph document).
|
|
183
186
|
*/
|
|
184
|
-
let
|
|
185
|
-
~
|
|
186
|
-
~
|
|
187
|
+
let assembleSdl = (
|
|
188
|
+
~fragments: array<Reventless.Plugin.apiSchemaFragment>,
|
|
189
|
+
~includeGlobalNodeQuery: bool,
|
|
187
190
|
): string => {
|
|
188
|
-
let
|
|
189
|
-
let parts = allFragments->Array.map(decode)
|
|
191
|
+
let parts = fragments->Array.map(decode)
|
|
190
192
|
|
|
191
193
|
// Collect all type definitions — detect collisions by type name
|
|
192
194
|
// Start with Relay base types (Node interface, PageInfo)
|
|
@@ -229,14 +231,18 @@ let stitch = (
|
|
|
229
231
|
)
|
|
230
232
|
|
|
231
233
|
// Collect all query fields — detect collisions by field name
|
|
232
|
-
// Start with Relay node query
|
|
234
|
+
// Start with Relay node query (single-resolver-owned: omitted from
|
|
235
|
+
// standalone subgraph documents, where the platform's canonical source
|
|
236
|
+
// owns the global `node` field)
|
|
233
237
|
let seenQueryFields: Set.t<string> = Set.make()
|
|
234
238
|
let allQueries: array<string> = []
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
if includeGlobalNodeQuery {
|
|
240
|
+
relayBaseQueries->Array.forEach(field => {
|
|
241
|
+
let fieldName = extractLeadingName(field)
|
|
242
|
+
seenQueryFields->Set.add(fieldName)
|
|
243
|
+
allQueries->Array.push(field)
|
|
244
|
+
})
|
|
245
|
+
}
|
|
240
246
|
|
|
241
247
|
parts->Array.forEach(({queries}) =>
|
|
242
248
|
queries->Array.forEach(field => {
|
|
@@ -291,115 +297,27 @@ let stitch = (
|
|
|
291
297
|
sdlParts->Array.join("\n\n")
|
|
292
298
|
}
|
|
293
299
|
|
|
294
|
-
let
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
List the field NAMES defined inside a root object type (`Mutation` / `Query` /
|
|
303
|
-
`Subscription`) of a full SDL string.
|
|
304
|
-
|
|
305
|
-
This is the identity-aware primitive underlying every schema-clobber guard:
|
|
306
|
-
`countRootTypeFields` is just its length, and the deploy-time repair / shrink
|
|
307
|
-
guards compare the returned NAME SETS rather than cardinalities so they catch
|
|
308
|
-
field *swaps* and equal-cardinality drift that a count comparison misses.
|
|
309
|
-
|
|
310
|
-
Heuristic but adequate: root operation fields are flat single-line declarations
|
|
311
|
-
(`name(args): T` or `name: T`), so we slice the `type <typeName> { … }` block at
|
|
312
|
-
its first `{`/`}` and read the leading identifier of each indented line that
|
|
313
|
-
begins with an identifier character, ignoring directive-only lines (`@…`),
|
|
314
|
-
comments (`#…`) and braces.
|
|
315
|
-
*/
|
|
316
|
-
let rootTypeFieldNames = (~sdl: string, ~typeName: string): array<string> => {
|
|
317
|
-
let marker = `type ${typeName}`
|
|
318
|
-
switch sdl->String.indexOfOpt(marker) {
|
|
319
|
-
| None => []
|
|
320
|
-
| Some(typeStart) =>
|
|
321
|
-
let rest = sdl->String.slice(~start=typeStart)
|
|
322
|
-
switch (rest->String.indexOfOpt("{"), rest->String.indexOfOpt("}")) {
|
|
323
|
-
| (Some(openIdx), Some(closeIdx)) if closeIdx > openIdx =>
|
|
324
|
-
rest
|
|
325
|
-
->String.slice(~start=openIdx + 1, ~end=closeIdx)
|
|
326
|
-
->String.split("\n")
|
|
327
|
-
->Array.filterMap(line => {
|
|
328
|
-
let trimmed = line->String.trim
|
|
329
|
-
if trimmed->String.length > 0 && isIdentStart(trimmed) {
|
|
330
|
-
// `name(args): T` → extractLeadingName strips the arg list; `name: T`
|
|
331
|
-
// (no args) leaves a trailing colon on the token — drop it so a field
|
|
332
|
-
// reads the same whether or not it takes arguments.
|
|
333
|
-
let name = extractLeadingName(trimmed)
|
|
334
|
-
let name = name->String.endsWith(":")
|
|
335
|
-
? name->String.slice(~start=0, ~end=name->String.length - 1)
|
|
336
|
-
: name
|
|
337
|
-
name->String.length > 0 ? Some(name) : None
|
|
338
|
-
} else {
|
|
339
|
-
None
|
|
340
|
-
}
|
|
341
|
-
})
|
|
342
|
-
| _ => []
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
Count the field definitions inside a root object type (`Mutation` / `Query`) of a
|
|
349
|
-
full SDL string. Length of `rootTypeFieldNames`.
|
|
350
|
-
*/
|
|
351
|
-
let countRootTypeFields = (~sdl: string, ~typeName: string): int =>
|
|
352
|
-
rootTypeFieldNames(~sdl, ~typeName)->Array.length
|
|
353
|
-
|
|
354
|
-
// The three GraphQL root operation types every clobber guard reasons over.
|
|
355
|
-
let rootTypeNames = ["Mutation", "Query", "Subscription"]
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
Union of every root-operation field name across `Mutation` + `Query` +
|
|
359
|
-
`Subscription`. The identity set the deploy-time repair and shrink guards diff.
|
|
360
|
-
*/
|
|
361
|
-
let allRootFieldNames = (~sdl: string): array<string> =>
|
|
362
|
-
rootTypeNames->Array.flatMap(typeName => rootTypeFieldNames(~sdl, ~typeName))
|
|
300
|
+
let stitch = (
|
|
301
|
+
~baseFragment: Reventless.Plugin.apiSchemaFragment,
|
|
302
|
+
~pluginFragments: array<Reventless.Plugin.apiSchemaFragment>,
|
|
303
|
+
): string =>
|
|
304
|
+
assembleSdl(
|
|
305
|
+
~fragments=Array.concat([baseFragment], pluginFragments),
|
|
306
|
+
~includeGlobalNodeQuery=true,
|
|
307
|
+
)
|
|
363
308
|
|
|
364
309
|
/**
|
|
365
|
-
|
|
366
|
-
(
|
|
367
|
-
|
|
368
|
-
|
|
310
|
+
Render ONE fragment as a standalone schema document for merge-based
|
|
311
|
+
composition (plugin = subgraph, platform = merge). The relay base types are
|
|
312
|
+
included so the document is self-contained and valid on its own; the global
|
|
313
|
+
`node(id: ID!)` root query is omitted — exactly one source (the platform's
|
|
314
|
+
canonical one) may own that field's resolver, so only the canonical base
|
|
315
|
+
document carries it (via `stitch`).
|
|
369
316
|
*/
|
|
370
|
-
let
|
|
371
|
-
|
|
372
|
-
allRootFieldNames(~sdl=expectedSdl)->Array.filter(name => !(liveSet->Set.has(name)))
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
Decide whether replacing the live schema (`currentSdl`) with `newSdl` would drop
|
|
377
|
-
root-operation fields the pushing stack does not carry — i.e. an almost-certainly
|
|
378
|
-
transient/incomplete stitch rather than an intentional removal.
|
|
379
|
-
|
|
380
|
-
Identity-based (across `Mutation` + `Query` + `Subscription`):
|
|
381
|
-
- When `newSdl`'s field-name set is a SUPERSET of the live set — nothing is
|
|
382
|
-
dropped — it is purely additive (or unchanged) and never a clobber, so this
|
|
383
|
-
returns `false` even when the pushing stack is legitimately adding its own
|
|
384
|
-
fields.
|
|
385
|
-
- Otherwise it falls back to the cardinality threshold on the union of the three
|
|
386
|
-
root types: `true` only when the new total drops below `threshold` × the
|
|
387
|
-
current total. A small intentional removal (above threshold) is allowed.
|
|
317
|
+
let stitchStandalone = (~fragment: Reventless.Plugin.apiSchemaFragment): string =>
|
|
318
|
+
assembleSdl(~fragments=[fragment], ~includeGlobalNodeQuery=false)
|
|
388
319
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
let isCatastrophicSchemaShrink = (~currentSdl: string, ~newSdl: string, ~threshold: float): bool => {
|
|
394
|
-
let currentNames = allRootFieldNames(~sdl=currentSdl)
|
|
395
|
-
let newSet = allRootFieldNames(~sdl=newSdl)->Set.fromArray
|
|
396
|
-
let dropped = currentNames->Array.filter(name => !(newSet->Set.has(name)))
|
|
397
|
-
if dropped->Array.length == 0 {
|
|
398
|
-
// Additive or unchanged push — the new schema keeps every live field.
|
|
399
|
-
false
|
|
400
|
-
} else {
|
|
401
|
-
let current = currentNames->Array.length
|
|
402
|
-
let next = allRootFieldNames(~sdl=newSdl)->Array.length
|
|
403
|
-
current > 0 && next->Int.toFloat < current->Int.toFloat *. threshold
|
|
404
|
-
}
|
|
405
|
-
}
|
|
320
|
+
// (The schema-clobber guard family — rootTypeFieldNames / countRootTypeFields /
|
|
321
|
+
// missingRootFields / isCatastrophicSchemaShrink — was retired with the push
|
|
322
|
+
// machinery: under merged-API composition every source API is a single writer,
|
|
323
|
+
// so no whole-replace push exists to guard.)
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
4
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
-
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
7
6
|
import * as Logger$ReventlessCore from "../../util/Logger.res.mjs";
|
|
8
7
|
|
|
9
8
|
let log = Logger$ReventlessCore.fromEnv();
|
|
@@ -132,9 +131,8 @@ let relayBaseTypes = [
|
|
|
132
131
|
|
|
133
132
|
let relayBaseQueries = [` node(id: ID!): Node`];
|
|
134
133
|
|
|
135
|
-
function
|
|
136
|
-
let
|
|
137
|
-
let parts = allFragments.map(decode);
|
|
134
|
+
function assembleSdl(fragments, includeGlobalNodeQuery) {
|
|
135
|
+
let parts = fragments.map(decode);
|
|
138
136
|
let seenTypeNames = new Set();
|
|
139
137
|
let allTypes = [];
|
|
140
138
|
relayBaseTypes.forEach(typeDef => {
|
|
@@ -173,11 +171,13 @@ function stitch(baseFragment, pluginFragments) {
|
|
|
173
171
|
});
|
|
174
172
|
let seenQueryFields = new Set();
|
|
175
173
|
let allQueries = [];
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
174
|
+
if (includeGlobalNodeQuery) {
|
|
175
|
+
relayBaseQueries.forEach(field => {
|
|
176
|
+
let fieldName = extractLeadingName(field);
|
|
177
|
+
seenQueryFields.add(fieldName);
|
|
178
|
+
allQueries.push(field);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
181
|
parts.forEach(param => {
|
|
182
182
|
param.queries.forEach(field => {
|
|
183
183
|
let fieldName = extractLeadingName(field);
|
|
@@ -217,86 +217,12 @@ function stitch(baseFragment, pluginFragments) {
|
|
|
217
217
|
return sdlParts.join("\n\n");
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
if (c !== undefined) {
|
|
223
|
-
if (c >= 65 && c <= 90 || c >= 97 && c <= 122) {
|
|
224
|
-
return true;
|
|
225
|
-
} else {
|
|
226
|
-
return c === 95;
|
|
227
|
-
}
|
|
228
|
-
} else {
|
|
229
|
-
return false;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function rootTypeFieldNames(sdl, typeName) {
|
|
234
|
-
let marker = `type ` + typeName;
|
|
235
|
-
let typeStart = Stdlib_String.indexOfOpt(sdl, marker);
|
|
236
|
-
if (typeStart === undefined) {
|
|
237
|
-
return [];
|
|
238
|
-
}
|
|
239
|
-
let rest = sdl.slice(typeStart);
|
|
240
|
-
let match = Stdlib_String.indexOfOpt(rest, "{");
|
|
241
|
-
let match$1 = Stdlib_String.indexOfOpt(rest, "}");
|
|
242
|
-
if (match !== undefined) {
|
|
243
|
-
if (match$1 !== undefined) {
|
|
244
|
-
if (match$1 > match) {
|
|
245
|
-
return Stdlib_Array.filterMap(rest.slice(match + 1 | 0, match$1).split("\n"), line => {
|
|
246
|
-
let trimmed = line.trim();
|
|
247
|
-
if (!(trimmed.length > 0 && isIdentStart(trimmed))) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
let name = extractLeadingName(trimmed);
|
|
251
|
-
let name$1 = name.endsWith(":") ? name.slice(0, name.length - 1 | 0) : name;
|
|
252
|
-
if (name$1.length > 0) {
|
|
253
|
-
return name$1;
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
} else {
|
|
257
|
-
return [];
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
return [];
|
|
261
|
-
}
|
|
262
|
-
} else {
|
|
263
|
-
return [];
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function countRootTypeFields(sdl, typeName) {
|
|
268
|
-
return rootTypeFieldNames(sdl, typeName).length;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
let rootTypeNames = [
|
|
272
|
-
"Mutation",
|
|
273
|
-
"Query",
|
|
274
|
-
"Subscription"
|
|
275
|
-
];
|
|
276
|
-
|
|
277
|
-
function allRootFieldNames(sdl) {
|
|
278
|
-
return rootTypeNames.flatMap(typeName => rootTypeFieldNames(sdl, typeName));
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function missingRootFields(expectedSdl, liveSdl) {
|
|
282
|
-
let liveSet = new Set(allRootFieldNames(liveSdl));
|
|
283
|
-
return allRootFieldNames(expectedSdl).filter(name => !liveSet.has(name));
|
|
220
|
+
function stitch(baseFragment, pluginFragments) {
|
|
221
|
+
return assembleSdl([baseFragment].concat(pluginFragments), true);
|
|
284
222
|
}
|
|
285
223
|
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
let newSet = new Set(allRootFieldNames(newSdl));
|
|
289
|
-
let dropped = currentNames.filter(name => !newSet.has(name));
|
|
290
|
-
if (dropped.length === 0) {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
let current = currentNames.length;
|
|
294
|
-
let next = allRootFieldNames(newSdl).length;
|
|
295
|
-
if (current > 0) {
|
|
296
|
-
return next < current * threshold;
|
|
297
|
-
} else {
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
224
|
+
function stitchStandalone(fragment) {
|
|
225
|
+
return assembleSdl([fragment], false);
|
|
300
226
|
}
|
|
301
227
|
|
|
302
228
|
export {
|
|
@@ -308,13 +234,8 @@ export {
|
|
|
308
234
|
extractLeadingName,
|
|
309
235
|
relayBaseTypes,
|
|
310
236
|
relayBaseQueries,
|
|
237
|
+
assembleSdl,
|
|
311
238
|
stitch,
|
|
312
|
-
|
|
313
|
-
rootTypeFieldNames,
|
|
314
|
-
countRootTypeFields,
|
|
315
|
-
rootTypeNames,
|
|
316
|
-
allRootFieldNames,
|
|
317
|
-
missingRootFields,
|
|
318
|
-
isCatastrophicSchemaShrink,
|
|
239
|
+
stitchStandalone,
|
|
319
240
|
}
|
|
320
241
|
/* log Not a pure module */
|