@reventlessdev/reventless-aws 3.0.0-alpha.201 → 3.0.0-alpha.203
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 +14 -0
- package/package.json +7 -7
- package/src/Platform.res +129 -25
- package/src/Platform.res.mjs +210 -102
- package/src/adapter/Api/ApiFragmentDeregistration.res +5 -4
- package/src/adapter/Api/ApiFragmentDeregistration.res.mjs +2 -2
- package/src/adapter/Api/ApiSchemaPush.res +54 -0
- package/src/adapter/Api/ApiSchemaPush.res.mjs +37 -0
- package/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs +197 -0
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +47 -0
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +34 -0
- package/src/components/SideEffectHandler_Single.res +8 -0
- package/src/components/SideEffectHandler_Single.res.mjs +5 -2
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Runtime-pure push engine for the admin ApiSchemaPush SideEffect
|
|
2
|
+
// (docs/plans/event-sourced-fragment-registries.md § Reactive writer design).
|
|
3
|
+
//
|
|
4
|
+
// The ApiFragmentRegistry singleton aggregate's behaviour emits ApiSchemaComputed
|
|
5
|
+
// { snapshot } — the WHOLE per-plugin fragment set folded from the aggregate's own
|
|
6
|
+
// consistent single-partition state after each change. The ApiSchemaPush SideEffect
|
|
7
|
+
// (compiled from ApiSchemaPush.res) subscribes to that event via the aggregate's
|
|
8
|
+
// DynamoDB stream (single-shard for a singleton → naturally serialized, no concurrent
|
|
9
|
+
// StartSchemaCreation), and hands the snapshot here.
|
|
10
|
+
//
|
|
11
|
+
// This module stitches one AWS-decorated schema per target API from the snapshot
|
|
12
|
+
// (identical decoration to the deploy path via the runtime-pure
|
|
13
|
+
// AppSync_SdlDecorate.planAwsPushes), pushes each behind the catastrophic-shrink
|
|
14
|
+
// guard, and writes the outcome back with RecordApiFragmentPush onto the
|
|
15
|
+
// ApiFragmentRegistry aggregate's command topic so the deploy waiter can poll
|
|
16
|
+
// Platform_ApiFragments.
|
|
17
|
+
//
|
|
18
|
+
// Runtime-purity discipline: NO @pulumi imports (this loads in the SideEffectHandler
|
|
19
|
+
// Lambda — see reference_pulumi_leaks_into_lambda_runtime_graph). All config is read
|
|
20
|
+
// from env at invocation time; the AppSync push uses the AppSync SDK directly.
|
|
21
|
+
|
|
22
|
+
import { makeQueueRef, log } from "./HandlerFactoryHelpers.mjs";
|
|
23
|
+
import { publishJsons as sqsPublishJsons } from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
24
|
+
import { planAwsPushes } from "@reventlessdev/reventless-aws/src/components/Api/AppSync_SdlDecorate.res.mjs";
|
|
25
|
+
import {
|
|
26
|
+
countRootTypeFields,
|
|
27
|
+
isCatastrophicSchemaShrink,
|
|
28
|
+
} from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
29
|
+
import {
|
|
30
|
+
baseFragment as adminBaseFragment,
|
|
31
|
+
systemCallerFieldNames,
|
|
32
|
+
} from "@reventlessdev/reventless-core/src/admin/AdminApi.res.mjs";
|
|
33
|
+
|
|
34
|
+
const COMP = "apiSchemaPush";
|
|
35
|
+
|
|
36
|
+
// ── AppSync push primitives (mirror AdminEventCollectorEntryPoint.mjs) ──────────
|
|
37
|
+
|
|
38
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
39
|
+
|
|
40
|
+
// Push the SDL. Bounded retry on the AppSync API-level lock ("Schema is currently being
|
|
41
|
+
// altered" / ConcurrentModification) — the legacy connect-driven mkUpdateApiSchema still
|
|
42
|
+
// pushes until Plan Phase 4, and a standalone-service push can also contend. The lock
|
|
43
|
+
// clears in seconds, so this is NOT the plan's rejected lagging-read retry (that had no
|
|
44
|
+
// ceiling). The singleton aggregate's single-shard stream already serializes THIS writer
|
|
45
|
+
// against itself; this only covers cross-writer contention.
|
|
46
|
+
async function updateAppSyncSchema(apiId, sdl) {
|
|
47
|
+
const { AppSyncClient, StartSchemaCreationCommand } = await import("@aws-sdk/client-appsync");
|
|
48
|
+
const client = new AppSyncClient({});
|
|
49
|
+
const maxAttempts = 6;
|
|
50
|
+
for (let attempt = 1; ; attempt++) {
|
|
51
|
+
try {
|
|
52
|
+
await client.send(new StartSchemaCreationCommand({ apiId, definition: sdl }));
|
|
53
|
+
return;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
const msg = (e && e.message) || String(e);
|
|
56
|
+
const locked = /being altered|ConcurrentModification|currently in the process/i.test(msg);
|
|
57
|
+
if (!locked || attempt >= maxAttempts) throw e;
|
|
58
|
+
const backoff = Math.min(1000 * 2 ** (attempt - 1), 8000);
|
|
59
|
+
log.warn(`schema push contended (${msg}) — retry ${attempt}/${maxAttempts} in ${backoff}ms`, { comp: COMP });
|
|
60
|
+
await sleep(backoff);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Current live schema as SDL for the shrink guard. "" (not error) when the API has
|
|
66
|
+
// no schema yet or introspection fails — the caller treats "" as "no baseline".
|
|
67
|
+
async function getCurrentSchemaSdl(apiId) {
|
|
68
|
+
try {
|
|
69
|
+
const { AppSyncClient, GetIntrospectionSchemaCommand } = await import("@aws-sdk/client-appsync");
|
|
70
|
+
const client = new AppSyncClient({});
|
|
71
|
+
const resp = await client.send(new GetIntrospectionSchemaCommand({ apiId, format: "SDL" }));
|
|
72
|
+
if (!resp || !resp.schema) return "";
|
|
73
|
+
return Buffer.from(resp.schema).toString("utf-8");
|
|
74
|
+
} catch (e) {
|
|
75
|
+
log.warn(`could not introspect current schema (${(e && e.message) || e}) — skipping shrink guard`, { comp: COMP });
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseShrinkThreshold(raw) {
|
|
81
|
+
const n = raw ? Number(raw) : NaN;
|
|
82
|
+
return Number.isFinite(n) && n > 0 && n < 1 ? n : 0.5;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Emit the SchemaShrinkRejected CloudWatch metric via EMF (raw console.log — must
|
|
86
|
+
// stay unwrapped so CloudWatch's EMF auto-detect fires).
|
|
87
|
+
function emitShrinkRejectionMetric(apiId, currentRootFields, newRootFields) {
|
|
88
|
+
try {
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.log(
|
|
91
|
+
JSON.stringify({
|
|
92
|
+
_aws: {
|
|
93
|
+
Timestamp: Date.now(),
|
|
94
|
+
CloudWatchMetrics: [
|
|
95
|
+
{
|
|
96
|
+
Namespace: "Reventless/Runtime",
|
|
97
|
+
Dimensions: [["ApiId"]],
|
|
98
|
+
Metrics: [{ Name: "SchemaShrinkRejected", Unit: "Count" }],
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
ApiId: apiId,
|
|
103
|
+
SchemaShrinkRejected: 1,
|
|
104
|
+
currentRootFields,
|
|
105
|
+
newRootFields,
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
} catch {
|
|
109
|
+
// metric emission is best-effort
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Config (deploy-derived, injected as Lambda env by the admin SideEffectHandler) ──
|
|
114
|
+
|
|
115
|
+
function readConfig() {
|
|
116
|
+
const na = (v) => (v && v !== "NOT_AVAILABLE" ? v : "");
|
|
117
|
+
const domainApiId = na(process.env["API_SCHEMA_PUSH_DOMAIN_API_ID"]);
|
|
118
|
+
const platformApiId = na(process.env["API_SCHEMA_PUSH_PLATFORM_API_ID"]) || domainApiId;
|
|
119
|
+
return {
|
|
120
|
+
domainApiId,
|
|
121
|
+
platformApiId,
|
|
122
|
+
splitApi: process.env["API_SCHEMA_PUSH_SPLIT_API"] === "true",
|
|
123
|
+
clonerEnabled: process.env["API_SCHEMA_PUSH_CLONER"] === "true",
|
|
124
|
+
cmdTopicUrl: na(process.env["API_SCHEMA_PUSH_CMD_TOPIC_URL"]),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Write RecordApiFragmentPush back per plugin onto the ApiFragmentRegistry aggregate's
|
|
129
|
+
// command topic (FIFO, singleton id="registry"). @noApi + idempotent — a no-op in the
|
|
130
|
+
// aggregate behaviour if the plugin was deregistered in the meantime.
|
|
131
|
+
async function recordPushOutcomes(cmdTopicUrl, pluginIds, ok, message) {
|
|
132
|
+
if (!cmdTopicUrl || pluginIds.length === 0) return;
|
|
133
|
+
const publisher = sqsPublishJsons(makeQueueRef(cmdTopicUrl), "SQS_FIFO");
|
|
134
|
+
const at = new Date().toISOString();
|
|
135
|
+
const commandJsons = pluginIds.map((pluginId) => ({
|
|
136
|
+
id: "registry",
|
|
137
|
+
meta: { service: "ApiFragmentRegistry", time: at, msgId: "pending", correlationId: pluginId },
|
|
138
|
+
commandJson: { TAG: "RecordApiFragmentPush", pluginId, ok, message, at },
|
|
139
|
+
}));
|
|
140
|
+
try {
|
|
141
|
+
await publisher(commandJsons);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
log.error(`RecordApiFragmentPush dispatch failed: ${(e && e.message) || e}`, { comp: COMP });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Entry point ────────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
// snapshot: array of { pluginId, encoded, protocol, apiTarget } — the consistent
|
|
150
|
+
// registry contents after the triggering change, carried on the ApiSchemaComputed event.
|
|
151
|
+
export async function pushApiSchema(snapshot) {
|
|
152
|
+
const cfg = readConfig();
|
|
153
|
+
const entries = Array.isArray(snapshot) ? snapshot.filter((e) => e && typeof e.encoded === "string" && e.encoded) : [];
|
|
154
|
+
const pluginIds = [...new Set(entries.map((e) => e.pluginId).filter((p) => typeof p === "string"))];
|
|
155
|
+
if (!cfg.cmdTopicUrl) {
|
|
156
|
+
log.warn("ApiSchemaPush: no command-topic URL configured — skipping", { comp: COMP });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Fragments straight from the consistent snapshot — NO eventually-consistent read.
|
|
161
|
+
const fragments = entries.map((e) => ({
|
|
162
|
+
encoded: e.encoded,
|
|
163
|
+
protocol: typeof e.protocol === "string" ? e.protocol : "graphql",
|
|
164
|
+
target: e.apiTarget === "Platform" ? "Platform" : "Domain",
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
const rawAdminBase = adminBaseFragment(cfg.clonerEnabled);
|
|
168
|
+
const plans = planAwsPushes(rawAdminBase, systemCallerFieldNames, fragments, cfg.splitApi);
|
|
169
|
+
|
|
170
|
+
let ok = true;
|
|
171
|
+
let message = "";
|
|
172
|
+
for (const plan of plans) {
|
|
173
|
+
const apiId = plan.api === "PlatformApi" ? cfg.platformApiId : cfg.domainApiId;
|
|
174
|
+
if (!apiId) continue;
|
|
175
|
+
const threshold = parseShrinkThreshold(process.env["RUNTIME_SCHEMA_SHRINK_THRESHOLD"]);
|
|
176
|
+
const currentSdl = await getCurrentSchemaSdl(apiId);
|
|
177
|
+
if (isCatastrophicSchemaShrink(currentSdl, plan.sdl, threshold)) {
|
|
178
|
+
const cur = countRootTypeFields(currentSdl, "Mutation") + countRootTypeFields(currentSdl, "Query");
|
|
179
|
+
const nw = countRootTypeFields(plan.sdl, "Mutation") + countRootTypeFields(plan.sdl, "Query");
|
|
180
|
+
log.error(`ABORTED schema push for ${plan.api} (${apiId}): ${nw} root field(s) vs ${cur} live (threshold ${threshold}).`, { comp: COMP });
|
|
181
|
+
emitShrinkRejectionMetric(apiId, cur, nw);
|
|
182
|
+
ok = false;
|
|
183
|
+
message = `shrink guard aborted push for ${plan.api}`;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
await updateAppSyncSchema(apiId, plan.sdl);
|
|
188
|
+
log.info(`schema push OK: ${plan.api} (${apiId})`, { comp: COMP });
|
|
189
|
+
} catch (e) {
|
|
190
|
+
ok = false;
|
|
191
|
+
message = (e && e.message) || String(e);
|
|
192
|
+
log.error(`schema push FAILED: ${plan.api} (${apiId}): ${message}`, { comp: COMP });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
await recordPushOutcomes(cfg.cmdTopicUrl, pluginIds, ok, message);
|
|
197
|
+
}
|
|
@@ -15,6 +15,15 @@ let sideEffectInfos: dict<sideEffectInfo> = Dict.make()
|
|
|
15
15
|
let registerSideEffectHandler = (~sideEffectHandlerName, ~sideEffectModulePaths) =>
|
|
16
16
|
sideEffectInfos->Dict.set(sideEffectHandlerName, {sideEffectModulePaths: sideEffectModulePaths})
|
|
17
17
|
|
|
18
|
+
// Extra Lambda env vars contributed by bespoke side effects (e.g. admin ApiSchemaPush).
|
|
19
|
+
// The shared "AllSideEffectHandlers" Lambda is built once in finish(); all registered
|
|
20
|
+
// entries are merged onto its env there. Deploy-derived config only — never overrides
|
|
21
|
+
// HANDLER_CONFIG.
|
|
22
|
+
let extraEnvVarsAll: dict<Pulumi.Input.t<string>> = Dict.make()
|
|
23
|
+
|
|
24
|
+
let registerExtraEnv = (~extraEnvVars: dict<Pulumi.Input.t<string>>) =>
|
|
25
|
+
extraEnvVars->Dict.forEachWithKey((v, k) => extraEnvVarsAll->Dict.set(k, v))
|
|
26
|
+
|
|
18
27
|
type storedSpec = {
|
|
19
28
|
componentName: string,
|
|
20
29
|
parentResource: Pulumi.Resource.t,
|
|
@@ -143,6 +152,12 @@ let finish = () =>
|
|
|
143
152
|
|
|
144
153
|
let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
|
|
145
154
|
envVars->Dict.set("HANDLER_CONFIG", handlerConfigOutput->Pulumi.Output.asInput)
|
|
155
|
+
// Merge bespoke side-effect config (never overrides HANDLER_CONFIG).
|
|
156
|
+
extraEnvVarsAll->Dict.forEachWithKey((v, k) =>
|
|
157
|
+
if k != "HANDLER_CONFIG" {
|
|
158
|
+
envVars->Dict.set(k, v)
|
|
159
|
+
}
|
|
160
|
+
)
|
|
146
161
|
|
|
147
162
|
// Build AssetArchive: static re-export + user packages
|
|
148
163
|
let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
@@ -161,6 +176,38 @@ let finish = () =>
|
|
|
161
176
|
~opts,
|
|
162
177
|
)
|
|
163
178
|
|
|
179
|
+
// The admin ApiSchemaPush side effect (the only extra-env contributor) pushes the
|
|
180
|
+
// stitched schema to AppSync via StartSchemaCreation and reads the live schema for
|
|
181
|
+
// the shrink guard. Grant those AppSync perms to the shared side-effect-handler
|
|
182
|
+
// Lambda role ONLY when a bespoke side effect registered config — Task-only
|
|
183
|
+
// deployments keep the narrow default perimeter. Mirrors the admin EventCollector's
|
|
184
|
+
// AllowAdminStartSchemaCreation grant (PluginRuntime_Builder).
|
|
185
|
+
if extraEnvVarsAll->Dict.keysToArray->Array.length > 0 {
|
|
186
|
+
let _ = PulumiAws.IAM.RolePolicy.make(
|
|
187
|
+
~name="AllSideEffectHandlers-appsyncSchemaPush",
|
|
188
|
+
~args={
|
|
189
|
+
policy: PulumiAws.PolicyDocument.make(
|
|
190
|
+
~id="AllSideEffectHandlersAppsyncSchemaPushPolicy",
|
|
191
|
+
~statements=[
|
|
192
|
+
{
|
|
193
|
+
sid: "AllowSideEffectStartSchemaCreation",
|
|
194
|
+
effect: Allow,
|
|
195
|
+
actions: Actions([
|
|
196
|
+
"appsync:StartSchemaCreation",
|
|
197
|
+
"appsync:GetSchemaCreationStatus",
|
|
198
|
+
"appsync:GetIntrospectionSchema",
|
|
199
|
+
]),
|
|
200
|
+
resources: AllResources,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
)
|
|
204
|
+
->PulumiAws.PolicyDocument.toJsonString
|
|
205
|
+
->Pulumi.Input.make,
|
|
206
|
+
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
164
211
|
let channelSpecs = storedSpecs->Array.map(({channelSpec}) => channelSpec)
|
|
165
212
|
let _connectResources = EventCollectorChannel.connect(
|
|
166
213
|
~name="AllSideEffectHandlers",
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Aws from "@pulumi/aws";
|
|
4
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
3
5
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
6
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
7
|
import * as Pulumi from "@pulumi/pulumi";
|
|
6
8
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
9
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
8
10
|
import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
|
|
11
|
+
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
9
12
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
10
13
|
import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
|
|
11
14
|
import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
|
|
@@ -20,6 +23,14 @@ function registerSideEffectHandler(sideEffectHandlerName, sideEffectModulePaths)
|
|
|
20
23
|
};
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
let extraEnvVarsAll = {};
|
|
27
|
+
|
|
28
|
+
function registerExtraEnv(extraEnvVars) {
|
|
29
|
+
Stdlib_Dict.forEachWithKey(extraEnvVars, (v, k) => {
|
|
30
|
+
extraEnvVarsAll[k] = v;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
let storedSpecs = [];
|
|
24
35
|
|
|
25
36
|
let grandParent = {
|
|
@@ -99,8 +110,29 @@ function finish() {
|
|
|
99
110
|
let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => `{"handlers":[` + handlers.join(",") + `]}`);
|
|
100
111
|
let envVars = {};
|
|
101
112
|
envVars["HANDLER_CONFIG"] = handlerConfigOutput;
|
|
113
|
+
Stdlib_Dict.forEachWithKey(extraEnvVarsAll, (v, k) => {
|
|
114
|
+
if (k !== "HANDLER_CONFIG") {
|
|
115
|
+
envVars[k] = v;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
102
119
|
let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/SideEffectEntryPoint.mjs", packageDirs, undefined);
|
|
103
120
|
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllSideEffectHandlers", "Reactor", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, undefined, opts);
|
|
121
|
+
if (Object.keys(extraEnvVarsAll).length !== 0) {
|
|
122
|
+
new (Aws.iam.RolePolicy)("AllSideEffectHandlers-appsyncSchemaPush", {
|
|
123
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, "AllSideEffectHandlersAppsyncSchemaPushPolicy", [{
|
|
124
|
+
Sid: "AllowSideEffectStartSchemaCreation",
|
|
125
|
+
Effect: "Allow",
|
|
126
|
+
Action: [
|
|
127
|
+
"appsync:StartSchemaCreation",
|
|
128
|
+
"appsync:GetSchemaCreationStatus",
|
|
129
|
+
"appsync:GetIntrospectionSchema"
|
|
130
|
+
],
|
|
131
|
+
Resource: "*"
|
|
132
|
+
}])),
|
|
133
|
+
role: runtime.parts.lambdaRole.id
|
|
134
|
+
});
|
|
135
|
+
}
|
|
104
136
|
let channelSpecs = storedSpecs.map(param => param.channelSpec);
|
|
105
137
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllSideEffectHandlers", channelSpecs, runtime, opts);
|
|
106
138
|
} else {
|
|
@@ -120,6 +152,8 @@ export {
|
|
|
120
152
|
log,
|
|
121
153
|
sideEffectInfos,
|
|
122
154
|
registerSideEffectHandler,
|
|
155
|
+
extraEnvVarsAll,
|
|
156
|
+
registerExtraEnv,
|
|
123
157
|
storedSpecs,
|
|
124
158
|
grandParent,
|
|
125
159
|
forEventCollector,
|
|
@@ -22,6 +22,7 @@ module Make = (): ReventlessCore.SideEffectHandler.T => {
|
|
|
22
22
|
~resourceNaming,
|
|
23
23
|
~memorySize=?,
|
|
24
24
|
~timeout=?,
|
|
25
|
+
~extraEnvVars=?,
|
|
25
26
|
~opts=?,
|
|
26
27
|
) => {
|
|
27
28
|
let component = Inner.make(
|
|
@@ -49,6 +50,13 @@ module Make = (): ReventlessCore.SideEffectHandler.T => {
|
|
|
49
50
|
~sideEffectModulePaths,
|
|
50
51
|
)
|
|
51
52
|
|
|
53
|
+
// Bespoke side effects (e.g. admin ApiSchemaPush) inject deploy-derived config as
|
|
54
|
+
// extra Lambda env vars, merged onto the shared side-effect-handler Lambda in finish().
|
|
55
|
+
switch extraEnvVars {
|
|
56
|
+
| Some(env) => EventCollectorRuntimeBuilder.registerExtraEnv(~extraEnvVars=env)
|
|
57
|
+
| None => ()
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
component
|
|
53
61
|
}
|
|
54
62
|
}
|
|
@@ -34,10 +34,13 @@ function Make($star) {
|
|
|
34
34
|
forEventCollector: SideEffectHandlerRuntime_Builder_Single$ReventlessAws.forEventCollector,
|
|
35
35
|
finish: SideEffectHandlerRuntime_Builder_Single$ReventlessAws.finish
|
|
36
36
|
});
|
|
37
|
-
let make = (name, sideEffects, allEventTopics, allCommandTopics, targets, queryEngine, scheduler, resourceNaming, memorySize, timeout, opts) => {
|
|
38
|
-
let component = Inner.make(name, sideEffects, allEventTopics, allCommandTopics, targets, queryEngine, scheduler, resourceNaming, memorySize, timeout, opts);
|
|
37
|
+
let make = (name, sideEffects, allEventTopics, allCommandTopics, targets, queryEngine, scheduler, resourceNaming, memorySize, timeout, extraEnvVars, opts) => {
|
|
38
|
+
let component = Inner.make(name, sideEffects, allEventTopics, allCommandTopics, targets, queryEngine, scheduler, resourceNaming, memorySize, timeout, undefined, opts);
|
|
39
39
|
let sideEffectModulePaths = sideEffects.map(SE => Util_Bundle$ReventlessAws.getModuleSpecifier(SE.moduleUrl));
|
|
40
40
|
SideEffectHandlerRuntime_Builder_Single$ReventlessAws.registerSideEffectHandler(name, sideEffectModulePaths);
|
|
41
|
+
if (extraEnvVars !== undefined) {
|
|
42
|
+
SideEffectHandlerRuntime_Builder_Single$ReventlessAws.registerExtraEnv(extraEnvVars);
|
|
43
|
+
}
|
|
41
44
|
return component;
|
|
42
45
|
};
|
|
43
46
|
return {
|