@reventlessdev/reventless-aws 3.0.0-alpha.392 → 3.0.0-alpha.394
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 +8 -8
- package/src/Platform.res +5 -1
- package/src/components/Api/AppSync_Adapter.res +10 -0
- package/src/components/Api/AppSync_Adapter.res.mjs +10 -1
- package/src/util/Util_ShellConfig.res +8 -19
- package/src/util/Util_ShellConfig.res.mjs +0 -8
- package/tests/AppSync_AdapterTest.res +33 -0
- package/tests/AppSync_AdapterTest.res.mjs +25 -0
- package/tests/Util_ShellConfigTest.res +7 -32
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.394 (2026-10-22)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **local:** the local platform provisions an identity provider over the accounts file ([1a46867](https://github.com/ReventlessDev/reventless-core/commit/1a46867ac391b3b4a2d6973606d782547190dcb8))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.393 (2026-10-21)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **aws:** a failed AppSync schema push names the API and AWS's message ([01f662a](https://github.com/ReventlessDev/reventless-core/commit/01f662a7c664807808c15933f6234b6b6588addd))
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# 3.0.0-alpha.392 (2026-10-21)
|
|
7
21
|
|
|
8
22
|
### 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.394",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -23,18 +23,18 @@
|
|
|
23
23
|
"uuid": "^13.0.0",
|
|
24
24
|
"yaml": "^2.8.3",
|
|
25
25
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.33",
|
|
26
|
-
"@reventlessdev/rescript-
|
|
26
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.13",
|
|
27
27
|
"@reventlessdev/rescript-node": "2.0.0-alpha.23",
|
|
28
|
+
"@reventlessdev/rescript-effect": "1.0.0-alpha.1",
|
|
28
29
|
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.31",
|
|
29
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.13",
|
|
30
|
-
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
31
30
|
"@reventlessdev/rescript-pulumi-pulumi": "3.0.0-alpha.0",
|
|
32
|
-
"@reventlessdev/
|
|
33
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.200",
|
|
31
|
+
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
34
32
|
"@reventlessdev/rescript-web": "1.0.0-alpha.8",
|
|
33
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.202",
|
|
34
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.305",
|
|
35
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.169",
|
|
35
36
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.42",
|
|
36
|
-
"@reventlessdev/reventless-
|
|
37
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.172"
|
|
37
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.174"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"rescript": "12.3.1",
|
package/src/Platform.res
CHANGED
|
@@ -1287,6 +1287,10 @@ module MakeWithConfig = (
|
|
|
1287
1287
|
// door: unlike geocoding, a browser has no business sending mail from the
|
|
1288
1288
|
// deployment's identity. Unset ⇒ no export, no grant.
|
|
1289
1289
|
messagingSender?: ReventlessInfra.Platform.messagingSender,
|
|
1290
|
+
// Ignored here: an in-process provider cannot reach a Lambda, and no
|
|
1291
|
+
// pool-backed handle exists yet. The deploy gate refuses a plugin that
|
|
1292
|
+
// declares the need, naming this.
|
|
1293
|
+
identityProvider?: Reventless.IdentityProvider.t,
|
|
1290
1294
|
// Optional object store for direct-to-S3 uploads, as returned by
|
|
1291
1295
|
// `Capability_ObjectStore_S3.make`. When set, the deploy registers it with
|
|
1292
1296
|
// the platform's one Upload service (`Upload_Presign_S3`) under the `uploads`
|
|
@@ -2375,7 +2379,7 @@ module MakeWithConfig = (
|
|
|
2375
2379
|
->Pulumi.Output.apply((((domainEp, platformEp, poolId, clientId), eventsEpOpt)) => {
|
|
2376
2380
|
// `authMode` stays `"cognito"`: it is a value naming which provider
|
|
2377
2381
|
// authenticates this deployment, and that is true. The identity *keys*
|
|
2378
|
-
//
|
|
2382
|
+
// are provider-neutral — see [Util_ShellConfig.identityFields].
|
|
2379
2383
|
let computed =
|
|
2380
2384
|
[
|
|
2381
2385
|
("apiEndpoint", JSON.Encode.string(domainEp)),
|
|
@@ -45,6 +45,15 @@ let startSchemaCreation = (client: appSyncClient, input: startSchemaCreationInpu
|
|
|
45
45
|
// jittered exponential backoff so a losing call can wait for the in-progress
|
|
46
46
|
// schema to finish and try again. Permanent errors (e.g. invalid SDL) are not
|
|
47
47
|
// retried — see AppSync_Error.classify.
|
|
48
|
+
// What the deploy reports when the push gives up. Carries AWS's own message: the classified
|
|
49
|
+
// error is a variant, which a rejected promise prints as `[object Object]`.
|
|
50
|
+
let schemaPushFailure = (~apiId: string, err: AppSync_Error.t): string =>
|
|
51
|
+
switch err {
|
|
52
|
+
| Transient(msg) =>
|
|
53
|
+
`StartSchemaCreation for AppSync API ${apiId} still failed after its retries: ${msg}`
|
|
54
|
+
| Permanent(msg) => `StartSchemaCreation for AppSync API ${apiId} failed: ${msg}`
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
let startSchemaCreationRetrying = (client: appSyncClient, input: startSchemaCreationInput): promise<
|
|
49
58
|
unit,
|
|
50
59
|
> => {
|
|
@@ -52,6 +61,7 @@ let startSchemaCreationRetrying = (client: appSyncClient, input: startSchemaCrea
|
|
|
52
61
|
startSchemaCreation(client, input)->Promise.then(_ => Promise.resolve())
|
|
53
62
|
)
|
|
54
63
|
->Effect.retry(AppSync_Error.retrySchedule)
|
|
64
|
+
->Effect.catchAll(err => Effect.fail(schemaPushFailure(~apiId=input.apiId, err)))
|
|
55
65
|
->Effect.runPromise
|
|
56
66
|
}
|
|
57
67
|
|
|
@@ -33,8 +33,16 @@ function startSchemaCreation(client, input) {
|
|
|
33
33
|
return client.send(new ClientAppsync.StartSchemaCreationCommand(input));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function schemaPushFailure(apiId, err) {
|
|
37
|
+
if (err.TAG === "Transient") {
|
|
38
|
+
return `StartSchemaCreation for AppSync API ` + apiId + ` still failed after its retries: ` + err._0;
|
|
39
|
+
} else {
|
|
40
|
+
return `StartSchemaCreation for AppSync API ` + apiId + ` failed: ` + err._0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
function startSchemaCreationRetrying(client, input) {
|
|
37
|
-
return Effect$1.runPromise(Effect$1.retry(Effect.tryPromise(AppSync_Error$ReventlessAws.classify, () => client.send(new ClientAppsync.StartSchemaCreationCommand(input)).then(param => Promise.resolve())), AppSync_Error$ReventlessAws.retrySchedule));
|
|
45
|
+
return Effect$1.runPromise(Effect$1.catchAll(Effect$1.retry(Effect.tryPromise(AppSync_Error$ReventlessAws.classify, () => client.send(new ClientAppsync.StartSchemaCreationCommand(input)).then(param => Promise.resolve())), AppSync_Error$ReventlessAws.retrySchedule), err => Effect$1.fail(schemaPushFailure(input.apiId, err))));
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
async function waitForSchemaActive(client, apiId, maxAttemptsOpt, attemptOpt) {
|
|
@@ -326,6 +334,7 @@ export {
|
|
|
326
334
|
log,
|
|
327
335
|
sha256Hex,
|
|
328
336
|
startSchemaCreation,
|
|
337
|
+
schemaPushFailure,
|
|
329
338
|
startSchemaCreationRetrying,
|
|
330
339
|
waitForSchemaActive,
|
|
331
340
|
_client,
|
|
@@ -32,31 +32,20 @@ let modeOptions = (mode: Platform.viewMode): array<(string, JSON.t)> =>
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
The identity fields a shell reads,
|
|
35
|
+
The identity fields a shell reads. The concept is not AWS-specific, so a second
|
|
36
|
+
cloud's adapter meets the same names.
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
The `cognitoUserPoolId` / `cognitoClientId` pair travelled beside these through
|
|
39
|
+
the rename, because a shell reads `config.json` at runtime and a bundle that
|
|
40
|
+
cannot find its client id gets `None` and a login that silently fails. It went
|
|
41
|
+
once every pinned shell read these names (host shell 3.0.0-alpha.122 and later).
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
where the served bundle and the served config disagree. `cognitoClientId` is read
|
|
44
|
-
into an `option`: a bundle that cannot find its key does not error, it gets
|
|
45
|
-
`None`, and login, silent refresh and token refresh all quietly fall through —
|
|
46
|
-
the window is a total auth outage that reports nothing.
|
|
47
|
-
|
|
48
|
-
Writing both removes the ordering dependency rather than managing it: any bundle,
|
|
49
|
-
old or new or stale in a CDN, finds one it understands. The `cognito*` pair goes
|
|
50
|
-
once the shell that prefers the other is the pinned one.
|
|
51
|
-
|
|
52
|
-
Here rather than inline at the call site so the pairing is one fact in one place,
|
|
53
|
-
and so the property can be asserted without a deploy.
|
|
43
|
+
Here rather than inline at the call site so the key set is one fact in one place,
|
|
44
|
+
and so it can be asserted without a deploy.
|
|
54
45
|
*/
|
|
55
46
|
let identityFields = (~providerId: string, ~clientId: string): array<(string, JSON.t)> => [
|
|
56
47
|
("identityProviderId", JSON.Encode.string(providerId)),
|
|
57
48
|
("identityProviderClientId", JSON.Encode.string(clientId)),
|
|
58
|
-
("cognitoUserPoolId", JSON.Encode.string(providerId)),
|
|
59
|
-
("cognitoClientId", JSON.Encode.string(clientId)),
|
|
60
49
|
]
|
|
61
50
|
|
|
62
51
|
/**
|
|
@@ -1080,3 +1080,36 @@ describe("AppSync_Adapter.injectAwsAuth — subscriptions", () => {
|
|
|
1080
1080
|
expect(decodeFragment(aug).subscriptions->Array.length)->toBe(2)
|
|
1081
1081
|
})
|
|
1082
1082
|
})
|
|
1083
|
+
|
|
1084
|
+
// ── A failed schema push says why ────────────────────────────────────────────
|
|
1085
|
+
|
|
1086
|
+
describe("AppSync_Adapter.startSchemaCreationRetrying", () => {
|
|
1087
|
+
// A client whose every call rejects the way the SDK does, with an Error.
|
|
1088
|
+
let rejectingClient: string => AppSync_Adapter.appSyncClient = %raw(`
|
|
1089
|
+
message => ({send: () => Promise.reject(new Error(message))})
|
|
1090
|
+
`)
|
|
1091
|
+
|
|
1092
|
+
test("fails with the API and AWS's message, not [object Object]", async () => {
|
|
1093
|
+
let message = ref("")
|
|
1094
|
+
try {
|
|
1095
|
+
await AppSync_Adapter.startSchemaCreationRetrying(
|
|
1096
|
+
rejectingClient("BadRequestException: Failed to parse schema document"),
|
|
1097
|
+
{apiId: "api-1", definition: "type Query"},
|
|
1098
|
+
)
|
|
1099
|
+
} catch {
|
|
1100
|
+
| exn => message := exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("")
|
|
1101
|
+
}
|
|
1102
|
+
expect(message.contents)->toContain("api-1")
|
|
1103
|
+
expect(message.contents)->toContain("Failed to parse schema document")
|
|
1104
|
+
expect(message.contents)->not_->toContain("[object Object]")
|
|
1105
|
+
})
|
|
1106
|
+
|
|
1107
|
+
testSync("says when the retries ran out", () =>
|
|
1108
|
+
expect(
|
|
1109
|
+
AppSync_Adapter.schemaPushFailure(
|
|
1110
|
+
~apiId="api-1",
|
|
1111
|
+
Transient("ConcurrentModificationException: Schema is currently being altered"),
|
|
1112
|
+
),
|
|
1113
|
+
)->toContain("still failed after its retries")
|
|
1114
|
+
)
|
|
1115
|
+
})
|
|
@@ -953,6 +953,31 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — subscriptions", () => {
|
|
|
953
953
|
});
|
|
954
954
|
});
|
|
955
955
|
|
|
956
|
+
globalThis.describe("AppSync_Adapter.startSchemaCreationRetrying", () => {
|
|
957
|
+
let rejectingClient = (message => ({send: () => Promise.reject(new Error(message))}));
|
|
958
|
+
globalThis.test("fails with the API and AWS's message, not [object Object]", async () => {
|
|
959
|
+
let message = "";
|
|
960
|
+
try {
|
|
961
|
+
await AppSync_Adapter$ReventlessAws.startSchemaCreationRetrying(rejectingClient("BadRequestException: Failed to parse schema document"), {
|
|
962
|
+
apiId: "api-1",
|
|
963
|
+
definition: "type Query"
|
|
964
|
+
});
|
|
965
|
+
} catch (raw_exn) {
|
|
966
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
967
|
+
message = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "");
|
|
968
|
+
}
|
|
969
|
+
globalThis.expect(message).toContain("api-1");
|
|
970
|
+
globalThis.expect(message).toContain("Failed to parse schema document");
|
|
971
|
+
globalThis.expect(message).not.toContain("[object Object]");
|
|
972
|
+
});
|
|
973
|
+
globalThis.test("says when the retries ran out", () => {
|
|
974
|
+
globalThis.expect(AppSync_Adapter$ReventlessAws.schemaPushFailure("api-1", {
|
|
975
|
+
TAG: "Transient",
|
|
976
|
+
_0: "ConcurrentModificationException: Schema is currently being altered"
|
|
977
|
+
})).toContain("still failed after its retries");
|
|
978
|
+
});
|
|
979
|
+
});
|
|
980
|
+
|
|
956
981
|
export {
|
|
957
982
|
decodeFragment,
|
|
958
983
|
TwoRuleFeed,
|
|
@@ -298,50 +298,25 @@ describe("Util_ShellConfig.fields — shellConfig passthrough", () => {
|
|
|
298
298
|
})
|
|
299
299
|
})
|
|
300
300
|
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
// read into an option, so a bundle that cannot find its key does not error, it
|
|
306
|
-
// gets `None` and login silently stops working.
|
|
307
|
-
//
|
|
308
|
-
// These assert the property that removes that window, not the spelling.
|
|
301
|
+
// A shell reads config.json at runtime, and `identityProviderClientId` is read
|
|
302
|
+
// into an option: a bundle that cannot find it gets `None` and a login that
|
|
303
|
+
// silently stops working. These assert the key set, so a rename cannot slip out
|
|
304
|
+
// in a refactor without a test naming it.
|
|
309
305
|
describe("Util_ShellConfig.identityFields", () => {
|
|
310
306
|
let fields = Util_ShellConfig.identityFields(~providerId="eu-west-1_x", ~clientId="7cl13nt")
|
|
311
307
|
let get = key => fields->Array.find(((k, _)) => k == key)->Option.map(((_, v)) => v)
|
|
312
308
|
|
|
313
|
-
testSync("the
|
|
309
|
+
testSync("the identity keys carry the pool and the client", () =>
|
|
314
310
|
expect((get("identityProviderId"), get("identityProviderClientId")))->toEqual((
|
|
315
311
|
Some(JSON.Encode.string("eu-west-1_x")),
|
|
316
312
|
Some(JSON.Encode.string("7cl13nt")),
|
|
317
313
|
))
|
|
318
314
|
)
|
|
319
315
|
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
testSync("the deprecated spelling is present too", () =>
|
|
323
|
-
expect((get("cognitoUserPoolId"), get("cognitoClientId")))->toEqual((
|
|
324
|
-
Some(JSON.Encode.string("eu-west-1_x")),
|
|
325
|
-
Some(JSON.Encode.string("7cl13nt")),
|
|
326
|
-
))
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
// The whole point: a bundle reading either name gets the same answer. Two keys
|
|
330
|
-
// carrying different values would be worse than one key, not better.
|
|
331
|
-
testSync("both spellings carry identical values", () =>
|
|
332
|
-
expect((
|
|
333
|
-
get("identityProviderId") == get("cognitoUserPoolId"),
|
|
334
|
-
get("identityProviderClientId") == get("cognitoClientId"),
|
|
335
|
-
))->toEqual((true, true))
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
// The client id is the load-bearing one — it is what the shell's auth provider
|
|
339
|
-
// branches on. The pool id is currently discarded by the shell, which is
|
|
340
|
-
// exactly why a `cognito*` sweep is dangerous: the safe one gives no warning.
|
|
316
|
+
// The transitional `cognito*` pair is gone: every pinned shell reads the
|
|
317
|
+
// names above, and a reader that still wants the old ones is the defect.
|
|
341
318
|
testSync("nothing else sneaks into the identity field set", () =>
|
|
342
319
|
expect(fields->Array.map(((k, _)) => k)->Array.toSorted(String.compare))->toEqual([
|
|
343
|
-
"cognitoClientId",
|
|
344
|
-
"cognitoUserPoolId",
|
|
345
320
|
"identityProviderClientId",
|
|
346
321
|
"identityProviderId",
|
|
347
322
|
])
|