@reventlessdev/reventless-aws 3.0.0-alpha.232 → 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 +16 -0
  2. package/package.json +8 -8
  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 +11 -1
  32. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +9 -3
  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,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
+ })