@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,75 @@
1
+ // Byte-parity guard for the AppSync Events SigV4 signer port.
2
+ //
3
+ // AppSyncEventsSigner_Ops.signedHeaders is a ReScript port of the hand-rolled
4
+ // SigV4 the two stream-relay Lambdas used to carry as inline JS. A one-character
5
+ // drift in the canonical request or the signing-key chain silently produces a
6
+ // 403 from AppSync and stops live event delivery — invisible until a deploy.
7
+ // This asserts the port is byte-identical to the preserved original
8
+ // (AppSyncEventsSigner_ParityFixture.mjs) across a range of inputs.
9
+
10
+ open JestGlobals
11
+
12
+ type jsCreds = {accessKeyId: string, secretAccessKey: string, sessionToken?: string}
13
+ @module("./AppSyncEventsSigner_ParityFixture.mjs")
14
+ external signedHeadersJs: (
15
+ ~host: string,
16
+ ~path: string,
17
+ ~body: string,
18
+ ~region: string,
19
+ ~isoNow: string,
20
+ ~creds: jsCreds,
21
+ ) => dict<string> = "signedHeadersJs"
22
+
23
+ let get = (d, k) => d->Dict.get(k)->Option.getOr("<missing>")
24
+
25
+ // Compare the ReScript signer to the JS reference for one input tuple.
26
+ let assertParity = (~host, ~path, ~body, ~region, ~isoNow, ~accessKeyId, ~secretAccessKey, ~sessionToken) => {
27
+ let creds: AppSyncEventsSigner_Ops.creds = {accessKeyId, secretAccessKey, sessionToken}
28
+ let mine = AppSyncEventsSigner_Ops.signedHeaders(~host, ~path, ~body, ~region, ~isoNow, ~creds)
29
+ let jsCreds: jsCreds = {accessKeyId, secretAccessKey, sessionToken: ?sessionToken}
30
+ let ref = signedHeadersJs(~host, ~path, ~body, ~region, ~isoNow, ~creds=jsCreds)
31
+ expect(mine->get("Authorization"))->toBe(ref->get("Authorization"))
32
+ expect(mine->get("x-amz-date"))->toBe(ref->get("x-amz-date"))
33
+ expect(mine->get("host"))->toBe(ref->get("host"))
34
+ }
35
+
36
+ describe("AppSyncEventsSigner_Ops.signedHeaders parity with the original JS", () => {
37
+ testSync("session-token present", () => {
38
+ assertParity(
39
+ ~host="abc123.appsync-api.eu-west-1.amazonaws.com",
40
+ ~path="/event",
41
+ ~body=`{"id":"m-1","channel":"/default/Catalog","events":["{\\"position\\":\\"1\\"}"]}`,
42
+ ~region="eu-west-1",
43
+ ~isoNow="2026-07-27T13:45:07.123Z",
44
+ ~accessKeyId="ASIAEXAMPLE",
45
+ ~secretAccessKey="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
46
+ ~sessionToken=Some("FQoGZXIvYXdzEXAMPLESESSIONTOKEN=="),
47
+ )
48
+ })
49
+
50
+ testSync("no session token (short-lived / role creds without token)", () => {
51
+ assertParity(
52
+ ~host="xyz.appsync-api.us-east-1.amazonaws.com",
53
+ ~path="/event",
54
+ ~body=`{"id":"e-42","channel":"/default/Orders/o-9","events":["{\\"changeKind\\":\\"Added\\"}"]}`,
55
+ ~region="us-east-1",
56
+ ~isoNow="2026-01-02T00:00:00.000Z",
57
+ ~accessKeyId="AKIAEXAMPLE2",
58
+ ~secretAccessKey="anotherSecretKeyValue1234567890abcdef",
59
+ ~sessionToken=None,
60
+ )
61
+ })
62
+
63
+ testSync("different region + midnight-adjacent timestamp", () => {
64
+ assertParity(
65
+ ~host="host.appsync-api.ap-southeast-2.amazonaws.com",
66
+ ~path="/event",
67
+ ~body="{}",
68
+ ~region="ap-southeast-2",
69
+ ~isoNow="2026-12-31T23:59:59.999Z",
70
+ ~accessKeyId="AKIA3",
71
+ ~secretAccessKey="k3",
72
+ ~sessionToken=Some("tok3"),
73
+ )
74
+ })
75
+ })
@@ -0,0 +1,44 @@
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 AppSyncEventsSigner_Ops$ReventlessAws from "../src/adapter/Api/AppSyncEventsSigner_Ops.res.mjs";
5
+ import * as AppSyncEventsSigner_ParityFixtureMjs from "./AppSyncEventsSigner_ParityFixture.mjs";
6
+
7
+ function signedHeadersJs(prim0, prim1, prim2, prim3, prim4, prim5) {
8
+ return AppSyncEventsSigner_ParityFixtureMjs.signedHeadersJs(prim0, prim1, prim2, prim3, prim4, prim5);
9
+ }
10
+
11
+ function get(d, k) {
12
+ return Stdlib_Option.getOr(d[k], "<missing>");
13
+ }
14
+
15
+ function assertParity(host, path, body, region, isoNow, accessKeyId, secretAccessKey, sessionToken) {
16
+ let creds = {
17
+ accessKeyId: accessKeyId,
18
+ secretAccessKey: secretAccessKey,
19
+ sessionToken: sessionToken
20
+ };
21
+ let mine = AppSyncEventsSigner_Ops$ReventlessAws.signedHeaders(host, path, body, region, isoNow, creds);
22
+ let jsCreds = {
23
+ accessKeyId: accessKeyId,
24
+ secretAccessKey: secretAccessKey,
25
+ sessionToken: sessionToken
26
+ };
27
+ let ref = signedHeadersJs(host, path, body, region, isoNow, jsCreds);
28
+ globalThis.expect(get(mine, "Authorization")).toBe(get(ref, "Authorization"));
29
+ globalThis.expect(get(mine, "x-amz-date")).toBe(get(ref, "x-amz-date"));
30
+ globalThis.expect(get(mine, "host")).toBe(get(ref, "host"));
31
+ }
32
+
33
+ globalThis.describe("AppSyncEventsSigner_Ops.signedHeaders parity with the original JS", () => {
34
+ globalThis.test("session-token present", () => assertParity("abc123.appsync-api.eu-west-1.amazonaws.com", "/event", `{"id":"m-1","channel":"/default/Catalog","events":["{\\"position\\":\\"1\\"}"]}`, "eu-west-1", "2026-07-27T13:45:07.123Z", "ASIAEXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "FQoGZXIvYXdzEXAMPLESESSIONTOKEN=="));
35
+ globalThis.test("no session token (short-lived / role creds without token)", () => assertParity("xyz.appsync-api.us-east-1.amazonaws.com", "/event", `{"id":"e-42","channel":"/default/Orders/o-9","events":["{\\"changeKind\\":\\"Added\\"}"]}`, "us-east-1", "2026-01-02T00:00:00.000Z", "AKIAEXAMPLE2", "anotherSecretKeyValue1234567890abcdef", undefined));
36
+ globalThis.test("different region + midnight-adjacent timestamp", () => assertParity("host.appsync-api.ap-southeast-2.amazonaws.com", "/event", "{}", "ap-southeast-2", "2026-12-31T23:59:59.999Z", "AKIA3", "k3", "tok3"));
37
+ });
38
+
39
+ export {
40
+ signedHeadersJs,
41
+ get,
42
+ assertParity,
43
+ }
44
+ /* Not a pure module */
@@ -0,0 +1,36 @@
1
+ // Golden reference for the AppSync Events SigV4 signer parity test.
2
+ //
3
+ // This is the EXACT hand-rolled `signedHeaders` that lived in the inline JS
4
+ // handler strings of EventLogSubscription_AppSync.res and StateTopic_AppSync.res
5
+ // before those handlers were ported to ReScript (AppSyncEventsSigner_Ops.res).
6
+ // Preserved verbatim except: `new Date()` becomes `new Date(isoNow)` and the
7
+ // credentials are passed in rather than read from process.env — so the signer is
8
+ // deterministic and comparable. AppSyncEventsSigner_OpsTest asserts the ReScript
9
+ // port produces byte-identical output to this reference, guarding against any
10
+ // drift in the canonical-request construction or the HMAC signing-key chain.
11
+
12
+ import { createHmac, createHash } from "node:crypto";
13
+
14
+ function sha256hex(data) {
15
+ return createHash("sha256").update(typeof data === "string" ? data : JSON.stringify(data)).digest("hex");
16
+ }
17
+ function hmacBuf(key, data) {
18
+ return createHmac("sha256", key).update(data).digest();
19
+ }
20
+
21
+ export function signedHeadersJs(host, path, body, region, isoNow, creds) {
22
+ const { accessKeyId, secretAccessKey, sessionToken } = creds;
23
+ const now = new Date(isoNow);
24
+ const amzDate = now.toISOString().replace(/[:\-]|\..../g, "").slice(0, 15) + "Z";
25
+ const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
26
+ const headers = { host, "x-amz-date": amzDate, ...(sessionToken ? { "x-amz-security-token": sessionToken } : {}) };
27
+ const canonH = Object.entries(headers).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}:${v}\n`).join("");
28
+ const signH = Object.keys(headers).sort().join(";");
29
+ const cr = ["POST", path, "", canonH, signH, sha256hex(body)].join("\n");
30
+ const scope = `${dateStamp}/${region}/appsync/aws4_request`;
31
+ const sts = ["AWS4-HMAC-SHA256", amzDate, scope, sha256hex(cr)].join("\n");
32
+ const kDate = hmacBuf("AWS4" + secretAccessKey, dateStamp);
33
+ const kSigning = hmacBuf(hmacBuf(hmacBuf(kDate, region), "appsync"), "aws4_request");
34
+ const sig = createHmac("sha256", kSigning).update(sts).digest("hex");
35
+ return { ...headers, Authorization: `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${scope}, SignedHeaders=${signH}, Signature=${sig}` };
36
+ }
@@ -0,0 +1,165 @@
1
+ // Guards the typed cold-start core hoisted out of AutomationSliceEntryPoint.mjs
2
+ // — the entry point whose former shell had silently drifted from the reworked
3
+ // callback functors (single-applied curried Make, `todoItems.contents`, wrong
4
+ // phase1 shape) and threw on its first event:
5
+ // - parseHandlerConfig — incl. the new `bodyModule` + `context` fields the
6
+ // repaired wiring depends on.
7
+ // - makeAutomationJsonEventsHandler — raw envelope JSONs + context into
8
+ // phase 1, sync → awaited phase 2 → sync ordering.
9
+ // - makeOutboundJsonEventsHandler — tolerant DcbDecode of the inner `event`
10
+ // payload (non-consumed event types dropped silently), decoded events into
11
+ // phase 1.
12
+
13
+ open JestGlobals
14
+
15
+ let str = JSON.Encode.string
16
+ let obj = pairs => JSON.Encode.object(Dict.fromArray(pairs))
17
+
18
+ describe("AutomationSliceEntryPoint_Ops.parseHandlerConfig", () => {
19
+ testSync("empty raw config yields no handlers", () => {
20
+ expect(AutomationSliceEntryPoint_Ops.parseHandlerConfig("")->Array.length)->toBe(0)
21
+ })
22
+
23
+ testSync("decodes a full entry incl. bodyModule and context", () => {
24
+ let config = obj([
25
+ (
26
+ "handlers",
27
+ JSON.Encode.array([
28
+ obj([
29
+ ("specModule", str("@x/plugin/src/Order/AutomationSlice/Restock.res.mjs")),
30
+ ("bodyModule", str("@x/plugin/src/Order/AutomationSlice/Restock_Automation.res.mjs")),
31
+ ("callbackType", str("automation")),
32
+ ("queryDbTableName", str("RestockTodo-abc")),
33
+ ("dcbQueueUrl", str("https://sqs/queue.fifo")),
34
+ ("sourceUrn", str("arn:aws:dynamodb:eu-west-1:1:table/log/stream/x")),
35
+ (
36
+ "context",
37
+ obj([
38
+ ("environment", str("alpha")),
39
+ ("platformName", str("OnlineShop")),
40
+ ("pluginName", str("Ordering")),
41
+ ("sliceName", str("Restock")),
42
+ ]),
43
+ ),
44
+ ]),
45
+ ]),
46
+ ),
47
+ ])->JSON.stringify
48
+ let entries = AutomationSliceEntryPoint_Ops.parseHandlerConfig(config)
49
+ expect(entries->Array.length)->toBe(1)
50
+ let e = entries->Array.getUnsafe(0)
51
+ expect(e.bodyModule)->toBe("@x/plugin/src/Order/AutomationSlice/Restock_Automation.res.mjs")
52
+ expect(e.callbackType)->toBe("automation")
53
+ expect(e.dcbQueueUrl)->toBe("https://sqs/queue.fifo")
54
+ switch e.context {
55
+ | Some(ctx) => {
56
+ expect(ctx.environment)->toBe("alpha")
57
+ expect(ctx.platformName)->toBe("OnlineShop")
58
+ expect(ctx.pluginName)->toBe("Ordering")
59
+ expect(ctx.sliceName)->toBe("Restock")
60
+ }
61
+ | None => JsError.throwWithMessage("expected Some(context)")
62
+ }
63
+ })
64
+
65
+ testSync("a stale entry without bodyModule/context decodes with defaults", () => {
66
+ let config = obj([
67
+ (
68
+ "handlers",
69
+ JSON.Encode.array([
70
+ obj([
71
+ ("specModule", str("a.res.mjs")),
72
+ ("callbackType", str("outbound")),
73
+ ("queryDbTableName", str("t")),
74
+ ("dcbQueueUrl", str("u")),
75
+ ("sourceUrn", str("arn")),
76
+ ]),
77
+ ]),
78
+ ),
79
+ ])->JSON.stringify
80
+ let e = AutomationSliceEntryPoint_Ops.parseHandlerConfig(config)->Array.getUnsafe(0)
81
+ expect(e.bodyModule)->toBe("")
82
+ expect(e.context->Option.isNone)->toBe(true)
83
+ })
84
+ })
85
+
86
+ // Shared stubs — the pipelines only touch phase1/phase2 plus the injected
87
+ // sync/publish, so the callback records carry empty TODO dicts.
88
+ let noopPublish: ReventlessCore.CommandTopic.publishJsons = async _ => ()
89
+
90
+ describe("AutomationSliceEntryPoint_Ops.makeAutomationJsonEventsHandler", () => {
91
+ test("passes raw envelope JSONs + context to phase 1; sync → phase 2 → sync", async () => {
92
+ let steps = []
93
+ let receivedJsons = ref([])
94
+ let receivedCtx = ref(None)
95
+ let callback: AutomationSliceEntryPoint_Ops.automationCallback = {
96
+ todoItems: Dict.make(),
97
+ phase1: (jsons, ctx) => {
98
+ receivedJsons := jsons
99
+ receivedCtx := Some(ctx)
100
+ steps->Array.push("phase1")
101
+ },
102
+ phase2: async _publish => steps->Array.push("phase2"),
103
+ }
104
+ let context: Reventless.AutomationSlice.context = {
105
+ environment: "test",
106
+ platformName: "P",
107
+ pluginName: "Pl",
108
+ sliceName: "S",
109
+ }
110
+ let handler = AutomationSliceEntryPoint_Ops.makeAutomationJsonEventsHandler(
111
+ ~context,
112
+ ~callback,
113
+ ~publishJsons=noopPublish,
114
+ ~syncTodoItems=async () => steps->Array.push("sync"),
115
+ )
116
+ let envelope = obj([("meta", obj([("service", str("Order"))])), ("event", str("Placed"))])
117
+ await Stream.fromIterable([envelope, str("bare")])->handler->Effect.runPromise
118
+ // Raw JSONs — the callback unwraps envelopes itself.
119
+ expect(receivedJsons.contents)->toEqual([envelope, str("bare")])
120
+ expect(receivedCtx.contents->Option.map(c => c.Reventless.AutomationSlice.sliceName))->toEqual(
121
+ Some("S"),
122
+ )
123
+ expect(steps)->toEqual(["phase1", "sync", "phase2", "sync"])
124
+ })
125
+ })
126
+
127
+ // Consumed-event fixture for the outbound decode: one consumed variant with a
128
+ // payload, one payload-less, decoded from a source that also emits types this
129
+ // slice does not consume.
130
+ @schema
131
+ type outboundEvent =
132
+ | OrderPlaced({orderId: string})
133
+ | OrderArchived
134
+
135
+ describe("AutomationSliceEntryPoint_Ops.makeOutboundJsonEventsHandler", () => {
136
+ test("decodes inner event payloads, drops non-consumed types silently", async () => {
137
+ let steps = []
138
+ let received = ref([])
139
+ let callback: AutomationSliceEntryPoint_Ops.outboundCallback<outboundEvent> = {
140
+ todoItems: Dict.make(),
141
+ phase1: events => {
142
+ received := events
143
+ steps->Array.push("phase1")
144
+ },
145
+ phase2: async _publish => steps->Array.push("phase2"),
146
+ }
147
+ let handler = AutomationSliceEntryPoint_Ops.makeOutboundJsonEventsHandler(
148
+ ~consumedEventSchema=outboundEventSchema,
149
+ ~callback,
150
+ ~publishJsons=noopPublish,
151
+ ~syncTodoItems=async () => steps->Array.push("sync"),
152
+ )
153
+ let placed = obj([
154
+ ("meta", obj([("service", str("Order"))])),
155
+ ("event", obj([("TAG", str("OrderPlaced")), ("orderId", str("ord-1"))])),
156
+ ])
157
+ // Not consumed by this slice — dropped without an error.
158
+ let ignored = obj([("event", obj([("TAG", str("OrderShipped")), ("orderId", str("ord-1"))]))])
159
+ // Bare payload-less variant, no envelope.
160
+ let archived = str("OrderArchived")
161
+ await Stream.fromIterable([placed, ignored, archived])->handler->Effect.runPromise
162
+ expect(received.contents)->toEqual([OrderPlaced({orderId: "ord-1"}), OrderArchived])
163
+ expect(steps)->toEqual(["phase1", "sync", "phase2", "sync"])
164
+ })
165
+ })
@@ -0,0 +1,280 @@
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 Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
8
+ import * as AutomationSliceEntryPoint_Ops$ReventlessAws from "../src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs";
9
+
10
+ function str(prim) {
11
+ return prim;
12
+ }
13
+
14
+ function obj(pairs) {
15
+ return Object.fromEntries(pairs);
16
+ }
17
+
18
+ globalThis.describe("AutomationSliceEntryPoint_Ops.parseHandlerConfig", () => {
19
+ globalThis.test("empty raw config yields no handlers", () => {
20
+ globalThis.expect(AutomationSliceEntryPoint_Ops$ReventlessAws.parseHandlerConfig("").length).toBe(0);
21
+ });
22
+ globalThis.test("decodes a full entry incl. bodyModule and context", () => {
23
+ let config = JSON.stringify(Object.fromEntries([[
24
+ "handlers",
25
+ [Object.fromEntries([
26
+ [
27
+ "specModule",
28
+ "@x/plugin/src/Order/AutomationSlice/Restock.res.mjs"
29
+ ],
30
+ [
31
+ "bodyModule",
32
+ "@x/plugin/src/Order/AutomationSlice/Restock_Automation.res.mjs"
33
+ ],
34
+ [
35
+ "callbackType",
36
+ "automation"
37
+ ],
38
+ [
39
+ "queryDbTableName",
40
+ "RestockTodo-abc"
41
+ ],
42
+ [
43
+ "dcbQueueUrl",
44
+ "https://sqs/queue.fifo"
45
+ ],
46
+ [
47
+ "sourceUrn",
48
+ "arn:aws:dynamodb:eu-west-1:1:table/log/stream/x"
49
+ ],
50
+ [
51
+ "context",
52
+ Object.fromEntries([
53
+ [
54
+ "environment",
55
+ "alpha"
56
+ ],
57
+ [
58
+ "platformName",
59
+ "OnlineShop"
60
+ ],
61
+ [
62
+ "pluginName",
63
+ "Ordering"
64
+ ],
65
+ [
66
+ "sliceName",
67
+ "Restock"
68
+ ]
69
+ ])
70
+ ]
71
+ ])]
72
+ ]]));
73
+ let entries = AutomationSliceEntryPoint_Ops$ReventlessAws.parseHandlerConfig(config);
74
+ globalThis.expect(entries.length).toBe(1);
75
+ let e = entries[0];
76
+ globalThis.expect(e.bodyModule).toBe("@x/plugin/src/Order/AutomationSlice/Restock_Automation.res.mjs");
77
+ globalThis.expect(e.callbackType).toBe("automation");
78
+ globalThis.expect(e.dcbQueueUrl).toBe("https://sqs/queue.fifo");
79
+ let ctx = e.context;
80
+ if (ctx !== undefined) {
81
+ globalThis.expect(ctx.environment).toBe("alpha");
82
+ globalThis.expect(ctx.platformName).toBe("OnlineShop");
83
+ globalThis.expect(ctx.pluginName).toBe("Ordering");
84
+ globalThis.expect(ctx.sliceName).toBe("Restock");
85
+ return;
86
+ } else {
87
+ return Stdlib_JsError.throwWithMessage("expected Some(context)");
88
+ }
89
+ });
90
+ globalThis.test("a stale entry without bodyModule/context decodes with defaults", () => {
91
+ let config = JSON.stringify(Object.fromEntries([[
92
+ "handlers",
93
+ [Object.fromEntries([
94
+ [
95
+ "specModule",
96
+ "a.res.mjs"
97
+ ],
98
+ [
99
+ "callbackType",
100
+ "outbound"
101
+ ],
102
+ [
103
+ "queryDbTableName",
104
+ "t"
105
+ ],
106
+ [
107
+ "dcbQueueUrl",
108
+ "u"
109
+ ],
110
+ [
111
+ "sourceUrn",
112
+ "arn"
113
+ ]
114
+ ])]
115
+ ]]));
116
+ let e = AutomationSliceEntryPoint_Ops$ReventlessAws.parseHandlerConfig(config)[0];
117
+ globalThis.expect(e.bodyModule).toBe("");
118
+ globalThis.expect(Stdlib_Option.isNone(e.context)).toBe(true);
119
+ });
120
+ });
121
+
122
+ async function noopPublish(param) {
123
+
124
+ }
125
+
126
+ globalThis.describe("AutomationSliceEntryPoint_Ops.makeAutomationJsonEventsHandler", () => {
127
+ globalThis.test("passes raw envelope JSONs + context to phase 1; sync → phase 2 → sync", async () => {
128
+ let steps = [];
129
+ let receivedJsons = {
130
+ contents: []
131
+ };
132
+ let receivedCtx = {
133
+ contents: undefined
134
+ };
135
+ let callback_todoItems = {};
136
+ let callback_phase1 = (jsons, ctx) => {
137
+ receivedJsons.contents = jsons;
138
+ receivedCtx.contents = ctx;
139
+ steps.push("phase1");
140
+ };
141
+ let callback_phase2 = async _publish => {
142
+ steps.push("phase2");
143
+ };
144
+ let callback = {
145
+ todoItems: callback_todoItems,
146
+ phase1: callback_phase1,
147
+ phase2: callback_phase2
148
+ };
149
+ let handler = AutomationSliceEntryPoint_Ops$ReventlessAws.makeAutomationJsonEventsHandler({
150
+ environment: "test",
151
+ platformName: "P",
152
+ pluginName: "Pl",
153
+ sliceName: "S"
154
+ }, callback, noopPublish, async () => {
155
+ steps.push("sync");
156
+ });
157
+ let envelope = Object.fromEntries([
158
+ [
159
+ "meta",
160
+ Object.fromEntries([[
161
+ "service",
162
+ "Order"
163
+ ]])
164
+ ],
165
+ [
166
+ "event",
167
+ "Placed"
168
+ ]
169
+ ]);
170
+ await Effect.runPromise(handler(Stream.fromIterable([
171
+ envelope,
172
+ "bare"
173
+ ])));
174
+ globalThis.expect(receivedJsons.contents).toEqual([
175
+ envelope,
176
+ "bare"
177
+ ]);
178
+ globalThis.expect(Stdlib_Option.map(receivedCtx.contents, c => c.sliceName)).toEqual("S");
179
+ globalThis.expect(steps).toEqual([
180
+ "phase1",
181
+ "sync",
182
+ "phase2",
183
+ "sync"
184
+ ]);
185
+ });
186
+ });
187
+
188
+ let outboundEventSchema = S.union([
189
+ S.schema(s => ({
190
+ TAG: "OrderPlaced",
191
+ orderId: s.m(S.string)
192
+ })),
193
+ S.literal("OrderArchived")
194
+ ]);
195
+
196
+ globalThis.describe("AutomationSliceEntryPoint_Ops.makeOutboundJsonEventsHandler", () => {
197
+ globalThis.test("decodes inner event payloads, drops non-consumed types silently", async () => {
198
+ let steps = [];
199
+ let received = {
200
+ contents: []
201
+ };
202
+ let callback_todoItems = {};
203
+ let callback_phase1 = events => {
204
+ received.contents = events;
205
+ steps.push("phase1");
206
+ };
207
+ let callback_phase2 = async _publish => {
208
+ steps.push("phase2");
209
+ };
210
+ let callback = {
211
+ todoItems: callback_todoItems,
212
+ phase1: callback_phase1,
213
+ phase2: callback_phase2
214
+ };
215
+ let handler = AutomationSliceEntryPoint_Ops$ReventlessAws.makeOutboundJsonEventsHandler(outboundEventSchema, callback, noopPublish, async () => {
216
+ steps.push("sync");
217
+ });
218
+ let placed = Object.fromEntries([
219
+ [
220
+ "meta",
221
+ Object.fromEntries([[
222
+ "service",
223
+ "Order"
224
+ ]])
225
+ ],
226
+ [
227
+ "event",
228
+ Object.fromEntries([
229
+ [
230
+ "TAG",
231
+ "OrderPlaced"
232
+ ],
233
+ [
234
+ "orderId",
235
+ "ord-1"
236
+ ]
237
+ ])
238
+ ]
239
+ ]);
240
+ let ignored = Object.fromEntries([[
241
+ "event",
242
+ Object.fromEntries([
243
+ [
244
+ "TAG",
245
+ "OrderShipped"
246
+ ],
247
+ [
248
+ "orderId",
249
+ "ord-1"
250
+ ]
251
+ ])
252
+ ]]);
253
+ await Effect.runPromise(handler(Stream.fromIterable([
254
+ placed,
255
+ ignored,
256
+ "OrderArchived"
257
+ ])));
258
+ globalThis.expect(received.contents).toEqual([
259
+ {
260
+ TAG: "OrderPlaced",
261
+ orderId: "ord-1"
262
+ },
263
+ "OrderArchived"
264
+ ]);
265
+ globalThis.expect(steps).toEqual([
266
+ "phase1",
267
+ "sync",
268
+ "phase2",
269
+ "sync"
270
+ ]);
271
+ });
272
+ });
273
+
274
+ export {
275
+ str,
276
+ obj,
277
+ noopPublish,
278
+ outboundEventSchema,
279
+ }
280
+ /* Not a pure module */