@reventlessdev/reventless-aws 3.0.0-alpha.345 → 3.0.0-alpha.347

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.
Files changed (112) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/package.json +19 -12
  3. package/rescript.json +2 -1
  4. package/run-bake-manifest.mjs +3 -0
  5. package/run-deploy-app.mjs +3 -0
  6. package/run-provision-accounts.mjs +3 -0
  7. package/run-provision-admin.mjs +3 -0
  8. package/scripts/BakeManifest.res +395 -0
  9. package/scripts/BakeManifest.res.mjs +487 -0
  10. package/scripts/DeployApp.res +708 -0
  11. package/scripts/DeployApp.res.mjs +1015 -0
  12. package/scripts/DeployManifest.res +84 -0
  13. package/scripts/DeployManifest.res.mjs +112 -0
  14. package/scripts/ProvisionAccounts.res +339 -0
  15. package/scripts/ProvisionAccounts.res.mjs +351 -0
  16. package/scripts/ProvisionAdmin.res +275 -0
  17. package/scripts/ProvisionAdmin.res.mjs +289 -0
  18. package/scripts/ProvisionCognito.res +149 -0
  19. package/scripts/ProvisionCognito.res.mjs +98 -0
  20. package/scripts/ProvisionIdentity.res +76 -11
  21. package/scripts/ProvisionIdentity.res.mjs +72 -7
  22. package/scripts/ProvisionProvider.res +104 -0
  23. package/scripts/ProvisionProvider.res.mjs +123 -0
  24. package/scripts/PulumiCli.res +43 -0
  25. package/scripts/PulumiCli.res.mjs +106 -0
  26. package/src/Platform.res +51 -4
  27. package/src/Platform.res.mjs +32 -14
  28. package/src/Platform_Stack.res +119 -9
  29. package/src/Platform_Stack.res.mjs +42 -4
  30. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -5
  31. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
  32. package/src/adapter/Api/Platform_UIFragments_Lambda.res +1 -5
  33. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +1 -2
  34. package/src/adapter/Auth/Auth_ActiveRoleStore.res +1 -5
  35. package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +1 -2
  36. package/src/adapter/Auth/Auth_ActiveRoleTrigger.res +1 -5
  37. package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +1 -2
  38. package/src/adapter/Auth/Auth_LoginIdentifier.res +66 -0
  39. package/src/adapter/Auth/Auth_LoginIdentifier.res.mjs +70 -0
  40. package/src/adapter/Auth/Auth_SignUpMode.res +78 -0
  41. package/src/adapter/Auth/Auth_SignUpMode.res.mjs +56 -0
  42. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +52 -31
  43. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +75 -44
  44. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +1 -5
  45. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +1 -2
  46. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +1 -5
  47. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +1 -2
  48. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +5 -4
  49. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +1 -1
  50. package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
  51. package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -2
  52. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +5 -0
  53. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +5 -1
  54. package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res +2 -0
  55. package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res.mjs +2 -2
  56. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +15 -6
  57. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +51 -30
  58. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +31 -14
  59. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -5
  60. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -1
  61. package/src/adapter/StateTopic/StateTopic_AppSync.res +1 -5
  62. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +1 -1
  63. package/src/adapter/Task/TaskBucket_S3.res +1 -3
  64. package/src/adapter/Task/TaskBucket_S3.res.mjs +1 -1
  65. package/src/adapter/Upload/Upload_Claim_S3.res +1 -5
  66. package/src/adapter/Upload/Upload_Claim_S3.res.mjs +1 -1
  67. package/src/adapter/Upload/Upload_Presign_S3.res +1 -5
  68. package/src/adapter/Upload/Upload_Presign_S3.res.mjs +1 -2
  69. package/src/capability/Capability_Geocoding_AwsLocation.res +5 -1
  70. package/src/capability/Capability_Geocoding_AwsLocation.res.mjs +7 -1
  71. package/src/components/Api/AppSync_Adapter.res +56 -72
  72. package/src/components/Api/AppSync_Adapter.res.mjs +26 -60
  73. package/src/components/Api/AppSync_SdlDecorate.res +82 -1
  74. package/src/components/Api/AppSync_SdlDecorate.res.mjs +45 -0
  75. package/src/plugin/cloner/ClonerRunner_Fargate.res +1 -5
  76. package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +1 -1
  77. package/src/plugin/stack/Plugin_Stack.res +4 -0
  78. package/src/plugin/stack/Plugin_Stack.res.mjs +2 -0
  79. package/src/util/Util_DeadLetterQueue.res +1 -5
  80. package/src/util/Util_DeadLetterQueue.res.mjs +1 -2
  81. package/src/util/Util_StoreLayout.res +15 -2
  82. package/src/util/Util_StoreLayout.res.mjs +11 -3
  83. package/tests/AppSync_AdapterTest.res +83 -0
  84. package/tests/AppSync_AdapterTest.res.mjs +69 -0
  85. package/tests/Auth_LoginIdentifierTest.res +51 -0
  86. package/tests/Auth_LoginIdentifierTest.res.mjs +51 -0
  87. package/tests/Auth_SignUpModeTest.res +56 -0
  88. package/tests/Auth_SignUpModeTest.res.mjs +53 -0
  89. package/tests/BakeManifestTest.res +204 -0
  90. package/tests/BakeManifestTest.res.mjs +259 -0
  91. package/tests/DcbColdStartPartitionTest.res +32 -0
  92. package/tests/DcbColdStartPartitionTest.res.mjs +33 -0
  93. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +88 -0
  94. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +112 -0
  95. package/tests/DcbUnresolvedPartitionSlice.res +28 -0
  96. package/tests/DcbUnresolvedPartitionSlice.res.mjs +38 -0
  97. package/tests/DeployAppTest.res +90 -0
  98. package/tests/DeployAppTest.res.mjs +133 -0
  99. package/tests/LambdaLayerLookupTest.res +104 -0
  100. package/tests/LambdaLayerLookupTest.res.mjs +117 -0
  101. package/tests/ProvisionAccountsTest.res +168 -0
  102. package/tests/ProvisionAccountsTest.res.mjs +184 -0
  103. package/tests/ProvisionAdminTest.res +55 -0
  104. package/tests/ProvisionAdminTest.res.mjs +66 -0
  105. package/tests/ProvisionIdentityTest.res +89 -3
  106. package/tests/ProvisionIdentityTest.res.mjs +66 -1
  107. package/tests/Util_OwnerScopeEnvTest.res +25 -0
  108. package/tests/Util_OwnerScopeEnvTest.res.mjs +13 -0
  109. package/tests/Util_StoreLayoutTest.res +6 -0
  110. package/tests/Util_StoreLayoutTest.res.mjs +7 -4
  111. package/tests/dcbColdStartPartition.mjs +66 -0
  112. package/tests/setup/layerArn.cjs +5 -0
@@ -328,6 +328,11 @@ let capabilities = (): Reventless.Capabilities.t => {
328
328
  ~text,
329
329
  ),
330
330
  messaging: messagingEmailProvider(),
331
+ // Refusing until the Cognito backend lands. The pool is provisioned, but the
332
+ // store mapping a domain user id to a provider handle is not, and creating a
333
+ // principal before that exists would write a handle with nowhere to live.
334
+ identityProvider: Reventless.Capabilities.none.identityProvider,
335
+ secrets: ReventlessCore.Secrets_Node_Backend.provider,
331
336
  }
332
337
 
333
338
  // ── Phase-1/phase-2 pipelines ───────────────────────────────────────────────
@@ -12,9 +12,11 @@ import * as DcbDecode$Reventless from "@reventlessdev/reventless-spec/src/compon
12
12
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
13
13
  import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
14
14
  import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
15
+ import * as Capabilities$Reventless from "@reventlessdev/reventless-spec/src/semantic/Capabilities.res.mjs";
15
16
  import * as EffectLogger$ReventlessCore from "@reventlessdev/reventless-core/src/util/EffectLogger.res.mjs";
16
17
  import * as DynamoDb_Error$ReventlessAws from "../../errors/DynamoDb_Error.res.mjs";
17
18
  import * as Messaging_Ses_Backend$ReventlessAws from "../Messaging/Messaging_Ses_Backend.res.mjs";
19
+ import * as Secrets_Node_Backend$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Secrets/Secrets_Node_Backend.res.mjs";
18
20
  import * as Util_DynamoDb_Runtime$ReventlessAws from "../../util/Util_DynamoDb_Runtime.res.mjs";
19
21
  import * as Messaging_Log_Backend$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Messaging/Messaging_Log_Backend.res.mjs";
20
22
  import * as AutomationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs";
@@ -199,7 +201,9 @@ function messagingEmailProvider() {
199
201
  function capabilities() {
200
202
  return {
201
203
  geocode: text => Geocoder_AwsLocation_Backend$ReventlessAws.search(Stdlib_Option.getOr(process.env["PLACE_INDEX_NAME"], ""), text, undefined),
202
- messaging: messagingEmailProvider()
204
+ messaging: messagingEmailProvider(),
205
+ identityProvider: Capabilities$Reventless.none.identityProvider,
206
+ secrets: Secrets_Node_Backend$ReventlessCore.provider
203
207
  };
204
208
  }
205
209
 
@@ -22,6 +22,7 @@ let makeCommandGenerator = (
22
22
  ~commandSchema,
23
23
  ~componentKind: ReventlessCore.CommandGenerator_Callback.commandComponentKind,
24
24
  ~stripIdFromParams: bool,
25
+ ~partitionTag: option<Reventless.DcbTag.derivedPartitionTag>=?,
25
26
  ): ReventlessCore.CommandGenerator.commandGenerator =>
26
27
  ReventlessCore.CommandGenerator_Callback.makeGenerateCommand(
27
28
  ~publishJsons,
@@ -30,4 +31,5 @@ let makeCommandGenerator = (
30
31
  ~commandSchema,
31
32
  ~componentKind,
32
33
  ~stripIdFromParams,
34
+ ~partitionTag?,
33
35
  )
@@ -2,8 +2,8 @@
2
2
 
3
3
  import * as CommandGenerator_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs";
4
4
 
5
- function makeCommandGenerator(publishJsons, publishJsonsAndWait, serviceName, commandSchema, componentKind, stripIdFromParams) {
6
- return CommandGenerator_Callback$ReventlessCore.makeGenerateCommand(publishJsons, publishJsonsAndWait, serviceName, commandSchema, componentKind, stripIdFromParams);
5
+ function makeCommandGenerator(publishJsons, publishJsonsAndWait, serviceName, commandSchema, componentKind, stripIdFromParams, partitionTag) {
6
+ return CommandGenerator_Callback$ReventlessCore.makeGenerateCommand(publishJsons, publishJsonsAndWait, serviceName, commandSchema, componentKind, stripIdFromParams, partitionTag);
7
7
  }
8
8
 
9
9
  export {
@@ -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/partitionTag derivation, storage-ops wiring, and
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
- tagKeysByEventType,
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(commandSchemasByType[typeName]);
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 tag for one consistency boundary,
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
- let scope = Reventless.DcbTag.deriveEffectiveScope(
52
- specs->Array.map(s => {
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
- // `derivePartitionTag` throws only on a misconfigured spec, which the deploy
61
- // would already have rejected; degrade to untagged fences rather than crash
62
- // cold start (matches the shell's prior defensive behaviour).
63
- let partitionTag = try Some(
64
- Reventless.DcbTag.derivePartitionTag(
65
- specs->Array.map(s => (
66
- specName(s),
67
- specModuleUrl(s)->Nullable.toOption->Option.getOr(specName(s)),
68
- specEventSchema(s),
69
- )),
70
- ),
71
- ) catch {
72
- | _ => None
73
- }
74
-
75
- {
76
- crossPartitionTagKeys: scope.crossPartitionTagKeys,
77
- tagKeysByEventType: scope.tagKeysByEventType,
78
- partitionTag,
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
- ~tagKeysByEventType: dict<array<string>>,
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(~tagKeysByEventType, ~crossPartitionTagKeys, dcbEventLog, decodedStream)
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
- let scope = DcbTag$Reventless.deriveEffectiveScope(specs.map(s => ({
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
- let partitionTag;
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
- partitionTag = DcbTag$Reventless.derivePartitionTag(specs.map(s => [
32
- s.name,
33
- Stdlib_Option.getOr(Primitive_option.fromNullable(s.moduleUrl), s.name),
34
- s.eventSchema
35
- ]));
36
- } catch (exn) {
37
- partitionTag = undefined;
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, tagKeysByEventType, crossPartitionTagKeys, dcbEventLog) {
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 = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
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 = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
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.protectionFor(Pulumi.getStack(), undefined) === "Unprotected";
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 = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
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 = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
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
- indexName: Pulumi.Input.make(name),
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 */