@reventlessdev/reventless-aws 3.0.0-alpha.219 → 3.0.0-alpha.221

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 (164) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +8 -8
  3. package/src/Platform.res.mjs +4 -0
  4. package/src/Platform_Stack.res +6 -0
  5. package/src/Platform_Stack.res.mjs +3 -1
  6. package/src/adapter/AWS_Tags.res +127 -12
  7. package/src/adapter/AWS_Tags.res.mjs +58 -17
  8. package/src/adapter/Api/AppSync_SourceApiAssociation_Retrying.res +26 -1
  9. package/src/adapter/Api/AppSync_SourceApiAssociation_Retrying.res.mjs +12 -1
  10. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +13 -1
  11. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +3 -4
  12. package/src/adapter/Api/Platform_UIFragments_Lambda.res +13 -1
  13. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +3 -4
  14. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res +12 -0
  15. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res.mjs +4 -2
  16. package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res +6 -0
  17. package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res.mjs +2 -1
  18. package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res +2 -2
  19. package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res.mjs +2 -2
  20. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res +2 -2
  21. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res.mjs +2 -2
  22. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res +2 -2
  23. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res.mjs +2 -2
  24. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res +2 -2
  25. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res.mjs +2 -2
  26. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +1 -0
  27. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +1 -1
  28. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res +1 -1
  29. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res.mjs +1 -2
  30. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res +1 -1
  31. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs +1 -1
  32. package/src/adapter/EventCollector/EventCollectorChannel_SQS.res +2 -2
  33. package/src/adapter/EventCollector/EventCollectorChannel_SQS.res.mjs +2 -2
  34. package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res +2 -2
  35. package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res.mjs +2 -2
  36. package/src/adapter/EventLog/EventLogStorage.res +3 -3
  37. package/src/adapter/EventLog/EventLogStorage.res.mjs +3 -3
  38. package/src/adapter/EventLog/EventLogStorage_DynamoDb.res +2 -2
  39. package/src/adapter/EventLog/EventLogStorage_DynamoDb.res.mjs +2 -2
  40. package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res +2 -2
  41. package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res.mjs +2 -2
  42. package/src/adapter/EventLog/EventLogStorage_Postgres.res +1 -1
  43. package/src/adapter/EventLog/EventLogStorage_Postgres.res.mjs +1 -1
  44. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +7 -1
  45. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +2 -2
  46. package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res +1 -1
  47. package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res.mjs +1 -1
  48. package/src/adapter/EventTopic/EventTopicPublisher_SNS.res +2 -2
  49. package/src/adapter/EventTopic/EventTopicPublisher_SNS.res.mjs +2 -2
  50. package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res +2 -2
  51. package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res.mjs +2 -2
  52. package/src/adapter/Mcp/MCP_Lambda.res.mjs +1 -1
  53. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +7 -0
  54. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +4 -2
  55. package/src/adapter/Postgres/PgMigration_Builder.res +1 -0
  56. package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -1
  57. package/src/adapter/QueryDb/PgQueryResolver_Builder.res +7 -0
  58. package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +4 -2
  59. package/src/adapter/QueryDb/QueryDbStorage.res +6 -6
  60. package/src/adapter/QueryDb/QueryDbStorage.res.mjs +6 -6
  61. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res +2 -2
  62. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs +2 -2
  63. package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res +2 -2
  64. package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res.mjs +2 -2
  65. package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +1 -1
  66. package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +1 -1
  67. package/src/adapter/Runtime/AdminEventCollectorEntryPoint.mjs +24 -41
  68. package/src/adapter/Runtime/AggregateEntryPoint.mjs +52 -45
  69. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res +19 -2
  70. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res.mjs +8 -5
  71. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res +19 -2
  72. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res.mjs +8 -5
  73. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res +9 -1
  74. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res.mjs +4 -2
  75. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res +9 -1
  76. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res.mjs +4 -2
  77. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +9 -1
  78. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +4 -2
  79. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +9 -1
  80. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +4 -2
  81. package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +36 -22
  82. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +1 -0
  83. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +1 -1
  84. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +39 -42
  85. package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res +7 -1
  86. package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res.mjs +4 -2
  87. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +19 -1
  88. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +5 -2
  89. package/src/adapter/Runtime/EventMapperEntryPoint.mjs +25 -23
  90. package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +25 -41
  91. package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res +1 -0
  92. package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res.mjs +1 -1
  93. package/src/adapter/Runtime/HandlerFactoryHelpers.mjs +126 -0
  94. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs +24 -41
  95. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +33 -24
  96. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +52 -2
  97. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +25 -7
  98. package/src/adapter/Runtime/SideEffectEntryPoint.mjs +25 -24
  99. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +9 -1
  100. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +4 -2
  101. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +1 -0
  102. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +1 -1
  103. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +26 -21
  104. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +2 -1
  105. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +2 -2
  106. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res +1 -0
  107. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +1 -1
  108. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res +12 -0
  109. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res.mjs +4 -2
  110. package/src/adapter/StateTopic/StateTopic_AppSync.res +7 -1
  111. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +2 -2
  112. package/src/adapter/Task/TaskBucket_S3.res +1 -0
  113. package/src/adapter/Task/TaskBucket_S3.res.mjs +7 -1
  114. package/src/components/Aggregate_Builder_Micro.res.mjs +2 -0
  115. package/src/components/Aggregate_Builder_NoResolver.res.mjs +2 -0
  116. package/src/components/Aggregate_Builder_PerAggregate.res.mjs +2 -0
  117. package/src/components/Aggregate_Builder_Single.res.mjs +2 -0
  118. package/src/components/Aggregate_Builder_Single_Async.res.mjs +2 -0
  119. package/src/components/Api/AppSync_Adapter.res +12 -0
  120. package/src/components/Api/AppSync_Adapter.res.mjs +6 -2
  121. package/src/components/Api/AppSync_MergedApi.res +12 -0
  122. package/src/components/Api/AppSync_MergedApi.res.mjs +5 -2
  123. package/src/components/AutomationSlice_Builder.res.mjs +2 -0
  124. package/src/components/ExtensionPoint_Builder.res.mjs +2 -0
  125. package/src/components/ForeignReadModel_Builder.res.mjs +4 -0
  126. package/src/components/OutboundTranslationSlice_Builder.res.mjs +2 -0
  127. package/src/components/Plugin.res.mjs +2 -0
  128. package/src/components/ReadModel_Builder_NoResolver.res.mjs +2 -0
  129. package/src/components/ReadModel_Builder_NoResolver_Stream.res.mjs +2 -0
  130. package/src/components/ReadModel_Builder_PerReadModel.res.mjs +2 -0
  131. package/src/components/ReadModel_Builder_Single.res.mjs +2 -0
  132. package/src/components/ReadModel_Builder_Single_Stream.res.mjs +2 -0
  133. package/src/components/SideEffectHandler_PerSideEffectHandler.res.mjs +6 -0
  134. package/src/components/SideEffectHandler_Single.res.mjs +4 -0
  135. package/src/components/StateViewSlice_Builder.res.mjs +2 -0
  136. package/src/components/StateViewSlice_Builder_Stream.res.mjs +2 -0
  137. package/src/components/Task_Builder_PerBucket.res.mjs +4 -0
  138. package/src/components/Vpc.res +10 -1
  139. package/src/components/Vpc.res.mjs +3 -1
  140. package/src/plugin/cloner/ClonerRunner_Fargate.res +26 -1
  141. package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +9 -6
  142. package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res +6 -0
  143. package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res.mjs +4 -1
  144. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res +1 -0
  145. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +1 -1
  146. package/src/plugin/runtime/PluginRuntime_Builder.res +2 -0
  147. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +2 -2
  148. package/src/plugin/stack/Plugin_ExtensionPoint_Builder.res.mjs +4 -0
  149. package/src/plugin/stack/Plugin_Stack.res +26 -1
  150. package/src/plugin/stack/Plugin_Stack.res.mjs +8 -3
  151. package/src/util/Util_CognitoGroupUser.res +6 -0
  152. package/src/util/Util_CognitoGroupUser.res.mjs +3 -1
  153. package/src/util/Util_DeadLetterQueue.res +8 -2
  154. package/src/util/Util_DeadLetterQueue.res.mjs +3 -4
  155. package/src/util/Util_LogAttribution.res +31 -0
  156. package/src/util/Util_LogAttribution.res.mjs +27 -0
  157. package/tests/AWS_TagsTest.res +236 -0
  158. package/tests/AWS_TagsTest.res.mjs +153 -0
  159. package/tests/AppSync_SourceApiAssociation_RetryingTest.res +26 -0
  160. package/tests/AppSync_SourceApiAssociation_RetryingTest.res.mjs +20 -0
  161. package/tests/HandlerFactoryDispatchTest.res +190 -0
  162. package/tests/HandlerFactoryDispatchTest.res.mjs +135 -0
  163. package/tests/integration/AggregateEntryPoint_IntegrationTest.res +5 -3
  164. package/tests/integration/AggregateEntryPoint_IntegrationTest.res.mjs +5 -3
@@ -0,0 +1,190 @@
1
+ // Regression guard for the deployed entry points' dispatch boundary.
2
+ //
3
+ // `HandlerFactoryHelpers.mjs` is the single place where every Lambda invocation
4
+ // gets its log annotations and its `RequestContext` — the AWS counterpart of
5
+ // ReventlessCore.Runtime.annotateInvocation, which does not run in a Lambda (the
6
+ // runtime builders take `~handler as _` and deploy the .mjs shell instead). Two
7
+ // things here are checkable only from JS and break silently otherwise:
8
+ //
9
+ // 1. `RequestContext.fromOptions` is called across the ReScript/JS seam. A
10
+ // labelled-argument constructor compiles to positional arguments, so an
11
+ // inserted field would silently shift every value; `fromOptions` takes one
12
+ // options object precisely to avoid that, and this test fails if the shell
13
+ // and the record ever disagree.
14
+ // 2. The transport extractors read two different record shapes — SQS (`body`
15
+ // envelope + `attributes`) and DynamoDB streams (flat top-level meta
16
+ // attributes on `NewImage` + `ApproximateCreationDateTime` in *seconds*).
17
+ //
18
+ // See docs/plans/entrypoint-dispatch-parity-and-latency-fields.md.
19
+
20
+ open JestGlobals
21
+
22
+ // ─── The shell under test ───────────────────────────────────────────────────
23
+
24
+ type dispatchOptions = {
25
+ correlationId?: string,
26
+ causationId?: string,
27
+ comp?: string,
28
+ plugin?: string,
29
+ timestamp?: float,
30
+ retryCount?: int,
31
+ }
32
+
33
+ @module("@reventlessdev/reventless-aws/src/adapter/Runtime/HandlerFactoryHelpers.mjs")
34
+ external runEffect: (Effect.t<'a, 'e, 'r>, dispatchOptions) => promise<'a> = "runEffect"
35
+
36
+ @module("@reventlessdev/reventless-aws/src/adapter/Runtime/HandlerFactoryHelpers.mjs")
37
+ external setRequestId: string => unit = "setRequestId"
38
+
39
+ @module("@reventlessdev/reventless-aws/src/adapter/Runtime/HandlerFactoryHelpers.mjs")
40
+ external extractMetaField: (array<'record>, string) => option<string> = "extractMetaField"
41
+
42
+ @module("@reventlessdev/reventless-aws/src/adapter/Runtime/HandlerFactoryHelpers.mjs")
43
+ external extractSentTimestamp: array<'record> => option<float> = "extractSentTimestamp"
44
+
45
+ @module("@reventlessdev/reventless-aws/src/adapter/Runtime/HandlerFactoryHelpers.mjs")
46
+ external extractRetryCount: array<'record> => int = "extractRetryCount"
47
+
48
+ // ─── Record fixtures, typed rather than cast ────────────────────────────────
49
+
50
+ type sqsAttributes = {
51
+ @as("SentTimestamp") sentTimestamp: string,
52
+ @as("ApproximateReceiveCount") approximateReceiveCount: string,
53
+ }
54
+ type sqsRecord = {body: string, attributes: sqsAttributes}
55
+ type bodyOnlyRecord = {body: string}
56
+
57
+ type streamAttribute = {@as("S") s: string}
58
+ type streamImage = {correlationId: streamAttribute, causationId: streamAttribute}
59
+ type streamPayload = {
60
+ @as("NewImage") newImage: streamImage,
61
+ @as("ApproximateCreationDateTime") approximateCreationDateTime: float,
62
+ }
63
+ type streamRecord = {dynamodb: streamPayload}
64
+
65
+ let sqsRecords = [
66
+ {
67
+ body: `{"meta":{"correlationId":"corr-1","causationId":"cause-1"}}`,
68
+ attributes: {sentTimestamp: "1750000000000", approximateReceiveCount: "3"},
69
+ },
70
+ ]
71
+
72
+ let streamRecords = [
73
+ {
74
+ dynamodb: {
75
+ newImage: {correlationId: {s: "corr-2"}, causationId: {s: "cause-2"}},
76
+ approximateCreationDateTime: 1750000001.0,
77
+ },
78
+ },
79
+ ]
80
+
81
+ // Read back what the dispatch boundary provided, so the assertions see exactly
82
+ // what an application handler would.
83
+ let capture = (): Effect.t<ReventlessCore.RequestContext.t, string, _> =>
84
+ Effect.serviceWith(ReventlessCore.RequestContext.tag, ctx => ctx)
85
+
86
+ describe("entry-point dispatch boundary", () => {
87
+ describe("SQS records", () => {
88
+ testSync("reads the envelope out of the body", () => {
89
+ expect(extractMetaField(sqsRecords, "correlationId"))->Expect.toEqual(Some("corr-1"))
90
+ expect(extractMetaField(sqsRecords, "causationId"))->Expect.toEqual(Some("cause-1"))
91
+ })
92
+
93
+ testSync("reads SentTimestamp as ms since epoch", () =>
94
+ expect(extractSentTimestamp(sqsRecords))->Expect.toEqual(Some(1750000000000.0))
95
+ )
96
+
97
+ testSync("reads ApproximateReceiveCount as the delivery attempt", () =>
98
+ expect(extractRetryCount(sqsRecords))->Expect.toBe(3)
99
+ )
100
+ })
101
+
102
+ describe("DynamoDB stream records", () => {
103
+ // EventLog rows store meta keys flat as top-level attributes
104
+ // (Message.composeMeta rebuilds `meta` from them) — not as a nested map.
105
+ testSync("reads the envelope off the flat NewImage attributes", () => {
106
+ expect(extractMetaField(streamRecords, "correlationId"))->Expect.toEqual(Some("corr-2"))
107
+ expect(extractMetaField(streamRecords, "causationId"))->Expect.toEqual(Some("cause-2"))
108
+ })
109
+
110
+ testSync("converts ApproximateCreationDateTime from seconds to ms", () =>
111
+ expect(extractSentTimestamp(streamRecords))->Expect.toEqual(Some(1750000001000.0))
112
+ )
113
+
114
+ testSync("reports a first delivery — streams have no receive count", () =>
115
+ expect(extractRetryCount(streamRecords))->Expect.toBe(1)
116
+ )
117
+ })
118
+
119
+ describe("absent or malformed input", () => {
120
+ testSync("an empty batch has no send time", () =>
121
+ expect(extractSentTimestamp([]))->Expect.toEqual(None)
122
+ )
123
+
124
+ testSync("an unparseable body yields no envelope field", () =>
125
+ expect(extractMetaField([{body: "not json"}], "correlationId"))->Expect.toEqual(None)
126
+ )
127
+ })
128
+
129
+ describe("RequestContext provided at dispatch", () => {
130
+ test("carries the full record, not a bare correlationId literal", async () => {
131
+ let ctx = await runEffect(
132
+ capture(),
133
+ {
134
+ correlationId: "corr-1",
135
+ causationId: "cause-1",
136
+ comp: "AggregateRuntime(Customer)",
137
+ plugin: "Catalog",
138
+ timestamp: 1750000000000.0,
139
+ retryCount: 3,
140
+ },
141
+ )
142
+ expect(ctx.correlationId)->Expect.toBe("corr-1")
143
+ expect(ctx.causationId)->Expect.toEqual(Some("cause-1"))
144
+ expect(ctx.component)->Expect.toEqual(Some("AggregateRuntime(Customer)"))
145
+ expect(ctx.pluginName)->Expect.toEqual(Some("Catalog"))
146
+ expect(ctx.timestamp)->Expect.toEqual(Some(1750000000000.0))
147
+ expect(ctx.retryCount)->Expect.toEqual(Some(3))
148
+ })
149
+
150
+ test("fills identity and claims, which the record declares non-optional", async () => {
151
+ let ctx = await runEffect(capture(), {correlationId: "corr-1"})
152
+ expect(ctx.identity.userId)->Expect.toBe(Reventless.Identity.anonymous.userId)
153
+ expect(ctx.claims->Dict.keysToArray)->Expect.toEqual([])
154
+ })
155
+
156
+ test("falls back to a placeholder when no correlationId is on the message", async () => {
157
+ let ctx = await runEffect(capture(), {comp: "X(Y)"})
158
+ expect(ctx.correlationId)->Expect.toBe("unknown")
159
+ })
160
+ })
161
+
162
+ describe("log annotations", () => {
163
+ // Effect keeps annotations in a HashMap; read it as a dict.
164
+ let annotations = (): Effect.t<Effect.logAnnotationMap, string, _> => Effect.logAnnotations
165
+
166
+ test("promotes comp, causationId and the request id onto every line", async () => {
167
+ setRequestId("req-abc")
168
+ let annotated = await runEffect(
169
+ annotations(),
170
+ {correlationId: "corr-1", causationId: "cause-1", comp: "EventCollector(Customers)"},
171
+ )
172
+ let annotated = annotated->Effect.annotationsToDict
173
+ expect(annotated->Dict.get("comp"))->Expect.toEqual(Some("EventCollector(Customers)"))
174
+ expect(annotated->Dict.get("causationId"))->Expect.toEqual(Some("cause-1"))
175
+ expect(annotated->Dict.get("correlationId"))->Expect.toEqual(Some("corr-1"))
176
+ expect(annotated->Dict.get("requestId"))->Expect.toEqual(Some("req-abc"))
177
+ })
178
+
179
+ // A constant "1" on every line is noise; its absence is what makes
180
+ // `filter retryCount > 1` a usable CloudWatch query.
181
+ test("annotates retryCount only on a redelivery", async () => {
182
+ let first = await runEffect(annotations(), {correlationId: "c", retryCount: 1})
183
+ expect(first->Effect.annotationsToDict->Dict.get("retryCount"))->Expect.toEqual(None)
184
+ let redelivered = await runEffect(annotations(), {correlationId: "c", retryCount: 2})
185
+ expect(redelivered->Effect.annotationsToDict->Dict.get("retryCount"))->Expect.toEqual(
186
+ Some("2"),
187
+ )
188
+ })
189
+ })
190
+ })
@@ -0,0 +1,135 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Effect from "@reventlessdev/rescript-effect/src/Effect.res.mjs";
4
+ import * as Effect$1 from "effect/Effect";
5
+ import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
6
+ import * as RequestContext$ReventlessCore from "@reventlessdev/reventless-core/src/RequestContext.res.mjs";
7
+ import * as HandlerFactoryHelpersMjs from "@reventlessdev/reventless-aws/src/adapter/Runtime/HandlerFactoryHelpers.mjs";
8
+
9
+ let sqsRecords = [{
10
+ body: `{"meta":{"correlationId":"corr-1","causationId":"cause-1"}}`,
11
+ attributes: {
12
+ SentTimestamp: "1750000000000",
13
+ ApproximateReceiveCount: "3"
14
+ }
15
+ }];
16
+
17
+ let streamRecords = [{
18
+ dynamodb: {
19
+ NewImage: {
20
+ correlationId: {
21
+ S: "corr-2"
22
+ },
23
+ causationId: {
24
+ S: "cause-2"
25
+ }
26
+ },
27
+ ApproximateCreationDateTime: 1750000001.0
28
+ }
29
+ }];
30
+
31
+ function capture() {
32
+ return Effect.serviceWith(RequestContext$ReventlessCore.tag, ctx => ctx);
33
+ }
34
+
35
+ globalThis.describe("entry-point dispatch boundary", () => {
36
+ globalThis.describe("SQS records", () => {
37
+ globalThis.test("reads the envelope out of the body", () => {
38
+ globalThis.expect(HandlerFactoryHelpersMjs.extractMetaField(sqsRecords, "correlationId")).toEqual("corr-1");
39
+ globalThis.expect(HandlerFactoryHelpersMjs.extractMetaField(sqsRecords, "causationId")).toEqual("cause-1");
40
+ });
41
+ globalThis.test("reads SentTimestamp as ms since epoch", () => {
42
+ globalThis.expect(HandlerFactoryHelpersMjs.extractSentTimestamp(sqsRecords)).toEqual(1750000000000.0);
43
+ });
44
+ globalThis.test("reads ApproximateReceiveCount as the delivery attempt", () => {
45
+ globalThis.expect(HandlerFactoryHelpersMjs.extractRetryCount(sqsRecords)).toBe(3);
46
+ });
47
+ });
48
+ globalThis.describe("DynamoDB stream records", () => {
49
+ globalThis.test("reads the envelope off the flat NewImage attributes", () => {
50
+ globalThis.expect(HandlerFactoryHelpersMjs.extractMetaField(streamRecords, "correlationId")).toEqual("corr-2");
51
+ globalThis.expect(HandlerFactoryHelpersMjs.extractMetaField(streamRecords, "causationId")).toEqual("cause-2");
52
+ });
53
+ globalThis.test("converts ApproximateCreationDateTime from seconds to ms", () => {
54
+ globalThis.expect(HandlerFactoryHelpersMjs.extractSentTimestamp(streamRecords)).toEqual(1750000001000.0);
55
+ });
56
+ globalThis.test("reports a first delivery — streams have no receive count", () => {
57
+ globalThis.expect(HandlerFactoryHelpersMjs.extractRetryCount(streamRecords)).toBe(1);
58
+ });
59
+ });
60
+ globalThis.describe("absent or malformed input", () => {
61
+ globalThis.test("an empty batch has no send time", () => {
62
+ globalThis.expect(HandlerFactoryHelpersMjs.extractSentTimestamp([])).toEqual(undefined);
63
+ });
64
+ globalThis.test("an unparseable body yields no envelope field", () => {
65
+ globalThis.expect(HandlerFactoryHelpersMjs.extractMetaField([{
66
+ body: "not json"
67
+ }], "correlationId")).toEqual(undefined);
68
+ });
69
+ });
70
+ globalThis.describe("RequestContext provided at dispatch", () => {
71
+ globalThis.test("carries the full record, not a bare correlationId literal", async () => {
72
+ let ctx = await HandlerFactoryHelpersMjs.runEffect(Effect.serviceWith(RequestContext$ReventlessCore.tag, ctx => ctx), {
73
+ correlationId: "corr-1",
74
+ causationId: "cause-1",
75
+ comp: "AggregateRuntime(Customer)",
76
+ plugin: "Catalog",
77
+ timestamp: 1750000000000.0,
78
+ retryCount: 3
79
+ });
80
+ globalThis.expect(ctx.correlationId).toBe("corr-1");
81
+ globalThis.expect(ctx.causationId).toEqual("cause-1");
82
+ globalThis.expect(ctx.component).toEqual("AggregateRuntime(Customer)");
83
+ globalThis.expect(ctx.pluginName).toEqual("Catalog");
84
+ globalThis.expect(ctx.timestamp).toEqual(1750000000000.0);
85
+ globalThis.expect(ctx.retryCount).toEqual(3);
86
+ });
87
+ globalThis.test("fills identity and claims, which the record declares non-optional", async () => {
88
+ let ctx = await HandlerFactoryHelpersMjs.runEffect(Effect.serviceWith(RequestContext$ReventlessCore.tag, ctx => ctx), {
89
+ correlationId: "corr-1"
90
+ });
91
+ globalThis.expect(ctx.identity.userId).toBe(Identity$Reventless.anonymous.userId);
92
+ globalThis.expect(Object.keys(ctx.claims)).toEqual([]);
93
+ });
94
+ globalThis.test("falls back to a placeholder when no correlationId is on the message", async () => {
95
+ let ctx = await HandlerFactoryHelpersMjs.runEffect(Effect.serviceWith(RequestContext$ReventlessCore.tag, ctx => ctx), {
96
+ comp: "X(Y)"
97
+ });
98
+ globalThis.expect(ctx.correlationId).toBe("unknown");
99
+ });
100
+ });
101
+ globalThis.describe("log annotations", () => {
102
+ globalThis.test("promotes comp, causationId and the request id onto every line", async () => {
103
+ HandlerFactoryHelpersMjs.setRequestId("req-abc");
104
+ let annotated = await HandlerFactoryHelpersMjs.runEffect(Effect$1.logAnnotations, {
105
+ correlationId: "corr-1",
106
+ causationId: "cause-1",
107
+ comp: "EventCollector(Customers)"
108
+ });
109
+ let annotated$1 = Object.fromEntries(annotated);
110
+ globalThis.expect(annotated$1["comp"]).toEqual("EventCollector(Customers)");
111
+ globalThis.expect(annotated$1["causationId"]).toEqual("cause-1");
112
+ globalThis.expect(annotated$1["correlationId"]).toEqual("corr-1");
113
+ globalThis.expect(annotated$1["requestId"]).toEqual("req-abc");
114
+ });
115
+ globalThis.test("annotates retryCount only on a redelivery", async () => {
116
+ let first = await HandlerFactoryHelpersMjs.runEffect(Effect$1.logAnnotations, {
117
+ correlationId: "c",
118
+ retryCount: 1
119
+ });
120
+ globalThis.expect(Object.fromEntries(first)["retryCount"]).toEqual(undefined);
121
+ let redelivered = await HandlerFactoryHelpersMjs.runEffect(Effect$1.logAnnotations, {
122
+ correlationId: "c",
123
+ retryCount: 2
124
+ });
125
+ globalThis.expect(Object.fromEntries(redelivered)["retryCount"]).toEqual("2");
126
+ });
127
+ });
128
+ });
129
+
130
+ export {
131
+ sqsRecords,
132
+ streamRecords,
133
+ capture,
134
+ }
135
+ /* Not a pure module */
@@ -29,7 +29,7 @@ let runOneAppSyncEvent: (string, JSON.t) => promise<JSON.t> = %raw(`
29
29
  "@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs"
30
30
  );
31
31
  const Effect = await import("effect/Effect");
32
- const { tag: requestContextTag } = await import(
32
+ const { tag: requestContextTag, fromOptions: makeRequestContext } = await import(
33
33
  "@reventlessdev/reventless-core/src/RequestContext.res.mjs"
34
34
  );
35
35
  const { commandOutcomeToJson } = await import(
@@ -53,10 +53,12 @@ let runOneAppSyncEvent: (string, JSON.t) => promise<JSON.t> = %raw(`
53
53
  };
54
54
 
55
55
  const [, cmdGenHandlers] = await buildHandlersForConfig(config, { loadModule });
56
- const cmdGenHandler = cmdGenHandlers["AggTestAggregate"];
56
+ // Registry entries pair the handler with its dispatch comp
57
+ // (docs/plans/entrypoint-dispatch-parity-and-latency-fields.md).
58
+ const { handler: cmdGenHandler } = cmdGenHandlers["AggTestAggregate"];
57
59
 
58
60
  const effect = cmdGenHandler(event, {})
59
- .pipe(Effect.provideService(requestContextTag, { correlationId: "agg-test" }));
61
+ .pipe(Effect.provideService(requestContextTag, makeRequestContext({ correlationId: "agg-test" })));
60
62
  const outcome = await Effect.runPromise(effect);
61
63
  return commandOutcomeToJson(outcome);
62
64
  }
@@ -8,7 +8,7 @@ let runOneAppSyncEvent = (async (tableName, event) => {
8
8
  "@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs"
9
9
  );
10
10
  const Effect = await import("effect/Effect");
11
- const { tag: requestContextTag } = await import(
11
+ const { tag: requestContextTag, fromOptions: makeRequestContext } = await import(
12
12
  "@reventlessdev/reventless-core/src/RequestContext.res.mjs"
13
13
  );
14
14
  const { commandOutcomeToJson } = await import(
@@ -32,10 +32,12 @@ let runOneAppSyncEvent = (async (tableName, event) => {
32
32
  };
33
33
 
34
34
  const [, cmdGenHandlers] = await buildHandlersForConfig(config, { loadModule });
35
- const cmdGenHandler = cmdGenHandlers["AggTestAggregate"];
35
+ // Registry entries pair the handler with its dispatch comp
36
+ // (docs/plans/entrypoint-dispatch-parity-and-latency-fields.md).
37
+ const { handler: cmdGenHandler } = cmdGenHandlers["AggTestAggregate"];
36
38
 
37
39
  const effect = cmdGenHandler(event, {})
38
- .pipe(Effect.provideService(requestContextTag, { correlationId: "agg-test" }));
40
+ .pipe(Effect.provideService(requestContextTag, makeRequestContext({ correlationId: "agg-test" })));
39
41
  const outcome = await Effect.runPromise(effect);
40
42
  return commandOutcomeToJson(outcome);
41
43
  });