@reventlessdev/reventless-aws 3.0.0-alpha.300 → 3.0.0-alpha.302
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 +20 -0
- package/package.json +8 -8
- package/src/Platform.res +18 -4
- package/src/Platform.res.mjs +24 -4
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +40 -4
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +35 -13
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +88 -20
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +81 -18
- package/src/adapter/Auth/Auth_ActiveRoleStore.res +10 -1
- package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +1 -1
- package/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res +39 -4
- package/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res.mjs +22 -3
- package/src/util/Util_DynamoDbStream.res +15 -4
- package/src/util/Util_DynamoDbStream.res.mjs +15 -5
- package/src/util/Util_ShellConfig.res +22 -2
- package/src/util/Util_ShellConfig.res.mjs +11 -0
- package/tests/Auth_ActiveRoleStoreTest.res +32 -0
- package/tests/Auth_ActiveRoleStoreTest.res.mjs +29 -0
- package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res +46 -0
- package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res.mjs +31 -0
- package/tests/Util_DynamoDbStreamTest.res +43 -0
- package/tests/Util_DynamoDbStreamTest.res.mjs +41 -0
- package/tests/Util_ShellConfigTest.res +107 -0
- package/tests/Util_ShellConfigTest.res.mjs +100 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.302 (2026-08-15)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** let a stream resource answer for its own stream ([4617773](https://github.com/ReventlessDev/reventless-core/commit/461777349f10ebc83085d59907bac5b5537429ed))
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **aws:** serve one baked manifest per journey ([16d6307](https://github.com/ReventlessDev/reventless-core/commit/16d63071f059cf1fc7025f9be9206dda51f71053))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# 3.0.0-alpha.301 (2026-08-15)
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **aws:** ask Cognito about a caller by the name it knows them by ([265f436](https://github.com/ReventlessDev/reventless-core/commit/265f4361f9695a12d1dcc055b256ec72174d428e))
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* **core:** curate the pages a shell builds across a plugin's views ([589835a](https://github.com/ReventlessDev/reventless-core/commit/589835a1ad428c5e2dea8bf6e4c64e49d2f67e0d))
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
# 3.0.0-alpha.300 (2026-08-14)
|
|
7
27
|
|
|
8
28
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.302",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
"uuid": "^13.0.0",
|
|
15
15
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
16
16
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
17
|
-
"@reventlessdev/rescript-
|
|
18
|
-
"@reventlessdev/rescript-
|
|
19
|
-
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.
|
|
17
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.7",
|
|
18
|
+
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.11",
|
|
19
|
+
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.76",
|
|
20
20
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
21
21
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
22
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
22
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.142",
|
|
23
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.236",
|
|
23
24
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.31",
|
|
24
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.
|
|
25
|
-
"@reventlessdev/reventless-
|
|
26
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.113"
|
|
25
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.100",
|
|
26
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.114"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"rescript": "12.3.0",
|
package/src/Platform.res
CHANGED
|
@@ -2221,9 +2221,14 @@ module MakeWithConfig = (
|
|
|
2221
2221
|
// target without reading either out of the deploy program.
|
|
2222
2222
|
switch (componentDefinitions, cfg.bakedManifest) {
|
|
2223
2223
|
| (Some(fn), Some(bake)) =>
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2224
|
+
// Every file the declaration produces: the default journey's, which is
|
|
2225
|
+
// what the shell's `manifestUrl` points at, and one per declared journey.
|
|
2226
|
+
// Resolved through the same function the bake and `config.json` use, so
|
|
2227
|
+
// a grant cannot be scoped to a key nothing writes.
|
|
2228
|
+
let manifestKeys =
|
|
2229
|
+
ReventlessCore.Platform_BakedManifest.files(~config=bake)->Array.map(((key, _)) => key)
|
|
2230
|
+
let manifestKey =
|
|
2231
|
+
manifestKeys->Array.get(0)->Option.getOr(ReventlessCore.Platform_BakedManifest.defaultKey)
|
|
2227
2232
|
let _ =
|
|
2228
2233
|
(fn.roleId, bucketName)
|
|
2229
2234
|
->Pulumi.Output.all2
|
|
@@ -2239,7 +2244,16 @@ module MakeWithConfig = (
|
|
|
2239
2244
|
sid: "AllowWriteBakedManifest",
|
|
2240
2245
|
effect: Allow,
|
|
2241
2246
|
actions: Actions(["s3:PutObject"]),
|
|
2242
|
-
|
|
2247
|
+
// A single key stays a bare `Resource` string: the two
|
|
2248
|
+
// forms mean the same thing to IAM, and a deployment that
|
|
2249
|
+
// declares no journey should not see its policy rewritten
|
|
2250
|
+
// for a field it does not use.
|
|
2251
|
+
resources: switch manifestKeys->Array.map(key =>
|
|
2252
|
+
`arn:aws:s3:::${bucket}/${key}`
|
|
2253
|
+
) {
|
|
2254
|
+
| [only] => Resource(only)
|
|
2255
|
+
| arns => Resources(arns)
|
|
2256
|
+
},
|
|
2243
2257
|
},
|
|
2244
2258
|
],
|
|
2245
2259
|
)
|
package/src/Platform.res.mjs
CHANGED
|
@@ -1210,17 +1210,27 @@ function MakeWithConfig(Config) {
|
|
|
1210
1210
|
}
|
|
1211
1211
|
let match$5 = hostUiBundle.bakedManifest;
|
|
1212
1212
|
if (componentDefinitions !== undefined && match$5 !== undefined) {
|
|
1213
|
-
let
|
|
1213
|
+
let manifestKeys = Platform_BakedManifest$ReventlessCore.files(match$5).map(param => param[0]);
|
|
1214
|
+
let manifestKey = Stdlib_Option.getOr(manifestKeys[0], Platform_BakedManifest$ReventlessCore.defaultKey);
|
|
1214
1215
|
Pulumi.all([
|
|
1215
1216
|
componentDefinitions.roleId,
|
|
1216
1217
|
bucketName
|
|
1217
1218
|
]).apply(param => {
|
|
1219
|
+
let bucket = param[1];
|
|
1220
|
+
let arns = manifestKeys.map(key => `arn:aws:s3:::` + bucket + `/` + key);
|
|
1221
|
+
let tmp;
|
|
1222
|
+
if (arns.length !== 1) {
|
|
1223
|
+
tmp = arns;
|
|
1224
|
+
} else {
|
|
1225
|
+
let only = arns[0];
|
|
1226
|
+
tmp = only;
|
|
1227
|
+
}
|
|
1218
1228
|
new (Aws.iam.RolePolicy)("PlatformUIDefinitionsBake", {
|
|
1219
1229
|
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, "PlatformUIDefinitionsBakePolicy", [{
|
|
1220
1230
|
Sid: "AllowWriteBakedManifest",
|
|
1221
1231
|
Effect: "Allow",
|
|
1222
1232
|
Action: ["s3:PutObject"],
|
|
1223
|
-
Resource:
|
|
1233
|
+
Resource: tmp
|
|
1224
1234
|
}])),
|
|
1225
1235
|
role: param[0]
|
|
1226
1236
|
});
|
|
@@ -2490,17 +2500,27 @@ function Make($star) {
|
|
|
2490
2500
|
}
|
|
2491
2501
|
let match$5 = hostUiBundle.bakedManifest;
|
|
2492
2502
|
if (componentDefinitions !== undefined && match$5 !== undefined) {
|
|
2493
|
-
let
|
|
2503
|
+
let manifestKeys = Platform_BakedManifest$ReventlessCore.files(match$5).map(param => param[0]);
|
|
2504
|
+
let manifestKey = Stdlib_Option.getOr(manifestKeys[0], Platform_BakedManifest$ReventlessCore.defaultKey);
|
|
2494
2505
|
Pulumi.all([
|
|
2495
2506
|
componentDefinitions.roleId,
|
|
2496
2507
|
bucketName
|
|
2497
2508
|
]).apply(param => {
|
|
2509
|
+
let bucket = param[1];
|
|
2510
|
+
let arns = manifestKeys.map(key => `arn:aws:s3:::` + bucket + `/` + key);
|
|
2511
|
+
let tmp;
|
|
2512
|
+
if (arns.length !== 1) {
|
|
2513
|
+
tmp = arns;
|
|
2514
|
+
} else {
|
|
2515
|
+
let only = arns[0];
|
|
2516
|
+
tmp = only;
|
|
2517
|
+
}
|
|
2498
2518
|
new (Aws.iam.RolePolicy)("PlatformUIDefinitionsBake", {
|
|
2499
2519
|
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, "PlatformUIDefinitionsBakePolicy", [{
|
|
2500
2520
|
Sid: "AllowWriteBakedManifest",
|
|
2501
2521
|
Effect: "Allow",
|
|
2502
2522
|
Action: ["s3:PutObject"],
|
|
2503
|
-
Resource:
|
|
2523
|
+
Resource: tmp
|
|
2504
2524
|
}])),
|
|
2505
2525
|
role: param[0]
|
|
2506
2526
|
});
|
|
@@ -141,10 +141,8 @@ let make = (
|
|
|
141
141
|
// as a record so the deploy input and the runtime input are the same shape —
|
|
142
142
|
// `views`/`commands` absent means "every public component", which a record
|
|
143
143
|
// with explicit nulls would not say.
|
|
144
|
-
let
|
|
145
|
-
|
|
146
|
-
| Some(bake) =>
|
|
147
|
-
bake.components
|
|
144
|
+
let encodeSelections = (components: array<ReventlessCore.Platform_BakedManifest.selection>) =>
|
|
145
|
+
components
|
|
148
146
|
->Array.map(sel => {
|
|
149
147
|
let entry = Dict.fromArray([("plugin", JSON.Encode.string(sel.plugin))])
|
|
150
148
|
let strings = (key, value) =>
|
|
@@ -153,9 +151,43 @@ let make = (
|
|
|
153
151
|
)
|
|
154
152
|
strings("views", sel.views)
|
|
155
153
|
strings("commands", sel.commands)
|
|
154
|
+
strings("derived", sel.derived)
|
|
156
155
|
entry->JSON.Encode.object
|
|
157
156
|
})
|
|
158
157
|
->JSON.Encode.array
|
|
158
|
+
|
|
159
|
+
let bakeSelectionsJson = switch bakedManifest {
|
|
160
|
+
| None => ""
|
|
161
|
+
| Some(bake) =>
|
|
162
|
+
bake.components
|
|
163
|
+
->Array.map(ReventlessCore.Platform_BakedManifest.toSelection)
|
|
164
|
+
->encodeSelections
|
|
165
|
+
->JSON.stringify
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// The journeys, in a variable of their own rather than folded into the one
|
|
169
|
+
// above: a deployment that declares none then puts the same string in the same
|
|
170
|
+
// place it always did, and its function is not replaced for a field it does
|
|
171
|
+
// not use.
|
|
172
|
+
//
|
|
173
|
+
// Each carries the key the DECLARATION resolves to, taken from the function
|
|
174
|
+
// that also decides the IAM grant and the URL `config.json` publishes. A key
|
|
175
|
+
// the handler derived for itself would be a fourth derivation of one name, and
|
|
176
|
+
// the way it fails is a file written where nothing fetches it.
|
|
177
|
+
let bakeJourneysJson = switch bakedManifest->Option.mapOr([], config =>
|
|
178
|
+
ReventlessCore.Platform_BakedManifest.journeyFiles(~config)
|
|
179
|
+
) {
|
|
180
|
+
| [] => ""
|
|
181
|
+
| journeys =>
|
|
182
|
+
journeys
|
|
183
|
+
->Array.map(j =>
|
|
184
|
+
Dict.fromArray([
|
|
185
|
+
("group", JSON.Encode.string(j.group)),
|
|
186
|
+
("key", JSON.Encode.string(j.key)),
|
|
187
|
+
("components", j.selections->encodeSelections),
|
|
188
|
+
])->JSON.Encode.object
|
|
189
|
+
)
|
|
190
|
+
->JSON.Encode.array
|
|
159
191
|
->JSON.stringify
|
|
160
192
|
}
|
|
161
193
|
|
|
@@ -225,6 +257,10 @@ let make = (
|
|
|
225
257
|
// An include-list is names only, so it stays far from the 4096-byte
|
|
226
258
|
// total the admin entry already pushed out of this dict.
|
|
227
259
|
("BAKE_SELECTIONS", bakeSelectionsJson->Pulumi.Input.make),
|
|
260
|
+
// One curated surface per audience beside the default one. Empty for
|
|
261
|
+
// a deployment that declares none, which is every deployment that
|
|
262
|
+
// predates journeys.
|
|
263
|
+
("BAKE_JOURNEYS", bakeJourneysJson->Pulumi.Input.make),
|
|
228
264
|
("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
|
|
229
265
|
("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
|
|
230
266
|
Util_LambdaLogging.logLevelEntry(),
|
|
@@ -13,6 +13,7 @@ import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
|
13
13
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
14
14
|
import * as Util_LambdaLogging$ReventlessAws from "../../util/Util_LambdaLogging.res.mjs";
|
|
15
15
|
import * as AppSync_Resolver_Native$ReventlessAws from "./AppSync_Resolver_Native.res.mjs";
|
|
16
|
+
import * as Platform_BakedManifest$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_BakedManifest.res.mjs";
|
|
16
17
|
import * as Platform_Admin_Structure$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_Admin_Structure.res.mjs";
|
|
17
18
|
import * as Platform_ComponentDefinitionsApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_ComponentDefinitionsApi.res.mjs";
|
|
18
19
|
|
|
@@ -77,19 +78,36 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, bak
|
|
|
77
78
|
role: lambdaRole.id
|
|
78
79
|
}, opts$1);
|
|
79
80
|
});
|
|
80
|
-
let
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
let encodeSelections = components => components.map(sel => {
|
|
82
|
+
let entry = Object.fromEntries([[
|
|
83
|
+
"plugin",
|
|
84
|
+
sel.plugin
|
|
85
|
+
]]);
|
|
86
|
+
let strings = (key, value) => Stdlib_Option.forEach(value, names => {
|
|
87
|
+
entry[key] = names.map(prim => prim);
|
|
88
|
+
});
|
|
89
|
+
strings("views", sel.views);
|
|
90
|
+
strings("commands", sel.commands);
|
|
91
|
+
strings("derived", sel.derived);
|
|
92
|
+
return entry;
|
|
93
|
+
});
|
|
94
|
+
let bakeSelectionsJson = bakedManifest !== undefined ? JSON.stringify(encodeSelections(bakedManifest.components.map(Platform_BakedManifest$ReventlessCore.toSelection))) : "";
|
|
95
|
+
let journeys = Stdlib_Option.mapOr(bakedManifest, [], Platform_BakedManifest$ReventlessCore.journeyFiles);
|
|
96
|
+
let bakeJourneysJson = journeys.length !== 0 ? JSON.stringify(journeys.map(j => Object.fromEntries([
|
|
97
|
+
[
|
|
98
|
+
"group",
|
|
99
|
+
j.group
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"key",
|
|
103
|
+
j.key
|
|
104
|
+
],
|
|
105
|
+
[
|
|
106
|
+
"components",
|
|
107
|
+
encodeSelections(j.selections)
|
|
108
|
+
]
|
|
109
|
+
]))) : "";
|
|
110
|
+
let adminEntryJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(Platform_Admin_Structure$ReventlessCore.pluginId, undefined, Platform_Admin_Structure$ReventlessCore.structure));
|
|
93
111
|
let packageDirs = Object.fromEntries([[
|
|
94
112
|
"@reventlessdev/reventless-aws",
|
|
95
113
|
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
@@ -127,6 +145,10 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, bak
|
|
|
127
145
|
"BAKE_SELECTIONS",
|
|
128
146
|
bakeSelectionsJson
|
|
129
147
|
],
|
|
148
|
+
[
|
|
149
|
+
"BAKE_JOURNEYS",
|
|
150
|
+
bakeJourneysJson
|
|
151
|
+
],
|
|
130
152
|
[
|
|
131
153
|
"NODE_OPTIONS",
|
|
132
154
|
Util_Bundle$ReventlessAws.esmLoaderNodeOptions
|
|
@@ -288,6 +288,18 @@ let structureOf = (item: dict<JSON.t>, ~name as _: string): option<(
|
|
|
288
288
|
| _ => None
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
// One curated surface per audience, beside the default one. Only what the write
|
|
292
|
+
// needs travels: the file's key, resolved by the deploy so that the grant, the
|
|
293
|
+
// URL in `config.json` and the object written here are one string, and the
|
|
294
|
+
// include-list that decides its contents. The group the journey serves is
|
|
295
|
+
// carried too, purely so the invocation's report names the audience a file was
|
|
296
|
+
// written for.
|
|
297
|
+
type journey = {
|
|
298
|
+
group: string,
|
|
299
|
+
key: string,
|
|
300
|
+
selections: array<ReventlessCore.Platform_BakedManifest.selection>,
|
|
301
|
+
}
|
|
302
|
+
|
|
291
303
|
let bakeSelection = (json: JSON.t): option<ReventlessCore.Platform_BakedManifest.selection> => {
|
|
292
304
|
let strings = (o, key) =>
|
|
293
305
|
o
|
|
@@ -304,10 +316,14 @@ let bakeSelection = (json: JSON.t): option<ReventlessCore.Platform_BakedManifest
|
|
|
304
316
|
ReventlessCore.Platform_BakedManifest.plugin,
|
|
305
317
|
views: strings(o, "views"),
|
|
306
318
|
commands: strings(o, "commands"),
|
|
319
|
+
derived: strings(o, "derived"),
|
|
307
320
|
})
|
|
308
321
|
)
|
|
309
322
|
}
|
|
310
323
|
|
|
324
|
+
let decodeSelections = (json: JSON.t): array<ReventlessCore.Platform_BakedManifest.selection> =>
|
|
325
|
+
json->JSON.Decode.array->Option.getOr([])->Array.filterMap(bakeSelection)
|
|
326
|
+
|
|
311
327
|
// The declaration travels as an env var because it is deploy input — stated once
|
|
312
328
|
// in the platform program, beside the bucket it is written to. The target travels
|
|
313
329
|
// in the invocation payload instead: the bucket only exists inside the host-UI
|
|
@@ -318,14 +334,44 @@ let bakeSelections = (): array<ReventlessCore.Platform_BakedManifest.selection>
|
|
|
318
334
|
switch NodeProcess.env->Dict.get("BAKE_SELECTIONS") {
|
|
319
335
|
| None | Some("") => []
|
|
320
336
|
| Some(raw) =>
|
|
321
|
-
switch raw->JSON.parseOrThrow
|
|
322
|
-
|
|
|
323
|
-
| None => []
|
|
337
|
+
switch raw->JSON.parseOrThrow {
|
|
338
|
+
| json => json->decodeSelections
|
|
324
339
|
| exception _ =>
|
|
325
340
|
JsError.throwWithMessage("baked manifest: BAKE_SELECTIONS is not a JSON array")
|
|
326
341
|
}
|
|
327
342
|
}
|
|
328
343
|
|
|
344
|
+
let bakeJourney = (json: JSON.t): option<journey> =>
|
|
345
|
+
json
|
|
346
|
+
->JSON.Decode.object
|
|
347
|
+
->Option.flatMap(o =>
|
|
348
|
+
switch (
|
|
349
|
+
o->Dict.get("group")->Option.flatMap(JSON.Decode.string),
|
|
350
|
+
o->Dict.get("key")->Option.flatMap(JSON.Decode.string),
|
|
351
|
+
) {
|
|
352
|
+
| (Some(group), Some(key)) =>
|
|
353
|
+
Some({
|
|
354
|
+
group,
|
|
355
|
+
key,
|
|
356
|
+
selections: o->Dict.get("components")->Option.mapOr([], decodeSelections),
|
|
357
|
+
})
|
|
358
|
+
| _ => None
|
|
359
|
+
}
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
// Absent for every deployment that declares one audience, which is what the
|
|
363
|
+
// declaration meant before journeys existed and what it still means.
|
|
364
|
+
let bakeJourneys = (): array<journey> =>
|
|
365
|
+
switch NodeProcess.env->Dict.get("BAKE_JOURNEYS") {
|
|
366
|
+
| None | Some("") => []
|
|
367
|
+
| Some(raw) =>
|
|
368
|
+
switch raw->JSON.parseOrThrow->JSON.Decode.array {
|
|
369
|
+
| Some(entries) => entries->Array.filterMap(bakeJourney)
|
|
370
|
+
| None => []
|
|
371
|
+
| exception _ => JsError.throwWithMessage("baked manifest: BAKE_JOURNEYS is not a JSON array")
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
329
375
|
type bakeTarget = {bucket: string, key: string}
|
|
330
376
|
|
|
331
377
|
let bakeTargetOf = (event: JSON.t): option<bakeTarget> =>
|
|
@@ -416,27 +462,49 @@ let runBake = async (
|
|
|
416
462
|
"so there is nothing to write and a shell pointed at the file would find none.",
|
|
417
463
|
)
|
|
418
464
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
465
|
+
// The default journey first and always — the caller supplies its key, because
|
|
466
|
+
// the caller is what knows where the shell it configured fetches from — then
|
|
467
|
+
// one file per declared journey, under the key the deploy resolved.
|
|
468
|
+
let files = Array.concat(
|
|
469
|
+
[(None, target.key, selections)],
|
|
470
|
+
bakeJourneys()->Array.map(j => (Some(j.group), j.key, j.selections)),
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
// Every file is curated before any is written, for the reason the in-memory
|
|
474
|
+
// platform curates before it writes: a declaration naming a component that
|
|
475
|
+
// does not exist fails the bake, and failing it halfway would leave the bucket
|
|
476
|
+
// serving one audience's file beside a stale copy of another's.
|
|
477
|
+
let curated = files->Array.map(((group, key, selections)) => (
|
|
478
|
+
group,
|
|
479
|
+
key,
|
|
480
|
+
selections,
|
|
481
|
+
switch ReventlessCore.Platform_BakedManifest.curate(~structures, ~selections) {
|
|
482
|
+
| Error(e) => JsError.throwWithMessage(ReventlessCore.Platform_BakedManifest.describe(e))
|
|
483
|
+
| Ok(manifest) => JSON.stringify(manifest, ~space=2)
|
|
484
|
+
},
|
|
485
|
+
))
|
|
486
|
+
|
|
487
|
+
await Promise.all(
|
|
488
|
+
curated->Array.map(async ((group, key, selections, body)) => {
|
|
489
|
+
let _ = await AwsSdk.S3.PutObjectCommand.make({
|
|
490
|
+
bucket: target.bucket,
|
|
491
|
+
key,
|
|
492
|
+
body: AwsSdk.S3.PutObjectCommand.bodyFromString(body),
|
|
493
|
+
contentType: "application/json",
|
|
494
|
+
})->AwsSdk.S3.PutObjectCommand.send
|
|
495
|
+
let report = Dict.fromArray([
|
|
432
496
|
("baked", JSON.Encode.bool(true)),
|
|
433
497
|
("bucket", JSON.Encode.string(target.bucket)),
|
|
434
|
-
("key", JSON.Encode.string(
|
|
498
|
+
("key", JSON.Encode.string(key)),
|
|
435
499
|
("plugins", JSON.Encode.int(selections->Array.length)),
|
|
436
500
|
("bytes", JSON.Encode.int(body->String.length)),
|
|
437
|
-
])
|
|
438
|
-
|
|
439
|
-
|
|
501
|
+
])
|
|
502
|
+
// Only a journey's entry carries the audience it was written for, so the
|
|
503
|
+
// default one reports exactly what it reported before journeys existed.
|
|
504
|
+
group->Option.forEach(g => report->Dict.set("group", JSON.Encode.string(g)))
|
|
505
|
+
report->JSON.Encode.object
|
|
506
|
+
}),
|
|
507
|
+
)
|
|
440
508
|
}
|
|
441
509
|
|
|
442
510
|
let handler = async (event: JSON.t): array<JSON.t> => {
|
|
@@ -263,10 +263,15 @@ function bakeSelection(json) {
|
|
|
263
263
|
return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), o => Stdlib_Option.map(Stdlib_Option.flatMap(o["plugin"], Stdlib_JSON.Decode.string), plugin => ({
|
|
264
264
|
plugin: plugin,
|
|
265
265
|
views: strings(o, "views"),
|
|
266
|
-
commands: strings(o, "commands")
|
|
266
|
+
commands: strings(o, "commands"),
|
|
267
|
+
derived: strings(o, "derived")
|
|
267
268
|
})));
|
|
268
269
|
}
|
|
269
270
|
|
|
271
|
+
function decodeSelections(json) {
|
|
272
|
+
return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_JSON.Decode.array(json), []), bakeSelection);
|
|
273
|
+
}
|
|
274
|
+
|
|
270
275
|
function bakeSelections() {
|
|
271
276
|
let raw = process.env["BAKE_SELECTIONS"];
|
|
272
277
|
if (raw === undefined) {
|
|
@@ -275,14 +280,45 @@ function bakeSelections() {
|
|
|
275
280
|
if (raw === "") {
|
|
276
281
|
return [];
|
|
277
282
|
}
|
|
283
|
+
let json;
|
|
284
|
+
try {
|
|
285
|
+
json = JSON.parse(raw);
|
|
286
|
+
} catch (exn) {
|
|
287
|
+
return Stdlib_JsError.throwWithMessage("baked manifest: BAKE_SELECTIONS is not a JSON array");
|
|
288
|
+
}
|
|
289
|
+
return decodeSelections(json);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function bakeJourney(json) {
|
|
293
|
+
return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), o => {
|
|
294
|
+
let match = Stdlib_Option.flatMap(o["group"], Stdlib_JSON.Decode.string);
|
|
295
|
+
let match$1 = Stdlib_Option.flatMap(o["key"], Stdlib_JSON.Decode.string);
|
|
296
|
+
if (match !== undefined && match$1 !== undefined) {
|
|
297
|
+
return {
|
|
298
|
+
group: match,
|
|
299
|
+
key: match$1,
|
|
300
|
+
selections: Stdlib_Option.mapOr(o["components"], [], decodeSelections)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function bakeJourneys() {
|
|
307
|
+
let raw = process.env["BAKE_JOURNEYS"];
|
|
308
|
+
if (raw === undefined) {
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
if (raw === "") {
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
278
314
|
let entries;
|
|
279
315
|
try {
|
|
280
316
|
entries = Stdlib_JSON.Decode.array(JSON.parse(raw));
|
|
281
317
|
} catch (exn) {
|
|
282
|
-
return Stdlib_JsError.throwWithMessage("baked manifest:
|
|
318
|
+
return Stdlib_JsError.throwWithMessage("baked manifest: BAKE_JOURNEYS is not a JSON array");
|
|
283
319
|
}
|
|
284
320
|
if (entries !== undefined) {
|
|
285
|
-
return Stdlib_Array.filterMap(entries,
|
|
321
|
+
return Stdlib_Array.filterMap(entries, bakeJourney);
|
|
286
322
|
} else {
|
|
287
323
|
return [];
|
|
288
324
|
}
|
|
@@ -335,18 +371,37 @@ async function runBake(target, structures) {
|
|
|
335
371
|
if (selections.length === 0) {
|
|
336
372
|
Stdlib_JsError.throwWithMessage("baked manifest: BAKE_SELECTIONS is empty — this platform declares no bake, so there is nothing to write and a shell pointed at the file would find none.");
|
|
337
373
|
}
|
|
338
|
-
let
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
374
|
+
let files = [[
|
|
375
|
+
undefined,
|
|
376
|
+
target.key,
|
|
377
|
+
selections
|
|
378
|
+
]].concat(bakeJourneys().map(j => [
|
|
379
|
+
j.group,
|
|
380
|
+
j.key,
|
|
381
|
+
j.selections
|
|
382
|
+
]));
|
|
383
|
+
let curated = files.map(param => {
|
|
384
|
+
let selections = param[2];
|
|
385
|
+
let e = Platform_BakedManifest$ReventlessCore.curate(structures, selections);
|
|
386
|
+
let tmp;
|
|
387
|
+
tmp = e.TAG === "Ok" ? JSON.stringify(e._0, undefined, 2) : Stdlib_JsError.throwWithMessage(Platform_BakedManifest$ReventlessCore.describe(e._0));
|
|
388
|
+
return [
|
|
389
|
+
param[0],
|
|
390
|
+
param[1],
|
|
391
|
+
selections,
|
|
392
|
+
tmp
|
|
393
|
+
];
|
|
394
|
+
});
|
|
395
|
+
return await Promise.all(curated.map(async param => {
|
|
396
|
+
let body = param[3];
|
|
397
|
+
let key = param[1];
|
|
398
|
+
await S3$AwsSdk.PutObjectCommand.send(new ClientS3.PutObjectCommand({
|
|
399
|
+
Bucket: target.bucket,
|
|
400
|
+
Key: key,
|
|
401
|
+
Body: body,
|
|
402
|
+
ContentType: "application/json"
|
|
403
|
+
}));
|
|
404
|
+
let report = Object.fromEntries([
|
|
350
405
|
[
|
|
351
406
|
"baked",
|
|
352
407
|
true
|
|
@@ -357,17 +412,22 @@ async function runBake(target, structures) {
|
|
|
357
412
|
],
|
|
358
413
|
[
|
|
359
414
|
"key",
|
|
360
|
-
|
|
415
|
+
key
|
|
361
416
|
],
|
|
362
417
|
[
|
|
363
418
|
"plugins",
|
|
364
|
-
|
|
419
|
+
param[2].length
|
|
365
420
|
],
|
|
366
421
|
[
|
|
367
422
|
"bytes",
|
|
368
423
|
body.length
|
|
369
424
|
]
|
|
370
|
-
])
|
|
425
|
+
]);
|
|
426
|
+
Stdlib_Option.forEach(param[0], g => {
|
|
427
|
+
report["group"] = g;
|
|
428
|
+
});
|
|
429
|
+
return report;
|
|
430
|
+
}));
|
|
371
431
|
}
|
|
372
432
|
|
|
373
433
|
async function handler(event) {
|
|
@@ -432,7 +492,10 @@ export {
|
|
|
432
492
|
isComplete,
|
|
433
493
|
structureOf,
|
|
434
494
|
bakeSelection,
|
|
495
|
+
decodeSelections,
|
|
435
496
|
bakeSelections,
|
|
497
|
+
bakeJourney,
|
|
498
|
+
bakeJourneys,
|
|
436
499
|
bakeTargetOf,
|
|
437
500
|
bakeExpectations,
|
|
438
501
|
structureRefKey,
|
|
@@ -40,6 +40,15 @@ type storeTable = {
|
|
|
40
40
|
// JS resolver code (APPSYNC_JS runtime): forward the caller's arguments and the
|
|
41
41
|
// authorizer-verified identity. `sub` is what keys the row, and it comes from
|
|
42
42
|
// here — the mutation has no `sub` argument for a client to supply.
|
|
43
|
+
//
|
|
44
|
+
// `username` travels with it because the two answer different questions. The row
|
|
45
|
+
// is keyed on `sub`, which is stable across a rename and is what the pre-token
|
|
46
|
+
// trigger looks the row up by; but Cognito's admin API addresses a user by
|
|
47
|
+
// *username*, and a pool with neither `UsernameAttributes` nor `AliasAttributes`
|
|
48
|
+
// does not accept a `sub` there — it answers `UserNotFoundException`, which is
|
|
49
|
+
// how the membership read used to fail for every caller on such a pool. Both
|
|
50
|
+
// fields come from the authorizer, so forwarding the second grants nothing the
|
|
51
|
+
// first did not.
|
|
43
52
|
let invokeCode: Pulumi.Input.t<string> = `import { util } from '@aws-appsync/utils';
|
|
44
53
|
export function request(ctx) {
|
|
45
54
|
const id = ctx.identity;
|
|
@@ -47,7 +56,7 @@ export function request(ctx) {
|
|
|
47
56
|
operation: 'Invoke',
|
|
48
57
|
payload: {
|
|
49
58
|
arguments: ctx.args,
|
|
50
|
-
identity: id != null && id.sub != null ? { sub: id.sub } : null
|
|
59
|
+
identity: id != null && id.sub != null ? { sub: id.sub, username: id.username ?? null } : null
|
|
51
60
|
}
|
|
52
61
|
};
|
|
53
62
|
}
|
|
@@ -22,7 +22,7 @@ export function request(ctx) {
|
|
|
22
22
|
operation: 'Invoke',
|
|
23
23
|
payload: {
|
|
24
24
|
arguments: ctx.args,
|
|
25
|
-
identity: id != null && id.sub != null ? { sub: id.sub } : null
|
|
25
|
+
identity: id != null && id.sub != null ? { sub: id.sub, username: id.username ?? null } : null
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
}
|