@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
@@ -0,0 +1,66 @@
1
+ // The attribute a person signs in with. One definition, two consumers: the auto
2
+ // pool the platform stack declares, and the provisioning script that makes a
3
+ // pool no stack owns. Free of Pulumi and of the AWS SDK so both can import it.
4
+
5
+ /**
6
+ Which attribute identifies a person at sign-in.
7
+
8
+ 🚨 **Fixed at pool creation, and no later deploy can change it.** `CreateUserPool`
9
+ accepts `UsernameAttributes`; `UpdateUserPool` has no such member, so nothing can
10
+ change it in place and every provider replaces the pool instead. A replaced pool
11
+ is an empty one — Cognito exports no password material, so every account
12
+ re-registers. A deployment that will ever want phone sign-in has to say so on its
13
+ first deploy.
14
+ */
15
+ type t =
16
+ | Email
17
+ | Phone
18
+ | EmailOrPhone
19
+
20
+ /** What every pool has been created with so far, so an existing stack redeploys
21
+ unchanged. */
22
+ let default = Email
23
+
24
+ let all: array<t> = [Email, Phone, EmailOrPhone]
25
+
26
+ /** The config spelling, and the stack output. */
27
+ let toString = (identifier: t): string =>
28
+ switch identifier {
29
+ | Email => "email"
30
+ | Phone => "phone"
31
+ | EmailOrPhone => "emailOrPhone"
32
+ }
33
+
34
+ let fromString = (value: string): option<t> => all->Array.find(i => toString(i) == value)
35
+
36
+ /** Cognito's `usernameAttributes`. */
37
+ let usernameAttributes = (identifier: t): array<string> =>
38
+ switch identifier {
39
+ | Email => ["email"]
40
+ | Phone => ["phone_number"]
41
+ | EmailOrPhone => ["email", "phone_number"]
42
+ }
43
+
44
+ /**
45
+ The deploy-time choice, or why the deploy cannot proceed.
46
+
47
+ An unrecognised spelling refuses rather than falling back to the default: a typo
48
+ that defaulted would create a pool signing in on the wrong attribute, which is
49
+ the one property no later deploy can correct.
50
+ */
51
+ let parse = (value: option<string>): result<t, string> =>
52
+ switch value {
53
+ | None => Ok(default)
54
+ | Some(raw) =>
55
+ switch fromString(raw) {
56
+ | Some(identifier) => Ok(identifier)
57
+ | None =>
58
+ Error(
59
+ `loginIdentifier "${raw}" is not one of ${all
60
+ ->Array.map(toString)
61
+ ->Array.join(
62
+ ", ",
63
+ )}. It fixes the sign-in attribute at pool creation and no later deploy can change it, so an unrecognised spelling refuses rather than defaulting.`,
64
+ )
65
+ }
66
+ }
@@ -0,0 +1,70 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ let all = [
5
+ "Email",
6
+ "Phone",
7
+ "EmailOrPhone"
8
+ ];
9
+
10
+ function toString(identifier) {
11
+ switch (identifier) {
12
+ case "Email" :
13
+ return "email";
14
+ case "Phone" :
15
+ return "phone";
16
+ case "EmailOrPhone" :
17
+ return "emailOrPhone";
18
+ }
19
+ }
20
+
21
+ function fromString(value) {
22
+ return all.find(i => toString(i) === value);
23
+ }
24
+
25
+ function usernameAttributes(identifier) {
26
+ switch (identifier) {
27
+ case "Email" :
28
+ return ["email"];
29
+ case "Phone" :
30
+ return ["phone_number"];
31
+ case "EmailOrPhone" :
32
+ return [
33
+ "email",
34
+ "phone_number"
35
+ ];
36
+ }
37
+ }
38
+
39
+ function parse(value) {
40
+ if (value === undefined) {
41
+ return {
42
+ TAG: "Ok",
43
+ _0: "Email"
44
+ };
45
+ }
46
+ let identifier = fromString(value);
47
+ if (identifier !== undefined) {
48
+ return {
49
+ TAG: "Ok",
50
+ _0: identifier
51
+ };
52
+ } else {
53
+ return {
54
+ TAG: "Error",
55
+ _0: `loginIdentifier "` + value + `" is not one of ` + all.map(toString).join(", ") + `. It fixes the sign-in attribute at pool creation and no later deploy can change it, so an unrecognised spelling refuses rather than defaulting.`
56
+ };
57
+ }
58
+ }
59
+
60
+ let $$default = "Email";
61
+
62
+ export {
63
+ $$default as default,
64
+ all,
65
+ toString,
66
+ fromString,
67
+ usernameAttributes,
68
+ parse,
69
+ }
70
+ /* No side effect */
@@ -0,0 +1,78 @@
1
+ // Whether a stranger can create their own account. One definition, two consumers:
2
+ // the auto pool the platform stack declares, and the provisioning script that
3
+ // makes a pool no stack owns. Free of Pulumi and of the AWS SDK so both can
4
+ // import it — the same shape, and the same drift it prevents, as
5
+ // [Auth_LoginIdentifier].
6
+
7
+ /**
8
+ Who may create a principal in this pool.
9
+
10
+ Unlike the sign-in attribute, this **is** updatable in place: `AdminCreateUserConfig`
11
+ is a member of `UpdateUserPoolRequest`, so a pool full of accounts can be flipped
12
+ either way at any time, for free and reversibly. It is a setting, not a
13
+ first-deploy-or-never decision.
14
+
15
+ 🚨 **On a shared pool the flip is pool-wide, not stack-wide.** The setting belongs
16
+ to the pool and the pool has one of it, so every stack pointed at that pool gets
17
+ self-service the moment one deployment turns it on. A deployment that must never
18
+ self-register cannot rely on its own config to prevent it, and should not share a
19
+ pool with one that does.
20
+ */
21
+ type t =
22
+ /** Only an administrator creates principals — what every pool so far was
23
+ created with, and what a deployment with no registration flow wants. */
24
+ | AdminOnly
25
+ /** A stranger can register themselves. */
26
+ | SelfService
27
+
28
+ /** What every pool has been created with so far, so an existing stack redeploys
29
+ unchanged. Closed is also the right default on its own terms: a deployment that
30
+ has not asked for self-registration should not acquire it by upgrading. */
31
+ let default = AdminOnly
32
+
33
+ let all: array<t> = [AdminOnly, SelfService]
34
+
35
+ /** The config spelling. */
36
+ let toString = (mode: t): string =>
37
+ switch mode {
38
+ | AdminOnly => "adminOnly"
39
+ | SelfService => "selfService"
40
+ }
41
+
42
+ let fromString = (value: string): option<t> => all->Array.find(m => toString(m) == value)
43
+
44
+ /** Cognito's `allowAdminCreateUserOnly`, which asks the negative of what this
45
+ type asks. Converting in one place is the point: a bool named for the negative
46
+ is easy to read backwards at a call site. */
47
+ let allowAdminCreateUserOnly = (mode: t): bool =>
48
+ switch mode {
49
+ | AdminOnly => true
50
+ | SelfService => false
51
+ }
52
+
53
+ /**
54
+ The deploy-time choice, or why the deploy cannot proceed.
55
+
56
+ An unrecognised spelling refuses rather than falling back to the default, and the
57
+ reason is *not* [Auth_LoginIdentifier]'s — this setting is correctable at any
58
+ time. It is that the mistake would be silent. The default is closed, so a typo
59
+ fails safe, but it fails safe *invisibly*: the deploy is green, the config says
60
+ what the operator meant, and the symptom is every registration being refused,
61
+ which surfaces far from the misspelling that caused it.
62
+ */
63
+ let parse = (value: option<string>): result<t, string> =>
64
+ switch value {
65
+ | None => Ok(default)
66
+ | Some(raw) =>
67
+ switch fromString(raw) {
68
+ | Some(mode) => Ok(mode)
69
+ | None =>
70
+ Error(
71
+ `signUpMode "${raw}" is not one of ${all
72
+ ->Array.map(toString)
73
+ ->Array.join(
74
+ ", ",
75
+ )}. It decides whether a stranger can create an account. An unrecognised spelling refuses rather than defaulting, because defaulting would deploy green and refuse every registration, with nothing pointing back at the spelling.`,
76
+ )
77
+ }
78
+ }
@@ -0,0 +1,56 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ let all = [
5
+ "AdminOnly",
6
+ "SelfService"
7
+ ];
8
+
9
+ function toString(mode) {
10
+ if (mode === "AdminOnly") {
11
+ return "adminOnly";
12
+ } else {
13
+ return "selfService";
14
+ }
15
+ }
16
+
17
+ function fromString(value) {
18
+ return all.find(m => toString(m) === value);
19
+ }
20
+
21
+ function allowAdminCreateUserOnly(mode) {
22
+ return mode === "AdminOnly";
23
+ }
24
+
25
+ function parse(value) {
26
+ if (value === undefined) {
27
+ return {
28
+ TAG: "Ok",
29
+ _0: "AdminOnly"
30
+ };
31
+ }
32
+ let mode = fromString(value);
33
+ if (mode !== undefined) {
34
+ return {
35
+ TAG: "Ok",
36
+ _0: mode
37
+ };
38
+ } else {
39
+ return {
40
+ TAG: "Error",
41
+ _0: `signUpMode "` + value + `" is not one of ` + all.map(toString).join(", ") + `. It decides whether a stranger can create an account. An unrecognised spelling refuses rather than defaulting, because defaulting would deploy green and refuse every registration, with nothing pointing back at the spelling.`
42
+ };
43
+ }
44
+ }
45
+
46
+ let $$default = "AdminOnly";
47
+
48
+ export {
49
+ $$default as default,
50
+ all,
51
+ toString,
52
+ fromString,
53
+ allowAdminCreateUserOnly,
54
+ parse,
55
+ }
56
+ /* No side effect */
@@ -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