@reventlessdev/reventless-aws 3.0.0-alpha.259 → 3.0.0-alpha.261

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 (29) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +9 -9
  3. package/src/Platform.res +79 -4
  4. package/src/Platform.res.mjs +24 -8
  5. package/src/adapter/Geocoder/Geocoder_AwsLocation_Backend.res +77 -0
  6. package/src/adapter/Geocoder/Geocoder_AwsLocation_Backend.res.mjs +87 -0
  7. package/src/adapter/Geocoder/Geocoder_AwsLocation_Ops.res +42 -36
  8. package/src/adapter/Geocoder/Geocoder_AwsLocation_Ops.res.mjs +24 -6
  9. package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +13 -1
  10. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +87 -8
  11. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +36 -5
  12. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +3 -16
  13. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +4 -19
  14. package/src/components/AutomationSlice_Builder.res +13 -0
  15. package/src/components/AutomationSlice_Builder.res.mjs +7 -2
  16. package/src/components/OutboundTranslationSlice_Builder.res +18 -2
  17. package/src/components/OutboundTranslationSlice_Builder.res.mjs +9 -3
  18. package/src/plugin/runtime/PluginRuntime_Builder.res +31 -0
  19. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +13 -0
  20. package/src/util/Util_DcbMetrics.res +48 -0
  21. package/src/util/Util_DcbMetrics.res.mjs +37 -0
  22. package/src/util/Util_ShellConfig.res +72 -0
  23. package/src/util/Util_ShellConfig.res.mjs +60 -0
  24. package/tests/Geocoder_AwsLocation_OpsTest.res +78 -0
  25. package/tests/Geocoder_AwsLocation_OpsTest.res.mjs +71 -0
  26. package/tests/Util_DcbMetricsTest.res +35 -0
  27. package/tests/Util_DcbMetricsTest.res.mjs +29 -0
  28. package/tests/Util_ShellConfigTest.res +78 -0
  29. package/tests/Util_ShellConfigTest.res.mjs +113 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as Location$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/Location.res.mjs";
5
6
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
6
7
  import * as ClientLocation from "@aws-sdk/client-location";
7
8
 
@@ -42,7 +43,18 @@ async function handler(event) {
42
43
  try {
43
44
  let indexName = Stdlib_Option.getOr(getEnv("PLACE_INDEX_NAME"), "");
44
45
  let q = Stdlib_Option.getOr(readQueryParam(event), "");
45
- if (indexName === "" || q === "") {
46
+ if (indexName === "") {
47
+ console.error("Geocoder: PLACE_INDEX_NAME is unset");
48
+ return {
49
+ statusCode: 502,
50
+ headers: Object.fromEntries([[
51
+ "content-type",
52
+ "application/json"
53
+ ]]),
54
+ body: "[]"
55
+ };
56
+ }
57
+ if (q === "") {
46
58
  return {
47
59
  statusCode: 200,
48
60
  headers: Object.fromEntries([[
@@ -52,8 +64,7 @@ async function handler(event) {
52
64
  body: "[]"
53
65
  };
54
66
  }
55
- let client = new ClientLocation.LocationClient();
56
- let resp = await client.send(new ClientLocation.SearchPlaceIndexForTextCommand({
67
+ let resp = await Location$AwsSdk.SearchPlaceIndexForTextCommand.send(new ClientLocation.SearchPlaceIndexForTextCommand({
57
68
  IndexName: indexName,
58
69
  Text: q,
59
70
  MaxResults: 5
@@ -73,6 +84,7 @@ async function handler(event) {
73
84
  }
74
85
  let lng = pt[0];
75
86
  let lat = pt[1];
87
+ let rel = r.Relevance;
76
88
  return Object.fromEntries([
77
89
  [
78
90
  "label",
@@ -86,7 +98,10 @@ async function handler(event) {
86
98
  "lng",
87
99
  lng
88
100
  ]
89
- ]);
101
+ ].concat(rel !== undefined ? [[
102
+ "relevance",
103
+ rel
104
+ ]] : []));
90
105
  });
91
106
  return {
92
107
  statusCode: 200,
@@ -100,7 +115,7 @@ async function handler(event) {
100
115
  let exn = Primitive_exceptions.internalToException(raw_exn);
101
116
  console.error("Geocoder: search failed", exn);
102
117
  return {
103
- statusCode: 200,
118
+ statusCode: 502,
104
119
  headers: Object.fromEntries([[
105
120
  "content-type",
106
121
  "application/json"
@@ -110,10 +125,13 @@ async function handler(event) {
110
125
  }
111
126
  }
112
127
 
128
+ let Search;
129
+
113
130
  export {
131
+ Search,
114
132
  getEnv,
115
133
  jsonHeaders,
116
134
  readQueryParam,
117
135
  handler,
118
136
  }
119
- /* @aws-sdk/client-location Not a pure module */
137
+ /* Location-AwsSdk Not a pure module */
@@ -45,7 +45,19 @@ async function buildAllHandlers() {
45
45
  specModule.name,
46
46
  automationSliceCallbackMake(specModule)(bodyModule)
47
47
  );
48
- StreamOps.addToRegistry(handlers, entry.sourceUrn, registered);
48
+ // A slice can listen on several streams — its plugin's DCB log and any
49
+ // Aggregate EventTopic it named. Register the one handler under each, since
50
+ // the router dispatches by the record's own eventSourceARN. `sourceUrn` is
51
+ // the pre-multi-source spelling, kept as a fallback so an older handler
52
+ // config still routes.
53
+ const sourceUrns = entry.sourceUrns?.length
54
+ ? entry.sourceUrns
55
+ : entry.sourceUrn
56
+ ? [entry.sourceUrn]
57
+ : [];
58
+ for (const sourceUrn of sourceUrns) {
59
+ StreamOps.addToRegistry(handlers, sourceUrn, registered);
60
+ }
49
61
  }));
50
62
 
51
63
  return handlers;
@@ -24,6 +24,20 @@ type sliceInfo = {
24
24
  // Threaded to phase 1 collect/resolve on the automation path (mirrors the
25
25
  // in-process builder's `~context`); outbound slices take no context.
26
26
  context: option<Reventless.AutomationSlice.context>,
27
+ // The non-DCB topics this slice subscribes to — an Aggregate's EventTopic when
28
+ // it names one by `Spec.name`. Empty for the common DCB-only slice.
29
+ //
30
+ // Captured *synchronously* here for the same reason the whole `bundledInfos`
31
+ // path exists: `forEventCollector` runs inside a `Pulumi.Output.apply`, so
32
+ // `storedSpecs` — which carries the resolved topics on the dead `finish` path
33
+ // — is still empty when `finishWithDcbEventLog` runs. The core builders
34
+ // compute this dict before that apply, so the value is available in time; it
35
+ // just had nowhere to go until now.
36
+ sourceTopics: ReventlessCore.EventTopic.allOutputs,
37
+ // Whether this slice also reads the plugin's own DCB event log. True for every
38
+ // slice that declares no sources (the default) and for any that names the log
39
+ // explicitly, which is why it is not derivable from `sourceTopics` being empty.
40
+ consumesDcbLog: bool,
27
41
  }
28
42
 
29
43
  let bundledInfos: dict<sliceInfo> = Dict.make()
@@ -35,6 +49,9 @@ let setDcbQueueUrl = url => dcbQueueUrlRef := Some(url)
35
49
  // ApiFragmentRegistry push (2e) to dispatch RecordApiFragmentPush.
36
50
  let getDcbQueueUrl = () => dcbQueueUrlRef.contents
37
51
 
52
+ // `~sourceTopics` / `~consumesDcbLog` default to the DCB-only shape every slice
53
+ // had before aggregate sources existed, so a caller that has not been updated
54
+ // keeps exactly its previous wiring.
38
55
  let registerAutomationSlice = (
39
56
  ~name,
40
57
  ~specModulePath,
@@ -43,10 +60,21 @@ let registerAutomationSlice = (
43
60
  ~queryDbTableName,
44
61
  ~queryDbResources=[],
45
62
  ~context=?,
63
+ ~sourceTopics=Dict.make(),
64
+ ~consumesDcbLog=true,
46
65
  ) =>
47
66
  bundledInfos->Dict.set(
48
67
  name,
49
- {specModulePath, bodyModulePath, callbackType, queryDbTableName, queryDbResources, context},
68
+ {
69
+ specModulePath,
70
+ bodyModulePath,
71
+ callbackType,
72
+ queryDbTableName,
73
+ queryDbResources,
74
+ context,
75
+ sourceTopics,
76
+ consumesDcbLog,
77
+ },
50
78
  )
51
79
 
52
80
  type storedSpec = {
@@ -198,6 +226,16 @@ let finish = () =>
198
226
 
199
227
  let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
200
228
  envVars->Dict.set("HANDLER_CONFIG", handlerConfigOutput->Pulumi.Output.asInput)
229
+ // Deploy-derived capability endpoints — a geocoder URL today. Plugin-wide
230
+ // rather than per-handler, which is why they sit here and not inside
231
+ // HANDLER_CONFIG: this is one shared Lambda and every slice on it reaches
232
+ // the same capability. A capability the platform did not provision arrives
233
+ // as "", which its client reads as "not configured" and reports as a
234
+ // retryable `Unavailable` — a modelled outcome rather than a missing
235
+ // variable that fails as a crash.
236
+ PluginRuntime_Builder.capabilityEnv()->Array.forEach(((name, value)) =>
237
+ envVars->Dict.set(name, value->Pulumi.Output.asInput)
238
+ )
201
239
 
202
240
  let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
203
241
  ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/AutomationSliceEntryPoint.mjs",
@@ -249,9 +287,18 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
249
287
  let opts = {Pulumi.ComponentResource.parent: parent}
250
288
  let dcbOutputs: ReventlessCore.DcbEventLog.outputs =
251
289
  dcbEventLog->ReventlessCore.Component.outputs
290
+ // The plugin's DCB log, plus every Aggregate EventTopic any slice on this
291
+ // Lambda named. One channel spans them all: `connect` turns each topic
292
+ // into its own event-source mapping, and the entry point routes an
293
+ // incoming record to the handlers registered for its stream, so a slice
294
+ // only ever sees the sources it asked for.
252
295
  let eventTopics: ReventlessCore.EventTopic.allOutputs = Dict.fromArray([
253
296
  ("DcbEventLog", dcbOutputs.eventTopic),
254
297
  ])
298
+ bundledInfos->Dict.forEach(info =>
299
+ info.sourceTopics->Dict.forEachWithKey((topic, key) => eventTopics->Dict.set(key, topic))
300
+ )
301
+
255
302
  let channel = EventCollectorChannel.make(
256
303
  ~name="AllAutomationSlices",
257
304
  ~eventTopics,
@@ -259,13 +306,29 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
259
306
  ~opts,
260
307
  )
261
308
 
262
- // One DCB stream URN, identical for every slice on this plugin log — the
263
- // dispatch key the entry point maps event-source records back to.
264
- let sourceUrn = switch dcbOutputs.eventTopic.resources->Array.get(0) {
309
+ let dcbSourceUrn = switch dcbOutputs.eventTopic.resources->Array.get(0) {
265
310
  | Some(resource) => resource.urn
266
311
  | None => Pulumi.Output.make("")
267
312
  }
268
313
 
314
+ // The stream URNs this one slice listens on — the dispatch keys the entry
315
+ // point maps event-source records back to. Per slice rather than one
316
+ // shared DCB URN, which is what lets an Aggregate-sourced slice exist at
317
+ // all: its events arrive on the Aggregate's stream and would otherwise
318
+ // reach a registry that has never heard of them.
319
+ let sourceUrnsFor = (info: sliceInfo): Pulumi.Output.t<array<string>> => {
320
+ let urns = []
321
+ if info.consumesDcbLog {
322
+ urns->Array.push(dcbSourceUrn)->ignore
323
+ }
324
+ info.sourceTopics
325
+ ->Dict.valuesToArray
326
+ ->Array.forEach(topic =>
327
+ topic.resources->Array.forEach(resource => urns->Array.push(resource.urn)->ignore)
328
+ )
329
+ urns->Pulumi.Output.all
330
+ }
331
+
269
332
  let dcbQueueUrl = switch dcbQueueUrlRef.contents {
270
333
  | Some(url) => url
271
334
  | None => Pulumi.Output.make("NOT_AVAILABLE")
@@ -297,10 +360,16 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
297
360
  }
298
361
 
299
362
  let handlerJson =
300
- Pulumi.Output.all3((info.queryDbTableName, dcbQueueUrl, sourceUrn))
301
- ->Pulumi.Output.apply(((tableName, queueUrl, urn)) =>
302
- `{"specModule":${specModule},"bodyModule":${bodyModule},"callbackType":${callbackType},"queryDbTableName":"${tableName}","dcbQueueUrl":"${queueUrl}","sourceUrn":"${urn}"${contextFragment}}`
303
- )
363
+ Pulumi.Output.all3((info.queryDbTableName, dcbQueueUrl, sourceUrnsFor(info)))
364
+ ->Pulumi.Output.apply(((tableName, queueUrl, urns)) => {
365
+ let urnsJson =
366
+ urns->Array.map(JSON.Encode.string)->JSON.Encode.array->JSON.stringify
367
+ // `sourceUrn` stays beside `sourceUrns` so a handler config read by
368
+ // an entry point that predates multi-source still routes its first
369
+ // stream rather than none.
370
+ let firstUrn = urns->Array.get(0)->Option.getOr("")
371
+ `{"specModule":${specModule},"bodyModule":${bodyModule},"callbackType":${callbackType},"queryDbTableName":"${tableName}","dcbQueueUrl":"${queueUrl}","sourceUrn":"${firstUrn}","sourceUrns":${urnsJson}${contextFragment}}`
372
+ })
304
373
  let _ = handlerOutputs->Array.push(handlerJson)
305
374
  })
306
375
 
@@ -310,6 +379,16 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
310
379
 
311
380
  let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
312
381
  envVars->Dict.set("HANDLER_CONFIG", handlerConfigOutput->Pulumi.Output.asInput)
382
+ // Deploy-derived capability endpoints — a geocoder URL today. Plugin-wide
383
+ // rather than per-handler, which is why they sit here and not inside
384
+ // HANDLER_CONFIG: this is one shared Lambda and every slice on it reaches
385
+ // the same capability. A capability the platform did not provision arrives
386
+ // as "", which its client reads as "not configured" and reports as a
387
+ // retryable `Unavailable` — a modelled outcome rather than a missing
388
+ // variable that fails as a crash.
389
+ PluginRuntime_Builder.capabilityEnv()->Array.forEach(((name, value)) =>
390
+ envVars->Dict.set(name, value->Pulumi.Output.asInput)
391
+ )
313
392
 
314
393
  let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
315
394
  ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/AutomationSliceEntryPoint.mjs",
@@ -8,6 +8,7 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
8
8
  import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
9
9
  import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
10
10
  import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
11
+ import * as PluginRuntime_Builder$ReventlessAws from "../../plugin/runtime/PluginRuntime_Builder.res.mjs";
11
12
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
12
13
  import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
13
14
 
@@ -27,16 +28,20 @@ function getDcbQueueUrl() {
27
28
  return dcbQueueUrlRef.contents;
28
29
  }
29
30
 
30
- function registerAutomationSlice(name, specModulePath, bodyModulePath, callbackTypeOpt, queryDbTableName, queryDbResourcesOpt, context) {
31
+ function registerAutomationSlice(name, specModulePath, bodyModulePath, callbackTypeOpt, queryDbTableName, queryDbResourcesOpt, context, sourceTopicsOpt, consumesDcbLogOpt) {
31
32
  let callbackType = callbackTypeOpt !== undefined ? callbackTypeOpt : "automation";
32
33
  let queryDbResources = queryDbResourcesOpt !== undefined ? queryDbResourcesOpt : [];
34
+ let sourceTopics = sourceTopicsOpt !== undefined ? sourceTopicsOpt : ({});
35
+ let consumesDcbLog = consumesDcbLogOpt !== undefined ? consumesDcbLogOpt : true;
33
36
  bundledInfos[name] = {
34
37
  specModulePath: specModulePath,
35
38
  bodyModulePath: bodyModulePath,
36
39
  callbackType: callbackType,
37
40
  queryDbTableName: queryDbTableName,
38
41
  queryDbResources: queryDbResources,
39
- context: context
42
+ context: context,
43
+ sourceTopics: sourceTopics,
44
+ consumesDcbLog: consumesDcbLog
40
45
  };
41
46
  }
42
47
 
@@ -129,6 +134,9 @@ function finish() {
129
134
  let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => `{"handlers":[` + handlers.join(",") + `]}`);
130
135
  let envVars = {};
131
136
  envVars["HANDLER_CONFIG"] = handlerConfigOutput;
137
+ PluginRuntime_Builder$ReventlessAws.capabilityEnv().forEach(param => {
138
+ envVars[param[0]] = param[1];
139
+ });
132
140
  let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AutomationSliceEntryPoint.mjs", packageDirs, undefined);
133
141
  let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAutomationSlices", "Reactor", "AutomationSlice", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, undefined, opts);
134
142
  let channelSpecs = storedSpecs.map(param => param.channelSpec);
@@ -157,9 +165,24 @@ function finishWithDcbEventLog(dcbEventLog) {
157
165
  "DcbEventLog",
158
166
  dcbOutputs.eventTopic
159
167
  ]]);
168
+ Stdlib_Dict.forEach(bundledInfos, info => Stdlib_Dict.forEachWithKey(info.sourceTopics, (topic, key) => {
169
+ eventTopics[key] = topic;
170
+ }));
160
171
  let channel = EventCollectorChannel_DynamoDbStream$ReventlessAws.make("AllAutomationSlices", eventTopics, undefined, opts);
161
172
  let resource = dcbOutputs.eventTopic.resources[0];
162
- let sourceUrn = resource !== undefined ? resource.urn : Pulumi.output("");
173
+ let dcbSourceUrn = resource !== undefined ? resource.urn : Pulumi.output("");
174
+ let sourceUrnsFor = info => {
175
+ let urns = [];
176
+ if (info.consumesDcbLog) {
177
+ urns.push(dcbSourceUrn);
178
+ }
179
+ Object.values(info.sourceTopics).forEach(topic => {
180
+ topic.resources.forEach(resource => {
181
+ urns.push(resource.urn);
182
+ });
183
+ });
184
+ return Pulumi.all(urns);
185
+ };
163
186
  let url = dcbQueueUrlRef.contents;
164
187
  let dcbQueueUrl = url !== undefined ? url : Pulumi.output("NOT_AVAILABLE");
165
188
  let handlerOutputs = [];
@@ -181,13 +204,21 @@ function finishWithDcbEventLog(dcbEventLog) {
181
204
  let handlerJson = Pulumi.all([
182
205
  info.queryDbTableName,
183
206
  dcbQueueUrl,
184
- sourceUrn
185
- ]).apply(param => `{"specModule":` + specModule + `,"bodyModule":` + bodyModule + `,"callbackType":` + callbackType + `,"queryDbTableName":"` + param[0] + `","dcbQueueUrl":"` + param[1] + `","sourceUrn":"` + param[2] + `"` + contextFragment + `}`);
207
+ sourceUrnsFor(info)
208
+ ]).apply(param => {
209
+ let urns = param[2];
210
+ let urnsJson = JSON.stringify(urns.map(prim => prim));
211
+ let firstUrn = Stdlib_Option.getOr(urns[0], "");
212
+ return `{"specModule":` + specModule + `,"bodyModule":` + bodyModule + `,"callbackType":` + callbackType + `,"queryDbTableName":"` + param[0] + `","dcbQueueUrl":"` + param[1] + `","sourceUrn":"` + firstUrn + `","sourceUrns":` + urnsJson + contextFragment + `}`;
213
+ });
186
214
  handlerOutputs.push(handlerJson);
187
215
  });
188
216
  let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => `{"handlers":[` + handlers.join(",") + `]}`);
189
217
  let envVars = {};
190
218
  envVars["HANDLER_CONFIG"] = handlerConfigOutput;
219
+ PluginRuntime_Builder$ReventlessAws.capabilityEnv().forEach(param => {
220
+ envVars[param[0]] = param[1];
221
+ });
191
222
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AutomationSliceEntryPoint.mjs", packageDirs, undefined);
192
223
  let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAutomationSlices", "Reactor", "AutomationSlice", match.code, match.sourceCodeHash, envVars, 1024, 30, undefined, undefined, undefined, undefined, undefined, opts);
193
224
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllAutomationSlices", [{
@@ -331,26 +331,13 @@ let makeFromCodeAsset: (
331
331
  // is no race against Lambda's lazy auto-created group. Namespace/dimension
332
332
  // are CloudWatch-specific and live here, not in core (provider-neutral).
333
333
  if dcbMetrics {
334
- [
335
- "AppendRetry",
336
- "AppendConflict",
337
- "DcbDecisionModelCacheHit",
338
- "DcbDecisionModelCacheMiss",
339
- "DcbDecisionModelDeltaEventCount",
340
- ]->Array.forEach(metricName => {
334
+ Util_DcbMetrics.metricNames->Array.forEach(metricName => {
341
335
  let _ = Cloudwatch.LogMetricFilter.make(
342
336
  ~name=`${name}${metricName}Filter`,
343
337
  ~args={
344
- pattern: `{ $.reventlessMetric = "${metricName}" }`->Pulumi.Input.make,
338
+ pattern: Util_DcbMetrics.patternFor(metricName)->Pulumi.Input.make,
345
339
  logGroupName: logGroup.name->Pulumi.Output.asInput,
346
- metricTransformation: ({
347
- name: metricName->Pulumi.Input.make,
348
- namespace: "Reventless/DCB"->Pulumi.Input.make,
349
- value: "$.value"->Pulumi.Input.make,
350
- defaultValue: "0"->Pulumi.Input.make,
351
- unit: "Count"->Pulumi.Input.make,
352
- dimensions: Dict.fromArray([("slice", "$.slice")])->Pulumi.Input.make,
353
- }: Cloudwatch.LogMetricFilter.metricTransformation)->Pulumi.Input.make,
340
+ metricTransformation: Util_DcbMetrics.transformationFor(metricName)->Pulumi.Input.make,
354
341
  },
355
342
  ~opts?,
356
343
  )
@@ -20,6 +20,7 @@ import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
20
20
  import * as Util_Lambda$ReventlessAws from "../../util/Util_Lambda.res.mjs";
21
21
  import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
22
22
  import * as Util_IAM_Role$ReventlessAws from "../../util/Util_IAM_Role.res.mjs";
23
+ import * as Util_DcbMetrics$ReventlessAws from "../../util/Util_DcbMetrics.res.mjs";
23
24
  import * as Util_LocalConfig$ReventlessAws from "../../util/Util_LocalConfig.res.mjs";
24
25
  import * as Util_HostUiDomain$ReventlessAws from "../../util/Util_HostUiDomain.res.mjs";
25
26
  import * as Util_LogRetention$ReventlessAws from "../../util/Util_LogRetention.res.mjs";
@@ -136,27 +137,11 @@ function makeFromCodeAsset(name, unitKind, componentKind, code, sourceCodeHash,
136
137
  tags: tagsFor(name + `LogGroup`, "Logs")
137
138
  }, opts$1 !== undefined ? Primitive_option.valFromOption(opts$1) : undefined);
138
139
  if (dcbMetrics) {
139
- [
140
- "AppendRetry",
141
- "AppendConflict",
142
- "DcbDecisionModelCacheHit",
143
- "DcbDecisionModelCacheMiss",
144
- "DcbDecisionModelDeltaEventCount"
145
- ].forEach(metricName => {
140
+ Util_DcbMetrics$ReventlessAws.metricNames.forEach(metricName => {
146
141
  new (Aws.cloudwatch.LogMetricFilter)(name + metricName + `Filter`, {
147
- pattern: `{ $.reventlessMetric = "` + metricName + `" }`,
142
+ pattern: Util_DcbMetrics$ReventlessAws.patternFor(metricName),
148
143
  logGroupName: logGroup.name,
149
- metricTransformation: {
150
- name: metricName,
151
- namespace: "Reventless/DCB",
152
- value: "$.value",
153
- defaultValue: "0",
154
- unit: "Count",
155
- dimensions: Object.fromEntries([[
156
- "slice",
157
- "$.slice"
158
- ]])
159
- }
144
+ metricTransformation: Util_DcbMetrics$ReventlessAws.transformationFor(metricName)
160
145
  }, opts$1 !== undefined ? Primitive_option.valFromOption(opts$1) : undefined);
161
146
  });
162
147
  return;
@@ -37,6 +37,17 @@ module Make = (Api: {
37
37
  let tableResource = queryDbOutputs.resources->Array.getUnsafe(0)
38
38
  let queryDbTableName = tableResource.name
39
39
 
40
+ // An automation names its sources through its Mappings rather than a flat
41
+ // list, but the runtime question is the same one the outbound builder
42
+ // answers: which streams must this Lambda listen on. Unlike outbound, an
43
+ // automation's DCB log is already a key in `allEventTopics`, so the filter
44
+ // picks it up and nothing extra has to be assumed.
45
+ let sourceTopics =
46
+ allEventTopics->ReventlessCore.EventTopic.filter(sourceNames->Belt.Set.String.fromArray)
47
+ let consumesDcbLog =
48
+ sourceNames->Array.length == 0 ||
49
+ sourceNames->Array.some(name => !(allEventTopics->Dict.has(name)))
50
+
40
51
  EventCollectorRuntimeBuilder.registerAutomationSlice(
41
52
  ~name=Spec.name,
42
53
  ~specModulePath=Util_Bundle.getModuleSpecifier(Spec.moduleUrl),
@@ -45,6 +56,8 @@ module Make = (Api: {
45
56
  ~queryDbTableName,
46
57
  ~queryDbResources=queryDbOutputs.resources,
47
58
  ~context,
59
+ ~sourceTopics,
60
+ ~consumesDcbLog,
48
61
  )
49
62
 
50
63
  as_
@@ -1,6 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
3
4
  import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
5
+ import * as EventTopic$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventTopic/EventTopic.res.mjs";
4
6
  import * as Util_Bundle$ReventlessAws from "../util/Util_Bundle.res.mjs";
5
7
  import * as QueryDbStorage_DynamoDb$ReventlessAws from "../adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs";
6
8
  import * as AutomationSlice_Builder$ReventlessCore from "@reventlessdev/reventless-core/src/components/AutomationSlice/AutomationSlice_Builder.res.mjs";
@@ -36,19 +38,22 @@ function Make(Api) {
36
38
  })(Api);
37
39
  let Make$1 = Spec => (Automation => {
38
40
  let InnerMake = Inner.Make(Spec)(Automation);
41
+ let sourceNames = InnerMake.sourceNames;
39
42
  let make = (allEventTopics, publishJsons, context, runtime, opts) => {
40
43
  let as_ = InnerMake.make(allEventTopics, publishJsons, context, runtime, opts);
41
44
  let queryDbOutputs = Component$ReventlessCore.outputs(as_).queryDb;
42
45
  let tableResource = queryDbOutputs.resources[0];
43
46
  let queryDbTableName = tableResource.name;
44
- AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Automation.moduleUrl), "automation", queryDbTableName, queryDbOutputs.resources, context);
47
+ let sourceTopics = EventTopic$ReventlessCore.filter(allEventTopics, Belt_SetString.fromArray(sourceNames));
48
+ let consumesDcbLog = sourceNames.length === 0 || sourceNames.some(name => !(name in allEventTopics));
49
+ AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Automation.moduleUrl), "automation", queryDbTableName, queryDbOutputs.resources, context, sourceTopics, consumesDcbLog);
45
50
  return as_;
46
51
  };
47
52
  return {
48
53
  Spec: Spec,
49
54
  Automation: Automation,
50
55
  queryDbName: InnerMake.queryDbName,
51
- sourceNames: InnerMake.sourceNames,
56
+ sourceNames: sourceNames,
52
57
  make: make
53
58
  };
54
59
  });
@@ -38,13 +38,27 @@ module Make = (Api: {
38
38
  type component = InnerMake.component
39
39
  let queryDbName = InnerMake.queryDbName
40
40
 
41
- let make = (~dcbEventLog, ~publishJsons, ~runtime=?, ~opts=?): component => {
42
- let ots = InnerMake.make(~dcbEventLog, ~publishJsons, ~runtime?, ~opts?)
41
+ let make = (~dcbEventLog, ~allEventTopics=Dict.make(), ~publishJsons, ~runtime=?, ~opts=?): component => {
42
+ let ots = InnerMake.make(~dcbEventLog, ~allEventTopics, ~publishJsons, ~runtime?, ~opts?)
43
43
 
44
44
  let queryDbOutputs = (ots->ReventlessCore.Component.outputs).queryDb
45
45
  let tableResource = queryDbOutputs.resources->Array.getUnsafe(0)
46
46
  let queryDbTableName = tableResource.name
47
47
 
48
+ // Which streams this slice's Lambda must actually listen on. The core
49
+ // `make` above has already validated every declared name and thrown on a
50
+ // typo, so this only has to select — a name absent from `allEventTopics`
51
+ // is necessarily the plugin's own DCB log, the one source that dict does
52
+ // not carry.
53
+ let declaredSources = Spec.sourceNames
54
+ let sourceTopics =
55
+ allEventTopics->ReventlessCore.EventTopic.filter(
56
+ declaredSources->Belt.Set.String.fromArray,
57
+ )
58
+ let consumesDcbLog =
59
+ declaredSources->Array.length == 0 ||
60
+ declaredSources->Array.some(name => !(allEventTopics->Dict.has(name)))
61
+
48
62
  AutomationSliceRuntime_Builder_Single.registerAutomationSlice(
49
63
  ~name=Spec.name,
50
64
  ~specModulePath=Util_Bundle.getModuleSpecifier(Spec.moduleUrl),
@@ -52,6 +66,8 @@ module Make = (Api: {
52
66
  ~callbackType="outbound",
53
67
  ~queryDbTableName,
54
68
  ~queryDbResources=queryDbOutputs.resources,
69
+ ~sourceTopics,
70
+ ~consumesDcbLog,
55
71
  )
56
72
 
57
73
  ots
@@ -1,6 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
3
4
  import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
5
+ import * as EventTopic$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventTopic/EventTopic.res.mjs";
4
6
  import * as Util_Bundle$ReventlessAws from "../util/Util_Bundle.res.mjs";
5
7
  import * as QueryDbStorage_DynamoDb$ReventlessAws from "../adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs";
6
8
  import * as QueryDbResolvers_AppSync$ReventlessAws from "../adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs";
@@ -43,12 +45,16 @@ function Make(Api) {
43
45
  })(Api);
44
46
  let Make$1 = Spec => (Translation => {
45
47
  let InnerMake = Inner.Make(Spec)(Translation);
46
- let make = (dcbEventLog, publishJsons, runtime, opts) => {
47
- let ots = InnerMake.make(dcbEventLog, publishJsons, runtime, opts);
48
+ let make = (dcbEventLog, allEventTopicsOpt, publishJsons, runtime, opts) => {
49
+ let allEventTopics = allEventTopicsOpt !== undefined ? allEventTopicsOpt : ({});
50
+ let ots = InnerMake.make(dcbEventLog, allEventTopics, publishJsons, runtime, opts);
48
51
  let queryDbOutputs = Component$ReventlessCore.outputs(ots).queryDb;
49
52
  let tableResource = queryDbOutputs.resources[0];
50
53
  let queryDbTableName = tableResource.name;
51
- AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Translation.moduleUrl), "outbound", queryDbTableName, queryDbOutputs.resources, undefined);
54
+ let declaredSources = Spec.sourceNames;
55
+ let sourceTopics = EventTopic$ReventlessCore.filter(allEventTopics, Belt_SetString.fromArray(declaredSources));
56
+ let consumesDcbLog = declaredSources.length === 0 || declaredSources.some(name => !(name in allEventTopics));
57
+ AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Translation.moduleUrl), "outbound", queryDbTableName, queryDbOutputs.resources, undefined, sourceTopics, consumesDcbLog);
52
58
  return ots;
53
59
  };
54
60
  return {
@@ -20,6 +20,37 @@ let configRef: ref<adminConfig> = ref({
20
20
  clonerEnabled: false,
21
21
  })
22
22
 
23
+ /**
24
+ Deploy-derived environment for a plugin's slice Lambdas, keyed by variable name.
25
+
26
+ A capability the platform provisions — a geocoder, and the same shape whenever
27
+ the next one arrives — is reachable only through a value the platform computes:
28
+ a URL, an index name, a queue. The deployer never types it, which is what keeps
29
+ it out of `commandHandlerConfig` (memory, timeout, the knobs a human chooses)
30
+ and out of the hand-edited config files this framework exists to remove.
31
+
32
+ Kept apart from `configRef` because the two are filled at different moments and
33
+ by different halves: `registerConfig` runs inside `deployPlatform` and replaces
34
+ its record wholesale, while these values are only knowable in `deployPlugin`,
35
+ where a plugin stack reads them off the platform's stack reference.
36
+
37
+ Keyed by the variable name rather than by capability so the runtime builders
38
+ merge a dict they need no knowledge of. Naming a capability's variable is then
39
+ a single decision at the single call site that knows which capability it is —
40
+ not a table two modules have to agree on.
41
+ */
42
+ let capabilityEnvRef: dict<Pulumi.Output.t<string>> = Dict.make()
43
+
44
+ /** Registers one deploy-derived variable. Must be called before the plugin
45
+ builds — the slice runtimes read the dict when their finalizer runs. */
46
+ let registerCapabilityEnv = (name: string, value: Pulumi.Output.t<string>) =>
47
+ capabilityEnvRef->Dict.set(name, value)
48
+
49
+ /** The registered variables, for a runtime builder to merge into its Lambda's
50
+ environment. Empty is the ordinary answer for a platform that provisions no
51
+ capability, so this returns `[]` rather than throwing. */
52
+ let capabilityEnv = () => capabilityEnvRef->Dict.toArray
53
+
23
54
  type sliceModulePaths = {
24
55
  specPath: string,
25
56
  behaviorPath: string,
@@ -34,6 +34,16 @@ let configRef = {
34
34
  }
35
35
  };
36
36
 
37
+ let capabilityEnvRef = {};
38
+
39
+ function registerCapabilityEnv(name, value) {
40
+ capabilityEnvRef[name] = value;
41
+ }
42
+
43
+ function capabilityEnv() {
44
+ return Object.entries(capabilityEnvRef);
45
+ }
46
+
37
47
  let dcbConfigRef = {
38
48
  contents: {
39
49
  pluginName: "",
@@ -493,6 +503,9 @@ function Make(EventCollectorChannel) {
493
503
  export {
494
504
  log,
495
505
  configRef,
506
+ capabilityEnvRef,
507
+ registerCapabilityEnv,
508
+ capabilityEnv,
496
509
  dcbConfigRef,
497
510
  registeredSliceModulePaths,
498
511
  registerStateChangeSliceSpec,