@reventlessdev/reventless-aws 3.0.0-alpha.332 → 3.0.0-alpha.333

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.333 (2026-09-02)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **automation:** the slice registry rides in the archive, not the environment ([d03b42c](https://github.com/ReventlessDev/reventless-core/commit/d03b42cc074181fb6506f00e40205d617bc70ddb))
11
+
12
+
6
13
  # 3.0.0-alpha.332 (2026-09-01)
7
14
 
8
15
  **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.332",
3
+ "version": "3.0.0-alpha.333",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -20,12 +20,12 @@
20
20
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
21
21
  "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.7",
22
22
  "@reventlessdev/rescript-node": "2.0.0-alpha.8",
23
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
24
23
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
24
+ "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
25
25
  "@reventlessdev/reventless-core": "3.0.0-alpha.253",
26
26
  "@reventlessdev/reventless-infra": "3.0.0-alpha.154",
27
- "@reventlessdev/reventless-interop": "3.0.0-alpha.34",
28
27
  "@reventlessdev/reventless-postgres": "3.0.0-alpha.117",
28
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.34",
29
29
  "@reventlessdev/reventless-spec": "3.0.0-alpha.126"
30
30
  },
31
31
  "devDependencies": {
@@ -278,9 +278,11 @@ let finish = () =>
278
278
 
279
279
  let handlerConfigOutput =
280
280
  Pulumi.Output.all(handlerOutputs)
281
- ->Pulumi.Output.apply(handlers =>
282
- `{"handlers":[${handlers->Array.join(",")}]}`
283
- )
281
+ ->Pulumi.Output.apply(handlers => {
282
+ let json = `{"handlers":[${handlers->Array.join(",")}]}`
283
+ Util_LambdaEnvBudget.check(~lambdaName="AllAggregatesCmdHandler", ~handlerConfigJson=json)
284
+ json
285
+ })
284
286
 
285
287
  let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
286
288
  envVars->Dict.set("HANDLER_CONFIG", handlerConfigOutput->Pulumi.Output.asInput)
@@ -14,6 +14,7 @@ import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
14
14
  import * as PgConnection$ReventlessAws from "../Postgres/PgConnection.res.mjs";
15
15
  import * as EventLogBackend$ReventlessAws from "../EventLog/EventLogBackend.res.mjs";
16
16
  import * as Util_LogAttribution$ReventlessAws from "../../util/Util_LogAttribution.res.mjs";
17
+ import * as Util_LambdaEnvBudget$ReventlessAws from "../../util/Util_LambdaEnvBudget.res.mjs";
17
18
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
18
19
  import * as CommandTopicChannel_SQS_Sync$ReventlessAws from "../CommandTopic/CommandTopicChannel_SQS_Sync.res.mjs";
19
20
  import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
@@ -184,7 +185,11 @@ function finish() {
184
185
  ]).apply(param => `{"specModule":` + specModule + `,"behaviorModule":` + behaviorModule + `,"eventLogTable":"` + param[0] + `","queueUrl":"` + param[1] + `","queueArn":"` + param[2] + `"` + pluginFragment + param[3] + `}`);
185
186
  handlerOutputs.push(handlerJson);
186
187
  });
187
- let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => `{"handlers":[` + handlers.join(",") + `]}`);
188
+ let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => {
189
+ let json = `{"handlers":[` + handlers.join(",") + `]}`;
190
+ Util_LambdaEnvBudget$ReventlessAws.check("AllAggregatesCmdHandler", json, undefined);
191
+ return json;
192
+ });
188
193
  let envVars = {};
189
194
  envVars["HANDLER_CONFIG"] = handlerConfigOutput;
190
195
  let tableName = pluginRmTableName.contents;
@@ -18,9 +18,21 @@ import { Make as outboundTranslationSliceCallbackMake } from "@reventlessdev/rev
18
18
  import * as StreamOps from "./StreamRoutedEntryPoint_Ops.res.mjs";
19
19
  import * as Ops from "./AutomationSliceEntryPoint_Ops.res.mjs";
20
20
  import { runtimeExtensionsReady } from "./HandlerFactoryHelpers.mjs";
21
+ import { readFileSync } from "node:fs";
22
+ import { join } from "node:path";
21
23
 
22
24
  const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
23
25
 
26
+ // Absent whenever the config is full-key (tests, and any pre-split deployment),
27
+ // which is why a missing file is not an error.
28
+ const readSliceModules = () => {
29
+ try {
30
+ return readFileSync(join(process.cwd(), "automationSliceModules.json"), "utf-8");
31
+ } catch {
32
+ return "";
33
+ }
34
+ };
35
+
24
36
  async function buildAllHandlers() {
25
37
  // Runtime extension seam: any registered out-of-tree extension gets its
26
38
  // onColdStart before a single handler is built, which is what makes the
@@ -29,7 +41,15 @@ async function buildAllHandlers() {
29
41
  await runtimeExtensionsReady;
30
42
  const handlers = {};
31
43
  const sweeps = [];
32
- const entries = Ops.parseHandlerConfig(process.env["HANDLER_CONFIG"] || "");
44
+ // The slice registry rides in the archive, not the environment — two module
45
+ // specifiers per slice outgrow Lambda's 4KB env-var ceiling (see the asset in
46
+ // AutomationSliceRuntime_Builder_Single). A full-key config still wins, so a
47
+ // caller that supplies one — every test that drives a config directly — needs
48
+ // no asset on disk. process.cwd() is /var/task, where the archive unpacks.
49
+ const entries = Ops.parseHandlerConfigWithModules(
50
+ readSliceModules(),
51
+ process.env["HANDLER_CONFIG"] || "",
52
+ );
33
53
 
34
54
  await Promise.all(entries.map(async (entry) => {
35
55
  if (!entry.bodyModule) {
@@ -62,16 +62,83 @@ let decodeEntry = (json: JSON.t): option<handlerEntry> =>
62
62
  context: h->Dict.get("context")->Option.flatMap(decodeContext),
63
63
  })
64
64
 
65
- let parseHandlerConfig = (rawJson: string): array<handlerEntry> =>
65
+ // A compact entry names its slice and carries only what the deploy resolved;
66
+ // the module paths and context live in the archive registry, keyed by the same
67
+ // name (AutomationSliceRuntime_Builder_Single). Shared `queueUrl` /
68
+ // `commandQueueIsFifo` / `urns` are hoisted out of the per-entry keys, since a
69
+ // slice publishing to the DCB fallback and reading its plugin's log repeats
70
+ // both verbatim — which is what outgrew the 4KB env limit.
71
+ let decodeCompactEntry = (
72
+ ~modules: dict<JSON.t>,
73
+ ~sharedQueueUrl: string,
74
+ ~sharedIsFifo: option<bool>,
75
+ ~sharedUrns: array<string>,
76
+ h: dict<JSON.t>,
77
+ name: string,
78
+ ): handlerEntry => {
79
+ let m = modules->Dict.get(name)->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
80
+ let urns =
81
+ h
82
+ ->Dict.get("u")
83
+ ->Option.flatMap(JSON.Decode.array)
84
+ ->Option.map(a => a->Array.filterMap(JSON.Decode.string))
85
+ ->Option.getOr(sharedUrns)
86
+ {
87
+ specModule: m->strOf("specModule")->Option.getOr(""),
88
+ bodyModule: m->strOf("bodyModule")->Option.getOr(""),
89
+ callbackType: m->strOf("callbackType")->Option.getOr("automation"),
90
+ queryDbTableName: h->strOf("q")->Option.getOr(""),
91
+ dcbQueueUrl: h->strOf("k")->Option.getOr(sharedQueueUrl),
92
+ commandQueueIsFifo: switch h->Dict.get("f")->Option.flatMap(JSON.Decode.bool) {
93
+ | Some(f) => Some(f)
94
+ | None => sharedIsFifo
95
+ },
96
+ // Only the first, which is all the shell registers today. The remaining
97
+ // urns stay in the config for the multi-source registration to use.
98
+ sourceUrn: urns->Array.get(0)->Option.getOr(""),
99
+ context: m->Dict.get("context")->Option.flatMap(decodeContext),
100
+ }
101
+ }
102
+
103
+ /** `modulesJson` is the archive-side slice registry (`""` when there is none);
104
+ an entry carrying its own `specModule` is a full-key config and needs it. */
105
+ let parseHandlerConfigWithModules = (modulesJson: string, rawJson: string): array<handlerEntry> =>
66
106
  rawJson == ""
67
107
  ? []
68
- : rawJson
69
- ->JSON.parseOrThrow
70
- ->JSON.Decode.object
71
- ->Option.flatMap(obj => obj->Dict.get("handlers"))
72
- ->Option.flatMap(JSON.Decode.array)
73
- ->Option.getOr([])
74
- ->Array.filterMap(decodeEntry)
108
+ : {
109
+ let obj = rawJson->JSON.parseOrThrow->JSON.Decode.object->Option.getOr(Dict.make())
110
+ let modules =
111
+ modulesJson == ""
112
+ ? Dict.make()
113
+ : modulesJson->JSON.parseOrThrow->JSON.Decode.object->Option.getOr(Dict.make())
114
+ let sharedQueueUrl = obj->strOf("queueUrl")->Option.getOr("")
115
+ let sharedIsFifo = obj->Dict.get("commandQueueIsFifo")->Option.flatMap(JSON.Decode.bool)
116
+ let sharedUrns =
117
+ obj
118
+ ->Dict.get("urns")
119
+ ->Option.flatMap(JSON.Decode.array)
120
+ ->Option.map(a => a->Array.filterMap(JSON.Decode.string))
121
+ ->Option.getOr([])
122
+ obj
123
+ ->Dict.get("handlers")
124
+ ->Option.flatMap(JSON.Decode.array)
125
+ ->Option.getOr([])
126
+ ->Array.filterMap(item =>
127
+ switch item->JSON.Decode.object {
128
+ | None => None
129
+ | Some(h) =>
130
+ switch h->strOf("n") {
131
+ | Some(name) =>
132
+ Some(decodeCompactEntry(~modules, ~sharedQueueUrl, ~sharedIsFifo, ~sharedUrns, h, name))
133
+ | None => decodeEntry(item)
134
+ }
135
+ }
136
+ )
137
+ }
138
+
139
+ /** The full-key form, for a config that carries its own module paths. */
140
+ let parseHandlerConfig = (rawJson: string): array<handlerEntry> =>
141
+ parseHandlerConfigWithModules("", rawJson)
75
142
 
76
143
  // A pre-`bodyModule` config entry cannot rebuild the callback (the spec module
77
144
  // alone lacks the mappings/process). Warn and skip rather than crash the whole
@@ -49,12 +49,47 @@ function decodeEntry(json) {
49
49
  }));
50
50
  }
51
51
 
52
- function parseHandlerConfig(rawJson) {
52
+ function decodeCompactEntry(modules, sharedQueueUrl, sharedIsFifo, sharedUrns, h, name) {
53
+ let m = Stdlib_Option.getOr(Stdlib_Option.flatMap(modules[name], Stdlib_JSON.Decode.object), {});
54
+ let urns = Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_Option.flatMap(h["u"], Stdlib_JSON.Decode.array), a => Stdlib_Array.filterMap(a, Stdlib_JSON.Decode.string)), sharedUrns);
55
+ let f = Stdlib_Option.flatMap(h["f"], Stdlib_JSON.Decode.bool);
56
+ return {
57
+ specModule: Stdlib_Option.getOr(strOf(m, "specModule"), ""),
58
+ bodyModule: Stdlib_Option.getOr(strOf(m, "bodyModule"), ""),
59
+ callbackType: Stdlib_Option.getOr(strOf(m, "callbackType"), "automation"),
60
+ queryDbTableName: Stdlib_Option.getOr(strOf(h, "q"), ""),
61
+ dcbQueueUrl: Stdlib_Option.getOr(strOf(h, "k"), sharedQueueUrl),
62
+ commandQueueIsFifo: f !== undefined ? f : sharedIsFifo,
63
+ sourceUrn: Stdlib_Option.getOr(urns[0], ""),
64
+ context: Stdlib_Option.flatMap(m["context"], decodeContext)
65
+ };
66
+ }
67
+
68
+ function parseHandlerConfigWithModules(modulesJson, rawJson) {
53
69
  if (rawJson === "") {
54
70
  return [];
55
- } else {
56
- return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(JSON.parse(rawJson)), obj => obj["handlers"]), Stdlib_JSON.Decode.array), []), decodeEntry);
57
71
  }
72
+ let obj = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(rawJson)), {});
73
+ let modules = modulesJson === "" ? ({}) : Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(modulesJson)), {});
74
+ let sharedQueueUrl = Stdlib_Option.getOr(strOf(obj, "queueUrl"), "");
75
+ let sharedIsFifo = Stdlib_Option.flatMap(obj["commandQueueIsFifo"], Stdlib_JSON.Decode.bool);
76
+ let sharedUrns = Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_Option.flatMap(obj["urns"], Stdlib_JSON.Decode.array), a => Stdlib_Array.filterMap(a, Stdlib_JSON.Decode.string)), []);
77
+ return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(obj["handlers"], Stdlib_JSON.Decode.array), []), item => {
78
+ let h = Stdlib_JSON.Decode.object(item);
79
+ if (h === undefined) {
80
+ return;
81
+ }
82
+ let name = strOf(h, "n");
83
+ if (name !== undefined) {
84
+ return decodeCompactEntry(modules, sharedQueueUrl, sharedIsFifo, sharedUrns, h, name);
85
+ } else {
86
+ return decodeEntry(item);
87
+ }
88
+ });
89
+ }
90
+
91
+ function parseHandlerConfig(rawJson) {
92
+ return parseHandlerConfigWithModules("", rawJson);
58
93
  }
59
94
 
60
95
  function warnMissingBodyModule(entry) {
@@ -247,6 +282,8 @@ export {
247
282
  strOf,
248
283
  decodeContext,
249
284
  decodeEntry,
285
+ decodeCompactEntry,
286
+ parseHandlerConfigWithModules,
250
287
  parseHandlerConfig,
251
288
  warnMissingBodyModule,
252
289
  defaultContext,
@@ -542,6 +542,12 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
542
542
  }
543
543
 
544
544
  let handlerOutputs: array<Pulumi.Output.t<string>> = []
545
+ // The static half of each slice's config — module paths and context, all
546
+ // known synchronously. Rides in the code archive rather than the
547
+ // environment: two module specifiers per slice is the term that grows
548
+ // with the plugin, and it is what pushed this Lambda past AWS's 4KB env
549
+ // limit. Keyed by slice name, which is what the compact env entry names.
550
+ let sliceModuleEntries: array<string> = []
545
551
  let packageDirs: dict<string> = Dict.make()
546
552
  let allQueryDbResources: array<ReventlessInfra.Adapter.resource> = []
547
553
  // Whether any slice publishes to the DCB fallback rather than a target
@@ -549,7 +555,7 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
549
555
  // too, once, below the loop.
550
556
  let anyDcbFallback = ref(false)
551
557
 
552
- bundledInfos->Dict.forEachWithKey((info, _name) => {
558
+ bundledInfos->Dict.forEachWithKey((info, name) => {
553
559
  info.queryDbResources->Array.forEach(r => allQueryDbResources->Array.push(r)->ignore)
554
560
  // The queue this slice publishes its command to. An Aggregate registers
555
561
  // its CommandTopic while plugin construction is still synchronous —
@@ -582,6 +588,12 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
582
588
  | None => ""
583
589
  }
584
590
 
591
+ let nameJson = name->JSON.stringifyAny->Option.getOr(`""`)
592
+ let _ =
593
+ sliceModuleEntries->Array.push(
594
+ `${nameJson}:{"specModule":${specModule},"bodyModule":${bodyModule},"callbackType":${callbackType}${contextFragment}}`,
595
+ )
596
+
585
597
  // Per slice, not plugin-wide: an outbound slice targeting an Aggregate
586
598
  // publishes to that aggregate's CommandTopic. Everything else keeps the
587
599
  // DCB topic, which is what the field has always meant — and with it the
@@ -590,17 +602,23 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
590
602
  | Some({queueUrl, isFifo}) => (queueUrl, isFifo)
591
603
  | None => (dcbQueueUrl, dcbQueueIsFifo.contents)
592
604
  }
605
+ // What each slice may override is decided structurally, not from the
606
+ // resolved values: a slice listening only on the plugin's DCB log, and
607
+ // publishing to the DCB fallback, repeats the shared urn and queue URL
608
+ // exactly — so it emits neither and inherits both.
609
+ let hasOwnUrns =
610
+ !(info.consumesDcbLog) || info.sourceTopics->Dict.keysToArray->Array.length > 0
593
611
  let handlerJson =
594
612
  Pulumi.Output.all3((info.queryDbTableName, commandQueueUrl, sourceUrnsFor(info)))
595
613
  ->Pulumi.Output.apply(((tableName, queueUrl, urns)) => {
596
- let urnsJson =
597
- urns->Array.map(JSON.Encode.string)->JSON.Encode.array->JSON.stringify
598
- // `sourceUrn` stays beside `sourceUrns` so a handler config read by
599
- // an entry point that predates multi-source still routes its first
600
- // stream rather than none.
601
- let firstUrn = urns->Array.get(0)->Option.getOr("")
602
- let fifoJson = commandQueueIsFifo ? "true" : "false"
603
- `{"specModule":${specModule},"bodyModule":${bodyModule},"callbackType":${callbackType},"queryDbTableName":"${tableName}","dcbQueueUrl":"${queueUrl}","commandQueueIsFifo":${fifoJson},"sourceUrn":"${firstUrn}","sourceUrns":${urnsJson}${contextFragment}}`
614
+ let urnsFragment = hasOwnUrns
615
+ ? `,"u":${urns->Array.map(JSON.Encode.string)->JSON.Encode.array->JSON.stringify}`
616
+ : ""
617
+ let queueFragment = switch target {
618
+ | Some(_) => `,"k":"${queueUrl}","f":${commandQueueIsFifo ? "true" : "false"}`
619
+ | None => ""
620
+ }
621
+ `{"n":${nameJson},"q":"${tableName}"${urnsFragment}${queueFragment}}`
604
622
  })
605
623
  let _ = handlerOutputs->Array.push(handlerJson)
606
624
  })
@@ -613,9 +631,24 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
613
631
  dcbQueueResources->Array.forEach(r => allQueryDbResources->Array.push(r)->ignore)
614
632
  }
615
633
 
634
+ // The defaults every non-overriding slice inherits, carried once.
635
+ let sharedOutput =
636
+ Pulumi.Output.all2((dcbQueueUrl, dcbSourceUrn))->Pulumi.Output.apply(((
637
+ queueUrl,
638
+ urn,
639
+ )) => {
640
+ let fifoJson = dcbQueueIsFifo.contents ? "true" : "false"
641
+ let urnJson = urn->JSON.stringifyAny->Option.getOr(`""`)
642
+ `"queueUrl":"${queueUrl}","commandQueueIsFifo":${fifoJson},"urns":[${urnJson}]`
643
+ })
644
+
616
645
  let handlerConfigOutput =
617
- Pulumi.Output.all(handlerOutputs)
618
- ->Pulumi.Output.apply(handlers => `{"handlers":[${handlers->Array.join(",")}]}`)
646
+ Pulumi.Output.all2((sharedOutput, Pulumi.Output.all(handlerOutputs)))
647
+ ->Pulumi.Output.apply(((shared, handlers)) => {
648
+ let json = `{${shared},"handlers":[${handlers->Array.join(",")}]}`
649
+ Util_LambdaEnvBudget.check(~lambdaName="AllAutomationSlices", ~handlerConfigJson=json)
650
+ json
651
+ })
619
652
 
620
653
  let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
621
654
  envVars->Dict.set("HANDLER_CONFIG", handlerConfigOutput->Pulumi.Output.asInput)
@@ -630,9 +663,18 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
630
663
  envVars->Dict.set(name, value->Pulumi.Output.asInput)
631
664
  )
632
665
 
666
+ // Carries no Pulumi Output, so the archive is still built synchronously —
667
+ // the table names and urns that do are what stays in HANDLER_CONFIG.
668
+ let extraStringAssets = Dict.make()
669
+ extraStringAssets->Dict.set(
670
+ "automationSliceModules.json",
671
+ `{${sliceModuleEntries->Array.join(",")}}`,
672
+ )
673
+
633
674
  let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
634
675
  ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/AutomationSliceEntryPoint.mjs",
635
676
  ~packageDirs,
677
+ ~extraStringAssets,
636
678
  )
637
679
 
638
680
  let runtime = RuntimeEnvironment_Lambda.makeFromCodeAsset(
@@ -18,6 +18,7 @@ import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/
18
18
  import * as Cloudwatch_EventRule$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Cloudwatch/Cloudwatch_EventRule.res.mjs";
19
19
  import * as Cloudwatch_EventTarget$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Cloudwatch/Cloudwatch_EventTarget.res.mjs";
20
20
  import * as CommandTopicRegistry$ReventlessAws from "../CommandTopic/CommandTopicRegistry.res.mjs";
21
+ import * as Util_LambdaEnvBudget$ReventlessAws from "../../util/Util_LambdaEnvBudget.res.mjs";
21
22
  import * as PluginRuntime_Builder$ReventlessAws from "../../plugin/runtime/PluginRuntime_Builder.res.mjs";
22
23
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
23
24
  import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
@@ -279,12 +280,13 @@ function finishWithDcbEventLog(dcbEventLog) {
279
280
  let url = dcbQueueUrlRef.contents;
280
281
  let dcbQueueUrl = url !== undefined ? url : Pulumi.output("NOT_AVAILABLE");
281
282
  let handlerOutputs = [];
283
+ let sliceModuleEntries = [];
282
284
  let packageDirs = {};
283
285
  let allQueryDbResources = [];
284
286
  let anyDcbFallback = {
285
287
  contents: false
286
288
  };
287
- Stdlib_Dict.forEachWithKey(bundledInfos, (info, _name) => {
289
+ Stdlib_Dict.forEachWithKey(bundledInfos, (info, name) => {
288
290
  info.queryDbResources.forEach(r => {
289
291
  allQueryDbResources.push(r);
290
292
  });
@@ -303,6 +305,8 @@ function finishWithDcbEventLog(dcbEventLog) {
303
305
  let callbackType = Stdlib_Option.getOr(JSON.stringify(info.callbackType), `""`);
304
306
  let context = info.context;
305
307
  let contextFragment = context !== undefined ? Stdlib_Option.getOr(Stdlib_Option.map(JSON.stringify(context), json => `,"context":` + json), "") : "";
308
+ let nameJson = Stdlib_Option.getOr(JSON.stringify(name), `""`);
309
+ sliceModuleEntries.push(nameJson + `:{"specModule":` + specModule + `,"bodyModule":` + bodyModule + `,"callbackType":` + callbackType + contextFragment + `}`);
306
310
  let match = target !== undefined ? [
307
311
  target.queueUrl,
308
312
  target.isFifo
@@ -311,16 +315,17 @@ function finishWithDcbEventLog(dcbEventLog) {
311
315
  dcbQueueIsFifo.contents
312
316
  ];
313
317
  let commandQueueIsFifo = match[1];
318
+ let hasOwnUrns = !info.consumesDcbLog || Object.keys(info.sourceTopics).length !== 0;
314
319
  let handlerJson = Pulumi.all([
315
320
  info.queryDbTableName,
316
321
  match[0],
317
322
  sourceUrnsFor(info)
318
323
  ]).apply(param => {
319
- let urns = param[2];
320
- let urnsJson = JSON.stringify(urns.map(prim => prim));
321
- let firstUrn = Stdlib_Option.getOr(urns[0], "");
322
- let fifoJson = commandQueueIsFifo ? "true" : "false";
323
- return `{"specModule":` + specModule + `,"bodyModule":` + bodyModule + `,"callbackType":` + callbackType + `,"queryDbTableName":"` + param[0] + `","dcbQueueUrl":"` + param[1] + `","commandQueueIsFifo":` + fifoJson + `,"sourceUrn":"` + firstUrn + `","sourceUrns":` + urnsJson + contextFragment + `}`;
324
+ let urnsFragment = hasOwnUrns ? `,"u":` + JSON.stringify(param[2].map(prim => prim)) : "";
325
+ let queueFragment = target !== undefined ? `,"k":"` + param[1] + `","f":` + (
326
+ commandQueueIsFifo ? "true" : "false"
327
+ ) : "";
328
+ return `{"n":` + nameJson + `,"q":"` + param[0] + `"` + urnsFragment + queueFragment + `}`;
324
329
  });
325
330
  handlerOutputs.push(handlerJson);
326
331
  });
@@ -329,13 +334,30 @@ function finishWithDcbEventLog(dcbEventLog) {
329
334
  allQueryDbResources.push(r);
330
335
  });
331
336
  }
332
- let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => `{"handlers":[` + handlers.join(",") + `]}`);
337
+ let sharedOutput = Pulumi.all([
338
+ dcbQueueUrl,
339
+ dcbSourceUrn
340
+ ]).apply(param => {
341
+ let fifoJson = dcbQueueIsFifo.contents ? "true" : "false";
342
+ let urnJson = Stdlib_Option.getOr(JSON.stringify(param[1]), `""`);
343
+ return `"queueUrl":"` + param[0] + `","commandQueueIsFifo":` + fifoJson + `,"urns":[` + urnJson + `]`;
344
+ });
345
+ let handlerConfigOutput = Pulumi.all([
346
+ sharedOutput,
347
+ Pulumi.all(handlerOutputs)
348
+ ]).apply(param => {
349
+ let json = `{` + param[0] + `,"handlers":[` + param[1].join(",") + `]}`;
350
+ Util_LambdaEnvBudget$ReventlessAws.check("AllAutomationSlices", json, undefined);
351
+ return json;
352
+ });
333
353
  let envVars = {};
334
354
  envVars["HANDLER_CONFIG"] = handlerConfigOutput;
335
355
  PluginRuntime_Builder$ReventlessAws.capabilityEnv().forEach(param => {
336
356
  envVars[param[0]] = param[1];
337
357
  });
338
- let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AutomationSliceEntryPoint.mjs", packageDirs, undefined, undefined);
358
+ let extraStringAssets = {};
359
+ extraStringAssets["automationSliceModules.json"] = `{` + sliceModuleEntries.join(",") + `}`;
360
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AutomationSliceEntryPoint.mjs", packageDirs, extraStringAssets, undefined);
339
361
  let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAutomationSlices", "Reactor", "AutomationSlice", match.code, match.sourceCodeHash, envVars, 1024, 30, undefined, undefined, undefined, undefined, undefined, opts);
340
362
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllAutomationSlices", [{
341
363
  channel: channel,
@@ -251,9 +251,11 @@ let finish = () =>
251
251
 
252
252
  let handlerConfigOutput =
253
253
  Pulumi.Output.all(handlerOutputs)
254
- ->Pulumi.Output.apply(handlers =>
255
- `{"handlers":[${handlers->Array.join(",")}]}`
256
- )
254
+ ->Pulumi.Output.apply(handlers => {
255
+ let json = `{"handlers":[${handlers->Array.join(",")}]}`
256
+ Util_LambdaEnvBudget.check(~lambdaName="AllReadModels", ~handlerConfigJson=json)
257
+ json
258
+ })
257
259
 
258
260
  let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
259
261
  envVars->Dict.set("HANDLER_CONFIG", handlerConfigOutput->Pulumi.Output.asInput)
@@ -18,6 +18,7 @@ import * as EventLogBackend$ReventlessAws from "../EventLog/EventLogBackend.res.
18
18
  import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
19
19
  import * as PgProjectionFeed$ReventlessAws from "../Postgres/PgProjectionFeed.res.mjs";
20
20
  import * as Util_LogAttribution$ReventlessAws from "../../util/Util_LogAttribution.res.mjs";
21
+ import * as Util_LambdaEnvBudget$ReventlessAws from "../../util/Util_LambdaEnvBudget.res.mjs";
21
22
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
22
23
  import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
23
24
 
@@ -142,7 +143,11 @@ function finish() {
142
143
  });
143
144
  handlerOutputs.push(handlerJson);
144
145
  });
145
- let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => `{"handlers":[` + handlers.join(",") + `]}`);
146
+ let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => {
147
+ let json = `{"handlers":[` + handlers.join(",") + `]}`;
148
+ Util_LambdaEnvBudget$ReventlessAws.check("AllReadModels", json, undefined);
149
+ return json;
150
+ });
146
151
  let envVars = {};
147
152
  envVars["HANDLER_CONFIG"] = handlerConfigOutput;
148
153
  let anyPgStream = Object.keys(readModelInfos).some(rmName => {
@@ -153,9 +153,11 @@ let finish = () =>
153
153
 
154
154
  let handlerConfigOutput =
155
155
  Pulumi.Output.all(handlerOutputs)
156
- ->Pulumi.Output.apply(handlers =>
157
- `{"handlers":[${handlers->Array.join(",")}]}`
158
- )
156
+ ->Pulumi.Output.apply(handlers => {
157
+ let json = `{"handlers":[${handlers->Array.join(",")}]}`
158
+ Util_LambdaEnvBudget.check(~lambdaName="AllSideEffectHandlers", ~handlerConfigJson=json)
159
+ json
160
+ })
159
161
 
160
162
  let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
161
163
  envVars->Dict.set("HANDLER_CONFIG", handlerConfigOutput->Pulumi.Output.asInput)
@@ -11,6 +11,7 @@ import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/co
11
11
  import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
12
12
  import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
13
13
  import * as Util_LogAttribution$ReventlessAws from "../../util/Util_LogAttribution.res.mjs";
14
+ import * as Util_LambdaEnvBudget$ReventlessAws from "../../util/Util_LambdaEnvBudget.res.mjs";
14
15
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
15
16
  import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
16
17
 
@@ -109,7 +110,11 @@ function finish() {
109
110
  });
110
111
  handlerOutputs.push(handlerJson);
111
112
  });
112
- let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => `{"handlers":[` + handlers.join(",") + `]}`);
113
+ let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => {
114
+ let json = `{"handlers":[` + handlers.join(",") + `]}`;
115
+ Util_LambdaEnvBudget$ReventlessAws.check("AllSideEffectHandlers", json, undefined);
116
+ return json;
117
+ });
113
118
  let envVars = {};
114
119
  envVars["HANDLER_CONFIG"] = handlerConfigOutput;
115
120
  Stdlib_Dict.forEachWithKey(extraEnvVarsAll, (v, k) => {
@@ -222,7 +222,9 @@ let buildLambda = (
222
222
  ->Array.join(",")
223
223
  let baseJson = base->JSON.stringifyAny->Option.getOr(`""`)
224
224
  let urnJson = sharedUrn->JSON.stringifyAny->Option.getOr(`""`)
225
- `{"v":2,"base":${baseJson},"sourceUrn":${urnJson},"handlers":[${entries}]${pgFragment}}`
225
+ let json = `{"v":2,"base":${baseJson},"sourceUrn":${urnJson},"handlers":[${entries}]${pgFragment}}`
226
+ Util_LambdaEnvBudget.check(~lambdaName="AllStateViewSlices", ~handlerConfigJson=json)
227
+ json
226
228
  })
227
229
 
228
230
  let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
@@ -18,6 +18,7 @@ import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/
18
18
  import * as QueryDbBackend$ReventlessAws from "../QueryDb/QueryDbBackend.res.mjs";
19
19
  import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
20
20
  import * as PgProjectionFeed$ReventlessAws from "../Postgres/PgProjectionFeed.res.mjs";
21
+ import * as Util_LambdaEnvBudget$ReventlessAws from "../../util/Util_LambdaEnvBudget.res.mjs";
21
22
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
22
23
  import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
23
24
 
@@ -144,7 +145,9 @@ function buildLambda(parent, handlerOutputs, packageDirs, channelSpecs, feedQueu
144
145
  }).join(",");
145
146
  let baseJson = Stdlib_Option.getOr(JSON.stringify(base), `""`);
146
147
  let urnJson = Stdlib_Option.getOr(JSON.stringify(sharedUrn), `""`);
147
- return `{"v":2,"base":` + baseJson + `,"sourceUrn":` + urnJson + `,"handlers":[` + entries + `]` + param[1] + `}`;
148
+ let json = `{"v":2,"base":` + baseJson + `,"sourceUrn":` + urnJson + `,"handlers":[` + entries + `]` + param[1] + `}`;
149
+ Util_LambdaEnvBudget$ReventlessAws.check("AllStateViewSlices", json, undefined);
150
+ return json;
148
151
  });
149
152
  let envVars = {};
150
153
  envVars["HANDLER_CONFIG"] = handlerConfigOutput;
@@ -83,6 +83,128 @@ describe("AutomationSliceEntryPoint_Ops.parseHandlerConfig", () => {
83
83
  })
84
84
  })
85
85
 
86
+ // The compact form the builder emits: module paths and context ride in the
87
+ // archive registry, and the DCB queue + source urn are hoisted out of every
88
+ // entry that does not override them. Together these are what keep the Lambda's
89
+ // environment under AWS's 4KB ceiling as a plugin gains slices.
90
+ describe("AutomationSliceEntryPoint_Ops.parseHandlerConfigWithModules", () => {
91
+ let modules =
92
+ obj([
93
+ (
94
+ "Restock",
95
+ obj([
96
+ ("specModule", str("@x/plugin/src/Order/AutomationSlice/Restock.res.mjs")),
97
+ ("bodyModule", str("@x/plugin/src/Order/AutomationSlice/Restock_Automation.res.mjs")),
98
+ ("callbackType", str("automation")),
99
+ (
100
+ "context",
101
+ obj([
102
+ ("environment", str("alpha")),
103
+ ("platformName", str("OnlineShop")),
104
+ ("pluginName", str("Ordering")),
105
+ ("sliceName", str("Restock")),
106
+ ]),
107
+ ),
108
+ ]),
109
+ ),
110
+ (
111
+ "Ship",
112
+ obj([
113
+ ("specModule", str("@x/plugin/src/Order/OutboundTranslationSlice/Ship.res.mjs")),
114
+ ("bodyModule", str("@x/plugin/src/Order/OutboundTranslationSlice/Ship_Translation.res.mjs")),
115
+ ("callbackType", str("outbound")),
116
+ ]),
117
+ ),
118
+ ])->JSON.stringify
119
+
120
+ let compactConfig = handlers =>
121
+ obj([
122
+ ("queueUrl", str("https://sqs/dcb")),
123
+ ("commandQueueIsFifo", JSON.Encode.bool(false)),
124
+ ("urns", JSON.Encode.array([str("arn:dcb:log")])),
125
+ ("handlers", JSON.Encode.array(handlers)),
126
+ ])->JSON.stringify
127
+
128
+ testSync("a compact entry takes its modules from the registry and the shared defaults", () => {
129
+ let config = compactConfig([obj([("n", str("Restock")), ("q", str("RestockTodo-abc"))])])
130
+ let e =
131
+ AutomationSliceEntryPoint_Ops.parseHandlerConfigWithModules(
132
+ modules,
133
+ config,
134
+ )->Array.getUnsafe(0)
135
+ expect(e.specModule)->toBe("@x/plugin/src/Order/AutomationSlice/Restock.res.mjs")
136
+ expect(e.bodyModule)->toBe("@x/plugin/src/Order/AutomationSlice/Restock_Automation.res.mjs")
137
+ expect(e.callbackType)->toBe("automation")
138
+ expect(e.queryDbTableName)->toBe("RestockTodo-abc")
139
+ expect(e.dcbQueueUrl)->toBe("https://sqs/dcb")
140
+ expect(e.commandQueueIsFifo)->toEqual(Some(false))
141
+ expect(e.sourceUrn)->toBe("arn:dcb:log")
142
+ switch e.context {
143
+ | Some(ctx) => expect(ctx.sliceName)->toBe("Restock")
144
+ | None => JsError.throwWithMessage("expected Some(context)")
145
+ }
146
+ })
147
+
148
+ testSync("per-entry queue and urn override the shared defaults", () => {
149
+ let config = compactConfig([
150
+ obj([
151
+ ("n", str("Ship")),
152
+ ("q", str("ShipTodo-abc")),
153
+ ("u", JSON.Encode.array([str("arn:aggregate:orders"), str("arn:dcb:log")])),
154
+ ("k", str("https://sqs/orders.fifo")),
155
+ ("f", JSON.Encode.bool(true)),
156
+ ]),
157
+ ])
158
+ let e =
159
+ AutomationSliceEntryPoint_Ops.parseHandlerConfigWithModules(
160
+ modules,
161
+ config,
162
+ )->Array.getUnsafe(0)
163
+ expect(e.callbackType)->toBe("outbound")
164
+ expect(e.dcbQueueUrl)->toBe("https://sqs/orders.fifo")
165
+ expect(e.commandQueueIsFifo)->toEqual(Some(true))
166
+ // The first of its own urns, not the shared one.
167
+ expect(e.sourceUrn)->toBe("arn:aggregate:orders")
168
+ expect(e.context->Option.isNone)->toBe(true)
169
+ })
170
+
171
+ testSync("a compact entry with no registry match yields no bodyModule to skip on", () => {
172
+ let config = compactConfig([obj([("n", str("Gone")), ("q", str("GoneTodo-abc"))])])
173
+ let e =
174
+ AutomationSliceEntryPoint_Ops.parseHandlerConfigWithModules(
175
+ modules,
176
+ config,
177
+ )->Array.getUnsafe(0)
178
+ expect(e.specModule)->toBe("")
179
+ expect(e.bodyModule)->toBe("")
180
+ })
181
+
182
+ testSync("full-key entries still decode when a registry is present", () => {
183
+ let config = obj([
184
+ (
185
+ "handlers",
186
+ JSON.Encode.array([
187
+ obj([
188
+ ("specModule", str("a.res.mjs")),
189
+ ("bodyModule", str("b.res.mjs")),
190
+ ("callbackType", str("outbound")),
191
+ ("queryDbTableName", str("t")),
192
+ ("dcbQueueUrl", str("u")),
193
+ ("sourceUrn", str("arn")),
194
+ ]),
195
+ ]),
196
+ ),
197
+ ])->JSON.stringify
198
+ let e =
199
+ AutomationSliceEntryPoint_Ops.parseHandlerConfigWithModules(
200
+ modules,
201
+ config,
202
+ )->Array.getUnsafe(0)
203
+ expect(e.specModule)->toBe("a.res.mjs")
204
+ expect(e.dcbQueueUrl)->toBe("u")
205
+ })
206
+ })
207
+
86
208
  // Shared stubs — the pipelines only touch phase1/phase2 plus the injected
87
209
  // sync/publish, so the callback records carry empty TODO dicts.
88
210
  let noopPublish: ReventlessCore.CommandTopic.publishJsons = async _ => ()
@@ -119,6 +119,193 @@ globalThis.describe("AutomationSliceEntryPoint_Ops.parseHandlerConfig", () => {
119
119
  });
120
120
  });
121
121
 
122
+ globalThis.describe("AutomationSliceEntryPoint_Ops.parseHandlerConfigWithModules", () => {
123
+ let modules = JSON.stringify(Object.fromEntries([
124
+ [
125
+ "Restock",
126
+ Object.fromEntries([
127
+ [
128
+ "specModule",
129
+ "@x/plugin/src/Order/AutomationSlice/Restock.res.mjs"
130
+ ],
131
+ [
132
+ "bodyModule",
133
+ "@x/plugin/src/Order/AutomationSlice/Restock_Automation.res.mjs"
134
+ ],
135
+ [
136
+ "callbackType",
137
+ "automation"
138
+ ],
139
+ [
140
+ "context",
141
+ Object.fromEntries([
142
+ [
143
+ "environment",
144
+ "alpha"
145
+ ],
146
+ [
147
+ "platformName",
148
+ "OnlineShop"
149
+ ],
150
+ [
151
+ "pluginName",
152
+ "Ordering"
153
+ ],
154
+ [
155
+ "sliceName",
156
+ "Restock"
157
+ ]
158
+ ])
159
+ ]
160
+ ])
161
+ ],
162
+ [
163
+ "Ship",
164
+ Object.fromEntries([
165
+ [
166
+ "specModule",
167
+ "@x/plugin/src/Order/OutboundTranslationSlice/Ship.res.mjs"
168
+ ],
169
+ [
170
+ "bodyModule",
171
+ "@x/plugin/src/Order/OutboundTranslationSlice/Ship_Translation.res.mjs"
172
+ ],
173
+ [
174
+ "callbackType",
175
+ "outbound"
176
+ ]
177
+ ])
178
+ ]
179
+ ]));
180
+ let compactConfig = handlers => JSON.stringify(Object.fromEntries([
181
+ [
182
+ "queueUrl",
183
+ "https://sqs/dcb"
184
+ ],
185
+ [
186
+ "commandQueueIsFifo",
187
+ false
188
+ ],
189
+ [
190
+ "urns",
191
+ ["arn:dcb:log"]
192
+ ],
193
+ [
194
+ "handlers",
195
+ handlers
196
+ ]
197
+ ]));
198
+ globalThis.test("a compact entry takes its modules from the registry and the shared defaults", () => {
199
+ let config = compactConfig([Object.fromEntries([
200
+ [
201
+ "n",
202
+ "Restock"
203
+ ],
204
+ [
205
+ "q",
206
+ "RestockTodo-abc"
207
+ ]
208
+ ])]);
209
+ let e = AutomationSliceEntryPoint_Ops$ReventlessAws.parseHandlerConfigWithModules(modules, config)[0];
210
+ globalThis.expect(e.specModule).toBe("@x/plugin/src/Order/AutomationSlice/Restock.res.mjs");
211
+ globalThis.expect(e.bodyModule).toBe("@x/plugin/src/Order/AutomationSlice/Restock_Automation.res.mjs");
212
+ globalThis.expect(e.callbackType).toBe("automation");
213
+ globalThis.expect(e.queryDbTableName).toBe("RestockTodo-abc");
214
+ globalThis.expect(e.dcbQueueUrl).toBe("https://sqs/dcb");
215
+ globalThis.expect(e.commandQueueIsFifo).toEqual(false);
216
+ globalThis.expect(e.sourceUrn).toBe("arn:dcb:log");
217
+ let ctx = e.context;
218
+ if (ctx !== undefined) {
219
+ globalThis.expect(ctx.sliceName).toBe("Restock");
220
+ return;
221
+ } else {
222
+ return Stdlib_JsError.throwWithMessage("expected Some(context)");
223
+ }
224
+ });
225
+ globalThis.test("per-entry queue and urn override the shared defaults", () => {
226
+ let config = compactConfig([Object.fromEntries([
227
+ [
228
+ "n",
229
+ "Ship"
230
+ ],
231
+ [
232
+ "q",
233
+ "ShipTodo-abc"
234
+ ],
235
+ [
236
+ "u",
237
+ [
238
+ "arn:aggregate:orders",
239
+ "arn:dcb:log"
240
+ ]
241
+ ],
242
+ [
243
+ "k",
244
+ "https://sqs/orders.fifo"
245
+ ],
246
+ [
247
+ "f",
248
+ true
249
+ ]
250
+ ])]);
251
+ let e = AutomationSliceEntryPoint_Ops$ReventlessAws.parseHandlerConfigWithModules(modules, config)[0];
252
+ globalThis.expect(e.callbackType).toBe("outbound");
253
+ globalThis.expect(e.dcbQueueUrl).toBe("https://sqs/orders.fifo");
254
+ globalThis.expect(e.commandQueueIsFifo).toEqual(true);
255
+ globalThis.expect(e.sourceUrn).toBe("arn:aggregate:orders");
256
+ globalThis.expect(Stdlib_Option.isNone(e.context)).toBe(true);
257
+ });
258
+ globalThis.test("a compact entry with no registry match yields no bodyModule to skip on", () => {
259
+ let config = compactConfig([Object.fromEntries([
260
+ [
261
+ "n",
262
+ "Gone"
263
+ ],
264
+ [
265
+ "q",
266
+ "GoneTodo-abc"
267
+ ]
268
+ ])]);
269
+ let e = AutomationSliceEntryPoint_Ops$ReventlessAws.parseHandlerConfigWithModules(modules, config)[0];
270
+ globalThis.expect(e.specModule).toBe("");
271
+ globalThis.expect(e.bodyModule).toBe("");
272
+ });
273
+ globalThis.test("full-key entries still decode when a registry is present", () => {
274
+ let config = JSON.stringify(Object.fromEntries([[
275
+ "handlers",
276
+ [Object.fromEntries([
277
+ [
278
+ "specModule",
279
+ "a.res.mjs"
280
+ ],
281
+ [
282
+ "bodyModule",
283
+ "b.res.mjs"
284
+ ],
285
+ [
286
+ "callbackType",
287
+ "outbound"
288
+ ],
289
+ [
290
+ "queryDbTableName",
291
+ "t"
292
+ ],
293
+ [
294
+ "dcbQueueUrl",
295
+ "u"
296
+ ],
297
+ [
298
+ "sourceUrn",
299
+ "arn"
300
+ ]
301
+ ])]
302
+ ]]));
303
+ let e = AutomationSliceEntryPoint_Ops$ReventlessAws.parseHandlerConfigWithModules(modules, config)[0];
304
+ globalThis.expect(e.specModule).toBe("a.res.mjs");
305
+ globalThis.expect(e.dcbQueueUrl).toBe("u");
306
+ });
307
+ });
308
+
122
309
  async function noopPublish(param) {
123
310
 
124
311
  }