@reventlessdev/reventless-aws 3.0.0-alpha.422 → 3.0.0-alpha.424

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,21 @@
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.424 (2026-11-06)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **deps:** update sury to 11.1.0 ([512bc78](https://github.com/ReventlessDev/reventless-core/commit/512bc78da92c03664fa52fb3ab32df65f5e49b19))
11
+
12
+
13
+ # 3.0.0-alpha.423 (2026-11-05)
14
+
15
+ ### Bug Fixes
16
+
17
+ * **aws:** a line logged outside its invocation's fiber still names the plugin ([8e6f553](https://github.com/ReventlessDev/reventless-core/commit/8e6f553cc8ac2e439862bf1f3636ac86a4a22137))
18
+ * **aws:** an extension's lines name its extension point once ([b448c59](https://github.com/ReventlessDev/reventless-core/commit/b448c5976feafd7d49ae8c04a67dcfbb72e973b5))
19
+
20
+
6
21
  # 3.0.0-alpha.422 (2026-11-05)
7
22
 
8
23
  **Note:** Version bump only for package @reventlessdev/reventless-aws
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.422",
3
+ "version": "3.0.0-alpha.424",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -19,26 +19,26 @@
19
19
  "@aws-sdk/client-location": "3.970.0",
20
20
  "@aws-sdk/client-s3": "3.970.0",
21
21
  "@aws-sdk/s3-request-presigner": "3.970.0",
22
- "sury": "11.0.0",
22
+ "sury": "11.1.0",
23
23
  "uuid": "^13.0.0",
24
24
  "yaml": "^2.8.3",
25
25
  "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.39",
26
26
  "@reventlessdev/rescript-effect": "1.0.0-alpha.1",
27
- "@reventlessdev/rescript-node": "2.0.0-alpha.26",
28
- "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.39",
27
+ "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.40",
29
28
  "@reventlessdev/rescript-jest": "1.0.0-alpha.13",
29
+ "@reventlessdev/rescript-node": "2.0.0-alpha.26",
30
30
  "@reventlessdev/rescript-pulumi-pulumi": "3.0.0-alpha.0",
31
31
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
32
+ "@reventlessdev/reventless-core": "3.0.0-alpha.331",
33
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.224",
34
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.43",
35
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.195",
32
36
  "@reventlessdev/rescript-web": "1.0.0-alpha.8",
33
- "@reventlessdev/reventless-infra": "3.0.0-alpha.223",
34
- "@reventlessdev/reventless-interop": "3.0.0-alpha.42",
35
- "@reventlessdev/reventless-spec": "3.0.0-alpha.194",
36
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.193",
37
- "@reventlessdev/reventless-core": "3.0.0-alpha.329"
37
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.195"
38
38
  },
39
39
  "devDependencies": {
40
40
  "rescript": "12.3.1",
41
- "sury-ppx": "11.0.0"
41
+ "sury-ppx": "11.1.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "rescript": "^12.3.1"
@@ -495,22 +495,30 @@ let outputString = (outputs: dict<Automation.outputValue>, name: string): option
495
495
  /** The stack's own accounts file beside the platform, started from the app's
496
496
  template, or `None` when the app has no template — then there is nothing to
497
497
  create. Per stack because the file records the ids one pool minted: sharing
498
- `accounts.yaml` would overwrite another deployment's record of its accounts. A stack deployed
499
- before the file was renamed keeps its `users.<stack>.yaml` until it is renamed. */
498
+ `accounts.yaml` would overwrite another deployment's record of its accounts. */
500
499
  let accountsFileName = stack => `accounts.${stack}.yaml`
501
- let oldAccountsFileName = stack => `users.${stack}.yaml`
502
500
 
503
- let accountsFile = (~platformDir: string, ~stack: string): option<string> => {
504
- let file = {
505
- let current = NodePath.join([platformDir, ".reventless", accountsFileName(stack)])
506
- let old = NodePath.join([platformDir, ".reventless", oldAccountsFileName(stack)])
507
- if !NodeFs.existsSync(current) && NodeFs.existsSync(old) {
508
- Manifest.noticeOldName(old)
509
- old
510
- } else {
511
- current
512
- }
501
+ let _accountsPath = (~platformDir, ~stack) =>
502
+ NodePath.join([platformDir, ".reventless", accountsFileName(stack)])
503
+
504
+ /** The refusal when the stack's record or the template is still under its old name. Checked
505
+ before deploying: copying the template over a record left as `users.<stack>.yaml` would
506
+ lose the ids its pool minted. */
507
+ let oldAccountsNameLeft = (~platformDir: string, ~stack: string): option<string> =>
508
+ switch Manifest.oldNameLeft(
509
+ ~current=_accountsPath(~platformDir, ~stack),
510
+ ~old=NodePath.join([platformDir, ".reventless", `users.${stack}.yaml`]),
511
+ ) {
512
+ | Some(_) as refusal => refusal
513
+ | None =>
514
+ Manifest.oldNameLeft(
515
+ ~current=NodePath.join([platformDir, Manifest.templateName]),
516
+ ~old=NodePath.join([platformDir, Manifest.oldTemplateName]),
517
+ )
513
518
  }
519
+
520
+ let accountsFile = (~platformDir: string, ~stack: string): option<string> => {
521
+ let file = _accountsPath(~platformDir, ~stack)
514
522
  let template = Manifest.templateIn(platformDir)
515
523
  switch (NodeFs.existsSync(file), NodeFs.existsSync(template)) {
516
524
  | (false, false) => None
@@ -533,7 +541,12 @@ let signInLines = (entries: array<Manifest.entry>): array<string> => {
533
541
  // ── up / down ────────────────────────────────────────────────────────────────
534
542
 
535
543
  let up = async (~manifest: DeployManifest.resolved, ~stack: string): result<unit, string> =>
536
- switch projectsOf(manifest) {
544
+ switch projectsOf(manifest)->Result.flatMap(((platform, _) as projects) =>
545
+ switch oldAccountsNameLeft(~platformDir=platform.dir, ~stack) {
546
+ | Some(refusal) => Error(refusal)
547
+ | None => Ok(projects)
548
+ }
549
+ ) {
537
550
  | Error(_) as e => e
538
551
  | Ok((platform, plugins)) =>
539
552
  switch await check(~projects=[platform]->Array.concat(plugins)) {
@@ -614,14 +614,21 @@ function accountsFileName(stack) {
614
614
  return `accounts.` + stack + `.yaml`;
615
615
  }
616
616
 
617
- function oldAccountsFileName(stack) {
618
- return `users.` + stack + `.yaml`;
617
+ function _accountsPath(platformDir, stack) {
618
+ return Nodepath.join(platformDir, ".reventless", accountsFileName(stack));
619
+ }
620
+
621
+ function oldAccountsNameLeft(platformDir, stack) {
622
+ let refusal = AccountsManifest$Reventless.oldNameLeft(_accountsPath(platformDir, stack), Nodepath.join(platformDir, ".reventless", `users.` + stack + `.yaml`));
623
+ if (refusal !== undefined) {
624
+ return refusal;
625
+ } else {
626
+ return AccountsManifest$Reventless.oldNameLeft(Nodepath.join(platformDir, AccountsManifest$Reventless.templateName), Nodepath.join(platformDir, AccountsManifest$Reventless.oldTemplateName));
627
+ }
619
628
  }
620
629
 
621
630
  function accountsFile(platformDir, stack) {
622
- let current = Nodepath.join(platformDir, ".reventless", accountsFileName(stack));
623
- let old = Nodepath.join(platformDir, ".reventless", oldAccountsFileName(stack));
624
- let file = !Nodefs.existsSync(current) && Nodefs.existsSync(old) ? (AccountsManifest$Reventless.noticeOldName(old), old) : current;
631
+ let file = _accountsPath(platformDir, stack);
625
632
  let template = AccountsManifest$Reventless.templateIn(platformDir);
626
633
  let match = Nodefs.existsSync(file);
627
634
  let match$1 = Nodefs.existsSync(template);
@@ -645,7 +652,20 @@ function signInLines(entries) {
645
652
  }
646
653
 
647
654
  async function up(manifest, stack) {
648
- let e = projectsOf(manifest);
655
+ let e = Stdlib_Result.flatMap(projectsOf(manifest), projects => {
656
+ let refusal = oldAccountsNameLeft(projects[0].dir, stack);
657
+ if (refusal !== undefined) {
658
+ return {
659
+ TAG: "Error",
660
+ _0: refusal
661
+ };
662
+ } else {
663
+ return {
664
+ TAG: "Ok",
665
+ _0: projects
666
+ };
667
+ }
668
+ });
649
669
  if (e.TAG !== "Ok") {
650
670
  return e;
651
671
  }
@@ -952,7 +972,8 @@ export {
952
972
  deploy,
953
973
  outputString,
954
974
  accountsFileName,
955
- oldAccountsFileName,
975
+ _accountsPath,
976
+ oldAccountsNameLeft,
956
977
  accountsFile,
957
978
  signInLines,
958
979
  up,
@@ -218,7 +218,7 @@ async function buildHandler() {
218
218
  };
219
219
  const transformedMapping = extensionMappingMake(fullMapping);
220
220
  const mappingsModule = {
221
- name: ext.name,
221
+ name: Ops.mappingsName(ext),
222
222
  moduleUrl: ext.mappingsModule,
223
223
  mappings: [transformedMapping],
224
224
  };
@@ -561,6 +561,16 @@ let extensionPublishDicts = (names: queueUrlNames, ext: extensionConfig): extens
561
561
  {publishToAggregates: resolve(ext.aggregateNames, names.publishToAggregates)}
562
562
  }
563
563
 
564
+ /** The name an extension's mappings carry, as at deploy: its plugin's. The config names the
565
+ extension component, `<extension point>.<plugin>`, and `Extension_Operations` prefixes the
566
+ extension point itself. */
567
+ let mappingsName = (ext: extensionConfig): string => {
568
+ let prefix = ext.extensionPointName ++ "."
569
+ ext.name->String.startsWith(prefix)
570
+ ? ext.name->String.slice(~start=prefix->String.length, ~end=ext.name->String.length)
571
+ : ext.name
572
+ }
573
+
564
574
  let warnSkippedExtension = (ext: extensionConfig) =>
565
575
  logWarn(
566
576
  `extension '${ext.name}' missing module specifier(s); skipping. specModule=${ext.specModule}, mappingsModule=${ext.mappingsModule}, delegateModule=${ext.delegateModule}`,
@@ -445,6 +445,15 @@ function extensionPublishDicts(names, ext) {
445
445
  };
446
446
  }
447
447
 
448
+ function mappingsName(ext) {
449
+ let prefix = ext.extensionPointName + ".";
450
+ if (ext.name.startsWith(prefix)) {
451
+ return ext.name.slice(prefix.length, ext.name.length);
452
+ } else {
453
+ return ext.name;
454
+ }
455
+ }
456
+
448
457
  function warnSkippedExtension(ext) {
449
458
  HandlerFactoryHelpersMjs.log.warn(`extension '` + ext.name + `' missing module specifier(s); skipping. specModule=` + ext.specModule + `, mappingsModule=` + ext.mappingsModule + `, delegateModule=` + ext.delegateModule, {
450
459
  comp: "EventCollectorEntryPoint"
@@ -592,6 +601,7 @@ export {
592
601
  makePublishJsons,
593
602
  buildPublishToAggregates,
594
603
  extensionPublishDicts,
604
+ mappingsName,
595
605
  warnSkippedExtension,
596
606
  assetPath,
597
607
  loadPluginDefinition,
@@ -6,6 +6,7 @@ import {
6
6
  tag as requestContextTag,
7
7
  fromOptions as makeRequestContext,
8
8
  } from "@reventlessdev/reventless-core/src/RequestContext.res.mjs";
9
+ import { currentPluginName } from "@reventlessdev/reventless-spec/src/LogPrefix.res.mjs";
9
10
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
10
11
  import { DynamoDBDocumentClient, ScanCommand } from "@aws-sdk/lib-dynamodb";
11
12
 
@@ -120,6 +121,19 @@ export function runEffect(effect, opts = {}) {
120
121
  // Only annotate a redelivery. A constant "1" on every line is noise, and its
121
122
  // absence is what makes `filter retryCount > 1` a usable query.
122
123
  if (retryCount > 1) e = e.pipe(Effect.annotateLogs("retryCount", String(retryCount)));
124
+ // A line run on a fiber of its own (`->Effect.runSync`) misses these annotations, and the
125
+ // component→plugin registry is empty in a Lambda: it names its plugin from the ambient one,
126
+ // set for the invocation and restored after it.
127
+ if (resolvedPlugin) {
128
+ const inner = e;
129
+ e = Effect.suspend(() => {
130
+ const before = currentPluginName.contents;
131
+ currentPluginName.contents = resolvedPlugin;
132
+ return inner.pipe(
133
+ Effect.ensuring(Effect.sync(() => { currentPluginName.contents = before; })),
134
+ );
135
+ });
136
+ }
123
137
  return e
124
138
  .pipe(
125
139
  Effect.provideService(
@@ -226,3 +226,23 @@ describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
226
226
  expect((c.extensions->Array.getUnsafe(0)).extensionPointName)->toBe("Ordering.Orders")
227
227
  })
228
228
  })
229
+
230
+ describe("EventCollectorEntryPoint_Ops.mappingsName", () => {
231
+ let ext = (~name): EventCollectorEntryPoint_Ops.extensionConfig => {
232
+ name,
233
+ specModule: "",
234
+ mappingsModule: "",
235
+ delegateModule: "",
236
+ extensionPointName: "Ordering.Orders",
237
+ aggregateNames: [],
238
+ }
239
+ // The component is `Extension(<point>.<name>)`: the point is not named twice.
240
+ testSync("is the plugin's name, as at deploy", () =>
241
+ expect(EventCollectorEntryPoint_Ops.mappingsName(ext(~name="Ordering.Orders.Catalog")))->toBe(
242
+ "Catalog",
243
+ )
244
+ )
245
+ testSync("keeps a name that does not start with its extension point", () =>
246
+ expect(EventCollectorEntryPoint_Ops.mappingsName(ext(~name="Catalog")))->toBe("Catalog")
247
+ )
248
+ })
@@ -315,6 +315,29 @@ globalThis.describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
315
315
  });
316
316
  });
317
317
 
318
+ globalThis.describe("EventCollectorEntryPoint_Ops.mappingsName", () => {
319
+ globalThis.test("is the plugin's name, as at deploy", () => {
320
+ globalThis.expect(EventCollectorEntryPoint_Ops$ReventlessAws.mappingsName({
321
+ name: "Ordering.Orders.Catalog",
322
+ specModule: "",
323
+ mappingsModule: "",
324
+ delegateModule: "",
325
+ extensionPointName: "Ordering.Orders",
326
+ aggregateNames: []
327
+ })).toBe("Catalog");
328
+ });
329
+ globalThis.test("keeps a name that does not start with its extension point", () => {
330
+ globalThis.expect(EventCollectorEntryPoint_Ops$ReventlessAws.mappingsName({
331
+ name: "Catalog",
332
+ specModule: "",
333
+ mappingsModule: "",
334
+ delegateModule: "",
335
+ extensionPointName: "Ordering.Orders",
336
+ aggregateNames: []
337
+ })).toBe("Catalog");
338
+ });
339
+ });
340
+
318
341
  let project = EventCollectorEntryPoint_Ops$ReventlessAws.projectPluginRow;
319
342
 
320
343
  export {
@@ -200,6 +200,23 @@ describe("entry-point dispatch boundary", () => {
200
200
  },
201
201
  )
202
202
 
203
+ // A line run on a fiber of its own (`->Effect.runSync`) misses the invocation's annotations,
204
+ // and a Lambda's component→plugin registry is empty: the ambient plugin names it.
205
+ test(
206
+ "a line logged on a fiber of its own names the invocation's plugin",
207
+ async () => {
208
+ let before = Reventless.LogPrefix.currentPluginName.contents
209
+ let seen = await runEffect(
210
+ Effect.sync(
211
+ () => Reventless.LogPrefix.resolvePlugin(~comp="StateChangeSlice(PlaceOrder)", ()),
212
+ ),
213
+ {correlationId: "c", comp: "StateChangeSlice(PlaceOrder)", plugin: "Ordering"},
214
+ )
215
+ expect(seen)->Expect.toEqual(Some("Ordering"))
216
+ expect(Reventless.LogPrefix.currentPluginName.contents)->Expect.toEqual(before)
217
+ },
218
+ )
219
+
203
220
  // A constant "1" on every line is noise; its absence is what makes
204
221
  // `filter retryCount > 1` a usable CloudWatch query.
205
222
  test(
@@ -3,6 +3,7 @@
3
3
  import * as Effect from "@reventlessdev/rescript-effect/src/Effect.res.mjs";
4
4
  import * as Effect$1 from "effect/Effect";
5
5
  import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
6
+ import * as LogPrefix$Reventless from "@reventlessdev/reventless-spec/src/LogPrefix.res.mjs";
6
7
  import * as RequestContext$ReventlessCore from "@reventlessdev/reventless-core/src/RequestContext.res.mjs";
7
8
  import * as HandlerFactoryHelpersMjs from "@reventlessdev/reventless-aws/src/adapter/Runtime/HandlerFactoryHelpers.mjs";
8
9
 
@@ -112,6 +113,16 @@ globalThis.describe("entry-point dispatch boundary", () => {
112
113
  globalThis.expect(annotated$1["correlationId"]).toEqual("corr-1");
113
114
  globalThis.expect(annotated$1["requestId"]).toEqual("req-abc");
114
115
  });
116
+ globalThis.test("a line logged on a fiber of its own names the invocation's plugin", async () => {
117
+ let before = LogPrefix$Reventless.currentPluginName.contents;
118
+ let seen = await HandlerFactoryHelpersMjs.runEffect(Effect$1.sync(() => LogPrefix$Reventless.resolvePlugin("StateChangeSlice(PlaceOrder)", undefined)), {
119
+ correlationId: "c",
120
+ comp: "StateChangeSlice(PlaceOrder)",
121
+ plugin: "Ordering"
122
+ });
123
+ globalThis.expect(seen).toEqual("Ordering");
124
+ globalThis.expect(LogPrefix$Reventless.currentPluginName.contents).toEqual(before);
125
+ });
115
126
  globalThis.test("annotates retryCount only on a redelivery", async () => {
116
127
  let first = await HandlerFactoryHelpersMjs.runEffect(Effect$1.logAnnotations, {
117
128
  correlationId: "c",