@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 +14 -0
- package/package.json +5 -5
- package/src/Platform.res +15 -3
- package/src/Platform.res.mjs +24 -16
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res +16 -7
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res.mjs +5 -2
- package/tests/AppSync_SdlDecorateTest.res +32 -0
- package/tests/AppSync_SdlDecorateTest.res.mjs +26 -0
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.
|
|
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-
|
|
13
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.7",
|
|
15
14
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.15",
|
|
16
|
-
"@reventlessdev/
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
(),
|
package/src/Platform.res.mjs
CHANGED
|
@@ -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 =>
|
|
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
|
-
|
|
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$
|
|
1276
|
-
if (r$
|
|
1277
|
-
Platform_UIFragments_Lambda$ReventlessAws.make(platformApi, r$
|
|
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$
|
|
1283
|
-
if (r$
|
|
1284
|
-
Platform_ApiFragments_Lambda$ReventlessAws.make(platformApi, r$
|
|
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 =>
|
|
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
|
-
|
|
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$
|
|
2640
|
-
if (r$
|
|
2641
|
-
Platform_UIFragments_Lambda$ReventlessAws.make(platformApi, r$
|
|
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$
|
|
2647
|
-
if (r$
|
|
2648
|
-
Platform_ApiFragments_Lambda$ReventlessAws.make(platformApi, r$
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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}`],
|