@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.
Files changed (104) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/package.json +7 -7
  3. package/src/adapter/Api/AppSyncEventsSigner_Ops.res +137 -0
  4. package/src/adapter/Api/AppSyncEventsSigner_Ops.res.mjs +121 -0
  5. package/src/adapter/Api/Platform_AdminScan_Ops.res +136 -0
  6. package/src/adapter/Api/Platform_AdminScan_Ops.res.mjs +98 -0
  7. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +12 -90
  8. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +11 -84
  9. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +86 -0
  10. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +79 -0
  11. package/src/adapter/Api/Platform_UIFragments_Lambda.res +9 -79
  12. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +7 -143
  13. package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res +48 -0
  14. package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res.mjs +63 -0
  15. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +21 -80
  16. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +23 -68
  17. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res +70 -0
  18. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res.mjs +68 -0
  19. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -1
  20. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +1 -1
  21. package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
  22. package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -1
  23. package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +42 -126
  24. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +246 -0
  25. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +145 -0
  26. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +27 -2
  27. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +10 -3
  28. package/src/adapter/Runtime/CounterEntryPoint.mjs +19 -73
  29. package/src/adapter/Runtime/CounterEntryPoint_Ops.res +145 -0
  30. package/src/adapter/Runtime/CounterEntryPoint_Ops.res.mjs +129 -0
  31. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +16 -3
  32. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +11 -6
  33. package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +52 -510
  34. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +816 -0
  35. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +651 -0
  36. package/src/adapter/Runtime/EventMapperEntryPoint.mjs +23 -67
  37. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +126 -0
  38. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res.mjs +83 -0
  39. package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +16 -63
  40. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +150 -0
  41. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res.mjs +111 -0
  42. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res +96 -0
  43. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res.mjs +70 -0
  44. package/src/adapter/Runtime/PgMigrationEntryPoint.res +54 -0
  45. package/src/adapter/Runtime/PgMigrationEntryPoint.res.mjs +37 -0
  46. package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +20 -55
  47. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +155 -0
  48. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +94 -0
  49. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +80 -0
  50. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res.mjs +39 -0
  51. package/src/adapter/Runtime/QueryDbEntryPoint_Ops.res +4 -4
  52. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +34 -153
  53. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res +114 -0
  54. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res.mjs +83 -0
  55. package/src/adapter/Runtime/SideEffectEntryPoint.mjs +23 -64
  56. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res +71 -0
  57. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res.mjs +57 -0
  58. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +34 -165
  59. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +206 -0
  60. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res.mjs +105 -0
  61. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +156 -0
  62. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res.mjs +123 -0
  63. package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +18 -138
  64. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +198 -0
  65. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res.mjs +155 -0
  66. package/src/adapter/StateTopic/StateTopic_AppSync.res +12 -170
  67. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +7 -167
  68. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +208 -0
  69. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +183 -0
  70. package/src/components/AutomationSlice_Builder.res +2 -0
  71. package/src/components/AutomationSlice_Builder.res.mjs +1 -1
  72. package/src/components/OutboundTranslationSlice_Builder.res +1 -0
  73. package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
  74. package/tests/AppSyncEventsSigner_OpsTest.res +75 -0
  75. package/tests/AppSyncEventsSigner_OpsTest.res.mjs +44 -0
  76. package/tests/AppSyncEventsSigner_ParityFixture.mjs +36 -0
  77. package/tests/AutomationSliceEntryPoint_OpsTest.res +165 -0
  78. package/tests/AutomationSliceEntryPoint_OpsTest.res.mjs +280 -0
  79. package/tests/CounterEntryPoint_OpsTest.res +115 -0
  80. package/tests/CounterEntryPoint_OpsTest.res.mjs +150 -0
  81. package/tests/EventCollectorEntryPoint_OpsTest.res +216 -0
  82. package/tests/EventCollectorEntryPoint_OpsTest.res.mjs +339 -0
  83. package/tests/EventMapperEntryPoint_OpsTest.res +103 -0
  84. package/tests/EventMapperEntryPoint_OpsTest.res.mjs +120 -0
  85. package/tests/ExtensionPointEntryPoint_OpsTest.res +57 -0
  86. package/tests/ExtensionPointEntryPoint_OpsTest.res.mjs +54 -0
  87. package/tests/PgMigrationEntryPoint_IntegrationTest.res +22 -31
  88. package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +20 -23
  89. package/tests/PgQueryResolverEntryPoint_OpsTest.res +38 -0
  90. package/tests/PgQueryResolverEntryPoint_OpsTest.res.mjs +31 -0
  91. package/tests/Platform_AdminScan_OpsTest.res +80 -0
  92. package/tests/Platform_AdminScan_OpsTest.res.mjs +127 -0
  93. package/tests/ReadModelEntryPoint_OpsTest.res +141 -0
  94. package/tests/ReadModelEntryPoint_OpsTest.res.mjs +267 -0
  95. package/tests/SideEffectEntryPoint_OpsTest.res +79 -0
  96. package/tests/SideEffectEntryPoint_OpsTest.res.mjs +105 -0
  97. package/tests/StateViewSliceEntryPoint_OpsTest.res +167 -0
  98. package/tests/StateViewSliceEntryPoint_OpsTest.res.mjs +296 -0
  99. package/tests/TaskBucketEntryPoint_OpsTest.res +117 -0
  100. package/tests/TaskBucketEntryPoint_OpsTest.res.mjs +165 -0
  101. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res +42 -13
  102. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res.mjs +29 -6
  103. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +0 -57
  104. 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`), run against DynamoDB Local.
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 *real* `.mjs` entry point that
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 point: builds the handler via its exported
52
- // `buildJsonEventsHandler` factory (DynamoDB path — `pgConnection`/`stateTopicName`
53
- // undefined), feeds one encoded event through the effect Stream, and runs the
54
- // resulting Effect through the request context the production handler supplies.
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 { buildJsonEventsHandler } = await import(
58
- "@reventlessdev/reventless-aws/src/adapter/Runtime/StateViewSliceEntryPoint.mjs"
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 handler = buildJsonEventsHandler(specModule, projectionModule, tableName, undefined, undefined);
69
- const stream = Stream.fromIterable([eventJson]);
70
- const effect = handler(stream)
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
  }