@reventlessdev/reventless-aws 3.0.0-alpha.269 → 3.0.0-alpha.270

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 (65) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +6 -6
  3. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -0
  4. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
  5. package/src/adapter/Api/Platform_UIFragments_Lambda.res +1 -0
  6. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +1 -1
  7. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +1 -1
  8. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +1 -0
  9. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +1 -1
  10. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +1 -0
  11. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +1 -1
  12. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +1 -1
  13. package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -1
  14. package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +1 -1
  15. package/src/adapter/Runtime/AggregateEntryPoint.mjs +6 -0
  16. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res.mjs +3 -3
  17. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res.mjs +3 -3
  18. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res.mjs +1 -1
  19. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res.mjs +1 -1
  20. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +1 -1
  21. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +1 -1
  22. package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +6 -0
  23. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +2 -2
  24. package/src/adapter/Runtime/CounterEntryPoint.mjs +6 -1
  25. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +6 -0
  26. package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +6 -1
  27. package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res.mjs +1 -1
  28. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +1 -1
  29. package/src/adapter/Runtime/EventMapperEntryPoint.mjs +6 -1
  30. package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +6 -1
  31. package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res.mjs +1 -1
  32. package/src/adapter/Runtime/HandlerFactoryHelpers.mjs +47 -0
  33. package/src/adapter/Runtime/HeartbeatEntryPoint.res +9 -0
  34. package/src/adapter/Runtime/HeartbeatEntryPoint.res.mjs +5 -0
  35. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res +9 -0
  36. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res.mjs +5 -1
  37. package/src/adapter/Runtime/PgMigrationEntryPoint.res +9 -0
  38. package/src/adapter/Runtime/PgMigrationEntryPoint.res.mjs +5 -1
  39. package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +6 -1
  40. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res +9 -0
  41. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res.mjs +4 -0
  42. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +6 -1
  43. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +28 -0
  44. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +32 -2
  45. package/src/adapter/Runtime/RuntimeExtensionEntryPoint_Ops.res +123 -0
  46. package/src/adapter/Runtime/RuntimeExtensionEntryPoint_Ops.res.mjs +75 -0
  47. package/src/adapter/Runtime/SideEffectEntryPoint.mjs +6 -0
  48. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +1 -1
  49. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +1 -1
  50. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +6 -0
  51. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +1 -1
  52. package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +6 -0
  53. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +1 -1
  54. package/src/adapter/StateTopic/StateTopic_AppSync.res +1 -0
  55. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +1 -1
  56. package/src/adapter/Upload/Upload_Claim_S3.res +1 -0
  57. package/src/adapter/Upload/Upload_Claim_S3.res.mjs +1 -1
  58. package/src/adapter/Upload/Upload_Presign_S3.res +1 -0
  59. package/src/adapter/Upload/Upload_Presign_S3.res.mjs +1 -1
  60. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +1 -1
  61. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +2 -2
  62. package/src/util/Util_Bundle.res +40 -0
  63. package/src/util/Util_Bundle.res.mjs +19 -1
  64. package/tests/RuntimeExtensionEntryPoint_OpsTest.res +110 -0
  65. package/tests/RuntimeExtensionEntryPoint_OpsTest.res.mjs +88 -0
@@ -47,7 +47,16 @@ let runMigration = async (
47
47
  }
48
48
  }
49
49
 
50
+ // Runtime extension seam: HandlerFactoryHelpers loads and fires any registered
51
+ // out-of-tree extension once, at module load. Awaiting its promise before this
52
+ // handler does any work is what makes "before the first request" a fact rather
53
+ // than a hope; it is already resolved on every invocation after the first, and
54
+ // resolves immediately when nothing is registered.
55
+ @module("./HandlerFactoryHelpers.mjs")
56
+ external runtimeExtensionsReady: promise<unit> = "runtimeExtensionsReady"
57
+
50
58
  let handler = async (_event: JSON.t, _context: PulumiAws.Lambda.context) => {
59
+ await runtimeExtensionsReady
51
60
  let config = NodeProcess.env->Dict.get("HANDLER_CONFIG")->Option.getOr("{}")->parseHandlerConfig
52
61
  await runMigration(config)
53
62
  }
@@ -24,7 +24,10 @@ async function runMigration(config, makePoolOpt) {
24
24
  return "ok";
25
25
  }
26
26
 
27
+ let runtimeExtensionsReady = HandlerFactoryHelpersMjs.runtimeExtensionsReady;
28
+
27
29
  async function handler(_event, _context) {
30
+ await runtimeExtensionsReady;
28
31
  let config = JSON.parse(Stdlib_Option.getOr(process.env["HANDLER_CONFIG"], "{}"));
29
32
  return await runMigration(config, undefined);
30
33
  }
@@ -32,6 +35,7 @@ async function handler(_event, _context) {
32
35
  export {
33
36
  logInfo,
34
37
  runMigration,
38
+ runtimeExtensionsReady,
35
39
  handler,
36
40
  }
37
- /* PgRuntime-ReventlessAws Not a pure module */
41
+ /* runtimeExtensionsReady Not a pure module */
@@ -17,12 +17,17 @@
17
17
  // live type-checked in PgQueryResolverEntryPoint_Ops.res.
18
18
 
19
19
  import { handler as dispatchHandler } from "@reventlessdev/reventless-aws/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs";
20
- import { patchSpecId, log } from "./HandlerFactoryHelpers.mjs";
20
+ import { patchSpecId, log, runtimeExtensionsReady } from "./HandlerFactoryHelpers.mjs";
21
21
  import * as Ops from "./PgQueryResolverEntryPoint_Ops.res.mjs";
22
22
 
23
23
  const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
24
24
 
25
25
  async function buildAllBindings() {
26
+ // Runtime extension seam: any registered out-of-tree extension gets its
27
+ // onColdStart before a single handler is built, which is what makes the
28
+ // framework's interception/publish hooks reachable here. No-op unless the
29
+ // deployment registered one.
30
+ await runtimeExtensionsReady;
26
31
  const config = Ops.parseResolverConfig(process.env["QUERY_RESOLVER_CONFIG"] || "");
27
32
  const pgConnection = config.pgConnection;
28
33
  if (!pgConnection) {
@@ -211,7 +211,16 @@ let comp = `ExtensionPoint(${ReventlessInfra.PluginExtensionPointSpec.name})`
211
211
 
212
212
  // === Exported handler ===
213
213
 
214
+ // Runtime extension seam: HandlerFactoryHelpers loads and fires any registered
215
+ // out-of-tree extension once, at module load. Awaiting its promise before this
216
+ // handler does any work is what makes "before the first request" a fact rather
217
+ // than a hope; it is already resolved on every invocation after the first, and
218
+ // resolves immediately when nothing is registered.
219
+ @module("./HandlerFactoryHelpers.mjs")
220
+ external runtimeExtensionsReady: promise<unit> = "runtimeExtensionsReady"
221
+
214
222
  let handler = async (event: PulumiAws.SQS.Queue.event, context: PulumiAws.Lambda.context) => {
223
+ await runtimeExtensionsReady
215
224
  setRequestId(context.awsRequestId)
216
225
  let records = event.records
217
226
 
@@ -179,7 +179,10 @@ let sqsHandler = CommandTopicChannel_SQS_Runtime$ReventlessAws.handleQueueEvent(
179
179
 
180
180
  let comp = `ExtensionPoint(` + PluginExtensionPointSpec$ReventlessInfra.name + `)`;
181
181
 
182
+ let runtimeExtensionsReady = HandlerFactoryHelpersMjs.runtimeExtensionsReady;
183
+
182
184
  async function handler(event, context) {
185
+ await runtimeExtensionsReady;
183
186
  HandlerFactoryHelpersMjs.setRequestId(context.awsRequestId);
184
187
  let records = event.Records;
185
188
  let prim0 = `processing ` + records.length.toString() + ` record(s)`;
@@ -221,6 +224,7 @@ export {
221
224
  queue,
222
225
  sqsHandler,
223
226
  comp,
227
+ runtimeExtensionsReady,
224
228
  handler,
225
229
  }
226
230
  /* config Not a pure module */
@@ -13,7 +13,7 @@
13
13
  // boundary) lives type-checked in ReadModelEntryPoint_Ops.res /
14
14
  // ProjectionEntryPoint_Ops.res / StreamRoutedEntryPoint_Ops.res.
15
15
 
16
- import { patchSpecId } from "./HandlerFactoryHelpers.mjs";
16
+ import { patchSpecId, runtimeExtensionsReady } from "./HandlerFactoryHelpers.mjs";
17
17
  import { Make as readModelCallbackMake } from "@reventlessdev/reventless-core/src/components/ReadModel/ReadModel_Callback.res.mjs";
18
18
  import * as StreamOps from "./StreamRoutedEntryPoint_Ops.res.mjs";
19
19
  import * as Ops from "./ReadModelEntryPoint_Ops.res.mjs";
@@ -33,6 +33,11 @@ function fixMappingsModule(mod) {
33
33
  }
34
34
 
35
35
  async function buildAllHandlers() {
36
+ // Runtime extension seam: any registered out-of-tree extension gets its
37
+ // onColdStart before a single handler is built, which is what makes the
38
+ // framework's interception/publish hooks reachable here. No-op unless the
39
+ // deployment registered one.
40
+ await runtimeExtensionsReady;
36
41
  const handlers = {};
37
42
  const entries = Ops.parseHandlerConfig(process.env["HANDLER_CONFIG"] || "");
38
43
 
@@ -257,6 +257,34 @@ let makeFromCodeAsset: (
257
257
  variables->Dict.set("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make)
258
258
  variables->Dict.set("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make)
259
259
 
260
+ // Runtime extension seam: the modules a cold start imports plus this runtime's
261
+ // identity, so an out-of-tree extension can register the framework's runtime
262
+ // callback hooks before the first request (see
263
+ // ReventlessCore.RuntimeExtension). Emitted here rather than folded into
264
+ // HANDLER_CONFIG for two reasons: every runtime builder already routes through
265
+ // makeFromCodeAsset with the kind and name the seam needs, so no call site
266
+ // changes; and HANDLER_CONFIG is already close to the 5120-byte
267
+ // UpdateFunctionConfiguration limit. Absent entirely when nothing is
268
+ // registered — an extension-free deployment gets the env it always had.
269
+ if !ReventlessCore.RuntimeExtension.isEmpty() {
270
+ let {ReventlessCore.ResourceAttribution.plugin: plugin, platform} =
271
+ ReventlessCore.ResourceAttribution.current.contents
272
+ let orNull = o => o->Option.mapOr(JSON.Null, s => JSON.String(s))
273
+ let config = JSON.Object(
274
+ Dict.fromArray([
275
+ (
276
+ "modules",
277
+ JSON.Array(Util_Bundle.runtimeExtensionSpecifiers()->Array.map(s => JSON.String(s))),
278
+ ),
279
+ ("runtimeKind", JSON.String(componentKind->ReventlessCore.ComponentType.toString)),
280
+ ("component", JSON.String(name)),
281
+ ("plugin", plugin->orNull),
282
+ ("platform", platform->orNull),
283
+ ]),
284
+ )
285
+ variables->Dict.set("RUNTIME_EXTENSIONS", config->JSON.stringify->Pulumi.Input.make)
286
+ }
287
+
260
288
  let tags = tagsFor(~resourceName=name, ~role=Runtime)
261
289
  let lambda = Lambda.Function.make(
262
290
  ~name,
@@ -20,11 +20,14 @@ 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 ComponentType$ReventlessCore from "@reventlessdev/reventless-core/src/ComponentType.res.mjs";
23
24
  import * as Util_DcbMetrics$ReventlessAws from "../../util/Util_DcbMetrics.res.mjs";
24
25
  import * as Util_LocalConfig$ReventlessAws from "../../util/Util_LocalConfig.res.mjs";
26
+ import * as RuntimeExtension$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/RuntimeExtension/RuntimeExtension.res.mjs";
25
27
  import * as Util_HostUiDomain$ReventlessAws from "../../util/Util_HostUiDomain.res.mjs";
26
28
  import * as Util_LogRetention$ReventlessAws from "../../util/Util_LogRetention.res.mjs";
27
29
  import * as Util_LambdaLogging$ReventlessAws from "../../util/Util_LambdaLogging.res.mjs";
30
+ import * as ResourceAttribution$ReventlessCore from "@reventlessdev/reventless-core/src/ResourceAttribution.res.mjs";
28
31
 
29
32
  let additionalEnvVars = {};
30
33
 
@@ -106,6 +109,33 @@ function makeFromCodeAsset(name, unitKind, componentKind, code, sourceCodeHash,
106
109
  Util_LambdaLogging$ReventlessAws.applyLogLevelDefault(variables);
107
110
  variables["NODE_OPTIONS"] = Util_Bundle$ReventlessAws.esmLoaderNodeOptions;
108
111
  variables["ESM_FALLBACK_DIRS"] = Util_Bundle$ReventlessAws.esmFallbackDirs;
112
+ if (!RuntimeExtension$ReventlessCore.isEmpty()) {
113
+ let match = ResourceAttribution$ReventlessCore.current.contents;
114
+ let orNull = o => Stdlib_Option.mapOr(o, null, s => (s));
115
+ let config = Object.fromEntries([
116
+ [
117
+ "modules",
118
+ Util_Bundle$ReventlessAws.runtimeExtensionSpecifiers().map(s => (s))
119
+ ],
120
+ [
121
+ "runtimeKind",
122
+ ComponentType$ReventlessCore.toString(componentKind)
123
+ ],
124
+ [
125
+ "component",
126
+ name
127
+ ],
128
+ [
129
+ "plugin",
130
+ orNull(match.plugin)
131
+ ],
132
+ [
133
+ "platform",
134
+ orNull(match.platform)
135
+ ]
136
+ ]);
137
+ variables["RUNTIME_EXTENSIONS"] = JSON.stringify(config);
138
+ }
109
139
  let tags = tagsFor(name, "Runtime");
110
140
  let lambda = new (Aws.lambda.Function)(name, {
111
141
  handler: "index.handler",
@@ -126,9 +156,9 @@ function makeFromCodeAsset(name, unitKind, componentKind, code, sourceCodeHash,
126
156
  })),
127
157
  vpcConfig: vpcConfig
128
158
  }, opts$1 !== undefined ? Primitive_option.valFromOption(opts$1) : undefined);
129
- let match = Util_LogRetention$ReventlessAws.managesLogGroup(stack, unmanagedStacks);
159
+ let match$1 = Util_LogRetention$ReventlessAws.managesLogGroup(stack, unmanagedStacks);
130
160
  let managedRetention = logRetentionDays !== undefined ? logRetentionDays : (
131
- match ? Util_LogRetention$ReventlessAws.retentionDaysFor(stack, prodStacks, Stdlib_Option.flatMap(Util_LocalConfig$ReventlessAws.get("logRetentionDays"), s => Stdlib_Int.fromString(s, undefined))) : undefined
161
+ match$1 ? Util_LogRetention$ReventlessAws.retentionDaysFor(stack, prodStacks, Stdlib_Option.flatMap(Util_LocalConfig$ReventlessAws.get("logRetentionDays"), s => Stdlib_Int.fromString(s, undefined))) : undefined
132
162
  );
133
163
  Stdlib_Option.forEach(managedRetention, days => {
134
164
  let logGroup = new (Aws.cloudwatch.LogGroup)(name + `LogGroup`, {
@@ -0,0 +1,123 @@
1
+ // Typed cold-start core for the runtime extension seam.
2
+ //
3
+ // The `.mjs` entry shells own one untyped seam — dynamically `import()`-ing the
4
+ // extension modules named in `RUNTIME_EXTENSIONS`, whose types are unknowable
5
+ // there. Everything either side of that lives here: parsing the env var, and
6
+ // invoking each module's `onColdStart` with the right arity.
7
+ //
8
+ // The invocation in particular must not sit in a shell. ReScript labelled
9
+ // arguments compile to positional ones, so a hand-written `.mjs` call is pinned
10
+ // to a signature the compiler never checks — the arity drift that cost this repo
11
+ // a production incident on the DCB command path (see the header of
12
+ // DcbCommandTopicEntryPoint_Ops.res). Typing the hooks as
13
+ // `ReventlessCore.RuntimeExtension.coldStartHook` makes the call site and the
14
+ // `Extension` module type move together.
15
+
16
+ let log = ReventlessCore.Logger.fromEnv()
17
+
18
+ /**
19
+ `RUNTIME_EXTENSIONS` as the deployed runtime receives it, written by
20
+ `RuntimeEnvironment_Lambda.makeFromCodeAsset`. The env var is absent whenever no
21
+ extension is registered, which is the only reason a runtime skips the seam.
22
+
23
+ `modules` are npm specifiers resolvable under `/var/task/node_modules` — the
24
+ extension packages ride in the code archive alongside the spec and behavior
25
+ packages. The remaining fields are this runtime's identity, resolved at
26
+ provisioning time from the ambient `ResourceAttribution` context.
27
+ */
28
+ type config = {
29
+ modules: array<string>,
30
+ runtimeKind: string,
31
+ component: string,
32
+ plugin: option<string>,
33
+ platform: option<string>,
34
+ }
35
+
36
+ let decodeString = (obj: dict<JSON.t>, key: string): option<string> =>
37
+ obj->Dict.get(key)->Option.flatMap(JSON.Decode.string)
38
+
39
+ /**
40
+ Parse the `RUNTIME_EXTENSIONS` env var. `None` for an absent, empty, malformed or
41
+ module-less value — all of which mean "no seam to fire", the default state.
42
+ Deliberately total: a config the runtime cannot read must not stop it from
43
+ serving requests.
44
+ */
45
+ let parseConfig = (raw: option<string>): option<config> =>
46
+ switch raw {
47
+ | None | Some("") => None
48
+ | Some(raw) =>
49
+ switch try raw->JSON.parseOrThrow->JSON.Decode.object catch {
50
+ | _ => None
51
+ } {
52
+ | None =>
53
+ log.error(~comp="RuntimeExtension", "RUNTIME_EXTENSIONS is not a JSON object; ignored")
54
+ None
55
+ | Some(obj) =>
56
+ let modules =
57
+ obj
58
+ ->Dict.get("modules")
59
+ ->Option.flatMap(JSON.Decode.array)
60
+ ->Option.getOr([])
61
+ ->Array.filterMap(JSON.Decode.string)
62
+ switch (modules->Array.length, obj->decodeString("runtimeKind"), obj->decodeString("component")) {
63
+ | (0, _, _) => None
64
+ | (_, Some(runtimeKind), Some(component)) =>
65
+ Some({
66
+ modules,
67
+ runtimeKind,
68
+ component,
69
+ plugin: obj->decodeString("plugin"),
70
+ platform: obj->decodeString("platform"),
71
+ })
72
+ | _ =>
73
+ log.error(
74
+ ~comp="RuntimeExtension",
75
+ "RUNTIME_EXTENSIONS is missing runtimeKind or component; ignored",
76
+ )
77
+ None
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ Fire every loaded extension's `onColdStart`, in the order the deploy program
84
+ registered them, each isolated from the others (see
85
+ `ReventlessCore.RuntimeExtension`).
86
+
87
+ An unrecognised `runtimeKind` means the layer's `ComponentType` and the
88
+ deploy-time one disagree — a framework version skew, not anything an extension
89
+ can fix. Logged at ERROR and skipped rather than fired under a guessed kind: an
90
+ extension that routes on kind would otherwise attach to the wrong runtime
91
+ silently.
92
+ */
93
+ let fire = (config: config, hooks: array<ReventlessCore.RuntimeExtension.coldStartHook>) =>
94
+ switch config.runtimeKind->ReventlessCore.ComponentType.ofString {
95
+ | None =>
96
+ log.error(
97
+ ~comp="RuntimeExtension",
98
+ `unknown runtimeKind "${config.runtimeKind}" in RUNTIME_EXTENSIONS; cold-start extensions skipped`,
99
+ )
100
+ | Some(runtimeKind) =>
101
+ log.debug(
102
+ ~comp="RuntimeExtension",
103
+ `firing ${hooks->Array.length->Int.toString} cold-start extension(s) for ${config.runtimeKind}(${config.component})`,
104
+ )
105
+ ReventlessCore.RuntimeExtension.notifyColdStartHooks(
106
+ ~hooks,
107
+ ~runtimeKind,
108
+ ~component=config.component,
109
+ ~plugin=config.plugin,
110
+ ~platform=config.platform,
111
+ )
112
+ }
113
+
114
+ /**
115
+ Report an extension module that could not be loaded, or that exports no
116
+ `onColdStart`. The shell owns the `import()`, so it owns the failure; this keeps
117
+ the message and level in one place with the rest of the seam's diagnostics.
118
+ */
119
+ let reportLoadFailure = (~specifier: string, ~reason: string) =>
120
+ log.error(
121
+ ~comp="RuntimeExtension",
122
+ `cold-start extension "${specifier}" could not be loaded; skipped: ${reason}`,
123
+ )
@@ -0,0 +1,75 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
4
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
7
+ import * as ComponentType$ReventlessCore from "@reventlessdev/reventless-core/src/ComponentType.res.mjs";
8
+ import * as RuntimeExtension$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/RuntimeExtension/RuntimeExtension.res.mjs";
9
+
10
+ let log = Logger$ReventlessCore.fromEnv();
11
+
12
+ function decodeString(obj, key) {
13
+ return Stdlib_Option.flatMap(obj[key], Stdlib_JSON.Decode.string);
14
+ }
15
+
16
+ function parseConfig(raw) {
17
+ if (raw === undefined) {
18
+ return;
19
+ }
20
+ if (raw === "") {
21
+ return;
22
+ }
23
+ let obj;
24
+ try {
25
+ obj = Stdlib_JSON.Decode.object(JSON.parse(raw));
26
+ } catch (exn) {
27
+ obj = undefined;
28
+ }
29
+ if (obj !== undefined) {
30
+ let modules = Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(obj["modules"], Stdlib_JSON.Decode.array), []), Stdlib_JSON.Decode.string);
31
+ let match = modules.length;
32
+ let match$1 = decodeString(obj, "runtimeKind");
33
+ let match$2 = decodeString(obj, "component");
34
+ if (match !== 0) {
35
+ if (match$1 !== undefined && match$2 !== undefined) {
36
+ return {
37
+ modules: modules,
38
+ runtimeKind: match$1,
39
+ component: match$2,
40
+ plugin: decodeString(obj, "plugin"),
41
+ platform: decodeString(obj, "platform")
42
+ };
43
+ } else {
44
+ log.error("RuntimeExtension", undefined, "RUNTIME_EXTENSIONS is missing runtimeKind or component; ignored");
45
+ return;
46
+ }
47
+ } else {
48
+ return;
49
+ }
50
+ }
51
+ log.error("RuntimeExtension", undefined, "RUNTIME_EXTENSIONS is not a JSON object; ignored");
52
+ }
53
+
54
+ function fire(config, hooks) {
55
+ let runtimeKind = ComponentType$ReventlessCore.ofString(config.runtimeKind);
56
+ if (runtimeKind !== undefined) {
57
+ log.debug("RuntimeExtension", undefined, `firing ` + hooks.length.toString() + ` cold-start extension(s) for ` + config.runtimeKind + `(` + config.component + `)`);
58
+ return RuntimeExtension$ReventlessCore.notifyColdStartHooks(hooks, runtimeKind, config.component, config.plugin, config.platform);
59
+ } else {
60
+ return log.error("RuntimeExtension", undefined, `unknown runtimeKind "` + config.runtimeKind + `" in RUNTIME_EXTENSIONS; cold-start extensions skipped`);
61
+ }
62
+ }
63
+
64
+ function reportLoadFailure(specifier, reason) {
65
+ log.error("RuntimeExtension", undefined, `cold-start extension "` + specifier + `" could not be loaded; skipped: ` + reason);
66
+ }
67
+
68
+ export {
69
+ log,
70
+ decodeString,
71
+ parseConfig,
72
+ fire,
73
+ reportLoadFailure,
74
+ }
75
+ /* log Not a pure module */
@@ -13,10 +13,16 @@
13
13
  import { Make as sideEffectHandlerCallbackMake } from "@reventlessdev/reventless-core/src/components/SideEffectHandler/SideEffectHandler_Callback.res.mjs";
14
14
  import * as StreamOps from "./StreamRoutedEntryPoint_Ops.res.mjs";
15
15
  import * as Ops from "./SideEffectEntryPoint_Ops.res.mjs";
16
+ import { runtimeExtensionsReady } from "./HandlerFactoryHelpers.mjs";
16
17
 
17
18
  const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
18
19
 
19
20
  async function buildAllHandlers() {
21
+ // Runtime extension seam: any registered out-of-tree extension gets its
22
+ // onColdStart before a single handler is built, which is what makes the
23
+ // framework's interception/publish hooks reachable here. No-op unless the
24
+ // deployment registered one.
25
+ await runtimeExtensionsReady;
20
26
  const handlers = {};
21
27
  const entries = Ops.parseHandlerConfig(process.env["HANDLER_CONFIG"] || "");
22
28
 
@@ -118,7 +118,7 @@ function finish() {
118
118
  return;
119
119
  }
120
120
  });
121
- let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/SideEffectEntryPoint.mjs", packageDirs, undefined);
121
+ let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/SideEffectEntryPoint.mjs", packageDirs, undefined, undefined);
122
122
  let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllSideEffectHandlers", "Reactor", "SideEffectHandler", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, undefined, opts);
123
123
  if (Object.keys(extraEnvVarsAll).length !== 0) {
124
124
  new (Aws.iam.RolePolicy)("AllSideEffectHandlers-appsyncSchemaPush", {
@@ -97,7 +97,7 @@ function forDcbCommandTopic(slicePaths, inboundSlicesOpt, dcbTableName, pluginNa
97
97
  });
98
98
  packageDirs["@reventlessdev/reventless-aws"] = Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws");
99
99
  packageDirs["@reventlessdev/reventless-core"] = Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-core");
100
- let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs", packageDirs, undefined);
100
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs", packageDirs, undefined, undefined);
101
101
  Stdlib_Option.forEach(cfg.sqsBatchSize, CommandTopicChannel_SQS$ReventlessAws.setBatchSize);
102
102
  let vpcConfig = pgSelection !== undefined ? pgSelection.securityGroupId.apply(sgId => ({
103
103
  subnetIds: pgSelection.subnetIds,
@@ -15,10 +15,16 @@
15
15
 
16
16
  import * as StreamOps from "./StreamRoutedEntryPoint_Ops.res.mjs";
17
17
  import * as Ops from "./StateViewSliceEntryPoint_Ops.res.mjs";
18
+ import { runtimeExtensionsReady } from "./HandlerFactoryHelpers.mjs";
18
19
 
19
20
  const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
20
21
 
21
22
  async function buildAllHandlers() {
23
+ // Runtime extension seam: any registered out-of-tree extension gets its
24
+ // onColdStart before a single handler is built, which is what makes the
25
+ // framework's interception/publish hooks reachable here. No-op unless the
26
+ // deployment registered one.
27
+ await runtimeExtensionsReady;
22
28
  const handlers = {};
23
29
  const entries = Ops.parseHandlerConfig(process.env["HANDLER_CONFIG"] || "");
24
30
 
@@ -153,7 +153,7 @@ function buildLambda(parent, handlerOutputs, packageDirs, channelSpecs, feedQueu
153
153
  }
154
154
  packageDirs["@reventlessdev/reventless-aws"] = Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws");
155
155
  packageDirs["@reventlessdev/reventless-core"] = Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-core");
156
- let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/StateViewSliceEntryPoint.mjs", packageDirs, undefined);
156
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/StateViewSliceEntryPoint.mjs", packageDirs, undefined, undefined);
157
157
  let vpcConfig = qdbSelection !== undefined ? qdbSelection.securityGroupId.apply(sgId => ({
158
158
  subnetIds: qdbSelection.subnetIds,
159
159
  securityGroupIds: [sgId]
@@ -13,10 +13,16 @@
13
13
  // TaskBucketEntryPoint_Ops.res.
14
14
 
15
15
  import * as Ops from "./TaskBucketEntryPoint_Ops.res.mjs";
16
+ import { runtimeExtensionsReady } from "./HandlerFactoryHelpers.mjs";
16
17
 
17
18
  const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
18
19
 
19
20
  async function buildHandler() {
21
+ // Runtime extension seam: any registered out-of-tree extension gets its
22
+ // onColdStart before a single handler is built, which is what makes the
23
+ // framework's interception/publish hooks reachable here. No-op unless the
24
+ // deployment registered one.
25
+ await runtimeExtensionsReady;
20
26
  const config = Ops.parseHandlerConfig(process.env["HANDLER_CONFIG"] || "");
21
27
  const callbackModule = await dynamicImport(config.callbackModule);
22
28
  return Ops.makeHandler(callbackModule.callback, config);
@@ -28,7 +28,7 @@ function forBucketCallback(param, connect, memorySizeOpt, timeoutOpt, name, call
28
28
  let packageDirs = {};
29
29
  let pkg = Util_Bundle$ReventlessAws.extractPackageName(callbackModulePath);
30
30
  packageDirs[pkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, pkg);
31
- let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/TaskBucketEntryPoint.mjs", packageDirs, undefined);
31
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/TaskBucketEntryPoint.mjs", packageDirs, undefined, undefined);
32
32
  let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(fullName, "Task", "Task", match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, {
33
33
  parent: resource
34
34
  });
@@ -207,6 +207,7 @@ let finish = (
207
207
  let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
208
208
  ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs",
209
209
  ~packageDirs,
210
+ ~bundleRuntimeExtensions=false,
210
211
  )
211
212
 
212
213
  let layers =
@@ -87,7 +87,7 @@ function finish(eventsApi, opts) {
87
87
  "@reventlessdev/reventless-aws",
88
88
  Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
89
89
  ]]);
90
- let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs", packageDirs, undefined);
90
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs", packageDirs, undefined, false);
91
91
  let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
92
92
  let appsyncEndpoint = AppSync_EventsApi$ReventlessAws.httpEndpoint(eventsApi);
93
93
  let lambda = new (Aws.lambda.Function)(name + "StateTopicPublisher", {
@@ -257,6 +257,7 @@ let finish = (
257
257
  let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
258
258
  ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Claim_S3_Ops.res.mjs",
259
259
  ~packageDirs,
260
+ ~bundleRuntimeExtensions=false,
260
261
  )
261
262
 
262
263
  let layers =
@@ -123,7 +123,7 @@ function finish(plugin, stores, iteratorAgeAlarmMsOpt, opts) {
123
123
  "@reventlessdev/reventless-aws",
124
124
  Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
125
125
  ]]);
126
- let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Claim_S3_Ops.res.mjs", packageDirs, undefined);
126
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Claim_S3_Ops.res.mjs", packageDirs, undefined, false);
127
127
  let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
128
128
  let lambda = new (Aws.lambda.Function)(name, {
129
129
  handler: "index.handler",
@@ -175,6 +175,7 @@ let make = (
175
175
  let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
176
176
  ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs",
177
177
  ~packageDirs,
178
+ ~bundleRuntimeExtensions=false,
178
179
  )
179
180
 
180
181
  let layers =
@@ -92,7 +92,7 @@ function make(api, stores, releaseWindowSecondsOpt, nameOpt, opts) {
92
92
  "@reventlessdev/reventless-aws",
93
93
  Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
94
94
  ]]);
95
- let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs", packageDirs, undefined);
95
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs", packageDirs, undefined, false);
96
96
  let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
97
97
  let lambda = new (Aws.lambda.Function)(name + "Lambda", {
98
98
  handler: "index.handler",
@@ -65,7 +65,7 @@ function forCommandTopic(param, connect, memorySizeOpt, timeoutOpt, param$1, par
65
65
  return `{"queueUrl":"` + queueUrl + `","pluginReadModelTableName":"` + rmTable + `","schedulerRoleArn":"` + schedRoleArn + `","schedulerQueueArn":"` + schedQueueArn + `","schedulerQueueName":"` + schedQueueName + `","publishToAggregates":{` + publishToAggregatesJson + `}}`;
66
66
  });
67
67
  envVars["HANDLER_CONFIG"] = handlerConfigJson;
68
- let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/PluginExtensionPointEntryPoint.res.mjs", {}, undefined);
68
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/PluginExtensionPointEntryPoint.res.mjs", {}, undefined, undefined);
69
69
  return connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, "CommandHandler", "ExtensionPoint", match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts));
70
70
  }
71
71
 
@@ -371,7 +371,7 @@ function Make(EventCollectorChannel) {
371
371
  let extraStringAssets = {};
372
372
  extraStringAssets["pluginDefinition.json"] = param[0];
373
373
  extraStringAssets["uiFragments.json"] = param[1];
374
- return Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/EventCollectorEntryPoint.mjs", packageDirs, extraStringAssets);
374
+ return Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/EventCollectorEntryPoint.mjs", packageDirs, extraStringAssets, undefined);
375
375
  });
376
376
  let codeOutput = bundleOutput.apply(b => b.code);
377
377
  let sourceCodeHashOutput = bundleOutput.apply(b => b.sourceCodeHash);
@@ -486,7 +486,7 @@ function Make(EventCollectorChannel) {
486
486
  envVars["EP_QUEUE_URL"] = epQueueUrl;
487
487
  envVars["PLUGIN_ID"] = Pulumi.output(hbConfig.pluginId);
488
488
  envVars["HEARTBEAT_TIMEOUT"] = Pulumi.output(hbConfig.heartbeatTimeout.toString());
489
- let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/HeartbeatEntryPoint.res.mjs", {}, undefined);
489
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/HeartbeatEntryPoint.res.mjs", {}, undefined, undefined);
490
490
  connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, "Scheduler", "Scheduler", match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts));
491
491
  };
492
492
  let forDcbCommandTopic = (param, connect, memorySizeOpt, timeoutOpt, dcbCommandTopic) => {