@reventlessdev/reventless-aws 3.0.0-alpha.232 → 3.0.0-alpha.234
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.mjs +14 -1
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +22 -2
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +15 -4
- 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/RuntimeEnvironment_Lambda.res +26 -6
- 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/StateChangeSliceRuntime_Builder_Single.res +5 -2
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +1 -1
- 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/src/plugin/runtime/PluginRuntime_Builder.res +13 -4
- package/src/plugin/runtime/PluginRuntime_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
|
@@ -13,9 +13,28 @@ type iamPolicy = {
|
|
|
13
13
|
}
|
|
14
14
|
let additionalIamPolicies: array<iamPolicy> = []
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
16
|
+
// CallbackFunction path — the last serialized-closure Lambda builder on AWS.
|
|
17
|
+
//
|
|
18
|
+
// REACHABLE, despite most deployments going through makeFromCodeAsset. Three
|
|
19
|
+
// component builders instantiate ReventlessCore.EventCollectorRuntime_Builder_
|
|
20
|
+
// PerEventCollector.Make over this module — ForeignReadModel_Builder,
|
|
21
|
+
// SideEffectHandler_PerSideEffectHandler and Task_Builder_PerBucket — and that
|
|
22
|
+
// functor's forEventCollector calls `make` below. The live route is a Task whose
|
|
23
|
+
// spec returns `Task.sideEffects`: Task_Builder then constructs a
|
|
24
|
+
// SideEffectHandler, which provisions its collector Lambda here.
|
|
25
|
+
//
|
|
26
|
+
// A Lambda built this way is a serialized closure, and Pulumi's closure walker
|
|
27
|
+
// fails on Effect-TS — silently, so the function is simply never created and the
|
|
28
|
+
// deploy still reports success (see docs/plans/done/complete-bundled-migration.md,
|
|
29
|
+
// where an ordering-aws deploy came up four Lambdas short). Where it does
|
|
30
|
+
// serialize, it then resolves @aws-sdk against whatever the layer and the runtime
|
|
31
|
+
// each supply, carrying the cold-start SDK-skew failure compiled entry points
|
|
32
|
+
// were introduced to avoid. Converting these three builders to a compiled entry
|
|
33
|
+
// point is the fix; until then a side-effect-bearing Task is the exposed surface.
|
|
34
|
+
//
|
|
35
|
+
// `make` cannot simply be dropped: Runtime.Environment requires environmentMaker
|
|
36
|
+
// and the in-memory platform implements it for real (LocalRuntimeEnvironment
|
|
37
|
+
// registers the handler in a Deferred).
|
|
19
38
|
let make: ReventlessCore.Runtime.environmentMaker<'event, context, 'result, parts> = (
|
|
20
39
|
~name,
|
|
21
40
|
~handler,
|
|
@@ -27,8 +46,8 @@ let make: ReventlessCore.Runtime.environmentMaker<'event, context, 'result, part
|
|
|
27
46
|
let opts =
|
|
28
47
|
opts->Option.map(ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions)
|
|
29
48
|
|
|
30
|
-
//
|
|
31
|
-
//
|
|
49
|
+
// The environmentMaker signature carries no component kind, so callers cannot
|
|
50
|
+
// pass one through; attributed to the Platform rather than to an invented kind.
|
|
32
51
|
let tags = AWS.Tags.make(~name, ~kind=ReventlessCore.ComponentType.Platform, ~role=Runtime, ~scope=Platform)
|
|
33
52
|
|
|
34
53
|
let lambdaRole = IAM.Role.makeWithDefaultPolicy(
|
|
@@ -58,7 +77,8 @@ let make: ReventlessCore.Runtime.environmentMaker<'event, context, 'result, part
|
|
|
58
77
|
)
|
|
59
78
|
|
|
60
79
|
// Coerce CallbackFunction.t → Function.t (structurally compatible: both have arn, id, name).
|
|
61
|
-
//
|
|
80
|
+
// The coercion goes away with the path itself, once the three PerEventCollector
|
|
81
|
+
// builders above move to a compiled entry point.
|
|
62
82
|
let lambdaAsFunction: Pulumi.Output.t<PulumiAws.Lambda.Function.t> = lambda->Obj.magic
|
|
63
83
|
|
|
64
84
|
{
|
|
@@ -1,82 +1,41 @@
|
|
|
1
1
|
// SideEffectHandler Lambda entry point.
|
|
2
2
|
// At cold start: reads HANDLER_CONFIG, dynamically imports SideEffect modules,
|
|
3
3
|
// wires SideEffectHandler_Callback.Make, builds handler map keyed by source URN.
|
|
4
|
+
//
|
|
5
|
+
// Thin untyped shell ("typed core, thin shell" —
|
|
6
|
+
// docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
|
|
7
|
+
// seams that are inherently untyped — the dynamic `import()` of the side-effect
|
|
8
|
+
// modules named in HANDLER_CONFIG and the SideEffectHandler_Callback.Make
|
|
9
|
+
// functor application consuming them. HANDLER_CONFIG parsing and handler
|
|
10
|
+
// registration live type-checked in SideEffectEntryPoint_Ops.res; the routed
|
|
11
|
+
// dispatch boundary in StreamRoutedEntryPoint_Ops.res.
|
|
4
12
|
|
|
5
|
-
import {
|
|
6
|
-
log,
|
|
7
|
-
runEffect,
|
|
8
|
-
setRequestId,
|
|
9
|
-
extractMetaField,
|
|
10
|
-
extractSentTimestamp,
|
|
11
|
-
extractRetryCount,
|
|
12
|
-
} from "./HandlerFactoryHelpers.mjs";
|
|
13
13
|
import { Make as sideEffectHandlerCallbackMake } from "@reventlessdev/reventless-core/src/components/SideEffectHandler/SideEffectHandler_Callback.res.mjs";
|
|
14
|
-
import
|
|
14
|
+
import * as StreamOps from "./StreamRoutedEntryPoint_Ops.res.mjs";
|
|
15
|
+
import * as Ops from "./SideEffectEntryPoint_Ops.res.mjs";
|
|
15
16
|
|
|
16
17
|
const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
|
|
17
18
|
|
|
18
|
-
function groupBySource(records) {
|
|
19
|
-
const dict = {};
|
|
20
|
-
records.forEach(record => {
|
|
21
|
-
const arn = record.eventSourceARN;
|
|
22
|
-
const existing = dict[arn] || [];
|
|
23
|
-
dict[arn] = existing.concat([record]);
|
|
24
|
-
});
|
|
25
|
-
return dict;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
19
|
async function buildAllHandlers() {
|
|
29
|
-
const configStr = process.env["HANDLER_CONFIG"] || '{"handlers":[]}';
|
|
30
|
-
const config = JSON.parse(configStr);
|
|
31
20
|
const handlers = {};
|
|
21
|
+
const entries = Ops.parseHandlerConfig(process.env["HANDLER_CONFIG"] || "");
|
|
32
22
|
|
|
33
|
-
await Promise.all(
|
|
23
|
+
await Promise.all(entries.map(async (entry) => {
|
|
34
24
|
const sideEffectModules = await Promise.all(
|
|
35
|
-
|
|
25
|
+
entry.sideEffectModules.map(async modPath => await dynamicImport(modPath))
|
|
26
|
+
);
|
|
27
|
+
const callback = sideEffectHandlerCallbackMake({
|
|
28
|
+
sideEffects: sideEffectModules,
|
|
29
|
+
queryEngine: Ops.noopQueryEngine,
|
|
30
|
+
});
|
|
31
|
+
StreamOps.addToRegistry(
|
|
32
|
+
handlers,
|
|
33
|
+
entry.sourceUrn,
|
|
34
|
+
Ops.makeRegisteredHandler(entry, callback.handleJsonEvents)
|
|
36
35
|
);
|
|
37
|
-
const noOpQueryEngine = { scan: async () => [], query: async () => [] };
|
|
38
|
-
const callback = sideEffectHandlerCallbackMake({ sideEffects: sideEffectModules, queryEngine: noOpQueryEngine });
|
|
39
|
-
const handler = (event, context) => handleStreamEvent(callback.handleJsonEvents, event, context);
|
|
40
|
-
// Multiple side-effect handlers can share one source stream. Accumulate all
|
|
41
|
-
// per source ARN; a plain assignment collapses them to one (Promise.all
|
|
42
|
-
// race), silently dropping the rest. `comp`/`plugin` come from
|
|
43
|
-
// HANDLER_CONFIG — the shell has only module paths to identify a handler by.
|
|
44
|
-
(handlers[h.sourceUrn] ||= []).push({ handler, comp: h.comp, plugin: h.plugin });
|
|
45
36
|
}));
|
|
46
37
|
|
|
47
38
|
return handlers;
|
|
48
39
|
}
|
|
49
40
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
export async function handler(event, context) {
|
|
53
|
-
setRequestId(context?.awsRequestId);
|
|
54
|
-
const handlers = await initPromise;
|
|
55
|
-
const records = event.Records || [];
|
|
56
|
-
const grouped = groupBySource(records);
|
|
57
|
-
|
|
58
|
-
await Promise.all(Object.entries(grouped).map(async ([arn, subRecords]) => {
|
|
59
|
-
const streamHandlers = handlers[arn];
|
|
60
|
-
if (streamHandlers !== undefined && streamHandlers.length > 0) {
|
|
61
|
-
log.debug("found " + streamHandlers.length + " handler(s) for " + arn, { comp: "SideEffectRuntime" });
|
|
62
|
-
const correlationId = extractMetaField(subRecords, "correlationId");
|
|
63
|
-
const causationId = extractMetaField(subRecords, "causationId");
|
|
64
|
-
const timestamp = extractSentTimestamp(subRecords);
|
|
65
|
-
const retryCount = extractRetryCount(subRecords);
|
|
66
|
-
await Promise.all(streamHandlers.map(({ handler: h, comp, plugin }) =>
|
|
67
|
-
runEffect(h({ Records: subRecords }, context), {
|
|
68
|
-
correlationId,
|
|
69
|
-
causationId,
|
|
70
|
-
comp,
|
|
71
|
-
plugin,
|
|
72
|
-
timestamp,
|
|
73
|
-
retryCount,
|
|
74
|
-
})
|
|
75
|
-
));
|
|
76
|
-
} else {
|
|
77
|
-
log.warn("no handler found: " + arn, { comp: "SideEffectRuntime" });
|
|
78
|
-
}
|
|
79
|
-
}));
|
|
80
|
-
|
|
81
|
-
return "";
|
|
82
|
-
}
|
|
41
|
+
export const handler = StreamOps.makeRoutedHandler("SideEffectRuntime", buildAllHandlers());
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Typed cold-start core for the SideEffectHandler Lambda entry point.
|
|
2
|
+
//
|
|
3
|
+
// The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
|
|
4
|
+
// SideEffectEntryPoint.mjs keeps only the untyped seam — the dynamic `import()`
|
|
5
|
+
// of the side-effect modules named in HANDLER_CONFIG and the
|
|
6
|
+
// SideEffectHandler_Callback.Make functor application consuming them.
|
|
7
|
+
// HANDLER_CONFIG parsing and handler registration live here, fully
|
|
8
|
+
// type-checked; the routed dispatch boundary is shared with the other
|
|
9
|
+
// stream-routed entry points in StreamRoutedEntryPoint_Ops.
|
|
10
|
+
|
|
11
|
+
type handlerEntry = {
|
|
12
|
+
sourceUrn: string,
|
|
13
|
+
sideEffectModules: array<string>,
|
|
14
|
+
// Resolved at deploy time and baked into HANDLER_CONFIG
|
|
15
|
+
// (SideEffectHandlerRuntime_Builder_Single) — the shell has only module paths
|
|
16
|
+
// to identify a handler by.
|
|
17
|
+
comp: option<string>,
|
|
18
|
+
plugin: option<string>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let strOf = (obj: dict<JSON.t>, key: string): option<string> =>
|
|
22
|
+
obj->Dict.get(key)->Option.flatMap(JSON.Decode.string)
|
|
23
|
+
|
|
24
|
+
let decodeEntry = (json: JSON.t): option<handlerEntry> =>
|
|
25
|
+
json
|
|
26
|
+
->JSON.Decode.object
|
|
27
|
+
->Option.map(h => {
|
|
28
|
+
sourceUrn: h->strOf("sourceUrn")->Option.getOr(""),
|
|
29
|
+
sideEffectModules: h
|
|
30
|
+
->Dict.get("sideEffectModules")
|
|
31
|
+
->Option.flatMap(JSON.Decode.array)
|
|
32
|
+
->Option.getOr([])
|
|
33
|
+
->Array.filterMap(JSON.Decode.string),
|
|
34
|
+
comp: h->strOf("comp"),
|
|
35
|
+
plugin: h->strOf("plugin"),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
let parseHandlerConfig = (rawJson: string): array<handlerEntry> =>
|
|
39
|
+
rawJson == ""
|
|
40
|
+
? []
|
|
41
|
+
: rawJson
|
|
42
|
+
->JSON.parseOrThrow
|
|
43
|
+
->JSON.Decode.object
|
|
44
|
+
->Option.flatMap(obj => obj->Dict.get("handlers"))
|
|
45
|
+
->Option.flatMap(JSON.Decode.array)
|
|
46
|
+
->Option.getOr([])
|
|
47
|
+
->Array.filterMap(decodeEntry)
|
|
48
|
+
|
|
49
|
+
// Side effects are fire-and-forget consumers with no query access in the
|
|
50
|
+
// bundled handler — same restriction as the former shell's no-op stub.
|
|
51
|
+
let noopQueryEngine: Reventless.QueryEngine.operations = {
|
|
52
|
+
scan: async (~readModelName as _, ~filterConfigs as _, ~limit as _) => [],
|
|
53
|
+
query: async (
|
|
54
|
+
~readModelName as _,
|
|
55
|
+
~key as _=?,
|
|
56
|
+
~id as _,
|
|
57
|
+
~subIdConfig as _=?,
|
|
58
|
+
~filterConfigs as _=?,
|
|
59
|
+
~ascending as _=?,
|
|
60
|
+
~limit as _=?,
|
|
61
|
+
) => [],
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let makeRegisteredHandler = (
|
|
65
|
+
entry: handlerEntry,
|
|
66
|
+
handleJsonEvents: ReventlessCore.EventCollector.jsonEventsHandler,
|
|
67
|
+
): StreamRoutedEntryPoint_Ops.registeredHandler => {
|
|
68
|
+
handler: StreamRoutedEntryPoint_Ops.toStreamHandler(handleJsonEvents),
|
|
69
|
+
comp: ?entry.comp,
|
|
70
|
+
plugin: ?entry.plugin,
|
|
71
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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 StreamRoutedEntryPoint_Ops$ReventlessAws from "./StreamRoutedEntryPoint_Ops.res.mjs";
|
|
7
|
+
|
|
8
|
+
function strOf(obj, key) {
|
|
9
|
+
return Stdlib_Option.flatMap(obj[key], Stdlib_JSON.Decode.string);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function decodeEntry(json) {
|
|
13
|
+
return Stdlib_Option.map(Stdlib_JSON.Decode.object(json), h => ({
|
|
14
|
+
sourceUrn: Stdlib_Option.getOr(strOf(h, "sourceUrn"), ""),
|
|
15
|
+
sideEffectModules: Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(h["sideEffectModules"], Stdlib_JSON.Decode.array), []), Stdlib_JSON.Decode.string),
|
|
16
|
+
comp: strOf(h, "comp"),
|
|
17
|
+
plugin: strOf(h, "plugin")
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseHandlerConfig(rawJson) {
|
|
22
|
+
if (rawJson === "") {
|
|
23
|
+
return [];
|
|
24
|
+
} else {
|
|
25
|
+
return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(JSON.parse(rawJson)), obj => obj["handlers"]), Stdlib_JSON.Decode.array), []), decodeEntry);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function noopQueryEngine_scan(param, param$1, param$2) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function noopQueryEngine_query(param, param$1, param$2, param$3, param$4, param$5, param$6) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let noopQueryEngine = {
|
|
38
|
+
scan: noopQueryEngine_scan,
|
|
39
|
+
query: noopQueryEngine_query
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function makeRegisteredHandler(entry, handleJsonEvents) {
|
|
43
|
+
return {
|
|
44
|
+
handler: StreamRoutedEntryPoint_Ops$ReventlessAws.toStreamHandler(handleJsonEvents),
|
|
45
|
+
comp: entry.comp,
|
|
46
|
+
plugin: entry.plugin
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
strOf,
|
|
52
|
+
decodeEntry,
|
|
53
|
+
parseHandlerConfig,
|
|
54
|
+
noopQueryEngine,
|
|
55
|
+
makeRegisteredHandler,
|
|
56
|
+
}
|
|
57
|
+
/* StreamRoutedEntryPoint_Ops-ReventlessAws Not a pure module */
|
|
@@ -21,7 +21,10 @@ let log = ReventlessCore.Logger.fromEnv()
|
|
|
21
21
|
type inboundSlicePaths = {
|
|
22
22
|
specPath: string,
|
|
23
23
|
translationPath: string,
|
|
24
|
-
|
|
24
|
+
// Plain Output, never `option<Pulumi.Output.t<string>>` — see the note on
|
|
25
|
+
// `PluginRuntime_Builder.inboundSliceReg.auditTableName`. Empty string = no
|
|
26
|
+
// audit table (serialized as `null`).
|
|
27
|
+
auditTableName: Pulumi.Output.t<string>,
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
// `slicePaths`: `(specPath, behaviorPath)` per StateChangeSlice (already merged
|
|
@@ -142,7 +145,7 @@ let forDcbCommandTopic = (
|
|
|
142
145
|
| [] => Pulumi.Output.make("")
|
|
143
146
|
| slices =>
|
|
144
147
|
slices
|
|
145
|
-
->Array.map(s => s.auditTableName
|
|
148
|
+
->Array.map(s => s.auditTableName)
|
|
146
149
|
->Pulumi.Output.all
|
|
147
150
|
->Pulumi.Output.apply(tableNames => {
|
|
148
151
|
let entries =
|
|
@@ -59,7 +59,7 @@ function forDcbCommandTopic(slicePaths, inboundSlicesOpt, dcbTableName, pluginNa
|
|
|
59
59
|
return `{"spec":` + s + `,"behavior":` + b + `}`;
|
|
60
60
|
}).join(",");
|
|
61
61
|
let pgConnectionFragment = pgSelection !== undefined ? pgSelection.connectionConfig.apply(cc => `,"pgConnection":` + JSON.stringify(PgConnection$ReventlessAws.connectionConfigToJson(pgSelection.lockStrategy, cc))) : Pulumi.output("");
|
|
62
|
-
let inboundFragment = inboundSlices.length !== 0 ? Pulumi.all(inboundSlices.map(s =>
|
|
62
|
+
let inboundFragment = inboundSlices.length !== 0 ? Pulumi.all(inboundSlices.map(s => s.auditTableName)).apply(tableNames => {
|
|
63
63
|
let entries = inboundSlices.map((s, i) => {
|
|
64
64
|
let spec = Stdlib_Option.getOr(JSON.stringify(s.specPath), `""`);
|
|
65
65
|
let translation = Stdlib_Option.getOr(JSON.stringify(s.translationPath), `""`);
|
|
@@ -1,177 +1,46 @@
|
|
|
1
1
|
// StateViewSlice Lambda entry point.
|
|
2
|
-
// At cold start: reads HANDLER_CONFIG, dynamically imports Spec
|
|
3
|
-
// builds projection handler
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import
|
|
18
|
-
// Typed cold-start core — DynamoDB QueryDb ops, compiler-checked against the
|
|
19
|
-
// framework signatures (see the module header and
|
|
20
|
-
// docs/plans/minimize-lambda-entrypoint-mjs-shell.md).
|
|
21
|
-
import { makeDynamoQueryDbOps } from "./QueryDbEntryPoint_Ops.res.mjs";
|
|
22
|
-
import { opsFor as pgQdbOpsFor } from "@reventlessdev/reventless-aws/src/adapter/QueryDb/QueryDbStorage_Postgres_Runtime.res.mjs";
|
|
23
|
-
import { withLiveUpdates } from "./StateTopicPublish.mjs";
|
|
24
|
-
import { handleStreamEvent } from "@reventlessdev/reventless-aws/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res.mjs";
|
|
2
|
+
// At cold start: reads HANDLER_CONFIG, dynamically imports Spec/Projection
|
|
3
|
+
// modules, builds a projection handler per slice, and routes stream events by
|
|
4
|
+
// source URN.
|
|
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 spec/projection modules
|
|
9
|
+
// named in HANDLER_CONFIG and the reads of their exports. Everything else
|
|
10
|
+
// (HANDLER_CONFIG parsing incl. the compact-v2 expansion, the QueryDb operation
|
|
11
|
+
// assembly incl. the Postgres / live-update branch, the projection stream
|
|
12
|
+
// pipeline, the routed dispatch boundary) lives type-checked in
|
|
13
|
+
// StateViewSliceEntryPoint_Ops.res / ProjectionEntryPoint_Ops.res /
|
|
14
|
+
// StreamRoutedEntryPoint_Ops.res.
|
|
15
|
+
|
|
16
|
+
import * as StreamOps from "./StreamRoutedEntryPoint_Ops.res.mjs";
|
|
17
|
+
import * as Ops from "./StateViewSliceEntryPoint_Ops.res.mjs";
|
|
25
18
|
|
|
26
19
|
const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
|
|
27
20
|
|
|
28
|
-
// One Lambda hosts every state-view slice of a plugin, so each handler carries
|
|
29
|
-
// the comp of the slice it projects — matching the `StateViewSlice(<name>)`
|
|
30
|
-
// shape the rest of the framework logs under.
|
|
31
|
-
const stateViewComp = (sliceName) => `StateViewSlice(${sliceName})`;
|
|
32
|
-
|
|
33
|
-
function groupBySource(records) {
|
|
34
|
-
const dict = {};
|
|
35
|
-
records.forEach(record => {
|
|
36
|
-
const arn = record.eventSourceARN;
|
|
37
|
-
const existing = dict[arn] || [];
|
|
38
|
-
dict[arn] = existing.concat([record]);
|
|
39
|
-
});
|
|
40
|
-
return dict;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// `pgConnection`, when present, selects the Postgres QueryDb runtime for this
|
|
44
|
-
// slice's view table (`queryDbTableName` is then the slice spec name, the shared
|
|
45
|
-
// `qdb_<name>` discriminator). Absent → the DynamoDB path is byte-identical.
|
|
46
|
-
export function buildJsonEventsHandler(specModule, projectionModule, queryDbTableName, pgConnection, stateTopicName) {
|
|
47
|
-
let queryDbOps;
|
|
48
|
-
if (pgConnection) {
|
|
49
|
-
const indexes = (specModule.config && specModule.config.indexes) || [];
|
|
50
|
-
const subIdField = specModule.subIdConfig ? specModule.subIdConfig.subIdField : undefined;
|
|
51
|
-
const pgOps = pgQdbOpsFor(pgConnection, queryDbTableName, indexes, subIdField);
|
|
52
|
-
// B3.3: subscription-enabled (Stream) Postgres view slice → publish a
|
|
53
|
-
// live-update descriptor after save/delete. `stateTopicName` + APPSYNC_ENDPOINT
|
|
54
|
-
// gate it; absent → withLiveUpdates passes pgOps through. The view slice's id
|
|
55
|
-
// is the save's first arg (no id injection), so no wrap-ordering concern.
|
|
56
|
-
queryDbOps = withLiveUpdates(pgOps, {
|
|
57
|
-
endpoint: process.env.APPSYNC_ENDPOINT,
|
|
58
|
-
region: process.env.AWS_REGION,
|
|
59
|
-
topicName: stateTopicName,
|
|
60
|
-
subIdField,
|
|
61
|
-
});
|
|
62
|
-
} else {
|
|
63
|
-
queryDbOps = makeDynamoQueryDbOps(queryDbTableName);
|
|
64
|
-
}
|
|
65
|
-
// Spec module exports `consumedEventSchema`; the `project` function lives in
|
|
66
|
-
// the sibling projection module (`<Name>_Projection.res`).
|
|
67
|
-
const eventSchema = specModule.consumedEventSchema;
|
|
68
|
-
const project = projectionModule.project;
|
|
69
|
-
|
|
70
|
-
return stream => Stream.runForEach(
|
|
71
|
-
Stream.flatMap(
|
|
72
|
-
Stream.mapEffect(stream, json => Effect.sync(() => {
|
|
73
|
-
try {
|
|
74
|
-
// Events arrive as `{id, meta, recordedAt, event}` envelopes
|
|
75
|
-
// (Util_DynamoDbStream_Runtime.buildJsonEvent' / PgChangeFeedRelay).
|
|
76
|
-
// Surface `meta` + `recordedAt` to the projection as the `consumed`
|
|
77
|
-
// envelope; `recordedAt` defaults to "" if a producer omitted it.
|
|
78
|
-
const eventJson = json.event != null ? json.event : json;
|
|
79
|
-
const meta = json.meta;
|
|
80
|
-
const recordedAt = json.recordedAt != null ? json.recordedAt : "";
|
|
81
|
-
return project({ event: parseJsonOrThrow(eventJson, eventSchema), meta, recordedAt });
|
|
82
|
-
} catch (exn) {
|
|
83
|
-
log.error("failed to decode event", { comp: "StateViewSliceRuntime", detail: exn && exn.message ? exn.message : String(exn) });
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
86
|
-
})),
|
|
87
|
-
actions => Stream.fromIterable(actions)
|
|
88
|
-
),
|
|
89
|
-
action => Effect.map(
|
|
90
|
-
// Thread the slice's `subIdConfig` (generated from `@subId`) so sub-id-dependent
|
|
91
|
-
// actions like `UpdateMultiState` can resolve the sort key. Dropping it here made
|
|
92
|
-
// every such action hit the `MissingSubIdConfig` guard and silently write nothing,
|
|
93
|
-
// while the test-harness callback path (which threads it) stayed green. `undefined`
|
|
94
|
-
// for slices without an `@subId` is correct — those never emit sub-id actions.
|
|
95
|
-
// First positional arg is the compiled `~comp=` optional (attribution
|
|
96
|
-
// for Projection.handleAction's debug-lazy action log) — the slice's
|
|
97
|
-
// spec name. Compiled shape: (comp, action, operations, subIdConfig).
|
|
98
|
-
Effect.promise(() => handleAction(specModule.name, action, queryDbOps, specModule.subIdConfig)),
|
|
99
|
-
_ => {}
|
|
100
|
-
)
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// HANDLER_CONFIG is emitted compact (StateViewSliceRuntime_Builder_Single) to
|
|
105
|
-
// stay under AWS Lambda's 4KB env-var limit: a shared module-path `base` and a
|
|
106
|
-
// shared `sourceUrn` are hoisted out, and per-handler keys are shortened to
|
|
107
|
-
// s/p/q/u. Expand back to the full {specModule, projectionModule,
|
|
108
|
-
// queryDbTableName, sourceUrn} shape. Legacy full-key entries pass through.
|
|
109
|
-
function expandHandlers(config) {
|
|
110
|
-
const base = config.base || "";
|
|
111
|
-
const sharedUrn = config.sourceUrn || "";
|
|
112
|
-
return (config.handlers || []).map(h => {
|
|
113
|
-
if (h.specModule !== undefined) return h;
|
|
114
|
-
return {
|
|
115
|
-
specModule: base + h.s,
|
|
116
|
-
projectionModule: base + h.p,
|
|
117
|
-
queryDbTableName: h.q,
|
|
118
|
-
sourceUrn: h.u !== undefined ? h.u : sharedUrn,
|
|
119
|
-
// Shared across a plugin's slices (all follow the platform backend toggle);
|
|
120
|
-
// hoisted to the top level of the compressed config like base/sourceUrn.
|
|
121
|
-
pgConnection: config.pgConnection,
|
|
122
|
-
// B3.3: per-slice AppSync Events channel root (list field name). Present
|
|
123
|
-
// only for subscription-enabled (Stream) Postgres view slices.
|
|
124
|
-
stateTopicName: h.t,
|
|
125
|
-
};
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
21
|
async function buildAllHandlers() {
|
|
130
|
-
const configStr = process.env["HANDLER_CONFIG"] || '{"handlers":[]}';
|
|
131
|
-
const config = JSON.parse(configStr);
|
|
132
22
|
const handlers = {};
|
|
133
|
-
|
|
23
|
+
const entries = Ops.parseHandlerConfig(process.env["HANDLER_CONFIG"] || "");
|
|
24
|
+
|
|
25
|
+
await Promise.all(entries.map(async (entry) => {
|
|
134
26
|
const [specModule, projectionModule] = await Promise.all([
|
|
135
|
-
dynamicImport(
|
|
136
|
-
dynamicImport(
|
|
27
|
+
dynamicImport(entry.specModule),
|
|
28
|
+
dynamicImport(entry.projectionModule),
|
|
137
29
|
]);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
30
|
+
StreamOps.addToRegistry(
|
|
31
|
+
handlers,
|
|
32
|
+
entry.sourceUrn,
|
|
33
|
+
Ops.makeRegisteredHandler(entry, {
|
|
34
|
+
name: specModule.name,
|
|
35
|
+
consumedEventSchema: specModule.consumedEventSchema,
|
|
36
|
+
project: projectionModule.project,
|
|
37
|
+
config: specModule.config,
|
|
38
|
+
subIdConfig: specModule.subIdConfig,
|
|
39
|
+
})
|
|
40
|
+
);
|
|
143
41
|
}));
|
|
42
|
+
|
|
144
43
|
return handlers;
|
|
145
44
|
}
|
|
146
45
|
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
export async function handler(event, context) {
|
|
150
|
-
setRequestId(context?.awsRequestId);
|
|
151
|
-
const handlers = await initPromise;
|
|
152
|
-
const records = event.Records || [];
|
|
153
|
-
const grouped = groupBySource(records);
|
|
154
|
-
|
|
155
|
-
await Promise.all(Object.entries(grouped).map(async ([arn, subRecords]) => {
|
|
156
|
-
const streamHandlers = handlers[arn];
|
|
157
|
-
if (streamHandlers !== undefined && streamHandlers.length > 0) {
|
|
158
|
-
log.debug("found " + streamHandlers.length + " handler(s) for " + arn, { comp: "StateViewSliceRuntime" });
|
|
159
|
-
const correlationId = extractMetaField(subRecords, "correlationId");
|
|
160
|
-
const causationId = extractMetaField(subRecords, "causationId");
|
|
161
|
-
const timestamp = extractSentTimestamp(subRecords);
|
|
162
|
-
const retryCount = extractRetryCount(subRecords);
|
|
163
|
-
await Promise.all(streamHandlers.map(({ handler: h, comp }) =>
|
|
164
|
-
runEffect(h({ Records: subRecords }, context), {
|
|
165
|
-
correlationId,
|
|
166
|
-
causationId,
|
|
167
|
-
comp,
|
|
168
|
-
timestamp,
|
|
169
|
-
retryCount,
|
|
170
|
-
})
|
|
171
|
-
));
|
|
172
|
-
} else {
|
|
173
|
-
log.warn("no handler found: " + arn, { comp: "StateViewSliceRuntime" });
|
|
174
|
-
}
|
|
175
|
-
}));
|
|
176
|
-
return "";
|
|
177
|
-
}
|
|
46
|
+
export const handler = StreamOps.makeRoutedHandler("StateViewSliceRuntime", buildAllHandlers());
|