@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
|
@@ -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 */
|
|
@@ -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());
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Typed cold-start core for the StateViewSlice Lambda entry point.
|
|
2
|
+
//
|
|
3
|
+
// The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
|
|
4
|
+
// StateViewSliceEntryPoint.mjs keeps only the inherently-untyped seam — the
|
|
5
|
+
// dynamic `import()` of spec/projection modules named in HANDLER_CONFIG and the
|
|
6
|
+
// reads of their exports. HANDLER_CONFIG parsing (including the compact-v2
|
|
7
|
+
// expansion), the QueryDb operation assembly, and the whole projection stream
|
|
8
|
+
// pipeline (envelope decode → sury event parse → project → handleAction) live
|
|
9
|
+
// here, fully type-checked; the QueryDb backend branch is shared with the
|
|
10
|
+
// ReadModel entry point in ProjectionEntryPoint_Ops and the routed dispatch
|
|
11
|
+
// boundary lives in StreamRoutedEntryPoint_Ops.
|
|
12
|
+
|
|
13
|
+
type handlerEntry = {
|
|
14
|
+
specModule: string,
|
|
15
|
+
projectionModule: string,
|
|
16
|
+
queryDbTableName: string,
|
|
17
|
+
sourceUrn: string,
|
|
18
|
+
// B3.3: per-slice AppSync Events channel root (list field name). Present only
|
|
19
|
+
// for subscription-enabled (Stream) Postgres view slices.
|
|
20
|
+
stateTopicName: option<string>,
|
|
21
|
+
pgConnection: option<PgConnection.connectionConfig>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The builder writes `pgConnection` via PgConnection.connectionConfigToJson, so
|
|
25
|
+
// the parsed object IS the record shape — cast at the documented JSON contract
|
|
26
|
+
// (the AggregateEntryPoint_Ops.asConnectionConfig pattern).
|
|
27
|
+
external asPgConnection: JSON.t => PgConnection.connectionConfig = "%identity"
|
|
28
|
+
|
|
29
|
+
let strOf = (obj: dict<JSON.t>, key: string): option<string> =>
|
|
30
|
+
obj->Dict.get(key)->Option.flatMap(JSON.Decode.string)
|
|
31
|
+
|
|
32
|
+
let pgConnectionOf = (obj: dict<JSON.t>): option<PgConnection.connectionConfig> =>
|
|
33
|
+
obj
|
|
34
|
+
->Dict.get("pgConnection")
|
|
35
|
+
->Option.filter(j => j != JSON.Null)
|
|
36
|
+
->Option.map(asPgConnection)
|
|
37
|
+
|
|
38
|
+
// HANDLER_CONFIG is emitted compact (StateViewSliceRuntime_Builder_Single) to
|
|
39
|
+
// stay under AWS Lambda's 4KB env-var limit: a shared module-path `base`, a
|
|
40
|
+
// shared `sourceUrn`, and a shared `pgConnection` are hoisted out (all of a
|
|
41
|
+
// plugin's slices follow the platform backend toggle), and per-handler keys are
|
|
42
|
+
// shortened to s/p/q/u/t. Expand back to the full handlerEntry shape. Legacy
|
|
43
|
+
// full-key entries pass through with their own per-entry fields.
|
|
44
|
+
let parseHandlerConfig = (rawJson: string): array<handlerEntry> =>
|
|
45
|
+
rawJson == ""
|
|
46
|
+
? []
|
|
47
|
+
: {
|
|
48
|
+
let obj =
|
|
49
|
+
rawJson
|
|
50
|
+
->JSON.parseOrThrow
|
|
51
|
+
->JSON.Decode.object
|
|
52
|
+
->Option.getOr(Dict.make())
|
|
53
|
+
let base = obj->strOf("base")->Option.getOr("")
|
|
54
|
+
let sharedUrn = obj->strOf("sourceUrn")->Option.getOr("")
|
|
55
|
+
let sharedPgConnection = obj->pgConnectionOf
|
|
56
|
+
obj
|
|
57
|
+
->Dict.get("handlers")
|
|
58
|
+
->Option.flatMap(JSON.Decode.array)
|
|
59
|
+
->Option.getOr([])
|
|
60
|
+
->Array.filterMap(item =>
|
|
61
|
+
item
|
|
62
|
+
->JSON.Decode.object
|
|
63
|
+
->Option.map(h =>
|
|
64
|
+
switch h->strOf("specModule") {
|
|
65
|
+
| Some(specModule) => {
|
|
66
|
+
specModule,
|
|
67
|
+
projectionModule: h->strOf("projectionModule")->Option.getOr(""),
|
|
68
|
+
queryDbTableName: h->strOf("queryDbTableName")->Option.getOr(""),
|
|
69
|
+
sourceUrn: h->strOf("sourceUrn")->Option.getOr(""),
|
|
70
|
+
stateTopicName: h->strOf("stateTopicName"),
|
|
71
|
+
pgConnection: h->pgConnectionOf,
|
|
72
|
+
}
|
|
73
|
+
| None => {
|
|
74
|
+
specModule: base ++ h->strOf("s")->Option.getOr(""),
|
|
75
|
+
projectionModule: base ++ h->strOf("p")->Option.getOr(""),
|
|
76
|
+
queryDbTableName: h->strOf("q")->Option.getOr(""),
|
|
77
|
+
sourceUrn: h->strOf("u")->Option.getOr(sharedUrn),
|
|
78
|
+
stateTopicName: h->strOf("t"),
|
|
79
|
+
pgConnection: sharedPgConnection,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Projection stream pipeline ──────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
// Events arrive as `{id, meta, recordedAt, event}` envelopes
|
|
89
|
+
// (Util_DynamoDbStream_Runtime.buildJsonEvent' / PgChangeFeedRelay). Surface
|
|
90
|
+
// `meta` + `recordedAt` to the projection as the `consumed` envelope;
|
|
91
|
+
// `recordedAt` defaults to "" if a producer omitted it. `meta` is already
|
|
92
|
+
// schema-shaped JSON written by the producer — cast at the documented contract
|
|
93
|
+
// (absent maps to undefined, exactly what the former JS passed through).
|
|
94
|
+
external asMeta: option<JSON.t> => Reventless.Message.meta = "%identity"
|
|
95
|
+
|
|
96
|
+
type envelope<'event> = {
|
|
97
|
+
event: JSON.t,
|
|
98
|
+
meta: Reventless.Message.meta,
|
|
99
|
+
recordedAt: string,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let decodeEnvelope = (json: JSON.t): envelope<'event> => {
|
|
103
|
+
let fields = json->JSON.Decode.object
|
|
104
|
+
{
|
|
105
|
+
event: switch fields->Option.flatMap(f => f->Dict.get("event")) {
|
|
106
|
+
| Some(JSON.Null) | None => json
|
|
107
|
+
| Some(event) => event
|
|
108
|
+
},
|
|
109
|
+
meta: fields->Option.flatMap(f => f->Dict.get("meta"))->asMeta,
|
|
110
|
+
recordedAt: fields
|
|
111
|
+
->Option.flatMap(f => f->Dict.get("recordedAt"))
|
|
112
|
+
->Option.flatMap(JSON.Decode.string)
|
|
113
|
+
->Option.getOr(""),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let exnMessage = StreamRoutedEntryPoint_Ops.exnMessage
|
|
118
|
+
|
|
119
|
+
// The typed core of buildJsonEventsHandler: decode each envelope, parse the
|
|
120
|
+
// event against the slice's consumed-event schema, project, and run every
|
|
121
|
+
// resulting action against the view table. A decode failure logs and yields no
|
|
122
|
+
// actions (the batch continues — at-least-once delivery would otherwise wedge
|
|
123
|
+
// the whole source on one malformed record).
|
|
124
|
+
let makeJsonEventsHandler = (
|
|
125
|
+
~sliceName: string,
|
|
126
|
+
~eventSchema: S.t<'event>,
|
|
127
|
+
~project: Reventless.StateViewSlice.consumed<'event> => array<
|
|
128
|
+
Reventless.Projection.action<string, JSON.t>,
|
|
129
|
+
>,
|
|
130
|
+
~queryDbOps: ReventlessCore.QueryDb_Adapter.operations,
|
|
131
|
+
~subIdConfig: option<Reventless.ReadModel.subIdConfig<JSON.t>>,
|
|
132
|
+
): ReventlessCore.EventCollector.jsonEventsHandler =>
|
|
133
|
+
stream =>
|
|
134
|
+
stream
|
|
135
|
+
->Stream.mapEffect(json =>
|
|
136
|
+
Effect.sync(() =>
|
|
137
|
+
try {
|
|
138
|
+
let {event, meta, recordedAt} = decodeEnvelope(json)
|
|
139
|
+
project({event: event->S.parseJsonOrThrow(eventSchema), meta, recordedAt})
|
|
140
|
+
} catch {
|
|
141
|
+
| exn =>
|
|
142
|
+
StreamRoutedEntryPoint_Ops.logError(
|
|
143
|
+
"failed to decode event",
|
|
144
|
+
{comp: "StateViewSliceRuntime", detail: exnMessage(exn)},
|
|
145
|
+
)
|
|
146
|
+
[]
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
->Stream.flatMap(actions => Stream.fromIterable(actions))
|
|
151
|
+
->Stream.runForEach(action =>
|
|
152
|
+
// Thread the slice's `subIdConfig` (generated from `@subId`) so
|
|
153
|
+
// sub-id-dependent actions like `UpdateMultiState` can resolve the sort
|
|
154
|
+
// key. Dropping it here made every such action hit the
|
|
155
|
+
// `MissingSubIdConfig` guard and silently write nothing, while the
|
|
156
|
+
// test-harness callback path (which threads it) stayed green. `None` for
|
|
157
|
+
// slices without an `@subId` is correct — those never emit sub-id
|
|
158
|
+
// actions. `~comp` is the slice's spec name — attribution for
|
|
159
|
+
// Projection.handleAction's debug-lazy action log.
|
|
160
|
+
Effect.promise(() =>
|
|
161
|
+
ReventlessCore.Projection.handleAction(~comp=sliceName, action, queryDbOps, subIdConfig)
|
|
162
|
+
)->Effect.map(_ => ())
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
// ── Handler assembly (called by the shell per HANDLER_CONFIG entry) ─────────
|
|
166
|
+
// `sliceModules` types the shell's reads off the two dynamically imported
|
|
167
|
+
// modules: the spec module exports `name` / `consumedEventSchema` / `config` /
|
|
168
|
+
// `subIdConfig`; the `project` function lives in the sibling projection module
|
|
169
|
+
// (`<Name>_Projection.res`).
|
|
170
|
+
|
|
171
|
+
type sliceModules<'event> = {
|
|
172
|
+
name: string,
|
|
173
|
+
consumedEventSchema: S.t<'event>,
|
|
174
|
+
project: Reventless.StateViewSlice.consumed<'event> => array<
|
|
175
|
+
Reventless.Projection.action<string, JSON.t>,
|
|
176
|
+
>,
|
|
177
|
+
config: option<ProjectionEntryPoint_Ops.specConfig>,
|
|
178
|
+
subIdConfig: option<Reventless.ReadModel.subIdConfig<JSON.t>>,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let makeRegisteredHandler = (
|
|
182
|
+
entry: handlerEntry,
|
|
183
|
+
modules: sliceModules<'event>,
|
|
184
|
+
): StreamRoutedEntryPoint_Ops.registeredHandler => {
|
|
185
|
+
let queryDbOps = ProjectionEntryPoint_Ops.makeQueryDbOps(
|
|
186
|
+
~queryDbTableName=entry.queryDbTableName,
|
|
187
|
+
~pgConnection=entry.pgConnection,
|
|
188
|
+
~stateTopicName=entry.stateTopicName,
|
|
189
|
+
~indexes=ProjectionEntryPoint_Ops.indexesOf(modules.config),
|
|
190
|
+
~subIdField=ProjectionEntryPoint_Ops.subIdFieldOf(modules.subIdConfig),
|
|
191
|
+
)
|
|
192
|
+
{
|
|
193
|
+
handler: StreamRoutedEntryPoint_Ops.toStreamHandler(
|
|
194
|
+
makeJsonEventsHandler(
|
|
195
|
+
~sliceName=modules.name,
|
|
196
|
+
~eventSchema=modules.consumedEventSchema,
|
|
197
|
+
~project=modules.project,
|
|
198
|
+
~queryDbOps,
|
|
199
|
+
~subIdConfig=modules.subIdConfig,
|
|
200
|
+
),
|
|
201
|
+
),
|
|
202
|
+
// Matches the `StateViewSlice(<name>)` shape the rest of the framework
|
|
203
|
+
// logs under.
|
|
204
|
+
comp: `StateViewSlice(${modules.name})`,
|
|
205
|
+
}
|
|
206
|
+
}
|