@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,296 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Stream from "effect/Stream";
|
|
7
|
+
import * as StateViewSliceEntryPoint_Ops$ReventlessAws from "../src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res.mjs";
|
|
8
|
+
|
|
9
|
+
function str(prim) {
|
|
10
|
+
return prim;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function obj(pairs) {
|
|
14
|
+
return Object.fromEntries(pairs);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
globalThis.describe("StateViewSliceEntryPoint_Ops.parseHandlerConfig", () => {
|
|
18
|
+
globalThis.test("empty raw config yields no handlers", () => {
|
|
19
|
+
globalThis.expect(StateViewSliceEntryPoint_Ops$ReventlessAws.parseHandlerConfig("").length).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
globalThis.test("expands compact-v2 entries against shared base/sourceUrn/pgConnection", () => {
|
|
22
|
+
let config = JSON.stringify(Object.fromEntries([
|
|
23
|
+
[
|
|
24
|
+
"v",
|
|
25
|
+
2
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"base",
|
|
29
|
+
"@x/plugin/src/StateViewSlice/"
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
"sourceUrn",
|
|
33
|
+
"arn:shared"
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"pgConnection",
|
|
37
|
+
Object.fromEntries([
|
|
38
|
+
[
|
|
39
|
+
"host",
|
|
40
|
+
"db.local"
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
"port",
|
|
44
|
+
5432
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
"database",
|
|
48
|
+
"app"
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
"username",
|
|
52
|
+
"master"
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
"secretArn",
|
|
56
|
+
"arn:secret"
|
|
57
|
+
]
|
|
58
|
+
])
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"handlers",
|
|
62
|
+
[
|
|
63
|
+
Object.fromEntries([
|
|
64
|
+
[
|
|
65
|
+
"s",
|
|
66
|
+
"Carts.res.mjs"
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
"p",
|
|
70
|
+
"Carts_Projection.res.mjs"
|
|
71
|
+
],
|
|
72
|
+
[
|
|
73
|
+
"q",
|
|
74
|
+
"qdb_Carts"
|
|
75
|
+
]
|
|
76
|
+
]),
|
|
77
|
+
Object.fromEntries([
|
|
78
|
+
[
|
|
79
|
+
"s",
|
|
80
|
+
"Totals.res.mjs"
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
"p",
|
|
84
|
+
"Totals_Projection.res.mjs"
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
"q",
|
|
88
|
+
"qdb_Totals"
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
"u",
|
|
92
|
+
"arn:override"
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
"t",
|
|
96
|
+
"cartTotals"
|
|
97
|
+
]
|
|
98
|
+
])
|
|
99
|
+
]
|
|
100
|
+
]
|
|
101
|
+
]));
|
|
102
|
+
let entries = StateViewSliceEntryPoint_Ops$ReventlessAws.parseHandlerConfig(config);
|
|
103
|
+
globalThis.expect(entries.length).toBe(2);
|
|
104
|
+
let first = entries[0];
|
|
105
|
+
globalThis.expect(first.specModule).toBe("@x/plugin/src/StateViewSlice/Carts.res.mjs");
|
|
106
|
+
globalThis.expect(first.projectionModule).toBe("@x/plugin/src/StateViewSlice/Carts_Projection.res.mjs");
|
|
107
|
+
globalThis.expect(first.queryDbTableName).toBe("qdb_Carts");
|
|
108
|
+
globalThis.expect(first.sourceUrn).toBe("arn:shared");
|
|
109
|
+
globalThis.expect(Stdlib_Option.isNone(first.stateTopicName)).toBe(true);
|
|
110
|
+
globalThis.expect(Stdlib_Option.map(first.pgConnection, cc => cc.host)).toEqual("db.local");
|
|
111
|
+
let second = entries[1];
|
|
112
|
+
globalThis.expect(second.sourceUrn).toBe("arn:override");
|
|
113
|
+
globalThis.expect(second.stateTopicName).toEqual("cartTotals");
|
|
114
|
+
});
|
|
115
|
+
globalThis.test("passes legacy full-key entries through with per-entry fields", () => {
|
|
116
|
+
let config = JSON.stringify(Object.fromEntries([
|
|
117
|
+
[
|
|
118
|
+
"base",
|
|
119
|
+
"ignored/"
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
"sourceUrn",
|
|
123
|
+
"arn:shared"
|
|
124
|
+
],
|
|
125
|
+
[
|
|
126
|
+
"handlers",
|
|
127
|
+
[Object.fromEntries([
|
|
128
|
+
[
|
|
129
|
+
"specModule",
|
|
130
|
+
"full/Carts.res.mjs"
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
"projectionModule",
|
|
134
|
+
"full/Carts_Projection.res.mjs"
|
|
135
|
+
],
|
|
136
|
+
[
|
|
137
|
+
"queryDbTableName",
|
|
138
|
+
"qdb_Carts"
|
|
139
|
+
],
|
|
140
|
+
[
|
|
141
|
+
"sourceUrn",
|
|
142
|
+
"arn:own"
|
|
143
|
+
]
|
|
144
|
+
])]
|
|
145
|
+
]
|
|
146
|
+
]));
|
|
147
|
+
let e = StateViewSliceEntryPoint_Ops$ReventlessAws.parseHandlerConfig(config)[0];
|
|
148
|
+
globalThis.expect(e.specModule).toBe("full/Carts.res.mjs");
|
|
149
|
+
globalThis.expect(e.projectionModule).toBe("full/Carts_Projection.res.mjs");
|
|
150
|
+
globalThis.expect(e.sourceUrn).toBe("arn:own");
|
|
151
|
+
globalThis.expect(Stdlib_Option.isNone(e.pgConnection)).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
globalThis.describe("StateViewSliceEntryPoint_Ops.decodeEnvelope", () => {
|
|
156
|
+
globalThis.test("surfaces event/recordedAt from the envelope", () => {
|
|
157
|
+
let envelope = Object.fromEntries([
|
|
158
|
+
[
|
|
159
|
+
"id",
|
|
160
|
+
"evt-1"
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
"event",
|
|
164
|
+
"payload"
|
|
165
|
+
],
|
|
166
|
+
[
|
|
167
|
+
"meta",
|
|
168
|
+
Object.fromEntries([[
|
|
169
|
+
"service",
|
|
170
|
+
"Carts"
|
|
171
|
+
]])
|
|
172
|
+
],
|
|
173
|
+
[
|
|
174
|
+
"recordedAt",
|
|
175
|
+
"2026-07-27T00:00:00Z"
|
|
176
|
+
]
|
|
177
|
+
]);
|
|
178
|
+
let decoded = StateViewSliceEntryPoint_Ops$ReventlessAws.decodeEnvelope(envelope);
|
|
179
|
+
globalThis.expect(decoded.event).toEqual("payload");
|
|
180
|
+
globalThis.expect(decoded.recordedAt).toBe("2026-07-27T00:00:00Z");
|
|
181
|
+
});
|
|
182
|
+
globalThis.test("falls back to the whole json for bare / null-event records", () => {
|
|
183
|
+
let bare = Object.fromEntries([[
|
|
184
|
+
"kind",
|
|
185
|
+
"CartCreated"
|
|
186
|
+
]]);
|
|
187
|
+
globalThis.expect(StateViewSliceEntryPoint_Ops$ReventlessAws.decodeEnvelope(bare).event).toEqual(bare);
|
|
188
|
+
let nullEvent = Object.fromEntries([
|
|
189
|
+
[
|
|
190
|
+
"event",
|
|
191
|
+
null
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
"recordedAt",
|
|
195
|
+
"t"
|
|
196
|
+
]
|
|
197
|
+
]);
|
|
198
|
+
globalThis.expect(StateViewSliceEntryPoint_Ops$ReventlessAws.decodeEnvelope(nullEvent).event).toEqual(nullEvent);
|
|
199
|
+
globalThis.expect(StateViewSliceEntryPoint_Ops$ReventlessAws.decodeEnvelope(bare).recordedAt).toBe("");
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
globalThis.describe("StateViewSliceEntryPoint_Ops.makeJsonEventsHandler", () => {
|
|
204
|
+
let makeStub = saved => ({
|
|
205
|
+
load: async param => ({
|
|
206
|
+
TAG: "Ok",
|
|
207
|
+
_0: []
|
|
208
|
+
}),
|
|
209
|
+
loadStream: param => Stream.fromIterable([]),
|
|
210
|
+
save: async (id, state, _mode, _ttl) => {
|
|
211
|
+
saved.push([
|
|
212
|
+
id,
|
|
213
|
+
state
|
|
214
|
+
]);
|
|
215
|
+
return {
|
|
216
|
+
TAG: "Ok",
|
|
217
|
+
_0: undefined
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
saveBatch: async items => {
|
|
221
|
+
items.forEach(param => {
|
|
222
|
+
saved.push([
|
|
223
|
+
param[0],
|
|
224
|
+
param[1]
|
|
225
|
+
]);
|
|
226
|
+
});
|
|
227
|
+
return {
|
|
228
|
+
TAG: "Ok",
|
|
229
|
+
_0: undefined
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
count: async (param, param$1, param$2) => ({
|
|
233
|
+
TAG: "Ok",
|
|
234
|
+
_0: 0
|
|
235
|
+
}),
|
|
236
|
+
delete: async (param, param$1) => ({
|
|
237
|
+
TAG: "Ok",
|
|
238
|
+
_0: undefined
|
|
239
|
+
}),
|
|
240
|
+
deleteBatch: async param => ({
|
|
241
|
+
TAG: "Ok",
|
|
242
|
+
_0: undefined
|
|
243
|
+
})
|
|
244
|
+
});
|
|
245
|
+
let project = param => [{
|
|
246
|
+
TAG: "Set",
|
|
247
|
+
_0: param.event,
|
|
248
|
+
_1: param.recordedAt
|
|
249
|
+
}];
|
|
250
|
+
globalThis.test("decodes envelopes, projects, and runs actions against the ops", async () => {
|
|
251
|
+
let saved = [];
|
|
252
|
+
let handler = StateViewSliceEntryPoint_Ops$ReventlessAws.makeJsonEventsHandler("TestSlice", S.string, project, makeStub(saved), undefined);
|
|
253
|
+
let envelope = Object.fromEntries([
|
|
254
|
+
[
|
|
255
|
+
"event",
|
|
256
|
+
"row-1"
|
|
257
|
+
],
|
|
258
|
+
[
|
|
259
|
+
"recordedAt",
|
|
260
|
+
"2026-07-27"
|
|
261
|
+
]
|
|
262
|
+
]);
|
|
263
|
+
await Effect.runPromise(handler(Stream.fromIterable([
|
|
264
|
+
envelope,
|
|
265
|
+
"row-2"
|
|
266
|
+
])));
|
|
267
|
+
globalThis.expect(saved).toEqual([
|
|
268
|
+
[
|
|
269
|
+
"row-1",
|
|
270
|
+
"2026-07-27"
|
|
271
|
+
],
|
|
272
|
+
[
|
|
273
|
+
"row-2",
|
|
274
|
+
""
|
|
275
|
+
]
|
|
276
|
+
]);
|
|
277
|
+
});
|
|
278
|
+
globalThis.test("a decode failure yields no actions and does not wedge the batch", async () => {
|
|
279
|
+
let saved = [];
|
|
280
|
+
let handler = StateViewSliceEntryPoint_Ops$ReventlessAws.makeJsonEventsHandler("TestSlice", S.string, project, makeStub(saved), undefined);
|
|
281
|
+
await Effect.runPromise(handler(Stream.fromIterable([
|
|
282
|
+
42,
|
|
283
|
+
"row-after"
|
|
284
|
+
])));
|
|
285
|
+
globalThis.expect(saved).toEqual([[
|
|
286
|
+
"row-after",
|
|
287
|
+
""
|
|
288
|
+
]]);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
export {
|
|
293
|
+
str,
|
|
294
|
+
obj,
|
|
295
|
+
}
|
|
296
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Guards the typed cold-start core hoisted out of TaskBucketEntryPoint.mjs:
|
|
2
|
+
// - parseHandlerConfig — the callbackModule / publishToAggregates /
|
|
3
|
+
// scheduler env-indirection contract.
|
|
4
|
+
// - buildPublishCommands — env-var queue resolution incl. the skip on
|
|
5
|
+
// unset/empty URLs.
|
|
6
|
+
// - dispatchTaskActions — the exhaustive task-action dispatch (publish
|
|
7
|
+
// routing, schedule create/delete, and the no-scheduler skip paths).
|
|
8
|
+
|
|
9
|
+
open JestGlobals
|
|
10
|
+
|
|
11
|
+
let str = JSON.Encode.string
|
|
12
|
+
let obj = pairs => JSON.Encode.object(Dict.fromArray(pairs))
|
|
13
|
+
|
|
14
|
+
@val @scope("process") external processEnv: dict<string> = "env"
|
|
15
|
+
|
|
16
|
+
describe("TaskBucketEntryPoint_Ops.parseHandlerConfig", () => {
|
|
17
|
+
testSync("empty raw config decodes to empty defaults", () => {
|
|
18
|
+
let c = TaskBucketEntryPoint_Ops.parseHandlerConfig("")
|
|
19
|
+
expect(c.callbackModule)->toBe("")
|
|
20
|
+
expect(c.publishToAggregates->Dict.toArray->Array.length)->toBe(0)
|
|
21
|
+
expect(c.scheduler->Option.isNone)->toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
testSync("decodes callbackModule, publishToAggregates, and scheduler env names", () => {
|
|
25
|
+
let config = obj([
|
|
26
|
+
("callbackModule", str("@x/plugin/src/Task/ImportCatalog.res.mjs")),
|
|
27
|
+
("publishToAggregates", obj([("Product", str("PTA_Product_QUEUE_URL"))])),
|
|
28
|
+
(
|
|
29
|
+
"scheduler",
|
|
30
|
+
obj([
|
|
31
|
+
("roleArnEnv", str("SCHEDULER_ROLE_ARN")),
|
|
32
|
+
("targetArnEnv", str("SCHEDULER_TARGET_ARN")),
|
|
33
|
+
("targetNameEnv", str("SCHEDULER_TARGET_NAME")),
|
|
34
|
+
]),
|
|
35
|
+
),
|
|
36
|
+
])->JSON.stringify
|
|
37
|
+
let c = TaskBucketEntryPoint_Ops.parseHandlerConfig(config)
|
|
38
|
+
expect(c.callbackModule)->toBe("@x/plugin/src/Task/ImportCatalog.res.mjs")
|
|
39
|
+
expect(c.publishToAggregates->Dict.get("Product"))->toEqual(Some("PTA_Product_QUEUE_URL"))
|
|
40
|
+
switch c.scheduler {
|
|
41
|
+
| Some(s) => expect(s.roleArnEnv)->toBe("SCHEDULER_ROLE_ARN")
|
|
42
|
+
| None => JsError.throwWithMessage("expected Some(scheduler)")
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe("TaskBucketEntryPoint_Ops.buildPublishCommands", () => {
|
|
48
|
+
testSync("resolves queue URLs via env vars, skipping unset or empty ones", () => {
|
|
49
|
+
processEnv->Dict.set("TB_TEST_QUEUE_A", "https://sqs/a.fifo")
|
|
50
|
+
processEnv->Dict.set("TB_TEST_QUEUE_EMPTY", "")
|
|
51
|
+
let map = Dict.fromArray([
|
|
52
|
+
("AggA", "TB_TEST_QUEUE_A"),
|
|
53
|
+
("AggEmpty", "TB_TEST_QUEUE_EMPTY"),
|
|
54
|
+
("AggUnset", "TB_TEST_QUEUE_UNSET"),
|
|
55
|
+
])
|
|
56
|
+
let publish = TaskBucketEntryPoint_Ops.buildPublishCommands(map)
|
|
57
|
+
expect(publish->Dict.get("AggA")->Option.isSome)->toBe(true)
|
|
58
|
+
expect(publish->Dict.get("AggEmpty")->Option.isNone)->toBe(true)
|
|
59
|
+
expect(publish->Dict.get("AggUnset")->Option.isNone)->toBe(true)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe("TaskBucketEntryPoint_Ops.dispatchTaskActions", () => {
|
|
64
|
+
let schedule: Reventless.Schedule.schedule = {
|
|
65
|
+
name: "sweep",
|
|
66
|
+
rate: Reventless.Schedule.Minutes(5),
|
|
67
|
+
payload: "{}",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
test("routes PublishCommands to the aggregate's publish fn", async () => {
|
|
71
|
+
let published = []
|
|
72
|
+
let publishCommands = Dict.fromArray([
|
|
73
|
+
(
|
|
74
|
+
"Product",
|
|
75
|
+
(
|
|
76
|
+
async cmds => {
|
|
77
|
+
published->Array.push(cmds)
|
|
78
|
+
}: ReventlessCore.CommandTopic.publishJsons
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
])
|
|
82
|
+
await TaskBucketEntryPoint_Ops.dispatchTaskActions(
|
|
83
|
+
~actions=[
|
|
84
|
+
Reventless.Task.PublishCommands("Product", []),
|
|
85
|
+
// Unknown aggregate — logged, not thrown.
|
|
86
|
+
Reventless.Task.PublishCommands("Nope", []),
|
|
87
|
+
],
|
|
88
|
+
~publishCommands,
|
|
89
|
+
~schedulerOps=() => None,
|
|
90
|
+
)
|
|
91
|
+
expect(published->Array.length)->toBe(1)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test("routes schedule actions to the scheduler ops; skips when unconfigured", async () => {
|
|
95
|
+
let created = []
|
|
96
|
+
let deleted = []
|
|
97
|
+
let ops: TaskBucketEntryPoint_Ops.schedulerOps = {
|
|
98
|
+
createSchedule: async s => created->Array.push(s.name),
|
|
99
|
+
deleteSchedule: async name => deleted->Array.push(name),
|
|
100
|
+
}
|
|
101
|
+
await TaskBucketEntryPoint_Ops.dispatchTaskActions(
|
|
102
|
+
~actions=[Reventless.Task.CreateSchedule(schedule), Reventless.Task.DeleteSchedule("sweep")],
|
|
103
|
+
~publishCommands=Dict.make(),
|
|
104
|
+
~schedulerOps=() => Some(ops),
|
|
105
|
+
)
|
|
106
|
+
expect(created)->toEqual(["sweep"])
|
|
107
|
+
expect(deleted)->toEqual(["sweep"])
|
|
108
|
+
|
|
109
|
+
// No scheduler configured — both actions skip without throwing.
|
|
110
|
+
await TaskBucketEntryPoint_Ops.dispatchTaskActions(
|
|
111
|
+
~actions=[Reventless.Task.CreateSchedule(schedule), Reventless.Task.DeleteSchedule("sweep")],
|
|
112
|
+
~publishCommands=Dict.make(),
|
|
113
|
+
~schedulerOps=() => None,
|
|
114
|
+
)
|
|
115
|
+
expect(created->Array.length)->toBe(1)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
@@ -0,0 +1,165 @@
|
|
|
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 Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
5
|
+
import * as TaskBucketEntryPoint_Ops$ReventlessAws from "../src/adapter/Runtime/TaskBucketEntryPoint_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("TaskBucketEntryPoint_Ops.parseHandlerConfig", () => {
|
|
16
|
+
globalThis.test("empty raw config decodes to empty defaults", () => {
|
|
17
|
+
let c = TaskBucketEntryPoint_Ops$ReventlessAws.parseHandlerConfig("");
|
|
18
|
+
globalThis.expect(c.callbackModule).toBe("");
|
|
19
|
+
globalThis.expect(Object.entries(c.publishToAggregates).length).toBe(0);
|
|
20
|
+
globalThis.expect(Stdlib_Option.isNone(c.scheduler)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
globalThis.test("decodes callbackModule, publishToAggregates, and scheduler env names", () => {
|
|
23
|
+
let config = JSON.stringify(Object.fromEntries([
|
|
24
|
+
[
|
|
25
|
+
"callbackModule",
|
|
26
|
+
"@x/plugin/src/Task/ImportCatalog.res.mjs"
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
"publishToAggregates",
|
|
30
|
+
Object.fromEntries([[
|
|
31
|
+
"Product",
|
|
32
|
+
"PTA_Product_QUEUE_URL"
|
|
33
|
+
]])
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"scheduler",
|
|
37
|
+
Object.fromEntries([
|
|
38
|
+
[
|
|
39
|
+
"roleArnEnv",
|
|
40
|
+
"SCHEDULER_ROLE_ARN"
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
"targetArnEnv",
|
|
44
|
+
"SCHEDULER_TARGET_ARN"
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
"targetNameEnv",
|
|
48
|
+
"SCHEDULER_TARGET_NAME"
|
|
49
|
+
]
|
|
50
|
+
])
|
|
51
|
+
]
|
|
52
|
+
]));
|
|
53
|
+
let c = TaskBucketEntryPoint_Ops$ReventlessAws.parseHandlerConfig(config);
|
|
54
|
+
globalThis.expect(c.callbackModule).toBe("@x/plugin/src/Task/ImportCatalog.res.mjs");
|
|
55
|
+
globalThis.expect(c.publishToAggregates["Product"]).toEqual("PTA_Product_QUEUE_URL");
|
|
56
|
+
let s = c.scheduler;
|
|
57
|
+
if (s !== undefined) {
|
|
58
|
+
globalThis.expect(s.roleArnEnv).toBe("SCHEDULER_ROLE_ARN");
|
|
59
|
+
return;
|
|
60
|
+
} else {
|
|
61
|
+
return Stdlib_JsError.throwWithMessage("expected Some(scheduler)");
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
globalThis.describe("TaskBucketEntryPoint_Ops.buildPublishCommands", () => {
|
|
67
|
+
globalThis.test("resolves queue URLs via env vars, skipping unset or empty ones", () => {
|
|
68
|
+
process.env["TB_TEST_QUEUE_A"] = "https://sqs/a.fifo";
|
|
69
|
+
process.env["TB_TEST_QUEUE_EMPTY"] = "";
|
|
70
|
+
let map = Object.fromEntries([
|
|
71
|
+
[
|
|
72
|
+
"AggA",
|
|
73
|
+
"TB_TEST_QUEUE_A"
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
"AggEmpty",
|
|
77
|
+
"TB_TEST_QUEUE_EMPTY"
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
"AggUnset",
|
|
81
|
+
"TB_TEST_QUEUE_UNSET"
|
|
82
|
+
]
|
|
83
|
+
]);
|
|
84
|
+
let publish = TaskBucketEntryPoint_Ops$ReventlessAws.buildPublishCommands(map);
|
|
85
|
+
globalThis.expect(Stdlib_Option.isSome(publish["AggA"])).toBe(true);
|
|
86
|
+
globalThis.expect(Stdlib_Option.isNone(publish["AggEmpty"])).toBe(true);
|
|
87
|
+
globalThis.expect(Stdlib_Option.isNone(publish["AggUnset"])).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
globalThis.describe("TaskBucketEntryPoint_Ops.dispatchTaskActions", () => {
|
|
92
|
+
let schedule = {
|
|
93
|
+
name: "sweep",
|
|
94
|
+
rate: {
|
|
95
|
+
TAG: "Minutes",
|
|
96
|
+
_0: 5
|
|
97
|
+
},
|
|
98
|
+
payload: "{}"
|
|
99
|
+
};
|
|
100
|
+
globalThis.test("routes PublishCommands to the aggregate's publish fn", async () => {
|
|
101
|
+
let published = [];
|
|
102
|
+
let publishCommands = Object.fromEntries([[
|
|
103
|
+
"Product",
|
|
104
|
+
async cmds => {
|
|
105
|
+
published.push(cmds);
|
|
106
|
+
}
|
|
107
|
+
]]);
|
|
108
|
+
await TaskBucketEntryPoint_Ops$ReventlessAws.dispatchTaskActions([
|
|
109
|
+
{
|
|
110
|
+
TAG: "PublishCommands",
|
|
111
|
+
_0: "Product",
|
|
112
|
+
_1: []
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
TAG: "PublishCommands",
|
|
116
|
+
_0: "Nope",
|
|
117
|
+
_1: []
|
|
118
|
+
}
|
|
119
|
+
], publishCommands, () => {});
|
|
120
|
+
globalThis.expect(published.length).toBe(1);
|
|
121
|
+
});
|
|
122
|
+
globalThis.test("routes schedule actions to the scheduler ops; skips when unconfigured", async () => {
|
|
123
|
+
let created = [];
|
|
124
|
+
let deleted = [];
|
|
125
|
+
let ops_createSchedule = async s => {
|
|
126
|
+
created.push(s.name);
|
|
127
|
+
};
|
|
128
|
+
let ops_deleteSchedule = async name => {
|
|
129
|
+
deleted.push(name);
|
|
130
|
+
};
|
|
131
|
+
let ops = {
|
|
132
|
+
createSchedule: ops_createSchedule,
|
|
133
|
+
deleteSchedule: ops_deleteSchedule
|
|
134
|
+
};
|
|
135
|
+
await TaskBucketEntryPoint_Ops$ReventlessAws.dispatchTaskActions([
|
|
136
|
+
{
|
|
137
|
+
TAG: "CreateSchedule",
|
|
138
|
+
_0: schedule
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
TAG: "DeleteSchedule",
|
|
142
|
+
_0: "sweep"
|
|
143
|
+
}
|
|
144
|
+
], {}, () => ops);
|
|
145
|
+
globalThis.expect(created).toEqual(["sweep"]);
|
|
146
|
+
globalThis.expect(deleted).toEqual(["sweep"]);
|
|
147
|
+
await TaskBucketEntryPoint_Ops$ReventlessAws.dispatchTaskActions([
|
|
148
|
+
{
|
|
149
|
+
TAG: "CreateSchedule",
|
|
150
|
+
_0: schedule
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
TAG: "DeleteSchedule",
|
|
154
|
+
_0: "sweep"
|
|
155
|
+
}
|
|
156
|
+
], {}, () => {});
|
|
157
|
+
globalThis.expect(created.length).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
export {
|
|
162
|
+
str,
|
|
163
|
+
obj,
|
|
164
|
+
}
|
|
165
|
+
/* Not a pure module */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Integration regression guard for the StateViewSlice projection Lambda entry
|
|
2
|
-
// point (`StateViewSliceEntryPoint.mjs`
|
|
2
|
+
// point (`StateViewSliceEntryPoint.mjs` + `StateViewSliceEntryPoint_Ops.res`),
|
|
3
|
+
// run against DynamoDB Local.
|
|
3
4
|
//
|
|
4
5
|
// Covers the 2026-07-09 incident: the deployed runtime built its projection
|
|
5
6
|
// loop with `handleAction(action, queryDbOps, undefined)` — hardcoding
|
|
@@ -8,8 +9,10 @@
|
|
|
8
9
|
// then hit the `MissingSubIdConfig` guard and silently wrote nothing, while the
|
|
9
10
|
// in-memory GWT/callback path (which threads `subIdConfig`) stayed green. This
|
|
10
11
|
// is a deployed-runtime vs test-harness parity gap: a unit test on the callback
|
|
11
|
-
// path cannot catch it, so this test drives the
|
|
12
|
-
// production uses
|
|
12
|
+
// path cannot catch it, so this test drives the real entry-point wiring the
|
|
13
|
+
// production shell uses — `makeRegisteredHandler` with the modules record read
|
|
14
|
+
// off the dynamically imported spec/projection modules, invoked with an
|
|
15
|
+
// SQS-shaped Lambda record so the full decode path runs.
|
|
13
16
|
//
|
|
14
17
|
// The fixture `SvsTestSlice` is an `@subId productId` view slice whose only
|
|
15
18
|
// projection path is `UpdateMultiState`. Before the fix both appends no-op and
|
|
@@ -48,26 +51,52 @@ let createViewTable = async (tableName): Util_DynamoDb_Runtime.resolvedTable =>
|
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
// Drives the real entry
|
|
52
|
-
//
|
|
53
|
-
// undefined)
|
|
54
|
-
//
|
|
54
|
+
// Drives the real entry-point wiring: mirrors the shell's untyped seam line for
|
|
55
|
+
// line — a HANDLER_CONFIG-shaped entry (DynamoDB path — `pgConnection`/
|
|
56
|
+
// `stateTopicName` undefined) plus the modules record read off the dynamically
|
|
57
|
+
// imported spec/projection modules — then feeds one SQS-shaped Lambda record
|
|
58
|
+
// through the registered stream handler, so the same decode path the deployed
|
|
59
|
+
// Lambda runs (handleStreamEvent → envelope decode → schema parse → project →
|
|
60
|
+
// handleAction) is exercised end to end.
|
|
55
61
|
let runOneEvent: (string, JSON.t) => promise<unit> = %raw(`
|
|
56
62
|
async (tableName, eventJson) => {
|
|
57
|
-
const
|
|
58
|
-
"@reventlessdev/reventless-aws/src/adapter/Runtime/
|
|
63
|
+
const Ops = await import(
|
|
64
|
+
"@reventlessdev/reventless-aws/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res.mjs"
|
|
59
65
|
);
|
|
60
66
|
const specModule = await import("./SvsTestSlice.res.mjs");
|
|
61
67
|
const projectionModule = await import("./SvsTestSlice_Projection.res.mjs");
|
|
62
68
|
const Effect = await import("effect/Effect");
|
|
63
|
-
const Stream = await import("effect/Stream");
|
|
64
69
|
const { tag: requestContextTag } = await import(
|
|
65
70
|
"@reventlessdev/reventless-core/src/RequestContext.res.mjs"
|
|
66
71
|
);
|
|
67
72
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
const registered = Ops.makeRegisteredHandler(
|
|
74
|
+
{
|
|
75
|
+
specModule: "",
|
|
76
|
+
projectionModule: "",
|
|
77
|
+
queryDbTableName: tableName,
|
|
78
|
+
sourceUrn: "svs-test-urn",
|
|
79
|
+
stateTopicName: undefined,
|
|
80
|
+
pgConnection: undefined,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: specModule.name,
|
|
84
|
+
consumedEventSchema: specModule.consumedEventSchema,
|
|
85
|
+
project: projectionModule.project,
|
|
86
|
+
config: specModule.config,
|
|
87
|
+
subIdConfig: specModule.subIdConfig,
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
const event = {
|
|
91
|
+
Records: [
|
|
92
|
+
{
|
|
93
|
+
eventSource: "aws:sqs",
|
|
94
|
+
eventSourceARN: "svs-test-urn",
|
|
95
|
+
body: JSON.stringify(eventJson),
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
const effect = registered.handler(event, {})
|
|
71
100
|
.pipe(Effect.provideService(requestContextTag, { correlationId: "svs-test" }));
|
|
72
101
|
await Effect.runPromise(effect);
|
|
73
102
|
}
|