@reventlessdev/reventless-aws 3.0.0-alpha.346 → 3.0.0-alpha.348
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 +34 -0
- package/package.json +17 -13
- package/rescript.json +2 -1
- package/run-bake-manifest.mjs +3 -0
- package/run-deploy-app.mjs +3 -0
- package/scripts/BakeManifest.res +395 -0
- package/scripts/BakeManifest.res.mjs +487 -0
- package/scripts/DeployApp.res +708 -0
- package/scripts/DeployApp.res.mjs +1015 -0
- package/scripts/DeployManifest.res +84 -0
- package/scripts/DeployManifest.res.mjs +112 -0
- package/scripts/ProvisionAccounts.res +60 -46
- package/scripts/ProvisionAccounts.res.mjs +51 -37
- package/scripts/PulumiCli.res +43 -0
- package/scripts/PulumiCli.res.mjs +106 -0
- package/src/Platform.res +19 -2
- package/src/Platform.res.mjs +12 -4
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -5
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +1 -5
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +1 -2
- package/src/adapter/Auth/Auth_ActiveRoleStore.res +1 -5
- package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +1 -2
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res +1 -5
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +1 -2
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +52 -31
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +75 -44
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +1 -5
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +1 -2
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +1 -5
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +1 -2
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +5 -4
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -2
- package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res +2 -0
- package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res.mjs +2 -2
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +15 -6
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +51 -30
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +31 -14
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -5
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res +1 -5
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +1 -1
- package/src/adapter/Task/TaskBucket_S3.res +1 -3
- package/src/adapter/Task/TaskBucket_S3.res.mjs +1 -1
- package/src/adapter/Upload/Upload_Claim_S3.res +1 -5
- package/src/adapter/Upload/Upload_Claim_S3.res.mjs +1 -1
- package/src/adapter/Upload/Upload_Presign_S3.res +1 -5
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +1 -2
- package/src/capability/Capability_Geocoding_AwsLocation.res +5 -1
- package/src/capability/Capability_Geocoding_AwsLocation.res.mjs +7 -1
- package/src/components/Api/AppSync_SdlDecorate.res +4 -2
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +1 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res +1 -5
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +1 -1
- package/src/plugin/stack/Plugin_Stack.res +4 -0
- package/src/plugin/stack/Plugin_Stack.res.mjs +2 -0
- package/src/util/Util_DeadLetterQueue.res +1 -5
- package/src/util/Util_DeadLetterQueue.res.mjs +1 -2
- package/src/util/Util_StoreLayout.res +15 -2
- package/src/util/Util_StoreLayout.res.mjs +11 -3
- package/tests/BakeManifestTest.res +204 -0
- package/tests/BakeManifestTest.res.mjs +259 -0
- package/tests/DcbColdStartPartitionTest.res +32 -0
- package/tests/DcbColdStartPartitionTest.res.mjs +33 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +88 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +112 -0
- package/tests/DcbUnresolvedPartitionSlice.res +28 -0
- package/tests/DcbUnresolvedPartitionSlice.res.mjs +38 -0
- package/tests/DeployAppTest.res +90 -0
- package/tests/DeployAppTest.res.mjs +133 -0
- package/tests/LambdaLayerLookupTest.res +104 -0
- package/tests/LambdaLayerLookupTest.res.mjs +117 -0
- package/tests/ProvisionAccountsTest.res +26 -0
- package/tests/ProvisionAccountsTest.res.mjs +31 -0
- package/tests/Util_StoreLayoutTest.res +6 -0
- package/tests/Util_StoreLayoutTest.res.mjs +7 -4
- package/tests/dcbColdStartPartition.mjs +66 -0
- package/tests/setup/layerArn.cjs +5 -0
|
@@ -26,7 +26,7 @@ import { commandOutcomeToJson, runInlineAndCollect } from "@reventlessdev/revent
|
|
|
26
26
|
// re-exporting it in sury 11. This is the same binding the compiled ReScript uses.
|
|
27
27
|
import { json as jsonSchema } from "sury";
|
|
28
28
|
import { handleQueueEvent, publishJsons as sqsPublishJsons } from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
29
|
-
// Typed cold-start core — scope/
|
|
29
|
+
// Typed cold-start core — scope/partition derivation, storage-ops wiring, and
|
|
30
30
|
// per-slice handler building (functor + decode + handleCommands), all
|
|
31
31
|
// compiler-checked against the framework signatures (see the module header and
|
|
32
32
|
// docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md).
|
|
@@ -89,7 +89,6 @@ export async function buildHandlersForConfig(config, opts = {}) {
|
|
|
89
89
|
// as `dcb_event.log_name`); absence keeps the DynamoDB path byte-identical.
|
|
90
90
|
const specs = loadedSlices.map(({ patchedSpec }) => patchedSpec);
|
|
91
91
|
const scope = deriveScope(specs);
|
|
92
|
-
const { crossPartitionTagKeys, tagKeysByEventType } = scope;
|
|
93
92
|
const rawStorageOps = makeStorageOps(
|
|
94
93
|
config.dcbEventLogTableName,
|
|
95
94
|
config.pgConnection,
|
|
@@ -98,6 +97,7 @@ export async function buildHandlersForConfig(config, opts = {}) {
|
|
|
98
97
|
|
|
99
98
|
const handlersByType = {};
|
|
100
99
|
const commandSchemasByType = {};
|
|
100
|
+
const partitionTagsByType = {};
|
|
101
101
|
const sharedDcbEventLogOps = dcbEventLogOperationsMake({
|
|
102
102
|
name: config.pluginName,
|
|
103
103
|
// Mirrors DcbEventLog_Builder.res's `name ++ "DcbEventLog"` — the
|
|
@@ -116,8 +116,7 @@ export async function buildHandlersForConfig(config, opts = {}) {
|
|
|
116
116
|
const jsonHandler = buildSliceHandler(
|
|
117
117
|
patchedSpec,
|
|
118
118
|
behaviorModule,
|
|
119
|
-
|
|
120
|
-
crossPartitionTagKeys,
|
|
119
|
+
scope,
|
|
121
120
|
sharedDcbEventLogOps,
|
|
122
121
|
);
|
|
123
122
|
for (const typeName of commandTypeNames(patchedSpec)) {
|
|
@@ -134,6 +133,8 @@ export async function buildHandlersForConfig(config, opts = {}) {
|
|
|
134
133
|
);
|
|
135
134
|
} else {
|
|
136
135
|
commandSchemasByType[typeName] = patchedSpec.commandSchema;
|
|
136
|
+
// The envelope id is the command's value of its slice's partition key.
|
|
137
|
+
partitionTagsByType[typeName] = scope.partitionTagBySlice[patchedSpec.name];
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
});
|
|
@@ -225,17 +226,21 @@ export async function buildHandlersForConfig(config, opts = {}) {
|
|
|
225
226
|
// reads a command's `@owner` fields from: given `S.json` it finds none, for every
|
|
226
227
|
// command, and the write keeps whatever owner the CLIENT sent. Validation was
|
|
227
228
|
// never the only job.
|
|
228
|
-
const makeGeneratorFor = (commandSchema) => makeCommandGenerator(
|
|
229
|
+
const makeGeneratorFor = (commandSchema, partitionTag) => makeCommandGenerator(
|
|
229
230
|
publishJsons,
|
|
230
231
|
publishJsonsAndWait,
|
|
231
232
|
config.pluginName,
|
|
232
233
|
commandSchema,
|
|
233
234
|
"StateChangeSlice",
|
|
234
235
|
false,
|
|
236
|
+
partitionTag,
|
|
235
237
|
);
|
|
236
238
|
const generatorsByType = {};
|
|
237
239
|
for (const typeName of Object.keys(commandSchemasByType)) {
|
|
238
|
-
generatorsByType[typeName] = makeGeneratorFor(
|
|
240
|
+
generatorsByType[typeName] = makeGeneratorFor(
|
|
241
|
+
commandSchemasByType[typeName],
|
|
242
|
+
partitionTagsByType[typeName],
|
|
243
|
+
);
|
|
239
244
|
}
|
|
240
245
|
// A command no loaded slice claims keeps the permissive generator — what every
|
|
241
246
|
// command got before this change — so anything reaching this handler by another
|
|
@@ -301,6 +306,10 @@ async function buildHandler() {
|
|
|
301
306
|
}
|
|
302
307
|
|
|
303
308
|
const initPromise = buildHandler();
|
|
309
|
+
// Startup runs at module load, before any invocation awaits it. A failure left
|
|
310
|
+
// unhandled until then can surface as a bare `Runtime.UnhandledPromiseRejection`
|
|
311
|
+
// and a restart; handled here, every invocation rethrows the same clear error.
|
|
312
|
+
initPromise.catch(() => {});
|
|
304
313
|
|
|
305
314
|
export async function handler(event, context) {
|
|
306
315
|
setRequestId(context?.awsRequestId);
|
|
@@ -37,47 +37,62 @@ type pgConnectionJson
|
|
|
37
37
|
@get external pgLockStrategy: pgConnectionJson => Nullable.t<string> = "lockStrategy"
|
|
38
38
|
external asConnectionConfig: pgConnectionJson => PgConnection.connectionConfig = "%identity"
|
|
39
39
|
|
|
40
|
-
// The decision-read scope + storage partition
|
|
40
|
+
// The decision-read scope + storage partition for one consistency boundary,
|
|
41
41
|
// derived exactly the way the deploy-time `Dcb_Builder` does (both call the same
|
|
42
42
|
// `Reventless.DcbTag` functions — the single source of truth), so the runtime
|
|
43
43
|
// query can't diverge from the storage/GSI scope.
|
|
44
44
|
type derivedScope = {
|
|
45
45
|
crossPartitionTagKeys: array<string>,
|
|
46
46
|
tagKeysByEventType: dict<array<string>>,
|
|
47
|
+
/** `None` only for a Lambda hosting no StateChangeSlices, which never appends. */
|
|
47
48
|
partitionTag: option<Reventless.DcbTag.derivedPartitionTag>,
|
|
49
|
+
/** Slice name -> its partition: the handler's entity id and each command's envelope id. */
|
|
50
|
+
partitionTagBySlice: dict<Reventless.DcbTag.derivedPartitionTag>,
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
let deriveScope = (specs: array<specModule>): derivedScope =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
let deriveScope = (specs: array<specModule>): derivedScope =>
|
|
54
|
+
switch specs {
|
|
55
|
+
| [] => {
|
|
56
|
+
crossPartitionTagKeys: [],
|
|
57
|
+
tagKeysByEventType: Dict.make(),
|
|
58
|
+
partitionTag: None,
|
|
59
|
+
partitionTagBySlice: Dict.make(),
|
|
60
|
+
}
|
|
61
|
+
| _ =>
|
|
62
|
+
let slices = specs->Array.map(s => {
|
|
53
63
|
Reventless.DcbTag.name: specName(s),
|
|
54
64
|
commandSchema: specCommandSchema(s),
|
|
55
65
|
consumedEventSchema: specConsumedEventSchema(s),
|
|
56
66
|
eventSchema: specEventSchema(s),
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
moduleUrl: ?(specModuleUrl(s)->Nullable.toOption),
|
|
68
|
+
})
|
|
69
|
+
let scope = Reventless.DcbTag.deriveEffectiveScope(slices)
|
|
70
|
+
// Deploy derives this from the same slice list, so on the same code it cannot
|
|
71
|
+
// fail here. It can when the framework in the Lambda layer differs from the
|
|
72
|
+
// one deploy used — and then carrying on would file events under the wrong
|
|
73
|
+
// keys. Refuse to start instead, saying why before the rejection can be lost.
|
|
74
|
+
// Rethrown as the original JS error, so each invocation reports its message.
|
|
75
|
+
let boundary = try Reventless.DcbTag.deriveBoundaryPartition(slices) catch {
|
|
76
|
+
| JsExn(err) =>
|
|
77
|
+
ReventlessCore.EffectLogger.logError(
|
|
78
|
+
~comp="DcbCommandTopicRuntime",
|
|
79
|
+
`refusing to start: ${err->JsExn.message->Option.getOr("unknown error")}`,
|
|
80
|
+
)->Effect.runSync
|
|
81
|
+
JsExn.throw(err)
|
|
82
|
+
}
|
|
83
|
+
let partitionTagBySlice = Dict.make()
|
|
84
|
+
slices->Array.forEach(s =>
|
|
85
|
+
boundary
|
|
86
|
+
->Reventless.DcbTag.slicePartitionTag(s.name)
|
|
87
|
+
->Option.forEach(tag => partitionTagBySlice->Dict.set(s.name, tag))
|
|
88
|
+
)
|
|
89
|
+
{
|
|
90
|
+
crossPartitionTagKeys: scope.crossPartitionTagKeys,
|
|
91
|
+
tagKeysByEventType: scope.tagKeysByEventType,
|
|
92
|
+
partitionTag: Some(boundary.partitionTag),
|
|
93
|
+
partitionTagBySlice,
|
|
94
|
+
}
|
|
79
95
|
}
|
|
80
|
-
}
|
|
81
96
|
|
|
82
97
|
// The command TAG names this slice handles — used by the shell to build its
|
|
83
98
|
// `handlersByType` routing map.
|
|
@@ -166,6 +181,7 @@ type sliceCallback = {
|
|
|
166
181
|
handleCommands: (
|
|
167
182
|
~tagKeysByEventType: dict<array<string>>=?,
|
|
168
183
|
~crossPartitionTagKeys: array<string>=?,
|
|
184
|
+
~partitionTag: Reventless.DcbTag.derivedPartitionTag=?,
|
|
169
185
|
ReventlessInfra.DcbEventLog.operations,
|
|
170
186
|
Stream.t<
|
|
171
187
|
ReventlessInfra.CommandTopic.topicItem<
|
|
@@ -188,8 +204,7 @@ external makeSliceCallback: specModule => behaviorModule => sliceCallback = "Mak
|
|
|
188
204
|
let buildSliceHandler = (
|
|
189
205
|
spec: specModule,
|
|
190
206
|
behavior: behaviorModule,
|
|
191
|
-
|
|
192
|
-
~crossPartitionTagKeys: array<string>,
|
|
207
|
+
scope: derivedScope,
|
|
193
208
|
dcbEventLog: ReventlessInfra.DcbEventLog.operations,
|
|
194
209
|
) => {
|
|
195
210
|
let callback = makeSliceCallback(spec)(behavior)
|
|
@@ -220,7 +235,13 @@ let buildSliceHandler = (
|
|
|
220
235
|
| None => Stream.empty
|
|
221
236
|
}
|
|
222
237
|
)
|
|
223
|
-
callback.handleCommands(
|
|
238
|
+
callback.handleCommands(
|
|
239
|
+
~tagKeysByEventType=scope.tagKeysByEventType,
|
|
240
|
+
~crossPartitionTagKeys=scope.crossPartitionTagKeys,
|
|
241
|
+
~partitionTag=?scope.partitionTagBySlice->Dict.get(specName(spec)),
|
|
242
|
+
dcbEventLog,
|
|
243
|
+
decodedStream,
|
|
244
|
+
)
|
|
224
245
|
}
|
|
225
246
|
}
|
|
226
247
|
|
|
@@ -20,26 +20,43 @@ import * as StateChangeSlice_CallbackResMjs from "@reventlessdev/reventless-core
|
|
|
20
20
|
import * as InboundTranslationSlice_CallbackResMjs from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs";
|
|
21
21
|
|
|
22
22
|
function deriveScope(specs) {
|
|
23
|
-
|
|
23
|
+
if (specs.length === 0) {
|
|
24
|
+
return {
|
|
25
|
+
crossPartitionTagKeys: [],
|
|
26
|
+
tagKeysByEventType: {},
|
|
27
|
+
partitionTag: undefined,
|
|
28
|
+
partitionTagBySlice: {}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
let slices = specs.map(s => ({
|
|
24
32
|
name: s.name,
|
|
25
33
|
commandSchema: s.commandSchema,
|
|
26
34
|
consumedEventSchema: s.consumedEventSchema,
|
|
27
|
-
eventSchema: s.eventSchema
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
eventSchema: s.eventSchema,
|
|
36
|
+
moduleUrl: Primitive_option.fromNullable(s.moduleUrl)
|
|
37
|
+
}));
|
|
38
|
+
let scope = DcbTag$Reventless.deriveEffectiveScope(slices);
|
|
39
|
+
let boundary;
|
|
30
40
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
boundary = DcbTag$Reventless.deriveBoundaryPartition(slices);
|
|
42
|
+
} catch (raw_err) {
|
|
43
|
+
let err = Primitive_exceptions.internalToException(raw_err);
|
|
44
|
+
if (err.RE_EXN_ID === "JsExn") {
|
|
45
|
+
let err$1 = err._1;
|
|
46
|
+
Effect.runSync(EffectLogger$ReventlessCore.logError("DcbCommandTopicRuntime", undefined, `refusing to start: ` + Stdlib_Option.getOr(Stdlib_JsExn.message(err$1), "unknown error")));
|
|
47
|
+
throw err$1;
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
38
50
|
}
|
|
51
|
+
let partitionTagBySlice = {};
|
|
52
|
+
slices.forEach(s => Stdlib_Option.forEach(DcbTag$Reventless.slicePartitionTag(boundary, s.name), tag => {
|
|
53
|
+
partitionTagBySlice[s.name] = tag;
|
|
54
|
+
}));
|
|
39
55
|
return {
|
|
40
56
|
crossPartitionTagKeys: scope.crossPartitionTagKeys,
|
|
41
57
|
tagKeysByEventType: scope.tagKeysByEventType,
|
|
42
|
-
partitionTag: partitionTag
|
|
58
|
+
partitionTag: boundary.partitionTag,
|
|
59
|
+
partitionTagBySlice: partitionTagBySlice
|
|
43
60
|
};
|
|
44
61
|
}
|
|
45
62
|
|
|
@@ -75,7 +92,7 @@ function makeStorageOps(tableName, pgConnection, scope) {
|
|
|
75
92
|
}
|
|
76
93
|
}
|
|
77
94
|
|
|
78
|
-
function buildSliceHandler(spec, behavior,
|
|
95
|
+
function buildSliceHandler(spec, behavior, scope, dcbEventLog) {
|
|
79
96
|
let callback = StateChangeSlice_CallbackResMjs.Make(spec)(behavior);
|
|
80
97
|
let commandSchema = spec.commandSchema;
|
|
81
98
|
return jsonStream => {
|
|
@@ -97,7 +114,7 @@ function buildSliceHandler(spec, behavior, tagKeysByEventType, crossPartitionTag
|
|
|
97
114
|
return Stream.empty;
|
|
98
115
|
}
|
|
99
116
|
});
|
|
100
|
-
return callback.handleCommands(tagKeysByEventType, crossPartitionTagKeys, dcbEventLog, decodedStream);
|
|
117
|
+
return callback.handleCommands(scope.tagKeysByEventType, scope.crossPartitionTagKeys, scope.partitionTagBySlice[spec.name], dcbEventLog, decodedStream);
|
|
101
118
|
};
|
|
102
119
|
}
|
|
103
120
|
|
|
@@ -224,11 +224,7 @@ let makeFromCodeAsset: (
|
|
|
224
224
|
})
|
|
225
225
|
})
|
|
226
226
|
|
|
227
|
-
let layers =
|
|
228
|
-
Lambda.reventlessLayerArn
|
|
229
|
-
->Option.map(arn => [arn->Pulumi.Input.make])
|
|
230
|
-
->Option.getOr([])
|
|
231
|
-
->Pulumi.Input.make
|
|
227
|
+
let layers = Lambda.reventlessLayers()
|
|
232
228
|
|
|
233
229
|
let variables = Dict.fromArray([("Environment", stack->Pulumi.Input.make)])
|
|
234
230
|
envVars->Dict.forEachWithKey((value, key) => {
|
|
@@ -91,7 +91,7 @@ function makeFromCodeAsset(name, unitKind, componentKind, code, sourceCodeHash,
|
|
|
91
91
|
});
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
|
-
let layers =
|
|
94
|
+
let layers = Lambda$PulumiAws.reventlessLayers();
|
|
95
95
|
let variables = Object.fromEntries([[
|
|
96
96
|
"Environment",
|
|
97
97
|
stack
|
|
@@ -332,11 +332,7 @@ let finish = (~eventsApi: AppSync_EventsApi.t, ~opts: Pulumi.CustomResourceOptio
|
|
|
332
332
|
~bundleRuntimeExtensions=false,
|
|
333
333
|
)
|
|
334
334
|
|
|
335
|
-
let layers =
|
|
336
|
-
Lambda.reventlessLayerArn
|
|
337
|
-
->Option.map(arn => [arn->Pulumi.Input.make])
|
|
338
|
-
->Option.getOr([])
|
|
339
|
-
->Pulumi.Input.make
|
|
335
|
+
let layers = Lambda.reventlessLayers()
|
|
340
336
|
|
|
341
337
|
let appsyncEndpoint = AppSync_EventsApi.httpEndpoint(eventsApi)
|
|
342
338
|
|
|
@@ -120,7 +120,7 @@ function finish(eventsApi, opts) {
|
|
|
120
120
|
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
121
121
|
]]);
|
|
122
122
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs", packageDirs, undefined, false);
|
|
123
|
-
let layers =
|
|
123
|
+
let layers = Lambda$PulumiAws.reventlessLayers();
|
|
124
124
|
let appsyncEndpoint = AppSync_EventsApi$ReventlessAws.httpEndpoint(eventsApi);
|
|
125
125
|
let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "StateTopicPublisher", undefined, AWS_Tags$ReventlessAws.make(name + "StateTopicPublisherLogGroup", QueryDb$ReventlessCore.componentType, "Logs", undefined, name, undefined, undefined, undefined), opts, undefined);
|
|
126
126
|
let lambda = new (Aws.lambda.Function)(name + "StateTopicPublisher", {
|
|
@@ -136,9 +136,7 @@ let make: ReventlessCore.Task_Adapter.bucketMaker<bucketParts> = (~name, ~opts)
|
|
|
136
136
|
// bucket fails the deploy with `BucketNotEmpty` — which would wedge precisely
|
|
137
137
|
// the throwaway stacks that are recreated most often. Protected stacks keep
|
|
138
138
|
// the safe default and drain by hand.
|
|
139
|
-
let forceDestroy =
|
|
140
|
-
Util_StoreLayout.protectionFor(~stack=Pulumi.Pulumi.getStackName()) ==
|
|
141
|
-
Util_StoreLayout.Unprotected
|
|
139
|
+
let forceDestroy = Util_StoreLayout.protectionOfDeployedStack() == Util_StoreLayout.Unprotected
|
|
142
140
|
|
|
143
141
|
let bucket = {
|
|
144
142
|
PulumiAws.S3.Bucket.make(
|
|
@@ -76,7 +76,7 @@ function connect(name, bucket, bucketMode, commandTopics, runtime, opts) {
|
|
|
76
76
|
|
|
77
77
|
function make(name, opts) {
|
|
78
78
|
let opts$1 = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
|
|
79
|
-
let forceDestroy = Util_StoreLayout$ReventlessAws.
|
|
79
|
+
let forceDestroy = Util_StoreLayout$ReventlessAws.protectionOfDeployedStack() === "Unprotected";
|
|
80
80
|
let bucket = new (Aws.s3.Bucket)(name, {
|
|
81
81
|
corsRules: [{
|
|
82
82
|
allowedHeaders: ["*"],
|
|
@@ -257,11 +257,7 @@ let finish = (
|
|
|
257
257
|
~bundleRuntimeExtensions=false,
|
|
258
258
|
)
|
|
259
259
|
|
|
260
|
-
let layers =
|
|
261
|
-
Lambda.reventlessLayerArn
|
|
262
|
-
->Option.map(arn => [arn->Pulumi.Input.make])
|
|
263
|
-
->Option.getOr([])
|
|
264
|
-
->Pulumi.Input.make
|
|
260
|
+
let layers = Lambda.reventlessLayers()
|
|
265
261
|
|
|
266
262
|
// Created before the function that writes to it — see
|
|
267
263
|
// `Util_LambdaLogging.makeManagedLogGroup` for why the ordering matters.
|
|
@@ -124,7 +124,7 @@ function finish(plugin, stores, iteratorAgeAlarmMsOpt, opts) {
|
|
|
124
124
|
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
125
125
|
]]);
|
|
126
126
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Claim_S3_Ops.res.mjs", packageDirs, undefined, false);
|
|
127
|
-
let layers =
|
|
127
|
+
let layers = Lambda$PulumiAws.reventlessLayers();
|
|
128
128
|
let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name, undefined, AWS_Tags$ReventlessAws.make(name + "LogGroup", "Plugin", "Logs", undefined, name, undefined, undefined, undefined), opts, undefined);
|
|
129
129
|
let lambda = new (Aws.lambda.Function)(name, {
|
|
130
130
|
handler: "index.handler",
|
|
@@ -180,11 +180,7 @@ let make = (
|
|
|
180
180
|
~bundleRuntimeExtensions=false,
|
|
181
181
|
)
|
|
182
182
|
|
|
183
|
-
let layers =
|
|
184
|
-
Lambda.reventlessLayerArn
|
|
185
|
-
->Option.map(arn => [arn->Pulumi.Input.make])
|
|
186
|
-
->Option.getOr([])
|
|
187
|
-
->Pulumi.Input.make
|
|
183
|
+
let layers = Lambda.reventlessLayers()
|
|
188
184
|
|
|
189
185
|
// Created before the function that writes to it — see
|
|
190
186
|
// `Util_LambdaLogging.makeManagedLogGroup` for why the ordering matters.
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import * as Aws from "@pulumi/aws";
|
|
4
4
|
import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.res.mjs";
|
|
5
5
|
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
6
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
6
|
import * as Pulumi from "@pulumi/pulumi";
|
|
8
7
|
import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
|
|
9
8
|
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
@@ -93,7 +92,7 @@ function make(api, stores, releaseWindowSecondsOpt, nameOpt, opts) {
|
|
|
93
92
|
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
94
93
|
]]);
|
|
95
94
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs", packageDirs, undefined, false);
|
|
96
|
-
let layers =
|
|
95
|
+
let layers = Lambda$PulumiAws.reventlessLayers();
|
|
97
96
|
let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", undefined, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
|
|
98
97
|
let lambda = new (Aws.lambda.Function)(name + "Lambda", {
|
|
99
98
|
handler: "index.handler",
|
|
@@ -23,6 +23,8 @@ open PulumiAws
|
|
|
23
23
|
let defaultDataSource = "Esri"
|
|
24
24
|
let defaultIntendedUse = "SingleUse"
|
|
25
25
|
|
|
26
|
+
let physicalName = (~name: string, ~stack: string): string => `${name}-${stack}`
|
|
27
|
+
|
|
26
28
|
/** Create a place index with framework attribution tags and config-driven
|
|
27
29
|
provider/retention settings. */
|
|
28
30
|
let make = (
|
|
@@ -35,7 +37,9 @@ let make = (
|
|
|
35
37
|
let index = Location.PlaceIndex.make(
|
|
36
38
|
~name,
|
|
37
39
|
~args={
|
|
38
|
-
|
|
40
|
+
// Stack-scoped: an index name is unique per account and region, so two
|
|
41
|
+
// stacks of one app in the same place would otherwise collide.
|
|
42
|
+
indexName: Pulumi.Input.make(physicalName(~name, ~stack=Pulumi.Pulumi.getStackName())),
|
|
39
43
|
dataSource: Pulumi.Input.make(dataSource),
|
|
40
44
|
dataSourceConfiguration: Pulumi.Input.make({
|
|
41
45
|
Location.PlaceIndex.intendedUse: Pulumi.Input.make(intendedUse),
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Aws from "@pulumi/aws";
|
|
4
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Pulumi from "@pulumi/pulumi";
|
|
5
6
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
6
7
|
import * as AWS_Tags$ReventlessAws from "../adapter/AWS_Tags.res.mjs";
|
|
7
8
|
import * as Util_LocalConfig$ReventlessAws from "../util/Util_LocalConfig.res.mjs";
|
|
@@ -10,11 +11,15 @@ let defaultDataSource = "Esri";
|
|
|
10
11
|
|
|
11
12
|
let defaultIntendedUse = "SingleUse";
|
|
12
13
|
|
|
14
|
+
function physicalName(name, stack) {
|
|
15
|
+
return name + `-` + stack;
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
function make(name, opts) {
|
|
14
19
|
let dataSource = Stdlib_Option.getOr(Util_LocalConfig$ReventlessAws.get("geocoderDataSource"), defaultDataSource);
|
|
15
20
|
let intendedUse = Stdlib_Option.getOr(Util_LocalConfig$ReventlessAws.get("geocoderIntendedUse"), defaultIntendedUse);
|
|
16
21
|
let index = new (Aws.location.PlaceIndex)(name, {
|
|
17
|
-
indexName: name,
|
|
22
|
+
indexName: physicalName(name, Pulumi.getStack()),
|
|
18
23
|
dataSource: dataSource,
|
|
19
24
|
dataSourceConfiguration: {
|
|
20
25
|
intendedUse: intendedUse
|
|
@@ -32,6 +37,7 @@ function make(name, opts) {
|
|
|
32
37
|
export {
|
|
33
38
|
defaultDataSource,
|
|
34
39
|
defaultIntendedUse,
|
|
40
|
+
physicalName,
|
|
35
41
|
make,
|
|
36
42
|
}
|
|
37
43
|
/* @pulumi/aws Not a pure module */
|
|
@@ -162,8 +162,10 @@ let refuseAnonymousFields = (fieldNames: array<string>): 'a =>
|
|
|
162
162
|
`Authorization.isAllowed), so a spec that passes locally can still not be\n` ++
|
|
163
163
|
`deployable here. That divergence is the reason this refuses instead of\n` ++
|
|
164
164
|
`emitting a directive that reads as gated and is not.\n\n` ++
|
|
165
|
-
`
|
|
166
|
-
`
|
|
165
|
+
`Anonymous fields on AWS are deferred. When they land it will be as a separate\n` ++
|
|
166
|
+
`API carrying only the unauthenticated fields. Do not add API_KEY to this one:\n` ++
|
|
167
|
+
`every field here would then have to say whether the key reaches it, and one\n` ++
|
|
168
|
+
`that forgot would be open. Until then, declare the field AllowAuthenticated\n` ++ `(or AllowGroups) and mean it.`,
|
|
167
169
|
)
|
|
168
170
|
|
|
169
171
|
// Injects the Cognito group gate on ALL mutation, query, and subscription fields
|
|
@@ -81,7 +81,7 @@ function permissionToGate(permission) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
function refuseAnonymousFields(fieldNames) {
|
|
84
|
-
return Stdlib_JsError.throwWithMessage(`Refusing to push an AppSync schema that cannot honour AllowAnonymous.\n\n` + (` ` + fieldNames.length.toString() + ` field(s) declare AllowAnonymous:\n`) + (` ` + fieldNames.join(", ") + `\n\n`) + `AppSync has no anonymous authorization mode. This API is provisioned with\nAMAZON_COGNITO_USER_POOLS primary and AWS_IAM additional, so the only\ndirective available is @aws_cognito_user_pools — which means ANY\nAUTHENTICATED caller, the opposite of what the spec declares. Mutations have\nno runtime authorization check to correct it: on this platform the directive\nis the whole enforcement.\n\nThe local platform DOES honour AllowAnonymous (its resolvers call\nAuthorization.isAllowed), so a spec that passes locally can still not be\ndeployable here. That divergence is the reason this refuses instead of\nemitting a directive that reads as gated and is not.\n\
|
|
84
|
+
return Stdlib_JsError.throwWithMessage(`Refusing to push an AppSync schema that cannot honour AllowAnonymous.\n\n` + (` ` + fieldNames.length.toString() + ` field(s) declare AllowAnonymous:\n`) + (` ` + fieldNames.join(", ") + `\n\n`) + `AppSync has no anonymous authorization mode. This API is provisioned with\nAMAZON_COGNITO_USER_POOLS primary and AWS_IAM additional, so the only\ndirective available is @aws_cognito_user_pools — which means ANY\nAUTHENTICATED caller, the opposite of what the spec declares. Mutations have\nno runtime authorization check to correct it: on this platform the directive\nis the whole enforcement.\n\nThe local platform DOES honour AllowAnonymous (its resolvers call\nAuthorization.isAllowed), so a spec that passes locally can still not be\ndeployable here. That divergence is the reason this refuses instead of\nemitting a directive that reads as gated and is not.\n\nAnonymous fields on AWS are deferred. When they land it will be as a separate\nAPI carrying only the unauthenticated fields. Do not add API_KEY to this one:\nevery field here would then have to say whether the key reaches it, and one\nthat forgot would be open. Until then, declare the field AllowAuthenticated\n(or AllowGroups) and mean it.`);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
function injectAwsAuthAll(fragment, group, iamFieldNamesOpt) {
|
|
@@ -141,11 +141,7 @@ export const handler = async (event) => {
|
|
|
141
141
|
let code = Pulumi.Archive.assetArchive(clonerArchiveContents)
|
|
142
142
|
let sourceCodeHash = Util_Bundle.hashString(entryPointCode ++ "\n---\n" ++ loaderHash)
|
|
143
143
|
|
|
144
|
-
let layers =
|
|
145
|
-
Lambda.reventlessLayerArn
|
|
146
|
-
->Option.map(arn => [arn->Pulumi.Input.make])
|
|
147
|
-
->Option.getOr([])
|
|
148
|
-
->Pulumi.Input.make
|
|
144
|
+
let layers = Lambda.reventlessLayers()
|
|
149
145
|
|
|
150
146
|
let vpcStackName = Pulumi.Config.make(Some("vpc"))->Pulumi.Config.get("stack")->Option.getOrThrow
|
|
151
147
|
let vpcConfig = Util_Vpc.getVpcConfig(~stackName=vpcStackName, ~outputName="vpc")
|
|
@@ -87,7 +87,7 @@ export const handler = async (event) => {
|
|
|
87
87
|
let loaderHash = Util_Bundle$ReventlessAws.addEsmLoaderAssets(clonerArchiveContents);
|
|
88
88
|
let code = new (Pulumi.asset.AssetArchive)(clonerArchiveContents);
|
|
89
89
|
let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(entryPointCode + "\n---\n" + loaderHash);
|
|
90
|
-
let layers =
|
|
90
|
+
let layers = Lambda$PulumiAws.reventlessLayers();
|
|
91
91
|
let vpcStackName = Stdlib_Option.getOrThrow(new Pulumi.Config("vpc").get("stack"), undefined);
|
|
92
92
|
let vpcConfig = Util_Vpc$ReventlessAws.getVpcConfig(vpcStackName, "vpc");
|
|
93
93
|
let secrets = Pulumi.all(secretUrns.map(urn => GetSecretVersion$PulumiAws.getSecretNames(urn).apply(names => names.map(name => ({
|
|
@@ -157,9 +157,13 @@ let makeUiBundleDistribution = (
|
|
|
157
157
|
// shell, where blue/green between versions is wanted.
|
|
158
158
|
let name = stableName ? pluginId : pluginId ++ "-" ++ bundleVersion
|
|
159
159
|
|
|
160
|
+
// Emptied on removal from a disposable stack: the manifest bake writes files
|
|
161
|
+
// here outside Pulumi, which would otherwise fail the destroy.
|
|
160
162
|
let bucket = PulumiAws.S3.Bucket.make(
|
|
161
163
|
~name=name ++ "-bundle",
|
|
162
164
|
~args={
|
|
165
|
+
forceDestroy: (Util_StoreLayout.protectionOfDeployedStack() == Unprotected)
|
|
166
|
+
->Pulumi.Input.make,
|
|
163
167
|
tags: AWS.Tags.make(
|
|
164
168
|
~name=name ++ "-bundle",
|
|
165
169
|
~kind=ReventlessCore.ComponentType.Plugin,
|
|
@@ -11,6 +11,7 @@ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_excep
|
|
|
11
11
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
12
12
|
import * as AWS_Tags$ReventlessAws from "../../adapter/AWS_Tags.res.mjs";
|
|
13
13
|
import * as ClientCloudfront from "@aws-sdk/client-cloudfront";
|
|
14
|
+
import * as Util_StoreLayout$ReventlessAws from "../../util/Util_StoreLayout.res.mjs";
|
|
14
15
|
import * as Util_StaticBundle$ReventlessAws from "../../util/Util_StaticBundle.res.mjs";
|
|
15
16
|
|
|
16
17
|
let log = Logger$ReventlessCore.fromEnv();
|
|
@@ -76,6 +77,7 @@ function makeUiBundleDistribution(pluginId, bundleVersion, assetsDir, spaFallbac
|
|
|
76
77
|
let servedBuckets = servedBucketsOpt !== undefined ? servedBucketsOpt : [];
|
|
77
78
|
let name = stableName ? pluginId : pluginId + "-" + bundleVersion;
|
|
78
79
|
let bucket = new (Aws.s3.Bucket)(name + "-bundle", {
|
|
80
|
+
forceDestroy: Util_StoreLayout$ReventlessAws.protectionOfDeployedStack() === "Unprotected",
|
|
79
81
|
tags: AWS_Tags$ReventlessAws.make(name + "-bundle", "Plugin", "Hosting", "Plugin", undefined, undefined, pluginId, undefined)
|
|
80
82
|
});
|
|
81
83
|
new (Aws.s3.BucketPublicAccessBlock)(name + "-bundle-pab", {
|
|
@@ -123,11 +123,7 @@ archiveContents->Dict.set(
|
|
|
123
123
|
let code = Pulumi.Archive.assetArchive(archiveContents)
|
|
124
124
|
let sourceCodeHash = Util_Bundle.hashString(entryPointCode)
|
|
125
125
|
|
|
126
|
-
let layers =
|
|
127
|
-
Lambda.reventlessLayerArn
|
|
128
|
-
->Option.map(arn => [arn->Pulumi.Input.make])
|
|
129
|
-
->Option.getOr([])
|
|
130
|
-
->Pulumi.Input.make
|
|
126
|
+
let layers = Lambda.reventlessLayers()
|
|
131
127
|
|
|
132
128
|
// This is the one Lambda in the framework built by hand rather than through
|
|
133
129
|
// `RuntimeEnvironment_Lambda`, and it was the one Lambda whose logs Lambda
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Aws from "@pulumi/aws";
|
|
4
4
|
import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.res.mjs";
|
|
5
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
5
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
6
|
import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
|
|
8
7
|
import * as AWS$ReventlessAws from "../adapter/AWS.res.mjs";
|
|
@@ -76,7 +75,7 @@ let code = new (Pulumi.asset.AssetArchive)(archiveContents);
|
|
|
76
75
|
|
|
77
76
|
let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(entryPointCode);
|
|
78
77
|
|
|
79
|
-
let layers =
|
|
78
|
+
let layers = Lambda$PulumiAws.reventlessLayers();
|
|
80
79
|
|
|
81
80
|
let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name, undefined, AWS_Tags$ReventlessAws.make(name + `LogGroup`, "Plugin", "Logs", "Plugin", undefined, undefined, undefined, undefined), opts, undefined);
|
|
82
81
|
|
|
@@ -86,13 +86,26 @@ Destroy semantics follow disposability, **not** layout.
|
|
|
86
86
|
`reventless:wipeable`, but that authorises the reset tool to empty stores
|
|
87
87
|
*deliberately*, after a scope prompt and a typed confirmation. It does not say a
|
|
88
88
|
field rename may destroy a bucket by accident. Only stacks that are routinely
|
|
89
|
-
torn down are unprotected
|
|
89
|
+
torn down are unprotected: a `pr-*` stack, or one that declares
|
|
90
|
+
`reventless:disposable: "true"` — a try-out meant to be removed whole.
|
|
90
91
|
*/
|
|
91
92
|
let protectionFor = (
|
|
92
93
|
~stack: string,
|
|
94
|
+
~disposable: bool=false,
|
|
93
95
|
~ephemeralPrefixes: array<string>=defaultEphemeralPrefixes,
|
|
94
96
|
): protection =>
|
|
95
|
-
ephemeralPrefixes->Array.some(p => stack->String.startsWith(p))
|
|
97
|
+
disposable || ephemeralPrefixes->Array.some(p => stack->String.startsWith(p))
|
|
98
|
+
? Unprotected
|
|
99
|
+
: Protected
|
|
100
|
+
|
|
101
|
+
/** `protectionFor` the stack being deployed, reading its `reventless:disposable`
|
|
102
|
+
setting. The one function here that touches Pulumi. */
|
|
103
|
+
let protectionOfDeployedStack = (): protection =>
|
|
104
|
+
protectionFor(
|
|
105
|
+
~stack=Pulumi.Pulumi.getStackName(),
|
|
106
|
+
~disposable=Pulumi.Config.make(Some("reventless"))->Pulumi.Config.get("disposable") ==
|
|
107
|
+
Some("true"),
|
|
108
|
+
)
|
|
96
109
|
|
|
97
110
|
/**
|
|
98
111
|
The bucket a store's objects live in.
|