@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.
Files changed (80) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/package.json +17 -13
  3. package/rescript.json +2 -1
  4. package/run-bake-manifest.mjs +3 -0
  5. package/run-deploy-app.mjs +3 -0
  6. package/scripts/BakeManifest.res +395 -0
  7. package/scripts/BakeManifest.res.mjs +487 -0
  8. package/scripts/DeployApp.res +708 -0
  9. package/scripts/DeployApp.res.mjs +1015 -0
  10. package/scripts/DeployManifest.res +84 -0
  11. package/scripts/DeployManifest.res.mjs +112 -0
  12. package/scripts/ProvisionAccounts.res +60 -46
  13. package/scripts/ProvisionAccounts.res.mjs +51 -37
  14. package/scripts/PulumiCli.res +43 -0
  15. package/scripts/PulumiCli.res.mjs +106 -0
  16. package/src/Platform.res +19 -2
  17. package/src/Platform.res.mjs +12 -4
  18. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -5
  19. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
  20. package/src/adapter/Api/Platform_UIFragments_Lambda.res +1 -5
  21. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +1 -2
  22. package/src/adapter/Auth/Auth_ActiveRoleStore.res +1 -5
  23. package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +1 -2
  24. package/src/adapter/Auth/Auth_ActiveRoleTrigger.res +1 -5
  25. package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +1 -2
  26. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +52 -31
  27. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +75 -44
  28. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +1 -5
  29. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +1 -2
  30. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +1 -5
  31. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +1 -2
  32. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +5 -4
  33. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +1 -1
  34. package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
  35. package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -2
  36. package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res +2 -0
  37. package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res.mjs +2 -2
  38. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +15 -6
  39. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +51 -30
  40. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +31 -14
  41. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -5
  42. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -1
  43. package/src/adapter/StateTopic/StateTopic_AppSync.res +1 -5
  44. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +1 -1
  45. package/src/adapter/Task/TaskBucket_S3.res +1 -3
  46. package/src/adapter/Task/TaskBucket_S3.res.mjs +1 -1
  47. package/src/adapter/Upload/Upload_Claim_S3.res +1 -5
  48. package/src/adapter/Upload/Upload_Claim_S3.res.mjs +1 -1
  49. package/src/adapter/Upload/Upload_Presign_S3.res +1 -5
  50. package/src/adapter/Upload/Upload_Presign_S3.res.mjs +1 -2
  51. package/src/capability/Capability_Geocoding_AwsLocation.res +5 -1
  52. package/src/capability/Capability_Geocoding_AwsLocation.res.mjs +7 -1
  53. package/src/components/Api/AppSync_SdlDecorate.res +4 -2
  54. package/src/components/Api/AppSync_SdlDecorate.res.mjs +1 -1
  55. package/src/plugin/cloner/ClonerRunner_Fargate.res +1 -5
  56. package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +1 -1
  57. package/src/plugin/stack/Plugin_Stack.res +4 -0
  58. package/src/plugin/stack/Plugin_Stack.res.mjs +2 -0
  59. package/src/util/Util_DeadLetterQueue.res +1 -5
  60. package/src/util/Util_DeadLetterQueue.res.mjs +1 -2
  61. package/src/util/Util_StoreLayout.res +15 -2
  62. package/src/util/Util_StoreLayout.res.mjs +11 -3
  63. package/tests/BakeManifestTest.res +204 -0
  64. package/tests/BakeManifestTest.res.mjs +259 -0
  65. package/tests/DcbColdStartPartitionTest.res +32 -0
  66. package/tests/DcbColdStartPartitionTest.res.mjs +33 -0
  67. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +88 -0
  68. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +112 -0
  69. package/tests/DcbUnresolvedPartitionSlice.res +28 -0
  70. package/tests/DcbUnresolvedPartitionSlice.res.mjs +38 -0
  71. package/tests/DeployAppTest.res +90 -0
  72. package/tests/DeployAppTest.res.mjs +133 -0
  73. package/tests/LambdaLayerLookupTest.res +104 -0
  74. package/tests/LambdaLayerLookupTest.res.mjs +117 -0
  75. package/tests/ProvisionAccountsTest.res +26 -0
  76. package/tests/ProvisionAccountsTest.res.mjs +31 -0
  77. package/tests/Util_StoreLayoutTest.res +6 -0
  78. package/tests/Util_StoreLayoutTest.res.mjs +7 -4
  79. package/tests/dcbColdStartPartition.mjs +66 -0
  80. package/tests/setup/layerArn.cjs +5 -0
@@ -873,7 +873,7 @@ function MakeWithConfig(Config) {
873
873
  }
874
874
  });
875
875
  PluginRuntime_Builder$ReventlessAws.registerConfig(pluginEpEventTopicArn, pluginReadModelTableName, hooks_schedulerRoleUrn.contents, undefined, undefined, domainApiId, Config.cloner, undefined);
876
- let offloadProtection = Util_StoreLayout$ReventlessAws.protectionFor(Pulumi.getStack(), undefined);
876
+ let offloadProtection = Util_StoreLayout$ReventlessAws.protectionOfDeployedStack();
877
877
  let offloadBucket = new (Aws.s3.Bucket)("reventless-offload", {
878
878
  forceDestroy: offloadProtection === "Unprotected",
879
879
  tags: AWS_Tags$ReventlessAws.make("reventless-offload", "Platform", {
@@ -892,6 +892,7 @@ function MakeWithConfig(Config) {
892
892
  restrictPublicBuckets: true
893
893
  });
894
894
  Pulumi$Pulumi.$$export("offloadBucket", offloadBucketName);
895
+ Pulumi$Pulumi.$$export("platformStack", Pulumi.output(Pulumi.getOrganization() + `/` + Pulumi.getProject() + `/` + Pulumi.getStack()));
895
896
  let componentDefinitions = pluginReadModelTableName !== undefined ? (AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName), Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, admin.adminSchemaPushed, Stdlib_Option.flatMap(hostUiBundle, cfg => cfg.bakedManifest), {})) : undefined;
896
897
  let rm = admin.stateViewSlicesOutputs["UiFragments"];
897
898
  if (rm !== undefined) {
@@ -969,7 +970,7 @@ function MakeWithConfig(Config) {
969
970
  });
970
971
  let stackName = Pulumi.getStack();
971
972
  let storeLayout = Util_StoreLayout$ReventlessAws.layoutFor(stackName, Util_HostUiDomain$ReventlessAws.resolveProdStacks());
972
- let storeProtection = Util_StoreLayout$ReventlessAws.protectionFor(stackName, undefined);
973
+ let storeProtection = Util_StoreLayout$ReventlessAws.protectionOfDeployedStack();
973
974
  let declaredStores = Stdlib_Array.reduce(Stdlib_Array.filterMap(capabilities, c => {
974
975
  if (typeof c !== "object") {
975
976
  return;
@@ -1327,6 +1328,9 @@ function MakeWithConfig(Config) {
1327
1328
  let startServers = () => {};
1328
1329
  let deployPlugin = (plugin, apiTargetOpt) => {
1329
1330
  let apiTarget = apiTargetOpt !== undefined ? apiTargetOpt : "Domain";
1331
+ if (Stdlib_Option.isNone(platformStackRef)) {
1332
+ Stdlib_JsError.throwWithMessage(`This plugin stack has no platform:stack setting, so it cannot find its platform. Set it to the platform's stack: pulumi config set platform:stack ` + (Pulumi.getOrganization() + `/<platform project>/` + Pulumi.getStack()));
1333
+ }
1330
1334
  let tmp;
1331
1335
  tmp = apiTarget === "Domain" ? "Domain" : "Platform";
1332
1336
  log.info("Platform:deployPlugin", undefined, `target=` + tmp);
@@ -2260,7 +2264,7 @@ function Make($star) {
2260
2264
  }
2261
2265
  });
2262
2266
  PluginRuntime_Builder$ReventlessAws.registerConfig(pluginEpEventTopicArn, pluginReadModelTableName, hooks_schedulerRoleUrn.contents, undefined, undefined, domainApiId, false, undefined);
2263
- let offloadProtection = Util_StoreLayout$ReventlessAws.protectionFor(Pulumi.getStack(), undefined);
2267
+ let offloadProtection = Util_StoreLayout$ReventlessAws.protectionOfDeployedStack();
2264
2268
  let offloadBucket = new (Aws.s3.Bucket)("reventless-offload", {
2265
2269
  forceDestroy: offloadProtection === "Unprotected",
2266
2270
  tags: AWS_Tags$ReventlessAws.make("reventless-offload", "Platform", {
@@ -2279,6 +2283,7 @@ function Make($star) {
2279
2283
  restrictPublicBuckets: true
2280
2284
  });
2281
2285
  Pulumi$Pulumi.$$export("offloadBucket", offloadBucketName);
2286
+ Pulumi$Pulumi.$$export("platformStack", Pulumi.output(Pulumi.getOrganization() + `/` + Pulumi.getProject() + `/` + Pulumi.getStack()));
2282
2287
  let componentDefinitions = pluginReadModelTableName !== undefined ? (AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName), Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, admin.adminSchemaPushed, Stdlib_Option.flatMap(hostUiBundle, cfg => cfg.bakedManifest), {})) : undefined;
2283
2288
  let rm = admin.stateViewSlicesOutputs["UiFragments"];
2284
2289
  if (rm !== undefined) {
@@ -2350,7 +2355,7 @@ function Make($star) {
2350
2355
  });
2351
2356
  let stackName = Pulumi.getStack();
2352
2357
  let storeLayout = Util_StoreLayout$ReventlessAws.layoutFor(stackName, Util_HostUiDomain$ReventlessAws.resolveProdStacks());
2353
- let storeProtection = Util_StoreLayout$ReventlessAws.protectionFor(stackName, undefined);
2358
+ let storeProtection = Util_StoreLayout$ReventlessAws.protectionOfDeployedStack();
2354
2359
  let declaredStores = Stdlib_Array.reduce(Stdlib_Array.filterMap(capabilities, c => {
2355
2360
  if (typeof c !== "object") {
2356
2361
  return;
@@ -2706,6 +2711,9 @@ function Make($star) {
2706
2711
  let startServers = () => {};
2707
2712
  let deployPlugin = (plugin, apiTargetOpt) => {
2708
2713
  let apiTarget = apiTargetOpt !== undefined ? apiTargetOpt : "Domain";
2714
+ if (Stdlib_Option.isNone(platformStackRef)) {
2715
+ Stdlib_JsError.throwWithMessage(`This plugin stack has no platform:stack setting, so it cannot find its platform. Set it to the platform's stack: pulumi config set platform:stack ` + (Pulumi.getOrganization() + `/<platform project>/` + Pulumi.getStack()));
2716
+ }
2709
2717
  let tmp;
2710
2718
  tmp = apiTarget === "Domain" ? "Domain" : "Platform";
2711
2719
  log.info("Platform:deployPlugin", undefined, `target=` + tmp);
@@ -217,11 +217,7 @@ let make = (
217
217
  ~bundleRuntimeExtensions=false,
218
218
  )
219
219
 
220
- let layers =
221
- Lambda.reventlessLayerArn
222
- ->Option.map(arn => [arn->Pulumi.Input.make])
223
- ->Option.getOr([])
224
- ->Pulumi.Input.make
220
+ let layers = Lambda.reventlessLayers()
225
221
 
226
222
  // Created before the function that writes to it — see
227
223
  // `Util_LambdaLogging.makeManagedLogGroup` for why the ordering matters.
@@ -116,7 +116,7 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, bak
116
116
  "adminEntry.json",
117
117
  adminEntryJson
118
118
  ]]), false);
119
- let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
119
+ let layers = Lambda$PulumiAws.reventlessLayers();
120
120
  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);
121
121
  let lambda = new (Aws.lambda.Function)(name + "Lambda", {
122
122
  handler: "index.handler",
@@ -89,11 +89,7 @@ let make = (
89
89
  ~bundleRuntimeExtensions=false,
90
90
  )
91
91
 
92
- let layers =
93
- Lambda.reventlessLayerArn
94
- ->Option.map(arn => [arn->Pulumi.Input.make])
95
- ->Option.getOr([])
96
- ->Pulumi.Input.make
92
+ let layers = Lambda.reventlessLayers()
97
93
 
98
94
  // Created before the function that writes to it — see
99
95
  // `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";
@@ -53,7 +52,7 @@ function make(api, uiFragmentRegistryTableName, schemaReady, opts) {
53
52
  Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
54
53
  ]]);
55
54
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res.mjs", packageDirs, undefined, false);
56
- let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
55
+ let layers = Lambda$PulumiAws.reventlessLayers();
57
56
  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);
58
57
  let lambda = new (Aws.lambda.Function)(name + "Lambda", {
59
58
  handler: "index.handler",
@@ -261,11 +261,7 @@ let makeWriteDoor = (
261
261
  ~bundleRuntimeExtensions=false,
262
262
  )
263
263
 
264
- let layers =
265
- Lambda.reventlessLayerArn
266
- ->Option.map(arn => [arn->Pulumi.Input.make])
267
- ->Option.getOr([])
268
- ->Pulumi.Input.make
264
+ let layers = Lambda.reventlessLayers()
269
265
 
270
266
  let logGroup = Util_LambdaLogging.makeManagedLogGroup(
271
267
  ~name=name ++ "Lambda",
@@ -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";
@@ -136,7 +135,7 @@ function makeWriteDoor(api, table, cognitoUserPoolId, cognitoUserPoolArn, nameOp
136
135
  Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
137
136
  ]]);
138
137
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res.mjs", packageDirs, undefined, false);
139
- let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
138
+ let layers = Lambda$PulumiAws.reventlessLayers();
140
139
  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);
141
140
  let lambda = new (Aws.lambda.Function)(name + "Lambda", {
142
141
  handler: "index.handler",
@@ -99,11 +99,7 @@ let make = (
99
99
  ~bundleRuntimeExtensions=false,
100
100
  )
101
101
 
102
- let layers =
103
- Lambda.reventlessLayerArn
104
- ->Option.map(arn => [arn->Pulumi.Input.make])
105
- ->Option.getOr([])
106
- ->Pulumi.Input.make
102
+ let layers = Lambda.reventlessLayers()
107
103
 
108
104
  let logGroup = Util_LambdaLogging.makeManagedLogGroup(
109
105
  ~name,
@@ -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 "../AWS.res.mjs";
@@ -45,7 +44,7 @@ function make(activeRoleTableName, activeRoleTableArn, nameOpt, opts) {
45
44
  ]]);
46
45
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res.mjs", packageDirs, undefined, false);
47
46
  let sourceCodeHash = match.sourceCodeHash;
48
- let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
47
+ let layers = Lambda$PulumiAws.reventlessLayers();
49
48
  let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name, undefined, AWS_Tags$ReventlessAws.make(name + "LogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
50
49
  let lambda = new (Aws.lambda.Function)(name, {
51
50
  handler: "index.handler",
@@ -61,28 +61,55 @@ let compositeTagKey = (tags: array<Reventless.DcbTag.tag>) =>
61
61
 
62
62
  // --- Partition Key Derivation ---
63
63
 
64
- let derivePartitionKey = (
65
- ~partitionTag: option<Reventless.DcbTag.derivedPartitionTag>=?,
64
+ // The tag an event is filed under: the one named by its partition key. An event
65
+ // that lacks it throws — filing it under another tag would put it where no
66
+ // decision read of its entity looks. With no partition configured at all (a
67
+ // single-tag log), the event's only tag.
68
+ let partitionTagOfEvent = (
69
+ ~partitionTag: option<Reventless.DcbTag.derivedPartitionTag>,
70
+ ~eventType: string,
66
71
  tags: array<Reventless.DcbTag.tag>,
67
- ): string => {
68
- if tags->Array.length == 0 {
69
- "dcb"
70
- } else {
71
- switch partitionTag {
72
+ ): option<Reventless.DcbTag.tag> => {
73
+ let byKey = key =>
74
+ switch tags->Array.find(t => t.key == key) {
75
+ | Some(t) => Some(t)
72
76
  | None =>
73
- let tag = tags->Array.getUnsafe(0)
74
- `${tag.key}:${tag.value}`
75
- | Some(Simple(pt)) =>
76
- let tag = switch tags->Array.find(t => t.key == pt.key) {
77
- | Some(t) => t
78
- | None => tags->Array.getUnsafe(0)
79
- }
80
- `${tag.key}:${tag.value}`
81
- | Some(Composite(spec)) => Reventless.DcbTag.getCompositePartitionKeyValue(tags, spec)
77
+ JsError.throwWithMessage(
78
+ `DCB event ${eventType} carries no ${key} tag (tags: ${tags
79
+ ->Array.map(t => t.key)
80
+ ->Array.join(", ")}) — it cannot be filed under its partition`,
81
+ )
82
+ }
83
+ switch (tags, partitionTag) {
84
+ | ([], _) | (_, Some(Composite(_))) => None
85
+ | (_, None) => tags->Array.get(0)
86
+ | (_, Some(Simple({key}))) => byKey(key)
87
+ | (_, Some(ByEventType(keys))) =>
88
+ switch keys->Dict.get(eventType) {
89
+ | Some(key) => byKey(key)
90
+ | None =>
91
+ JsError.throwWithMessage(
92
+ `DCB event ${eventType} has no partition key — no slice of this boundary writes it`,
93
+ )
82
94
  }
83
95
  }
84
96
  }
85
97
 
98
+ let derivePartitionKey = (
99
+ ~partitionTag: option<Reventless.DcbTag.derivedPartitionTag>=?,
100
+ ~eventType: string,
101
+ tags: array<Reventless.DcbTag.tag>,
102
+ ): string =>
103
+ switch (tags, partitionTag) {
104
+ | ([], _) => "dcb"
105
+ | (_, Some(Composite(spec))) => Reventless.DcbTag.getCompositePartitionKeyValue(tags, spec)
106
+ | _ =>
107
+ switch partitionTagOfEvent(~partitionTag, ~eventType, tags) {
108
+ | Some(tag) => `${tag.key}:${tag.value}`
109
+ | None => "dcb"
110
+ }
111
+ }
112
+
86
113
  // --- Item Conversion ---
87
114
 
88
115
  let toItem = (
@@ -93,7 +120,10 @@ let toItem = (
93
120
  ): JSON.t => {
94
121
  // Create base item
95
122
  let item = Dict.make()
96
- item->Dict.set("id", derivePartitionKey(~partitionTag?, event.tags)->JSON.Encode.string)
123
+ item->Dict.set(
124
+ "id",
125
+ derivePartitionKey(~partitionTag?, ~eventType=event.eventType, event.tags)->JSON.Encode.string,
126
+ )
97
127
  item->Dict.set("position", position->JSON.Encode.string)
98
128
  item->Dict.set("event", event.eventType->JSON.Encode.string)
99
129
  item->Dict.set("data", event.data)
@@ -985,20 +1015,11 @@ let eventPartitionTags = (
985
1015
  ): array<Reventless.DcbTag.tag> =>
986
1016
  switch partitionTag {
987
1017
  | Some(Composite(spec)) => [makeCompositeFenceTag(event.tags, spec)]
988
- | Some(Simple(pt)) =>
989
- switch event.tags->Array.find(t => t.key == pt.key) {
990
- | Some(t) => [t]
991
- | None =>
992
- switch event.tags->Array.get(0) {
993
- | Some(t) => [t]
994
- | None => []
995
- }
996
- }
997
- | None =>
998
- switch event.tags->Array.get(0) {
999
- | Some(t) => [t]
1000
- | None => []
1001
- }
1018
+ | _ =>
1019
+ partitionTagOfEvent(~partitionTag, ~eventType=event.eventType, event.tags)->Option.mapOr(
1020
+ [],
1021
+ t => [t],
1022
+ )
1002
1023
  }
1003
1024
 
1004
1025
  let collectEventPartitionTags = (
@@ -64,26 +64,60 @@ function compositeTagKey(tags) {
64
64
  return tags.toSorted((a, b) => Primitive_string.compare(a.key, b.key)).map(t => t.key + `:` + t.value).join("#");
65
65
  }
66
66
 
67
- function derivePartitionKey(partitionTag, tags) {
67
+ function partitionTagOfEvent(partitionTag, eventType, tags) {
68
+ let byKey = key => {
69
+ let t = tags.find(t => t.key === key);
70
+ if (t !== undefined) {
71
+ return t;
72
+ } else {
73
+ return Stdlib_JsError.throwWithMessage(`DCB event ` + eventType + ` carries no ` + key + ` tag (tags: ` + tags.map(t => t.key).join(", ") + `) — it cannot be filed under its partition`);
74
+ }
75
+ };
76
+ if (tags.length === 0) {
77
+ return;
78
+ }
79
+ if (partitionTag === undefined) {
80
+ return tags[0];
81
+ }
82
+ switch (partitionTag.TAG) {
83
+ case "Simple" :
84
+ return byKey(partitionTag._0.key);
85
+ case "Composite" :
86
+ return;
87
+ case "ByEventType" :
88
+ let key = partitionTag._0[eventType];
89
+ if (key !== undefined) {
90
+ return byKey(key);
91
+ } else {
92
+ return Stdlib_JsError.throwWithMessage(`DCB event ` + eventType + ` has no partition key — no slice of this boundary writes it`);
93
+ }
94
+ }
95
+ }
96
+
97
+ function derivePartitionKey(partitionTag, eventType, tags) {
68
98
  if (tags.length === 0) {
69
99
  return "dcb";
70
100
  }
71
101
  if (partitionTag !== undefined) {
72
- if (partitionTag.TAG !== "Simple") {
73
- return DcbTag$Reventless.getCompositePartitionKeyValue(tags, partitionTag._0);
102
+ switch (partitionTag.TAG) {
103
+ case "Composite" :
104
+ return DcbTag$Reventless.getCompositePartitionKeyValue(tags, partitionTag._0);
105
+ case "Simple" :
106
+ case "ByEventType" :
107
+ break;
74
108
  }
75
- let pt = partitionTag._0;
76
- let t = tags.find(t => t.key === pt.key);
77
- let tag = t !== undefined ? t : tags[0];
109
+ }
110
+ let tag = partitionTagOfEvent(partitionTag, eventType, tags);
111
+ if (tag !== undefined) {
78
112
  return tag.key + `:` + tag.value;
113
+ } else {
114
+ return "dcb";
79
115
  }
80
- let tag$1 = tags[0];
81
- return tag$1.key + `:` + tag$1.value;
82
116
  }
83
117
 
84
118
  function toItem(position, event, partitionTag, recordedAt) {
85
119
  let item = {};
86
- item["id"] = derivePartitionKey(partitionTag, event.tags);
120
+ item["id"] = derivePartitionKey(partitionTag, event.eventType, event.tags);
87
121
  item["position"] = position;
88
122
  item["event"] = event.eventType;
89
123
  item["data"] = event.data;
@@ -733,27 +767,15 @@ function makeCompositeFenceTag(tags, spec) {
733
767
 
734
768
  function eventPartitionTags(event, partitionTag) {
735
769
  if (partitionTag !== undefined) {
736
- if (partitionTag.TAG !== "Simple") {
737
- return [makeCompositeFenceTag(event.tags, partitionTag._0)];
738
- }
739
- let pt = partitionTag._0;
740
- let t = event.tags.find(t => t.key === pt.key);
741
- if (t !== undefined) {
742
- return [t];
743
- }
744
- let t$1 = event.tags[0];
745
- if (t$1 !== undefined) {
746
- return [t$1];
747
- } else {
748
- return [];
770
+ switch (partitionTag.TAG) {
771
+ case "Composite" :
772
+ return [makeCompositeFenceTag(event.tags, partitionTag._0)];
773
+ case "Simple" :
774
+ case "ByEventType" :
775
+ break;
749
776
  }
750
777
  }
751
- let t$2 = event.tags[0];
752
- if (t$2 !== undefined) {
753
- return [t$2];
754
- } else {
755
- return [];
756
- }
778
+ return Stdlib_Option.mapOr(partitionTagOfEvent(partitionTag, event.eventType, event.tags), [], t => [t]);
757
779
  }
758
780
 
759
781
  function collectEventPartitionTags(events, partitionTag) {
@@ -790,22 +812,30 @@ function partitionTypesByTag(events, partitionTag) {
790
812
  function buildConditionalTransactItems(table, events, cond, basePosition, partitionTag, crossPartitionTagKeysOpt) {
791
813
  let crossPartitionTagKeys = crossPartitionTagKeysOpt !== undefined ? crossPartitionTagKeysOpt : [];
792
814
  let cond$1;
793
- if (partitionTag !== undefined && partitionTag.TAG !== "Simple") {
794
- let spec = partitionTag._0;
795
- let newrecord = {...cond};
796
- newrecord.query = cond.query.map(qi => {
797
- let clauseTags = qi.tags;
798
- if (clauseTags === undefined) {
799
- return qi;
800
- }
801
- if (clauseTags.length <= 1) {
802
- return qi;
803
- }
804
- let newrecord = {...qi};
805
- newrecord.tags = [makeCompositeFenceTag(clauseTags, spec)];
806
- return newrecord;
807
- });
808
- cond$1 = newrecord;
815
+ if (partitionTag !== undefined) {
816
+ switch (partitionTag.TAG) {
817
+ case "Composite" :
818
+ let spec = partitionTag._0;
819
+ let newrecord = {...cond};
820
+ newrecord.query = cond.query.map(qi => {
821
+ let clauseTags = qi.tags;
822
+ if (clauseTags === undefined) {
823
+ return qi;
824
+ }
825
+ if (clauseTags.length <= 1) {
826
+ return qi;
827
+ }
828
+ let newrecord = {...qi};
829
+ newrecord.tags = [makeCompositeFenceTag(clauseTags, spec)];
830
+ return newrecord;
831
+ });
832
+ cond$1 = newrecord;
833
+ break;
834
+ case "Simple" :
835
+ case "ByEventType" :
836
+ cond$1 = cond;
837
+ break;
838
+ }
809
839
  } else {
810
840
  cond$1 = cond;
811
841
  }
@@ -1166,6 +1196,7 @@ export {
1166
1196
  compositeIndexName,
1167
1197
  indexKeepsFullProjection,
1168
1198
  compositeTagKey,
1199
+ partitionTagOfEvent,
1169
1200
  derivePartitionKey,
1170
1201
  toItem,
1171
1202
  fromItem,
@@ -173,11 +173,7 @@ let make = (
173
173
  ~bundleRuntimeExtensions=false,
174
174
  )
175
175
 
176
- let layers =
177
- Lambda.reventlessLayerArn
178
- ->Option.map(arn => [arn->Pulumi.Input.make])
179
- ->Option.getOr([])
180
- ->Pulumi.Input.make
176
+ let layers = Lambda.reventlessLayers()
181
177
 
182
178
  let appsyncEndpoint = AppSync_EventsApi.httpEndpoint(eventsApi)
183
179
 
@@ -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 "../AWS.res.mjs";
@@ -90,7 +89,7 @@ function make(name, topicName, eventTopicOutputs, eventsApi, opts) {
90
89
  Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
91
90
  ]]);
92
91
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res.mjs", packageDirs, undefined, false);
93
- let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
92
+ let layers = Lambda$PulumiAws.reventlessLayers();
94
93
  let appsyncEndpoint = AppSync_EventsApi$ReventlessAws.httpEndpoint(eventsApi);
95
94
  let lambda = new (Aws.lambda.Function)(name + "EventLogSubscriber", {
96
95
  handler: "index.handler",
@@ -115,11 +115,7 @@ let make = (
115
115
  ~bundleRuntimeExtensions=false,
116
116
  )
117
117
 
118
- let layers =
119
- Lambda.reventlessLayerArn
120
- ->Option.map(arn => [arn->Pulumi.Input.make])
121
- ->Option.getOr([])
122
- ->Pulumi.Input.make
118
+ let layers = Lambda.reventlessLayers()
123
119
 
124
120
  // Created before the function that writes to it — see
125
121
  // `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";
@@ -57,7 +56,7 @@ function make(api, placeIndexName, nameOpt, opts) {
57
56
  Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
58
57
  ]]);
59
58
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver_Ops.res.mjs", packageDirs, undefined, false);
60
- let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
59
+ let layers = Lambda$PulumiAws.reventlessLayers();
61
60
  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);
62
61
  let lambda = new (Aws.lambda.Function)(name + "Lambda", {
63
62
  handler: "index.handler",
@@ -27,6 +27,7 @@ let toEventCollectorJson = (
27
27
  "id",
28
28
  DcbEventLogStorage_DynamoDb_Runtime.derivePartitionKey(
29
29
  ~partitionTag?,
30
+ ~eventType=event.eventType,
30
31
  event.tags,
31
32
  )->JSON.Encode.string,
32
33
  )
@@ -55,10 +56,10 @@ let relayWithPool = async (
55
56
  ~pool: ReventlessPostgres.PgDriver.pool,
56
57
  ~logName: string,
57
58
  ~subscriber: string,
58
- // ~partitionTagJson: the DCB log's partition tag, sury-encoded into HANDLER_CONFIG
59
- // by the relay builder (B2.3d). Parsed once via the shared derivedPartitionTagSchema
60
- // so the `id` the relay emits matches the DynamoDB-stream path. Absent → None (a
61
- // single-tag log derives the same id without it).
59
+ // ~partitionTagJson: the DCB log's partition (the per-event-type key map, or a
60
+ // composite), sury-encoded into HANDLER_CONFIG by the relay builder (B2.3d). Parsed
61
+ // once via the shared derivedPartitionTagSchema so the `id` the relay emits matches
62
+ // the DynamoDB-stream path. Absent → None (a single-tag log derives the same id).
62
63
  ~partitionTagJson: option<JSON.t>=?,
63
64
  ~sendBatch: array<JSON.t> => promise<unit>,
64
65
  ): int => {
@@ -14,7 +14,7 @@ import * as DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws from "../DcbEventL
14
14
 
15
15
  function toEventCollectorJson(event, partitionTag) {
16
16
  let item = {};
17
- item["id"] = DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws.derivePartitionKey(partitionTag, event.tags);
17
+ item["id"] = DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws.derivePartitionKey(partitionTag, event.eventType, event.tags);
18
18
  item["position"] = event.position;
19
19
  item["event"] = event.eventType;
20
20
  item["data"] = event.data;
@@ -88,7 +88,7 @@ let make = (
88
88
 
89
89
  // Re-invoke whenever the migration code or the Reventless layer (which ships
90
90
  // PgSchema's DDL) changes; idempotent DDL makes an unchanged re-run a no-op.
91
- let layerArn = PulumiAws.Lambda.reventlessLayerArn->Option.getOr("no-layer")
91
+ let layerArn = PulumiAws.Lambda.reventlessLayerArn()
92
92
  let inputJson =
93
93
  [("trigger", `${sourceCodeHash}:${layerArn}`->JSON.Encode.string)]
94
94
  ->Dict.fromArray
@@ -2,7 +2,6 @@
2
2
 
3
3
  import * as Aws from "@pulumi/aws";
4
4
  import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.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 Adapter$ReventlessInfra from "@reventlessdev/reventless-infra/src/adapter/Adapter.res.mjs";
@@ -34,7 +33,7 @@ function make(name, handlerConfig, secretArn, securityGroupId, subnetIds, opts)
34
33
  }]))),
35
34
  role: runtime.parts.lambdaRole.id
36
35
  });
37
- let layerArn = Stdlib_Option.getOr(Lambda$PulumiAws.reventlessLayerArn, "no-layer");
36
+ let layerArn = Lambda$PulumiAws.reventlessLayerArn();
38
37
  let inputJson = JSON.stringify(Object.fromEntries([[
39
38
  "trigger",
40
39
  sourceCodeHash + `:` + layerArn
@@ -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 {