@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 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.300",
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-aws-sdk": "3.0.0-alpha.10",
18
- "@reventlessdev/rescript-node": "2.0.0-alpha.6",
19
- "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.75",
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.141",
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.98",
25
- "@reventlessdev/reventless-core": "3.0.0-alpha.234",
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
- let manifestKey = bake.key->Option.getOr(
2225
- ReventlessCore.Platform_BakedManifest.defaultKey,
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
- resources: Resource(`arn:aws:s3:::${bucket}/${manifestKey}`),
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
  )
@@ -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 manifestKey = Stdlib_Option.getOr(match$5.key, Platform_BakedManifest$ReventlessCore.defaultKey);
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: `arn:aws:s3:::` + param[1] + `/` + manifestKey
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 manifestKey = Stdlib_Option.getOr(match$5.key, Platform_BakedManifest$ReventlessCore.defaultKey);
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: `arn:aws:s3:::` + param[1] + `/` + manifestKey
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 bakeSelectionsJson = switch bakedManifest {
145
- | None => ""
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 bakeSelectionsJson = bakedManifest !== undefined ? JSON.stringify(bakedManifest.components.map(sel => {
81
- let entry = Object.fromEntries([[
82
- "plugin",
83
- sel.plugin
84
- ]]);
85
- let strings = (key, value) => Stdlib_Option.forEach(value, names => {
86
- entry[key] = names.map(prim => prim);
87
- });
88
- strings("views", sel.views);
89
- strings("commands", sel.commands);
90
- return entry;
91
- })) : "";
92
- let adminEntryJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(Platform_Admin_Structure$ReventlessCore.pluginId, Platform_Admin_Structure$ReventlessCore.structure));
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->JSON.Decode.array {
322
- | Some(entries) => entries->Array.filterMap(bakeSelection)
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
- switch ReventlessCore.Platform_BakedManifest.curate(~structures, ~selections) {
420
- | Error(e) =>
421
- JsError.throwWithMessage(ReventlessCore.Platform_BakedManifest.describe(e))
422
- | Ok(manifest) =>
423
- let body = JSON.stringify(manifest, ~space=2)
424
- let _ = await AwsSdk.S3.PutObjectCommand.make({
425
- bucket: target.bucket,
426
- key: target.key,
427
- body: AwsSdk.S3.PutObjectCommand.bodyFromString(body),
428
- contentType: "application/json",
429
- })->AwsSdk.S3.PutObjectCommand.send
430
- [
431
- Dict.fromArray([
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(target.key)),
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
- ])->JSON.Encode.object,
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: BAKE_SELECTIONS is not a JSON array");
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, bakeSelection);
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 e = Platform_BakedManifest$ReventlessCore.curate(structures, selections);
339
- if (e.TAG !== "Ok") {
340
- return Stdlib_JsError.throwWithMessage(Platform_BakedManifest$ReventlessCore.describe(e._0));
341
- }
342
- let body = JSON.stringify(e._0, undefined, 2);
343
- await S3$AwsSdk.PutObjectCommand.send(new ClientS3.PutObjectCommand({
344
- Bucket: target.bucket,
345
- Key: target.key,
346
- Body: body,
347
- ContentType: "application/json"
348
- }));
349
- return [Object.fromEntries([
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
- target.key
415
+ key
361
416
  ],
362
417
  [
363
418
  "plugins",
364
- selections.length
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
  }