@reventlessdev/reventless-aws 3.0.0-alpha.231 → 3.0.0-alpha.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/package.json +7 -7
- package/src/adapter/Api/AppSyncEventsSigner_Ops.res +137 -0
- package/src/adapter/Api/AppSyncEventsSigner_Ops.res.mjs +121 -0
- package/src/adapter/Api/Platform_AdminScan_Ops.res +136 -0
- package/src/adapter/Api/Platform_AdminScan_Ops.res.mjs +98 -0
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +12 -90
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +11 -84
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +86 -0
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +79 -0
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +9 -79
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +7 -143
- package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res +48 -0
- package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res.mjs +63 -0
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +21 -80
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +23 -68
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res +70 -0
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res.mjs +68 -0
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -1
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -1
- package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +42 -126
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +246 -0
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +145 -0
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +27 -2
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +10 -3
- package/src/adapter/Runtime/CounterEntryPoint.mjs +19 -73
- package/src/adapter/Runtime/CounterEntryPoint_Ops.res +145 -0
- package/src/adapter/Runtime/CounterEntryPoint_Ops.res.mjs +129 -0
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +16 -3
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +11 -6
- package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +52 -510
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +816 -0
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +651 -0
- package/src/adapter/Runtime/EventMapperEntryPoint.mjs +23 -67
- package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +126 -0
- package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res.mjs +83 -0
- package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +16 -63
- package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +150 -0
- package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res.mjs +111 -0
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res +96 -0
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res.mjs +70 -0
- package/src/adapter/Runtime/PgMigrationEntryPoint.res +54 -0
- package/src/adapter/Runtime/PgMigrationEntryPoint.res.mjs +37 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +20 -55
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +155 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +94 -0
- package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +80 -0
- package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res.mjs +39 -0
- package/src/adapter/Runtime/QueryDbEntryPoint_Ops.res +4 -4
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +34 -153
- package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res +114 -0
- package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res.mjs +83 -0
- package/src/adapter/Runtime/SideEffectEntryPoint.mjs +23 -64
- package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res +71 -0
- package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res.mjs +57 -0
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +34 -165
- package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +206 -0
- package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res.mjs +105 -0
- package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +156 -0
- package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res.mjs +123 -0
- package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +18 -138
- package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +198 -0
- package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res.mjs +155 -0
- package/src/adapter/StateTopic/StateTopic_AppSync.res +12 -170
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +7 -167
- package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +208 -0
- package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +183 -0
- package/src/components/AutomationSlice_Builder.res +2 -0
- package/src/components/AutomationSlice_Builder.res.mjs +1 -1
- package/src/components/OutboundTranslationSlice_Builder.res +1 -0
- package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
- package/tests/AppSyncEventsSigner_OpsTest.res +75 -0
- package/tests/AppSyncEventsSigner_OpsTest.res.mjs +44 -0
- package/tests/AppSyncEventsSigner_ParityFixture.mjs +36 -0
- package/tests/AutomationSliceEntryPoint_OpsTest.res +165 -0
- package/tests/AutomationSliceEntryPoint_OpsTest.res.mjs +280 -0
- package/tests/CounterEntryPoint_OpsTest.res +115 -0
- package/tests/CounterEntryPoint_OpsTest.res.mjs +150 -0
- package/tests/EventCollectorEntryPoint_OpsTest.res +216 -0
- package/tests/EventCollectorEntryPoint_OpsTest.res.mjs +339 -0
- package/tests/EventMapperEntryPoint_OpsTest.res +103 -0
- package/tests/EventMapperEntryPoint_OpsTest.res.mjs +120 -0
- package/tests/ExtensionPointEntryPoint_OpsTest.res +57 -0
- package/tests/ExtensionPointEntryPoint_OpsTest.res.mjs +54 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res +22 -31
- package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +20 -23
- package/tests/PgQueryResolverEntryPoint_OpsTest.res +38 -0
- package/tests/PgQueryResolverEntryPoint_OpsTest.res.mjs +31 -0
- package/tests/Platform_AdminScan_OpsTest.res +80 -0
- package/tests/Platform_AdminScan_OpsTest.res.mjs +127 -0
- package/tests/ReadModelEntryPoint_OpsTest.res +141 -0
- package/tests/ReadModelEntryPoint_OpsTest.res.mjs +267 -0
- package/tests/SideEffectEntryPoint_OpsTest.res +79 -0
- package/tests/SideEffectEntryPoint_OpsTest.res.mjs +105 -0
- package/tests/StateViewSliceEntryPoint_OpsTest.res +167 -0
- package/tests/StateViewSliceEntryPoint_OpsTest.res.mjs +296 -0
- package/tests/TaskBucketEntryPoint_OpsTest.res +117 -0
- package/tests/TaskBucketEntryPoint_OpsTest.res.mjs +165 -0
- package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res +42 -13
- package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res.mjs +29 -6
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +0 -57
- package/src/adapter/Runtime/PgMigrationEntryPoint.mjs +0 -40
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
6
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Stream from "effect/Stream";
|
|
9
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
10
|
+
import * as Projection$ReventlessCore from "@reventlessdev/reventless-core/src/Projection.res.mjs";
|
|
11
|
+
import * as ProjectionEntryPoint_Ops$ReventlessAws from "./ProjectionEntryPoint_Ops.res.mjs";
|
|
12
|
+
import * as StreamRoutedEntryPoint_Ops$ReventlessAws from "./StreamRoutedEntryPoint_Ops.res.mjs";
|
|
13
|
+
|
|
14
|
+
function strOf(obj, key) {
|
|
15
|
+
return Stdlib_Option.flatMap(obj[key], Stdlib_JSON.Decode.string);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function pgConnectionOf(obj) {
|
|
19
|
+
return Stdlib_Option.map(Stdlib_Option.filter(obj["pgConnection"], j => j !== null), prim => prim);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseHandlerConfig(rawJson) {
|
|
23
|
+
if (rawJson === "") {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
let obj = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(rawJson)), {});
|
|
27
|
+
let base = Stdlib_Option.getOr(strOf(obj, "base"), "");
|
|
28
|
+
let sharedUrn = Stdlib_Option.getOr(strOf(obj, "sourceUrn"), "");
|
|
29
|
+
let sharedPgConnection = pgConnectionOf(obj);
|
|
30
|
+
return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(obj["handlers"], Stdlib_JSON.Decode.array), []), item => Stdlib_Option.map(Stdlib_JSON.Decode.object(item), h => {
|
|
31
|
+
let specModule = strOf(h, "specModule");
|
|
32
|
+
if (specModule !== undefined) {
|
|
33
|
+
return {
|
|
34
|
+
specModule: specModule,
|
|
35
|
+
projectionModule: Stdlib_Option.getOr(strOf(h, "projectionModule"), ""),
|
|
36
|
+
queryDbTableName: Stdlib_Option.getOr(strOf(h, "queryDbTableName"), ""),
|
|
37
|
+
sourceUrn: Stdlib_Option.getOr(strOf(h, "sourceUrn"), ""),
|
|
38
|
+
stateTopicName: strOf(h, "stateTopicName"),
|
|
39
|
+
pgConnection: pgConnectionOf(h)
|
|
40
|
+
};
|
|
41
|
+
} else {
|
|
42
|
+
return {
|
|
43
|
+
specModule: base + Stdlib_Option.getOr(strOf(h, "s"), ""),
|
|
44
|
+
projectionModule: base + Stdlib_Option.getOr(strOf(h, "p"), ""),
|
|
45
|
+
queryDbTableName: Stdlib_Option.getOr(strOf(h, "q"), ""),
|
|
46
|
+
sourceUrn: Stdlib_Option.getOr(strOf(h, "u"), sharedUrn),
|
|
47
|
+
stateTopicName: strOf(h, "t"),
|
|
48
|
+
pgConnection: sharedPgConnection
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function decodeEnvelope(json) {
|
|
55
|
+
let fields = Stdlib_JSON.Decode.object(json);
|
|
56
|
+
let event = Stdlib_Option.flatMap(fields, f => f["event"]);
|
|
57
|
+
let tmp;
|
|
58
|
+
tmp = event !== undefined && event !== null ? event : json;
|
|
59
|
+
return {
|
|
60
|
+
event: tmp,
|
|
61
|
+
meta: Stdlib_Option.flatMap(fields, f => f["meta"]),
|
|
62
|
+
recordedAt: Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(fields, f => f["recordedAt"]), Stdlib_JSON.Decode.string), "")
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function makeJsonEventsHandler(sliceName, eventSchema, project, queryDbOps, subIdConfig) {
|
|
67
|
+
return stream => Stream.runForEach(Stream.flatMap(Stream.mapEffect(stream, json => Effect.sync(() => {
|
|
68
|
+
try {
|
|
69
|
+
let match = decodeEnvelope(json);
|
|
70
|
+
return project({
|
|
71
|
+
event: S.parseJsonOrThrow(match.event, eventSchema),
|
|
72
|
+
meta: match.meta,
|
|
73
|
+
recordedAt: match.recordedAt
|
|
74
|
+
});
|
|
75
|
+
} catch (raw_exn) {
|
|
76
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
77
|
+
StreamRoutedEntryPoint_Ops$ReventlessAws.logError("failed to decode event", {
|
|
78
|
+
comp: "StateViewSliceRuntime",
|
|
79
|
+
detail: StreamRoutedEntryPoint_Ops$ReventlessAws.exnMessage(exn)
|
|
80
|
+
});
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
})), actions => Stream.fromIterable(actions)), action => Effect.map(Effect.promise(() => Projection$ReventlessCore.handleAction(sliceName, action, queryDbOps, subIdConfig)), param => {}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function makeRegisteredHandler(entry, modules) {
|
|
87
|
+
let queryDbOps = ProjectionEntryPoint_Ops$ReventlessAws.makeQueryDbOps(entry.queryDbTableName, entry.pgConnection, entry.stateTopicName, ProjectionEntryPoint_Ops$ReventlessAws.indexesOf(modules.config), ProjectionEntryPoint_Ops$ReventlessAws.subIdFieldOf(modules.subIdConfig));
|
|
88
|
+
return {
|
|
89
|
+
handler: StreamRoutedEntryPoint_Ops$ReventlessAws.toStreamHandler(makeJsonEventsHandler(modules.name, modules.consumedEventSchema, modules.project, queryDbOps, modules.subIdConfig)),
|
|
90
|
+
comp: `StateViewSlice(` + modules.name + `)`
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let exnMessage = StreamRoutedEntryPoint_Ops$ReventlessAws.exnMessage;
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
strOf,
|
|
98
|
+
pgConnectionOf,
|
|
99
|
+
parseHandlerConfig,
|
|
100
|
+
decodeEnvelope,
|
|
101
|
+
exnMessage,
|
|
102
|
+
makeJsonEventsHandler,
|
|
103
|
+
makeRegisteredHandler,
|
|
104
|
+
}
|
|
105
|
+
/* S Not a pure module */
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Typed routed dispatch boundary shared by the stream-routed Lambda entry
|
|
2
|
+
// points (ReadModelEntryPoint.mjs, StateViewSliceEntryPoint.mjs,
|
|
3
|
+
// AutomationSliceEntryPoint.mjs).
|
|
4
|
+
//
|
|
5
|
+
// The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
|
|
6
|
+
// all three shells route DynamoDB-stream / SQS-feed records to per-source-URN
|
|
7
|
+
// handler lists with per-handler log attribution — machinery the `.mjs` files
|
|
8
|
+
// previously duplicated verbatim. It lives here so every co-hosted component
|
|
9
|
+
// dispatches identically; the shells keep only their dynamic-import seams.
|
|
10
|
+
//
|
|
11
|
+
// Kept free of storage-backend imports (in particular the Postgres runtime,
|
|
12
|
+
// which lives in ProjectionEntryPoint_Ops): the AutomationSlice Lambda routes
|
|
13
|
+
// through this module but must not carry `pg`/PgRuntime in its cold-start
|
|
14
|
+
// graph.
|
|
15
|
+
//
|
|
16
|
+
// Runtime-pure: no `open PulumiAws` values — Pulumi appears in type positions
|
|
17
|
+
// only (erased).
|
|
18
|
+
|
|
19
|
+
// ── Shim bindings (HandlerFactoryHelpers.mjs) ───────────────────────────────
|
|
20
|
+
// The structured-log + Effect dispatch boundary shared by every deployed entry
|
|
21
|
+
// point.
|
|
22
|
+
|
|
23
|
+
type dispatchOpts = {
|
|
24
|
+
correlationId?: string,
|
|
25
|
+
causationId?: string,
|
|
26
|
+
comp?: string,
|
|
27
|
+
plugin?: string,
|
|
28
|
+
timestamp?: float,
|
|
29
|
+
retryCount?: int,
|
|
30
|
+
detail?: string,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@module("./HandlerFactoryHelpers.mjs")
|
|
34
|
+
external setRequestId: string => unit = "setRequestId"
|
|
35
|
+
@module("./HandlerFactoryHelpers.mjs")
|
|
36
|
+
external runEffect: (Effect.t<'a, 'e, 'r>, dispatchOpts) => promise<unit> = "runEffect"
|
|
37
|
+
@module("./HandlerFactoryHelpers.mjs")
|
|
38
|
+
external extractMetaField: (
|
|
39
|
+
array<PulumiAws.Lambda.CallbackFunction.record>,
|
|
40
|
+
string,
|
|
41
|
+
) => option<string> = "extractMetaField"
|
|
42
|
+
@module("./HandlerFactoryHelpers.mjs")
|
|
43
|
+
external extractSentTimestamp: array<PulumiAws.Lambda.CallbackFunction.record> => option<float> =
|
|
44
|
+
"extractSentTimestamp"
|
|
45
|
+
@module("./HandlerFactoryHelpers.mjs")
|
|
46
|
+
external extractRetryCount: array<PulumiAws.Lambda.CallbackFunction.record> => int =
|
|
47
|
+
"extractRetryCount"
|
|
48
|
+
@module("./HandlerFactoryHelpers.mjs") @scope("log")
|
|
49
|
+
external logDebug: (string, dispatchOpts) => unit = "debug"
|
|
50
|
+
@module("./HandlerFactoryHelpers.mjs") @scope("log")
|
|
51
|
+
external logWarn: (string, dispatchOpts) => unit = "warn"
|
|
52
|
+
@module("./HandlerFactoryHelpers.mjs") @scope("log")
|
|
53
|
+
external logError: (string, dispatchOpts) => unit = "error"
|
|
54
|
+
|
|
55
|
+
let exnMessage = (exn: exn): string =>
|
|
56
|
+
exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown error")
|
|
57
|
+
|
|
58
|
+
// ── Handler registry + routed dispatch boundary ─────────────────────────────
|
|
59
|
+
|
|
60
|
+
type streamHandler = (
|
|
61
|
+
PulumiAws.Lambda.CallbackFunction.event,
|
|
62
|
+
PulumiAws.Lambda.context,
|
|
63
|
+
) => Effect.t<unit, string, unit>
|
|
64
|
+
|
|
65
|
+
// One Lambda hosts every component of its kind, so each handler carries the
|
|
66
|
+
// comp of the component it runs (and, for read models, the owning plugin —
|
|
67
|
+
// the shared Lambda's own name carries no single plugin identity).
|
|
68
|
+
type registeredHandler = {
|
|
69
|
+
handler: streamHandler,
|
|
70
|
+
comp?: string,
|
|
71
|
+
plugin?: string,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let toStreamHandler = (
|
|
75
|
+
handleJsonEvents: ReventlessCore.EventCollector.jsonEventsHandler,
|
|
76
|
+
): streamHandler =>
|
|
77
|
+
(event, context) =>
|
|
78
|
+
EventCollectorChannel_DynamoDbStream_Runtime.handleStreamEvent(handleJsonEvents, event, context)
|
|
79
|
+
|
|
80
|
+
// Multiple components can share one source stream — e.g. every admin read
|
|
81
|
+
// model (Plugins, PluginHistory, PlatformEventGraph, UIFragmentRegistry)
|
|
82
|
+
// projects the Plugin aggregate's EventLog stream, and several automation
|
|
83
|
+
// slices can react to the same DcbEventLog. Accumulate ALL handlers per source
|
|
84
|
+
// URN; a plain `registry[urn] = handler` collapses them to one (whichever
|
|
85
|
+
// async builder wins the Promise.all race), silently dropping the rest.
|
|
86
|
+
let addToRegistry = (
|
|
87
|
+
registry: dict<array<registeredHandler>>,
|
|
88
|
+
sourceUrn: string,
|
|
89
|
+
handler: registeredHandler,
|
|
90
|
+
) =>
|
|
91
|
+
switch registry->Dict.get(sourceUrn) {
|
|
92
|
+
| Some(handlers) => handlers->Array.push(handler)
|
|
93
|
+
| None => registry->Dict.set(sourceUrn, [handler])
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let groupBySource = (
|
|
97
|
+
records: array<PulumiAws.Lambda.CallbackFunction.record>,
|
|
98
|
+
): dict<array<PulumiAws.Lambda.CallbackFunction.record>> => {
|
|
99
|
+
let grouped = Dict.make()
|
|
100
|
+
records->Array.forEach(record =>
|
|
101
|
+
switch grouped->Dict.get(record.eventSourceARN) {
|
|
102
|
+
| Some(existing) => existing->Array.push(record)
|
|
103
|
+
| None => grouped->Dict.set(record.eventSourceARN, [record])
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
grouped
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// The exported Lambda handler: group the batch by source ARN and run every
|
|
110
|
+
// handler registered for each source (independent storage, so concurrent is
|
|
111
|
+
// safe). Each handler carries its own comp so the shared Lambda's log lines
|
|
112
|
+
// stay separable per component.
|
|
113
|
+
let makeRoutedHandler = (
|
|
114
|
+
~comp: string,
|
|
115
|
+
registryPromise: promise<dict<array<registeredHandler>>>,
|
|
116
|
+
) =>
|
|
117
|
+
async (event: PulumiAws.Lambda.CallbackFunction.event, context: PulumiAws.Lambda.context) => {
|
|
118
|
+
setRequestId(context.awsRequestId)
|
|
119
|
+
let registry = await registryPromise
|
|
120
|
+
let _ =
|
|
121
|
+
await groupBySource(event.records)
|
|
122
|
+
->Dict.toArray
|
|
123
|
+
->Array.map(async ((arn, subRecords)) =>
|
|
124
|
+
switch registry->Dict.get(arn)->Option.filter(hs => hs->Array.length > 0) {
|
|
125
|
+
| Some(streamHandlers) =>
|
|
126
|
+
logDebug(
|
|
127
|
+
`found ${streamHandlers->Array.length->Int.toString} handler(s) for ${arn}`,
|
|
128
|
+
{comp: comp},
|
|
129
|
+
)
|
|
130
|
+
let correlationId = extractMetaField(subRecords, "correlationId")
|
|
131
|
+
let causationId = extractMetaField(subRecords, "causationId")
|
|
132
|
+
let timestamp = extractSentTimestamp(subRecords)
|
|
133
|
+
let retryCount = extractRetryCount(subRecords)
|
|
134
|
+
let subEvent: PulumiAws.Lambda.CallbackFunction.event = {records: subRecords}
|
|
135
|
+
let _ =
|
|
136
|
+
await streamHandlers
|
|
137
|
+
->Array.map(registered =>
|
|
138
|
+
runEffect(
|
|
139
|
+
registered.handler(subEvent, context),
|
|
140
|
+
{
|
|
141
|
+
correlationId: ?correlationId,
|
|
142
|
+
causationId: ?causationId,
|
|
143
|
+
comp: ?registered.comp,
|
|
144
|
+
plugin: ?registered.plugin,
|
|
145
|
+
timestamp: ?timestamp,
|
|
146
|
+
retryCount,
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
->Promise.all
|
|
151
|
+
| None => logWarn("no handler found: " ++ arn, {comp: comp})
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
->Promise.all
|
|
155
|
+
""
|
|
156
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as HandlerFactoryHelpersMjs from "./HandlerFactoryHelpers.mjs";
|
|
6
|
+
import * as EventCollectorChannel_DynamoDbStream_Runtime$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res.mjs";
|
|
7
|
+
|
|
8
|
+
function setRequestId(prim) {
|
|
9
|
+
HandlerFactoryHelpersMjs.setRequestId(prim);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function runEffect(prim0, prim1) {
|
|
13
|
+
return HandlerFactoryHelpersMjs.runEffect(prim0, prim1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function extractMetaField(prim0, prim1) {
|
|
17
|
+
return HandlerFactoryHelpersMjs.extractMetaField(prim0, prim1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function extractSentTimestamp(prim) {
|
|
21
|
+
return HandlerFactoryHelpersMjs.extractSentTimestamp(prim);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function extractRetryCount(prim) {
|
|
25
|
+
return HandlerFactoryHelpersMjs.extractRetryCount(prim);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function logDebug(prim0, prim1) {
|
|
29
|
+
HandlerFactoryHelpersMjs.log.debug(prim0, prim1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function logWarn(prim0, prim1) {
|
|
33
|
+
HandlerFactoryHelpersMjs.log.warn(prim0, prim1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function logError(prim0, prim1) {
|
|
37
|
+
HandlerFactoryHelpersMjs.log.error(prim0, prim1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function exnMessage(exn) {
|
|
41
|
+
return Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown error");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function toStreamHandler(handleJsonEvents) {
|
|
45
|
+
return (event, context) => EventCollectorChannel_DynamoDbStream_Runtime$ReventlessAws.handleStreamEvent(handleJsonEvents, event, context);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function addToRegistry(registry, sourceUrn, handler) {
|
|
49
|
+
let handlers = registry[sourceUrn];
|
|
50
|
+
if (handlers !== undefined) {
|
|
51
|
+
handlers.push(handler);
|
|
52
|
+
} else {
|
|
53
|
+
registry[sourceUrn] = [handler];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function groupBySource(records) {
|
|
58
|
+
let grouped = {};
|
|
59
|
+
records.forEach(record => {
|
|
60
|
+
let existing = grouped[record.eventSourceARN];
|
|
61
|
+
if (existing !== undefined) {
|
|
62
|
+
existing.push(record);
|
|
63
|
+
} else {
|
|
64
|
+
grouped[record.eventSourceARN] = [record];
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return grouped;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function makeRoutedHandler(comp, registryPromise) {
|
|
71
|
+
return async (event, context) => {
|
|
72
|
+
HandlerFactoryHelpersMjs.setRequestId(context.awsRequestId);
|
|
73
|
+
let registry = await registryPromise;
|
|
74
|
+
await Promise.all(Object.entries(groupBySource(event.Records)).map(async param => {
|
|
75
|
+
let subRecords = param[1];
|
|
76
|
+
let arn = param[0];
|
|
77
|
+
let streamHandlers = Stdlib_Option.filter(registry[arn], hs => hs.length !== 0);
|
|
78
|
+
if (streamHandlers !== undefined) {
|
|
79
|
+
let prim0 = `found ` + streamHandlers.length.toString() + ` handler(s) for ` + arn;
|
|
80
|
+
HandlerFactoryHelpersMjs.log.debug(prim0, {
|
|
81
|
+
comp: comp
|
|
82
|
+
});
|
|
83
|
+
let correlationId = HandlerFactoryHelpersMjs.extractMetaField(subRecords, "correlationId");
|
|
84
|
+
let causationId = HandlerFactoryHelpersMjs.extractMetaField(subRecords, "causationId");
|
|
85
|
+
let timestamp = HandlerFactoryHelpersMjs.extractSentTimestamp(subRecords);
|
|
86
|
+
let retryCount = HandlerFactoryHelpersMjs.extractRetryCount(subRecords);
|
|
87
|
+
let subEvent = {
|
|
88
|
+
Records: subRecords
|
|
89
|
+
};
|
|
90
|
+
await Promise.all(streamHandlers.map(registered => HandlerFactoryHelpersMjs.runEffect(registered.handler(subEvent, context), {
|
|
91
|
+
correlationId: correlationId,
|
|
92
|
+
causationId: causationId,
|
|
93
|
+
comp: registered.comp,
|
|
94
|
+
plugin: registered.plugin,
|
|
95
|
+
timestamp: timestamp,
|
|
96
|
+
retryCount: retryCount
|
|
97
|
+
})));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
HandlerFactoryHelpersMjs.log.warn("no handler found: " + arn, {
|
|
101
|
+
comp: comp
|
|
102
|
+
});
|
|
103
|
+
}));
|
|
104
|
+
return "";
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
setRequestId,
|
|
110
|
+
runEffect,
|
|
111
|
+
extractMetaField,
|
|
112
|
+
extractSentTimestamp,
|
|
113
|
+
extractRetryCount,
|
|
114
|
+
logDebug,
|
|
115
|
+
logWarn,
|
|
116
|
+
logError,
|
|
117
|
+
exnMessage,
|
|
118
|
+
toStreamHandler,
|
|
119
|
+
addToRegistry,
|
|
120
|
+
groupBySource,
|
|
121
|
+
makeRoutedHandler,
|
|
122
|
+
}
|
|
123
|
+
/* Stdlib_JsExn Not a pure module */
|
|
@@ -1,150 +1,30 @@
|
|
|
1
1
|
// TaskBucket Lambda entry point.
|
|
2
|
-
// At cold start: reads HANDLER_CONFIG, dynamically imports callback
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
// At cold start: reads HANDLER_CONFIG, dynamically imports the task callback
|
|
3
|
+
// module, and dispatches the resulting task actions (command publishing, CW
|
|
4
|
+
// Events schedules).
|
|
5
|
+
//
|
|
6
|
+
// Thin untyped shell ("typed core, thin shell" —
|
|
7
|
+
// docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
|
|
8
|
+
// inherently-untyped seam — the dynamic `import()` of the callback module named
|
|
9
|
+
// in HANDLER_CONFIG and the read of its `callback` export. Everything else
|
|
10
|
+
// (HANDLER_CONFIG parsing, S3-event handling via TaskBucket_S3_Runtime, the
|
|
11
|
+
// exhaustive task-action dispatch, per-aggregate command publishing, the
|
|
12
|
+
// CloudWatch Events scheduler ops) lives type-checked in
|
|
13
|
+
// TaskBucketEntryPoint_Ops.res.
|
|
14
|
+
|
|
15
|
+
import * as Ops from "./TaskBucketEntryPoint_Ops.res.mjs";
|
|
15
16
|
|
|
16
17
|
const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
|
|
17
18
|
|
|
18
|
-
// Mirrors ScheduledPublisher_CloudWatchEvents_Runtime.toScheduleExpression — kept
|
|
19
|
-
// in sync manually because that runtime helper isn't bundled in this Lambda.
|
|
20
|
-
function toScheduleExpression(rate) {
|
|
21
|
-
// rate is a serialized variant from Reventless.Schedule.rate
|
|
22
|
-
switch (rate.TAG) {
|
|
23
|
-
case "Single": {
|
|
24
|
-
const [year, month, day, hour, minute] = [rate._0, rate._1, rate._2, rate._3, rate._4];
|
|
25
|
-
return `cron(${minute} ${hour} ${day} ${month} ? ${year})`;
|
|
26
|
-
}
|
|
27
|
-
case "Minutes": {
|
|
28
|
-
const n = rate._0;
|
|
29
|
-
return `rate(${n} minute${n === 1 ? "" : "s"})`;
|
|
30
|
-
}
|
|
31
|
-
case "Hours": {
|
|
32
|
-
const n = rate._0;
|
|
33
|
-
return `rate(${n} hour${n === 1 ? "" : "s"})`;
|
|
34
|
-
}
|
|
35
|
-
case "Days": {
|
|
36
|
-
const n = rate._0;
|
|
37
|
-
return `rate(${n} day${n === 1 ? "" : "s"})`;
|
|
38
|
-
}
|
|
39
|
-
case "Daily":
|
|
40
|
-
return `cron(${rate._1} ${rate._0} * * * *)`;
|
|
41
|
-
case "Weekdays":
|
|
42
|
-
return `cron(${rate._1} ${rate._0} ? * MON-FRI *)`;
|
|
43
|
-
case "WeekdaysAndSaturday":
|
|
44
|
-
return `cron(${rate._1} ${rate._0} ? * MON-SAT *)`;
|
|
45
|
-
default:
|
|
46
|
-
throw new Error(`TaskBucketEntryPoint: unknown schedule rate ${JSON.stringify(rate)}`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let cachedScheduler = null;
|
|
51
|
-
function makeSchedulerOps(schedulerCfg) {
|
|
52
|
-
if (!schedulerCfg) return null;
|
|
53
|
-
if (cachedScheduler) return cachedScheduler;
|
|
54
|
-
const roleArn = process.env[schedulerCfg.roleArnEnv];
|
|
55
|
-
const targetArn = process.env[schedulerCfg.targetArnEnv];
|
|
56
|
-
const targetName = process.env[schedulerCfg.targetNameEnv];
|
|
57
|
-
if (!roleArn || !targetArn || !targetName) {
|
|
58
|
-
log.warn("scheduler env vars missing — schedules will no-op", { comp: "TaskBucketRuntime" });
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
const client = new CloudWatchEventsClient({});
|
|
62
|
-
cachedScheduler = {
|
|
63
|
-
createSchedule: async (schedule) => {
|
|
64
|
-
await client.send(new PutRuleCommand({
|
|
65
|
-
Name: schedule.name,
|
|
66
|
-
ScheduleExpression: toScheduleExpression(schedule.rate),
|
|
67
|
-
RoleArn: roleArn,
|
|
68
|
-
State: "ENABLED",
|
|
69
|
-
}));
|
|
70
|
-
await client.send(new PutTargetsCommand({
|
|
71
|
-
Rule: schedule.name,
|
|
72
|
-
Targets: [{ Arn: targetArn, Id: targetName, Input: schedule.payload }],
|
|
73
|
-
}));
|
|
74
|
-
},
|
|
75
|
-
deleteSchedule: async (name) => {
|
|
76
|
-
await client.send(new RemoveTargetsCommand({ Rule: name, Ids: [targetName] }));
|
|
77
|
-
await client.send(new DeleteRuleCommand({ Name: name }));
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
return cachedScheduler;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
19
|
async function buildHandler() {
|
|
84
|
-
const
|
|
85
|
-
const config = JSON.parse(configStr);
|
|
86
|
-
|
|
20
|
+
const config = Ops.parseHandlerConfig(process.env["HANDLER_CONFIG"] || "");
|
|
87
21
|
const callbackModule = await dynamicImport(config.callbackModule);
|
|
88
|
-
|
|
89
|
-
const handleEvents = handleBucketEvent(bucketCallback);
|
|
90
|
-
|
|
91
|
-
// Build publishCommands dict from env var queue URLs. The deploy-side builder
|
|
92
|
-
// writes HANDLER_CONFIG.publishToAggregates as { aggregateName: envVarName }
|
|
93
|
-
// (see TaskRuntime_Builder_PerBucket.res), so iterate accordingly.
|
|
94
|
-
const publishCommandsFns = {};
|
|
95
|
-
for (const [aggName, envVarName] of Object.entries(config.publishToAggregates || {})) {
|
|
96
|
-
const queueUrl = process.env[envVarName] || "";
|
|
97
|
-
if (queueUrl !== "") {
|
|
98
|
-
publishCommandsFns[aggName] = sqsPublishJsons(makeQueueRef(queueUrl), "SQS_FIFO");
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return async (event, _context) => {
|
|
103
|
-
const taskActions = await handleEvents(event, _context);
|
|
104
|
-
|
|
105
|
-
await Promise.all(taskActions.map(async action => {
|
|
106
|
-
switch (action.TAG) {
|
|
107
|
-
case "PublishCommands": {
|
|
108
|
-
const aggregateName = action._0;
|
|
109
|
-
const cmdJsons = action._1;
|
|
110
|
-
const pub = publishCommandsFns[aggregateName];
|
|
111
|
-
if (pub !== undefined) {
|
|
112
|
-
await pub(cmdJsons);
|
|
113
|
-
} else {
|
|
114
|
-
log.warn(`no publish function for aggregate "${aggregateName}"`, { comp: "TaskBucketRuntime" });
|
|
115
|
-
}
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
case "CreateSchedule": {
|
|
119
|
-
const ops = makeSchedulerOps(config.scheduler);
|
|
120
|
-
if (ops) {
|
|
121
|
-
await ops.createSchedule(action._0);
|
|
122
|
-
} else {
|
|
123
|
-
log.warn("CreateSchedule skipped — no scheduler configured for this Task (add sideEffects to setup)", { comp: "TaskBucketRuntime" });
|
|
124
|
-
}
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
case "DeleteSchedule": {
|
|
128
|
-
const ops = makeSchedulerOps(config.scheduler);
|
|
129
|
-
if (ops) {
|
|
130
|
-
await ops.deleteSchedule(action._0);
|
|
131
|
-
} else {
|
|
132
|
-
log.warn("DeleteSchedule skipped — no scheduler configured for this Task (add sideEffects to setup)", { comp: "TaskBucketRuntime" });
|
|
133
|
-
}
|
|
134
|
-
break;
|
|
135
|
-
}
|
|
136
|
-
default:
|
|
137
|
-
log.warn(`unknown task action TAG ${action.TAG}`, { comp: "TaskBucketRuntime" });
|
|
138
|
-
}
|
|
139
|
-
}));
|
|
140
|
-
|
|
141
|
-
return "";
|
|
142
|
-
};
|
|
22
|
+
return Ops.makeHandler(callbackModule.callback, config);
|
|
143
23
|
}
|
|
144
24
|
|
|
145
25
|
const initPromise = buildHandler();
|
|
146
26
|
|
|
147
27
|
export async function handler(event, context) {
|
|
148
|
-
const
|
|
149
|
-
return await
|
|
28
|
+
const bucketHandler = await initPromise;
|
|
29
|
+
return await bucketHandler(event, context);
|
|
150
30
|
}
|