@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,267 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as Stream from "effect/Stream";
|
|
5
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
6
|
+
import * as ReadModelEntryPoint_Ops$ReventlessAws from "../src/adapter/Runtime/ReadModelEntryPoint_Ops.res.mjs";
|
|
7
|
+
|
|
8
|
+
function str(prim) {
|
|
9
|
+
return prim;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function obj(pairs) {
|
|
13
|
+
return Object.fromEntries(pairs);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
globalThis.describe("ReadModelEntryPoint_Ops.parseHandlerConfig", () => {
|
|
17
|
+
globalThis.test("empty raw config yields no handlers", () => {
|
|
18
|
+
globalThis.expect(ReadModelEntryPoint_Ops$ReventlessAws.parseHandlerConfig("").length).toBe(0);
|
|
19
|
+
globalThis.expect(ReadModelEntryPoint_Ops$ReventlessAws.parseHandlerConfig(`{"handlers":[]}`).length).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
globalThis.test("decodes a full entry incl. attribution and Postgres fields", () => {
|
|
22
|
+
let config = JSON.stringify(Object.fromEntries([[
|
|
23
|
+
"handlers",
|
|
24
|
+
[Object.fromEntries([
|
|
25
|
+
[
|
|
26
|
+
"specModule",
|
|
27
|
+
"@x/spec/src/ReadModel/Products.res.mjs"
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
"mappingsModule",
|
|
31
|
+
"@x/spec/src/ReadModel/Products_Projections.res.mjs"
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
"queryDbTableName",
|
|
35
|
+
"Products-abc"
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
"sourceUrn",
|
|
39
|
+
"arn:aws:sqs:eu-west-1:1:q"
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
"comp",
|
|
43
|
+
"EventCollector(ProductsEC)"
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
"plugin",
|
|
47
|
+
"Catalog"
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"stateTopicName",
|
|
51
|
+
"catalogProducts"
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"pgConnection",
|
|
55
|
+
Object.fromEntries([
|
|
56
|
+
[
|
|
57
|
+
"host",
|
|
58
|
+
"db.local"
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"port",
|
|
62
|
+
5432
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
"database",
|
|
66
|
+
"app"
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
"username",
|
|
70
|
+
"master"
|
|
71
|
+
],
|
|
72
|
+
[
|
|
73
|
+
"secretArn",
|
|
74
|
+
"arn:secret"
|
|
75
|
+
]
|
|
76
|
+
])
|
|
77
|
+
]
|
|
78
|
+
])]
|
|
79
|
+
]]));
|
|
80
|
+
let entries = ReadModelEntryPoint_Ops$ReventlessAws.parseHandlerConfig(config);
|
|
81
|
+
globalThis.expect(entries.length).toBe(1);
|
|
82
|
+
let e = entries[0];
|
|
83
|
+
globalThis.expect(e.specModule).toBe("@x/spec/src/ReadModel/Products.res.mjs");
|
|
84
|
+
globalThis.expect(e.mappingsModule).toBe("@x/spec/src/ReadModel/Products_Projections.res.mjs");
|
|
85
|
+
globalThis.expect(e.queryDbTableName).toBe("Products-abc");
|
|
86
|
+
globalThis.expect(e.sourceUrn).toBe("arn:aws:sqs:eu-west-1:1:q");
|
|
87
|
+
globalThis.expect(e.comp).toEqual("EventCollector(ProductsEC)");
|
|
88
|
+
globalThis.expect(e.plugin).toEqual("Catalog");
|
|
89
|
+
globalThis.expect(e.stateTopicName).toEqual("catalogProducts");
|
|
90
|
+
let cc = e.pgConnection;
|
|
91
|
+
if (cc !== undefined) {
|
|
92
|
+
globalThis.expect(cc.host).toBe("db.local");
|
|
93
|
+
globalThis.expect(cc.port).toBe(5432);
|
|
94
|
+
globalThis.expect(cc.database).toBe("app");
|
|
95
|
+
return;
|
|
96
|
+
} else {
|
|
97
|
+
return Stdlib_JsError.throwWithMessage("expected Some(pgConnection)");
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
globalThis.test("optional fields default to None; null pgConnection maps to None", () => {
|
|
101
|
+
let config = JSON.stringify(Object.fromEntries([[
|
|
102
|
+
"handlers",
|
|
103
|
+
[Object.fromEntries([
|
|
104
|
+
[
|
|
105
|
+
"specModule",
|
|
106
|
+
"a.res.mjs"
|
|
107
|
+
],
|
|
108
|
+
[
|
|
109
|
+
"mappingsModule",
|
|
110
|
+
"b.res.mjs"
|
|
111
|
+
],
|
|
112
|
+
[
|
|
113
|
+
"queryDbTableName",
|
|
114
|
+
"t"
|
|
115
|
+
],
|
|
116
|
+
[
|
|
117
|
+
"sourceUrn",
|
|
118
|
+
"u"
|
|
119
|
+
],
|
|
120
|
+
[
|
|
121
|
+
"pgConnection",
|
|
122
|
+
null
|
|
123
|
+
]
|
|
124
|
+
])]
|
|
125
|
+
]]));
|
|
126
|
+
let e = ReadModelEntryPoint_Ops$ReventlessAws.parseHandlerConfig(config)[0];
|
|
127
|
+
globalThis.expect(Stdlib_Option.isNone(e.comp)).toBe(true);
|
|
128
|
+
globalThis.expect(Stdlib_Option.isNone(e.plugin)).toBe(true);
|
|
129
|
+
globalThis.expect(Stdlib_Option.isNone(e.stateTopicName)).toBe(true);
|
|
130
|
+
globalThis.expect(Stdlib_Option.isNone(e.pgConnection)).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
globalThis.describe("ReadModelEntryPoint_Ops.injectId", () => {
|
|
135
|
+
globalThis.test("injects (and overwrites) id on object states", () => {
|
|
136
|
+
globalThis.expect(ReadModelEntryPoint_Ops$ReventlessAws.injectId("p-1", Object.fromEntries([[
|
|
137
|
+
"name",
|
|
138
|
+
"Widget"
|
|
139
|
+
]]))).toEqual(Object.fromEntries([
|
|
140
|
+
[
|
|
141
|
+
"name",
|
|
142
|
+
"Widget"
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
"id",
|
|
146
|
+
"p-1"
|
|
147
|
+
]
|
|
148
|
+
]));
|
|
149
|
+
globalThis.expect(ReadModelEntryPoint_Ops$ReventlessAws.injectId("p-1", Object.fromEntries([[
|
|
150
|
+
"id",
|
|
151
|
+
"stale"
|
|
152
|
+
]]))).toEqual(Object.fromEntries([[
|
|
153
|
+
"id",
|
|
154
|
+
"p-1"
|
|
155
|
+
]]));
|
|
156
|
+
});
|
|
157
|
+
globalThis.test("passes non-object states through unchanged", () => {
|
|
158
|
+
globalThis.expect(ReadModelEntryPoint_Ops$ReventlessAws.injectId("p-1", "scalar")).toEqual("scalar");
|
|
159
|
+
globalThis.expect(ReadModelEntryPoint_Ops$ReventlessAws.injectId("p-1", ["a"])).toEqual(["a"]);
|
|
160
|
+
globalThis.expect(ReadModelEntryPoint_Ops$ReventlessAws.injectId("p-1", null)).toEqual(null);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
globalThis.describe("ReadModelEntryPoint_Ops.withInjectedId", () => {
|
|
165
|
+
let makeStub = saved => ({
|
|
166
|
+
load: async param => ({
|
|
167
|
+
TAG: "Ok",
|
|
168
|
+
_0: []
|
|
169
|
+
}),
|
|
170
|
+
loadStream: param => Stream.fromIterable([]),
|
|
171
|
+
save: async (id, state, _mode, _ttl) => {
|
|
172
|
+
saved.push([
|
|
173
|
+
id,
|
|
174
|
+
state
|
|
175
|
+
]);
|
|
176
|
+
return {
|
|
177
|
+
TAG: "Ok",
|
|
178
|
+
_0: undefined
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
saveBatch: async items => {
|
|
182
|
+
items.forEach(param => {
|
|
183
|
+
saved.push([
|
|
184
|
+
param[0],
|
|
185
|
+
param[1]
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
TAG: "Ok",
|
|
190
|
+
_0: undefined
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
count: async (param, param$1, param$2) => ({
|
|
194
|
+
TAG: "Ok",
|
|
195
|
+
_0: 0
|
|
196
|
+
}),
|
|
197
|
+
delete: async (param, param$1) => ({
|
|
198
|
+
TAG: "Ok",
|
|
199
|
+
_0: undefined
|
|
200
|
+
}),
|
|
201
|
+
deleteBatch: async param => ({
|
|
202
|
+
TAG: "Ok",
|
|
203
|
+
_0: undefined
|
|
204
|
+
})
|
|
205
|
+
});
|
|
206
|
+
globalThis.test("save and saveBatch write id-injected states", async () => {
|
|
207
|
+
let saved = [];
|
|
208
|
+
let ops = ReadModelEntryPoint_Ops$ReventlessAws.withInjectedId(makeStub(saved));
|
|
209
|
+
await ops.save("p-1", Object.fromEntries([[
|
|
210
|
+
"name",
|
|
211
|
+
"A"
|
|
212
|
+
]]), "Any", undefined);
|
|
213
|
+
await ops.saveBatch([
|
|
214
|
+
[
|
|
215
|
+
"p-2",
|
|
216
|
+
Object.fromEntries([[
|
|
217
|
+
"name",
|
|
218
|
+
"B"
|
|
219
|
+
]]),
|
|
220
|
+
undefined
|
|
221
|
+
],
|
|
222
|
+
[
|
|
223
|
+
"p-3",
|
|
224
|
+
"scalar",
|
|
225
|
+
undefined
|
|
226
|
+
]
|
|
227
|
+
]);
|
|
228
|
+
globalThis.expect(saved).toEqual([
|
|
229
|
+
[
|
|
230
|
+
"p-1",
|
|
231
|
+
Object.fromEntries([
|
|
232
|
+
[
|
|
233
|
+
"name",
|
|
234
|
+
"A"
|
|
235
|
+
],
|
|
236
|
+
[
|
|
237
|
+
"id",
|
|
238
|
+
"p-1"
|
|
239
|
+
]
|
|
240
|
+
])
|
|
241
|
+
],
|
|
242
|
+
[
|
|
243
|
+
"p-2",
|
|
244
|
+
Object.fromEntries([
|
|
245
|
+
[
|
|
246
|
+
"name",
|
|
247
|
+
"B"
|
|
248
|
+
],
|
|
249
|
+
[
|
|
250
|
+
"id",
|
|
251
|
+
"p-2"
|
|
252
|
+
]
|
|
253
|
+
])
|
|
254
|
+
],
|
|
255
|
+
[
|
|
256
|
+
"p-3",
|
|
257
|
+
"scalar"
|
|
258
|
+
]
|
|
259
|
+
]);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
export {
|
|
264
|
+
str,
|
|
265
|
+
obj,
|
|
266
|
+
}
|
|
267
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Guards the typed cold-start core hoisted out of SideEffectEntryPoint.mjs:
|
|
2
|
+
// - parseHandlerConfig — the {handlers:[{sideEffectModules, sourceUrn,
|
|
3
|
+
// comp, plugin}]} shape written by SideEffectHandlerRuntime_Builder_Single;
|
|
4
|
+
// a drift here silently drops a side effect's stream subscription.
|
|
5
|
+
// - makeRegisteredHandler — comp/plugin attribution threading into the
|
|
6
|
+
// shared routed dispatch boundary.
|
|
7
|
+
|
|
8
|
+
open JestGlobals
|
|
9
|
+
|
|
10
|
+
let str = JSON.Encode.string
|
|
11
|
+
let obj = pairs => JSON.Encode.object(Dict.fromArray(pairs))
|
|
12
|
+
|
|
13
|
+
describe("SideEffectEntryPoint_Ops.parseHandlerConfig", () => {
|
|
14
|
+
testSync("empty raw config yields no handlers", () => {
|
|
15
|
+
expect(SideEffectEntryPoint_Ops.parseHandlerConfig("")->Array.length)->toBe(0)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
testSync("decodes handlers incl. attribution fields", () => {
|
|
19
|
+
let config = obj([
|
|
20
|
+
(
|
|
21
|
+
"handlers",
|
|
22
|
+
JSON.Encode.array([
|
|
23
|
+
obj([
|
|
24
|
+
("sideEffectModules", JSON.Encode.array([str("@x/p/src/A.res.mjs"), str("@x/p/src/B.res.mjs")])),
|
|
25
|
+
("sourceUrn", str("arn:stream-1")),
|
|
26
|
+
("comp", str("SideEffectHandler(Mailer)")),
|
|
27
|
+
("plugin", str("Ordering")),
|
|
28
|
+
]),
|
|
29
|
+
obj([
|
|
30
|
+
("sideEffectModules", JSON.Encode.array([str("@x/p/src/C.res.mjs")])),
|
|
31
|
+
("sourceUrn", str("arn:stream-2")),
|
|
32
|
+
]),
|
|
33
|
+
]),
|
|
34
|
+
),
|
|
35
|
+
])->JSON.stringify
|
|
36
|
+
let entries = SideEffectEntryPoint_Ops.parseHandlerConfig(config)
|
|
37
|
+
expect(entries->Array.length)->toBe(2)
|
|
38
|
+
let first = entries->Array.getUnsafe(0)
|
|
39
|
+
expect(first.sourceUrn)->toBe("arn:stream-1")
|
|
40
|
+
expect(first.sideEffectModules)->toEqual(["@x/p/src/A.res.mjs", "@x/p/src/B.res.mjs"])
|
|
41
|
+
expect(first.comp)->toEqual(Some("SideEffectHandler(Mailer)"))
|
|
42
|
+
expect(first.plugin)->toEqual(Some("Ordering"))
|
|
43
|
+
let second = entries->Array.getUnsafe(1)
|
|
44
|
+
expect(second.comp->Option.isNone)->toBe(true)
|
|
45
|
+
expect(second.plugin->Option.isNone)->toBe(true)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe("SideEffectEntryPoint_Ops.noopQueryEngine", () => {
|
|
50
|
+
test("returns empty results for scan and query", async () => {
|
|
51
|
+
let scanned = await SideEffectEntryPoint_Ops.noopQueryEngine.scan(
|
|
52
|
+
~readModelName="X",
|
|
53
|
+
~filterConfigs=[],
|
|
54
|
+
~limit=10,
|
|
55
|
+
)
|
|
56
|
+
expect(scanned->Array.length)->toBe(0)
|
|
57
|
+
let queried = await SideEffectEntryPoint_Ops.noopQueryEngine.query(
|
|
58
|
+
~readModelName="X",
|
|
59
|
+
~id=Reventless.QueryEngine.String("a"),
|
|
60
|
+
)
|
|
61
|
+
expect(queried->Array.length)->toBe(0)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe("SideEffectEntryPoint_Ops.makeRegisteredHandler", () => {
|
|
66
|
+
testSync("threads comp/plugin onto the registered handler", () => {
|
|
67
|
+
let entry: SideEffectEntryPoint_Ops.handlerEntry = {
|
|
68
|
+
sourceUrn: "arn:stream-1",
|
|
69
|
+
sideEffectModules: [],
|
|
70
|
+
comp: Some("SideEffectHandler(Mailer)"),
|
|
71
|
+
plugin: Some("Ordering"),
|
|
72
|
+
}
|
|
73
|
+
let registered = SideEffectEntryPoint_Ops.makeRegisteredHandler(entry, _stream =>
|
|
74
|
+
Effect.succeed()
|
|
75
|
+
)
|
|
76
|
+
expect(registered.comp)->toEqual(Some("SideEffectHandler(Mailer)"))
|
|
77
|
+
expect(registered.plugin)->toEqual(Some("Ordering"))
|
|
78
|
+
})
|
|
79
|
+
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as SideEffectEntryPoint_Ops$ReventlessAws from "../src/adapter/Runtime/SideEffectEntryPoint_Ops.res.mjs";
|
|
6
|
+
|
|
7
|
+
function str(prim) {
|
|
8
|
+
return prim;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function obj(pairs) {
|
|
12
|
+
return Object.fromEntries(pairs);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
globalThis.describe("SideEffectEntryPoint_Ops.parseHandlerConfig", () => {
|
|
16
|
+
globalThis.test("empty raw config yields no handlers", () => {
|
|
17
|
+
globalThis.expect(SideEffectEntryPoint_Ops$ReventlessAws.parseHandlerConfig("").length).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
globalThis.test("decodes handlers incl. attribution fields", () => {
|
|
20
|
+
let config = JSON.stringify(Object.fromEntries([[
|
|
21
|
+
"handlers",
|
|
22
|
+
[
|
|
23
|
+
Object.fromEntries([
|
|
24
|
+
[
|
|
25
|
+
"sideEffectModules",
|
|
26
|
+
[
|
|
27
|
+
"@x/p/src/A.res.mjs",
|
|
28
|
+
"@x/p/src/B.res.mjs"
|
|
29
|
+
]
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
"sourceUrn",
|
|
33
|
+
"arn:stream-1"
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"comp",
|
|
37
|
+
"SideEffectHandler(Mailer)"
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"plugin",
|
|
41
|
+
"Ordering"
|
|
42
|
+
]
|
|
43
|
+
]),
|
|
44
|
+
Object.fromEntries([
|
|
45
|
+
[
|
|
46
|
+
"sideEffectModules",
|
|
47
|
+
["@x/p/src/C.res.mjs"]
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"sourceUrn",
|
|
51
|
+
"arn:stream-2"
|
|
52
|
+
]
|
|
53
|
+
])
|
|
54
|
+
]
|
|
55
|
+
]]));
|
|
56
|
+
let entries = SideEffectEntryPoint_Ops$ReventlessAws.parseHandlerConfig(config);
|
|
57
|
+
globalThis.expect(entries.length).toBe(2);
|
|
58
|
+
let first = entries[0];
|
|
59
|
+
globalThis.expect(first.sourceUrn).toBe("arn:stream-1");
|
|
60
|
+
globalThis.expect(first.sideEffectModules).toEqual([
|
|
61
|
+
"@x/p/src/A.res.mjs",
|
|
62
|
+
"@x/p/src/B.res.mjs"
|
|
63
|
+
]);
|
|
64
|
+
globalThis.expect(first.comp).toEqual("SideEffectHandler(Mailer)");
|
|
65
|
+
globalThis.expect(first.plugin).toEqual("Ordering");
|
|
66
|
+
let second = entries[1];
|
|
67
|
+
globalThis.expect(Stdlib_Option.isNone(second.comp)).toBe(true);
|
|
68
|
+
globalThis.expect(Stdlib_Option.isNone(second.plugin)).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
globalThis.describe("SideEffectEntryPoint_Ops.noopQueryEngine", () => {
|
|
73
|
+
globalThis.test("returns empty results for scan and query", async () => {
|
|
74
|
+
let scanned = await SideEffectEntryPoint_Ops$ReventlessAws.noopQueryEngine.scan("X", [], 10);
|
|
75
|
+
globalThis.expect(scanned.length).toBe(0);
|
|
76
|
+
let queried = await SideEffectEntryPoint_Ops$ReventlessAws.noopQueryEngine.query("X", undefined, {
|
|
77
|
+
TAG: "String",
|
|
78
|
+
_0: "a"
|
|
79
|
+
}, undefined, undefined, undefined, undefined);
|
|
80
|
+
globalThis.expect(queried.length).toBe(0);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
globalThis.describe("SideEffectEntryPoint_Ops.makeRegisteredHandler", () => {
|
|
85
|
+
globalThis.test("threads comp/plugin onto the registered handler", () => {
|
|
86
|
+
let entry_sideEffectModules = [];
|
|
87
|
+
let entry_comp = "SideEffectHandler(Mailer)";
|
|
88
|
+
let entry_plugin = "Ordering";
|
|
89
|
+
let entry = {
|
|
90
|
+
sourceUrn: "arn:stream-1",
|
|
91
|
+
sideEffectModules: entry_sideEffectModules,
|
|
92
|
+
comp: entry_comp,
|
|
93
|
+
plugin: entry_plugin
|
|
94
|
+
};
|
|
95
|
+
let registered = SideEffectEntryPoint_Ops$ReventlessAws.makeRegisteredHandler(entry, _stream => Effect.succeed());
|
|
96
|
+
globalThis.expect(registered.comp).toEqual("SideEffectHandler(Mailer)");
|
|
97
|
+
globalThis.expect(registered.plugin).toEqual("Ordering");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
str,
|
|
103
|
+
obj,
|
|
104
|
+
}
|
|
105
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Guards the typed cold-start core hoisted out of StateViewSliceEntryPoint.mjs:
|
|
2
|
+
// - parseHandlerConfig — the compact-v2 HANDLER_CONFIG expansion (shared
|
|
3
|
+
// base / sourceUrn / pgConnection, shortened s/p/q/u/t keys) and the legacy
|
|
4
|
+
// full-key pass-through; a drift here mis-routes or mis-backends every
|
|
5
|
+
// view slice of a plugin at cold start.
|
|
6
|
+
// - decodeEnvelope — the `{event, meta, recordedAt}` envelope rule incl. the
|
|
7
|
+
// bare-event fallback.
|
|
8
|
+
// - makeJsonEventsHandler — the projection stream pipeline: schema parse →
|
|
9
|
+
// project → handleAction, with decode failures logged and skipped instead
|
|
10
|
+
// of wedging the batch.
|
|
11
|
+
|
|
12
|
+
open JestGlobals
|
|
13
|
+
|
|
14
|
+
let str = JSON.Encode.string
|
|
15
|
+
let obj = pairs => JSON.Encode.object(Dict.fromArray(pairs))
|
|
16
|
+
|
|
17
|
+
describe("StateViewSliceEntryPoint_Ops.parseHandlerConfig", () => {
|
|
18
|
+
testSync("empty raw config yields no handlers", () => {
|
|
19
|
+
expect(StateViewSliceEntryPoint_Ops.parseHandlerConfig("")->Array.length)->toBe(0)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
testSync("expands compact-v2 entries against shared base/sourceUrn/pgConnection", () => {
|
|
23
|
+
let config = obj([
|
|
24
|
+
("v", JSON.Encode.int(2)),
|
|
25
|
+
("base", str("@x/plugin/src/StateViewSlice/")),
|
|
26
|
+
("sourceUrn", str("arn:shared")),
|
|
27
|
+
(
|
|
28
|
+
"pgConnection",
|
|
29
|
+
obj([
|
|
30
|
+
("host", str("db.local")),
|
|
31
|
+
("port", JSON.Encode.int(5432)),
|
|
32
|
+
("database", str("app")),
|
|
33
|
+
("username", str("master")),
|
|
34
|
+
("secretArn", str("arn:secret")),
|
|
35
|
+
]),
|
|
36
|
+
),
|
|
37
|
+
(
|
|
38
|
+
"handlers",
|
|
39
|
+
JSON.Encode.array([
|
|
40
|
+
obj([("s", str("Carts.res.mjs")), ("p", str("Carts_Projection.res.mjs")), ("q", str("qdb_Carts"))]),
|
|
41
|
+
obj([
|
|
42
|
+
("s", str("Totals.res.mjs")),
|
|
43
|
+
("p", str("Totals_Projection.res.mjs")),
|
|
44
|
+
("q", str("qdb_Totals")),
|
|
45
|
+
("u", str("arn:override")),
|
|
46
|
+
("t", str("cartTotals")),
|
|
47
|
+
]),
|
|
48
|
+
]),
|
|
49
|
+
),
|
|
50
|
+
])->JSON.stringify
|
|
51
|
+
let entries = StateViewSliceEntryPoint_Ops.parseHandlerConfig(config)
|
|
52
|
+
expect(entries->Array.length)->toBe(2)
|
|
53
|
+
let first = entries->Array.getUnsafe(0)
|
|
54
|
+
expect(first.specModule)->toBe("@x/plugin/src/StateViewSlice/Carts.res.mjs")
|
|
55
|
+
expect(first.projectionModule)->toBe("@x/plugin/src/StateViewSlice/Carts_Projection.res.mjs")
|
|
56
|
+
expect(first.queryDbTableName)->toBe("qdb_Carts")
|
|
57
|
+
expect(first.sourceUrn)->toBe("arn:shared")
|
|
58
|
+
expect(first.stateTopicName->Option.isNone)->toBe(true)
|
|
59
|
+
expect(first.pgConnection->Option.map(cc => cc.host))->toEqual(Some("db.local"))
|
|
60
|
+
let second = entries->Array.getUnsafe(1)
|
|
61
|
+
expect(second.sourceUrn)->toBe("arn:override")
|
|
62
|
+
expect(second.stateTopicName)->toEqual(Some("cartTotals"))
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
testSync("passes legacy full-key entries through with per-entry fields", () => {
|
|
66
|
+
let config = obj([
|
|
67
|
+
("base", str("ignored/")),
|
|
68
|
+
("sourceUrn", str("arn:shared")),
|
|
69
|
+
(
|
|
70
|
+
"handlers",
|
|
71
|
+
JSON.Encode.array([
|
|
72
|
+
obj([
|
|
73
|
+
("specModule", str("full/Carts.res.mjs")),
|
|
74
|
+
("projectionModule", str("full/Carts_Projection.res.mjs")),
|
|
75
|
+
("queryDbTableName", str("qdb_Carts")),
|
|
76
|
+
("sourceUrn", str("arn:own")),
|
|
77
|
+
]),
|
|
78
|
+
]),
|
|
79
|
+
),
|
|
80
|
+
])->JSON.stringify
|
|
81
|
+
let e = StateViewSliceEntryPoint_Ops.parseHandlerConfig(config)->Array.getUnsafe(0)
|
|
82
|
+
// Legacy entries are NOT base-prefixed and keep their own sourceUrn.
|
|
83
|
+
expect(e.specModule)->toBe("full/Carts.res.mjs")
|
|
84
|
+
expect(e.projectionModule)->toBe("full/Carts_Projection.res.mjs")
|
|
85
|
+
expect(e.sourceUrn)->toBe("arn:own")
|
|
86
|
+
expect(e.pgConnection->Option.isNone)->toBe(true)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe("StateViewSliceEntryPoint_Ops.decodeEnvelope", () => {
|
|
91
|
+
testSync("surfaces event/recordedAt from the envelope", () => {
|
|
92
|
+
let envelope = obj([
|
|
93
|
+
("id", str("evt-1")),
|
|
94
|
+
("event", str("payload")),
|
|
95
|
+
("meta", obj([("service", str("Carts"))])),
|
|
96
|
+
("recordedAt", str("2026-07-27T00:00:00Z")),
|
|
97
|
+
])
|
|
98
|
+
let decoded = StateViewSliceEntryPoint_Ops.decodeEnvelope(envelope)
|
|
99
|
+
expect(decoded.event)->toEqual(str("payload"))
|
|
100
|
+
expect(decoded.recordedAt)->toBe("2026-07-27T00:00:00Z")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
testSync("falls back to the whole json for bare / null-event records", () => {
|
|
104
|
+
let bare = obj([("kind", str("CartCreated"))])
|
|
105
|
+
expect(StateViewSliceEntryPoint_Ops.decodeEnvelope(bare).event)->toEqual(bare)
|
|
106
|
+
let nullEvent = obj([("event", JSON.Null), ("recordedAt", str("t"))])
|
|
107
|
+
expect(StateViewSliceEntryPoint_Ops.decodeEnvelope(nullEvent).event)->toEqual(nullEvent)
|
|
108
|
+
expect(StateViewSliceEntryPoint_Ops.decodeEnvelope(bare).recordedAt)->toBe("")
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe("StateViewSliceEntryPoint_Ops.makeJsonEventsHandler", () => {
|
|
113
|
+
// Capturing stub operation set — Set actions land as saves.
|
|
114
|
+
let makeStub = (saved: array<(string, JSON.t)>): ReventlessCore.QueryDb_Adapter.operations => {
|
|
115
|
+
load: async _ => Ok([]),
|
|
116
|
+
loadStream: _ => Stream.fromIterable([]),
|
|
117
|
+
save: async (id, state, _mode, _ttl) => {
|
|
118
|
+
saved->Array.push((id, state))
|
|
119
|
+
Ok()
|
|
120
|
+
},
|
|
121
|
+
saveBatch: async items => {
|
|
122
|
+
items->Array.forEach(((id, state, _ttl)) => saved->Array.push((id, state)))
|
|
123
|
+
Ok()
|
|
124
|
+
},
|
|
125
|
+
count: async (_, _, _) => Ok(0),
|
|
126
|
+
delete: async (_, _) => Ok(),
|
|
127
|
+
deleteBatch: async _ => Ok(),
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// String-typed consumed events keep the pipeline test independent of any
|
|
131
|
+
// real slice: each event value becomes the row id, `recordedAt` the state.
|
|
132
|
+
let project = (
|
|
133
|
+
{event, recordedAt, _}: Reventless.StateViewSlice.consumed<string>,
|
|
134
|
+
): array<Reventless.Projection.action<string, JSON.t>> => [Set(event, str(recordedAt))]
|
|
135
|
+
|
|
136
|
+
test("decodes envelopes, projects, and runs actions against the ops", async () => {
|
|
137
|
+
let saved = []
|
|
138
|
+
let handler = StateViewSliceEntryPoint_Ops.makeJsonEventsHandler(
|
|
139
|
+
~sliceName="TestSlice",
|
|
140
|
+
~eventSchema=S.string,
|
|
141
|
+
~project,
|
|
142
|
+
~queryDbOps=makeStub(saved),
|
|
143
|
+
~subIdConfig=None,
|
|
144
|
+
)
|
|
145
|
+
let envelope = obj([("event", str("row-1")), ("recordedAt", str("2026-07-27"))])
|
|
146
|
+
let bare = str("row-2")
|
|
147
|
+
await Stream.fromIterable([envelope, bare])->handler->Effect.runPromise
|
|
148
|
+
expect(saved)->toEqual([("row-1", str("2026-07-27")), ("row-2", str(""))])
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test("a decode failure yields no actions and does not wedge the batch", async () => {
|
|
152
|
+
let saved = []
|
|
153
|
+
let handler = StateViewSliceEntryPoint_Ops.makeJsonEventsHandler(
|
|
154
|
+
~sliceName="TestSlice",
|
|
155
|
+
~eventSchema=S.string,
|
|
156
|
+
~project,
|
|
157
|
+
~queryDbOps=makeStub(saved),
|
|
158
|
+
~subIdConfig=None,
|
|
159
|
+
)
|
|
160
|
+
// A number cannot parse as S.string → logged and skipped; the following
|
|
161
|
+
// record still projects.
|
|
162
|
+
await Stream.fromIterable([JSON.Encode.int(42), str("row-after")])
|
|
163
|
+
->handler
|
|
164
|
+
->Effect.runPromise
|
|
165
|
+
expect(saved)->toEqual([("row-after", str(""))])
|
|
166
|
+
})
|
|
167
|
+
})
|