@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.
- package/CHANGELOG.md +18 -0
- package/package.json +8 -8
- package/src/Platform.res.mjs +4 -0
- package/src/Platform_Stack.res +6 -0
- package/src/Platform_Stack.res.mjs +3 -1
- package/src/adapter/AWS_Tags.res +127 -12
- package/src/adapter/AWS_Tags.res.mjs +58 -17
- package/src/adapter/Api/AppSync_SourceApiAssociation_Retrying.res +26 -1
- package/src/adapter/Api/AppSync_SourceApiAssociation_Retrying.res.mjs +12 -1
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +13 -1
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +3 -4
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +13 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +3 -4
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res +12 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res.mjs +4 -2
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res +6 -0
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res.mjs +2 -1
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res +2 -2
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res.mjs +2 -2
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res +2 -2
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res.mjs +2 -2
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res +2 -2
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res.mjs +2 -2
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res +2 -2
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res.mjs +2 -2
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +1 -0
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +1 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res +1 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res.mjs +1 -2
- package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res +1 -1
- package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs +1 -1
- package/src/adapter/EventCollector/EventCollectorChannel_SQS.res +2 -2
- package/src/adapter/EventCollector/EventCollectorChannel_SQS.res.mjs +2 -2
- package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res +2 -2
- package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res.mjs +2 -2
- package/src/adapter/EventLog/EventLogStorage.res +3 -3
- package/src/adapter/EventLog/EventLogStorage.res.mjs +3 -3
- package/src/adapter/EventLog/EventLogStorage_DynamoDb.res +2 -2
- package/src/adapter/EventLog/EventLogStorage_DynamoDb.res.mjs +2 -2
- package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res +2 -2
- package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res.mjs +2 -2
- package/src/adapter/EventLog/EventLogStorage_Postgres.res +1 -1
- package/src/adapter/EventLog/EventLogStorage_Postgres.res.mjs +1 -1
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +7 -1
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +2 -2
- package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res +1 -1
- package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res.mjs +1 -1
- package/src/adapter/EventTopic/EventTopicPublisher_SNS.res +2 -2
- package/src/adapter/EventTopic/EventTopicPublisher_SNS.res.mjs +2 -2
- package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res +2 -2
- package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res.mjs +2 -2
- package/src/adapter/Mcp/MCP_Lambda.res.mjs +1 -1
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +7 -0
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +4 -2
- package/src/adapter/Postgres/PgMigration_Builder.res +1 -0
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -1
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res +7 -0
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +4 -2
- package/src/adapter/QueryDb/QueryDbStorage.res +6 -6
- package/src/adapter/QueryDb/QueryDbStorage.res.mjs +6 -6
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res +2 -2
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs +2 -2
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res +2 -2
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res.mjs +2 -2
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +1 -1
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +1 -1
- package/src/adapter/Runtime/AdminEventCollectorEntryPoint.mjs +24 -41
- package/src/adapter/Runtime/AggregateEntryPoint.mjs +52 -45
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res +19 -2
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res.mjs +8 -5
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res +19 -2
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res.mjs +8 -5
- package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res +9 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res.mjs +4 -2
- package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res +9 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res.mjs +4 -2
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +9 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +4 -2
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +9 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +4 -2
- package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +36 -22
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +1 -0
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +1 -1
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +39 -42
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res +7 -1
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res.mjs +4 -2
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +19 -1
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +5 -2
- package/src/adapter/Runtime/EventMapperEntryPoint.mjs +25 -23
- package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +25 -41
- package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res +1 -0
- package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res.mjs +1 -1
- package/src/adapter/Runtime/HandlerFactoryHelpers.mjs +126 -0
- package/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs +24 -41
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +33 -24
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +52 -2
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +25 -7
- package/src/adapter/Runtime/SideEffectEntryPoint.mjs +25 -24
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +9 -1
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +4 -2
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +1 -0
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +1 -1
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +26 -21
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +2 -1
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +2 -2
- package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res +1 -0
- package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +1 -1
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res +12 -0
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res.mjs +4 -2
- package/src/adapter/StateTopic/StateTopic_AppSync.res +7 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +2 -2
- package/src/adapter/Task/TaskBucket_S3.res +1 -0
- package/src/adapter/Task/TaskBucket_S3.res.mjs +7 -1
- package/src/components/Aggregate_Builder_Micro.res.mjs +2 -0
- package/src/components/Aggregate_Builder_NoResolver.res.mjs +2 -0
- package/src/components/Aggregate_Builder_PerAggregate.res.mjs +2 -0
- package/src/components/Aggregate_Builder_Single.res.mjs +2 -0
- package/src/components/Aggregate_Builder_Single_Async.res.mjs +2 -0
- package/src/components/Api/AppSync_Adapter.res +12 -0
- package/src/components/Api/AppSync_Adapter.res.mjs +6 -2
- package/src/components/Api/AppSync_MergedApi.res +12 -0
- package/src/components/Api/AppSync_MergedApi.res.mjs +5 -2
- package/src/components/AutomationSlice_Builder.res.mjs +2 -0
- package/src/components/ExtensionPoint_Builder.res.mjs +2 -0
- package/src/components/ForeignReadModel_Builder.res.mjs +4 -0
- package/src/components/OutboundTranslationSlice_Builder.res.mjs +2 -0
- package/src/components/Plugin.res.mjs +2 -0
- package/src/components/ReadModel_Builder_NoResolver.res.mjs +2 -0
- package/src/components/ReadModel_Builder_NoResolver_Stream.res.mjs +2 -0
- package/src/components/ReadModel_Builder_PerReadModel.res.mjs +2 -0
- package/src/components/ReadModel_Builder_Single.res.mjs +2 -0
- package/src/components/ReadModel_Builder_Single_Stream.res.mjs +2 -0
- package/src/components/SideEffectHandler_PerSideEffectHandler.res.mjs +6 -0
- package/src/components/SideEffectHandler_Single.res.mjs +4 -0
- package/src/components/StateViewSlice_Builder.res.mjs +2 -0
- package/src/components/StateViewSlice_Builder_Stream.res.mjs +2 -0
- package/src/components/Task_Builder_PerBucket.res.mjs +4 -0
- package/src/components/Vpc.res +10 -1
- package/src/components/Vpc.res.mjs +3 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res +26 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +9 -6
- package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res +6 -0
- package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res.mjs +4 -1
- package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res +1 -0
- package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +1 -1
- package/src/plugin/runtime/PluginRuntime_Builder.res +2 -0
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +2 -2
- package/src/plugin/stack/Plugin_ExtensionPoint_Builder.res.mjs +4 -0
- package/src/plugin/stack/Plugin_Stack.res +26 -1
- package/src/plugin/stack/Plugin_Stack.res.mjs +8 -3
- package/src/util/Util_CognitoGroupUser.res +6 -0
- package/src/util/Util_CognitoGroupUser.res.mjs +3 -1
- package/src/util/Util_DeadLetterQueue.res +8 -2
- package/src/util/Util_DeadLetterQueue.res.mjs +3 -4
- package/src/util/Util_LogAttribution.res +31 -0
- package/src/util/Util_LogAttribution.res.mjs +27 -0
- package/tests/AWS_TagsTest.res +236 -0
- package/tests/AWS_TagsTest.res.mjs +153 -0
- package/tests/AppSync_SourceApiAssociation_RetryingTest.res +26 -0
- package/tests/AppSync_SourceApiAssociation_RetryingTest.res.mjs +20 -0
- package/tests/HandlerFactoryDispatchTest.res +190 -0
- package/tests/HandlerFactoryDispatchTest.res.mjs +135 -0
- package/tests/integration/AggregateEntryPoint_IntegrationTest.res +5 -3
- 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
|
-
|
|
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
|
-
|
|
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
|
});
|