@reventlessdev/reventless-aws 3.0.0-alpha.232 → 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.
Files changed (104) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +8 -8
  3. package/src/adapter/Api/AppSyncEventsSigner_Ops.res +137 -0
  4. package/src/adapter/Api/AppSyncEventsSigner_Ops.res.mjs +121 -0
  5. package/src/adapter/Api/Platform_AdminScan_Ops.res +136 -0
  6. package/src/adapter/Api/Platform_AdminScan_Ops.res.mjs +98 -0
  7. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +12 -90
  8. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +11 -84
  9. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +86 -0
  10. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +79 -0
  11. package/src/adapter/Api/Platform_UIFragments_Lambda.res +9 -79
  12. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +7 -143
  13. package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res +48 -0
  14. package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res.mjs +63 -0
  15. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +21 -80
  16. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +23 -68
  17. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res +70 -0
  18. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res.mjs +68 -0
  19. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -1
  20. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +1 -1
  21. package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
  22. package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -1
  23. package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +42 -126
  24. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +246 -0
  25. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +145 -0
  26. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +27 -2
  27. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +10 -3
  28. package/src/adapter/Runtime/CounterEntryPoint.mjs +19 -73
  29. package/src/adapter/Runtime/CounterEntryPoint_Ops.res +145 -0
  30. package/src/adapter/Runtime/CounterEntryPoint_Ops.res.mjs +129 -0
  31. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +11 -1
  32. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +9 -3
  33. package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +52 -510
  34. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +816 -0
  35. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +651 -0
  36. package/src/adapter/Runtime/EventMapperEntryPoint.mjs +23 -67
  37. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +126 -0
  38. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res.mjs +83 -0
  39. package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +16 -63
  40. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +150 -0
  41. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res.mjs +111 -0
  42. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res +96 -0
  43. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res.mjs +70 -0
  44. package/src/adapter/Runtime/PgMigrationEntryPoint.res +54 -0
  45. package/src/adapter/Runtime/PgMigrationEntryPoint.res.mjs +37 -0
  46. package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +20 -55
  47. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +155 -0
  48. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +94 -0
  49. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +80 -0
  50. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res.mjs +39 -0
  51. package/src/adapter/Runtime/QueryDbEntryPoint_Ops.res +4 -4
  52. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +34 -153
  53. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res +114 -0
  54. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res.mjs +83 -0
  55. package/src/adapter/Runtime/SideEffectEntryPoint.mjs +23 -64
  56. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res +71 -0
  57. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res.mjs +57 -0
  58. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +34 -165
  59. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +206 -0
  60. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res.mjs +105 -0
  61. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +156 -0
  62. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res.mjs +123 -0
  63. package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +18 -138
  64. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +198 -0
  65. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res.mjs +155 -0
  66. package/src/adapter/StateTopic/StateTopic_AppSync.res +12 -170
  67. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +7 -167
  68. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +208 -0
  69. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +183 -0
  70. package/src/components/AutomationSlice_Builder.res +2 -0
  71. package/src/components/AutomationSlice_Builder.res.mjs +1 -1
  72. package/src/components/OutboundTranslationSlice_Builder.res +1 -0
  73. package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
  74. package/tests/AppSyncEventsSigner_OpsTest.res +75 -0
  75. package/tests/AppSyncEventsSigner_OpsTest.res.mjs +44 -0
  76. package/tests/AppSyncEventsSigner_ParityFixture.mjs +36 -0
  77. package/tests/AutomationSliceEntryPoint_OpsTest.res +165 -0
  78. package/tests/AutomationSliceEntryPoint_OpsTest.res.mjs +280 -0
  79. package/tests/CounterEntryPoint_OpsTest.res +115 -0
  80. package/tests/CounterEntryPoint_OpsTest.res.mjs +150 -0
  81. package/tests/EventCollectorEntryPoint_OpsTest.res +216 -0
  82. package/tests/EventCollectorEntryPoint_OpsTest.res.mjs +339 -0
  83. package/tests/EventMapperEntryPoint_OpsTest.res +103 -0
  84. package/tests/EventMapperEntryPoint_OpsTest.res.mjs +120 -0
  85. package/tests/ExtensionPointEntryPoint_OpsTest.res +57 -0
  86. package/tests/ExtensionPointEntryPoint_OpsTest.res.mjs +54 -0
  87. package/tests/PgMigrationEntryPoint_IntegrationTest.res +22 -31
  88. package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +20 -23
  89. package/tests/PgQueryResolverEntryPoint_OpsTest.res +38 -0
  90. package/tests/PgQueryResolverEntryPoint_OpsTest.res.mjs +31 -0
  91. package/tests/Platform_AdminScan_OpsTest.res +80 -0
  92. package/tests/Platform_AdminScan_OpsTest.res.mjs +127 -0
  93. package/tests/ReadModelEntryPoint_OpsTest.res +141 -0
  94. package/tests/ReadModelEntryPoint_OpsTest.res.mjs +267 -0
  95. package/tests/SideEffectEntryPoint_OpsTest.res +79 -0
  96. package/tests/SideEffectEntryPoint_OpsTest.res.mjs +105 -0
  97. package/tests/StateViewSliceEntryPoint_OpsTest.res +167 -0
  98. package/tests/StateViewSliceEntryPoint_OpsTest.res.mjs +296 -0
  99. package/tests/TaskBucketEntryPoint_OpsTest.res +117 -0
  100. package/tests/TaskBucketEntryPoint_OpsTest.res.mjs +165 -0
  101. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res +42 -13
  102. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res.mjs +29 -6
  103. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +0 -57
  104. 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
- import { schema as surySchema, string as suryString, $$int as suryInt, parseJsonOrThrow } from "sury/src/S.res.mjs";
7
- import { patchSpecId, makeTableRef, makeQueueRef, log } from "./HandlerFactoryHelpers.mjs";
8
- import { parseDynamoDbStreamRecordState } from "@reventlessdev/reventless-aws/src/util/Util_DynamoDbStream_Runtime.res.mjs";
9
- import { Make as counterCallbackMake } from "@reventlessdev/reventless-core/src/components/Counter/Counter_Callback.res.mjs";
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 { count as qdbCount } from "@reventlessdev/reventless-aws/src/adapter/QueryDb/QueryDbStorage_DynamoDb_Runtime.res.mjs";
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
- // Build the referencesView schema: {id: string, inc: int}
17
- const referencesViewSchema = surySchema(s => ({
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 countsTable = makeTableRef(config.countsTableName);
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
- const counterHandler = MakeCounterHandler(patchedTarget)(mappingsModule)({ publishJsons: pubJsons, queryEngine });
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 = buildHandler();
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
- await callback.counterHandler(references, counts);
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 */
@@ -269,7 +269,17 @@ let buildInboundReceiver = (
269
269
  try {
270
270
  let _ = await ops.save(id, json, ReventlessCore.QueryDb.Overwrite, None)
271
271
  } catch {
272
- | _ => ()
272
+ | exn =>
273
+ // A failed audit write must not fail the mutation (the command was
274
+ // already published), but it must not vanish either: an AccessDenied
275
+ // here means the command Lambda's role lacks PutItem on the audit table,
276
+ // which otherwise surfaces only as a permanently empty audit view.
277
+ let detail =
278
+ exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown error")
279
+ ReventlessCore.EffectLogger.logError(
280
+ ~comp="InboundTranslationSlice.audit",
281
+ `failed to persist audit row ${id}: ${detail}`,
282
+ )->Effect.runSync
273
283
  }
274
284
  })
275
285
  | None => ()
@@ -2,13 +2,16 @@
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 EffectLogger$ReventlessCore from "@reventlessdev/reventless-core/src/util/EffectLogger.res.mjs";
12
15
  import * as CommandTopic_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Helpers.res.mjs";
13
16
  import * as InboundTranslationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs";
14
17
  import * as DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws from "../DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs";
@@ -105,13 +108,16 @@ function buildInboundReceiver(spec, translation, publishJsons, auditQueryDbOps)
105
108
  if (auditQueryDbOps !== undefined) {
106
109
  let rows = Object.entries(callback.auditLog);
107
110
  await Stdlib_Array.reduce(rows, Promise.resolve(), async (prev, param) => {
111
+ let id = param[0];
108
112
  await prev;
109
113
  let json = S.reverseConvertToJsonOrThrow(param[1], InboundTranslationSlice_Callback$ReventlessCore.auditRowSchema);
110
114
  try {
111
- await auditQueryDbOps.save(param[0], json, "Overwrite", undefined);
112
- return;
113
- } catch (exn) {
115
+ await auditQueryDbOps.save(id, json, "Overwrite", undefined);
114
116
  return;
117
+ } catch (raw_exn) {
118
+ let exn = Primitive_exceptions.internalToException(raw_exn);
119
+ let detail = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown error");
120
+ return Effect.runSync(EffectLogger$ReventlessCore.logError("InboundTranslationSlice.audit", undefined, `failed to persist audit row ` + id + `: ` + detail));
115
121
  }
116
122
  });
117
123
  }