@reventlessdev/reventless-aws 3.0.0-alpha.199 → 3.0.0-alpha.201

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,20 @@
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.201 (2026-07-13)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **api:** dual-auth arg-less IAM fields — extractLeadingName dropped the trailing colon ([ea34da2](https://github.com/ReventlessDev/reventless-core/commit/ea34da229164db63988ad035138c17ba9134ac55))
11
+
12
+
13
+ # 3.0.0-alpha.200 (2026-07-12)
14
+
15
+ ### Bug Fixes
16
+
17
+ * **admin:** fix admin-DCB-on-AWS deploy — Output-in-option corruption + orphan resolvers ([edde07d](https://github.com/ReventlessDev/reventless-core/commit/edde07d1d7a89a47a7496d46fbf83fbadffa74a9))
18
+
19
+
6
20
  # 3.0.0-alpha.199 (2026-07-12)
7
21
 
8
22
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.199",
3
+ "version": "3.0.0-alpha.201",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -8,17 +8,17 @@
8
8
  "@aws-sdk/client-cloudfront": "3.970.0",
9
9
  "sury": "11.0.0-alpha.4",
10
10
  "uuid": "^13.0.0",
11
- "@reventlessdev/rescript-jest": "1.0.0-alpha.7",
12
11
  "@reventlessdev/rescript-effect": "0.1.0-alpha.27",
13
12
  "@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.21",
14
- "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.50",
13
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.7",
15
14
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.15",
16
- "@reventlessdev/reventless-core": "3.0.0-alpha.158",
15
+ "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.50",
17
16
  "@reventlessdev/rescript-uuid": "1.1.0-alpha.15",
17
+ "@reventlessdev/reventless-core": "3.0.0-alpha.160",
18
18
  "@reventlessdev/reventless-infra": "3.0.0-alpha.98",
19
19
  "@reventlessdev/reventless-interop": "3.0.0-alpha.26",
20
20
  "@reventlessdev/reventless-spec": "3.0.0-alpha.76",
21
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.22"
21
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.24"
22
22
  },
23
23
  "devDependencies": {
24
24
  "rescript": "^12.3.0",
package/src/Platform.res CHANGED
@@ -862,6 +862,7 @@ module MakeWithConfig = (
862
862
  ~runtime=runtimeTyped,
863
863
  ~fieldNames,
864
864
  ~tags,
865
+ ~onAdminApi,
865
866
  ~opts,
866
867
  )
867
868
  },
@@ -1981,10 +1982,21 @@ module MakeWithConfig = (
1981
1982
  // table it re-folds from; the admin DCB command-topic FIFO URL it dispatches
1982
1983
  // RecordApiFragmentPush to (captured during Admin.construct); and the mode flag.
1983
1984
  ~platformApiId=platformApi->Pulumi.Output.flatMap(api => api.id),
1984
- ~apiFragmentRegistryTableName=?admin.stateViewSlicesOutputs
1985
+ // NB: must NOT be `->Option.map(r => r.name)`. `apiFragmentRegistryTableName`
1986
+ // is `option<Pulumi.Output.t<string>>` (the forbidden pattern, CLAUDE.md code
1987
+ // smells). The generic `Option.map` body runs `Primitive_option.some(r.name)`,
1988
+ // and because a Pulumi Output lifts arbitrary property access, `some` inspects
1989
+ // `.BS_PRIVATE_NESTED_SOME_NONE`, mis-reads the Output as a nested option, and
1990
+ // stores the sentinel `{BS_PRIVATE_NESTED_SOME_NONE: 0}` instead of the Output —
1991
+ // so the consumer's `tableOutput->Pulumi.Output.apply` crashes with
1992
+ // "apply is not a function". A `Some(r.name)` LITERAL compiles unboxed (bare
1993
+ // r.name), preserving the Output — the same dodge `pluginReadModelTableName` uses.
1994
+ ~apiFragmentRegistryTableName=?switch admin.stateViewSlicesOutputs
1985
1995
  ->Dict.get("ApiFragments")
1986
- ->Option.flatMap(rm => rm.queryDb.resources->Array.get(0))
1987
- ->Option.map(r => r.name),
1996
+ ->Option.flatMap(rm => rm.queryDb.resources->Array.get(0)) {
1997
+ | Some(r) => Some(r.name)
1998
+ | None => None
1999
+ },
1988
2000
  ~adminDcbCmdTopicUrl=?AutomationSliceRuntime_Builder_Single.getDcbQueueUrl(),
1989
2001
  ~splitApi=Config.splitApi,
1990
2002
  (),
@@ -867,7 +867,10 @@ function MakeWithConfig(Config) {
867
867
  return adminBarrier;
868
868
  };
869
869
  let hooks_inboundAppSyncResolverHook = param => InboundTranslationResolvers_AppSync$ReventlessAws.make(resolveHookedApi(), param.runtime, param.fieldNames, param.opts);
870
- let hooks_dcbAppSyncResolverHook = param => CommandGeneratorResolvers_AppSync$ReventlessAws.makeDcb(param.onAdminApi ? resolveAdminHookedApi() : resolveHookedApi(), param.runtime, param.fieldNames, param.tags, param.opts);
870
+ let hooks_dcbAppSyncResolverHook = param => {
871
+ let onAdminApi = param.onAdminApi;
872
+ CommandGeneratorResolvers_AppSync$ReventlessAws.makeDcb(onAdminApi ? resolveAdminHookedApi() : resolveHookedApi(), param.runtime, param.fieldNames, param.tags, onAdminApi, param.opts);
873
+ };
871
874
  let hooks_onDcbEventLogCreated = dcbEventLogUnknown => {
872
875
  if (DcbBackend$ReventlessAws.isPostgres()) {
873
876
  return;
@@ -1265,23 +1268,24 @@ function MakeWithConfig(Config) {
1265
1268
  return Pulumi.output("NOT_AVAILABLE");
1266
1269
  }
1267
1270
  });
1268
- PluginRuntime_Builder$ReventlessAws.registerConfig(pluginEpEventTopicArn, pluginReadModelTableName, pluginSchemaPersistenceTable.name, hooks_schedulerRoleUrn.contents, undefined, undefined, domainApiId, Config.cloner, Output$Pulumi.flatMap(platformApi, api => api.id), Stdlib_Option.map(Stdlib_Option.flatMap(admin.stateViewSlicesOutputs["ApiFragments"], rm => rm.queryDb.resources[0]), r => r.name), AutomationSliceRuntime_Builder_Single$ReventlessAws.getDcbQueueUrl(), Config.splitApi, undefined);
1271
+ let r$1 = Stdlib_Option.flatMap(admin.stateViewSlicesOutputs["ApiFragments"], rm => rm.queryDb.resources[0]);
1272
+ PluginRuntime_Builder$ReventlessAws.registerConfig(pluginEpEventTopicArn, pluginReadModelTableName, pluginSchemaPersistenceTable.name, hooks_schedulerRoleUrn.contents, undefined, undefined, domainApiId, Config.cloner, Output$Pulumi.flatMap(platformApi, api => api.id), r$1 !== undefined ? r$1.name : undefined, AutomationSliceRuntime_Builder_Single$ReventlessAws.getDcbQueueUrl(), Config.splitApi, undefined);
1269
1273
  if (pluginReadModelTableName !== undefined) {
1270
1274
  AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName);
1271
1275
  Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, {});
1272
1276
  }
1273
1277
  let rm = admin.stateViewSlicesOutputs["UiFragments"];
1274
1278
  if (rm !== undefined) {
1275
- let r$1 = rm.queryDb.resources[0];
1276
- if (r$1 !== undefined) {
1277
- Platform_UIFragments_Lambda$ReventlessAws.make(platformApi, r$1.name, admin.adminSchemaPushed, {});
1279
+ let r$2 = rm.queryDb.resources[0];
1280
+ if (r$2 !== undefined) {
1281
+ Platform_UIFragments_Lambda$ReventlessAws.make(platformApi, r$2.name, admin.adminSchemaPushed, {});
1278
1282
  }
1279
1283
  }
1280
1284
  let rm$1 = admin.stateViewSlicesOutputs["ApiFragments"];
1281
1285
  if (rm$1 !== undefined) {
1282
- let r$2 = rm$1.queryDb.resources[0];
1283
- if (r$2 !== undefined) {
1284
- Platform_ApiFragments_Lambda$ReventlessAws.make(platformApi, r$2.name, admin.adminSchemaPushed, {});
1286
+ let r$3 = rm$1.queryDb.resources[0];
1287
+ if (r$3 !== undefined) {
1288
+ Platform_ApiFragments_Lambda$ReventlessAws.make(platformApi, r$3.name, admin.adminSchemaPushed, {});
1285
1289
  }
1286
1290
  }
1287
1291
  if (Config.splitApi) {
@@ -2248,7 +2252,10 @@ function Make($star) {
2248
2252
  return adminBarrier;
2249
2253
  };
2250
2254
  let hooks_inboundAppSyncResolverHook = param => InboundTranslationResolvers_AppSync$ReventlessAws.make(resolveHookedApi(), param.runtime, param.fieldNames, param.opts);
2251
- let hooks_dcbAppSyncResolverHook = param => CommandGeneratorResolvers_AppSync$ReventlessAws.makeDcb(param.onAdminApi ? resolveAdminHookedApi() : resolveHookedApi(), param.runtime, param.fieldNames, param.tags, param.opts);
2255
+ let hooks_dcbAppSyncResolverHook = param => {
2256
+ let onAdminApi = param.onAdminApi;
2257
+ CommandGeneratorResolvers_AppSync$ReventlessAws.makeDcb(onAdminApi ? resolveAdminHookedApi() : resolveHookedApi(), param.runtime, param.fieldNames, param.tags, onAdminApi, param.opts);
2258
+ };
2252
2259
  let hooks_onDcbEventLogCreated = dcbEventLogUnknown => {
2253
2260
  if (DcbBackend$ReventlessAws.isPostgres()) {
2254
2261
  return;
@@ -2629,23 +2636,24 @@ function Make($star) {
2629
2636
  return Pulumi.output("NOT_AVAILABLE");
2630
2637
  }
2631
2638
  });
2632
- PluginRuntime_Builder$ReventlessAws.registerConfig(pluginEpEventTopicArn, pluginReadModelTableName, pluginSchemaPersistenceTable.name, hooks_schedulerRoleUrn.contents, undefined, undefined, domainApiId, false, Output$Pulumi.flatMap(platformApi, api => api.id), Stdlib_Option.map(Stdlib_Option.flatMap(admin.stateViewSlicesOutputs["ApiFragments"], rm => rm.queryDb.resources[0]), r => r.name), AutomationSliceRuntime_Builder_Single$ReventlessAws.getDcbQueueUrl(), true, undefined);
2639
+ let r$1 = Stdlib_Option.flatMap(admin.stateViewSlicesOutputs["ApiFragments"], rm => rm.queryDb.resources[0]);
2640
+ PluginRuntime_Builder$ReventlessAws.registerConfig(pluginEpEventTopicArn, pluginReadModelTableName, pluginSchemaPersistenceTable.name, hooks_schedulerRoleUrn.contents, undefined, undefined, domainApiId, false, Output$Pulumi.flatMap(platformApi, api => api.id), r$1 !== undefined ? r$1.name : undefined, AutomationSliceRuntime_Builder_Single$ReventlessAws.getDcbQueueUrl(), true, undefined);
2633
2641
  if (pluginReadModelTableName !== undefined) {
2634
2642
  AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName);
2635
2643
  Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, {});
2636
2644
  }
2637
2645
  let rm = admin.stateViewSlicesOutputs["UiFragments"];
2638
2646
  if (rm !== undefined) {
2639
- let r$1 = rm.queryDb.resources[0];
2640
- if (r$1 !== undefined) {
2641
- Platform_UIFragments_Lambda$ReventlessAws.make(platformApi, r$1.name, admin.adminSchemaPushed, {});
2647
+ let r$2 = rm.queryDb.resources[0];
2648
+ if (r$2 !== undefined) {
2649
+ Platform_UIFragments_Lambda$ReventlessAws.make(platformApi, r$2.name, admin.adminSchemaPushed, {});
2642
2650
  }
2643
2651
  }
2644
2652
  let rm$1 = admin.stateViewSlicesOutputs["ApiFragments"];
2645
2653
  if (rm$1 !== undefined) {
2646
- let r$2 = rm$1.queryDb.resources[0];
2647
- if (r$2 !== undefined) {
2648
- Platform_ApiFragments_Lambda$ReventlessAws.make(platformApi, r$2.name, admin.adminSchemaPushed, {});
2654
+ let r$3 = rm$1.queryDb.resources[0];
2655
+ if (r$3 !== undefined) {
2656
+ Platform_ApiFragments_Lambda$ReventlessAws.make(platformApi, r$3.name, admin.adminSchemaPushed, {});
2649
2657
  }
2650
2658
  }
2651
2659
  Pulumi$Pulumi.$$export("platformApiId", Output$Pulumi.flatMap(platformApi, api => api.id));
@@ -174,6 +174,12 @@ let makeDcb = (
174
174
  ~runtime: ReventlessCore.Runtime.environment<runtimeParts>,
175
175
  ~fieldNames: array<string>,
176
176
  ~tags: array<string>,
177
+ // Admin (onAdminApi) DCB slices expose ONLY their mutation fields (declared in the
178
+ // static AdminApi.baseFragment). The static base does NOT declare their `on<Field>`
179
+ // subscription counterparts, so creating Source-C subscription resolvers would fail
180
+ // with CreateResolver NotFound. Suppress subscriptions for admin; plugins keep them
181
+ // (their generated fragment declares the matching subscription fields).
182
+ ~onAdminApi: bool=false,
177
183
  ~opts: Pulumi.ComponentResource.options,
178
184
  ) => {
179
185
  let opts = opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
@@ -242,11 +248,14 @@ let makeDcb = (
242
248
  )
243
249
  })
244
250
 
245
- // Source C: create Subscription.onX resolver for each DCB mutation field.
246
- CommandSubscriptionResolvers_AppSync.make(
247
- ~api,
248
- ~mutationFields=fieldNames,
249
- ~dataSourceName=dataSource.name->Pulumi.Output.asInput,
250
- ~opts,
251
- )
251
+ // Source C: create Subscription.onX resolver for each DCB mutation field — skipped
252
+ // for admin slices, whose static base declares no such subscription fields.
253
+ if !onAdminApi {
254
+ CommandSubscriptionResolvers_AppSync.make(
255
+ ~api,
256
+ ~mutationFields=fieldNames,
257
+ ~dataSourceName=dataSource.name->Pulumi.Output.asInput,
258
+ ~opts,
259
+ )
260
+ }
252
261
  }
@@ -85,7 +85,8 @@ function make(name, api, fields, param, runtime, resources, opts) {
85
85
  };
86
86
  }
87
87
 
88
- function makeDcb(api, runtime, fieldNames, tags, opts) {
88
+ function makeDcb(api, runtime, fieldNames, tags, onAdminApiOpt, opts) {
89
+ let onAdminApi = onAdminApiOpt !== undefined ? onAdminApiOpt : false;
89
90
  let opts$1 = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
90
91
  let lambda = runtime.parts.lambda;
91
92
  let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy("DcbMutationDS", Pulumi.output(AWS$ReventlessAws.AppSync.principal), opts$1);
@@ -115,7 +116,9 @@ function makeDcb(api, runtime, fieldNames, tags, opts) {
115
116
  let fieldName = param[0];
116
117
  AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(Stdlib_String.capitalize(fieldName), api, dataSource.name, "Mutation", fieldName, AppSync_Resolver_Functions$PulumiAws.invokeDcbMutation(param[1]), opts$1);
117
118
  });
118
- CommandSubscriptionResolvers_AppSync$ReventlessAws.make(api, fieldNames, dataSource.name, opts$1);
119
+ if (!onAdminApi) {
120
+ return CommandSubscriptionResolvers_AppSync$ReventlessAws.make(api, fieldNames, dataSource.name, opts$1);
121
+ }
119
122
  }
120
123
 
121
124
  export {
@@ -94,6 +94,38 @@ describe("AppSync_Adapter.stitchWithAwsDirectives", () => {
94
94
  })
95
95
  })
96
96
 
97
+ describe("AppSync_SdlDecorate.injectAwsAuthAll", () => {
98
+ // Regression: an ARG-LESS field named in iamFieldNames must still get @aws_iam.
99
+ // extractLeadingName used to return `Platform_ApiFragments:` (trailing colon) for
100
+ // arg-less fields, so isIam missed it and the field stayed Cognito-only — which
101
+ // 401'd the deploy waiter's SigV4 poll of Platform_ApiFragments on real AWS.
102
+ testSync("dual-auths an arg-less query field named in iamFieldNames", () => {
103
+ let base = ReventlessCore.GraphQL_Stitcher.encode({
104
+ types: [],
105
+ mutations: [` Platform_RegisterApiFragment(input: In): CommandResult`],
106
+ queries: [` Platform_ApiFragments: [Entry!]!`, ` Other_Query: Int`],
107
+ subscriptions: [],
108
+ subscriptionSources: [],
109
+ })
110
+ let decorated = AppSync_SdlDecorate.injectAwsAuthAll(
111
+ base,
112
+ ~group="Admin",
113
+ ~iamFieldNames=["Platform_RegisterApiFragment", "Platform_ApiFragments"],
114
+ )
115
+ let parts = ReventlessCore.GraphQL_Stitcher.decode(decorated)
116
+ let queryField = name =>
117
+ parts.queries->Array.find(q => q->String.includes(name))->Option.getOrThrow
118
+ // The arg-less IAM query gets dual-auth.
119
+ expect(queryField("Platform_ApiFragments"))->toContain("@aws_iam")
120
+ // The arg-full IAM mutation still does too.
121
+ expect(parts.mutations->Array.getUnsafe(0))->toContain("@aws_iam")
122
+ // A query NOT in iamFieldNames stays Cognito-only.
123
+ let other = queryField("Other_Query")
124
+ expect(other)->toContain(`@aws_auth(cognito_groups: ["Admin"])`)
125
+ expect(other->String.includes("@aws_iam"))->toBe(false)
126
+ })
127
+ })
128
+
97
129
  describe("AppSync_SdlDecorate.planAwsPushes", () => {
98
130
  // Neutral admin base: a system-callable mutation + a shared traversal type.
99
131
  let rawAdminBase = ReventlessCore.GraphQL_Stitcher.encode({
@@ -89,6 +89,32 @@ globalThis.describe("AppSync_Adapter.stitchWithAwsDirectives", () => {
89
89
  });
90
90
  });
91
91
 
92
+ globalThis.describe("AppSync_SdlDecorate.injectAwsAuthAll", () => {
93
+ globalThis.test("dual-auths an arg-less query field named in iamFieldNames", () => {
94
+ let base = GraphQL_Stitcher$ReventlessCore.encode({
95
+ types: [],
96
+ mutations: [` Platform_RegisterApiFragment(input: In): CommandResult`],
97
+ queries: [
98
+ ` Platform_ApiFragments: [Entry!]!`,
99
+ ` Other_Query: Int`
100
+ ],
101
+ subscriptions: [],
102
+ subscriptionSources: []
103
+ });
104
+ let decorated = AppSync_SdlDecorate$ReventlessAws.injectAwsAuthAll(base, "Admin", [
105
+ "Platform_RegisterApiFragment",
106
+ "Platform_ApiFragments"
107
+ ]);
108
+ let parts = GraphQL_Stitcher$ReventlessCore.decode(decorated);
109
+ let queryField = name => Stdlib_Option.getOrThrow(parts.queries.find(q => q.includes(name)), undefined);
110
+ globalThis.expect(queryField("Platform_ApiFragments")).toContain("@aws_iam");
111
+ globalThis.expect(parts.mutations[0]).toContain("@aws_iam");
112
+ let other = queryField("Other_Query");
113
+ globalThis.expect(other).toContain(`@aws_auth(cognito_groups: ["Admin"])`);
114
+ globalThis.expect(other.includes("@aws_iam")).toBe(false);
115
+ });
116
+ });
117
+
92
118
  globalThis.describe("AppSync_SdlDecorate.planAwsPushes", () => {
93
119
  let rawAdminBase = GraphQL_Stitcher$ReventlessCore.encode({
94
120
  types: [`type CommandAccepted {\n id: ID!\n}`],