@reventlessdev/reventless-aws 3.0.0-alpha.340 → 3.0.0-alpha.341
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.341 (2026-09-08)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** the readiness gate waits for the collector's update, not its record ([c5a1d27](https://github.com/ReventlessDev/reventless-core/commit/c5a1d273be2c499bf1d1297a416d2cf6fce85ccf))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.340 (2026-09-08)
|
|
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.
|
|
3
|
+
"version": "3.0.0-alpha.341",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"uuid": "^13.0.0",
|
|
18
18
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.15",
|
|
19
19
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
20
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
21
20
|
"@reventlessdev/rescript-node": "2.0.0-alpha.9",
|
|
21
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
22
22
|
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.9",
|
|
23
23
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
24
24
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
25
25
|
"@reventlessdev/reventless-core": "3.0.0-alpha.259",
|
|
26
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.160",
|
|
26
27
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.35",
|
|
27
28
|
"@reventlessdev/reventless-postgres": "3.0.0-alpha.123",
|
|
28
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.160",
|
|
29
29
|
"@reventlessdev/reventless-spec": "3.0.0-alpha.132"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
@@ -677,7 +677,13 @@ module Make = (
|
|
|
677
677
|
~opts,
|
|
678
678
|
)
|
|
679
679
|
|
|
680
|
-
|
|
680
|
+
// Through an output property, not the resource. `apply` receives the Function
|
|
681
|
+
// record as soon as the outer Output carries it — at construction, fields
|
|
682
|
+
// still unresolved — so discarding it reported ready mid-update, and the
|
|
683
|
+
// re-detect reached a Lambda still serving the previous bundle. `flatMap`
|
|
684
|
+
// returns the Output itself, which Pulumi awaits.
|
|
685
|
+
eventCollectorReadyRef :=
|
|
686
|
+
runtime.parts.lambda->Pulumi.Output.flatMap(fn => fn.arn)->Pulumi.Output.apply(_ => ())
|
|
681
687
|
|
|
682
688
|
// Admin-only cross-plugin SNS subscription permissions. The admin EC
|
|
683
689
|
// Lambda's manageSubscriptions hook (Phase 3 Step 1) creates SNS
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Aws from "@pulumi/aws";
|
|
4
4
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
+
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
5
6
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
7
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
8
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
@@ -413,7 +414,7 @@ function Make(EventCollectorChannel) {
|
|
|
413
414
|
eventTopics: eventTopics,
|
|
414
415
|
resources: resources
|
|
415
416
|
}], runtime, opts);
|
|
416
|
-
eventCollectorReadyRef.contents = runtime.parts.lambda.apply(param => {});
|
|
417
|
+
eventCollectorReadyRef.contents = Output$Pulumi.flatMap(runtime.parts.lambda, fn => fn.arn).apply(param => {});
|
|
417
418
|
let isAdminEventCollector = Stdlib_Option.isNone(Plugin_Helpers$ReventlessCore.eventCollectorContextRef.contents[name]);
|
|
418
419
|
if (isAdminEventCollector) {
|
|
419
420
|
new (Aws.iam.RolePolicy)(name + `-snsManageSubs`, {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
// Guards what `eventCollectorReadyRef` waits for.
|
|
4
|
+
//
|
|
5
|
+
// The deploy publishes a synthetic re-detect once the EventCollector Lambda is
|
|
6
|
+
// ready, because that Lambda is what ANSWERS the handshake: reached while it is
|
|
7
|
+
// still serving the previous bundle, it replies with the previous definition,
|
|
8
|
+
// Connect sees a definition it already holds and emits nothing, and the
|
|
9
|
+
// registration keeps the old structure — with no second re-detect coming.
|
|
10
|
+
//
|
|
11
|
+
// The gate used to be `lambda->Output.apply(_ => ())`. `apply` receives the
|
|
12
|
+
// Function record as soon as the OUTER Output carries it, which is at
|
|
13
|
+
// construction, with every field inside still an unresolved Output — so the gate
|
|
14
|
+
// settled while the update was in flight and the whole barrier was inert. No
|
|
15
|
+
// deploy-shaped test catches it: the answer is only stale when a deploy changes a
|
|
16
|
+
// structure, and the race is won more often than it is lost.
|
|
17
|
+
//
|
|
18
|
+
// So this asserts the barrier directly — the gate must not settle until the
|
|
19
|
+
// resource's own outputs do.
|
|
20
|
+
|
|
21
|
+
// The engine's Output constructor, so a field can be left genuinely pending
|
|
22
|
+
// rather than faked. A resolved promise would settle on the next tick either
|
|
23
|
+
// way, which is the difference under test.
|
|
24
|
+
@module("@pulumi/pulumi") @new
|
|
25
|
+
external makeOutput: (
|
|
26
|
+
Set.t<unit>,
|
|
27
|
+
promise<'a>,
|
|
28
|
+
promise<bool>,
|
|
29
|
+
promise<bool>,
|
|
30
|
+
promise<Set.t<unit>>,
|
|
31
|
+
) => Pulumi.Output.t<'a> = "Output"
|
|
32
|
+
|
|
33
|
+
let pending = (value: 'a): (Pulumi.Output.t<'a>, unit => unit) => {
|
|
34
|
+
let settle = ref(() => ())
|
|
35
|
+
let p = Promise.make((resolve, _) => settle := () => resolve(value))
|
|
36
|
+
(
|
|
37
|
+
makeOutput(Set.make(), p, Promise.resolve(true), Promise.resolve(false), Promise.resolve(Set.make())),
|
|
38
|
+
settle.contents,
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let ticks = async () => await Promise.make((resolve, _) => {
|
|
43
|
+
let _ = setTimeout(() => resolve(), 50)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// A Lambda mid-update: the resource record exists, its outputs do not yet.
|
|
47
|
+
let deploying = (): (Pulumi.Output.t<PulumiAws.Lambda.Function.t>, unit => unit) => {
|
|
48
|
+
let (arn, complete) = pending("arn:aws:lambda:eu-west-1:123456789012:function:CatalogPluginEventColl")
|
|
49
|
+
let fn: PulumiAws.Lambda.Function.t = {
|
|
50
|
+
arn,
|
|
51
|
+
id: "CatalogPluginEventColl"->Pulumi.Output.make,
|
|
52
|
+
name: "CatalogPluginEventColl"->Pulumi.Output.make,
|
|
53
|
+
invokeArn: "arn:aws:apigateway:invoke"->Pulumi.Output.make,
|
|
54
|
+
}
|
|
55
|
+
// `apply` preserves the nested Outputs, which is how the real resource arrives.
|
|
56
|
+
(()->Pulumi.Output.make->Pulumi.Output.apply(_ => fn), complete)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let observe = (gate: Pulumi.Output.t<unit>): ref<bool> => {
|
|
60
|
+
let settled = ref(false)
|
|
61
|
+
let _ = gate->Pulumi.Output.apply(_ => settled := true)
|
|
62
|
+
settled
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("the EventCollector readiness gate", () => {
|
|
66
|
+
testAsync("does not settle while the function's outputs are still pending", async () => {
|
|
67
|
+
let (lambda, _complete) = deploying()
|
|
68
|
+
let settled = lambda->Pulumi.Output.flatMap(fn => fn.arn)->Pulumi.Output.apply(_ => ())->observe
|
|
69
|
+
await ticks()
|
|
70
|
+
expect(settled.contents)->toBe(false)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
testAsync("settles once they resolve", async () => {
|
|
74
|
+
let (lambda, complete) = deploying()
|
|
75
|
+
let settled = lambda->Pulumi.Output.flatMap(fn => fn.arn)->Pulumi.Output.apply(_ => ())->observe
|
|
76
|
+
complete()
|
|
77
|
+
await ticks()
|
|
78
|
+
expect(settled.contents)->toBe(true)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// The shape that shipped, kept as the reason the one above is written the way it
|
|
82
|
+
// is: applying to the resource settles against a Lambda still being updated.
|
|
83
|
+
testAsync("settles immediately when taken off the resource instead", async () => {
|
|
84
|
+
let (lambda, _complete) = deploying()
|
|
85
|
+
let settled = lambda->Pulumi.Output.apply(_ => ())->observe
|
|
86
|
+
await ticks()
|
|
87
|
+
expect(settled.contents)->toBe(true)
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
4
|
+
import * as Pulumi from "@pulumi/pulumi";
|
|
5
|
+
|
|
6
|
+
function pending(value) {
|
|
7
|
+
let settle = {
|
|
8
|
+
contents: () => {}
|
|
9
|
+
};
|
|
10
|
+
let p = new Promise((resolve, param) => {
|
|
11
|
+
settle.contents = () => resolve(value);
|
|
12
|
+
});
|
|
13
|
+
return [
|
|
14
|
+
new Pulumi.Output(new Set(), p, Promise.resolve(true), Promise.resolve(false), Promise.resolve(new Set())),
|
|
15
|
+
settle.contents
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function ticks() {
|
|
20
|
+
return await new Promise((resolve, param) => {
|
|
21
|
+
setTimeout(() => resolve(), 50);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function deploying() {
|
|
26
|
+
let match = pending("arn:aws:lambda:eu-west-1:123456789012:function:CatalogPluginEventColl");
|
|
27
|
+
let fn_arn = match[0];
|
|
28
|
+
let fn_id = Pulumi.output("CatalogPluginEventColl");
|
|
29
|
+
let fn_name = Pulumi.output("CatalogPluginEventColl");
|
|
30
|
+
let fn_invokeArn = Pulumi.output("arn:aws:apigateway:invoke");
|
|
31
|
+
let fn = {
|
|
32
|
+
arn: fn_arn,
|
|
33
|
+
id: fn_id,
|
|
34
|
+
name: fn_name,
|
|
35
|
+
invokeArn: fn_invokeArn
|
|
36
|
+
};
|
|
37
|
+
return [
|
|
38
|
+
Pulumi.output().apply(() => fn),
|
|
39
|
+
match[1]
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function observe(gate) {
|
|
44
|
+
let settled = {
|
|
45
|
+
contents: false
|
|
46
|
+
};
|
|
47
|
+
gate.apply(() => {
|
|
48
|
+
settled.contents = true;
|
|
49
|
+
});
|
|
50
|
+
return settled;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
globalThis.describe("the EventCollector readiness gate", () => {
|
|
54
|
+
globalThis.test("does not settle while the function's outputs are still pending", async () => {
|
|
55
|
+
let match = deploying();
|
|
56
|
+
let settled = observe(Output$Pulumi.flatMap(match[0], fn => fn.arn).apply(param => {}));
|
|
57
|
+
await ticks();
|
|
58
|
+
globalThis.expect(settled.contents).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
globalThis.test("settles once they resolve", async () => {
|
|
61
|
+
let match = deploying();
|
|
62
|
+
let settled = observe(Output$Pulumi.flatMap(match[0], fn => fn.arn).apply(param => {}));
|
|
63
|
+
match[1]();
|
|
64
|
+
await ticks();
|
|
65
|
+
globalThis.expect(settled.contents).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
globalThis.test("settles immediately when taken off the resource instead", async () => {
|
|
68
|
+
let match = deploying();
|
|
69
|
+
let settled = observe(match[0].apply(param => {}));
|
|
70
|
+
await ticks();
|
|
71
|
+
globalThis.expect(settled.contents).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
pending,
|
|
77
|
+
ticks,
|
|
78
|
+
deploying,
|
|
79
|
+
observe,
|
|
80
|
+
}
|
|
81
|
+
/* Not a pure module */
|