@reventlessdev/reventless-aws 3.0.0-alpha.182 → 3.0.0-alpha.183

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 CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.183 (2026-07-08)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **reventless-aws:** stop admin-base schema clobber of split-mode DomainApi ([afce85f](https://github.com/ReventlessDev/reventless-core/commit/afce85fa57474735cea3dcb1c2a151c2d8804f0e))
11
+
12
+
6
13
  # 3.0.0-alpha.182 (2026-07-07)
7
14
 
8
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.182",
3
+ "version": "3.0.0-alpha.183",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -9,16 +9,16 @@
9
9
  "sury": "11.0.0-alpha.4",
10
10
  "uuid": "^13.0.0",
11
11
  "@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.20",
12
+ "@reventlessdev/rescript-effect": "0.1.0-alpha.25",
12
13
  "@reventlessdev/rescript-jest": "1.0.0-alpha.6",
13
14
  "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.46",
14
- "@reventlessdev/rescript-effect": "0.1.0-alpha.25",
15
15
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
16
16
  "@reventlessdev/rescript-uuid": "1.1.0-alpha.14",
17
- "@reventlessdev/reventless-core": "3.0.0-alpha.144",
17
+ "@reventlessdev/reventless-core": "3.0.0-alpha.145",
18
18
  "@reventlessdev/reventless-infra": "3.0.0-alpha.90",
19
19
  "@reventlessdev/reventless-interop": "3.0.0-alpha.24",
20
- "@reventlessdev/reventless-spec": "3.0.0-alpha.68",
21
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.8"
20
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.9",
21
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.68"
22
22
  },
23
23
  "devDependencies": {
24
24
  "rescript": "^12.3.0",
package/src/Platform.res CHANGED
@@ -689,37 +689,56 @@ module MakeWithConfig = (
689
689
  // - split mode: platformApi from splitApiOutputsRef
690
690
  // - unified mode / not-yet-populated: domainApi
691
691
  preAdminResolversSchemaHook: (~adminBarrier) => {
692
- let targetApi = switch splitApiOutputsRef.contents {
693
- | Some({platformApi}) => platformApi
694
- | None => domainApi
692
+ // The admin-base SDL stitches AdminApi.baseFragment with an EMPTY plugin
693
+ // list, and startSchemaCreation REPLACES the whole schema. In split mode
694
+ // the admin schema belongs on the PlatformApi ONLY — the DomainApi carries
695
+ // plugin fields (emptyBaseFragment). If we pushed the admin-base-only SDL
696
+ // to the DomainApi it would wipe every plugin field, leaving exactly the
697
+ // admin-base set (the alpha 2026-07-08 clobber). So in split mode we push
698
+ // ONLY when the PlatformApi is known; if the ref is not yet populated we
699
+ // SKIP (never fall back to domainApi). Unified mode legitimately shares one
700
+ // API, so an unpopulated ref there means domainApi.
701
+ let targetApiOpt = switch (Config.splitApi, splitApiOutputsRef.contents) {
702
+ | (_, Some({platformApi})) => Some(platformApi)
703
+ | (false, None) => Some(domainApi)
704
+ | (true, None) => None
695
705
  }
696
- let adminBaseFragment = AppSync_Adapter.injectAwsAuthAll(
697
- ReventlessCore.AdminApi.baseFragment(~cloner=Config.cloner),
698
- ~group="Admin",
699
- )
700
- let sdl = ReventlessCore.GraphQL_Stitcher.stitch(
701
- ~baseFragment=adminBaseFragment,
702
- ~pluginFragments=[],
703
- )->AppSync_Adapter.stampSharedIamTypes
704
- (targetApi, adminBarrier)
705
- ->Pulumi.Output.all2
706
- ->Pulumi.Output.flatMap(((api, _)) =>
707
- api.id->Pulumi.Output.flatMap(apiId => {
708
- log.info(~comp="preAdminResolversSchemaHook", `Pushing admin schema to ${apiId}`)
709
- let client = AppSync_Adapter.getClient()
710
- client
711
- ->AppSync_Adapter.startSchemaCreationRetrying({apiId, definition: sdl})
712
- ->Promise.then(async _ => {
713
- log.info(
714
- ~comp="preAdminResolversSchemaHook",
715
- "startSchemaCreation called, waiting for ACTIVE",
716
- )
717
- await AppSync_Adapter.waitForSchemaActive(client, apiId)
718
- log.info(~comp="preAdminResolversSchemaHook", "schema is ACTIVE")
706
+ switch targetApiOpt {
707
+ | None =>
708
+ log.error(
709
+ ~comp="preAdminResolversSchemaHook",
710
+ "split mode but the PlatformApi is not available at hook time — SKIPPING the admin schema push to avoid clobbering the DomainApi (pushing admin-base here would wipe every plugin field)",
711
+ )
712
+ adminBarrier
713
+ | Some(targetApi) =>
714
+ let adminBaseFragment = AppSync_Adapter.injectAwsAuthAll(
715
+ ReventlessCore.AdminApi.baseFragment(~cloner=Config.cloner),
716
+ ~group="Admin",
717
+ )
718
+ let sdl = ReventlessCore.GraphQL_Stitcher.stitch(
719
+ ~baseFragment=adminBaseFragment,
720
+ ~pluginFragments=[],
721
+ )->AppSync_Adapter.stampSharedIamTypes
722
+ (targetApi, adminBarrier)
723
+ ->Pulumi.Output.all2
724
+ ->Pulumi.Output.flatMap(((api, _)) =>
725
+ api.id->Pulumi.Output.flatMap(apiId => {
726
+ log.info(~comp="preAdminResolversSchemaHook", `Pushing admin schema to ${apiId}`)
727
+ let client = AppSync_Adapter.getClient()
728
+ client
729
+ ->AppSync_Adapter.startSchemaCreationRetrying({apiId, definition: sdl})
730
+ ->Promise.then(async _ => {
731
+ log.info(
732
+ ~comp="preAdminResolversSchemaHook",
733
+ "startSchemaCreation called, waiting for ACTIVE",
734
+ )
735
+ await AppSync_Adapter.waitForSchemaActive(client, apiId)
736
+ log.info(~comp="preAdminResolversSchemaHook", "schema is ACTIVE")
737
+ })
738
+ ->Pulumi.Output.fromPromise
719
739
  })
720
- ->Pulumi.Output.fromPromise
721
- })
722
- )
740
+ )
741
+ }
723
742
  },
724
743
 
725
744
  // Accumulate fragments across independent plugin deployments: each plugin
@@ -957,26 +976,44 @@ module MakeWithConfig = (
957
976
  ~sdl=s,
958
977
  ~typeName="Subscription",
959
978
  )
979
+ // Identity-aware drift check (not a bare count): the live schema is
980
+ // "intact" only when it is a SUPERSET of every root field we would
981
+ // push. Comparing name SETS heals equal-cardinality drift and field
982
+ // *swaps* — an admin-base clobber that leaves the DomainApi with the
983
+ // SAME number of root fields but the WRONG ones (admin-base instead
984
+ // of plugin fields) has a matching count yet is missing every
985
+ // expected plugin field, so a count test would wrongly skip.
986
+ let missingFields = ReventlessCore.GraphQL_Stitcher.missingRootFields(
987
+ ~expectedSdl=sdl,
988
+ ~liveSdl,
989
+ )
960
990
  let skipPush = switch storedHash {
961
991
  | Some(prev) if prev == currentHash =>
962
- let expected = countRoots(sdl)
963
- let live = countRoots(liveSdl)
964
992
  if liveSdl == "" {
965
993
  log.info(
966
994
  ~comp="preResolversSchemaHook",
967
995
  `hash matches but live schema could not be introspected — forcing repair push (check appsync:GetIntrospectionSchema permission)`,
968
996
  )
969
997
  false
970
- } else if live < expected {
998
+ } else if missingFields->Array.length > 0 {
971
999
  log.info(
972
1000
  ~comp="preResolversSchemaHook",
973
- `hash matches but live schema drifted (${live->Int.toString} root field(s) live vs ${expected->Int.toString} expected) — forcing repair push`,
1001
+ `hash matches but live schema is missing ${missingFields
1002
+ ->Array.length
1003
+ ->Int.toString} expected root field(s) (e.g. ${missingFields
1004
+ ->Array.slice(~start=0, ~end=5)
1005
+ ->Array.join(", ")}) — forcing repair push`,
974
1006
  )
975
1007
  false
976
1008
  } else {
977
1009
  log.info(
978
1010
  ~comp="preResolversSchemaHook",
979
- `SDL unchanged (hash ${currentHash->String.slice(~start=0, ~end=12)}…) and live schema intact (${live->Int.toString} root fields); skipping push`,
1011
+ `SDL unchanged (hash ${currentHash->String.slice(
1012
+ ~start=0,
1013
+ ~end=12,
1014
+ )}…) and live schema is a superset of the expected root fields (${countRoots(
1015
+ liveSdl,
1016
+ )->Int.toString} live); skipping push`,
980
1017
  )
981
1018
  true
982
1019
  }
@@ -645,23 +645,12 @@ function MakeWithConfig(Config) {
645
645
  let client = AppSync_Adapter$ReventlessAws.getClient();
646
646
  let liveSdl = await AppSync_Adapter$ReventlessAws.getIntrospectionSdl(client, apiId);
647
647
  let countRoots = s => (GraphQL_Stitcher$ReventlessCore.countRootTypeFields(s, "Mutation") + GraphQL_Stitcher$ReventlessCore.countRootTypeFields(s, "Query") | 0) + GraphQL_Stitcher$ReventlessCore.countRootTypeFields(s, "Subscription") | 0;
648
- let skipPush;
649
- if (storedHash !== undefined && storedHash === currentHash) {
650
- let expected = countRoots(sdl);
651
- let live = countRoots(liveSdl);
652
- if (liveSdl === "") {
653
- log.info("preResolversSchemaHook", undefined, `hash matches but live schema could not be introspected — forcing repair push (check appsync:GetIntrospectionSchema permission)`);
654
- skipPush = false;
655
- } else if (live < expected) {
656
- log.info("preResolversSchemaHook", undefined, `hash matches but live schema drifted (` + live.toString() + ` root field(s) live vs ` + expected.toString() + ` expected) — forcing repair push`);
657
- skipPush = false;
658
- } else {
659
- log.info("preResolversSchemaHook", undefined, `SDL unchanged (hash ` + currentHash.slice(0, 12) + `…) and live schema intact (` + live.toString() + ` root fields); skipping push`);
660
- skipPush = true;
661
- }
662
- } else {
663
- skipPush = false;
664
- }
648
+ let missingFields = GraphQL_Stitcher$ReventlessCore.missingRootFields(sdl, liveSdl);
649
+ let skipPush = storedHash !== undefined && storedHash === currentHash ? (
650
+ liveSdl === "" ? (log.info("preResolversSchemaHook", undefined, `hash matches but live schema could not be introspected — forcing repair push (check appsync:GetIntrospectionSchema permission)`), false) : (
651
+ missingFields.length !== 0 ? (log.info("preResolversSchemaHook", undefined, `hash matches but live schema is missing ` + missingFields.length.toString() + ` expected root field(s) (e.g. ` + missingFields.slice(0, 5).join(", ") + `) — forcing repair push`), false) : (log.info("preResolversSchemaHook", undefined, `SDL unchanged (hash ` + currentHash.slice(0, 12) + `…) and live schema is a superset of the expected root fields (` + countRoots(liveSdl).toString() + ` live); skipping push`), true)
652
+ )
653
+ ) : false;
665
654
  if (!skipPush) {
666
655
  if (GraphQL_Stitcher$ReventlessCore.isCatastrophicSchemaShrink(liveSdl, sdl, deploySchemaShrinkThreshold)) {
667
656
  return log.error("preResolversSchemaHook", undefined, `ABORTED schema push for ` + apiId + `: stitched SDL (` + countRoots(sdl).toString() + ` root field(s)) would catastrophically shrink the live schema (` + countRoots(liveSdl).toString() + ` root field(s), threshold ` + deploySchemaShrinkThreshold.toString() + `) — refusing to clobber resolvers (likely a stale concurrent-deploy scan)`);
@@ -692,24 +681,30 @@ function MakeWithConfig(Config) {
692
681
  };
693
682
  let hooks_preAdminResolversSchemaHook = adminBarrier => {
694
683
  let match = splitApiOutputsRef.contents;
695
- let targetApi = match !== undefined ? match.platformApi : domainApi;
696
- let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin", undefined);
697
- let sdl = AppSync_Adapter$ReventlessAws.stampSharedIamTypes(GraphQL_Stitcher$ReventlessCore.stitch(adminBaseFragment, []));
698
- return Output$Pulumi.flatMap(Pulumi.all([
699
- targetApi,
700
- adminBarrier
701
- ]), param => Output$Pulumi.flatMap(param[0].id, apiId => {
702
- log.info("preAdminResolversSchemaHook", undefined, `Pushing admin schema to ` + apiId);
703
- let client = AppSync_Adapter$ReventlessAws.getClient();
704
- return AppSync_Adapter$ReventlessAws.startSchemaCreationRetrying(client, {
705
- apiId: apiId,
706
- definition: sdl
707
- }).then(async () => {
708
- log.info("preAdminResolversSchemaHook", undefined, "startSchemaCreation called, waiting for ACTIVE");
709
- await AppSync_Adapter$ReventlessAws.waitForSchemaActive(client, apiId, undefined, undefined);
710
- return log.info("preAdminResolversSchemaHook", undefined, "schema is ACTIVE");
711
- });
712
- }));
684
+ let targetApiOpt = match !== undefined ? match.platformApi : (
685
+ Config.splitApi ? undefined : domainApi
686
+ );
687
+ if (targetApiOpt !== undefined) {
688
+ let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin", undefined);
689
+ let sdl = AppSync_Adapter$ReventlessAws.stampSharedIamTypes(GraphQL_Stitcher$ReventlessCore.stitch(adminBaseFragment, []));
690
+ return Output$Pulumi.flatMap(Pulumi.all([
691
+ targetApiOpt,
692
+ adminBarrier
693
+ ]), param => Output$Pulumi.flatMap(param[0].id, apiId => {
694
+ log.info("preAdminResolversSchemaHook", undefined, `Pushing admin schema to ` + apiId);
695
+ let client = AppSync_Adapter$ReventlessAws.getClient();
696
+ return AppSync_Adapter$ReventlessAws.startSchemaCreationRetrying(client, {
697
+ apiId: apiId,
698
+ definition: sdl
699
+ }).then(async () => {
700
+ log.info("preAdminResolversSchemaHook", undefined, "startSchemaCreation called, waiting for ACTIVE");
701
+ await AppSync_Adapter$ReventlessAws.waitForSchemaActive(client, apiId, undefined, undefined);
702
+ return log.info("preAdminResolversSchemaHook", undefined, "schema is ACTIVE");
703
+ });
704
+ }));
705
+ }
706
+ log.error("preAdminResolversSchemaHook", undefined, "split mode but the PlatformApi is not available at hook time — SKIPPING the admin schema push to avoid clobbering the DomainApi (pushing admin-base here would wipe every plugin field)");
707
+ return adminBarrier;
713
708
  };
714
709
  let hooks_inboundAppSyncResolverHook = param => InboundTranslationResolvers_AppSync$ReventlessAws.make(resolveHookedApi(), param.runtime, param.fieldNames, param.opts);
715
710
  let hooks_dcbAppSyncResolverHook = param => CommandGeneratorResolvers_AppSync$ReventlessAws.makeDcb(resolveHookedApi(), param.runtime, param.fieldNames, param.tags, param.opts);
@@ -1876,23 +1871,12 @@ function Make($star) {
1876
1871
  let client = AppSync_Adapter$ReventlessAws.getClient();
1877
1872
  let liveSdl = await AppSync_Adapter$ReventlessAws.getIntrospectionSdl(client, apiId);
1878
1873
  let countRoots = s => (GraphQL_Stitcher$ReventlessCore.countRootTypeFields(s, "Mutation") + GraphQL_Stitcher$ReventlessCore.countRootTypeFields(s, "Query") | 0) + GraphQL_Stitcher$ReventlessCore.countRootTypeFields(s, "Subscription") | 0;
1879
- let skipPush;
1880
- if (storedHash !== undefined && storedHash === currentHash) {
1881
- let expected = countRoots(sdl);
1882
- let live = countRoots(liveSdl);
1883
- if (liveSdl === "") {
1884
- log.info("preResolversSchemaHook", undefined, `hash matches but live schema could not be introspected — forcing repair push (check appsync:GetIntrospectionSchema permission)`);
1885
- skipPush = false;
1886
- } else if (live < expected) {
1887
- log.info("preResolversSchemaHook", undefined, `hash matches but live schema drifted (` + live.toString() + ` root field(s) live vs ` + expected.toString() + ` expected) — forcing repair push`);
1888
- skipPush = false;
1889
- } else {
1890
- log.info("preResolversSchemaHook", undefined, `SDL unchanged (hash ` + currentHash.slice(0, 12) + `…) and live schema intact (` + live.toString() + ` root fields); skipping push`);
1891
- skipPush = true;
1892
- }
1893
- } else {
1894
- skipPush = false;
1895
- }
1874
+ let missingFields = GraphQL_Stitcher$ReventlessCore.missingRootFields(sdl, liveSdl);
1875
+ let skipPush = storedHash !== undefined && storedHash === currentHash ? (
1876
+ liveSdl === "" ? (log.info("preResolversSchemaHook", undefined, `hash matches but live schema could not be introspected — forcing repair push (check appsync:GetIntrospectionSchema permission)`), false) : (
1877
+ missingFields.length !== 0 ? (log.info("preResolversSchemaHook", undefined, `hash matches but live schema is missing ` + missingFields.length.toString() + ` expected root field(s) (e.g. ` + missingFields.slice(0, 5).join(", ") + `) — forcing repair push`), false) : (log.info("preResolversSchemaHook", undefined, `SDL unchanged (hash ` + currentHash.slice(0, 12) + `…) and live schema is a superset of the expected root fields (` + countRoots(liveSdl).toString() + ` live); skipping push`), true)
1878
+ )
1879
+ ) : false;
1896
1880
  if (!skipPush) {
1897
1881
  if (GraphQL_Stitcher$ReventlessCore.isCatastrophicSchemaShrink(liveSdl, sdl, deploySchemaShrinkThreshold)) {
1898
1882
  return log.error("preResolversSchemaHook", undefined, `ABORTED schema push for ` + apiId + `: stitched SDL (` + countRoots(sdl).toString() + ` root field(s)) would catastrophically shrink the live schema (` + countRoots(liveSdl).toString() + ` root field(s), threshold ` + deploySchemaShrinkThreshold.toString() + `) — refusing to clobber resolvers (likely a stale concurrent-deploy scan)`);
@@ -1923,24 +1907,28 @@ function Make($star) {
1923
1907
  };
1924
1908
  let hooks_preAdminResolversSchemaHook = adminBarrier => {
1925
1909
  let match = splitApiOutputsRef.contents;
1926
- let targetApi = match !== undefined ? match.platformApi : domainApi;
1927
- let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin", undefined);
1928
- let sdl = AppSync_Adapter$ReventlessAws.stampSharedIamTypes(GraphQL_Stitcher$ReventlessCore.stitch(adminBaseFragment, []));
1929
- return Output$Pulumi.flatMap(Pulumi.all([
1930
- targetApi,
1931
- adminBarrier
1932
- ]), param => Output$Pulumi.flatMap(param[0].id, apiId => {
1933
- log.info("preAdminResolversSchemaHook", undefined, `Pushing admin schema to ` + apiId);
1934
- let client = AppSync_Adapter$ReventlessAws.getClient();
1935
- return AppSync_Adapter$ReventlessAws.startSchemaCreationRetrying(client, {
1936
- apiId: apiId,
1937
- definition: sdl
1938
- }).then(async () => {
1939
- log.info("preAdminResolversSchemaHook", undefined, "startSchemaCreation called, waiting for ACTIVE");
1940
- await AppSync_Adapter$ReventlessAws.waitForSchemaActive(client, apiId, undefined, undefined);
1941
- return log.info("preAdminResolversSchemaHook", undefined, "schema is ACTIVE");
1942
- });
1943
- }));
1910
+ let targetApiOpt = match !== undefined ? match.platformApi : undefined;
1911
+ if (targetApiOpt !== undefined) {
1912
+ let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin", undefined);
1913
+ let sdl = AppSync_Adapter$ReventlessAws.stampSharedIamTypes(GraphQL_Stitcher$ReventlessCore.stitch(adminBaseFragment, []));
1914
+ return Output$Pulumi.flatMap(Pulumi.all([
1915
+ targetApiOpt,
1916
+ adminBarrier
1917
+ ]), param => Output$Pulumi.flatMap(param[0].id, apiId => {
1918
+ log.info("preAdminResolversSchemaHook", undefined, `Pushing admin schema to ` + apiId);
1919
+ let client = AppSync_Adapter$ReventlessAws.getClient();
1920
+ return AppSync_Adapter$ReventlessAws.startSchemaCreationRetrying(client, {
1921
+ apiId: apiId,
1922
+ definition: sdl
1923
+ }).then(async () => {
1924
+ log.info("preAdminResolversSchemaHook", undefined, "startSchemaCreation called, waiting for ACTIVE");
1925
+ await AppSync_Adapter$ReventlessAws.waitForSchemaActive(client, apiId, undefined, undefined);
1926
+ return log.info("preAdminResolversSchemaHook", undefined, "schema is ACTIVE");
1927
+ });
1928
+ }));
1929
+ }
1930
+ log.error("preAdminResolversSchemaHook", undefined, "split mode but the PlatformApi is not available at hook time — SKIPPING the admin schema push to avoid clobbering the DomainApi (pushing admin-base here would wipe every plugin field)");
1931
+ return adminBarrier;
1944
1932
  };
1945
1933
  let hooks_inboundAppSyncResolverHook = param => InboundTranslationResolvers_AppSync$ReventlessAws.make(resolveHookedApi(), param.runtime, param.fieldNames, param.opts);
1946
1934
  let hooks_dcbAppSyncResolverHook = param => CommandGeneratorResolvers_AppSync$ReventlessAws.makeDcb(resolveHookedApi(), param.runtime, param.fieldNames, param.tags, param.opts);