@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
|
@@ -2,90 +2,36 @@
|
|
|
2
2
|
// At cold start: reads HANDLER_CONFIG, dynamically imports Target Spec and Mappings,
|
|
3
3
|
// wires Counter_Callback.Make + EventMapper_Callback.MakeCounterHandler.
|
|
4
4
|
// Routes DynamoDB Stream events from references and counts tables.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
//
|
|
6
|
+
// Thin untyped shell ("typed core, thin shell" —
|
|
7
|
+
// docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
|
|
8
|
+
// seams that are inherently untyped — the dynamic `import()` of the
|
|
9
|
+
// target-spec/mappings modules named in HANDLER_CONFIG, the `patchSpecId`
|
|
10
|
+
// fix-up, and the MakeCounterHandler functor application consuming them.
|
|
11
|
+
// HANDLER_CONFIG parsing, the Counter_Callback.Make application, and the
|
|
12
|
+
// references/counts stream routing live type-checked in
|
|
13
|
+
// CounterEntryPoint_Ops.res.
|
|
14
|
+
|
|
15
|
+
import { patchSpecId } from "./HandlerFactoryHelpers.mjs";
|
|
10
16
|
import { MakeCounterHandler } from "@reventlessdev/reventless-core/src/components/EventMapper/EventMapper_Callback.res.mjs";
|
|
11
|
-
import
|
|
12
|
-
import { publishJsons as sqsPublishJsons } from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
17
|
+
import * as Ops from "./CounterEntryPoint_Ops.res.mjs";
|
|
13
18
|
|
|
14
19
|
const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
id: s.m(suryString),
|
|
19
|
-
inc: s.m(suryInt),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
async function buildHandler() {
|
|
23
|
-
const configStr = process.env["HANDLER_CONFIG"] || "{}";
|
|
24
|
-
const config = JSON.parse(configStr);
|
|
21
|
+
async function build() {
|
|
22
|
+
const config = Ops.parseHandlerConfig(process.env["HANDLER_CONFIG"] || "{}");
|
|
25
23
|
|
|
26
24
|
const targetSpecModule = await dynamicImport(config.targetSpecModule);
|
|
27
25
|
const mappingsModule = await dynamicImport(config.mappingsModule);
|
|
28
26
|
|
|
29
27
|
const patchedTarget = patchSpecId(targetSpecModule);
|
|
30
|
-
const
|
|
31
|
-
const countsDbCount = qdbCount(countsTable);
|
|
32
|
-
|
|
33
|
-
const pubJsons = sqsPublishJsons(makeQueueRef(config.publishQueueUrl), "SQS_FIFO");
|
|
34
|
-
const queryEngine = { query: async () => [], queryAll: async () => [] };
|
|
28
|
+
const counterHandler = MakeCounterHandler(patchedTarget)(mappingsModule)(Ops.makeCounterOps(config));
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
const callback = counterCallbackMake({
|
|
38
|
-
name: "BundledCounter",
|
|
39
|
-
countsDbCount,
|
|
40
|
-
jsonEventsHandler: counterHandler.handleCounterEvents,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return [config.referencesStreamArn, config.countsStreamArn, callback];
|
|
30
|
+
return Ops.makeHandler(config, counterHandler.handleCounterEvents);
|
|
44
31
|
}
|
|
45
32
|
|
|
46
|
-
const initPromise =
|
|
47
|
-
|
|
48
|
-
export async function handler(event, _context) {
|
|
49
|
-
const [referencesStreamArn, countsStreamArn, callback] = await initPromise;
|
|
50
|
-
const records = event.Records || [];
|
|
51
|
-
|
|
52
|
-
const dynamoDbRecords = records.filter(r =>
|
|
53
|
-
r.eventSource === "aws:dynamodb" &&
|
|
54
|
-
(r.eventSourceARN === referencesStreamArn || r.eventSourceARN === countsStreamArn)
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const referenceRecords = dynamoDbRecords.filter(r => r.eventSourceARN === referencesStreamArn);
|
|
58
|
-
const countRecords = dynamoDbRecords.filter(r => r.eventSourceARN === countsStreamArn);
|
|
59
|
-
|
|
60
|
-
const references = referenceRecords.flatMap(record => {
|
|
61
|
-
const state = parseDynamoDbStreamRecordState(record);
|
|
62
|
-
switch (state.TAG) {
|
|
63
|
-
case 0: { // NewImage(id, newImage)
|
|
64
|
-
const id = state._0;
|
|
65
|
-
let inc;
|
|
66
|
-
try {
|
|
67
|
-
inc = parseJsonOrThrow(state._1, referencesViewSchema).inc;
|
|
68
|
-
} catch (_) {
|
|
69
|
-
inc = 1;
|
|
70
|
-
}
|
|
71
|
-
return [[id, inc]];
|
|
72
|
-
}
|
|
73
|
-
case 2: // NewAndOldImage — duplicate
|
|
74
|
-
log.debug("ignoring duplicate id: " + state._0, { comp: "CounterEntryPoint" });
|
|
75
|
-
return [];
|
|
76
|
-
default:
|
|
77
|
-
return [];
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const counts = countRecords.flatMap(record => {
|
|
82
|
-
const state = parseDynamoDbStreamRecordState(record);
|
|
83
|
-
if (state.TAG === 0 || state.TAG === 2) {
|
|
84
|
-
return [state._1];
|
|
85
|
-
}
|
|
86
|
-
return [];
|
|
87
|
-
});
|
|
33
|
+
const initPromise = build();
|
|
88
34
|
|
|
89
|
-
|
|
90
|
-
return
|
|
35
|
+
export async function handler(event, context) {
|
|
36
|
+
return (await initPromise)(event, context);
|
|
91
37
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Typed cold-start core for the Counter Lambda entry point.
|
|
2
|
+
//
|
|
3
|
+
// The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
|
|
4
|
+
// CounterEntryPoint.mjs keeps only the untyped seam — the dynamic `import()`
|
|
5
|
+
// of the target-spec/mappings modules named in HANDLER_CONFIG, the
|
|
6
|
+
// `patchSpecId` fix-up, and the EventMapper_Callback.MakeCounterHandler
|
|
7
|
+
// functor application consuming them. HANDLER_CONFIG parsing, the
|
|
8
|
+
// Counter_Callback.Make application (a functor over an inline module capturing
|
|
9
|
+
// runtime values), and the references/counts stream routing (the runtime twin
|
|
10
|
+
// of the deploy-time CounterHandler_DynamoDbStream_Runtime.handleStreamEvent,
|
|
11
|
+
// over plain ARN strings instead of Pulumi resources) live here, fully
|
|
12
|
+
// type-checked.
|
|
13
|
+
//
|
|
14
|
+
// Two shapes the former shell got wrong are repaired here:
|
|
15
|
+
// - It read `config.publishQueueUrl`, but the deploy side
|
|
16
|
+
// (CounterHandler_DynamoDbStream.res) writes `publishChannelId` — the
|
|
17
|
+
// CountFinished publish went to an undefined queue URL.
|
|
18
|
+
// - Its queryEngine stub had `query`/`queryAll` fields where
|
|
19
|
+
// QueryEngine.operations needs `scan`/`query`.
|
|
20
|
+
|
|
21
|
+
type handlerConfig = {
|
|
22
|
+
targetSpecModule?: string,
|
|
23
|
+
mappingsModule?: string,
|
|
24
|
+
countsTableName?: string,
|
|
25
|
+
// The publish address of the target's command topic (SQS queue URL) — the
|
|
26
|
+
// field name the deploy side actually writes.
|
|
27
|
+
publishChannelId?: string,
|
|
28
|
+
referencesStreamArn?: string,
|
|
29
|
+
countsStreamArn?: string,
|
|
30
|
+
}
|
|
31
|
+
@val @scope("JSON") external jsonParse: string => handlerConfig = "parse"
|
|
32
|
+
// A real binding (not a bare external) so the shell can import it.
|
|
33
|
+
let parseHandlerConfig = (raw: string): handlerConfig => jsonParse(raw)
|
|
34
|
+
|
|
35
|
+
// ── Counter-handler operations (third argument of MakeCounterHandler) ───────
|
|
36
|
+
// The query engine is not available in the bundled handler; both operations
|
|
37
|
+
// silently return [] (the former shell's stub intent).
|
|
38
|
+
|
|
39
|
+
let makeCounterOps = (config: handlerConfig): EventMapperEntryPoint_Ops.counterOps => {
|
|
40
|
+
let queueUrl = config.publishChannelId->Option.getOr("")
|
|
41
|
+
let queue: Util_SQS_Runtime.resolvedQueue = {id: queueUrl, name: queueUrl, arn: ""}
|
|
42
|
+
{
|
|
43
|
+
publishJsons: queue->CommandTopicChannel_SQS_Runtime.publishJsons(AWS.SQS_FIFO),
|
|
44
|
+
queryEngine: {
|
|
45
|
+
scan: async (~readModelName as _, ~filterConfigs as _, ~limit as _) => [],
|
|
46
|
+
query: async (
|
|
47
|
+
~readModelName as _,
|
|
48
|
+
~key as _=?,
|
|
49
|
+
~id as _,
|
|
50
|
+
~subIdConfig as _=?,
|
|
51
|
+
~filterConfigs as _=?,
|
|
52
|
+
~ascending as _=?,
|
|
53
|
+
~limit as _=?,
|
|
54
|
+
) => [],
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Stream routing ──────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
// The references view rows carry the increment to apply: {id, inc}.
|
|
62
|
+
@schema
|
|
63
|
+
type referencesView = {
|
|
64
|
+
id: string,
|
|
65
|
+
inc: int,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Pure routing: split a mixed stream batch into reference increments and
|
|
69
|
+
// count rows, keyed by the two source ARNs. Records from other sources are
|
|
70
|
+
// dropped silently (the former shell's behavior).
|
|
71
|
+
let splitRecords = (
|
|
72
|
+
~referencesStreamArn: string,
|
|
73
|
+
~countsStreamArn: string,
|
|
74
|
+
records: array<PulumiAws.DynamoDb.Stream.record>,
|
|
75
|
+
): (array<(string, int)>, array<JSON.t>) => {
|
|
76
|
+
let dynamoDbRecords =
|
|
77
|
+
records->Array.filter(record =>
|
|
78
|
+
record.eventSource == "aws:dynamodb" &&
|
|
79
|
+
(record.eventSourceARN == referencesStreamArn ||
|
|
80
|
+
record.eventSourceARN == countsStreamArn)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
let (referenceRecords, countRecords) =
|
|
84
|
+
dynamoDbRecords->Array.partition(record => record.eventSourceARN == referencesStreamArn)
|
|
85
|
+
|
|
86
|
+
let references = referenceRecords->Array.filterMap(record =>
|
|
87
|
+
switch record->Util_DynamoDbStream_Runtime.parseDynamoDbStreamRecordState {
|
|
88
|
+
| NewImage(id, newImage) =>
|
|
89
|
+
let inc = switch newImage->S.parseJsonOrThrow(referencesViewSchema) {
|
|
90
|
+
| {inc} => inc
|
|
91
|
+
| exception _ => 1
|
|
92
|
+
}
|
|
93
|
+
Some((id, inc))
|
|
94
|
+
| NewAndOldImage(id, _, _) =>
|
|
95
|
+
StreamRoutedEntryPoint_Ops.logDebug(
|
|
96
|
+
"ignoring duplicate id: " ++ id,
|
|
97
|
+
{comp: "CounterEntryPoint"},
|
|
98
|
+
)
|
|
99
|
+
None
|
|
100
|
+
| _ => None
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
let counts = countRecords->Array.filterMap(record =>
|
|
105
|
+
switch record->Util_DynamoDbStream_Runtime.parseDynamoDbStreamRecordState {
|
|
106
|
+
| NewImage(_, newImage) | NewAndOldImage(_, newImage, _) => Some(newImage)
|
|
107
|
+
| _ => None
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
(references, counts)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let makeHandler = (
|
|
115
|
+
config: handlerConfig,
|
|
116
|
+
jsonEventsHandler: ReventlessCore.Counter.jsonEventsHandler,
|
|
117
|
+
) => {
|
|
118
|
+
module Callback = ReventlessCore.Counter_Callback.Make({
|
|
119
|
+
let name = "BundledCounter"
|
|
120
|
+
let countsDbCount = QueryDbStorage_DynamoDb_Runtime.count(
|
|
121
|
+
(
|
|
122
|
+
{
|
|
123
|
+
id: config.countsTableName->Option.getOr(""),
|
|
124
|
+
name: config.countsTableName->Option.getOr(""),
|
|
125
|
+
arn: "",
|
|
126
|
+
hashKey: "id",
|
|
127
|
+
}: Util_DynamoDb_Runtime.resolvedTable
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
let jsonEventsHandler = jsonEventsHandler
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
let referencesStreamArn = config.referencesStreamArn->Option.getOr("")
|
|
134
|
+
let countsStreamArn = config.countsStreamArn->Option.getOr("")
|
|
135
|
+
|
|
136
|
+
async (event: PulumiAws.DynamoDb.Stream.event, _context: PulumiAws.Lambda.context) => {
|
|
137
|
+
let (references, counts) = splitRecords(
|
|
138
|
+
~referencesStreamArn,
|
|
139
|
+
~countsStreamArn,
|
|
140
|
+
event.records,
|
|
141
|
+
)
|
|
142
|
+
await Callback.counterHandler(~references, ~counts)
|
|
143
|
+
""
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
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 Counter_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/Counter/Counter_Callback.res.mjs";
|
|
7
|
+
import * as StreamRoutedEntryPoint_Ops$ReventlessAws from "./StreamRoutedEntryPoint_Ops.res.mjs";
|
|
8
|
+
import * as Util_DynamoDbStream_Runtime$ReventlessAws from "../../util/Util_DynamoDbStream_Runtime.res.mjs";
|
|
9
|
+
import * as CommandTopicChannel_SQS_Runtime$ReventlessAws from "../CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
10
|
+
import * as QueryDbStorage_DynamoDb_Runtime$ReventlessAws from "../QueryDb/QueryDbStorage_DynamoDb_Runtime.res.mjs";
|
|
11
|
+
|
|
12
|
+
function parseHandlerConfig(raw) {
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeCounterOps(config) {
|
|
17
|
+
let queueUrl = Stdlib_Option.getOr(config.publishChannelId, "");
|
|
18
|
+
let queue = {
|
|
19
|
+
id: queueUrl,
|
|
20
|
+
name: queueUrl,
|
|
21
|
+
arn: ""
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
publishJsons: CommandTopicChannel_SQS_Runtime$ReventlessAws.publishJsons(queue, "SQS_FIFO"),
|
|
25
|
+
queryEngine: {
|
|
26
|
+
scan: async (param, param$1, param$2) => [],
|
|
27
|
+
query: async (param, param$1, param$2, param$3, param$4, param$5, param$6) => []
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let referencesViewSchema = S.schema(s => ({
|
|
33
|
+
id: s.m(S.string),
|
|
34
|
+
inc: s.m(S.int)
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
function splitRecords(referencesStreamArn, countsStreamArn, records) {
|
|
38
|
+
let dynamoDbRecords = records.filter(record => {
|
|
39
|
+
if (record.eventSource === "aws:dynamodb") {
|
|
40
|
+
if (record.eventSourceARN === referencesStreamArn) {
|
|
41
|
+
return true;
|
|
42
|
+
} else {
|
|
43
|
+
return record.eventSourceARN === countsStreamArn;
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
let match = Stdlib_Array.partition(dynamoDbRecords, record => record.eventSourceARN === referencesStreamArn);
|
|
50
|
+
let references = Stdlib_Array.filterMap(match[0], record => {
|
|
51
|
+
let match = Util_DynamoDbStream_Runtime$ReventlessAws.parseDynamoDbStreamRecordState(record);
|
|
52
|
+
if (typeof match !== "object") {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
switch (match.TAG) {
|
|
56
|
+
case "NewImage" :
|
|
57
|
+
let inc;
|
|
58
|
+
let exit = 0;
|
|
59
|
+
let val;
|
|
60
|
+
try {
|
|
61
|
+
val = S.parseJsonOrThrow(match._1, referencesViewSchema);
|
|
62
|
+
exit = 1;
|
|
63
|
+
} catch (exn) {
|
|
64
|
+
inc = 1;
|
|
65
|
+
}
|
|
66
|
+
if (exit === 1) {
|
|
67
|
+
inc = val.inc;
|
|
68
|
+
}
|
|
69
|
+
return [
|
|
70
|
+
match._0,
|
|
71
|
+
inc
|
|
72
|
+
];
|
|
73
|
+
case "OldImage" :
|
|
74
|
+
return;
|
|
75
|
+
case "NewAndOldImage" :
|
|
76
|
+
StreamRoutedEntryPoint_Ops$ReventlessAws.logDebug("ignoring duplicate id: " + match._0, {
|
|
77
|
+
comp: "CounterEntryPoint"
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
let counts = Stdlib_Array.filterMap(match[1], record => {
|
|
83
|
+
let match = Util_DynamoDbStream_Runtime$ReventlessAws.parseDynamoDbStreamRecordState(record);
|
|
84
|
+
if (typeof match !== "object") {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
switch (match.TAG) {
|
|
88
|
+
case "OldImage" :
|
|
89
|
+
return;
|
|
90
|
+
case "NewImage" :
|
|
91
|
+
case "NewAndOldImage" :
|
|
92
|
+
return match._1;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return [
|
|
96
|
+
references,
|
|
97
|
+
counts
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function makeHandler(config, jsonEventsHandler) {
|
|
102
|
+
let countsDbCount = QueryDbStorage_DynamoDb_Runtime$ReventlessAws.count({
|
|
103
|
+
id: Stdlib_Option.getOr(config.countsTableName, ""),
|
|
104
|
+
name: Stdlib_Option.getOr(config.countsTableName, ""),
|
|
105
|
+
arn: "",
|
|
106
|
+
hashKey: "id"
|
|
107
|
+
});
|
|
108
|
+
let Callback = Counter_Callback$ReventlessCore.Make({
|
|
109
|
+
name: "BundledCounter",
|
|
110
|
+
countsDbCount: countsDbCount,
|
|
111
|
+
jsonEventsHandler: jsonEventsHandler
|
|
112
|
+
});
|
|
113
|
+
let referencesStreamArn = Stdlib_Option.getOr(config.referencesStreamArn, "");
|
|
114
|
+
let countsStreamArn = Stdlib_Option.getOr(config.countsStreamArn, "");
|
|
115
|
+
return async (event, _context) => {
|
|
116
|
+
let match = splitRecords(referencesStreamArn, countsStreamArn, event.Records);
|
|
117
|
+
await Callback.counterHandler(match[0], match[1]);
|
|
118
|
+
return "";
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
parseHandlerConfig,
|
|
124
|
+
makeCounterOps,
|
|
125
|
+
referencesViewSchema,
|
|
126
|
+
splitRecords,
|
|
127
|
+
makeHandler,
|
|
128
|
+
}
|
|
129
|
+
/* referencesViewSchema Not a pure module */
|
|
@@ -160,7 +160,20 @@ export async function buildHandlersForConfig(config, opts = {}) {
|
|
|
160
160
|
await Promise.all(inboundModules.map(async ({ spec, translation, auditTableName }) => {
|
|
161
161
|
const specModule = await loadModule(spec);
|
|
162
162
|
const translationModule = await loadModule(translation);
|
|
163
|
-
|
|
163
|
+
// auditTableName must be a real non-empty string. A non-string here (e.g. a
|
|
164
|
+
// mis-serialized deploy-time value) would build a QueryDb ops whose TableName
|
|
165
|
+
// is an object, and every PutCommand would crash in AWS endpoint resolution
|
|
166
|
+
// ("value.split is not a function") — silently, since the audit save is
|
|
167
|
+
// best-effort. Guard against that instead of trusting the config shape.
|
|
168
|
+
const hasAuditTable = typeof auditTableName === "string" && auditTableName.length > 0;
|
|
169
|
+
if (auditTableName != null && !hasAuditTable) {
|
|
170
|
+
log.warn(
|
|
171
|
+
"inbound audit table name is not a string; skipping audit persistence: " +
|
|
172
|
+
JSON.stringify(auditTableName),
|
|
173
|
+
{ comp: "DcbCommandTopicRuntime" },
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const auditOps = (hasAuditTable && !config.pgConnection)
|
|
164
177
|
? makeDynamoQueryDbOps(auditTableName)
|
|
165
178
|
: undefined;
|
|
166
179
|
const fieldName = config.pluginName + "_" + specModule.name;
|
|
@@ -266,10 +266,30 @@ let buildInboundReceiver = (
|
|
|
266
266
|
let json = row->S.reverseConvertToJsonOrThrow(
|
|
267
267
|
ReventlessCore.InboundTranslationSlice_Callback.auditRowSchema,
|
|
268
268
|
)
|
|
269
|
+
// A failed audit write must not fail the mutation (the command was
|
|
270
|
+
// already published), but it must not vanish either. `save` RESOLVES
|
|
271
|
+
// with `Error(_)` on a storage failure (it does not throw), so the
|
|
272
|
+
// result must be inspected — a bare `let _ = await ops.save(...)` would
|
|
273
|
+
// swallow the failure and leave the audit view permanently, silently
|
|
274
|
+
// empty. `try` still guards an unexpected reject.
|
|
269
275
|
try {
|
|
270
|
-
|
|
276
|
+
switch await ops.save(id, json, ReventlessCore.QueryDb.Overwrite, None) {
|
|
277
|
+
| Ok() => ()
|
|
278
|
+
| Error(err) =>
|
|
279
|
+
ReventlessCore.EffectLogger.logError(
|
|
280
|
+
~comp="InboundTranslationSlice.audit",
|
|
281
|
+
`failed to persist audit row ${id}: ${err
|
|
282
|
+
->ReventlessCore.QueryDb.storageErrorToString}`,
|
|
283
|
+
)->Effect.runSync
|
|
284
|
+
}
|
|
271
285
|
} catch {
|
|
272
|
-
|
|
|
286
|
+
| exn =>
|
|
287
|
+
let detail =
|
|
288
|
+
exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown error")
|
|
289
|
+
ReventlessCore.EffectLogger.logError(
|
|
290
|
+
~comp="InboundTranslationSlice.audit",
|
|
291
|
+
`failed to persist audit row ${id}: ${detail}`,
|
|
292
|
+
)->Effect.runSync
|
|
273
293
|
}
|
|
274
294
|
})
|
|
275
295
|
| None => ()
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import * as S from "sury/src/S.res.mjs";
|
|
4
4
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
5
6
|
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
6
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
8
|
import * as Effect from "effect/Effect";
|
|
8
9
|
import * as Stream from "effect/Stream";
|
|
9
10
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
10
11
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
12
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
11
13
|
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
14
|
+
import * as QueryDb$ReventlessCore from "@reventlessdev/reventless-core/src/components/QueryDb/QueryDb.res.mjs";
|
|
15
|
+
import * as EffectLogger$ReventlessCore from "@reventlessdev/reventless-core/src/util/EffectLogger.res.mjs";
|
|
12
16
|
import * as CommandTopic_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Helpers.res.mjs";
|
|
13
17
|
import * as InboundTranslationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs";
|
|
14
18
|
import * as DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws from "../DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs";
|
|
@@ -105,13 +109,20 @@ function buildInboundReceiver(spec, translation, publishJsons, auditQueryDbOps)
|
|
|
105
109
|
if (auditQueryDbOps !== undefined) {
|
|
106
110
|
let rows = Object.entries(callback.auditLog);
|
|
107
111
|
await Stdlib_Array.reduce(rows, Promise.resolve(), async (prev, param) => {
|
|
112
|
+
let id = param[0];
|
|
108
113
|
await prev;
|
|
109
114
|
let json = S.reverseConvertToJsonOrThrow(param[1], InboundTranslationSlice_Callback$ReventlessCore.auditRowSchema);
|
|
110
115
|
try {
|
|
111
|
-
await auditQueryDbOps.save(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
let err = await auditQueryDbOps.save(id, json, "Overwrite", undefined);
|
|
117
|
+
if (err.TAG === "Ok") {
|
|
118
|
+
return;
|
|
119
|
+
} else {
|
|
120
|
+
return Effect.runSync(EffectLogger$ReventlessCore.logError("InboundTranslationSlice.audit", undefined, `failed to persist audit row ` + id + `: ` + QueryDb$ReventlessCore.storageErrorToString(err._0)));
|
|
121
|
+
}
|
|
122
|
+
} catch (raw_exn) {
|
|
123
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
124
|
+
let detail = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown error");
|
|
125
|
+
return Effect.runSync(EffectLogger$ReventlessCore.logError("InboundTranslationSlice.audit", undefined, `failed to persist audit row ` + id + `: ` + detail));
|
|
115
126
|
}
|
|
116
127
|
});
|
|
117
128
|
}
|