@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.
- package/CHANGELOG.md +23 -0
- package/package.json +7 -7
- package/src/adapter/Api/AppSyncEventsSigner_Ops.res +137 -0
- package/src/adapter/Api/AppSyncEventsSigner_Ops.res.mjs +121 -0
- package/src/adapter/Api/Platform_AdminScan_Ops.res +136 -0
- package/src/adapter/Api/Platform_AdminScan_Ops.res.mjs +98 -0
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +12 -90
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +11 -84
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +86 -0
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +79 -0
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +9 -79
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +7 -143
- package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res +48 -0
- package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res.mjs +63 -0
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +21 -80
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +23 -68
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res +70 -0
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res.mjs +68 -0
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -1
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -1
- package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +42 -126
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +246 -0
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +145 -0
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +27 -2
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +10 -3
- package/src/adapter/Runtime/CounterEntryPoint.mjs +19 -73
- package/src/adapter/Runtime/CounterEntryPoint_Ops.res +145 -0
- package/src/adapter/Runtime/CounterEntryPoint_Ops.res.mjs +129 -0
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +14 -1
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +22 -2
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +15 -4
- package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +52 -510
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +816 -0
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +651 -0
- package/src/adapter/Runtime/EventMapperEntryPoint.mjs +23 -67
- package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +126 -0
- package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res.mjs +83 -0
- package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +16 -63
- package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +150 -0
- package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res.mjs +111 -0
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res +96 -0
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res.mjs +70 -0
- package/src/adapter/Runtime/PgMigrationEntryPoint.res +54 -0
- package/src/adapter/Runtime/PgMigrationEntryPoint.res.mjs +37 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +20 -55
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +155 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +94 -0
- package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +80 -0
- package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res.mjs +39 -0
- package/src/adapter/Runtime/QueryDbEntryPoint_Ops.res +4 -4
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +34 -153
- package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res +114 -0
- package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res.mjs +83 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +26 -6
- package/src/adapter/Runtime/SideEffectEntryPoint.mjs +23 -64
- package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res +71 -0
- package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res.mjs +57 -0
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +5 -2
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +1 -1
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +34 -165
- package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +206 -0
- package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res.mjs +105 -0
- package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +156 -0
- package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res.mjs +123 -0
- package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +18 -138
- package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +198 -0
- package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res.mjs +155 -0
- package/src/adapter/StateTopic/StateTopic_AppSync.res +12 -170
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +7 -167
- package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +208 -0
- package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +183 -0
- package/src/components/AutomationSlice_Builder.res +2 -0
- package/src/components/AutomationSlice_Builder.res.mjs +1 -1
- package/src/components/OutboundTranslationSlice_Builder.res +1 -0
- package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
- package/src/plugin/runtime/PluginRuntime_Builder.res +13 -4
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +1 -1
- package/tests/AppSyncEventsSigner_OpsTest.res +75 -0
- package/tests/AppSyncEventsSigner_OpsTest.res.mjs +44 -0
- package/tests/AppSyncEventsSigner_ParityFixture.mjs +36 -0
- package/tests/AutomationSliceEntryPoint_OpsTest.res +165 -0
- package/tests/AutomationSliceEntryPoint_OpsTest.res.mjs +280 -0
- package/tests/CounterEntryPoint_OpsTest.res +115 -0
- package/tests/CounterEntryPoint_OpsTest.res.mjs +150 -0
- package/tests/EventCollectorEntryPoint_OpsTest.res +216 -0
- package/tests/EventCollectorEntryPoint_OpsTest.res.mjs +339 -0
- package/tests/EventMapperEntryPoint_OpsTest.res +103 -0
- package/tests/EventMapperEntryPoint_OpsTest.res.mjs +120 -0
- package/tests/ExtensionPointEntryPoint_OpsTest.res +57 -0
- package/tests/ExtensionPointEntryPoint_OpsTest.res.mjs +54 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res +22 -31
- package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +20 -23
- package/tests/PgQueryResolverEntryPoint_OpsTest.res +38 -0
- package/tests/PgQueryResolverEntryPoint_OpsTest.res.mjs +31 -0
- package/tests/Platform_AdminScan_OpsTest.res +80 -0
- package/tests/Platform_AdminScan_OpsTest.res.mjs +127 -0
- package/tests/ReadModelEntryPoint_OpsTest.res +141 -0
- package/tests/ReadModelEntryPoint_OpsTest.res.mjs +267 -0
- package/tests/SideEffectEntryPoint_OpsTest.res +79 -0
- package/tests/SideEffectEntryPoint_OpsTest.res.mjs +105 -0
- package/tests/StateViewSliceEntryPoint_OpsTest.res +167 -0
- package/tests/StateViewSliceEntryPoint_OpsTest.res.mjs +296 -0
- package/tests/TaskBucketEntryPoint_OpsTest.res +117 -0
- package/tests/TaskBucketEntryPoint_OpsTest.res.mjs +165 -0
- package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res +42 -13
- package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res.mjs +29 -6
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +0 -57
- package/src/adapter/Runtime/PgMigrationEntryPoint.mjs +0 -40
|
@@ -0,0 +1,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 */
|