@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.
- package/CHANGELOG.md +54 -0
- package/package.json +19 -12
- package/rescript.json +2 -1
- package/run-bake-manifest.mjs +3 -0
- package/run-deploy-app.mjs +3 -0
- package/run-provision-accounts.mjs +3 -0
- package/run-provision-admin.mjs +3 -0
- package/scripts/BakeManifest.res +395 -0
- package/scripts/BakeManifest.res.mjs +487 -0
- package/scripts/DeployApp.res +708 -0
- package/scripts/DeployApp.res.mjs +1015 -0
- package/scripts/DeployManifest.res +84 -0
- package/scripts/DeployManifest.res.mjs +112 -0
- package/scripts/ProvisionAccounts.res +339 -0
- package/scripts/ProvisionAccounts.res.mjs +351 -0
- package/scripts/ProvisionAdmin.res +275 -0
- package/scripts/ProvisionAdmin.res.mjs +289 -0
- package/scripts/ProvisionCognito.res +149 -0
- package/scripts/ProvisionCognito.res.mjs +98 -0
- package/scripts/ProvisionIdentity.res +76 -11
- package/scripts/ProvisionIdentity.res.mjs +72 -7
- package/scripts/ProvisionProvider.res +104 -0
- package/scripts/ProvisionProvider.res.mjs +123 -0
- package/scripts/PulumiCli.res +43 -0
- package/scripts/PulumiCli.res.mjs +106 -0
- package/src/Platform.res +51 -4
- package/src/Platform.res.mjs +32 -14
- package/src/Platform_Stack.res +119 -9
- package/src/Platform_Stack.res.mjs +42 -4
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -5
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +1 -5
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +1 -2
- package/src/adapter/Auth/Auth_ActiveRoleStore.res +1 -5
- package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +1 -2
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res +1 -5
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +1 -2
- package/src/adapter/Auth/Auth_LoginIdentifier.res +66 -0
- package/src/adapter/Auth/Auth_LoginIdentifier.res.mjs +70 -0
- package/src/adapter/Auth/Auth_SignUpMode.res +78 -0
- package/src/adapter/Auth/Auth_SignUpMode.res.mjs +56 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +52 -31
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +75 -44
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +1 -5
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +1 -2
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +1 -5
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +1 -2
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +5 -4
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -2
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +5 -0
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +5 -1
- package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res +2 -0
- package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res.mjs +2 -2
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +15 -6
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +51 -30
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +31 -14
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -5
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res +1 -5
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +1 -1
- package/src/adapter/Task/TaskBucket_S3.res +1 -3
- package/src/adapter/Task/TaskBucket_S3.res.mjs +1 -1
- package/src/adapter/Upload/Upload_Claim_S3.res +1 -5
- package/src/adapter/Upload/Upload_Claim_S3.res.mjs +1 -1
- package/src/adapter/Upload/Upload_Presign_S3.res +1 -5
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +1 -2
- package/src/capability/Capability_Geocoding_AwsLocation.res +5 -1
- package/src/capability/Capability_Geocoding_AwsLocation.res.mjs +7 -1
- package/src/components/Api/AppSync_Adapter.res +56 -72
- package/src/components/Api/AppSync_Adapter.res.mjs +26 -60
- package/src/components/Api/AppSync_SdlDecorate.res +82 -1
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +45 -0
- package/src/plugin/cloner/ClonerRunner_Fargate.res +1 -5
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +1 -1
- package/src/plugin/stack/Plugin_Stack.res +4 -0
- package/src/plugin/stack/Plugin_Stack.res.mjs +2 -0
- package/src/util/Util_DeadLetterQueue.res +1 -5
- package/src/util/Util_DeadLetterQueue.res.mjs +1 -2
- package/src/util/Util_StoreLayout.res +15 -2
- package/src/util/Util_StoreLayout.res.mjs +11 -3
- package/tests/AppSync_AdapterTest.res +83 -0
- package/tests/AppSync_AdapterTest.res.mjs +69 -0
- package/tests/Auth_LoginIdentifierTest.res +51 -0
- package/tests/Auth_LoginIdentifierTest.res.mjs +51 -0
- package/tests/Auth_SignUpModeTest.res +56 -0
- package/tests/Auth_SignUpModeTest.res.mjs +53 -0
- package/tests/BakeManifestTest.res +204 -0
- package/tests/BakeManifestTest.res.mjs +259 -0
- package/tests/DcbColdStartPartitionTest.res +32 -0
- package/tests/DcbColdStartPartitionTest.res.mjs +33 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +88 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +112 -0
- package/tests/DcbUnresolvedPartitionSlice.res +28 -0
- package/tests/DcbUnresolvedPartitionSlice.res.mjs +38 -0
- package/tests/DeployAppTest.res +90 -0
- package/tests/DeployAppTest.res.mjs +133 -0
- package/tests/LambdaLayerLookupTest.res +104 -0
- package/tests/LambdaLayerLookupTest.res.mjs +117 -0
- package/tests/ProvisionAccountsTest.res +168 -0
- package/tests/ProvisionAccountsTest.res.mjs +184 -0
- package/tests/ProvisionAdminTest.res +55 -0
- package/tests/ProvisionAdminTest.res.mjs +66 -0
- package/tests/ProvisionIdentityTest.res +89 -3
- package/tests/ProvisionIdentityTest.res.mjs +66 -1
- package/tests/Util_OwnerScopeEnvTest.res +25 -0
- package/tests/Util_OwnerScopeEnvTest.res.mjs +13 -0
- package/tests/Util_StoreLayoutTest.res +6 -0
- package/tests/Util_StoreLayoutTest.res.mjs +7 -4
- package/tests/dcbColdStartPartition.mjs +66 -0
- 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
|
-
|
|
65
|
-
|
|
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
|
-
):
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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(
|
|
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
|
-
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
|
|
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
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
59
|
-
// by the relay builder (B2.3d). Parsed
|
|
60
|
-
// so the `id` the relay emits matches
|
|
61
|
-
// single-tag log derives the same id
|
|
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
|
|
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 =
|
|
36
|
+
let layerArn = Lambda$PulumiAws.reventlessLayerArn();
|
|
38
37
|
let inputJson = JSON.stringify(Object.fromEntries([[
|
|
39
38
|
"trigger",
|
|
40
39
|
sourceCodeHash + `:` + layerArn
|