@reventlessdev/reventless-aws 3.0.0-alpha.296 → 3.0.0-alpha.297
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 +7 -0
- package/package.json +3 -3
- package/src/components/Api/AppSync_Adapter.res +4 -0
- package/src/components/Api/AppSync_Adapter.res.mjs +2 -1
- package/src/components/Api/AppSync_SdlDecorate.res +31 -0
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +19 -0
- package/tests/AppSync_SdlDecorateTest.res +61 -0
- package/tests/AppSync_SdlDecorateTest.res.mjs +40 -0
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.297 (2026-08-14)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** sweep undirectived fields at the schema assembly choke point ([f0ed736](https://github.com/ReventlessDev/reventless-core/commit/f0ed7369ec4dddfb697a3c93115b12f310a491b0))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.296 (2026-08-14)
|
|
7
14
|
|
|
8
15
|
### Features
|
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.297",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"sury": "11.0.0-alpha.4",
|
|
14
14
|
"uuid": "^13.0.0",
|
|
15
15
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.10",
|
|
16
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
17
16
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
17
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
18
18
|
"@reventlessdev/rescript-node": "2.0.0-alpha.6",
|
|
19
19
|
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.75",
|
|
20
20
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
21
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.233",
|
|
21
22
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
22
23
|
"@reventlessdev/reventless-infra": "3.0.0-alpha.140",
|
|
23
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.233",
|
|
24
24
|
"@reventlessdev/reventless-postgres": "3.0.0-alpha.97",
|
|
25
25
|
"@reventlessdev/reventless-spec": "3.0.0-alpha.113",
|
|
26
26
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.31"
|
|
@@ -460,6 +460,10 @@ let stitchStandaloneWithAwsDirectives = (
|
|
|
460
460
|
~baseFragment=fragment,
|
|
461
461
|
~pluginFragments=[],
|
|
462
462
|
)
|
|
463
|
+
// Every AWS source-API document assembles through here, so this is the one
|
|
464
|
+
// place that can guarantee no field or type reaches a deployed schema without
|
|
465
|
+
// an auth directive — whichever path injected it.
|
|
466
|
+
let fragment = AppSync_SdlDecorate.stampUndirectivedFields(fragment)
|
|
463
467
|
ReventlessCore.GraphQL_Stitcher.stitchStandalone(~fragment)
|
|
464
468
|
->AppSync_SdlDecorate.injectAwsSubscribe(~sources)
|
|
465
469
|
->stampSharedIamTypes
|
|
@@ -264,7 +264,8 @@ function injectAwsAuthAll(fragment, group, iamFieldNamesOpt) {
|
|
|
264
264
|
|
|
265
265
|
function stitchStandaloneWithAwsDirectives(fragment) {
|
|
266
266
|
let sources = GraphQL_Stitcher$ReventlessCore.collectSubscriptionSources(fragment, []);
|
|
267
|
-
|
|
267
|
+
let fragment$1 = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(fragment);
|
|
268
|
+
return AppSync_SdlDecorate$ReventlessAws.stampAllTypesCognito(AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes(AppSync_SdlDecorate$ReventlessAws.injectAwsSubscribe(GraphQL_Stitcher$ReventlessCore.stitchStandalone(fragment$1), sources)));
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
function _makeApiResourceWith(name, schema, userPoolConfig, opts) {
|
|
@@ -162,6 +162,37 @@ let stampSharedIamTypes = (sdl: string): string =>
|
|
|
162
162
|
acc->String.replace(`type ${name} {`, `type ${name} @aws_cognito_user_pools @aws_iam {`)
|
|
163
163
|
)
|
|
164
164
|
|
|
165
|
+
// Final field sweep, run at the assembly choke point on the FRAGMENT (where each
|
|
166
|
+
// element is one whole field, so "does it already carry a directive?" is a
|
|
167
|
+
// reliable question — on raw SDL text a directive placed on the following line
|
|
168
|
+
// would read as absent).
|
|
169
|
+
//
|
|
170
|
+
// `injectAwsAuth` only decorates fields it can pair with a schema entry, and
|
|
171
|
+
// several surfaces never reach it at all: the domain base document
|
|
172
|
+
// (`Platform_ping`), event-history queries, event-log `…_eventAppended`
|
|
173
|
+
// subscriptions, the upload presign/release mutations, `Platform_SetActiveRole`,
|
|
174
|
+
// and `geocode`. Those deployed with no directive — invisible under
|
|
175
|
+
// `defaultAction: ALLOW`, refused under DENY. Sweeping here rather than at each
|
|
176
|
+
// emission site means a future injected field cannot silently miss the net.
|
|
177
|
+
//
|
|
178
|
+
// Idempotent, and deliberately does not second-guess gating: it only fills in
|
|
179
|
+
// "open to any authenticated Cognito caller", which is what an undirectived
|
|
180
|
+
// field already means today.
|
|
181
|
+
let stampUndirectivedFields = (
|
|
182
|
+
fragment: Reventless.Plugin.apiSchemaFragment,
|
|
183
|
+
): Reventless.Plugin.apiSchemaFragment => {
|
|
184
|
+
let parts = ReventlessCore.GraphQL_Stitcher.decode(fragment)
|
|
185
|
+
let stamp = (field: string): string =>
|
|
186
|
+
field->String.includes("@aws_") ? field : `${field}\n ${cognitoOpenDirective}`
|
|
187
|
+
|
|
188
|
+
ReventlessCore.GraphQL_Stitcher.encode({
|
|
189
|
+
...parts,
|
|
190
|
+
mutations: parts.mutations->Array.map(stamp),
|
|
191
|
+
queries: parts.queries->Array.map(stamp),
|
|
192
|
+
subscriptions: parts.subscriptions->Array.map(stamp),
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
165
196
|
// Stamp every object `type` declaration that does not already carry an auth
|
|
166
197
|
// directive with the group-less Cognito arm. Run on the ASSEMBLED SDL, AFTER
|
|
167
198
|
// stampSharedIamTypes so the shared traversal types keep their `@aws_iam` arm
|
|
@@ -88,6 +88,24 @@ function stampSharedIamTypes(sdl) {
|
|
|
88
88
|
return Stdlib_Array.reduce(sharedIamTypeNames, sdl, (acc, name) => acc.replace(`type ` + name + ` {`, `type ` + name + ` @aws_cognito_user_pools @aws_iam {`));
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function stampUndirectivedFields(fragment) {
|
|
92
|
+
let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
|
|
93
|
+
let stamp = field => {
|
|
94
|
+
if (field.includes("@aws_")) {
|
|
95
|
+
return field;
|
|
96
|
+
} else {
|
|
97
|
+
return field + `\n ` + cognitoOpenDirective;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
return GraphQL_Stitcher$ReventlessCore.encode({
|
|
101
|
+
types: parts.types,
|
|
102
|
+
mutations: parts.mutations.map(stamp),
|
|
103
|
+
queries: parts.queries.map(stamp),
|
|
104
|
+
subscriptions: parts.subscriptions.map(stamp),
|
|
105
|
+
subscriptionSources: parts.subscriptionSources
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
91
109
|
function stampAllTypesCognito(sdl) {
|
|
92
110
|
return sdl.split("\n").map(line => {
|
|
93
111
|
if (!(line.startsWith("type ") && !line.includes("@aws_"))) {
|
|
@@ -147,6 +165,7 @@ export {
|
|
|
147
165
|
injectAwsAuthAll,
|
|
148
166
|
sharedIamTypeNames,
|
|
149
167
|
stampSharedIamTypes,
|
|
168
|
+
stampUndirectivedFields,
|
|
150
169
|
stampAllTypesCognito,
|
|
151
170
|
canonicalTypeNames,
|
|
152
171
|
stampCanonicalTypes,
|
|
@@ -188,3 +188,64 @@ describe("AppSync_SdlDecorate.stampAllTypesCognito", () => {
|
|
|
188
188
|
expect(AppSync_SdlDecorate.stampAllTypesCognito(once))->toBe(once)
|
|
189
189
|
})
|
|
190
190
|
})
|
|
191
|
+
|
|
192
|
+
// ── Final field sweep ───────────────────────────────────────────────────────
|
|
193
|
+
//
|
|
194
|
+
// `injectAwsAuth` only decorates fields it can pair with a schema entry. Several
|
|
195
|
+
// surfaces never reach it: the domain base (`Platform_ping`), event-history
|
|
196
|
+
// queries, `…_eventAppended` subscriptions, upload mutations,
|
|
197
|
+
// `Platform_SetActiveRole`, `geocode`. A deployed-SDL count after the first
|
|
198
|
+
// sweep found 11 such fields still bare on the domain API. This pass is the net.
|
|
199
|
+
|
|
200
|
+
describe("AppSync_SdlDecorate.stampUndirectivedFields", () => {
|
|
201
|
+
let frag = (~mutations=[], ~queries=[], ~subscriptions=[], ()) =>
|
|
202
|
+
ReventlessCore.GraphQL_Stitcher.encode({
|
|
203
|
+
types: [],
|
|
204
|
+
mutations,
|
|
205
|
+
queries,
|
|
206
|
+
subscriptions,
|
|
207
|
+
subscriptionSources: [],
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
testSync("stamps a query no schema entry ever paired with", () => {
|
|
211
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(
|
|
212
|
+
frag(~queries=[" Platform_ping: String"], ()),
|
|
213
|
+
)
|
|
214
|
+
let q = ReventlessCore.GraphQL_Stitcher.decode(out).queries->Array.getUnsafe(0)
|
|
215
|
+
expect(q)->toContain("@aws_cognito_user_pools")
|
|
216
|
+
expect(q)->not_->toContain("cognito_groups")
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
testSync("stamps an event-log subscription", () => {
|
|
220
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(
|
|
221
|
+
frag(~subscriptions=[" onFoo_eventAppended: FooEvent"], ()),
|
|
222
|
+
)
|
|
223
|
+
expect(
|
|
224
|
+
ReventlessCore.GraphQL_Stitcher.decode(out).subscriptions->Array.getUnsafe(0),
|
|
225
|
+
)->toContain("@aws_cognito_user_pools")
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
testSync("leaves an already-gated field untouched, group and all", () => {
|
|
229
|
+
let gated = ` Secret_Read: String\n @aws_cognito_user_pools(cognito_groups: ["Admin"])`
|
|
230
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(frag(~queries=[gated], ()))
|
|
231
|
+
let q = ReventlessCore.GraphQL_Stitcher.decode(out).queries->Array.getUnsafe(0)
|
|
232
|
+
expect(q)->toContain(`cognito_groups: ["Admin"]`)
|
|
233
|
+
expect(q->String.includes("@aws_cognito_user_pools @aws_cognito_user_pools"))->toBe(false)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
testSync("does not strip an @aws_iam arm", () => {
|
|
237
|
+
let dual = " Sys_Sync: String\n @aws_cognito_user_pools @aws_iam"
|
|
238
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(frag(~mutations=[dual], ()))
|
|
239
|
+
expect(
|
|
240
|
+
ReventlessCore.GraphQL_Stitcher.decode(out).mutations->Array.getUnsafe(0),
|
|
241
|
+
)->toContain("@aws_iam")
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
testSync("is idempotent", () => {
|
|
245
|
+
let once = AppSync_SdlDecorate.stampUndirectivedFields(frag(~queries=[" a: String"], ()))
|
|
246
|
+
let twice = AppSync_SdlDecorate.stampUndirectivedFields(once)
|
|
247
|
+
expect(ReventlessCore.GraphQL_Stitcher.decode(twice).queries->Array.getUnsafe(0))->toBe(
|
|
248
|
+
ReventlessCore.GraphQL_Stitcher.decode(once).queries->Array.getUnsafe(0),
|
|
249
|
+
)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
@@ -152,6 +152,46 @@ globalThis.describe("AppSync_SdlDecorate.stampAllTypesCognito", () => {
|
|
|
152
152
|
});
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
+
globalThis.describe("AppSync_SdlDecorate.stampUndirectivedFields", () => {
|
|
156
|
+
let frag = (mutationsOpt, queriesOpt, subscriptionsOpt, param) => {
|
|
157
|
+
let mutations = mutationsOpt !== undefined ? mutationsOpt : [];
|
|
158
|
+
let queries = queriesOpt !== undefined ? queriesOpt : [];
|
|
159
|
+
let subscriptions = subscriptionsOpt !== undefined ? subscriptionsOpt : [];
|
|
160
|
+
return GraphQL_Stitcher$ReventlessCore.encode({
|
|
161
|
+
types: [],
|
|
162
|
+
mutations: mutations,
|
|
163
|
+
queries: queries,
|
|
164
|
+
subscriptions: subscriptions,
|
|
165
|
+
subscriptionSources: []
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
globalThis.test("stamps a query no schema entry ever paired with", () => {
|
|
169
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [" Platform_ping: String"], undefined, undefined));
|
|
170
|
+
let q = GraphQL_Stitcher$ReventlessCore.decode(out).queries[0];
|
|
171
|
+
globalThis.expect(q).toContain("@aws_cognito_user_pools");
|
|
172
|
+
globalThis.expect(q).not.toContain("cognito_groups");
|
|
173
|
+
});
|
|
174
|
+
globalThis.test("stamps an event-log subscription", () => {
|
|
175
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, undefined, [" onFoo_eventAppended: FooEvent"], undefined));
|
|
176
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(out).subscriptions[0]).toContain("@aws_cognito_user_pools");
|
|
177
|
+
});
|
|
178
|
+
globalThis.test("leaves an already-gated field untouched, group and all", () => {
|
|
179
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [` Secret_Read: String\n @aws_cognito_user_pools(cognito_groups: ["Admin"])`], undefined, undefined));
|
|
180
|
+
let q = GraphQL_Stitcher$ReventlessCore.decode(out).queries[0];
|
|
181
|
+
globalThis.expect(q).toContain(`cognito_groups: ["Admin"]`);
|
|
182
|
+
globalThis.expect(q.includes("@aws_cognito_user_pools @aws_cognito_user_pools")).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
globalThis.test("does not strip an @aws_iam arm", () => {
|
|
185
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag([" Sys_Sync: String\n @aws_cognito_user_pools @aws_iam"], undefined, undefined, undefined));
|
|
186
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(out).mutations[0]).toContain("@aws_iam");
|
|
187
|
+
});
|
|
188
|
+
globalThis.test("is idempotent", () => {
|
|
189
|
+
let once = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [" a: String"], undefined, undefined));
|
|
190
|
+
let twice = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(once);
|
|
191
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(twice).queries[0]).toBe(GraphQL_Stitcher$ReventlessCore.decode(once).queries[0]);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
155
195
|
export {
|
|
156
196
|
sources,
|
|
157
197
|
stitchedSdl,
|