@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/package.json +7 -7
  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.mjs +14 -1
  32. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +22 -2
  33. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +15 -4
  34. package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +52 -510
  35. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +816 -0
  36. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +651 -0
  37. package/src/adapter/Runtime/EventMapperEntryPoint.mjs +23 -67
  38. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +126 -0
  39. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res.mjs +83 -0
  40. package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +16 -63
  41. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +150 -0
  42. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res.mjs +111 -0
  43. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res +96 -0
  44. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res.mjs +70 -0
  45. package/src/adapter/Runtime/PgMigrationEntryPoint.res +54 -0
  46. package/src/adapter/Runtime/PgMigrationEntryPoint.res.mjs +37 -0
  47. package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +20 -55
  48. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +155 -0
  49. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +94 -0
  50. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +80 -0
  51. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res.mjs +39 -0
  52. package/src/adapter/Runtime/QueryDbEntryPoint_Ops.res +4 -4
  53. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +34 -153
  54. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res +114 -0
  55. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res.mjs +83 -0
  56. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +26 -6
  57. package/src/adapter/Runtime/SideEffectEntryPoint.mjs +23 -64
  58. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res +71 -0
  59. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res.mjs +57 -0
  60. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +5 -2
  61. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +1 -1
  62. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +34 -165
  63. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +206 -0
  64. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res.mjs +105 -0
  65. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +156 -0
  66. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res.mjs +123 -0
  67. package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +18 -138
  68. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +198 -0
  69. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res.mjs +155 -0
  70. package/src/adapter/StateTopic/StateTopic_AppSync.res +12 -170
  71. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +7 -167
  72. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +208 -0
  73. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +183 -0
  74. package/src/components/AutomationSlice_Builder.res +2 -0
  75. package/src/components/AutomationSlice_Builder.res.mjs +1 -1
  76. package/src/components/OutboundTranslationSlice_Builder.res +1 -0
  77. package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
  78. package/src/plugin/runtime/PluginRuntime_Builder.res +13 -4
  79. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +1 -1
  80. package/tests/AppSyncEventsSigner_OpsTest.res +75 -0
  81. package/tests/AppSyncEventsSigner_OpsTest.res.mjs +44 -0
  82. package/tests/AppSyncEventsSigner_ParityFixture.mjs +36 -0
  83. package/tests/AutomationSliceEntryPoint_OpsTest.res +165 -0
  84. package/tests/AutomationSliceEntryPoint_OpsTest.res.mjs +280 -0
  85. package/tests/CounterEntryPoint_OpsTest.res +115 -0
  86. package/tests/CounterEntryPoint_OpsTest.res.mjs +150 -0
  87. package/tests/EventCollectorEntryPoint_OpsTest.res +216 -0
  88. package/tests/EventCollectorEntryPoint_OpsTest.res.mjs +339 -0
  89. package/tests/EventMapperEntryPoint_OpsTest.res +103 -0
  90. package/tests/EventMapperEntryPoint_OpsTest.res.mjs +120 -0
  91. package/tests/ExtensionPointEntryPoint_OpsTest.res +57 -0
  92. package/tests/ExtensionPointEntryPoint_OpsTest.res.mjs +54 -0
  93. package/tests/PgMigrationEntryPoint_IntegrationTest.res +22 -31
  94. package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +20 -23
  95. package/tests/PgQueryResolverEntryPoint_OpsTest.res +38 -0
  96. package/tests/PgQueryResolverEntryPoint_OpsTest.res.mjs +31 -0
  97. package/tests/Platform_AdminScan_OpsTest.res +80 -0
  98. package/tests/Platform_AdminScan_OpsTest.res.mjs +127 -0
  99. package/tests/ReadModelEntryPoint_OpsTest.res +141 -0
  100. package/tests/ReadModelEntryPoint_OpsTest.res.mjs +267 -0
  101. package/tests/SideEffectEntryPoint_OpsTest.res +79 -0
  102. package/tests/SideEffectEntryPoint_OpsTest.res.mjs +105 -0
  103. package/tests/StateViewSliceEntryPoint_OpsTest.res +167 -0
  104. package/tests/StateViewSliceEntryPoint_OpsTest.res.mjs +296 -0
  105. package/tests/TaskBucketEntryPoint_OpsTest.res +117 -0
  106. package/tests/TaskBucketEntryPoint_OpsTest.res.mjs +165 -0
  107. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res +42 -13
  108. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res.mjs +29 -6
  109. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +0 -57
  110. 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 */
@@ -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
- const auditOps = (auditTableName && !config.pgConnection)
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
- let _ = await ops.save(id, json, ReventlessCore.QueryDb.Overwrite, None)
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(param[0], json, "Overwrite", undefined);
112
- return;
113
- } catch (exn) {
114
- return;
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
  }