@reventlessdev/reventless-aws 3.0.0-alpha.340 → 3.0.0-alpha.342

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.342 (2026-09-08)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **aws:** the boot files a deploy rewrites in place carry no-cache ([01a4d21](https://github.com/ReventlessDev/reventless-core/commit/01a4d21a50a7890fcbd541f8a336b1333100148a))
11
+
12
+
13
+ # 3.0.0-alpha.341 (2026-09-08)
14
+
15
+ ### Bug Fixes
16
+
17
+ * **aws:** the readiness gate waits for the collector's update, not its record ([c5a1d27](https://github.com/ReventlessDev/reventless-core/commit/c5a1d273be2c499bf1d1297a416d2cf6fce85ccf))
18
+
19
+
6
20
  # 3.0.0-alpha.340 (2026-09-08)
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.340",
3
+ "version": "3.0.0-alpha.342",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -15,18 +15,18 @@
15
15
  "@aws-sdk/s3-request-presigner": "3.970.0",
16
16
  "sury": "11.0.0-rc.2",
17
17
  "uuid": "^13.0.0",
18
- "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.15",
19
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
18
+ "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.16",
20
19
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
21
- "@reventlessdev/rescript-node": "2.0.0-alpha.9",
22
- "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.9",
20
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
21
+ "@reventlessdev/rescript-node": "2.0.0-alpha.10",
22
+ "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.10",
23
23
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
24
24
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
25
- "@reventlessdev/reventless-core": "3.0.0-alpha.259",
25
+ "@reventlessdev/reventless-core": "3.0.0-alpha.260",
26
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.124",
27
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.133",
26
28
  "@reventlessdev/reventless-interop": "3.0.0-alpha.35",
27
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.123",
28
- "@reventlessdev/reventless-infra": "3.0.0-alpha.160",
29
- "@reventlessdev/reventless-spec": "3.0.0-alpha.132"
29
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.161"
30
30
  },
31
31
  "devDependencies": {
32
32
  "rescript": "12.3.0",
package/src/Platform.res CHANGED
@@ -2321,6 +2321,11 @@ module MakeWithConfig = (
2321
2321
  ->JSON.stringify
2322
2322
  })
2323
2323
 
2324
+ // `Plugin_Stack.cacheControlFor`'s rule, for this file and the two below:
2325
+ // the content changes across deploys while the URL does not, so without it
2326
+ // CloudFront and the browser serve the previous deploy's answer.
2327
+ let bootFileCacheControl = Pulumi.Input.make("no-cache")
2328
+
2324
2329
  let _ = PulumiAws.S3.BucketObject.make(
2325
2330
  ~name="host-ui-config-json",
2326
2331
  ~args={
@@ -2328,6 +2333,7 @@ module MakeWithConfig = (
2328
2333
  key: Pulumi.Input.make("config.json"),
2329
2334
  content: configJsonContent->Pulumi.Output.asInput,
2330
2335
  contentType: Pulumi.Input.make("application/json"),
2336
+ cacheControl: bootFileCacheControl,
2331
2337
  },
2332
2338
  )
2333
2339
 
@@ -2350,6 +2356,7 @@ module MakeWithConfig = (
2350
2356
  key: Pulumi.Input.make("ui-hints.json"),
2351
2357
  content: Pulumi.Input.make(hintsContent),
2352
2358
  contentType: Pulumi.Input.make("application/json"),
2359
+ cacheControl: bootFileCacheControl,
2353
2360
  },
2354
2361
  )
2355
2362
  }
@@ -2386,6 +2393,7 @@ module MakeWithConfig = (
2386
2393
  key: Pulumi.Input.make(ReventlessCore.Platform_UiSlots.fileName),
2387
2394
  content: Pulumi.Input.make(slotsContent),
2388
2395
  contentType: Pulumi.Input.make("application/javascript; charset=utf-8"),
2396
+ cacheControl: bootFileCacheControl,
2389
2397
  },
2390
2398
  )
2391
2399
  }
@@ -1251,11 +1251,13 @@ function MakeWithConfig(Config) {
1251
1251
  ]) : computed;
1252
1252
  return JSON.stringify(Util_ShellConfig$ReventlessAws.fields(withEvents, hostUiBundle.viewModes, hostUiBundle.bakedManifest, hostUiBundle.uiSlotsFile, hostUiBundle.shellConfig));
1253
1253
  });
1254
+ let bootFileCacheControl = "no-cache";
1254
1255
  new (Aws.s3.BucketObject)("host-ui-config-json", {
1255
1256
  bucket: bucketName,
1256
1257
  key: "config.json",
1257
1258
  content: configJsonContent,
1258
- contentType: "application/json"
1259
+ contentType: "application/json",
1260
+ cacheControl: bootFileCacheControl
1259
1261
  });
1260
1262
  let hintsPath = hostUiBundle.uiHintsFile;
1261
1263
  if (hintsPath !== undefined) {
@@ -1264,7 +1266,8 @@ function MakeWithConfig(Config) {
1264
1266
  bucket: bucketName,
1265
1267
  key: "ui-hints.json",
1266
1268
  content: hintsContent,
1267
- contentType: "application/json"
1269
+ contentType: "application/json",
1270
+ cacheControl: bootFileCacheControl
1268
1271
  });
1269
1272
  }
1270
1273
  let slotsPath = hostUiBundle.uiSlotsFile;
@@ -1274,7 +1277,8 @@ function MakeWithConfig(Config) {
1274
1277
  bucket: bucketName,
1275
1278
  key: Platform_UiSlots$ReventlessCore.fileName,
1276
1279
  content: slotsContent,
1277
- contentType: "application/javascript; charset=utf-8"
1280
+ contentType: "application/javascript; charset=utf-8",
1281
+ cacheControl: bootFileCacheControl
1278
1282
  });
1279
1283
  }
1280
1284
  let match$9 = hostUiBundle.bakedManifest;
@@ -2623,11 +2627,13 @@ function Make($star) {
2623
2627
  ]) : computed;
2624
2628
  return JSON.stringify(Util_ShellConfig$ReventlessAws.fields(withEvents, hostUiBundle.viewModes, hostUiBundle.bakedManifest, hostUiBundle.uiSlotsFile, hostUiBundle.shellConfig));
2625
2629
  });
2630
+ let bootFileCacheControl = "no-cache";
2626
2631
  new (Aws.s3.BucketObject)("host-ui-config-json", {
2627
2632
  bucket: bucketName,
2628
2633
  key: "config.json",
2629
2634
  content: configJsonContent,
2630
- contentType: "application/json"
2635
+ contentType: "application/json",
2636
+ cacheControl: bootFileCacheControl
2631
2637
  });
2632
2638
  let hintsPath = hostUiBundle.uiHintsFile;
2633
2639
  if (hintsPath !== undefined) {
@@ -2636,7 +2642,8 @@ function Make($star) {
2636
2642
  bucket: bucketName,
2637
2643
  key: "ui-hints.json",
2638
2644
  content: hintsContent,
2639
- contentType: "application/json"
2645
+ contentType: "application/json",
2646
+ cacheControl: bootFileCacheControl
2640
2647
  });
2641
2648
  }
2642
2649
  let slotsPath = hostUiBundle.uiSlotsFile;
@@ -2646,7 +2653,8 @@ function Make($star) {
2646
2653
  bucket: bucketName,
2647
2654
  key: Platform_UiSlots$ReventlessCore.fileName,
2648
2655
  content: slotsContent,
2649
- contentType: "application/javascript; charset=utf-8"
2656
+ contentType: "application/javascript; charset=utf-8",
2657
+ cacheControl: bootFileCacheControl
2650
2658
  });
2651
2659
  }
2652
2660
  let match$9 = hostUiBundle.bakedManifest;
@@ -645,6 +645,11 @@ let runBake = async (
645
645
  key,
646
646
  body: AwsSdk.S3.PutObjectCommand.bodyFromString(body),
647
647
  contentType: "application/json",
648
+ // Stable-named, rewritten in place every deploy — `Plugin_Stack.cacheControlFor`'s
649
+ // rule. This file needs it most: it is written AFTER the deploy's `/*`
650
+ // invalidation, so nothing else dislodges a stale copy, and a shell
651
+ // holding one queries fields the API no longer has.
652
+ cacheControl: "no-cache",
648
653
  })->AwsSdk.S3.PutObjectCommand.send
649
654
  let report = Dict.fromArray([
650
655
  ("baked", JSON.Encode.bool(true)),
@@ -550,7 +550,8 @@ async function runBake(target, structures) {
550
550
  Bucket: target.bucket,
551
551
  Key: key,
552
552
  Body: body,
553
- ContentType: "application/json"
553
+ ContentType: "application/json",
554
+ CacheControl: "no-cache"
554
555
  }));
555
556
  let report = Object.fromEntries([
556
557
  [
@@ -677,7 +677,13 @@ module Make = (
677
677
  ~opts,
678
678
  )
679
679
 
680
- eventCollectorReadyRef := runtime.parts.lambda->Pulumi.Output.apply(_ => ())
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 */