@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.
Files changed (32) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +6 -6
  3. package/src/Platform.res +116 -849
  4. package/src/Platform.res.mjs +75 -710
  5. package/src/adapter/QueryDb/PgQueryResolver_Builder.res +4 -47
  6. package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +1 -30
  7. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +0 -11
  8. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +0 -5
  9. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +6 -7
  10. package/src/components/Api/AppSync_Adapter.res +13 -105
  11. package/src/components/Api/AppSync_Adapter.res.mjs +0 -52
  12. package/src/components/Api/AppSync_MergedApi.res +1 -1
  13. package/src/components/Api/AppSync_SdlDecorate.res +6 -65
  14. package/src/components/Api/AppSync_SdlDecorate.res.mjs +1 -31
  15. package/src/components/Plugin.res.mjs +1 -2
  16. package/tests/AppSync_AdapterTest.res +23 -15
  17. package/tests/AppSync_AdapterTest.res.mjs +18 -16
  18. package/tests/AppSync_SdlDecorateTest.res +9 -89
  19. package/tests/AppSync_SdlDecorateTest.res.mjs +7 -72
  20. package/tests/MCP_LambdaTest.res +4 -6
  21. package/tests/MCP_LambdaTest.res.mjs +2 -2
  22. package/src/adapter/Api/ApiFragmentDeregistration.res +0 -138
  23. package/src/adapter/Api/ApiFragmentDeregistration.res.mjs +0 -108
  24. package/src/adapter/Api/ApiSchemaPush.res +0 -79
  25. package/src/adapter/Api/ApiSchemaPush.res.mjs +0 -64
  26. package/src/adapter/Api/Platform_ApiFragments_Lambda.res +0 -222
  27. package/src/adapter/Api/Platform_ApiFragments_Lambda.res.mjs +0 -202
  28. package/src/adapter/QueryDb/NodeResolver_AppSync.res +0 -71
  29. package/src/adapter/QueryDb/NodeResolver_AppSync.res.mjs +0 -44
  30. package/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs +0 -204
  31. package/tests/ApiSchemaPushTest.res +0 -32
  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.stitchWithAwsDirectives(~baseFragment, ~pluginFragments=[])
666
+ AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment=baseFragment)
667
667
  ->AppSync_SdlDecorate.stampCanonicalTypes
668
668
 
669
- let emptyBaseFragment = ReventlessCore.GraphQL_Stitcher.encode({
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 relayBaseSourceSdl = assembleCanonicalSourceSdl(~baseFragment=emptyBaseFragment)
686
+ let domainBaseSourceSdl = assembleCanonicalSourceSdl(~baseFragment=domainBaseFragment)
686
687
 
687
- testSync("admin source document carries the global node query", () => {
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 dual-auth stamps", () => {
705
+ testSync("admin source document keeps admin fields and shared-type IAM stamps", () => {
701
706
  expect(adminSourceSdl)->toContain("Platform_Plugin")
702
- expect(adminSourceSdl)->toContain("@aws_iam")
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("relay-base source document (split-mode Domain source) is node + relay types only", () => {
706
- expect(relayBaseSourceSdl)->toContain("node(id: ID!): Node")
707
- expect(relayBaseSourceSdl)->toContain("interface Node @canonical {")
708
- expect(relayBaseSourceSdl)->toContain("type PageInfo")
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(relayBaseSourceSdl)->not_->toContain("Platform_Plugin")
718
+ expect(domainBaseSourceSdl)->not_->toContain("Platform_Plugin")
711
719
  // No mutations → no CommandResult family on this source.
712
- expect(relayBaseSourceSdl)->not_->toContain("union CommandResult")
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 emptyBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
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", AdminApi$ReventlessCore.systemCallerFieldNames);
618
- let adminSourceSdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchWithAwsDirectives(baseFragment, []));
619
- let relayBaseSourceSdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchWithAwsDirectives(emptyBaseFragment, []));
620
- globalThis.test("admin source document carries the global node query", () => {
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 dual-auth stamps", () => {
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("relay-base source document (split-mode Domain source) is node + relay types only", () => {
634
- globalThis.expect(relayBaseSourceSdl).toContain("node(id: ID!): Node");
635
- globalThis.expect(relayBaseSourceSdl).toContain("interface Node @canonical {");
636
- globalThis.expect(relayBaseSourceSdl).toContain("type PageInfo");
637
- globalThis.expect(relayBaseSourceSdl).not.toContain("Platform_Plugin");
638
- globalThis.expect(relayBaseSourceSdl).not.toContain("union CommandResult");
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.stitchWithAwsDirectives", () => {
71
- testSync("assembles neutral fragments into AWS-dialect SDL", () => {
72
- let baseFragment = ReventlessCore.GraphQL_Stitcher.encode({
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 pluginFragment = ReventlessCore.GraphQL_Stitcher.encode({
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
- expect(sdl)->toContain(`@aws_subscribe(mutations: ["Catalog_AddProduct"])`)
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
- // Regression: the deploy waiter polls the IAM-callable Platform_ApiFragments query via
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.stitchWithAwsDirectives", () => {
65
- globalThis.test("assembles neutral fragments into AWS-dialect SDL", () => {
66
- let baseFragment = GraphQL_Stitcher$ReventlessCore.encode({
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 pluginFragment = GraphQL_Stitcher$ReventlessCore.encode({
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(`@aws_subscribe(mutations: ["Catalog_AddProduct"])`);
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 Platform_ApiFragmentEntry with dual-auth", () => {
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
  });
@@ -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
- // ApiFragmentRegistry_Register/DeregisterApiFragment mutations
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
- // ApiFragmentRegistry_Register/DeregisterApiFragment, Clone
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(5);
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(6);
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 */