@reventlessdev/reventless-aws 3.0.0-alpha.210 → 3.0.0-alpha.211
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 +7 -0
- package/package.json +6 -6
- package/src/Platform.res +116 -849
- package/src/Platform.res.mjs +75 -710
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res +4 -47
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +1 -30
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +0 -11
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +0 -5
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +6 -7
- package/src/components/Api/AppSync_Adapter.res +13 -105
- package/src/components/Api/AppSync_Adapter.res.mjs +0 -52
- package/src/components/Api/AppSync_MergedApi.res +1 -1
- package/src/components/Api/AppSync_SdlDecorate.res +6 -65
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +1 -31
- package/src/components/Plugin.res.mjs +1 -2
- package/tests/AppSync_AdapterTest.res +23 -15
- package/tests/AppSync_AdapterTest.res.mjs +18 -16
- package/tests/AppSync_SdlDecorateTest.res +9 -89
- package/tests/AppSync_SdlDecorateTest.res.mjs +7 -72
- package/tests/MCP_LambdaTest.res +4 -6
- package/tests/MCP_LambdaTest.res.mjs +2 -2
- package/src/adapter/Api/ApiFragmentDeregistration.res +0 -138
- package/src/adapter/Api/ApiFragmentDeregistration.res.mjs +0 -108
- package/src/adapter/Api/ApiSchemaPush.res +0 -79
- package/src/adapter/Api/ApiSchemaPush.res.mjs +0 -64
- package/src/adapter/Api/Platform_ApiFragments_Lambda.res +0 -222
- package/src/adapter/Api/Platform_ApiFragments_Lambda.res.mjs +0 -202
- package/src/adapter/QueryDb/NodeResolver_AppSync.res +0 -71
- package/src/adapter/QueryDb/NodeResolver_AppSync.res.mjs +0 -44
- package/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs +0 -204
- package/tests/ApiSchemaPushTest.res +0 -32
- package/tests/ApiSchemaPushTest.res.mjs +0 -20
|
@@ -663,13 +663,15 @@ describe("Split mode — empty base fragment", () => {
|
|
|
663
663
|
|
|
664
664
|
describe("Merged mode — canonical source documents", () => {
|
|
665
665
|
let assembleCanonicalSourceSdl = (~baseFragment) =>
|
|
666
|
-
AppSync_Adapter.
|
|
666
|
+
AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment=baseFragment)
|
|
667
667
|
->AppSync_SdlDecorate.stampCanonicalTypes
|
|
668
668
|
|
|
669
|
-
|
|
669
|
+
// Mirrors Platform.res's domainBaseFragment: no component fields, one
|
|
670
|
+
// Platform_ping so the Query type is non-empty.
|
|
671
|
+
let domainBaseFragment = ReventlessCore.GraphQL_Stitcher.encode({
|
|
670
672
|
types: [],
|
|
671
673
|
mutations: [],
|
|
672
|
-
queries: [],
|
|
674
|
+
queries: [" Platform_ping: String"],
|
|
673
675
|
subscriptions: [],
|
|
674
676
|
subscriptionSources: [],
|
|
675
677
|
})
|
|
@@ -678,14 +680,17 @@ describe("Merged mode — canonical source documents", () => {
|
|
|
678
680
|
~baseFragment=AppSync_Adapter.injectAwsAuthAll(
|
|
679
681
|
ReventlessCore.AdminApi.baseFragment(~cloner=false),
|
|
680
682
|
~group="Admin",
|
|
681
|
-
~iamFieldNames=ReventlessCore.AdminApi.systemCallerFieldNames,
|
|
682
683
|
),
|
|
683
684
|
)
|
|
684
685
|
|
|
685
|
-
let
|
|
686
|
+
let domainBaseSourceSdl = assembleCanonicalSourceSdl(~baseFragment=domainBaseFragment)
|
|
686
687
|
|
|
687
|
-
testSync("
|
|
688
|
-
expect(adminSourceSdl)->toContain("node(id: ID!): Node")
|
|
688
|
+
testSync("no AWS source document carries the global node query (dropped — see plan)", () => {
|
|
689
|
+
expect(adminSourceSdl)->not_->toContain("node(id: ID!): Node")
|
|
690
|
+
expect(domainBaseSourceSdl)->not_->toContain("node(id: ID!): Node")
|
|
691
|
+
// The Node interface and global IDs stay — they are what makes a future
|
|
692
|
+
// node() possible without a schema migration.
|
|
693
|
+
expect(adminSourceSdl)->toContain("interface Node")
|
|
689
694
|
})
|
|
690
695
|
|
|
691
696
|
testSync("admin source document stamps @canonical on the shared traversal types", () => {
|
|
@@ -697,19 +702,22 @@ describe("Merged mode — canonical source documents", () => {
|
|
|
697
702
|
)
|
|
698
703
|
})
|
|
699
704
|
|
|
700
|
-
testSync("admin source document keeps admin fields and
|
|
705
|
+
testSync("admin source document keeps admin fields and shared-type IAM stamps", () => {
|
|
701
706
|
expect(adminSourceSdl)->toContain("Platform_Plugin")
|
|
702
|
-
|
|
707
|
+
// Type-level dual-auth on the shared traversal types (stampSharedIamTypes);
|
|
708
|
+
// no FIELD carries @aws_iam anymore — the SigV4 register/deregister surface
|
|
709
|
+
// died with the fragment registry.
|
|
710
|
+
expect(adminSourceSdl)->toContain("type PageInfo @aws_cognito_user_pools @aws_iam")
|
|
703
711
|
})
|
|
704
712
|
|
|
705
|
-
testSync("
|
|
706
|
-
expect(
|
|
707
|
-
expect(
|
|
708
|
-
expect(
|
|
713
|
+
testSync("Domain base source document (split mode) is relay types + Platform_ping only", () => {
|
|
714
|
+
expect(domainBaseSourceSdl)->toContain("Platform_ping: String")
|
|
715
|
+
expect(domainBaseSourceSdl)->toContain("interface Node @canonical {")
|
|
716
|
+
expect(domainBaseSourceSdl)->toContain("type PageInfo")
|
|
709
717
|
// No admin or plugin fields on the split-mode Domain source.
|
|
710
|
-
expect(
|
|
718
|
+
expect(domainBaseSourceSdl)->not_->toContain("Platform_Plugin")
|
|
711
719
|
// No mutations → no CommandResult family on this source.
|
|
712
|
-
expect(
|
|
720
|
+
expect(domainBaseSourceSdl)->not_->toContain("union CommandResult")
|
|
713
721
|
})
|
|
714
722
|
|
|
715
723
|
// Phase 4: the plugin-stack subgraph document pushed to the plugin's own
|
|
@@ -607,18 +607,20 @@ globalThis.describe("Split mode — empty base fragment", () => {
|
|
|
607
607
|
});
|
|
608
608
|
|
|
609
609
|
globalThis.describe("Merged mode — canonical source documents", () => {
|
|
610
|
-
let
|
|
610
|
+
let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
|
|
611
611
|
types: [],
|
|
612
612
|
mutations: [],
|
|
613
|
-
queries: [],
|
|
613
|
+
queries: [" Platform_ping: String"],
|
|
614
614
|
subscriptions: [],
|
|
615
615
|
subscriptionSources: []
|
|
616
616
|
});
|
|
617
|
-
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin",
|
|
618
|
-
let adminSourceSdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.
|
|
619
|
-
let
|
|
620
|
-
globalThis.test("
|
|
621
|
-
globalThis.expect(adminSourceSdl).toContain("node(id: ID!): Node");
|
|
617
|
+
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin", undefined);
|
|
618
|
+
let adminSourceSdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(baseFragment));
|
|
619
|
+
let domainBaseSourceSdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(domainBaseFragment));
|
|
620
|
+
globalThis.test("no AWS source document carries the global node query (dropped — see plan)", () => {
|
|
621
|
+
globalThis.expect(adminSourceSdl).not.toContain("node(id: ID!): Node");
|
|
622
|
+
globalThis.expect(domainBaseSourceSdl).not.toContain("node(id: ID!): Node");
|
|
623
|
+
globalThis.expect(adminSourceSdl).toContain("interface Node");
|
|
622
624
|
});
|
|
623
625
|
globalThis.test("admin source document stamps @canonical on the shared traversal types", () => {
|
|
624
626
|
globalThis.expect(adminSourceSdl).toContain("type PageInfo @aws_cognito_user_pools @aws_iam @canonical {");
|
|
@@ -626,16 +628,16 @@ globalThis.describe("Merged mode — canonical source documents", () => {
|
|
|
626
628
|
globalThis.expect(adminSourceSdl).toContain("union CommandResult @canonical =");
|
|
627
629
|
globalThis.expect(adminSourceSdl).toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam @canonical {");
|
|
628
630
|
});
|
|
629
|
-
globalThis.test("admin source document keeps admin fields and
|
|
631
|
+
globalThis.test("admin source document keeps admin fields and shared-type IAM stamps", () => {
|
|
630
632
|
globalThis.expect(adminSourceSdl).toContain("Platform_Plugin");
|
|
631
|
-
globalThis.expect(adminSourceSdl).toContain("@aws_iam");
|
|
632
|
-
});
|
|
633
|
-
globalThis.test("
|
|
634
|
-
globalThis.expect(
|
|
635
|
-
globalThis.expect(
|
|
636
|
-
globalThis.expect(
|
|
637
|
-
globalThis.expect(
|
|
638
|
-
globalThis.expect(
|
|
633
|
+
globalThis.expect(adminSourceSdl).toContain("type PageInfo @aws_cognito_user_pools @aws_iam");
|
|
634
|
+
});
|
|
635
|
+
globalThis.test("Domain base source document (split mode) is relay types + Platform_ping only", () => {
|
|
636
|
+
globalThis.expect(domainBaseSourceSdl).toContain("Platform_ping: String");
|
|
637
|
+
globalThis.expect(domainBaseSourceSdl).toContain("interface Node @canonical {");
|
|
638
|
+
globalThis.expect(domainBaseSourceSdl).toContain("type PageInfo");
|
|
639
|
+
globalThis.expect(domainBaseSourceSdl).not.toContain("Platform_Plugin");
|
|
640
|
+
globalThis.expect(domainBaseSourceSdl).not.toContain("union CommandResult");
|
|
639
641
|
});
|
|
640
642
|
let pluginFragment = GraphQL_Stitcher$ReventlessCore.encode({
|
|
641
643
|
types: [
|
|
@@ -67,10 +67,10 @@ describe("AppSync_SdlDecorate.injectAwsSubscribe", () => {
|
|
|
67
67
|
})
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
describe("AppSync_Adapter.
|
|
71
|
-
testSync("assembles neutral
|
|
72
|
-
let
|
|
73
|
-
types: [],
|
|
70
|
+
describe("AppSync_Adapter.stitchStandaloneWithAwsDirectives", () => {
|
|
71
|
+
testSync("assembles a neutral fragment into an AWS-dialect standalone document", () => {
|
|
72
|
+
let fragment = ReventlessCore.GraphQL_Stitcher.encode({
|
|
73
|
+
types: [`type PluginStatusChangeEvent {\n pluginId: ID!\n}`],
|
|
74
74
|
mutations: [` Platform_PluginStatusChanged(pluginId: ID!, status: PluginStatus!): PluginStatusChangeEvent`],
|
|
75
75
|
queries: [],
|
|
76
76
|
subscriptions: [` onPluginStatusChange: PluginStatusChangeEvent`],
|
|
@@ -78,19 +78,11 @@ describe("AppSync_Adapter.stitchWithAwsDirectives", () => {
|
|
|
78
78
|
{field: "onPluginStatusChange", mutations: ["Platform_PluginStatusChanged"]},
|
|
79
79
|
],
|
|
80
80
|
})
|
|
81
|
-
let
|
|
82
|
-
types: [`type PluginStatusChangeEvent {\n pluginId: ID!\n}`],
|
|
83
|
-
mutations: [` Catalog_AddProduct(input: AddInput): CommandResult`],
|
|
84
|
-
queries: [],
|
|
85
|
-
subscriptions: [` onCatalog_AddProduct(id: ID): CommandResult`],
|
|
86
|
-
subscriptionSources: [{field: "onCatalog_AddProduct", mutations: ["Catalog_AddProduct"]}],
|
|
87
|
-
})
|
|
88
|
-
let sdl = AppSync_Adapter.stitchWithAwsDirectives(
|
|
89
|
-
~baseFragment,
|
|
90
|
-
~pluginFragments=[pluginFragment],
|
|
91
|
-
)
|
|
81
|
+
let sdl = AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment)
|
|
92
82
|
expect(sdl)->toContain(`@aws_subscribe(mutations: ["Platform_PluginStatusChanged"])`)
|
|
93
|
-
|
|
83
|
+
// Standalone documents never carry the global node query (dropped on AWS —
|
|
84
|
+
// see the merged-api plan's Relay node resolution).
|
|
85
|
+
expect(sdl)->not_->toContain("node(id: ID!): Node")
|
|
94
86
|
})
|
|
95
87
|
})
|
|
96
88
|
|
|
@@ -126,81 +118,9 @@ describe("AppSync_SdlDecorate.injectAwsAuthAll", () => {
|
|
|
126
118
|
})
|
|
127
119
|
})
|
|
128
120
|
|
|
129
|
-
describe("AppSync_SdlDecorate.planAwsPushes", () => {
|
|
130
|
-
// Neutral admin base: a system-callable mutation + a shared traversal type.
|
|
131
|
-
let rawAdminBase = ReventlessCore.GraphQL_Stitcher.encode({
|
|
132
|
-
types: [`type CommandAccepted {\n id: ID!\n}`],
|
|
133
|
-
mutations: [` Platform_RegisterApiFragment(input: ApiFragmentInput): CommandResult`],
|
|
134
|
-
queries: [],
|
|
135
|
-
subscriptions: [],
|
|
136
|
-
subscriptionSources: [],
|
|
137
|
-
})
|
|
138
|
-
let iamFieldNames = ["Platform_RegisterApiFragment"]
|
|
139
|
-
let mkFrag = (~mutation, ~target): AppSync_SdlDecorate.targetedFragmentInput => {
|
|
140
|
-
encoded: ReventlessCore.GraphQL_Stitcher.encode({
|
|
141
|
-
types: [],
|
|
142
|
-
mutations: [mutation],
|
|
143
|
-
queries: [],
|
|
144
|
-
subscriptions: [],
|
|
145
|
-
subscriptionSources: [],
|
|
146
|
-
}).encoded,
|
|
147
|
-
protocol: "graphql",
|
|
148
|
-
target,
|
|
149
|
-
}
|
|
150
|
-
let platformFrag = mkFrag(~mutation=` Inspector_Ping: CommandResult`, ~target="Platform")
|
|
151
|
-
let domainFrag = mkFrag(~mutation=` Catalog_AddProduct(input: AddInput): CommandResult`, ~target="Domain")
|
|
152
|
-
|
|
153
|
-
testSync("split mode: Platform API carries admin base (dual-auth) + Platform frags; Domain API excludes the admin base", () => {
|
|
154
|
-
let plans = AppSync_SdlDecorate.planAwsPushes(
|
|
155
|
-
~rawAdminBase,
|
|
156
|
-
~iamFieldNames,
|
|
157
|
-
~fragments=[platformFrag, domainFrag],
|
|
158
|
-
~splitApi=true,
|
|
159
|
-
)
|
|
160
|
-
expect(plans->Array.length)->toBe(2)
|
|
161
|
-
let platformPlan = plans->Array.find(p => p.api == "PlatformApi")->Option.getOrThrow
|
|
162
|
-
let domainPlan = plans->Array.find(p => p.api == "DomainApi")->Option.getOrThrow
|
|
163
|
-
// Admin base mutation lands on the Platform API with dual-auth (system caller).
|
|
164
|
-
expect(platformPlan.sdl)->toContain("Platform_RegisterApiFragment")
|
|
165
|
-
expect(platformPlan.sdl)->toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin"]) @aws_iam`)
|
|
166
|
-
// Platform-target plugin field present on the Platform API.
|
|
167
|
-
expect(platformPlan.sdl)->toContain("Inspector_Ping")
|
|
168
|
-
// Shared traversal type stamped once on the assembled SDL.
|
|
169
|
-
expect(platformPlan.sdl)->toContain(`type CommandAccepted @aws_cognito_user_pools @aws_iam {`)
|
|
170
|
-
// Domain-target field on the Domain API; admin base absent (empty base in split mode).
|
|
171
|
-
expect(domainPlan.sdl)->toContain("Catalog_AddProduct")
|
|
172
|
-
expect(domainPlan.sdl->String.includes("Platform_RegisterApiFragment"))->toBe(false)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
testSync("unified mode: a single Domain API carries admin base + all frags", () => {
|
|
176
|
-
let plans = AppSync_SdlDecorate.planAwsPushes(
|
|
177
|
-
~rawAdminBase,
|
|
178
|
-
~iamFieldNames,
|
|
179
|
-
~fragments=[platformFrag, domainFrag],
|
|
180
|
-
~splitApi=false,
|
|
181
|
-
)
|
|
182
|
-
expect(plans->Array.length)->toBe(1)
|
|
183
|
-
let plan = plans->Array.getUnsafe(0)
|
|
184
|
-
expect(plan.api)->toBe("DomainApi")
|
|
185
|
-
expect(plan.sdl)->toContain("Platform_RegisterApiFragment")
|
|
186
|
-
expect(plan.sdl)->toContain("Inspector_Ping")
|
|
187
|
-
expect(plan.sdl)->toContain("Catalog_AddProduct")
|
|
188
|
-
})
|
|
189
|
-
})
|
|
190
121
|
|
|
191
122
|
describe("AppSync_SdlDecorate.stampSharedIamTypes", () => {
|
|
192
|
-
|
|
193
|
-
// SigV4; its return type Platform_ApiFragmentEntry must carry the type-level @aws_iam or
|
|
194
|
-
// the SigV4 caller reaches the query field but gets "Not Authorized to access <field> on
|
|
195
|
-
// type Platform_ApiFragmentEntry" (deploy validation #7).
|
|
196
|
-
testSync("stamps Platform_ApiFragmentEntry with dual-auth", () => {
|
|
197
|
-
let sdl = AppSync_SdlDecorate.stampSharedIamTypes(
|
|
198
|
-
"type Platform_ApiFragmentEntry {\n pluginId: String!\n pushStatus: String!\n}",
|
|
199
|
-
)
|
|
200
|
-
expect(sdl)->toContain("type Platform_ApiFragmentEntry @aws_cognito_user_pools @aws_iam {")
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
testSync("still stamps the CommandResult members", () => {
|
|
123
|
+
testSync("stamps the CommandResult members", () => {
|
|
204
124
|
let sdl = AppSync_SdlDecorate.stampSharedIamTypes("type CommandAccepted {\n id: ID!\n}")
|
|
205
125
|
expect(sdl)->toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam {")
|
|
206
126
|
})
|
|
@@ -61,10 +61,10 @@ globalThis.describe("AppSync_SdlDecorate.injectAwsSubscribe", () => {
|
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
globalThis.describe("AppSync_Adapter.
|
|
65
|
-
globalThis.test("assembles neutral
|
|
66
|
-
let
|
|
67
|
-
types: [],
|
|
64
|
+
globalThis.describe("AppSync_Adapter.stitchStandaloneWithAwsDirectives", () => {
|
|
65
|
+
globalThis.test("assembles a neutral fragment into an AWS-dialect standalone document", () => {
|
|
66
|
+
let fragment = GraphQL_Stitcher$ReventlessCore.encode({
|
|
67
|
+
types: [`type PluginStatusChangeEvent {\n pluginId: ID!\n}`],
|
|
68
68
|
mutations: [` Platform_PluginStatusChanged(pluginId: ID!, status: PluginStatus!): PluginStatusChangeEvent`],
|
|
69
69
|
queries: [],
|
|
70
70
|
subscriptions: [` onPluginStatusChange: PluginStatusChangeEvent`],
|
|
@@ -73,19 +73,9 @@ globalThis.describe("AppSync_Adapter.stitchWithAwsDirectives", () => {
|
|
|
73
73
|
mutations: ["Platform_PluginStatusChanged"]
|
|
74
74
|
}]
|
|
75
75
|
});
|
|
76
|
-
let
|
|
77
|
-
types: [`type PluginStatusChangeEvent {\n pluginId: ID!\n}`],
|
|
78
|
-
mutations: [` Catalog_AddProduct(input: AddInput): CommandResult`],
|
|
79
|
-
queries: [],
|
|
80
|
-
subscriptions: [` onCatalog_AddProduct(id: ID): CommandResult`],
|
|
81
|
-
subscriptionSources: [{
|
|
82
|
-
field: "onCatalog_AddProduct",
|
|
83
|
-
mutations: ["Catalog_AddProduct"]
|
|
84
|
-
}]
|
|
85
|
-
});
|
|
86
|
-
let sdl = AppSync_Adapter$ReventlessAws.stitchWithAwsDirectives(baseFragment, [pluginFragment]);
|
|
76
|
+
let sdl = AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(fragment);
|
|
87
77
|
globalThis.expect(sdl).toContain(`@aws_subscribe(mutations: ["Platform_PluginStatusChanged"])`);
|
|
88
|
-
globalThis.expect(sdl).toContain(
|
|
78
|
+
globalThis.expect(sdl).not.toContain("node(id: ID!): Node");
|
|
89
79
|
});
|
|
90
80
|
});
|
|
91
81
|
|
|
@@ -115,63 +105,8 @@ globalThis.describe("AppSync_SdlDecorate.injectAwsAuthAll", () => {
|
|
|
115
105
|
});
|
|
116
106
|
});
|
|
117
107
|
|
|
118
|
-
globalThis.describe("AppSync_SdlDecorate.planAwsPushes", () => {
|
|
119
|
-
let rawAdminBase = GraphQL_Stitcher$ReventlessCore.encode({
|
|
120
|
-
types: [`type CommandAccepted {\n id: ID!\n}`],
|
|
121
|
-
mutations: [` Platform_RegisterApiFragment(input: ApiFragmentInput): CommandResult`],
|
|
122
|
-
queries: [],
|
|
123
|
-
subscriptions: [],
|
|
124
|
-
subscriptionSources: []
|
|
125
|
-
});
|
|
126
|
-
let iamFieldNames = ["Platform_RegisterApiFragment"];
|
|
127
|
-
let mkFrag = (mutation, target) => ({
|
|
128
|
-
encoded: GraphQL_Stitcher$ReventlessCore.encode({
|
|
129
|
-
types: [],
|
|
130
|
-
mutations: [mutation],
|
|
131
|
-
queries: [],
|
|
132
|
-
subscriptions: [],
|
|
133
|
-
subscriptionSources: []
|
|
134
|
-
}).encoded,
|
|
135
|
-
protocol: "graphql",
|
|
136
|
-
target: target
|
|
137
|
-
});
|
|
138
|
-
let platformFrag = mkFrag(` Inspector_Ping: CommandResult`, "Platform");
|
|
139
|
-
let domainFrag = mkFrag(` Catalog_AddProduct(input: AddInput): CommandResult`, "Domain");
|
|
140
|
-
globalThis.test("split mode: Platform API carries admin base (dual-auth) + Platform frags; Domain API excludes the admin base", () => {
|
|
141
|
-
let plans = AppSync_SdlDecorate$ReventlessAws.planAwsPushes(rawAdminBase, iamFieldNames, [
|
|
142
|
-
platformFrag,
|
|
143
|
-
domainFrag
|
|
144
|
-
], true);
|
|
145
|
-
globalThis.expect(plans.length).toBe(2);
|
|
146
|
-
let platformPlan = Stdlib_Option.getOrThrow(plans.find(p => p.api === "PlatformApi"), undefined);
|
|
147
|
-
let domainPlan = Stdlib_Option.getOrThrow(plans.find(p => p.api === "DomainApi"), undefined);
|
|
148
|
-
globalThis.expect(platformPlan.sdl).toContain("Platform_RegisterApiFragment");
|
|
149
|
-
globalThis.expect(platformPlan.sdl).toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin"]) @aws_iam`);
|
|
150
|
-
globalThis.expect(platformPlan.sdl).toContain("Inspector_Ping");
|
|
151
|
-
globalThis.expect(platformPlan.sdl).toContain(`type CommandAccepted @aws_cognito_user_pools @aws_iam {`);
|
|
152
|
-
globalThis.expect(domainPlan.sdl).toContain("Catalog_AddProduct");
|
|
153
|
-
globalThis.expect(domainPlan.sdl.includes("Platform_RegisterApiFragment")).toBe(false);
|
|
154
|
-
});
|
|
155
|
-
globalThis.test("unified mode: a single Domain API carries admin base + all frags", () => {
|
|
156
|
-
let plans = AppSync_SdlDecorate$ReventlessAws.planAwsPushes(rawAdminBase, iamFieldNames, [
|
|
157
|
-
platformFrag,
|
|
158
|
-
domainFrag
|
|
159
|
-
], false);
|
|
160
|
-
globalThis.expect(plans.length).toBe(1);
|
|
161
|
-
let plan = plans[0];
|
|
162
|
-
globalThis.expect(plan.api).toBe("DomainApi");
|
|
163
|
-
globalThis.expect(plan.sdl).toContain("Platform_RegisterApiFragment");
|
|
164
|
-
globalThis.expect(plan.sdl).toContain("Inspector_Ping");
|
|
165
|
-
globalThis.expect(plan.sdl).toContain("Catalog_AddProduct");
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
108
|
globalThis.describe("AppSync_SdlDecorate.stampSharedIamTypes", () => {
|
|
170
|
-
globalThis.test("stamps
|
|
171
|
-
let sdl = AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes("type Platform_ApiFragmentEntry {\n pluginId: String!\n pushStatus: String!\n}");
|
|
172
|
-
globalThis.expect(sdl).toContain("type Platform_ApiFragmentEntry @aws_cognito_user_pools @aws_iam {");
|
|
173
|
-
});
|
|
174
|
-
globalThis.test("still stamps the CommandResult members", () => {
|
|
109
|
+
globalThis.test("stamps the CommandResult members", () => {
|
|
175
110
|
let sdl = AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes("type CommandAccepted {\n id: ID!\n}");
|
|
176
111
|
globalThis.expect(sdl).toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam {");
|
|
177
112
|
});
|
package/tests/MCP_LambdaTest.res
CHANGED
|
@@ -7,9 +7,8 @@ describe("MCP_Lambda.generateAdminConfig", () => {
|
|
|
7
7
|
~serverVersion="1.0.0",
|
|
8
8
|
~cloner=false,
|
|
9
9
|
)
|
|
10
|
-
// Admin has Plugin_Activate, Plugin_Deactivate, Plugin_Retire
|
|
11
|
-
|
|
12
|
-
expect(config.tools)->toHaveLength(5)
|
|
10
|
+
// Admin has Plugin_Activate, Plugin_Deactivate, Plugin_Retire
|
|
11
|
+
expect(config.tools)->toHaveLength(3)
|
|
13
12
|
})
|
|
14
13
|
|
|
15
14
|
testSync("produces additional clone tool when cloner=true", () => {
|
|
@@ -18,9 +17,8 @@ describe("MCP_Lambda.generateAdminConfig", () => {
|
|
|
18
17
|
~serverVersion="1.0.0",
|
|
19
18
|
~cloner=true,
|
|
20
19
|
)
|
|
21
|
-
// Plugin_Activate, Plugin_Deactivate, Plugin_Retire,
|
|
22
|
-
|
|
23
|
-
expect(config.tools)->toHaveLength(6)
|
|
20
|
+
// Plugin_Activate, Plugin_Deactivate, Plugin_Retire, Clone
|
|
21
|
+
expect(config.tools)->toHaveLength(4)
|
|
24
22
|
})
|
|
25
23
|
|
|
26
24
|
testSync("produces resources from admin query entries", () => {
|
|
@@ -6,11 +6,11 @@ import * as MCP_Lambda$ReventlessAws from "../src/adapter/Mcp/MCP_Lambda.res.mjs
|
|
|
6
6
|
globalThis.describe("MCP_Lambda.generateAdminConfig", () => {
|
|
7
7
|
globalThis.test("produces tools from admin mutation entries (no cloner)", () => {
|
|
8
8
|
let config = MCP_Lambda$ReventlessAws.generateAdminConfig("test", "1.0.0", false, undefined, undefined);
|
|
9
|
-
globalThis.expect(config.tools).toHaveLength(
|
|
9
|
+
globalThis.expect(config.tools).toHaveLength(3);
|
|
10
10
|
});
|
|
11
11
|
globalThis.test("produces additional clone tool when cloner=true", () => {
|
|
12
12
|
let config = MCP_Lambda$ReventlessAws.generateAdminConfig("test", "1.0.0", true, undefined, undefined);
|
|
13
|
-
globalThis.expect(config.tools).toHaveLength(
|
|
13
|
+
globalThis.expect(config.tools).toHaveLength(4);
|
|
14
14
|
});
|
|
15
15
|
globalThis.test("produces resources from admin query entries", () => {
|
|
16
16
|
let config = MCP_Lambda$ReventlessAws.generateAdminConfig("test", "1.0.0", undefined, undefined, undefined);
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/** Pulumi dynamic resource whose sole purpose is to deregister a plugin's
|
|
2
|
-
API-schema fragment on `pulumi destroy` (final retirement of the plugin
|
|
3
|
-
stack). `create` / `update` are no-ops — registration is done by
|
|
4
|
-
`Platform.registerFragmentViaApi` at deploy time; only `delete` matters,
|
|
5
|
-
and it calls `Platform_DeregisterApiFragment` on the platform's Platform API
|
|
6
|
-
(SigV4), whose event triggers the platform-side reactive single writer (2e)
|
|
7
|
-
to re-stitch the schema WITHOUT this plugin's fields.
|
|
8
|
-
|
|
9
|
-
Serialization: Pulumi serialises the provider object (and its captured import
|
|
10
|
-
closure) into stack state, so it must NOT statically capture the AWS SDK —
|
|
11
|
-
that breaks Pulumi's closure serialiser (the CJS/ESM dual-export confusion
|
|
12
|
-
documented in AppSync_Resolver_Retrying). The delete handler therefore
|
|
13
|
-
DYNAMICALLY imports the signed caller at invocation time; only the string
|
|
14
|
-
specifier lands in state.
|
|
15
|
-
|
|
16
|
-
Version supersession must NOT deregister: `diff` never reports changes/replaces
|
|
17
|
-
(pluginId, endpoint, and region are all version-stable), so a version bump is
|
|
18
|
-
an in-place no-op and the `delete` handler fires only on a genuine stack
|
|
19
|
-
destroy — the "final retirement, resolvers gone" trigger. */
|
|
20
|
-
|
|
21
|
-
let log = ReventlessCore.Logger.fromEnv()
|
|
22
|
-
|
|
23
|
-
type providerInputs = {
|
|
24
|
-
pluginId: string,
|
|
25
|
-
endpoint: string,
|
|
26
|
-
region: string,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Pulumi passes OUTPUTS (possibly undefined) to delete/read handlers, so encode
|
|
30
|
-
// the carrier fields into the resource id — always available. "|" cannot appear
|
|
31
|
-
// in a plugin name, an AWS region, or an AppSync GraphQL endpoint URL.
|
|
32
|
-
let encodeId = (i: providerInputs): string => `${i.pluginId}|${i.region}|${i.endpoint}`
|
|
33
|
-
let decodeId = (id: string): option<providerInputs> =>
|
|
34
|
-
switch id->String.split("|") {
|
|
35
|
-
| [pluginId, region, endpoint] => Some({pluginId, region, endpoint})
|
|
36
|
-
| _ => None
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Dynamically import the signed caller and send Platform_DeregisterApiFragment.
|
|
40
|
-
// The `import(...)` keeps Util_AppSync_Caller (and its transitive @aws-sdk/@smithy
|
|
41
|
-
// deps) OUT of the serialised provider closure — only this string is captured.
|
|
42
|
-
// Positional call matches the compiled signature
|
|
43
|
-
// `sendMutation(endpoint, region, mutation, selection, variables)`.
|
|
44
|
-
let sendDeregister: (JSON.t, string, string) => promise<unit> = %raw(`
|
|
45
|
-
async function (variables, endpoint, region) {
|
|
46
|
-
const mod = await import("@reventlessdev/reventless-aws/src/util/Util_AppSync_Caller.res.mjs");
|
|
47
|
-
await mod.sendMutation(endpoint, region, "Platform_ApiFragmentRegistry_DeregisterApiFragment", "{ __typename }", variables);
|
|
48
|
-
}
|
|
49
|
-
`)
|
|
50
|
-
|
|
51
|
-
let errMessage: 'a => string = %raw(`(e) => (e && e.message) ? String(e.message) : String(e)`)
|
|
52
|
-
|
|
53
|
-
type createResult = {id: string, outs: providerInputs}
|
|
54
|
-
type updateResult = {outs: providerInputs}
|
|
55
|
-
type diffResult = {changes: bool, replaces: array<string>, deleteBeforeReplace: bool}
|
|
56
|
-
|
|
57
|
-
let create = async (inputs: providerInputs): createResult => {id: encodeId(inputs), outs: inputs}
|
|
58
|
-
|
|
59
|
-
let update = async (_id: string, _olds: providerInputs, news: providerInputs): updateResult => {
|
|
60
|
-
outs: news,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Never change/replace — see the module note: keeps version bumps from deleting
|
|
64
|
-
// (and therefore deregistering) the fragment.
|
|
65
|
-
let diff = (_id: string, _olds: providerInputs, _news: providerInputs): diffResult => {
|
|
66
|
-
changes: false,
|
|
67
|
-
replaces: [],
|
|
68
|
-
deleteBeforeReplace: false,
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let delete_ = async (id: string, props: providerInputs): unit => {
|
|
72
|
-
let carrier = switch decodeId(id) {
|
|
73
|
-
| Some(c) => c
|
|
74
|
-
| None => props
|
|
75
|
-
}
|
|
76
|
-
let variables =
|
|
77
|
-
Dict.fromArray([
|
|
78
|
-
// Platform_ApiFragmentRegistry_DeregisterApiFragment(id: ID!, pluginId: ID!) — the
|
|
79
|
-
// registry is a SINGLETON aggregate, so `id` is the fixed constant; `pluginId`
|
|
80
|
-
// (payload) names the plugin whose fragment is removed.
|
|
81
|
-
("id", JSON.Encode.string("registry")),
|
|
82
|
-
("pluginId", JSON.Encode.string(carrier.pluginId)),
|
|
83
|
-
])->JSON.Encode.object
|
|
84
|
-
// Best-effort: on a full teardown the platform API may already be gone, and a
|
|
85
|
-
// destroy must not fail because deregistration could not reach it.
|
|
86
|
-
try {
|
|
87
|
-
await sendDeregister(variables, carrier.endpoint, carrier.region)
|
|
88
|
-
log.info(~comp="ApiFragmentDeregistration", `Deregistered API fragment for ${carrier.pluginId}`)
|
|
89
|
-
} catch {
|
|
90
|
-
| exn =>
|
|
91
|
-
log.warn(
|
|
92
|
-
~comp="ApiFragmentDeregistration",
|
|
93
|
-
`deregister for ${carrier.pluginId} failed (best-effort; platform may be gone): ${errMessage(
|
|
94
|
-
exn,
|
|
95
|
-
)}`,
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Provider as a plain JS object — no Pulumi Output captures; all state flows
|
|
101
|
-
// through inputs / id.
|
|
102
|
-
let provider = {
|
|
103
|
-
"create": create,
|
|
104
|
-
"update": update,
|
|
105
|
-
"delete": delete_,
|
|
106
|
-
"diff": diff,
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ── Pulumi dynamic resource binding ──────────────────────────────────────────
|
|
110
|
-
|
|
111
|
-
type t
|
|
112
|
-
|
|
113
|
-
type constructorProps = {
|
|
114
|
-
pluginId: Pulumi.Input.t<string>,
|
|
115
|
-
endpoint: Pulumi.Input.t<string>,
|
|
116
|
-
region: Pulumi.Input.t<string>,
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// pulumi.dynamic.Resource constructor: (provider, name, props, opts). Explicit
|
|
120
|
-
// /index.js path because @pulumi/pulumi/dynamic is a directory import not
|
|
121
|
-
// resolvable in ESM mode.
|
|
122
|
-
@module("@pulumi/pulumi/dynamic/index.js") @new
|
|
123
|
-
external _newResource: ('provider, string, 'props, Pulumi.CustomResourceOptions.t) => t = "Resource"
|
|
124
|
-
|
|
125
|
-
let make = (
|
|
126
|
-
~name: string,
|
|
127
|
-
~pluginId: string,
|
|
128
|
-
~endpoint: Pulumi.Input.t<string>,
|
|
129
|
-
~region: string,
|
|
130
|
-
~opts: Pulumi.CustomResourceOptions.t={},
|
|
131
|
-
): t => {
|
|
132
|
-
let props: constructorProps = {
|
|
133
|
-
pluginId: pluginId->Pulumi.Input.make,
|
|
134
|
-
endpoint,
|
|
135
|
-
region: region->Pulumi.Input.make,
|
|
136
|
-
}
|
|
137
|
-
_newResource(provider, name, props, opts)
|
|
138
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
4
|
-
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
5
|
-
import * as IndexJs from "@pulumi/pulumi/dynamic/index.js";
|
|
6
|
-
|
|
7
|
-
let log = Logger$ReventlessCore.fromEnv();
|
|
8
|
-
|
|
9
|
-
function encodeId(i) {
|
|
10
|
-
return i.pluginId + `|` + i.region + `|` + i.endpoint;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function decodeId(id) {
|
|
14
|
-
let match = id.split("|");
|
|
15
|
-
if (match.length !== 3) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
let pluginId = match[0];
|
|
19
|
-
let region = match[1];
|
|
20
|
-
let endpoint = match[2];
|
|
21
|
-
return {
|
|
22
|
-
pluginId: pluginId,
|
|
23
|
-
endpoint: endpoint,
|
|
24
|
-
region: region
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let sendDeregister = (async function (variables, endpoint, region) {
|
|
29
|
-
const mod = await import("@reventlessdev/reventless-aws/src/util/Util_AppSync_Caller.res.mjs");
|
|
30
|
-
await mod.sendMutation(endpoint, region, "Platform_ApiFragmentRegistry_DeregisterApiFragment", "{ __typename }", variables);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
let errMessage = ((e) => (e && e.message) ? String(e.message) : String(e));
|
|
34
|
-
|
|
35
|
-
async function create(inputs) {
|
|
36
|
-
return {
|
|
37
|
-
id: encodeId(inputs),
|
|
38
|
-
outs: inputs
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function update(_id, _olds, news) {
|
|
43
|
-
return {
|
|
44
|
-
outs: news
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function diff(_id, _olds, _news) {
|
|
49
|
-
return {
|
|
50
|
-
changes: false,
|
|
51
|
-
replaces: [],
|
|
52
|
-
deleteBeforeReplace: false
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function delete_(id, props) {
|
|
57
|
-
let c = decodeId(id);
|
|
58
|
-
let carrier = c !== undefined ? c : props;
|
|
59
|
-
let variables = Object.fromEntries([
|
|
60
|
-
[
|
|
61
|
-
"id",
|
|
62
|
-
"registry"
|
|
63
|
-
],
|
|
64
|
-
[
|
|
65
|
-
"pluginId",
|
|
66
|
-
carrier.pluginId
|
|
67
|
-
]
|
|
68
|
-
]);
|
|
69
|
-
try {
|
|
70
|
-
await sendDeregister(variables, carrier.endpoint, carrier.region);
|
|
71
|
-
return log.info("ApiFragmentDeregistration", undefined, `Deregistered API fragment for ` + carrier.pluginId);
|
|
72
|
-
} catch (raw_exn) {
|
|
73
|
-
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
74
|
-
return log.warn("ApiFragmentDeregistration", undefined, `deregister for ` + carrier.pluginId + ` failed (best-effort; platform may be gone): ` + errMessage(exn));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
let provider = {
|
|
79
|
-
create: create,
|
|
80
|
-
update: update,
|
|
81
|
-
delete: delete_,
|
|
82
|
-
diff: diff
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
function make(name, pluginId, endpoint, region, optsOpt) {
|
|
86
|
-
let opts = optsOpt !== undefined ? optsOpt : ({});
|
|
87
|
-
let props = {
|
|
88
|
-
pluginId: pluginId,
|
|
89
|
-
endpoint: endpoint,
|
|
90
|
-
region: region
|
|
91
|
-
};
|
|
92
|
-
return new IndexJs.Resource(provider, name, props, opts);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export {
|
|
96
|
-
log,
|
|
97
|
-
encodeId,
|
|
98
|
-
decodeId,
|
|
99
|
-
sendDeregister,
|
|
100
|
-
errMessage,
|
|
101
|
-
create,
|
|
102
|
-
update,
|
|
103
|
-
diff,
|
|
104
|
-
delete_,
|
|
105
|
-
provider,
|
|
106
|
-
make,
|
|
107
|
-
}
|
|
108
|
-
/* log Not a pure module */
|