@reventlessdev/reventless-aws 3.0.0-alpha.229 → 3.0.0-alpha.231
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 +15 -0
- package/package.json +5 -5
- package/src/adapter/Geocoder/Geocoder_AwsLocation.res +60 -141
- package/src/adapter/Geocoder/Geocoder_AwsLocation.res.mjs +52 -118
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Ops.res +137 -0
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Ops.res.mjs +124 -0
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +51 -6
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +60 -0
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +29 -1
- package/src/adapter/Runtime/HeartbeatEntryPoint.res +44 -0
- package/src/adapter/Runtime/HeartbeatEntryPoint.res.mjs +47 -0
- package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res +234 -0
- package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res.mjs +226 -0
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +49 -4
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +22 -4
- package/src/adapter/Upload/Upload_Presign_S3.res +65 -164
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +56 -119
- package/src/adapter/Upload/Upload_Presign_S3_Ops.res +157 -0
- package/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs +120 -0
- package/src/components/InboundTranslationSlice_Builder.res +27 -1
- package/src/components/InboundTranslationSlice_Builder.res.mjs +15 -2
- package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res +1 -1
- package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +1 -1
- package/src/plugin/runtime/PluginRuntime_Builder.res +59 -1
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +49 -2
- package/tests/DcbInboundTranslationRoutingTest.res +73 -0
- package/tests/DcbInboundTranslationRoutingTest.res.mjs +71 -0
- package/tests/EpInboundTestSlice.res +19 -0
- package/tests/EpInboundTestSlice.res.mjs +31 -0
- package/tests/EpInboundTestSliceTranslation.res +13 -0
- package/tests/EpInboundTestSliceTranslation.res.mjs +33 -0
- package/src/adapter/Runtime/HeartbeatEntryPoint.mjs +0 -38
- package/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs +0 -127
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Aws from "@pulumi/aws";
|
|
4
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
6
|
import * as Pulumi from "@pulumi/pulumi";
|
|
6
7
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
@@ -50,6 +51,38 @@ function registerStateChangeSliceSpec(specPath, behaviorPath) {
|
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
let registeredInboundSlices = {};
|
|
55
|
+
|
|
56
|
+
function registerInboundTranslationSliceSpec(specName, specPath, translationPath) {
|
|
57
|
+
let reg = registeredInboundSlices[specName];
|
|
58
|
+
if (reg !== undefined) {
|
|
59
|
+
registeredInboundSlices[specName] = {
|
|
60
|
+
specPath: specPath,
|
|
61
|
+
translationPath: translationPath,
|
|
62
|
+
auditTableName: reg.auditTableName
|
|
63
|
+
};
|
|
64
|
+
} else {
|
|
65
|
+
registeredInboundSlices[specName] = {
|
|
66
|
+
specPath: specPath,
|
|
67
|
+
translationPath: translationPath,
|
|
68
|
+
auditTableName: undefined
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function registerInboundAuditTableName(specName, tableName) {
|
|
74
|
+
let reg = registeredInboundSlices[specName];
|
|
75
|
+
if (reg !== undefined) {
|
|
76
|
+
reg.auditTableName = tableName;
|
|
77
|
+
} else {
|
|
78
|
+
registeredInboundSlices[specName] = {
|
|
79
|
+
specPath: "",
|
|
80
|
+
translationPath: "",
|
|
81
|
+
auditTableName: tableName
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
53
86
|
function registerDcbConfig(pluginName, dcbTableName, stateChangeSliceModulePathsOpt, param) {
|
|
54
87
|
let stateChangeSliceModulePaths = stateChangeSliceModulePathsOpt !== undefined ? stateChangeSliceModulePathsOpt : [];
|
|
55
88
|
dcbConfigRef.contents = {
|
|
@@ -422,7 +455,7 @@ function Make(EventCollectorChannel) {
|
|
|
422
455
|
envVars["EP_QUEUE_URL"] = epQueueUrl;
|
|
423
456
|
envVars["PLUGIN_ID"] = Pulumi.output(hbConfig.pluginId);
|
|
424
457
|
envVars["HEARTBEAT_TIMEOUT"] = Pulumi.output(hbConfig.heartbeatTimeout.toString());
|
|
425
|
-
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/HeartbeatEntryPoint.mjs", {}, undefined);
|
|
458
|
+
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/HeartbeatEntryPoint.res.mjs", {}, undefined);
|
|
426
459
|
connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, "Scheduler", "Scheduler", match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts));
|
|
427
460
|
};
|
|
428
461
|
let forDcbCommandTopic = (param, connect, memorySizeOpt, timeoutOpt, dcbCommandTopic) => {
|
|
@@ -433,7 +466,18 @@ function Make(EventCollectorChannel) {
|
|
|
433
466
|
param.specPath,
|
|
434
467
|
param.behaviorPath
|
|
435
468
|
]);
|
|
436
|
-
|
|
469
|
+
let inboundSlices = Stdlib_Array.filterMap(Object.values(registeredInboundSlices), reg => {
|
|
470
|
+
if (reg.specPath === "") {
|
|
471
|
+
return;
|
|
472
|
+
} else {
|
|
473
|
+
return {
|
|
474
|
+
specPath: reg.specPath,
|
|
475
|
+
translationPath: reg.translationPath,
|
|
476
|
+
auditTableName: reg.auditTableName
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
StateChangeSliceRuntime_Builder_Single$ReventlessAws.forDcbCommandTopic(slicePaths, inboundSlices, dcbConfig.dcbTableName, dcbConfig.pluginName, syncStateChangesConfigRef.contents, asyncStateChangesConfigRef.contents, memorySize, timeout, connect, dcbCommandTopic);
|
|
437
481
|
};
|
|
438
482
|
let finish = () => {};
|
|
439
483
|
return {
|
|
@@ -452,6 +496,9 @@ export {
|
|
|
452
496
|
dcbConfigRef,
|
|
453
497
|
registeredSliceModulePaths,
|
|
454
498
|
registerStateChangeSliceSpec,
|
|
499
|
+
registeredInboundSlices,
|
|
500
|
+
registerInboundTranslationSliceSpec,
|
|
501
|
+
registerInboundAuditTableName,
|
|
455
502
|
registerDcbConfig,
|
|
456
503
|
registerDcbTableName,
|
|
457
504
|
heartbeatConfigRef,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// CI routing guard for the DCB CommandTopic Lambda's Route 0 (InboundTranslation).
|
|
2
|
+
//
|
|
3
|
+
// The AppSync resolver for an InboundTranslationSlice mutation invokes the shared
|
|
4
|
+
// DCB command Lambda with `{__inboundTranslation, fieldName, arguments}` — no
|
|
5
|
+
// `command`, no `Records`. Before Route 0 existed the payload fell through to the
|
|
6
|
+
// SQS route and crashed on `event.records` being undefined ("Cannot read
|
|
7
|
+
// properties of undefined (reading 'length')"), so every inbound mutation 500'd
|
|
8
|
+
// on a deployed stack (docs/plans/done/aws-inbound-translation-lambda-routing.md).
|
|
9
|
+
// The gap that let this ship: `__inboundTranslation` was only ever asserted on the
|
|
10
|
+
// sending side (the resolver-template test). This pins the receiving end.
|
|
11
|
+
//
|
|
12
|
+
// Drives the real `buildHandlersForConfig` with an inbound slice module and a stub
|
|
13
|
+
// loader, then dispatches the payload exactly as `handler`'s Route 0 does. The
|
|
14
|
+
// fixture translation rejects its input, so the receiver returns CommandRejected
|
|
15
|
+
// inline without touching SQS or DynamoDB — no Docker needed, runs in CI.
|
|
16
|
+
|
|
17
|
+
open JestGlobals
|
|
18
|
+
|
|
19
|
+
let routeInbound: JSON.t => promise<JSON.t> = %raw(`
|
|
20
|
+
async (event) => {
|
|
21
|
+
const { buildHandlersForConfig } = await import(
|
|
22
|
+
"@reventlessdev/reventless-aws/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs"
|
|
23
|
+
);
|
|
24
|
+
const loadModule = async (specifier) => {
|
|
25
|
+
if (specifier === "ep-inbound-test://spec") return await import("./EpInboundTestSlice.res.mjs");
|
|
26
|
+
if (specifier === "ep-inbound-test://translation") return await import("./EpInboundTestSliceTranslation.res.mjs");
|
|
27
|
+
throw new Error("unknown test specifier: " + specifier);
|
|
28
|
+
};
|
|
29
|
+
const config = {
|
|
30
|
+
pluginName: "EpInboundTestPlugin",
|
|
31
|
+
dcbEventLogTableName: "ep-inbound-test-table",
|
|
32
|
+
stateChangeSliceModules: [],
|
|
33
|
+
queueUrl: "https://sqs.eu-west-1.amazonaws.com/000000000000/ep-inbound-test-queue",
|
|
34
|
+
inboundTranslationSliceModules: [
|
|
35
|
+
{ spec: "ep-inbound-test://spec", translation: "ep-inbound-test://translation", auditTableName: null },
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
const [,,,, inboundReceivers] = await buildHandlersForConfig(config, { loadModule });
|
|
39
|
+
// Mirror handler's Route 0 dispatch.
|
|
40
|
+
const receiver = (inboundReceivers || {})[event.fieldName];
|
|
41
|
+
if (receiver === undefined) throw new Error("no inbound receiver for " + event.fieldName);
|
|
42
|
+
return await receiver(event.arguments);
|
|
43
|
+
}
|
|
44
|
+
`)
|
|
45
|
+
|
|
46
|
+
let inboundEvent = (~fieldName, ~currency): JSON.t => {
|
|
47
|
+
let arguments = Dict.fromArray([
|
|
48
|
+
("sku", "SKU-1"->JSON.Encode.string),
|
|
49
|
+
("currency", currency->JSON.Encode.string),
|
|
50
|
+
])
|
|
51
|
+
Dict.fromArray([
|
|
52
|
+
("__inboundTranslation", true->JSON.Encode.bool),
|
|
53
|
+
("fieldName", fieldName->JSON.Encode.string),
|
|
54
|
+
("arguments", arguments->JSON.Encode.object),
|
|
55
|
+
])->JSON.Encode.object
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("DcbCommandTopicEntryPoint Route 0 (InboundTranslation)", () => {
|
|
59
|
+
test(
|
|
60
|
+
"routes an __inboundTranslation payload to the slice's receive and encodes the outcome",
|
|
61
|
+
async () => {
|
|
62
|
+
// fieldName is `${pluginName}_${specName}` — the same string
|
|
63
|
+
// Api_Naming.sliceMutationField produces at deploy time.
|
|
64
|
+
let event = inboundEvent(~fieldName="EpInboundTestPlugin_EpInboundTest", ~currency="EUR")
|
|
65
|
+
let outcome = await routeInbound(event)
|
|
66
|
+
let s = outcome->JSON.stringifyAny->Option.getOr("<unserializable>")
|
|
67
|
+
// A serializable outcome at all proves the payload no longer crashes on the
|
|
68
|
+
// SQS route; the rejection proves it ran the slice's translate.
|
|
69
|
+
expect(s->String.includes("CommandRejected"))->toBe(true)
|
|
70
|
+
expect(s->String.includes("Unsupported currency"))->toBe(true)
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
|
|
5
|
+
let routeInbound = (async (event) => {
|
|
6
|
+
const { buildHandlersForConfig } = await import(
|
|
7
|
+
"@reventlessdev/reventless-aws/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs"
|
|
8
|
+
);
|
|
9
|
+
const loadModule = async (specifier) => {
|
|
10
|
+
if (specifier === "ep-inbound-test://spec") return await import("./EpInboundTestSlice.res.mjs");
|
|
11
|
+
if (specifier === "ep-inbound-test://translation") return await import("./EpInboundTestSliceTranslation.res.mjs");
|
|
12
|
+
throw new Error("unknown test specifier: " + specifier);
|
|
13
|
+
};
|
|
14
|
+
const config = {
|
|
15
|
+
pluginName: "EpInboundTestPlugin",
|
|
16
|
+
dcbEventLogTableName: "ep-inbound-test-table",
|
|
17
|
+
stateChangeSliceModules: [],
|
|
18
|
+
queueUrl: "https://sqs.eu-west-1.amazonaws.com/000000000000/ep-inbound-test-queue",
|
|
19
|
+
inboundTranslationSliceModules: [
|
|
20
|
+
{ spec: "ep-inbound-test://spec", translation: "ep-inbound-test://translation", auditTableName: null },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
const [,,,, inboundReceivers] = await buildHandlersForConfig(config, { loadModule });
|
|
24
|
+
// Mirror handler's Route 0 dispatch.
|
|
25
|
+
const receiver = (inboundReceivers || {})[event.fieldName];
|
|
26
|
+
if (receiver === undefined) throw new Error("no inbound receiver for " + event.fieldName);
|
|
27
|
+
return await receiver(event.arguments);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function inboundEvent(fieldName, currency) {
|
|
31
|
+
let $$arguments = Object.fromEntries([
|
|
32
|
+
[
|
|
33
|
+
"sku",
|
|
34
|
+
"SKU-1"
|
|
35
|
+
],
|
|
36
|
+
[
|
|
37
|
+
"currency",
|
|
38
|
+
currency
|
|
39
|
+
]
|
|
40
|
+
]);
|
|
41
|
+
return Object.fromEntries([
|
|
42
|
+
[
|
|
43
|
+
"__inboundTranslation",
|
|
44
|
+
true
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
"fieldName",
|
|
48
|
+
fieldName
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
"arguments",
|
|
52
|
+
$$arguments
|
|
53
|
+
]
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
globalThis.describe("DcbCommandTopicEntryPoint Route 0 (InboundTranslation)", () => {
|
|
58
|
+
globalThis.test("routes an __inboundTranslation payload to the slice's receive and encodes the outcome", async () => {
|
|
59
|
+
let event = inboundEvent("EpInboundTestPlugin_EpInboundTest", "EUR");
|
|
60
|
+
let outcome = await routeInbound(event);
|
|
61
|
+
let s = Stdlib_Option.getOr(JSON.stringify(outcome), "<unserializable>");
|
|
62
|
+
globalThis.expect(s.includes("CommandRejected")).toBe(true);
|
|
63
|
+
globalThis.expect(s.includes("Unsupported currency")).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
routeInbound,
|
|
69
|
+
inboundEvent,
|
|
70
|
+
}
|
|
71
|
+
/* routeInbound Not a pure module */
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Minimal InboundTranslationSlice spec fixture for the DCB entry-point Route 0
|
|
2
|
+
// routing test (DcbInboundTranslationRoutingTest). Hand-written — not a plugin
|
|
3
|
+
// component, so no folder-based ppx; only the fields
|
|
4
|
+
// InboundTranslationSlice_Callback.Make reads at runtime are provided.
|
|
5
|
+
|
|
6
|
+
let name = "EpInboundTest"
|
|
7
|
+
let moduleUrl = "ep-inbound-test://spec"
|
|
8
|
+
|
|
9
|
+
@schema
|
|
10
|
+
type externalInput = {
|
|
11
|
+
sku: string,
|
|
12
|
+
currency: string,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@schema
|
|
16
|
+
type command = AddThing({thingId: string})
|
|
17
|
+
|
|
18
|
+
let targetName = "AddThing"
|
|
19
|
+
let externalSystem: option<string> = Some("TestFeed")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
|
|
5
|
+
let externalInputSchema = S.schema(s => ({
|
|
6
|
+
sku: s.m(S.string),
|
|
7
|
+
currency: s.m(S.string)
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
let commandSchema = S.schema(s => ({
|
|
11
|
+
TAG: "AddThing",
|
|
12
|
+
thingId: s.m(S.string)
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
let name = "EpInboundTest";
|
|
16
|
+
|
|
17
|
+
let moduleUrl = "ep-inbound-test://spec";
|
|
18
|
+
|
|
19
|
+
let targetName = "AddThing";
|
|
20
|
+
|
|
21
|
+
let externalSystem = "TestFeed";
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
name,
|
|
25
|
+
moduleUrl,
|
|
26
|
+
externalInputSchema,
|
|
27
|
+
commandSchema,
|
|
28
|
+
targetName,
|
|
29
|
+
externalSystem,
|
|
30
|
+
}
|
|
31
|
+
/* externalInputSchema Not a pure module */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Translation fixture paired with EpInboundTestSlice. Rejects non-USD input so the
|
|
2
|
+
// routing test exercises the receive reject path — no command is published, so the
|
|
3
|
+
// test needs neither SQS nor DynamoDB.
|
|
4
|
+
|
|
5
|
+
module Spec = EpInboundTestSlice
|
|
6
|
+
open Spec
|
|
7
|
+
|
|
8
|
+
let moduleUrl = "ep-inbound-test://translation"
|
|
9
|
+
|
|
10
|
+
let translate = (input: externalInput): result<array<(string, command)>, string> =>
|
|
11
|
+
input.currency !== "USD"
|
|
12
|
+
? Error("Unsupported currency: " ++ input.currency)
|
|
13
|
+
: Ok([(input.sku, AddThing({thingId: input.sku}))])
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function translate(input) {
|
|
5
|
+
if (input.currency !== "USD") {
|
|
6
|
+
return {
|
|
7
|
+
TAG: "Error",
|
|
8
|
+
_0: "Unsupported currency: " + input.currency
|
|
9
|
+
};
|
|
10
|
+
} else {
|
|
11
|
+
return {
|
|
12
|
+
TAG: "Ok",
|
|
13
|
+
_0: [[
|
|
14
|
+
input.sku,
|
|
15
|
+
{
|
|
16
|
+
TAG: "AddThing",
|
|
17
|
+
thingId: input.sku
|
|
18
|
+
}
|
|
19
|
+
]]
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let Spec;
|
|
25
|
+
|
|
26
|
+
let moduleUrl = "ep-inbound-test://translation";
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
Spec,
|
|
30
|
+
moduleUrl,
|
|
31
|
+
translate,
|
|
32
|
+
}
|
|
33
|
+
/* No side effect */
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// Heartbeat Lambda handler.
|
|
2
|
-
// Publishes a Heartbeat(timeout) command to the PluginExtensionPoint CommandTopic.
|
|
3
|
-
// Triggered by CloudWatch Events on a schedule.
|
|
4
|
-
|
|
5
|
-
import { reverseConvertToJsonOrThrow } from "sury/src/S.res.mjs";
|
|
6
|
-
import { makeQueueRef } from "./HandlerFactoryHelpers.mjs";
|
|
7
|
-
import { uuid } from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
8
|
-
import { name as pluginExtensionPointName, commandSchema } from "@reventlessdev/reventless-infra/src/types/PluginExtensionPointSpec.res.mjs";
|
|
9
|
-
import { publishJsons as sqsPublishJsons } from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
10
|
-
|
|
11
|
-
// === Initialize eagerly at module load (Lambda cold start) ===
|
|
12
|
-
|
|
13
|
-
const epQueueUrl = process.env["EP_QUEUE_URL"] || "";
|
|
14
|
-
const pluginId = process.env["PLUGIN_ID"] || "";
|
|
15
|
-
const timeout = parseInt(process.env["HEARTBEAT_TIMEOUT"], 10) || 10;
|
|
16
|
-
|
|
17
|
-
const publishJsons = sqsPublishJsons(makeQueueRef(epQueueUrl), "SQS_FIFO");
|
|
18
|
-
|
|
19
|
-
// === Exported handler ===
|
|
20
|
-
|
|
21
|
-
export async function handler(_event, _context) {
|
|
22
|
-
const msgId = uuid();
|
|
23
|
-
const commandJson = reverseConvertToJsonOrThrow({ TAG: "Heartbeat", _0: timeout }, commandSchema);
|
|
24
|
-
const message = {
|
|
25
|
-
id: pluginId,
|
|
26
|
-
meta: {
|
|
27
|
-
service: pluginExtensionPointName,
|
|
28
|
-
time: new Date().toISOString(),
|
|
29
|
-
ip: "",
|
|
30
|
-
user: "Heartbeat",
|
|
31
|
-
msgId,
|
|
32
|
-
correlationId: msgId,
|
|
33
|
-
},
|
|
34
|
-
commandJson,
|
|
35
|
-
};
|
|
36
|
-
await publishJsons([message]);
|
|
37
|
-
return "";
|
|
38
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
// PluginExtensionPoint Lambda entry point.
|
|
2
|
-
// All imports are framework packages — no user modules needed.
|
|
3
|
-
// Handles Heartbeat, Cloner, and other plugin-level extension point commands.
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
patchSpecId,
|
|
7
|
-
makeQueueRef,
|
|
8
|
-
scanByTableName,
|
|
9
|
-
log,
|
|
10
|
-
runEffect,
|
|
11
|
-
setRequestId,
|
|
12
|
-
extractMetaField,
|
|
13
|
-
extractSentTimestamp,
|
|
14
|
-
extractRetryCount,
|
|
15
|
-
} from "./HandlerFactoryHelpers.mjs";
|
|
16
|
-
import { sendMessage } from "@reventlessdev/reventless-aws/src/plugin/runtime/Util_PluginMessage_Runtime.res.mjs";
|
|
17
|
-
import * as PluginExtensionPointSpec from "@reventlessdev/reventless-infra/src/types/PluginExtensionPointSpec.res.mjs";
|
|
18
|
-
import { Make as pluginEPPluginMake } from "@reventlessdev/reventless-core/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs";
|
|
19
|
-
import { Make as extensionPointCallbackMake } from "@reventlessdev/reventless-core/src/components/ExtensionPoint/ExtensionPoint_Callback.res.mjs";
|
|
20
|
-
import { Make as commandTopicCallbackMake } from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Callback.res.mjs";
|
|
21
|
-
import { handleQueueEvent, publishJsons as sqsPublishJsons } from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
22
|
-
import { createSchedule as cwCreateSchedule, deleteSchedule as cwDeleteSchedule } from "@reventlessdev/reventless-aws/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents_Runtime.res.mjs";
|
|
23
|
-
|
|
24
|
-
function buildHandler() {
|
|
25
|
-
const configStr = process.env["HANDLER_CONFIG"] || "{}";
|
|
26
|
-
const config = JSON.parse(configStr);
|
|
27
|
-
|
|
28
|
-
const patchedSpec = patchSpecId(PluginExtensionPointSpec);
|
|
29
|
-
|
|
30
|
-
// Reconstruct runtimeOps — only sendMessageToChannel is used by this
|
|
31
|
-
// Lambda's incoming-command path (ForwardCommand). Cross-plugin
|
|
32
|
-
// subscribe / unsubscribe directives were retired in Phase 3 Step 3.
|
|
33
|
-
const runtimeOps = {
|
|
34
|
-
messagePublish: { sendMessageToChannel: sendMessage },
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Instantiate Plugin EP mapping. updateApiSchema and manageSubscriptions are
|
|
38
|
-
// admin-only hooks; this Lambda only handles incoming commands (Heartbeat,
|
|
39
|
-
// ForwardCommand) so they stay undefined here.
|
|
40
|
-
//
|
|
41
|
-
// `environment` prefixes the disconnect schedule's EventBridge rule name, so it
|
|
42
|
-
// must be stable across deploys and unique per stack. The stack name is both;
|
|
43
|
-
// AWS_LAMBDA_FUNCTION_NAME is neither — it carries a content hash, so replacing
|
|
44
|
-
// this Lambda would orphan every outstanding rule the previous generation
|
|
45
|
-
// created. EventCollectorEntryPoint instantiates the same EP module and must
|
|
46
|
-
// agree, or one Lambda creates rules the other cannot delete.
|
|
47
|
-
const environment = process.env["Environment"] || "unknown";
|
|
48
|
-
const pluginModule = pluginEPPluginMake({
|
|
49
|
-
runtimeOps,
|
|
50
|
-
environment,
|
|
51
|
-
updateApiSchema: undefined,
|
|
52
|
-
manageSubscriptions: undefined,
|
|
53
|
-
});
|
|
54
|
-
const mappingsModule = { mappings: [pluginModule.Mapping] };
|
|
55
|
-
|
|
56
|
-
// Reconstruct publishToAggregates. The deploy-side builder writes
|
|
57
|
-
// HANDLER_CONFIG.publishToAggregates as { aggregateName: envVarName }
|
|
58
|
-
// (see PluginExtensionPointRuntime_Builder.res), so iterate accordingly.
|
|
59
|
-
const publishToAggregates = {};
|
|
60
|
-
for (const [aggName, envVarName] of Object.entries(config.publishToAggregates || {})) {
|
|
61
|
-
const queueUrl = process.env[envVarName] || "";
|
|
62
|
-
publishToAggregates[aggName] = sqsPublishJsons(makeQueueRef(queueUrl), "SQS_FIFO");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Reconstruct queryEngine
|
|
66
|
-
const queryEngine = {
|
|
67
|
-
scan: (readModelName, filterConfigs, limit) => scanByTableName(config.pluginReadModelTableName, filterConfigs, limit),
|
|
68
|
-
query: async () => { throw new Error("QueryEngine.query not available in bundled Plugin EP handler"); },
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Reconstruct scheduler
|
|
72
|
-
const scheduler = {
|
|
73
|
-
createSchedule: cwCreateSchedule(config.schedulerRoleArn),
|
|
74
|
-
deleteSchedule: cwDeleteSchedule,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
// CommandTopic resources for scheduler targets
|
|
78
|
-
const commandTopicResources = config.schedulerQueueArn !== ""
|
|
79
|
-
? [{
|
|
80
|
-
name: config.schedulerQueueName,
|
|
81
|
-
id: config.schedulerQueueName,
|
|
82
|
-
urn: config.schedulerQueueArn,
|
|
83
|
-
service: "unknown",
|
|
84
|
-
resourceInfo: "NoInfo",
|
|
85
|
-
role: "",
|
|
86
|
-
region: "",
|
|
87
|
-
resourceType: "",
|
|
88
|
-
configuration: {},
|
|
89
|
-
tags: {},
|
|
90
|
-
}]
|
|
91
|
-
: [];
|
|
92
|
-
|
|
93
|
-
const invalidNameChars = /[^.\-_a-zA-Z0-9]/g;
|
|
94
|
-
const resourceNaming = {
|
|
95
|
-
validateName: (n) => n.replace(invalidNameChars, "_"),
|
|
96
|
-
urnName: (arn) => { const parts = arn.split(":"); return parts[5] || "unknown"; },
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const callbackConfig = { publishToAggregates, commandTopicResources, scheduler, queryEngine, resourceNaming };
|
|
100
|
-
const callback = extensionPointCallbackMake(callbackConfig)(patchedSpec)(mappingsModule);
|
|
101
|
-
const commandTopicCallback = commandTopicCallbackMake(patchedSpec)({
|
|
102
|
-
Spec: patchedSpec,
|
|
103
|
-
commandsHandler: callback.handleIncomingCommands,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
sqsHandler: handleQueueEvent(makeQueueRef(config.queueUrl), commandTopicCallback.handleJsonCommands),
|
|
108
|
-
comp: `ExtensionPoint(${patchedSpec.name})`,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const { sqsHandler, comp } = buildHandler();
|
|
113
|
-
|
|
114
|
-
export async function handler(event, context) {
|
|
115
|
-
setRequestId(context?.awsRequestId);
|
|
116
|
-
const records = event.Records || [];
|
|
117
|
-
|
|
118
|
-
log.debug("processing " + records.length.toString() + " record(s)", { comp: "PluginExtensionPointRuntime" });
|
|
119
|
-
await runEffect(sqsHandler(event, context), {
|
|
120
|
-
correlationId: extractMetaField(records, "correlationId"),
|
|
121
|
-
causationId: extractMetaField(records, "causationId"),
|
|
122
|
-
comp,
|
|
123
|
-
timestamp: extractSentTimestamp(records),
|
|
124
|
-
retryCount: extractRetryCount(records),
|
|
125
|
-
});
|
|
126
|
-
return "";
|
|
127
|
-
}
|