@reventlessdev/reventless-aws 3.0.0-alpha.231 → 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.
- package/CHANGELOG.md +23 -0
- package/package.json +7 -7
- package/src/adapter/Api/AppSyncEventsSigner_Ops.res +137 -0
- package/src/adapter/Api/AppSyncEventsSigner_Ops.res.mjs +121 -0
- package/src/adapter/Api/Platform_AdminScan_Ops.res +136 -0
- package/src/adapter/Api/Platform_AdminScan_Ops.res.mjs +98 -0
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +12 -90
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +11 -84
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +86 -0
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +79 -0
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +9 -79
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +7 -143
- package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res +48 -0
- package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res.mjs +63 -0
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +21 -80
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +23 -68
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res +70 -0
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res.mjs +68 -0
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -1
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -1
- package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +42 -126
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +246 -0
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +145 -0
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +27 -2
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +10 -3
- package/src/adapter/Runtime/CounterEntryPoint.mjs +19 -73
- package/src/adapter/Runtime/CounterEntryPoint_Ops.res +145 -0
- package/src/adapter/Runtime/CounterEntryPoint_Ops.res.mjs +129 -0
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +16 -3
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +11 -6
- package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +52 -510
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +816 -0
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +651 -0
- package/src/adapter/Runtime/EventMapperEntryPoint.mjs +23 -67
- package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +126 -0
- package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res.mjs +83 -0
- package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +16 -63
- package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +150 -0
- package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res.mjs +111 -0
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res +96 -0
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res.mjs +70 -0
- package/src/adapter/Runtime/PgMigrationEntryPoint.res +54 -0
- package/src/adapter/Runtime/PgMigrationEntryPoint.res.mjs +37 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +20 -55
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +155 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +94 -0
- package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +80 -0
- package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res.mjs +39 -0
- package/src/adapter/Runtime/QueryDbEntryPoint_Ops.res +4 -4
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +34 -153
- package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res +114 -0
- package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res.mjs +83 -0
- package/src/adapter/Runtime/SideEffectEntryPoint.mjs +23 -64
- package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res +71 -0
- package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res.mjs +57 -0
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +34 -165
- package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +206 -0
- package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res.mjs +105 -0
- package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +156 -0
- package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res.mjs +123 -0
- package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +18 -138
- package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +198 -0
- package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res.mjs +155 -0
- package/src/adapter/StateTopic/StateTopic_AppSync.res +12 -170
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +7 -167
- package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +208 -0
- package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +183 -0
- package/src/components/AutomationSlice_Builder.res +2 -0
- package/src/components/AutomationSlice_Builder.res.mjs +1 -1
- package/src/components/OutboundTranslationSlice_Builder.res +1 -0
- package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
- package/tests/AppSyncEventsSigner_OpsTest.res +75 -0
- package/tests/AppSyncEventsSigner_OpsTest.res.mjs +44 -0
- package/tests/AppSyncEventsSigner_ParityFixture.mjs +36 -0
- package/tests/AutomationSliceEntryPoint_OpsTest.res +165 -0
- package/tests/AutomationSliceEntryPoint_OpsTest.res.mjs +280 -0
- package/tests/CounterEntryPoint_OpsTest.res +115 -0
- package/tests/CounterEntryPoint_OpsTest.res.mjs +150 -0
- package/tests/EventCollectorEntryPoint_OpsTest.res +216 -0
- package/tests/EventCollectorEntryPoint_OpsTest.res.mjs +339 -0
- package/tests/EventMapperEntryPoint_OpsTest.res +103 -0
- package/tests/EventMapperEntryPoint_OpsTest.res.mjs +120 -0
- package/tests/ExtensionPointEntryPoint_OpsTest.res +57 -0
- package/tests/ExtensionPointEntryPoint_OpsTest.res.mjs +54 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res +22 -31
- package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +20 -23
- package/tests/PgQueryResolverEntryPoint_OpsTest.res +38 -0
- package/tests/PgQueryResolverEntryPoint_OpsTest.res.mjs +31 -0
- package/tests/Platform_AdminScan_OpsTest.res +80 -0
- package/tests/Platform_AdminScan_OpsTest.res.mjs +127 -0
- package/tests/ReadModelEntryPoint_OpsTest.res +141 -0
- package/tests/ReadModelEntryPoint_OpsTest.res.mjs +267 -0
- package/tests/SideEffectEntryPoint_OpsTest.res +79 -0
- package/tests/SideEffectEntryPoint_OpsTest.res.mjs +105 -0
- package/tests/StateViewSliceEntryPoint_OpsTest.res +167 -0
- package/tests/StateViewSliceEntryPoint_OpsTest.res.mjs +296 -0
- package/tests/TaskBucketEntryPoint_OpsTest.res +117 -0
- package/tests/TaskBucketEntryPoint_OpsTest.res.mjs +165 -0
- package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res +42 -13
- package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res.mjs +29 -6
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +0 -57
- package/src/adapter/Runtime/PgMigrationEntryPoint.mjs +0 -40
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Guards the typed cold-start core hoisted out of CounterEntryPoint.mjs:
|
|
2
|
+
// - parseHandlerConfig — reads `publishChannelId`, the field the deploy side
|
|
3
|
+
// (CounterHandler_DynamoDbStream.res) actually writes; the former shell
|
|
4
|
+
// read `publishQueueUrl` and published CountFinished events to an
|
|
5
|
+
// undefined queue URL.
|
|
6
|
+
// - splitRecords — the references/counts stream routing: NewImage rows carry
|
|
7
|
+
// increments (unparsable inc falls back to 1), NewAndOldImage on the
|
|
8
|
+
// references stream is a duplicate to ignore, counts take the new image on
|
|
9
|
+
// insert AND update, and foreign records are dropped.
|
|
10
|
+
|
|
11
|
+
open JestGlobals
|
|
12
|
+
|
|
13
|
+
let attrS = (s: string): AwsSdk.DynamoDb.Util.attributeValue => {string: s}
|
|
14
|
+
let attrN = (n: string): AwsSdk.DynamoDb.Util.attributeValue => {number: n}
|
|
15
|
+
|
|
16
|
+
let refsArn = "arn:stream-references"
|
|
17
|
+
let countsArn = "arn:stream-counts"
|
|
18
|
+
|
|
19
|
+
let mkRecord = (
|
|
20
|
+
~arn: string,
|
|
21
|
+
~source="aws:dynamodb",
|
|
22
|
+
~keysId: string,
|
|
23
|
+
~newImage: option<dict<AwsSdk.DynamoDb.Util.attributeValue>>=?,
|
|
24
|
+
~oldImage: option<dict<AwsSdk.DynamoDb.Util.attributeValue>>=?,
|
|
25
|
+
): PulumiAws.DynamoDb.Stream.record => {
|
|
26
|
+
awsRegion: "eu-west-1",
|
|
27
|
+
dynamodb: {
|
|
28
|
+
keys: {id: attrS(keysId)},
|
|
29
|
+
newImage: ?newImage,
|
|
30
|
+
oldImage: ?oldImage,
|
|
31
|
+
},
|
|
32
|
+
eventID: "e1",
|
|
33
|
+
eventName: INSERT,
|
|
34
|
+
eventSource: source,
|
|
35
|
+
eventSourceARN: arn,
|
|
36
|
+
eventVersion: "1",
|
|
37
|
+
userIdentity: "",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("CounterEntryPoint_Ops.parseHandlerConfig", () => {
|
|
41
|
+
testSync("reads publishChannelId (the field the deploy side writes)", () => {
|
|
42
|
+
let config = CounterEntryPoint_Ops.parseHandlerConfig(
|
|
43
|
+
`{"targetSpecModule":"@x/p/src/Aggregate/Product.res.mjs","countsTableName":"counts","publishChannelId":"https://sqs/q","referencesStreamArn":"${refsArn}","countsStreamArn":"${countsArn}"}`,
|
|
44
|
+
)
|
|
45
|
+
expect(config.publishChannelId)->toEqual(Some("https://sqs/q"))
|
|
46
|
+
expect(config.countsTableName)->toEqual(Some("counts"))
|
|
47
|
+
expect(config.referencesStreamArn)->toEqual(Some(refsArn))
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe("CounterEntryPoint_Ops.splitRecords", () => {
|
|
52
|
+
testSync("routes references and counts records by source ARN", () => {
|
|
53
|
+
let records = [
|
|
54
|
+
// references insert: {id, inc} view row → increment 2
|
|
55
|
+
mkRecord(
|
|
56
|
+
~arn=refsArn,
|
|
57
|
+
~keysId="c1#ref-a",
|
|
58
|
+
~newImage=Dict.fromArray([("id", attrS("c1#ref-a")), ("inc", attrN("2"))]),
|
|
59
|
+
),
|
|
60
|
+
// references insert without a parsable inc → fallback 1
|
|
61
|
+
mkRecord(
|
|
62
|
+
~arn=refsArn,
|
|
63
|
+
~keysId="c1#ref-b",
|
|
64
|
+
~newImage=Dict.fromArray([("id", attrS("c1#ref-b"))]),
|
|
65
|
+
),
|
|
66
|
+
// references update (NewAndOldImage) → duplicate, ignored
|
|
67
|
+
mkRecord(
|
|
68
|
+
~arn=refsArn,
|
|
69
|
+
~keysId="c1#ref-a",
|
|
70
|
+
~newImage=Dict.fromArray([("id", attrS("c1#ref-a")), ("inc", attrN("1"))]),
|
|
71
|
+
~oldImage=Dict.fromArray([("id", attrS("c1#ref-a")), ("inc", attrN("1"))]),
|
|
72
|
+
),
|
|
73
|
+
// counts insert AND update both take the new image
|
|
74
|
+
mkRecord(
|
|
75
|
+
~arn=countsArn,
|
|
76
|
+
~keysId="c1",
|
|
77
|
+
~newImage=Dict.fromArray([("id", attrS("c1")), ("count", attrN("0"))]),
|
|
78
|
+
),
|
|
79
|
+
mkRecord(
|
|
80
|
+
~arn=countsArn,
|
|
81
|
+
~keysId="c2",
|
|
82
|
+
~newImage=Dict.fromArray([("id", attrS("c2")), ("count", attrN("3"))]),
|
|
83
|
+
~oldImage=Dict.fromArray([("id", attrS("c2")), ("count", attrN("4"))]),
|
|
84
|
+
),
|
|
85
|
+
// foreign stream / foreign source → dropped
|
|
86
|
+
mkRecord(
|
|
87
|
+
~arn="arn:other",
|
|
88
|
+
~keysId="x",
|
|
89
|
+
~newImage=Dict.fromArray([("id", attrS("x"))]),
|
|
90
|
+
),
|
|
91
|
+
mkRecord(
|
|
92
|
+
~arn=refsArn,
|
|
93
|
+
~source="aws:sqs",
|
|
94
|
+
~keysId="y",
|
|
95
|
+
~newImage=Dict.fromArray([("id", attrS("y"))]),
|
|
96
|
+
),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
let (references, counts) = CounterEntryPoint_Ops.splitRecords(
|
|
100
|
+
~referencesStreamArn=refsArn,
|
|
101
|
+
~countsStreamArn=countsArn,
|
|
102
|
+
records,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
expect(references)->toEqual([("c1#ref-a", 2), ("c1#ref-b", 1)])
|
|
106
|
+
expect(counts->Array.length)->toBe(2)
|
|
107
|
+
expect(
|
|
108
|
+
counts
|
|
109
|
+
->Array.getUnsafe(1)
|
|
110
|
+
->JSON.Decode.object
|
|
111
|
+
->Option.flatMap(o => o->Dict.get("count"))
|
|
112
|
+
->Option.flatMap(JSON.Decode.float),
|
|
113
|
+
)->toEqual(Some(3.))
|
|
114
|
+
})
|
|
115
|
+
})
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as CounterEntryPoint_Ops$ReventlessAws from "../src/adapter/Runtime/CounterEntryPoint_Ops.res.mjs";
|
|
6
|
+
|
|
7
|
+
function attrS(s) {
|
|
8
|
+
return {
|
|
9
|
+
S: s
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function attrN(n) {
|
|
14
|
+
return {
|
|
15
|
+
N: n
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let refsArn = "arn:stream-references";
|
|
20
|
+
|
|
21
|
+
let countsArn = "arn:stream-counts";
|
|
22
|
+
|
|
23
|
+
function mkRecord(arn, sourceOpt, keysId, newImage, oldImage) {
|
|
24
|
+
let source = sourceOpt !== undefined ? sourceOpt : "aws:dynamodb";
|
|
25
|
+
return {
|
|
26
|
+
awsRegion: "eu-west-1",
|
|
27
|
+
dynamodb: {
|
|
28
|
+
Keys: {
|
|
29
|
+
id: attrS(keysId)
|
|
30
|
+
},
|
|
31
|
+
NewImage: newImage,
|
|
32
|
+
OldImage: oldImage
|
|
33
|
+
},
|
|
34
|
+
eventID: "e1",
|
|
35
|
+
eventName: "INSERT",
|
|
36
|
+
eventSource: source,
|
|
37
|
+
eventSourceARN: arn,
|
|
38
|
+
eventVersion: "1",
|
|
39
|
+
userIdentity: ""
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
globalThis.describe("CounterEntryPoint_Ops.parseHandlerConfig", () => {
|
|
44
|
+
globalThis.test("reads publishChannelId (the field the deploy side writes)", () => {
|
|
45
|
+
let config = CounterEntryPoint_Ops$ReventlessAws.parseHandlerConfig(`{"targetSpecModule":"@x/p/src/Aggregate/Product.res.mjs","countsTableName":"counts","publishChannelId":"https://sqs/q","referencesStreamArn":"` + refsArn + `","countsStreamArn":"` + countsArn + `"}`);
|
|
46
|
+
globalThis.expect(config.publishChannelId).toEqual("https://sqs/q");
|
|
47
|
+
globalThis.expect(config.countsTableName).toEqual("counts");
|
|
48
|
+
globalThis.expect(config.referencesStreamArn).toEqual(refsArn);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
globalThis.describe("CounterEntryPoint_Ops.splitRecords", () => {
|
|
53
|
+
globalThis.test("routes references and counts records by source ARN", () => {
|
|
54
|
+
let records = [
|
|
55
|
+
mkRecord(refsArn, undefined, "c1#ref-a", Object.fromEntries([
|
|
56
|
+
[
|
|
57
|
+
"id",
|
|
58
|
+
attrS("c1#ref-a")
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"inc",
|
|
62
|
+
attrN("2")
|
|
63
|
+
]
|
|
64
|
+
]), undefined),
|
|
65
|
+
mkRecord(refsArn, undefined, "c1#ref-b", Object.fromEntries([[
|
|
66
|
+
"id",
|
|
67
|
+
attrS("c1#ref-b")
|
|
68
|
+
]]), undefined),
|
|
69
|
+
mkRecord(refsArn, undefined, "c1#ref-a", Object.fromEntries([
|
|
70
|
+
[
|
|
71
|
+
"id",
|
|
72
|
+
attrS("c1#ref-a")
|
|
73
|
+
],
|
|
74
|
+
[
|
|
75
|
+
"inc",
|
|
76
|
+
attrN("1")
|
|
77
|
+
]
|
|
78
|
+
]), Object.fromEntries([
|
|
79
|
+
[
|
|
80
|
+
"id",
|
|
81
|
+
attrS("c1#ref-a")
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
"inc",
|
|
85
|
+
attrN("1")
|
|
86
|
+
]
|
|
87
|
+
])),
|
|
88
|
+
mkRecord(countsArn, undefined, "c1", Object.fromEntries([
|
|
89
|
+
[
|
|
90
|
+
"id",
|
|
91
|
+
attrS("c1")
|
|
92
|
+
],
|
|
93
|
+
[
|
|
94
|
+
"count",
|
|
95
|
+
attrN("0")
|
|
96
|
+
]
|
|
97
|
+
]), undefined),
|
|
98
|
+
mkRecord(countsArn, undefined, "c2", Object.fromEntries([
|
|
99
|
+
[
|
|
100
|
+
"id",
|
|
101
|
+
attrS("c2")
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
"count",
|
|
105
|
+
attrN("3")
|
|
106
|
+
]
|
|
107
|
+
]), Object.fromEntries([
|
|
108
|
+
[
|
|
109
|
+
"id",
|
|
110
|
+
attrS("c2")
|
|
111
|
+
],
|
|
112
|
+
[
|
|
113
|
+
"count",
|
|
114
|
+
attrN("4")
|
|
115
|
+
]
|
|
116
|
+
])),
|
|
117
|
+
mkRecord("arn:other", undefined, "x", Object.fromEntries([[
|
|
118
|
+
"id",
|
|
119
|
+
attrS("x")
|
|
120
|
+
]]), undefined),
|
|
121
|
+
mkRecord(refsArn, "aws:sqs", "y", Object.fromEntries([[
|
|
122
|
+
"id",
|
|
123
|
+
attrS("y")
|
|
124
|
+
]]), undefined)
|
|
125
|
+
];
|
|
126
|
+
let match = CounterEntryPoint_Ops$ReventlessAws.splitRecords(refsArn, countsArn, records);
|
|
127
|
+
let counts = match[1];
|
|
128
|
+
globalThis.expect(match[0]).toEqual([
|
|
129
|
+
[
|
|
130
|
+
"c1#ref-a",
|
|
131
|
+
2
|
|
132
|
+
],
|
|
133
|
+
[
|
|
134
|
+
"c1#ref-b",
|
|
135
|
+
1
|
|
136
|
+
]
|
|
137
|
+
]);
|
|
138
|
+
globalThis.expect(counts.length).toBe(2);
|
|
139
|
+
globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(counts[1]), o => o["count"]), Stdlib_JSON.Decode.float)).toEqual(3);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
attrS,
|
|
145
|
+
attrN,
|
|
146
|
+
refsArn,
|
|
147
|
+
countsArn,
|
|
148
|
+
mkRecord,
|
|
149
|
+
}
|
|
150
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Guards the typed cold-start core hoisted out of EventCollectorEntryPoint.mjs:
|
|
2
|
+
// - projectPluginRow — the Plugin-RM row projection the admin cross-plugin
|
|
3
|
+
// subscription manager reads; a drift in the decode (a dropped nested
|
|
4
|
+
// field, a mis-defaulted optional) would silently break cross-plugin SNS
|
|
5
|
+
// wiring, invisible until a multi-plugin deploy.
|
|
6
|
+
// - parseHandlerConfig — the HANDLER_CONFIG contract every EventCollector
|
|
7
|
+
// Lambda cold-starts on: named errors for malformed deploys, the former JS
|
|
8
|
+
// normalizer's defaults, and the connectExtension null/absent → None rule.
|
|
9
|
+
|
|
10
|
+
open JestGlobals
|
|
11
|
+
|
|
12
|
+
let str = JSON.Encode.string
|
|
13
|
+
let obj = pairs => JSON.Encode.object(Dict.fromArray(pairs))
|
|
14
|
+
|
|
15
|
+
let project = EventCollectorEntryPoint_Ops.projectPluginRow
|
|
16
|
+
|
|
17
|
+
describe("EventCollectorEntryPoint_Ops.projectPluginRow", () => {
|
|
18
|
+
testSync("rejects a non-object row", () => {
|
|
19
|
+
expect((project(JSON.Encode.string("nope")))->Option.isNone)->toBe(true)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
testSync("rejects a row missing id or status", () => {
|
|
23
|
+
expect(project(obj([("status", str("Connected"))]))->Option.isNone)->toBe(true)
|
|
24
|
+
expect(project(obj([("id", str("Catalog@1"))]))->Option.isNone)->toBe(true)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
testSync("projects the id/status/eventCollector subset with defaults", () => {
|
|
28
|
+
let p = project(obj([("id", str("Catalog@1")), ("status", str("Connected"))]))->Option.getOrThrow
|
|
29
|
+
expect(p.id)->toBe("Catalog@1")
|
|
30
|
+
expect(p.status)->toBe("Connected")
|
|
31
|
+
expect(p.eventCollector)->toBe("") // defaulted when absent
|
|
32
|
+
expect(p.extensions->Array.length)->toBe(0)
|
|
33
|
+
expect(p.extensionPoints->Array.length)->toBe(0)
|
|
34
|
+
expect(p.dcbEventLog->Option.isNone)->toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
testSync("decodes extensions, dropping entries without extensionPointName", () => {
|
|
38
|
+
let row = obj([
|
|
39
|
+
("id", str("Ordering@2")),
|
|
40
|
+
("status", str("Connected")),
|
|
41
|
+
("eventCollector", str("ec-arn")),
|
|
42
|
+
(
|
|
43
|
+
"extensions",
|
|
44
|
+
JSON.Encode.array([
|
|
45
|
+
obj([("name", str("OrdersExt")), ("extensionPointName", str("Catalog.Products")), ("dcbSources", JSON.Encode.array([str("prod"), JSON.Encode.int(7)]))]),
|
|
46
|
+
obj([("name", str("Malformed"))]), // no extensionPointName -> dropped
|
|
47
|
+
]),
|
|
48
|
+
),
|
|
49
|
+
])
|
|
50
|
+
let p = project(row)->Option.getOrThrow
|
|
51
|
+
expect(p.eventCollector)->toBe("ec-arn")
|
|
52
|
+
expect(p.extensions->Array.length)->toBe(1)
|
|
53
|
+
let e = p.extensions->Array.getUnsafe(0)
|
|
54
|
+
expect(e.name)->toBe("OrdersExt")
|
|
55
|
+
expect(e.extensionPointName)->toBe("Catalog.Products")
|
|
56
|
+
// Non-string dcbSources elements are filtered out.
|
|
57
|
+
expect(e.dcbSources)->toEqual(["prod"])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
testSync("decodes extensionPoints and dcbEventLog; drops EPs without name/eventTopic", () => {
|
|
61
|
+
let row = obj([
|
|
62
|
+
("id", str("Catalog@1")),
|
|
63
|
+
("status", str("Connected")),
|
|
64
|
+
(
|
|
65
|
+
"extensionPoints",
|
|
66
|
+
JSON.Encode.array([
|
|
67
|
+
obj([("name", str("Catalog.Products")), ("eventTopic", str("t-arn"))]),
|
|
68
|
+
obj([("name", str("NoTopic"))]), // no eventTopic -> dropped
|
|
69
|
+
]),
|
|
70
|
+
),
|
|
71
|
+
("dcbEventLog", obj([("name", str("catalog-dcb")), ("eventTopicArn", str("dcb-arn"))])),
|
|
72
|
+
])
|
|
73
|
+
let p = project(row)->Option.getOrThrow
|
|
74
|
+
expect(p.extensionPoints->Array.length)->toBe(1)
|
|
75
|
+
let ep = p.extensionPoints->Array.getUnsafe(0)
|
|
76
|
+
expect(ep.name)->toBe("Catalog.Products")
|
|
77
|
+
expect(ep.commandTopic)->toBe("") // defaulted
|
|
78
|
+
expect(ep.eventTopic)->toBe("t-arn")
|
|
79
|
+
let dcb = p.dcbEventLog->Option.getOrThrow
|
|
80
|
+
expect(dcb.name)->toBe("catalog-dcb")
|
|
81
|
+
expect(dcb.eventTopicArn)->toBe("dcb-arn")
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
testSync("drops a dcbEventLog missing eventTopicArn", () => {
|
|
85
|
+
let row = obj([
|
|
86
|
+
("id", str("Catalog@1")),
|
|
87
|
+
("status", str("Connected")),
|
|
88
|
+
("dcbEventLog", obj([("name", str("catalog-dcb"))])),
|
|
89
|
+
])
|
|
90
|
+
expect((project(row)->Option.getOrThrow).dcbEventLog->Option.isNone)->toBe(true)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// ── parseHandlerConfig ────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
// A minimal valid HANDLER_CONFIG (every required field present).
|
|
97
|
+
let minimalConfig = (extras: array<(string, JSON.t)>): string =>
|
|
98
|
+
Array.concat(
|
|
99
|
+
[
|
|
100
|
+
("queueUrl", str("q-url")),
|
|
101
|
+
("pluginExtensionPointCmdTopicUrl", str("ep-url")),
|
|
102
|
+
("eventTopicArn", str("topic-arn")),
|
|
103
|
+
("pluginReadModelTableName", str("plugin-rm")),
|
|
104
|
+
("appSyncApiId", str("NOT_AVAILABLE")),
|
|
105
|
+
("schedulerRoleArn", str("role-arn")),
|
|
106
|
+
("schedulerQueueArn", str("")),
|
|
107
|
+
("schedulerQueueName", str("")),
|
|
108
|
+
("extensionPoints", JSON.Encode.array([])),
|
|
109
|
+
("extensions", JSON.Encode.array([])),
|
|
110
|
+
("publishToAggregates", obj([])),
|
|
111
|
+
],
|
|
112
|
+
extras,
|
|
113
|
+
)
|
|
114
|
+
->Dict.fromArray
|
|
115
|
+
->JSON.Encode.object
|
|
116
|
+
->JSON.stringify
|
|
117
|
+
|
|
118
|
+
let throws = (f: unit => 'a): option<string> =>
|
|
119
|
+
try {
|
|
120
|
+
let _ = f()
|
|
121
|
+
None
|
|
122
|
+
} catch {
|
|
123
|
+
| exn => exn->JsExn.fromException->Option.flatMap(JsExn.message)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
|
|
127
|
+
testSync("throws the named errors for empty / malformed / incomplete config", () => {
|
|
128
|
+
let msgOf = raw => throws(() => EventCollectorEntryPoint_Ops.parseHandlerConfig(raw))
|
|
129
|
+
expect(msgOf(""))->toEqual(Some("HANDLER_CONFIG env var is empty"))
|
|
130
|
+
expect(
|
|
131
|
+
msgOf("{nope")->Option.mapOr(false, m => m->String.startsWith("HANDLER_CONFIG JSON parse error:")),
|
|
132
|
+
)->toBe(true)
|
|
133
|
+
expect(msgOf(`{"queueUrl":"q"}`))->toEqual(
|
|
134
|
+
Some("HANDLER_CONFIG missing required field: pluginExtensionPointCmdTopicUrl"),
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
testSync("decodes a minimal config with empty-collection defaults", () => {
|
|
139
|
+
let c = EventCollectorEntryPoint_Ops.parseHandlerConfig(minimalConfig([]))
|
|
140
|
+
expect(c.queueUrl)->toBe("q-url")
|
|
141
|
+
expect(c.connectExtension->Option.isNone)->toBe(true) // absent → None
|
|
142
|
+
expect(c.readModelQueueUrls->Dict.keysToArray->Array.length)->toBe(0)
|
|
143
|
+
expect(c.readModelNamesForSourceName->Dict.keysToArray->Array.length)->toBe(0)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
testSync("connectExtension: JSON null (admin) maps to None; a record decodes", () => {
|
|
147
|
+
let asNull = EventCollectorEntryPoint_Ops.parseHandlerConfig(
|
|
148
|
+
minimalConfig([("connectExtension", JSON.Encode.null)]),
|
|
149
|
+
)
|
|
150
|
+
expect(asNull.connectExtension->Option.isNone)->toBe(true)
|
|
151
|
+
|
|
152
|
+
let asRecord = EventCollectorEntryPoint_Ops.parseHandlerConfig(
|
|
153
|
+
minimalConfig([
|
|
154
|
+
(
|
|
155
|
+
"connectExtension",
|
|
156
|
+
obj([
|
|
157
|
+
("specModule", str("spec.mjs")),
|
|
158
|
+
("mappingsModule", str("map.mjs")),
|
|
159
|
+
("extensionPointName", str("Platform.Plugin")),
|
|
160
|
+
]),
|
|
161
|
+
),
|
|
162
|
+
]),
|
|
163
|
+
)
|
|
164
|
+
let ce = asRecord.connectExtension->Option.getOrThrow
|
|
165
|
+
expect(ce.extensionPointName)->toBe("Platform.Plugin")
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
testSync("extension entries get the former JS normalizer's defaults", () => {
|
|
169
|
+
let c = EventCollectorEntryPoint_Ops.parseHandlerConfig(
|
|
170
|
+
minimalConfig([
|
|
171
|
+
(
|
|
172
|
+
"extensions",
|
|
173
|
+
JSON.Encode.array([
|
|
174
|
+
// no name, no module specifiers, no aggregate/readModel lists
|
|
175
|
+
obj([("extensionPointName", str("Ordering.Orders"))]),
|
|
176
|
+
]),
|
|
177
|
+
),
|
|
178
|
+
]),
|
|
179
|
+
)
|
|
180
|
+
let ext = c.extensions->Array.getUnsafe(0)
|
|
181
|
+
expect(ext.name)->toBe("Ordering.Orders") // name falls back to the EP name
|
|
182
|
+
expect(ext.specModule)->toBe("")
|
|
183
|
+
expect(ext.aggregateNames)->toEqual([])
|
|
184
|
+
expect(ext.readModelNames)->toEqual([])
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
testSync("decodes extensionPoints entries and the env-var maps", () => {
|
|
188
|
+
let c = EventCollectorEntryPoint_Ops.parseHandlerConfig(
|
|
189
|
+
minimalConfig([
|
|
190
|
+
(
|
|
191
|
+
"extensionPoints",
|
|
192
|
+
JSON.Encode.array([
|
|
193
|
+
obj([
|
|
194
|
+
("specModule", str("s.mjs")),
|
|
195
|
+
("mappingsModule", str("m.mjs")),
|
|
196
|
+
("eventTopicArn", str("arn:ep")),
|
|
197
|
+
("aggregateNames", JSON.Encode.array([str("Plugin")])),
|
|
198
|
+
]),
|
|
199
|
+
]),
|
|
200
|
+
),
|
|
201
|
+
("publishToAggregates", obj([("Plugin", str("PTA_Plugin_QUEUE_URL"))])),
|
|
202
|
+
(
|
|
203
|
+
"readModelNamesForSourceName",
|
|
204
|
+
obj([("Ordering.Orders", JSON.Encode.array([str("ProductDemands")]))]),
|
|
205
|
+
),
|
|
206
|
+
]),
|
|
207
|
+
)
|
|
208
|
+
let ep = c.extensionPoints->Array.getUnsafe(0)
|
|
209
|
+
expect(ep.eventTopicArn)->toBe("arn:ep")
|
|
210
|
+
expect(ep.aggregateNames)->toEqual(["Plugin"])
|
|
211
|
+
expect(c.publishToAggregates->Dict.get("Plugin"))->toEqual(Some("PTA_Plugin_QUEUE_URL"))
|
|
212
|
+
expect(c.readModelNamesForSourceName->Dict.get("Ordering.Orders"))->toEqual(
|
|
213
|
+
Some(["ProductDemands"]),
|
|
214
|
+
)
|
|
215
|
+
})
|
|
216
|
+
})
|