@reventlessdev/reventless-aws 3.0.0-alpha.304 → 3.0.0-alpha.306

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +9 -9
  3. package/src/Platform.res +1 -1
  4. package/src/adapter/Api/AppSync_Resolver_Native.res +1 -1
  5. package/src/adapter/Api/AppSync_Resolver_Retrying.res +2 -2
  6. package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res +69 -11
  7. package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res.mjs +19 -1
  8. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +2 -2
  9. package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res +1 -1
  10. package/src/adapter/EventLog/EventLogStorage_DynamoDb_Runtime.res +1 -1
  11. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +1 -1
  12. package/src/adapter/Postgres/PgProjectionFeed.res +1 -1
  13. package/src/adapter/QueryDb/QueryDbBackend.res +1 -1
  14. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +29 -15
  15. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +6 -5
  16. package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res +1 -1
  17. package/src/adapter/Runtime/AggregateEntryPoint.mjs +1 -1
  18. package/src/adapter/Runtime/AggregateEntryPoint_Ops.res +1 -1
  19. package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +1 -1
  20. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +1 -1
  21. package/src/adapter/Runtime/CounterEntryPoint.mjs +1 -1
  22. package/src/adapter/Runtime/CounterEntryPoint_Ops.res +1 -1
  23. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +44 -6
  24. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +1 -1
  25. package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +8 -9
  26. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +36 -8
  27. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +18 -7
  28. package/src/adapter/Runtime/EventMapperEntryPoint.mjs +1 -1
  29. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +1 -1
  30. package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +1 -1
  31. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +1 -1
  32. package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +1 -1
  33. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +1 -1
  34. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res +1 -1
  35. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +1 -1
  36. package/src/adapter/Runtime/QueryDbEntryPoint_Ops.res +1 -1
  37. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +1 -1
  38. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res +1 -1
  39. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -1
  40. package/src/adapter/Runtime/SideEffectEntryPoint.mjs +1 -1
  41. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res +1 -1
  42. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +3 -1
  43. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +4 -1
  44. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +1 -1
  45. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +1 -1
  46. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +1 -1
  47. package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +1 -1
  48. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +1 -1
  49. package/src/plugin/runtime/PluginRuntime_Builder.res +46 -22
  50. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +16 -11
  51. package/src/util/Util_Bundle.res +1 -1
  52. package/src/util/Util_DynamoDbStream_Runtime.res +1 -1
  53. package/src/util/Util_LambdaEnvBudget.res +74 -0
  54. package/src/util/Util_LambdaEnvBudget.res.mjs +37 -0
  55. package/tests/AWS_TagsTest.res +2 -2
  56. package/tests/Auth_ActiveRolePoolAttachmentTest.res +140 -1
  57. package/tests/Auth_ActiveRolePoolAttachmentTest.res.mjs +117 -2
  58. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +2 -2
  59. package/tests/EventCollectorEntryPoint_OpsTest.res +13 -4
  60. package/tests/EventCollectorEntryPoint_OpsTest.res.mjs +19 -13
  61. package/tests/EventLogStorage_DynamoDb_RuntimeTest.res +1 -1
  62. package/tests/QueryDbResolvers_AppSyncTest.res +2 -2
  63. package/tests/Util_BundleEsmLoaderTest.res +1 -1
  64. package/tests/Util_BundleRuntimeExtensionTest.res +1 -1
  65. package/tests/Util_LambdaEnvBudgetTest.res +78 -0
  66. package/tests/Util_LambdaEnvBudgetTest.res.mjs +55 -0
  67. package/tests/integration/AggregateEntryPoint_IntegrationTest.res +1 -1
  68. package/tests/integration/DcbCommandTopicEntryPoint_IntegrationTest.res +1 -1
  69. package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res +2 -2
@@ -3,7 +3,7 @@
3
3
  // driven entirely by HANDLER_CONFIG.
4
4
  //
5
5
  // Thin untyped shell ("typed core, thin shell" —
6
- // docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
6
+ // docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
7
7
  // seams that are inherently untyped — the dynamic `import()` of EP/extension/spec
8
8
  // modules named in HANDLER_CONFIG and the functor applications consuming those
9
9
  // runtime-loaded modules. Everything else (config parse/validation, the Plugin-RM
@@ -46,12 +46,9 @@
46
46
  // "readModelNames": string[] // RMs this extension may enqueue events into (filter into readModelQueueUrls)
47
47
  // }
48
48
  // ],
49
- // "publishToAggregates": { // aggregateName env-var name holding the aggregate's cmd-topic SQS URL
50
- // "Plugin": "PTA_Plugin_QUEUE_URL"
51
- // },
52
- // "readModelQueueUrls": { // rmName → env-var name holding the RM's EventCollector SQS URL
53
- // "ProductDemands": "PRM_ProductDemands_QUEUE_URL"
54
- // },
49
+ // (publishToAggregates / readModelQueueUrls moved to the queueUrls.json asset
50
+ // one entry per target, so they outgrew the 4KB environment. Each still maps a
51
+ // target name to the PTA_/PRM_ env var holding its SQS URL.)
55
52
  // "readModelNamesForSourceName": { // deploy-side inversion of RM.sourceNames; sourceServiceName → [rmName]
56
53
  // "Ordering.Orders": ["ProductDemands"]
57
54
  // }
@@ -100,6 +97,8 @@ async function buildHandler() {
100
97
  await runtimeExtensionsReady;
101
98
  const config = Ops.parseHandlerConfig(process.env["HANDLER_CONFIG"] || "");
102
99
  const pluginDefinition = Ops.loadPluginDefinition();
100
+ // target → env-var name; the URLs themselves stay in the environment.
101
+ const queueUrlNames = Ops.loadQueueUrlNames();
103
102
 
104
103
  const queryEngine = Ops.makeQueryEngine(config.pluginReadModelTableName);
105
104
  const scheduler = Ops.makeScheduler(config.schedulerRoleArn);
@@ -165,7 +164,7 @@ async function buildHandler() {
165
164
  return { aggregateNames: ep.aggregateNames, outgoingHandler: ops.outgoingJsonEventsHandler };
166
165
  }));
167
166
 
168
- const publishToAggregates = Ops.buildPublishToAggregates(config.publishToAggregates);
167
+ const publishToAggregates = Ops.buildPublishToAggregates(queueUrlNames.publishToAggregates);
169
168
  const publishToPluginExtensionPoint = Ops.makePublishJsons(config.pluginExtensionPointCmdTopicUrl);
170
169
 
171
170
  // Connect extension — exactly one entry per plugin Lambda; null for admin.
@@ -230,7 +229,7 @@ async function buildHandler() {
230
229
  mappings: [transformedMapping],
231
230
  };
232
231
 
233
- const dicts = Ops.extensionPublishDicts(config, ext);
232
+ const dicts = Ops.extensionPublishDicts(config, queueUrlNames, ext);
234
233
  const ops = extensionOperationsMake(epSpec)(mappingsModule)({
235
234
  publishToAggregates: dicts.publishToAggregates,
236
235
  publishToPluginExtensionPoint,
@@ -1,6 +1,6 @@
1
1
  // Typed cold-start core for the Plugin EventCollector Lambda entry point.
2
2
  //
3
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
3
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
4
4
  // EventCollectorEntryPoint.mjs owns ONLY the untyped seams — the dynamic
5
5
  // `import()` of EP/extension/spec modules named in HANDLER_CONFIG and the
6
6
  // functor applications consuming those runtime-loaded modules
@@ -109,9 +109,16 @@ type handlerConfig = {
109
109
  extensionPoints: array<extensionPointConfig>,
110
110
  connectExtension: option<connectExtensionConfig>,
111
111
  extensions: array<extensionConfig>,
112
+ readModelNamesForSourceName: dict<array<string>>,
113
+ }
114
+
115
+ // The target→env-var maps that used to ride HANDLER_CONFIG, now read from
116
+ // queueUrls.json (PluginRuntime_Builder). Values are env var NAMES, not URLs —
117
+ // the URLs stay in the environment because the builder cannot resolve those
118
+ // Outputs in bulk.
119
+ type queueUrlNames = {
112
120
  publishToAggregates: dict<string>,
113
121
  readModelQueueUrls: dict<string>,
114
- readModelNamesForSourceName: dict<array<string>>,
115
122
  }
116
123
 
117
124
  let strOf = (obj: dict<JSON.t>, key: string): option<string> =>
@@ -149,7 +156,6 @@ let parseHandlerConfig = (rawJson: string): handlerConfig => {
149
156
  "schedulerQueueName",
150
157
  "extensionPoints",
151
158
  "extensions",
152
- "publishToAggregates",
153
159
  ]->Array.forEach(k =>
154
160
  if obj->Dict.get(k)->Option.isNone {
155
161
  JsError.throwWithMessage("HANDLER_CONFIG missing required field: " ++ k)
@@ -225,8 +231,6 @@ let parseHandlerConfig = (rawJson: string): handlerConfig => {
225
231
  extensionPoints,
226
232
  connectExtension,
227
233
  extensions,
228
- publishToAggregates: obj->strDictOf("publishToAggregates"),
229
- readModelQueueUrls: obj->strDictOf("readModelQueueUrls"),
230
234
  readModelNamesForSourceName,
231
235
  }
232
236
  }
@@ -625,7 +629,11 @@ type extensionDicts = {
625
629
  readModelNamesForSourceName: dict<array<string>>,
626
630
  }
627
631
 
628
- let extensionPublishDicts = (config: handlerConfig, ext: extensionConfig): extensionDicts => {
632
+ let extensionPublishDicts = (
633
+ config: handlerConfig,
634
+ names: queueUrlNames,
635
+ ext: extensionConfig,
636
+ ): extensionDicts => {
629
637
  let resolve = (names: array<string>, envVarNames: dict<string>) =>
630
638
  names
631
639
  ->Array.filterMap(name =>
@@ -637,8 +645,8 @@ let extensionPublishDicts = (config: handlerConfig, ext: extensionConfig): exten
637
645
  )
638
646
  ->Dict.fromArray
639
647
  {
640
- publishToAggregates: resolve(ext.aggregateNames, config.publishToAggregates),
641
- publishToReadModels: resolve(ext.readModelNames, config.readModelQueueUrls),
648
+ publishToAggregates: resolve(ext.aggregateNames, names.publishToAggregates),
649
+ publishToReadModels: resolve(ext.readModelNames, names.readModelQueueUrls),
642
650
  // Scoped to this extension's EP — Extension_Operations uses this dict to
643
651
  // decide which RMs receive each incoming event.
644
652
  readModelNamesForSourceName: Dict.fromArray([
@@ -680,6 +688,26 @@ let loadPluginDefinition = (): Reventless.Plugin.pluginDefinition =>
680
688
  )
681
689
  }
682
690
 
691
+ // The target→env-var maps, read once at cold start. A plugin that publishes
692
+ // nowhere writes empty maps rather than omitting the file, so a read failure is
693
+ // a genuinely broken archive and not an empty-map case worth guessing at.
694
+ let loadQueueUrlNames = (): queueUrlNames =>
695
+ try {
696
+ let obj =
697
+ assetPath("queueUrls.json")
698
+ ->NodeFs.readFileSync
699
+ ->JSON.parseOrThrow
700
+ ->JSON.Decode.object
701
+ ->Option.getOr(Dict.make())
702
+ {
703
+ publishToAggregates: obj->strDictOf("publishToAggregates"),
704
+ readModelQueueUrls: obj->strDictOf("readModelQueueUrls"),
705
+ }
706
+ } catch {
707
+ | exn =>
708
+ JsError.throwWithMessage("Failed to load queueUrls.json from asset zip: " ++ exnMessage(exn))
709
+ }
710
+
683
711
  // The plugin's UI-fragment manifest — its own asset since the manifest no
684
712
  // longer rides pluginDefinition (the UiFragmentRegistry slice owns fragment
685
713
  // state). JSON null (plugins without a UI) and a missing file (older archives)
@@ -111,8 +111,7 @@ function parseHandlerConfig(rawJson) {
111
111
  "schedulerQueueArn",
112
112
  "schedulerQueueName",
113
113
  "extensionPoints",
114
- "extensions",
115
- "publishToAggregates"
114
+ "extensions"
116
115
  ].forEach(k => {
117
116
  if (Stdlib_Option.isNone(obj[k])) {
118
117
  return Stdlib_JsError.throwWithMessage("HANDLER_CONFIG missing required field: " + k);
@@ -159,8 +158,6 @@ function parseHandlerConfig(rawJson) {
159
158
  extensionPoints: extensionPoints,
160
159
  connectExtension: connectExtension,
161
160
  extensions: extensions,
162
- publishToAggregates: strDictOf(obj, "publishToAggregates"),
163
- readModelQueueUrls: strDictOf(obj, "readModelQueueUrls"),
164
161
  readModelNamesForSourceName: readModelNamesForSourceName
165
162
  };
166
163
  }
@@ -495,14 +492,14 @@ function buildPublishToAggregates(map) {
495
492
  ]));
496
493
  }
497
494
 
498
- function extensionPublishDicts(config, ext) {
495
+ function extensionPublishDicts(config, names, ext) {
499
496
  let resolve = (names, envVarNames) => Object.fromEntries(Stdlib_Array.filterMap(names, name => Stdlib_Option.map(Stdlib_Option.filter(Stdlib_Option.flatMap(envVarNames[name], envVarName => process.env[envVarName]), queueUrl => queueUrl !== ""), queueUrl => [
500
497
  name,
501
498
  makePublishJsons(queueUrl)
502
499
  ])));
503
500
  return {
504
- publishToAggregates: resolve(ext.aggregateNames, config.publishToAggregates),
505
- publishToReadModels: resolve(ext.readModelNames, config.readModelQueueUrls),
501
+ publishToAggregates: resolve(ext.aggregateNames, names.publishToAggregates),
502
+ publishToReadModels: resolve(ext.readModelNames, names.readModelQueueUrls),
506
503
  readModelNamesForSourceName: Object.fromEntries([[
507
504
  ext.extensionPointName,
508
505
  Stdlib_Option.getOr(config.readModelNamesForSourceName[ext.extensionPointName], [])
@@ -529,6 +526,19 @@ function loadPluginDefinition() {
529
526
  }
530
527
  }
531
528
 
529
+ function loadQueueUrlNames() {
530
+ try {
531
+ let obj = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(Nodefs.readFileSync(assetPath("queueUrls.json"), "utf8"))), {});
532
+ return {
533
+ publishToAggregates: strDictOf(obj, "publishToAggregates"),
534
+ readModelQueueUrls: strDictOf(obj, "readModelQueueUrls")
535
+ };
536
+ } catch (raw_exn) {
537
+ let exn = Primitive_exceptions.internalToException(raw_exn);
538
+ return Stdlib_JsError.throwWithMessage("Failed to load queueUrls.json from asset zip: " + exnMessage(exn));
539
+ }
540
+ }
541
+
532
542
  function loadUiFragments() {
533
543
  try {
534
544
  let json = JSON.parse(Nodefs.readFileSync(assetPath("uiFragments.json"), "utf8"));
@@ -649,6 +659,7 @@ export {
649
659
  warnSkippedExtension,
650
660
  assetPath,
651
661
  loadPluginDefinition,
662
+ loadQueueUrlNames,
652
663
  loadUiFragments,
653
664
  pushHandler,
654
665
  makeSqsHandlerBundle,
@@ -3,7 +3,7 @@
3
3
  // wires EventMapper_Callback.MakeCounterHandler + MakeEventCollectorHandler.
4
4
  //
5
5
  // Thin untyped shell ("typed core, thin shell" —
6
- // docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
6
+ // docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
7
7
  // seams that are inherently untyped — the dynamic `import()` of the
8
8
  // target-spec/mappings modules named in HANDLER_CONFIG, the shape fix-ups on
9
9
  // those runtime-loaded modules (patchSpecId / patchMappingsSourceIds), and the
@@ -1,6 +1,6 @@
1
1
  // Typed cold-start core for the EventMapper Lambda entry point (Micro mode).
2
2
  //
3
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
3
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
4
4
  // EventMapperEntryPoint.mjs keeps only the untyped seam — the dynamic
5
5
  // `import()` of the target-spec/mappings modules named in HANDLER_CONFIG, the
6
6
  // `patchSpecId` / mapping-Source-Id fix-ups, and the
@@ -4,7 +4,7 @@
4
4
  // routes SQS events through handleQueueEvent.
5
5
  //
6
6
  // Thin untyped shell ("typed core, thin shell" —
7
- // docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
7
+ // docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
8
8
  // seams that are inherently untyped — the dynamic `import()` of the
9
9
  // spec/mappings modules named in HANDLER_CONFIG, the `patchSpecId` fix-up, and
10
10
  // the two functor applications consuming them. The callback config,
@@ -1,7 +1,7 @@
1
1
  // Typed cold-start core for the (per-extension-point) ExtensionPoint Lambda
2
2
  // entry point.
3
3
  //
4
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
4
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
5
5
  // ExtensionPointEntryPoint.mjs keeps only the untyped seam — the dynamic
6
6
  // `import()` of the spec/mappings modules named in HANDLER_CONFIG, the
7
7
  // `patchSpecId` fix-up, and the ExtensionPoint_Callback.Make /
@@ -9,7 +9,7 @@
9
9
  // PgQueryResolver_Lambda.dispatch routes.
10
10
  //
11
11
  // Thin untyped shell ("typed core, thin shell" —
12
- // docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
12
+ // docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
13
13
  // seams that are inherently untyped — the dynamic `import()` of the spec
14
14
  // packages named in QUERY_RESOLVER_CONFIG, the `patchSpecId` fix-up, and the
15
15
  // reads of the runtime-loaded modules' exports. QUERY_RESOLVER_CONFIG parsing,
@@ -1,7 +1,7 @@
1
1
  // Typed cold-start core for the shared PgQueryResolver Lambda entry point
2
2
  // (B3.2b).
3
3
  //
4
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
4
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
5
5
  // PgQueryResolverEntryPoint.mjs keeps only the untyped seam — the dynamic
6
6
  // `import()` of each spec package named in QUERY_RESOLVER_CONFIG, the
7
7
  // `patchSpecId` fix-up, and the reads of the runtime-loaded module's exports
@@ -95,7 +95,7 @@ module Mappings = {
95
95
  // Parity with the former shell: only the Plugin lifecycle mapping. The
96
96
  // in-process wiring (PluginExtensionPoint_Builder) additionally registers
97
97
  // PluginExtensionPoint_UiFragment.Mapping — divergence tracked in
98
- // docs/plans/entry-point-rescript-conversion.md.
98
+ // docs/plans/done/entry-point-rescript-conversion.md.
99
99
  let mappings: array<module(Mapping)> = [module(PluginMappingInstance.Mapping)]
100
100
  }
101
101
 
@@ -1,7 +1,7 @@
1
1
  // Typed QueryDb-assembly core shared by the two projection Lambda entry points
2
2
  // (ReadModelEntryPoint.mjs, StateViewSliceEntryPoint.mjs).
3
3
  //
4
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
4
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
5
5
  // both shells build the QueryDb operation set (DynamoDB or Postgres, live-update
6
6
  // wrapped) for each projection from spec-module reads; that machinery lives
7
7
  // here, fully type-checked. The routed dispatch boundary they share (with the
@@ -1,7 +1,7 @@
1
1
  // Typed cold-start core shared by the QueryDb-backed entry-point shells
2
2
  // (ReadModelEntryPoint.mjs, StateViewSliceEntryPoint.mjs).
3
3
  //
4
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md).
4
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md).
5
5
  // The DynamoDB QueryDb operations were built in each shell with seven positional
6
6
  // `load(table)` / `save(table)` / … calls into the compiled runtime, assembled
7
7
  // into an object the shell then passes to the ReadModel/StateViewSlice callback
@@ -3,7 +3,7 @@
3
3
  // wires ReadModel_Callback.Make, builds handler map keyed by source URN.
4
4
  //
5
5
  // Thin untyped shell ("typed core, thin shell" —
6
- // docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
6
+ // docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
7
7
  // seams that are inherently untyped — the dynamic `import()` of spec/mappings
8
8
  // modules named in HANDLER_CONFIG, the shape fix-ups on those runtime-loaded
9
9
  // modules (patchSpecId / fixMappingsModule), and the ReadModel_Callback.Make
@@ -1,6 +1,6 @@
1
1
  // Typed cold-start core for the ReadModel Lambda entry point.
2
2
  //
3
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
3
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
4
4
  // ReadModelEntryPoint.mjs keeps only the untyped seams — the dynamic `import()`
5
5
  // of spec/mappings modules named in HANDLER_CONFIG, the `patchSpecId` /
6
6
  // `fixMappingsModule` shape fix-ups on those runtime-loaded modules, and the
@@ -354,7 +354,7 @@ let makeFromCodeAsset: (
354
354
 
355
355
  // Announce the provisioned execution unit to the Monitoring registry. No-op
356
356
  // unless a deploy program registered a backend via Monitoring.use — see
357
- // docs/plans/monitoring-hook-seam.md.
357
+ // docs/plans/done/monitoring-hook-seam.md.
358
358
  ReventlessCore.Monitoring.notify(~kind=unitKind, ~name, ~component=lambdaResource)
359
359
 
360
360
  {
@@ -3,7 +3,7 @@
3
3
  // wires SideEffectHandler_Callback.Make, builds handler map keyed by source URN.
4
4
  //
5
5
  // Thin untyped shell ("typed core, thin shell" —
6
- // docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
6
+ // docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
7
7
  // seams that are inherently untyped — the dynamic `import()` of the side-effect
8
8
  // modules named in HANDLER_CONFIG and the SideEffectHandler_Callback.Make
9
9
  // functor application consuming them. HANDLER_CONFIG parsing and handler
@@ -1,6 +1,6 @@
1
1
  // Typed cold-start core for the SideEffectHandler Lambda entry point.
2
2
  //
3
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
3
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
4
4
  // SideEffectEntryPoint.mjs keeps only the untyped seam — the dynamic `import()`
5
5
  // of the side-effect modules named in HANDLER_CONFIG and the
6
6
  // SideEffectHandler_Callback.Make functor application consuming them.
@@ -179,7 +179,9 @@ let forDcbCommandTopic = (
179
179
  Pulumi.Output.all4((dcbTableName, queue.id, pgConnectionFragment, inboundFragment))
180
180
  ->Pulumi.Output.apply(((table, queueUrl, pgFragment, inbFragment)) => {
181
181
  let pluginNameJson = pluginName->JSON.stringifyAny->Option.getOr(`""`)
182
- `{"dcbEventLogTableName":"${table}","queueUrl":"${queueUrl}","pluginName":${pluginNameJson}${pgFragment}${inbFragment}}`
182
+ let json = `{"dcbEventLogTableName":"${table}","queueUrl":"${queueUrl}","pluginName":${pluginNameJson}${pgFragment}${inbFragment}}`
183
+ Util_LambdaEnvBudget.check(~lambdaName=name, ~handlerConfigJson=json)
184
+ json
183
185
  })
184
186
  envVars->Dict.set("HANDLER_CONFIG", handlerConfigJson->Pulumi.Output.asInput)
185
187
 
@@ -11,6 +11,7 @@ import * as DcbBackend$ReventlessAws from "../DcbEventLog/DcbBackend.res.mjs";
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 PgConnection$ReventlessAws from "../Postgres/PgConnection.res.mjs";
14
+ import * as Util_LambdaEnvBudget$ReventlessAws from "../../util/Util_LambdaEnvBudget.res.mjs";
14
15
  import * as CommandTopicChannel_SQS$ReventlessAws from "../CommandTopic/CommandTopicChannel_SQS.res.mjs";
15
16
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
16
17
 
@@ -79,7 +80,9 @@ function forDcbCommandTopic(slicePaths, inboundSlicesOpt, dcbTableName, pluginNa
79
80
  inboundFragment
80
81
  ]).apply(param => {
81
82
  let pluginNameJson = Stdlib_Option.getOr(JSON.stringify(pluginName), `""`);
82
- return `{"dcbEventLogTableName":"` + param[0] + `","queueUrl":"` + param[1] + `","pluginName":` + pluginNameJson + param[2] + param[3] + `}`;
83
+ let json = `{"dcbEventLogTableName":"` + param[0] + `","queueUrl":"` + param[1] + `","pluginName":` + pluginNameJson + param[2] + param[3] + `}`;
84
+ Util_LambdaEnvBudget$ReventlessAws.check(name, json, undefined);
85
+ return json;
83
86
  });
84
87
  envVars["HANDLER_CONFIG"] = handlerConfigJson;
85
88
  let packageDirs = {};
@@ -4,7 +4,7 @@
4
4
  // source URN.
5
5
  //
6
6
  // Thin untyped shell ("typed core, thin shell" —
7
- // docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
7
+ // docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
8
8
  // inherently-untyped seam — the dynamic `import()` of spec/projection modules
9
9
  // named in HANDLER_CONFIG and the reads of their exports. Everything else
10
10
  // (HANDLER_CONFIG parsing incl. the compact-v2 expansion, the QueryDb operation
@@ -1,6 +1,6 @@
1
1
  // Typed cold-start core for the StateViewSlice Lambda entry point.
2
2
  //
3
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
3
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
4
4
  // StateViewSliceEntryPoint.mjs keeps only the inherently-untyped seam — the
5
5
  // dynamic `import()` of spec/projection modules named in HANDLER_CONFIG and the
6
6
  // reads of their exports. HANDLER_CONFIG parsing (including the compact-v2
@@ -2,7 +2,7 @@
2
2
  // points (ReadModelEntryPoint.mjs, StateViewSliceEntryPoint.mjs,
3
3
  // AutomationSliceEntryPoint.mjs).
4
4
  //
5
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
5
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
6
6
  // all three shells route DynamoDB-stream / SQS-feed records to per-source-URN
7
7
  // handler lists with per-handler log attribution — machinery the `.mjs` files
8
8
  // previously duplicated verbatim. It lives here so every co-hosted component
@@ -4,7 +4,7 @@
4
4
  // Events schedules).
5
5
  //
6
6
  // Thin untyped shell ("typed core, thin shell" —
7
- // docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
7
+ // docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
8
8
  // inherently-untyped seam — the dynamic `import()` of the callback module named
9
9
  // in HANDLER_CONFIG and the read of its `callback` export. Everything else
10
10
  // (HANDLER_CONFIG parsing, S3-event handling via TaskBucket_S3_Runtime, the
@@ -1,6 +1,6 @@
1
1
  // Typed cold-start core for the TaskBucket Lambda entry point.
2
2
  //
3
- // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
3
+ // The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
4
4
  // TaskBucketEntryPoint.mjs keeps only the inherently-untyped seam — the
5
5
  // dynamic `import()` of the task callback module named in HANDLER_CONFIG and
6
6
  // the read of its `callback` export. HANDLER_CONFIG parsing, the S3-event →
@@ -503,27 +503,13 @@ module Make = (
503
503
  })
504
504
  dict->Dict.set("extensions", JSON.Encode.array(extensionsArr))
505
505
 
506
- let publishToAggregatesDict = Dict.make()
507
- context.publishToAggregates
508
- ->Dict.keysToArray
509
- ->Array.forEach(aggName =>
510
- publishToAggregatesDict->Dict.set(
511
- aggName,
512
- JSON.Encode.string(`PTA_${aggName}_QUEUE_URL`),
513
- )
514
- )
515
- dict->Dict.set("publishToAggregates", JSON.Encode.object(publishToAggregatesDict))
516
-
517
- let readModelQueueUrlsDict = Dict.make()
518
- context.readModelQueueUrls
519
- ->Dict.keysToArray
520
- ->Array.forEach(rmName =>
521
- readModelQueueUrlsDict->Dict.set(
522
- rmName,
523
- JSON.Encode.string(`PRM_${rmName}_QUEUE_URL`),
524
- )
525
- )
526
- dict->Dict.set("readModelQueueUrls", JSON.Encode.object(readModelQueueUrlsDict))
506
+ // publishToAggregates / readModelQueueUrls live in queueUrls.json (see the
507
+ // asset bundle below). Both are one entry per target, so they grow with the
508
+ // plugin, and together with the PTA_/PRM_ vars they name they carried the
509
+ // catalog EventCollector past Lambda's 4KB environment ceiling. Only the
510
+ // maps move: they are built from Dict.keysToArray, so they hold no Pulumi
511
+ // Output and can be serialized here. The URLs themselves cannot follow —
512
+ // see the note on the aggregate walk further down.
527
513
 
528
514
  let rmNamesForSourceDict = Dict.make()
529
515
  context.readModelNamesForSourceName
@@ -536,7 +522,22 @@ module Make = (
536
522
  )
537
523
  dict->Dict.set("readModelNamesForSourceName", JSON.Encode.object(rmNamesForSourceDict))
538
524
 
539
- JSON.Encode.object(dict)->JSON.stringify
525
+ let json = JSON.Encode.object(dict)->JSON.stringify
526
+ // The PTA_/PRM_ vars are the rest of this Lambda's environment and the
527
+ // half that grows with the plugin. Their names are known here; their
528
+ // values are still Outputs, so the budget check estimates those.
529
+ let queueUrlKeys = Array.concat(
530
+ context.publishToAggregates
531
+ ->Dict.keysToArray
532
+ ->Array.map(n => `PTA_${n}_QUEUE_URL`),
533
+ context.readModelQueueUrls->Dict.keysToArray->Array.map(n => `PRM_${n}_QUEUE_URL`),
534
+ )
535
+ Util_LambdaEnvBudget.check(
536
+ ~lambdaName=name,
537
+ ~handlerConfigJson=json,
538
+ ~outputValuedKeys=queueUrlKeys,
539
+ )
540
+ json
540
541
  })
541
542
  envVars->Dict.set("HANDLER_CONFIG", handlerConfigJson->Pulumi.Output.asInput)
542
543
 
@@ -599,6 +600,29 @@ module Make = (
599
600
  ->Pulumi.Output.apply(((pluginDefinitionJson, uiFragmentsJson)) => {
600
601
  let extraStringAssets = Dict.make()
601
602
  extraStringAssets->Dict.set("pluginDefinition.json", pluginDefinitionJson)
603
+ // target name → the env var holding that target's queue URL. The
604
+ // indirection stays (the URLs are Outputs this builder must not resolve
605
+ // in bulk); only the naming map moves out of HANDLER_CONFIG.
606
+ let nameMap = (names: array<string>, prefix: string) => {
607
+ let d = Dict.make()
608
+ names
609
+ ->Array.toSorted(String.compare)
610
+ ->Array.forEach(n => d->Dict.set(n, JSON.Encode.string(`${prefix}_${n}_QUEUE_URL`)))
611
+ JSON.Encode.object(d)
612
+ }
613
+ let queueUrlsDict = Dict.make()
614
+ queueUrlsDict->Dict.set(
615
+ "publishToAggregates",
616
+ nameMap(context.publishToAggregates->Dict.keysToArray, "PTA"),
617
+ )
618
+ queueUrlsDict->Dict.set(
619
+ "readModelQueueUrls",
620
+ nameMap(context.readModelQueueUrls->Dict.keysToArray, "PRM"),
621
+ )
622
+ extraStringAssets->Dict.set(
623
+ "queueUrls.json",
624
+ JSON.Encode.object(queueUrlsDict)->JSON.stringify,
625
+ )
602
626
  // The UI-fragment manifest no longer rides pluginDefinition — ship it as
603
627
  // its own asset ("null" for plugins without a UI) so the bundled Connect
604
628
  // extension can emit RegisterUiFragment in the handshake answer.
@@ -16,6 +16,7 @@ import * as ComponentType$ReventlessCore from "@reventlessdev/reventless-core/sr
16
16
  import * as EventCollector$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventCollector/EventCollector.res.mjs";
17
17
  import * as EventLogBackend$ReventlessAws from "../../adapter/EventLog/EventLogBackend.res.mjs";
18
18
  import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
19
+ import * as Util_LambdaEnvBudget$ReventlessAws from "../../util/Util_LambdaEnvBudget.res.mjs";
19
20
  import * as Platform_Admin_Structure$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_Admin_Structure.res.mjs";
20
21
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "../../adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs";
21
22
  import * as StateChangeSliceRuntime_Builder_Single$ReventlessAws from "../../adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs";
@@ -323,22 +324,15 @@ function Make(EventCollectorChannel) {
323
324
  return entryDict;
324
325
  });
325
326
  dict["extensions"] = extensionsArr;
326
- let publishToAggregatesDict = {};
327
- Object.keys(context.publishToAggregates).forEach(aggName => {
328
- publishToAggregatesDict[aggName] = `PTA_` + aggName + `_QUEUE_URL`;
329
- });
330
- dict["publishToAggregates"] = publishToAggregatesDict;
331
- let readModelQueueUrlsDict = {};
332
- Object.keys(context.readModelQueueUrls).forEach(rmName => {
333
- readModelQueueUrlsDict[rmName] = `PRM_` + rmName + `_QUEUE_URL`;
334
- });
335
- dict["readModelQueueUrls"] = readModelQueueUrlsDict;
336
327
  let rmNamesForSourceDict = {};
337
328
  Object.entries(context.readModelNamesForSourceName).forEach(param => {
338
329
  rmNamesForSourceDict[param[0]] = param[1].map(prim => prim);
339
330
  });
340
331
  dict["readModelNamesForSourceName"] = rmNamesForSourceDict;
341
- return JSON.stringify(dict);
332
+ let json = JSON.stringify(dict);
333
+ let queueUrlKeys = Object.keys(context.publishToAggregates).map(n => `PTA_` + n + `_QUEUE_URL`).concat(Object.keys(context.readModelQueueUrls).map(n => `PRM_` + n + `_QUEUE_URL`));
334
+ Util_LambdaEnvBudget$ReventlessAws.check(name, json, queueUrlKeys);
335
+ return json;
342
336
  });
343
337
  envVars["HANDLER_CONFIG"] = handlerConfigJson;
344
338
  let packageDirs = {};
@@ -374,6 +368,17 @@ function Make(EventCollectorChannel) {
374
368
  ]).apply(param => {
375
369
  let extraStringAssets = {};
376
370
  extraStringAssets["pluginDefinition.json"] = param[0];
371
+ let nameMap = (names, prefix) => {
372
+ let d = {};
373
+ names.toSorted(Primitive_string.compare).forEach(n => {
374
+ d[n] = prefix + `_` + n + `_QUEUE_URL`;
375
+ });
376
+ return d;
377
+ };
378
+ let queueUrlsDict = {};
379
+ queueUrlsDict["publishToAggregates"] = nameMap(Object.keys(context.publishToAggregates), "PTA");
380
+ queueUrlsDict["readModelQueueUrls"] = nameMap(Object.keys(context.readModelQueueUrls), "PRM");
381
+ extraStringAssets["queueUrls.json"] = JSON.stringify(queueUrlsDict);
377
382
  extraStringAssets["uiFragments.json"] = param[1];
378
383
  return Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/EventCollectorEntryPoint.mjs", packageDirs, extraStringAssets, undefined);
379
384
  });
@@ -122,7 +122,7 @@ let hashString = (str: string): string =>
122
122
  // (set on the Lambda env by RuntimeEnvironment_Lambda.makeFromCodeAsset). Shipped
123
123
  // at the root of every code archive; NODE_OPTIONS=--import points Node at
124
124
  // register-hook.mjs. Validated on a Node 22.17.1 rig; see
125
- // docs/plans/deployed-lambda-esm-self-containment.md.
125
+ // docs/plans/done/deployed-lambda-esm-self-containment.md.
126
126
  let registerHookFileName = "register-hook.mjs"
127
127
  let layerResolverFileName = "layer-resolver.mjs"
128
128
 
@@ -5,7 +5,7 @@ type result =
5
5
  | Invalid
6
6
 
7
7
  // Returns None for rows that aren't events — e.g. DCB FENCE rows and aggregate
8
- // snapshot rows (position = "SNAPSHOT"; docs/plans/aggregate-snapshotting.md),
8
+ // snapshot rows (position = "SNAPSHOT"; docs/plans/done/aggregate-snapshotting.md),
9
9
  // neither of which has an `event` column. The caller filters these out via
10
10
  // `Invalid` rather than synthesising a bogus event that sury would later reject.
11
11
  let buildJsonEvent' = dict => {