@reventlessdev/reventless-aws 3.0.0-alpha.232 → 3.0.0-alpha.234

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 (110) 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.mjs +14 -1
  32. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +22 -2
  33. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +15 -4
  34. package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +52 -510
  35. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +816 -0
  36. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +651 -0
  37. package/src/adapter/Runtime/EventMapperEntryPoint.mjs +23 -67
  38. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +126 -0
  39. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res.mjs +83 -0
  40. package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +16 -63
  41. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +150 -0
  42. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res.mjs +111 -0
  43. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res +96 -0
  44. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res.mjs +70 -0
  45. package/src/adapter/Runtime/PgMigrationEntryPoint.res +54 -0
  46. package/src/adapter/Runtime/PgMigrationEntryPoint.res.mjs +37 -0
  47. package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +20 -55
  48. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +155 -0
  49. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +94 -0
  50. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +80 -0
  51. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res.mjs +39 -0
  52. package/src/adapter/Runtime/QueryDbEntryPoint_Ops.res +4 -4
  53. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +34 -153
  54. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res +114 -0
  55. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res.mjs +83 -0
  56. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +26 -6
  57. package/src/adapter/Runtime/SideEffectEntryPoint.mjs +23 -64
  58. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res +71 -0
  59. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res.mjs +57 -0
  60. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +5 -2
  61. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +1 -1
  62. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +34 -165
  63. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +206 -0
  64. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res.mjs +105 -0
  65. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +156 -0
  66. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res.mjs +123 -0
  67. package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +18 -138
  68. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +198 -0
  69. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res.mjs +155 -0
  70. package/src/adapter/StateTopic/StateTopic_AppSync.res +12 -170
  71. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +7 -167
  72. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +208 -0
  73. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +183 -0
  74. package/src/components/AutomationSlice_Builder.res +2 -0
  75. package/src/components/AutomationSlice_Builder.res.mjs +1 -1
  76. package/src/components/OutboundTranslationSlice_Builder.res +1 -0
  77. package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
  78. package/src/plugin/runtime/PluginRuntime_Builder.res +13 -4
  79. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +1 -1
  80. package/tests/AppSyncEventsSigner_OpsTest.res +75 -0
  81. package/tests/AppSyncEventsSigner_OpsTest.res.mjs +44 -0
  82. package/tests/AppSyncEventsSigner_ParityFixture.mjs +36 -0
  83. package/tests/AutomationSliceEntryPoint_OpsTest.res +165 -0
  84. package/tests/AutomationSliceEntryPoint_OpsTest.res.mjs +280 -0
  85. package/tests/CounterEntryPoint_OpsTest.res +115 -0
  86. package/tests/CounterEntryPoint_OpsTest.res.mjs +150 -0
  87. package/tests/EventCollectorEntryPoint_OpsTest.res +216 -0
  88. package/tests/EventCollectorEntryPoint_OpsTest.res.mjs +339 -0
  89. package/tests/EventMapperEntryPoint_OpsTest.res +103 -0
  90. package/tests/EventMapperEntryPoint_OpsTest.res.mjs +120 -0
  91. package/tests/ExtensionPointEntryPoint_OpsTest.res +57 -0
  92. package/tests/ExtensionPointEntryPoint_OpsTest.res.mjs +54 -0
  93. package/tests/PgMigrationEntryPoint_IntegrationTest.res +22 -31
  94. package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +20 -23
  95. package/tests/PgQueryResolverEntryPoint_OpsTest.res +38 -0
  96. package/tests/PgQueryResolverEntryPoint_OpsTest.res.mjs +31 -0
  97. package/tests/Platform_AdminScan_OpsTest.res +80 -0
  98. package/tests/Platform_AdminScan_OpsTest.res.mjs +127 -0
  99. package/tests/ReadModelEntryPoint_OpsTest.res +141 -0
  100. package/tests/ReadModelEntryPoint_OpsTest.res.mjs +267 -0
  101. package/tests/SideEffectEntryPoint_OpsTest.res +79 -0
  102. package/tests/SideEffectEntryPoint_OpsTest.res.mjs +105 -0
  103. package/tests/StateViewSliceEntryPoint_OpsTest.res +167 -0
  104. package/tests/StateViewSliceEntryPoint_OpsTest.res.mjs +296 -0
  105. package/tests/TaskBucketEntryPoint_OpsTest.res +117 -0
  106. package/tests/TaskBucketEntryPoint_OpsTest.res.mjs +165 -0
  107. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res +42 -13
  108. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res.mjs +29 -6
  109. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +0 -57
  110. package/src/adapter/Runtime/PgMigrationEntryPoint.mjs +0 -40
@@ -0,0 +1,206 @@
1
+ // Typed cold-start core for the StateViewSlice Lambda entry point.
2
+ //
3
+ // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
4
+ // StateViewSliceEntryPoint.mjs keeps only the inherently-untyped seam — the
5
+ // dynamic `import()` of spec/projection modules named in HANDLER_CONFIG and the
6
+ // reads of their exports. HANDLER_CONFIG parsing (including the compact-v2
7
+ // expansion), the QueryDb operation assembly, and the whole projection stream
8
+ // pipeline (envelope decode → sury event parse → project → handleAction) live
9
+ // here, fully type-checked; the QueryDb backend branch is shared with the
10
+ // ReadModel entry point in ProjectionEntryPoint_Ops and the routed dispatch
11
+ // boundary lives in StreamRoutedEntryPoint_Ops.
12
+
13
+ type handlerEntry = {
14
+ specModule: string,
15
+ projectionModule: string,
16
+ queryDbTableName: string,
17
+ sourceUrn: string,
18
+ // B3.3: per-slice AppSync Events channel root (list field name). Present only
19
+ // for subscription-enabled (Stream) Postgres view slices.
20
+ stateTopicName: option<string>,
21
+ pgConnection: option<PgConnection.connectionConfig>,
22
+ }
23
+
24
+ // The builder writes `pgConnection` via PgConnection.connectionConfigToJson, so
25
+ // the parsed object IS the record shape — cast at the documented JSON contract
26
+ // (the AggregateEntryPoint_Ops.asConnectionConfig pattern).
27
+ external asPgConnection: JSON.t => PgConnection.connectionConfig = "%identity"
28
+
29
+ let strOf = (obj: dict<JSON.t>, key: string): option<string> =>
30
+ obj->Dict.get(key)->Option.flatMap(JSON.Decode.string)
31
+
32
+ let pgConnectionOf = (obj: dict<JSON.t>): option<PgConnection.connectionConfig> =>
33
+ obj
34
+ ->Dict.get("pgConnection")
35
+ ->Option.filter(j => j != JSON.Null)
36
+ ->Option.map(asPgConnection)
37
+
38
+ // HANDLER_CONFIG is emitted compact (StateViewSliceRuntime_Builder_Single) to
39
+ // stay under AWS Lambda's 4KB env-var limit: a shared module-path `base`, a
40
+ // shared `sourceUrn`, and a shared `pgConnection` are hoisted out (all of a
41
+ // plugin's slices follow the platform backend toggle), and per-handler keys are
42
+ // shortened to s/p/q/u/t. Expand back to the full handlerEntry shape. Legacy
43
+ // full-key entries pass through with their own per-entry fields.
44
+ let parseHandlerConfig = (rawJson: string): array<handlerEntry> =>
45
+ rawJson == ""
46
+ ? []
47
+ : {
48
+ let obj =
49
+ rawJson
50
+ ->JSON.parseOrThrow
51
+ ->JSON.Decode.object
52
+ ->Option.getOr(Dict.make())
53
+ let base = obj->strOf("base")->Option.getOr("")
54
+ let sharedUrn = obj->strOf("sourceUrn")->Option.getOr("")
55
+ let sharedPgConnection = obj->pgConnectionOf
56
+ obj
57
+ ->Dict.get("handlers")
58
+ ->Option.flatMap(JSON.Decode.array)
59
+ ->Option.getOr([])
60
+ ->Array.filterMap(item =>
61
+ item
62
+ ->JSON.Decode.object
63
+ ->Option.map(h =>
64
+ switch h->strOf("specModule") {
65
+ | Some(specModule) => {
66
+ specModule,
67
+ projectionModule: h->strOf("projectionModule")->Option.getOr(""),
68
+ queryDbTableName: h->strOf("queryDbTableName")->Option.getOr(""),
69
+ sourceUrn: h->strOf("sourceUrn")->Option.getOr(""),
70
+ stateTopicName: h->strOf("stateTopicName"),
71
+ pgConnection: h->pgConnectionOf,
72
+ }
73
+ | None => {
74
+ specModule: base ++ h->strOf("s")->Option.getOr(""),
75
+ projectionModule: base ++ h->strOf("p")->Option.getOr(""),
76
+ queryDbTableName: h->strOf("q")->Option.getOr(""),
77
+ sourceUrn: h->strOf("u")->Option.getOr(sharedUrn),
78
+ stateTopicName: h->strOf("t"),
79
+ pgConnection: sharedPgConnection,
80
+ }
81
+ }
82
+ )
83
+ )
84
+ }
85
+
86
+ // ── Projection stream pipeline ──────────────────────────────────────────────
87
+
88
+ // Events arrive as `{id, meta, recordedAt, event}` envelopes
89
+ // (Util_DynamoDbStream_Runtime.buildJsonEvent' / PgChangeFeedRelay). Surface
90
+ // `meta` + `recordedAt` to the projection as the `consumed` envelope;
91
+ // `recordedAt` defaults to "" if a producer omitted it. `meta` is already
92
+ // schema-shaped JSON written by the producer — cast at the documented contract
93
+ // (absent maps to undefined, exactly what the former JS passed through).
94
+ external asMeta: option<JSON.t> => Reventless.Message.meta = "%identity"
95
+
96
+ type envelope<'event> = {
97
+ event: JSON.t,
98
+ meta: Reventless.Message.meta,
99
+ recordedAt: string,
100
+ }
101
+
102
+ let decodeEnvelope = (json: JSON.t): envelope<'event> => {
103
+ let fields = json->JSON.Decode.object
104
+ {
105
+ event: switch fields->Option.flatMap(f => f->Dict.get("event")) {
106
+ | Some(JSON.Null) | None => json
107
+ | Some(event) => event
108
+ },
109
+ meta: fields->Option.flatMap(f => f->Dict.get("meta"))->asMeta,
110
+ recordedAt: fields
111
+ ->Option.flatMap(f => f->Dict.get("recordedAt"))
112
+ ->Option.flatMap(JSON.Decode.string)
113
+ ->Option.getOr(""),
114
+ }
115
+ }
116
+
117
+ let exnMessage = StreamRoutedEntryPoint_Ops.exnMessage
118
+
119
+ // The typed core of buildJsonEventsHandler: decode each envelope, parse the
120
+ // event against the slice's consumed-event schema, project, and run every
121
+ // resulting action against the view table. A decode failure logs and yields no
122
+ // actions (the batch continues — at-least-once delivery would otherwise wedge
123
+ // the whole source on one malformed record).
124
+ let makeJsonEventsHandler = (
125
+ ~sliceName: string,
126
+ ~eventSchema: S.t<'event>,
127
+ ~project: Reventless.StateViewSlice.consumed<'event> => array<
128
+ Reventless.Projection.action<string, JSON.t>,
129
+ >,
130
+ ~queryDbOps: ReventlessCore.QueryDb_Adapter.operations,
131
+ ~subIdConfig: option<Reventless.ReadModel.subIdConfig<JSON.t>>,
132
+ ): ReventlessCore.EventCollector.jsonEventsHandler =>
133
+ stream =>
134
+ stream
135
+ ->Stream.mapEffect(json =>
136
+ Effect.sync(() =>
137
+ try {
138
+ let {event, meta, recordedAt} = decodeEnvelope(json)
139
+ project({event: event->S.parseJsonOrThrow(eventSchema), meta, recordedAt})
140
+ } catch {
141
+ | exn =>
142
+ StreamRoutedEntryPoint_Ops.logError(
143
+ "failed to decode event",
144
+ {comp: "StateViewSliceRuntime", detail: exnMessage(exn)},
145
+ )
146
+ []
147
+ }
148
+ )
149
+ )
150
+ ->Stream.flatMap(actions => Stream.fromIterable(actions))
151
+ ->Stream.runForEach(action =>
152
+ // Thread the slice's `subIdConfig` (generated from `@subId`) so
153
+ // sub-id-dependent actions like `UpdateMultiState` can resolve the sort
154
+ // key. Dropping it here made every such action hit the
155
+ // `MissingSubIdConfig` guard and silently write nothing, while the
156
+ // test-harness callback path (which threads it) stayed green. `None` for
157
+ // slices without an `@subId` is correct — those never emit sub-id
158
+ // actions. `~comp` is the slice's spec name — attribution for
159
+ // Projection.handleAction's debug-lazy action log.
160
+ Effect.promise(() =>
161
+ ReventlessCore.Projection.handleAction(~comp=sliceName, action, queryDbOps, subIdConfig)
162
+ )->Effect.map(_ => ())
163
+ )
164
+
165
+ // ── Handler assembly (called by the shell per HANDLER_CONFIG entry) ─────────
166
+ // `sliceModules` types the shell's reads off the two dynamically imported
167
+ // modules: the spec module exports `name` / `consumedEventSchema` / `config` /
168
+ // `subIdConfig`; the `project` function lives in the sibling projection module
169
+ // (`<Name>_Projection.res`).
170
+
171
+ type sliceModules<'event> = {
172
+ name: string,
173
+ consumedEventSchema: S.t<'event>,
174
+ project: Reventless.StateViewSlice.consumed<'event> => array<
175
+ Reventless.Projection.action<string, JSON.t>,
176
+ >,
177
+ config: option<ProjectionEntryPoint_Ops.specConfig>,
178
+ subIdConfig: option<Reventless.ReadModel.subIdConfig<JSON.t>>,
179
+ }
180
+
181
+ let makeRegisteredHandler = (
182
+ entry: handlerEntry,
183
+ modules: sliceModules<'event>,
184
+ ): StreamRoutedEntryPoint_Ops.registeredHandler => {
185
+ let queryDbOps = ProjectionEntryPoint_Ops.makeQueryDbOps(
186
+ ~queryDbTableName=entry.queryDbTableName,
187
+ ~pgConnection=entry.pgConnection,
188
+ ~stateTopicName=entry.stateTopicName,
189
+ ~indexes=ProjectionEntryPoint_Ops.indexesOf(modules.config),
190
+ ~subIdField=ProjectionEntryPoint_Ops.subIdFieldOf(modules.subIdConfig),
191
+ )
192
+ {
193
+ handler: StreamRoutedEntryPoint_Ops.toStreamHandler(
194
+ makeJsonEventsHandler(
195
+ ~sliceName=modules.name,
196
+ ~eventSchema=modules.consumedEventSchema,
197
+ ~project=modules.project,
198
+ ~queryDbOps,
199
+ ~subIdConfig=modules.subIdConfig,
200
+ ),
201
+ ),
202
+ // Matches the `StateViewSlice(<name>)` shape the rest of the framework
203
+ // logs under.
204
+ comp: `StateViewSlice(${modules.name})`,
205
+ }
206
+ }
@@ -0,0 +1,105 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Stream from "effect/Stream";
9
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
10
+ import * as Projection$ReventlessCore from "@reventlessdev/reventless-core/src/Projection.res.mjs";
11
+ import * as ProjectionEntryPoint_Ops$ReventlessAws from "./ProjectionEntryPoint_Ops.res.mjs";
12
+ import * as StreamRoutedEntryPoint_Ops$ReventlessAws from "./StreamRoutedEntryPoint_Ops.res.mjs";
13
+
14
+ function strOf(obj, key) {
15
+ return Stdlib_Option.flatMap(obj[key], Stdlib_JSON.Decode.string);
16
+ }
17
+
18
+ function pgConnectionOf(obj) {
19
+ return Stdlib_Option.map(Stdlib_Option.filter(obj["pgConnection"], j => j !== null), prim => prim);
20
+ }
21
+
22
+ function parseHandlerConfig(rawJson) {
23
+ if (rawJson === "") {
24
+ return [];
25
+ }
26
+ let obj = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(rawJson)), {});
27
+ let base = Stdlib_Option.getOr(strOf(obj, "base"), "");
28
+ let sharedUrn = Stdlib_Option.getOr(strOf(obj, "sourceUrn"), "");
29
+ let sharedPgConnection = pgConnectionOf(obj);
30
+ return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(obj["handlers"], Stdlib_JSON.Decode.array), []), item => Stdlib_Option.map(Stdlib_JSON.Decode.object(item), h => {
31
+ let specModule = strOf(h, "specModule");
32
+ if (specModule !== undefined) {
33
+ return {
34
+ specModule: specModule,
35
+ projectionModule: Stdlib_Option.getOr(strOf(h, "projectionModule"), ""),
36
+ queryDbTableName: Stdlib_Option.getOr(strOf(h, "queryDbTableName"), ""),
37
+ sourceUrn: Stdlib_Option.getOr(strOf(h, "sourceUrn"), ""),
38
+ stateTopicName: strOf(h, "stateTopicName"),
39
+ pgConnection: pgConnectionOf(h)
40
+ };
41
+ } else {
42
+ return {
43
+ specModule: base + Stdlib_Option.getOr(strOf(h, "s"), ""),
44
+ projectionModule: base + Stdlib_Option.getOr(strOf(h, "p"), ""),
45
+ queryDbTableName: Stdlib_Option.getOr(strOf(h, "q"), ""),
46
+ sourceUrn: Stdlib_Option.getOr(strOf(h, "u"), sharedUrn),
47
+ stateTopicName: strOf(h, "t"),
48
+ pgConnection: sharedPgConnection
49
+ };
50
+ }
51
+ }));
52
+ }
53
+
54
+ function decodeEnvelope(json) {
55
+ let fields = Stdlib_JSON.Decode.object(json);
56
+ let event = Stdlib_Option.flatMap(fields, f => f["event"]);
57
+ let tmp;
58
+ tmp = event !== undefined && event !== null ? event : json;
59
+ return {
60
+ event: tmp,
61
+ meta: Stdlib_Option.flatMap(fields, f => f["meta"]),
62
+ recordedAt: Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(fields, f => f["recordedAt"]), Stdlib_JSON.Decode.string), "")
63
+ };
64
+ }
65
+
66
+ function makeJsonEventsHandler(sliceName, eventSchema, project, queryDbOps, subIdConfig) {
67
+ return stream => Stream.runForEach(Stream.flatMap(Stream.mapEffect(stream, json => Effect.sync(() => {
68
+ try {
69
+ let match = decodeEnvelope(json);
70
+ return project({
71
+ event: S.parseJsonOrThrow(match.event, eventSchema),
72
+ meta: match.meta,
73
+ recordedAt: match.recordedAt
74
+ });
75
+ } catch (raw_exn) {
76
+ let exn = Primitive_exceptions.internalToException(raw_exn);
77
+ StreamRoutedEntryPoint_Ops$ReventlessAws.logError("failed to decode event", {
78
+ comp: "StateViewSliceRuntime",
79
+ detail: StreamRoutedEntryPoint_Ops$ReventlessAws.exnMessage(exn)
80
+ });
81
+ return [];
82
+ }
83
+ })), actions => Stream.fromIterable(actions)), action => Effect.map(Effect.promise(() => Projection$ReventlessCore.handleAction(sliceName, action, queryDbOps, subIdConfig)), param => {}));
84
+ }
85
+
86
+ function makeRegisteredHandler(entry, modules) {
87
+ let queryDbOps = ProjectionEntryPoint_Ops$ReventlessAws.makeQueryDbOps(entry.queryDbTableName, entry.pgConnection, entry.stateTopicName, ProjectionEntryPoint_Ops$ReventlessAws.indexesOf(modules.config), ProjectionEntryPoint_Ops$ReventlessAws.subIdFieldOf(modules.subIdConfig));
88
+ return {
89
+ handler: StreamRoutedEntryPoint_Ops$ReventlessAws.toStreamHandler(makeJsonEventsHandler(modules.name, modules.consumedEventSchema, modules.project, queryDbOps, modules.subIdConfig)),
90
+ comp: `StateViewSlice(` + modules.name + `)`
91
+ };
92
+ }
93
+
94
+ let exnMessage = StreamRoutedEntryPoint_Ops$ReventlessAws.exnMessage;
95
+
96
+ export {
97
+ strOf,
98
+ pgConnectionOf,
99
+ parseHandlerConfig,
100
+ decodeEnvelope,
101
+ exnMessage,
102
+ makeJsonEventsHandler,
103
+ makeRegisteredHandler,
104
+ }
105
+ /* S Not a pure module */
@@ -0,0 +1,156 @@
1
+ // Typed routed dispatch boundary shared by the stream-routed Lambda entry
2
+ // points (ReadModelEntryPoint.mjs, StateViewSliceEntryPoint.mjs,
3
+ // AutomationSliceEntryPoint.mjs).
4
+ //
5
+ // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
6
+ // all three shells route DynamoDB-stream / SQS-feed records to per-source-URN
7
+ // handler lists with per-handler log attribution — machinery the `.mjs` files
8
+ // previously duplicated verbatim. It lives here so every co-hosted component
9
+ // dispatches identically; the shells keep only their dynamic-import seams.
10
+ //
11
+ // Kept free of storage-backend imports (in particular the Postgres runtime,
12
+ // which lives in ProjectionEntryPoint_Ops): the AutomationSlice Lambda routes
13
+ // through this module but must not carry `pg`/PgRuntime in its cold-start
14
+ // graph.
15
+ //
16
+ // Runtime-pure: no `open PulumiAws` values — Pulumi appears in type positions
17
+ // only (erased).
18
+
19
+ // ── Shim bindings (HandlerFactoryHelpers.mjs) ───────────────────────────────
20
+ // The structured-log + Effect dispatch boundary shared by every deployed entry
21
+ // point.
22
+
23
+ type dispatchOpts = {
24
+ correlationId?: string,
25
+ causationId?: string,
26
+ comp?: string,
27
+ plugin?: string,
28
+ timestamp?: float,
29
+ retryCount?: int,
30
+ detail?: string,
31
+ }
32
+
33
+ @module("./HandlerFactoryHelpers.mjs")
34
+ external setRequestId: string => unit = "setRequestId"
35
+ @module("./HandlerFactoryHelpers.mjs")
36
+ external runEffect: (Effect.t<'a, 'e, 'r>, dispatchOpts) => promise<unit> = "runEffect"
37
+ @module("./HandlerFactoryHelpers.mjs")
38
+ external extractMetaField: (
39
+ array<PulumiAws.Lambda.CallbackFunction.record>,
40
+ string,
41
+ ) => option<string> = "extractMetaField"
42
+ @module("./HandlerFactoryHelpers.mjs")
43
+ external extractSentTimestamp: array<PulumiAws.Lambda.CallbackFunction.record> => option<float> =
44
+ "extractSentTimestamp"
45
+ @module("./HandlerFactoryHelpers.mjs")
46
+ external extractRetryCount: array<PulumiAws.Lambda.CallbackFunction.record> => int =
47
+ "extractRetryCount"
48
+ @module("./HandlerFactoryHelpers.mjs") @scope("log")
49
+ external logDebug: (string, dispatchOpts) => unit = "debug"
50
+ @module("./HandlerFactoryHelpers.mjs") @scope("log")
51
+ external logWarn: (string, dispatchOpts) => unit = "warn"
52
+ @module("./HandlerFactoryHelpers.mjs") @scope("log")
53
+ external logError: (string, dispatchOpts) => unit = "error"
54
+
55
+ let exnMessage = (exn: exn): string =>
56
+ exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown error")
57
+
58
+ // ── Handler registry + routed dispatch boundary ─────────────────────────────
59
+
60
+ type streamHandler = (
61
+ PulumiAws.Lambda.CallbackFunction.event,
62
+ PulumiAws.Lambda.context,
63
+ ) => Effect.t<unit, string, unit>
64
+
65
+ // One Lambda hosts every component of its kind, so each handler carries the
66
+ // comp of the component it runs (and, for read models, the owning plugin —
67
+ // the shared Lambda's own name carries no single plugin identity).
68
+ type registeredHandler = {
69
+ handler: streamHandler,
70
+ comp?: string,
71
+ plugin?: string,
72
+ }
73
+
74
+ let toStreamHandler = (
75
+ handleJsonEvents: ReventlessCore.EventCollector.jsonEventsHandler,
76
+ ): streamHandler =>
77
+ (event, context) =>
78
+ EventCollectorChannel_DynamoDbStream_Runtime.handleStreamEvent(handleJsonEvents, event, context)
79
+
80
+ // Multiple components can share one source stream — e.g. every admin read
81
+ // model (Plugins, PluginHistory, PlatformEventGraph, UIFragmentRegistry)
82
+ // projects the Plugin aggregate's EventLog stream, and several automation
83
+ // slices can react to the same DcbEventLog. Accumulate ALL handlers per source
84
+ // URN; a plain `registry[urn] = handler` collapses them to one (whichever
85
+ // async builder wins the Promise.all race), silently dropping the rest.
86
+ let addToRegistry = (
87
+ registry: dict<array<registeredHandler>>,
88
+ sourceUrn: string,
89
+ handler: registeredHandler,
90
+ ) =>
91
+ switch registry->Dict.get(sourceUrn) {
92
+ | Some(handlers) => handlers->Array.push(handler)
93
+ | None => registry->Dict.set(sourceUrn, [handler])
94
+ }
95
+
96
+ let groupBySource = (
97
+ records: array<PulumiAws.Lambda.CallbackFunction.record>,
98
+ ): dict<array<PulumiAws.Lambda.CallbackFunction.record>> => {
99
+ let grouped = Dict.make()
100
+ records->Array.forEach(record =>
101
+ switch grouped->Dict.get(record.eventSourceARN) {
102
+ | Some(existing) => existing->Array.push(record)
103
+ | None => grouped->Dict.set(record.eventSourceARN, [record])
104
+ }
105
+ )
106
+ grouped
107
+ }
108
+
109
+ // The exported Lambda handler: group the batch by source ARN and run every
110
+ // handler registered for each source (independent storage, so concurrent is
111
+ // safe). Each handler carries its own comp so the shared Lambda's log lines
112
+ // stay separable per component.
113
+ let makeRoutedHandler = (
114
+ ~comp: string,
115
+ registryPromise: promise<dict<array<registeredHandler>>>,
116
+ ) =>
117
+ async (event: PulumiAws.Lambda.CallbackFunction.event, context: PulumiAws.Lambda.context) => {
118
+ setRequestId(context.awsRequestId)
119
+ let registry = await registryPromise
120
+ let _ =
121
+ await groupBySource(event.records)
122
+ ->Dict.toArray
123
+ ->Array.map(async ((arn, subRecords)) =>
124
+ switch registry->Dict.get(arn)->Option.filter(hs => hs->Array.length > 0) {
125
+ | Some(streamHandlers) =>
126
+ logDebug(
127
+ `found ${streamHandlers->Array.length->Int.toString} handler(s) for ${arn}`,
128
+ {comp: comp},
129
+ )
130
+ let correlationId = extractMetaField(subRecords, "correlationId")
131
+ let causationId = extractMetaField(subRecords, "causationId")
132
+ let timestamp = extractSentTimestamp(subRecords)
133
+ let retryCount = extractRetryCount(subRecords)
134
+ let subEvent: PulumiAws.Lambda.CallbackFunction.event = {records: subRecords}
135
+ let _ =
136
+ await streamHandlers
137
+ ->Array.map(registered =>
138
+ runEffect(
139
+ registered.handler(subEvent, context),
140
+ {
141
+ correlationId: ?correlationId,
142
+ causationId: ?causationId,
143
+ comp: ?registered.comp,
144
+ plugin: ?registered.plugin,
145
+ timestamp: ?timestamp,
146
+ retryCount,
147
+ },
148
+ )
149
+ )
150
+ ->Promise.all
151
+ | None => logWarn("no handler found: " ++ arn, {comp: comp})
152
+ }
153
+ )
154
+ ->Promise.all
155
+ ""
156
+ }
@@ -0,0 +1,123 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as HandlerFactoryHelpersMjs from "./HandlerFactoryHelpers.mjs";
6
+ import * as EventCollectorChannel_DynamoDbStream_Runtime$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res.mjs";
7
+
8
+ function setRequestId(prim) {
9
+ HandlerFactoryHelpersMjs.setRequestId(prim);
10
+ }
11
+
12
+ function runEffect(prim0, prim1) {
13
+ return HandlerFactoryHelpersMjs.runEffect(prim0, prim1);
14
+ }
15
+
16
+ function extractMetaField(prim0, prim1) {
17
+ return HandlerFactoryHelpersMjs.extractMetaField(prim0, prim1);
18
+ }
19
+
20
+ function extractSentTimestamp(prim) {
21
+ return HandlerFactoryHelpersMjs.extractSentTimestamp(prim);
22
+ }
23
+
24
+ function extractRetryCount(prim) {
25
+ return HandlerFactoryHelpersMjs.extractRetryCount(prim);
26
+ }
27
+
28
+ function logDebug(prim0, prim1) {
29
+ HandlerFactoryHelpersMjs.log.debug(prim0, prim1);
30
+ }
31
+
32
+ function logWarn(prim0, prim1) {
33
+ HandlerFactoryHelpersMjs.log.warn(prim0, prim1);
34
+ }
35
+
36
+ function logError(prim0, prim1) {
37
+ HandlerFactoryHelpersMjs.log.error(prim0, prim1);
38
+ }
39
+
40
+ function exnMessage(exn) {
41
+ return Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown error");
42
+ }
43
+
44
+ function toStreamHandler(handleJsonEvents) {
45
+ return (event, context) => EventCollectorChannel_DynamoDbStream_Runtime$ReventlessAws.handleStreamEvent(handleJsonEvents, event, context);
46
+ }
47
+
48
+ function addToRegistry(registry, sourceUrn, handler) {
49
+ let handlers = registry[sourceUrn];
50
+ if (handlers !== undefined) {
51
+ handlers.push(handler);
52
+ } else {
53
+ registry[sourceUrn] = [handler];
54
+ }
55
+ }
56
+
57
+ function groupBySource(records) {
58
+ let grouped = {};
59
+ records.forEach(record => {
60
+ let existing = grouped[record.eventSourceARN];
61
+ if (existing !== undefined) {
62
+ existing.push(record);
63
+ } else {
64
+ grouped[record.eventSourceARN] = [record];
65
+ }
66
+ });
67
+ return grouped;
68
+ }
69
+
70
+ function makeRoutedHandler(comp, registryPromise) {
71
+ return async (event, context) => {
72
+ HandlerFactoryHelpersMjs.setRequestId(context.awsRequestId);
73
+ let registry = await registryPromise;
74
+ await Promise.all(Object.entries(groupBySource(event.Records)).map(async param => {
75
+ let subRecords = param[1];
76
+ let arn = param[0];
77
+ let streamHandlers = Stdlib_Option.filter(registry[arn], hs => hs.length !== 0);
78
+ if (streamHandlers !== undefined) {
79
+ let prim0 = `found ` + streamHandlers.length.toString() + ` handler(s) for ` + arn;
80
+ HandlerFactoryHelpersMjs.log.debug(prim0, {
81
+ comp: comp
82
+ });
83
+ let correlationId = HandlerFactoryHelpersMjs.extractMetaField(subRecords, "correlationId");
84
+ let causationId = HandlerFactoryHelpersMjs.extractMetaField(subRecords, "causationId");
85
+ let timestamp = HandlerFactoryHelpersMjs.extractSentTimestamp(subRecords);
86
+ let retryCount = HandlerFactoryHelpersMjs.extractRetryCount(subRecords);
87
+ let subEvent = {
88
+ Records: subRecords
89
+ };
90
+ await Promise.all(streamHandlers.map(registered => HandlerFactoryHelpersMjs.runEffect(registered.handler(subEvent, context), {
91
+ correlationId: correlationId,
92
+ causationId: causationId,
93
+ comp: registered.comp,
94
+ plugin: registered.plugin,
95
+ timestamp: timestamp,
96
+ retryCount: retryCount
97
+ })));
98
+ return;
99
+ }
100
+ HandlerFactoryHelpersMjs.log.warn("no handler found: " + arn, {
101
+ comp: comp
102
+ });
103
+ }));
104
+ return "";
105
+ };
106
+ }
107
+
108
+ export {
109
+ setRequestId,
110
+ runEffect,
111
+ extractMetaField,
112
+ extractSentTimestamp,
113
+ extractRetryCount,
114
+ logDebug,
115
+ logWarn,
116
+ logError,
117
+ exnMessage,
118
+ toStreamHandler,
119
+ addToRegistry,
120
+ groupBySource,
121
+ makeRoutedHandler,
122
+ }
123
+ /* Stdlib_JsExn Not a pure module */