@reventlessdev/reventless-aws 3.0.0-alpha.209 → 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 +16 -0
- package/package.json +8 -8
- package/src/Platform.res +348 -707
- package/src/Platform.res.mjs +271 -739
- 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 +143 -101
- package/src/components/Api/AppSync_Adapter.res.mjs +60 -57
- package/src/components/Api/AppSync_MergedApi.res +218 -0
- package/src/components/Api/AppSync_MergedApi.res.mjs +122 -0
- package/src/components/Api/AppSync_SdlDecorate.res +49 -61
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +38 -29
- package/src/components/Plugin.res.mjs +1 -2
- package/tests/AppSync_AdapterTest.res +184 -0
- package/tests/AppSync_AdapterTest.res.mjs +136 -0
- package/tests/AppSync_SdlDecorateTest.res +33 -86
- package/tests/AppSync_SdlDecorateTest.res.mjs +26 -71
- 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
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
4
|
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
5
5
|
import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
6
|
-
import * as GraphQL_PushPlanner$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_PushPlanner.res.mjs";
|
|
7
6
|
|
|
8
7
|
function injectAwsSubscribe(sdl, sources) {
|
|
9
8
|
if (sources.length === 0) {
|
|
@@ -80,39 +79,48 @@ let sharedIamTypeNames = [
|
|
|
80
79
|
"PageInfo",
|
|
81
80
|
"CommandAccepted",
|
|
82
81
|
"CommandRejected",
|
|
83
|
-
"CommandPending"
|
|
84
|
-
"Platform_ApiFragmentEntry"
|
|
82
|
+
"CommandPending"
|
|
85
83
|
];
|
|
86
84
|
|
|
87
85
|
function stampSharedIamTypes(sdl) {
|
|
88
86
|
return Stdlib_Array.reduce(sharedIamTypeNames, sdl, (acc, name) => acc.replace(`type ` + name + ` {`, `type ` + name + ` @aws_cognito_user_pools @aws_iam {`));
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
89
|
+
let canonicalTypeNames = [
|
|
90
|
+
"PageInfo",
|
|
91
|
+
"CommandAccepted",
|
|
92
|
+
"CommandRejected",
|
|
93
|
+
"CommandPending"
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
function stampCanonicalTypes(sdl) {
|
|
97
|
+
return sdl.split("\n").map(line => {
|
|
98
|
+
let isObjectDef = canonicalTypeNames.some(name => line.startsWith(`type ` + name + ` `));
|
|
99
|
+
let isNodeDef = line.startsWith("interface Node ") || line.startsWith("interface Node{");
|
|
100
|
+
let isUnionDef = line.startsWith("union CommandResult ") || line.startsWith("union CommandResult=");
|
|
101
|
+
if (line.includes("@canonical")) {
|
|
102
|
+
return line;
|
|
103
|
+
}
|
|
104
|
+
if (isObjectDef || isNodeDef) {
|
|
105
|
+
let braceIdx = Stdlib_String.indexOfOpt(line, "{");
|
|
106
|
+
if (braceIdx === undefined) {
|
|
107
|
+
return line;
|
|
108
|
+
}
|
|
109
|
+
let head = line.slice(0, braceIdx).trimEnd();
|
|
110
|
+
let tail = line.slice(braceIdx);
|
|
111
|
+
return head + ` @canonical ` + tail;
|
|
112
|
+
}
|
|
113
|
+
if (!isUnionDef) {
|
|
114
|
+
return line;
|
|
115
|
+
}
|
|
116
|
+
let eqIdx = Stdlib_String.indexOfOpt(line, "=");
|
|
117
|
+
if (eqIdx === undefined) {
|
|
118
|
+
return line;
|
|
119
|
+
}
|
|
120
|
+
let head$1 = line.slice(0, eqIdx).trimEnd();
|
|
121
|
+
let tail$1 = line.slice(eqIdx);
|
|
122
|
+
return head$1 + ` @canonical ` + tail$1;
|
|
123
|
+
}).join("\n");
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
export {
|
|
@@ -121,6 +129,7 @@ export {
|
|
|
121
129
|
injectAwsAuthAll,
|
|
122
130
|
sharedIamTypeNames,
|
|
123
131
|
stampSharedIamTypes,
|
|
124
|
-
|
|
132
|
+
canonicalTypeNames,
|
|
133
|
+
stampCanonicalTypes,
|
|
125
134
|
}
|
|
126
135
|
/* GraphQL_Stitcher-ReventlessCore Not a pure module */
|
|
@@ -28,8 +28,7 @@ function Make(HooksConfig) {
|
|
|
28
28
|
hooks: HooksConfig.hooks
|
|
29
29
|
})({})({
|
|
30
30
|
makeApiResource: AppSync_Adapter$ReventlessAws.makeApiResource,
|
|
31
|
-
generateFragment: AppSync_Adapter$ReventlessAws.generateFragment
|
|
32
|
-
updateSchema: AppSync_Adapter$ReventlessAws.updateSchema
|
|
31
|
+
generateFragment: AppSync_Adapter$ReventlessAws.generateFragment
|
|
33
32
|
})({
|
|
34
33
|
make: RuntimeEnvironment_Lambda$ReventlessAws.make,
|
|
35
34
|
groupBySource: RuntimeEnvironment_Lambda$ReventlessAws.groupBySource,
|
|
@@ -652,3 +652,187 @@ describe("Split mode — empty base fragment", () => {
|
|
|
652
652
|
expect(sdl)->toContain("Platform_Plugin")
|
|
653
653
|
})
|
|
654
654
|
})
|
|
655
|
+
|
|
656
|
+
// ── Merged mode — canonical source documents ─────────────────────────────────
|
|
657
|
+
//
|
|
658
|
+
// On the merge path (merged-api-push-free-composition, Phase 3) the platform-
|
|
659
|
+
// owned source APIs carry their schemas DECLARATIVELY: the admin base (or, in
|
|
660
|
+
// split mode, the bare relay base) stitched with an empty plugin list and
|
|
661
|
+
// stamped @canonical. These tests pin the assembled documents' load-bearing
|
|
662
|
+
// properties; Platform.res composes exactly this pipeline.
|
|
663
|
+
|
|
664
|
+
describe("Merged mode — canonical source documents", () => {
|
|
665
|
+
let assembleCanonicalSourceSdl = (~baseFragment) =>
|
|
666
|
+
AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment=baseFragment)
|
|
667
|
+
->AppSync_SdlDecorate.stampCanonicalTypes
|
|
668
|
+
|
|
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({
|
|
672
|
+
types: [],
|
|
673
|
+
mutations: [],
|
|
674
|
+
queries: [" Platform_ping: String"],
|
|
675
|
+
subscriptions: [],
|
|
676
|
+
subscriptionSources: [],
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
let adminSourceSdl = assembleCanonicalSourceSdl(
|
|
680
|
+
~baseFragment=AppSync_Adapter.injectAwsAuthAll(
|
|
681
|
+
ReventlessCore.AdminApi.baseFragment(~cloner=false),
|
|
682
|
+
~group="Admin",
|
|
683
|
+
),
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
let domainBaseSourceSdl = assembleCanonicalSourceSdl(~baseFragment=domainBaseFragment)
|
|
687
|
+
|
|
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")
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
testSync("admin source document stamps @canonical on the shared traversal types", () => {
|
|
697
|
+
expect(adminSourceSdl)->toContain("type PageInfo @aws_cognito_user_pools @aws_iam @canonical {")
|
|
698
|
+
expect(adminSourceSdl)->toContain("interface Node @canonical {")
|
|
699
|
+
expect(adminSourceSdl)->toContain("union CommandResult @canonical =")
|
|
700
|
+
expect(adminSourceSdl)->toContain(
|
|
701
|
+
"type CommandAccepted @aws_cognito_user_pools @aws_iam @canonical {",
|
|
702
|
+
)
|
|
703
|
+
})
|
|
704
|
+
|
|
705
|
+
testSync("admin source document keeps admin fields and shared-type IAM stamps", () => {
|
|
706
|
+
expect(adminSourceSdl)->toContain("Platform_Plugin")
|
|
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")
|
|
711
|
+
})
|
|
712
|
+
|
|
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")
|
|
717
|
+
// No admin or plugin fields on the split-mode Domain source.
|
|
718
|
+
expect(domainBaseSourceSdl)->not_->toContain("Platform_Plugin")
|
|
719
|
+
// No mutations → no CommandResult family on this source.
|
|
720
|
+
expect(domainBaseSourceSdl)->not_->toContain("union CommandResult")
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
// Phase 4: the plugin-stack subgraph document pushed to the plugin's own
|
|
724
|
+
// source API.
|
|
725
|
+
let pluginFragment = ReventlessCore.GraphQL_Stitcher.encode({
|
|
726
|
+
types: [
|
|
727
|
+
"type MyPlugin_Item implements Node {\n id: ID!\n name: String!\n}",
|
|
728
|
+
"type CommandAccepted {\n msgId: ID!\n eventCount: Int!\n}",
|
|
729
|
+
],
|
|
730
|
+
mutations: ["MyPlugin_Item_Create(name: String!): CommandResult"],
|
|
731
|
+
queries: ["MyPlugin_Item(id: ID!): MyPlugin_Item"],
|
|
732
|
+
subscriptions: ["onMyPlugin_Item_Create(id: ID): CommandResult"],
|
|
733
|
+
subscriptionSources: [
|
|
734
|
+
{field: "onMyPlugin_Item_Create", mutations: ["MyPlugin_Item_Create"]},
|
|
735
|
+
],
|
|
736
|
+
})
|
|
737
|
+
|
|
738
|
+
testSync("plugin subgraph document is standalone: relay types included, node omitted", () => {
|
|
739
|
+
let sdl = AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment=pluginFragment)
|
|
740
|
+
expect(sdl)->toContain("interface Node")
|
|
741
|
+
expect(sdl)->toContain("type PageInfo")
|
|
742
|
+
expect(sdl)->toContain("MyPlugin_Item_Create")
|
|
743
|
+
expect(sdl)->not_->toContain("node(id: ID!): Node")
|
|
744
|
+
})
|
|
745
|
+
|
|
746
|
+
testSync("plugin subgraph document carries @aws_subscribe + shared-type IAM stamps, no @canonical", () => {
|
|
747
|
+
let sdl = AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment=pluginFragment)
|
|
748
|
+
expect(sdl)->toContain(`@aws_subscribe(mutations: ["MyPlugin_Item_Create"])`)
|
|
749
|
+
expect(sdl)->toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam {")
|
|
750
|
+
// Plugin subgraphs stay unstamped — the admin source's canonical defs win.
|
|
751
|
+
expect(sdl)->not_->toContain("@canonical")
|
|
752
|
+
})
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
// ── waitForMergeSuccess — association merge-status poll ─────────────────────
|
|
756
|
+
|
|
757
|
+
describe("AppSync_Adapter.waitForMergeSuccess", () => {
|
|
758
|
+
// Fake AppSync client: `send` yields the canned responses in order and
|
|
759
|
+
// repeats the last one when the poll outruns the script.
|
|
760
|
+
let makeFakeClient = (
|
|
761
|
+
responses: array<AppSync_Adapter.getSourceApiAssociationResult>,
|
|
762
|
+
): AppSync_Adapter.appSyncClient => {
|
|
763
|
+
let i = ref(0)
|
|
764
|
+
let fake = {
|
|
765
|
+
"send": _cmd => {
|
|
766
|
+
let idx = Math.Int.min(i.contents, responses->Array.length - 1)
|
|
767
|
+
i := i.contents + 1
|
|
768
|
+
Promise.resolve(responses->Array.getUnsafe(idx))
|
|
769
|
+
},
|
|
770
|
+
}
|
|
771
|
+
fake->Obj.magic
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
let response = (~status, ~detail=?): AppSync_Adapter.getSourceApiAssociationResult => {
|
|
775
|
+
sourceApiAssociation: Some({
|
|
776
|
+
sourceApiAssociationStatus: Some(status),
|
|
777
|
+
sourceApiAssociationStatusDetail: detail,
|
|
778
|
+
}),
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
test("resolves once the association reports MERGE_SUCCESS", async () => {
|
|
782
|
+
let client = makeFakeClient([
|
|
783
|
+
response(~status="MERGE_SCHEDULED"),
|
|
784
|
+
response(~status="MERGE_IN_PROGRESS"),
|
|
785
|
+
response(~status="MERGE_SUCCESS"),
|
|
786
|
+
])
|
|
787
|
+
await AppSync_Adapter.waitForMergeSuccess(
|
|
788
|
+
client,
|
|
789
|
+
~associationId="assoc-1",
|
|
790
|
+
~mergedApiIdentifier="merged-1",
|
|
791
|
+
~delayMs=1,
|
|
792
|
+
)
|
|
793
|
+
})
|
|
794
|
+
|
|
795
|
+
test("throws with the AWS status detail on MERGE_FAILED", async () => {
|
|
796
|
+
let client = makeFakeClient([
|
|
797
|
+
response(
|
|
798
|
+
~status="MERGE_FAILED",
|
|
799
|
+
~detail="Unable to resolve conflict on object with name SharedThing.x",
|
|
800
|
+
),
|
|
801
|
+
])
|
|
802
|
+
let message = ref("")
|
|
803
|
+
try {
|
|
804
|
+
await AppSync_Adapter.waitForMergeSuccess(
|
|
805
|
+
client,
|
|
806
|
+
~associationId="assoc-1",
|
|
807
|
+
~mergedApiIdentifier="merged-1",
|
|
808
|
+
~delayMs=1,
|
|
809
|
+
)
|
|
810
|
+
} catch {
|
|
811
|
+
| exn =>
|
|
812
|
+
message :=
|
|
813
|
+
exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("(no message)")
|
|
814
|
+
}
|
|
815
|
+
expect(message.contents)->toContain("MERGE_FAILED")
|
|
816
|
+
expect(message.contents)->toContain("SharedThing.x")
|
|
817
|
+
})
|
|
818
|
+
|
|
819
|
+
test("times out after maxAttempts on a status that never settles", async () => {
|
|
820
|
+
let client = makeFakeClient([response(~status="MERGE_IN_PROGRESS")])
|
|
821
|
+
let message = ref("")
|
|
822
|
+
try {
|
|
823
|
+
await AppSync_Adapter.waitForMergeSuccess(
|
|
824
|
+
client,
|
|
825
|
+
~associationId="assoc-1",
|
|
826
|
+
~mergedApiIdentifier="merged-1",
|
|
827
|
+
~maxAttempts=2,
|
|
828
|
+
~delayMs=1,
|
|
829
|
+
)
|
|
830
|
+
} catch {
|
|
831
|
+
| exn =>
|
|
832
|
+
message :=
|
|
833
|
+
exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("(no message)")
|
|
834
|
+
}
|
|
835
|
+
expect(message.contents)->toContain("timed out")
|
|
836
|
+
expect(message.contents)->toContain("MERGE_IN_PROGRESS")
|
|
837
|
+
})
|
|
838
|
+
})
|
|
@@ -1,11 +1,14 @@
|
|
|
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 Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
4
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
6
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
6
8
|
import * as AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/AdminApi.res.mjs";
|
|
7
9
|
import * as AppSync_Adapter$ReventlessAws from "../src/components/Api/AppSync_Adapter.res.mjs";
|
|
8
10
|
import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
11
|
+
import * as AppSync_SdlDecorate$ReventlessAws from "../src/components/Api/AppSync_SdlDecorate.res.mjs";
|
|
9
12
|
import * as PluginBaseFragment$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/api/PluginBaseFragment.res.mjs";
|
|
10
13
|
|
|
11
14
|
globalThis.describe("AppSync_Adapter.sha256Hex", () => {
|
|
@@ -603,6 +606,139 @@ globalThis.describe("Split mode — empty base fragment", () => {
|
|
|
603
606
|
});
|
|
604
607
|
});
|
|
605
608
|
|
|
609
|
+
globalThis.describe("Merged mode — canonical source documents", () => {
|
|
610
|
+
let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
|
|
611
|
+
types: [],
|
|
612
|
+
mutations: [],
|
|
613
|
+
queries: [" Platform_ping: String"],
|
|
614
|
+
subscriptions: [],
|
|
615
|
+
subscriptionSources: []
|
|
616
|
+
});
|
|
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");
|
|
624
|
+
});
|
|
625
|
+
globalThis.test("admin source document stamps @canonical on the shared traversal types", () => {
|
|
626
|
+
globalThis.expect(adminSourceSdl).toContain("type PageInfo @aws_cognito_user_pools @aws_iam @canonical {");
|
|
627
|
+
globalThis.expect(adminSourceSdl).toContain("interface Node @canonical {");
|
|
628
|
+
globalThis.expect(adminSourceSdl).toContain("union CommandResult @canonical =");
|
|
629
|
+
globalThis.expect(adminSourceSdl).toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam @canonical {");
|
|
630
|
+
});
|
|
631
|
+
globalThis.test("admin source document keeps admin fields and shared-type IAM stamps", () => {
|
|
632
|
+
globalThis.expect(adminSourceSdl).toContain("Platform_Plugin");
|
|
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");
|
|
641
|
+
});
|
|
642
|
+
let pluginFragment = GraphQL_Stitcher$ReventlessCore.encode({
|
|
643
|
+
types: [
|
|
644
|
+
"type MyPlugin_Item implements Node {\n id: ID!\n name: String!\n}",
|
|
645
|
+
"type CommandAccepted {\n msgId: ID!\n eventCount: Int!\n}"
|
|
646
|
+
],
|
|
647
|
+
mutations: ["MyPlugin_Item_Create(name: String!): CommandResult"],
|
|
648
|
+
queries: ["MyPlugin_Item(id: ID!): MyPlugin_Item"],
|
|
649
|
+
subscriptions: ["onMyPlugin_Item_Create(id: ID): CommandResult"],
|
|
650
|
+
subscriptionSources: [{
|
|
651
|
+
field: "onMyPlugin_Item_Create",
|
|
652
|
+
mutations: ["MyPlugin_Item_Create"]
|
|
653
|
+
}]
|
|
654
|
+
});
|
|
655
|
+
globalThis.test("plugin subgraph document is standalone: relay types included, node omitted", () => {
|
|
656
|
+
let sdl = AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(pluginFragment);
|
|
657
|
+
globalThis.expect(sdl).toContain("interface Node");
|
|
658
|
+
globalThis.expect(sdl).toContain("type PageInfo");
|
|
659
|
+
globalThis.expect(sdl).toContain("MyPlugin_Item_Create");
|
|
660
|
+
globalThis.expect(sdl).not.toContain("node(id: ID!): Node");
|
|
661
|
+
});
|
|
662
|
+
globalThis.test("plugin subgraph document carries @aws_subscribe + shared-type IAM stamps, no @canonical", () => {
|
|
663
|
+
let sdl = AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(pluginFragment);
|
|
664
|
+
globalThis.expect(sdl).toContain(`@aws_subscribe(mutations: ["MyPlugin_Item_Create"])`);
|
|
665
|
+
globalThis.expect(sdl).toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam {");
|
|
666
|
+
globalThis.expect(sdl).not.toContain("@canonical");
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
globalThis.describe("AppSync_Adapter.waitForMergeSuccess", () => {
|
|
671
|
+
let makeFakeClient = responses => {
|
|
672
|
+
let i = {
|
|
673
|
+
contents: 0
|
|
674
|
+
};
|
|
675
|
+
return {
|
|
676
|
+
send: _cmd => {
|
|
677
|
+
let idx = Math.min(i.contents, responses.length - 1 | 0);
|
|
678
|
+
i.contents = i.contents + 1 | 0;
|
|
679
|
+
return Promise.resolve(responses[idx]);
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
};
|
|
683
|
+
globalThis.test("resolves once the association reports MERGE_SUCCESS", async () => {
|
|
684
|
+
let client = makeFakeClient([
|
|
685
|
+
{
|
|
686
|
+
sourceApiAssociation: {
|
|
687
|
+
sourceApiAssociationStatus: "MERGE_SCHEDULED",
|
|
688
|
+
sourceApiAssociationStatusDetail: undefined
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
sourceApiAssociation: {
|
|
693
|
+
sourceApiAssociationStatus: "MERGE_IN_PROGRESS",
|
|
694
|
+
sourceApiAssociationStatusDetail: undefined
|
|
695
|
+
}
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
sourceApiAssociation: {
|
|
699
|
+
sourceApiAssociationStatus: "MERGE_SUCCESS",
|
|
700
|
+
sourceApiAssociationStatusDetail: undefined
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
]);
|
|
704
|
+
return await AppSync_Adapter$ReventlessAws.waitForMergeSuccess(client, "assoc-1", "merged-1", undefined, undefined, 1);
|
|
705
|
+
});
|
|
706
|
+
globalThis.test("throws with the AWS status detail on MERGE_FAILED", async () => {
|
|
707
|
+
let client = makeFakeClient([{
|
|
708
|
+
sourceApiAssociation: {
|
|
709
|
+
sourceApiAssociationStatus: "MERGE_FAILED",
|
|
710
|
+
sourceApiAssociationStatusDetail: "Unable to resolve conflict on object with name SharedThing.x"
|
|
711
|
+
}
|
|
712
|
+
}]);
|
|
713
|
+
let message = "";
|
|
714
|
+
try {
|
|
715
|
+
await AppSync_Adapter$ReventlessAws.waitForMergeSuccess(client, "assoc-1", "merged-1", undefined, undefined, 1);
|
|
716
|
+
} catch (raw_exn) {
|
|
717
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
718
|
+
message = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "(no message)");
|
|
719
|
+
}
|
|
720
|
+
globalThis.expect(message).toContain("MERGE_FAILED");
|
|
721
|
+
globalThis.expect(message).toContain("SharedThing.x");
|
|
722
|
+
});
|
|
723
|
+
globalThis.test("times out after maxAttempts on a status that never settles", async () => {
|
|
724
|
+
let client = makeFakeClient([{
|
|
725
|
+
sourceApiAssociation: {
|
|
726
|
+
sourceApiAssociationStatus: "MERGE_IN_PROGRESS",
|
|
727
|
+
sourceApiAssociationStatusDetail: undefined
|
|
728
|
+
}
|
|
729
|
+
}]);
|
|
730
|
+
let message = "";
|
|
731
|
+
try {
|
|
732
|
+
await AppSync_Adapter$ReventlessAws.waitForMergeSuccess(client, "assoc-1", "merged-1", 2, undefined, 1);
|
|
733
|
+
} catch (raw_exn) {
|
|
734
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
735
|
+
message = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "(no message)");
|
|
736
|
+
}
|
|
737
|
+
globalThis.expect(message).toContain("timed out");
|
|
738
|
+
globalThis.expect(message).toContain("MERGE_IN_PROGRESS");
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
606
742
|
export {
|
|
607
743
|
decodeFragment,
|
|
608
744
|
}
|
|
@@ -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,82 +118,37 @@ 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
121
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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")
|
|
122
|
+
describe("AppSync_SdlDecorate.stampSharedIamTypes", () => {
|
|
123
|
+
testSync("stamps the CommandResult members", () => {
|
|
124
|
+
let sdl = AppSync_SdlDecorate.stampSharedIamTypes("type CommandAccepted {\n id: ID!\n}")
|
|
125
|
+
expect(sdl)->toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam {")
|
|
188
126
|
})
|
|
189
127
|
})
|
|
190
128
|
|
|
191
|
-
describe("AppSync_SdlDecorate.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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}",
|
|
129
|
+
describe("AppSync_SdlDecorate.stampCanonicalTypes", () => {
|
|
130
|
+
testSync("stamps shared object types, the Node interface, and the CommandResult union", () => {
|
|
131
|
+
let sdl = AppSync_SdlDecorate.stampCanonicalTypes(
|
|
132
|
+
"interface Node {\n id: ID!\n}\n\ntype PageInfo {\n hasNextPage: Boolean!\n}\n\nunion CommandResult = CommandAccepted | CommandRejected | CommandPending\n\ntype CommandAccepted {\n msgId: ID!\n}",
|
|
199
133
|
)
|
|
200
|
-
expect(sdl)->toContain("
|
|
134
|
+
expect(sdl)->toContain("interface Node @canonical {")
|
|
135
|
+
expect(sdl)->toContain("type PageInfo @canonical {")
|
|
136
|
+
expect(sdl)->toContain("union CommandResult @canonical = CommandAccepted")
|
|
137
|
+
expect(sdl)->toContain("type CommandAccepted @canonical {")
|
|
201
138
|
})
|
|
202
139
|
|
|
203
|
-
testSync("
|
|
204
|
-
let sdl =
|
|
205
|
-
|
|
140
|
+
testSync("composes after stampSharedIamTypes and leaves other types alone", () => {
|
|
141
|
+
let sdl =
|
|
142
|
+
AppSync_SdlDecorate.stampSharedIamTypes(
|
|
143
|
+
"type PageInfo {\n hasNextPage: Boolean!\n}\n\ntype Product {\n id: ID!\n}",
|
|
144
|
+
)->AppSync_SdlDecorate.stampCanonicalTypes
|
|
145
|
+
expect(sdl)->toContain("type PageInfo @aws_cognito_user_pools @aws_iam @canonical {")
|
|
146
|
+
expect(sdl)->toContain("type Product {")
|
|
147
|
+
expect(sdl->String.includes("type Product @canonical"))->toBe(false)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
testSync("is idempotent", () => {
|
|
151
|
+
let once = AppSync_SdlDecorate.stampCanonicalTypes("type PageInfo {\n x: Int\n}")
|
|
152
|
+
expect(AppSync_SdlDecorate.stampCanonicalTypes(once))->toBe(once)
|
|
206
153
|
})
|
|
207
154
|
})
|