@dontirun/state-machine-semaphore 0.0.0

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/lib/index.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ import { StateMachineFragment, State, IChainable, INextable } from 'aws-cdk-lib/aws-stepfunctions';
2
+ import { Construct } from 'constructs';
3
+ /**
4
+ * Interface for creating a SemaphoreGenerator
5
+ */
6
+ export interface SemaphoreGeneratorProps {
7
+ /**
8
+ * Optionally set the DynamoDB table to have a specific read/write capacity with PROVISIONED billing.
9
+ * @default PAY_PER_REQUEST
10
+ */
11
+ readonly tableReadWriteCapacity?: TableReadWriteCapacity;
12
+ }
13
+ /**
14
+ * Read and write capacity for a PROVISIONED billing DynamoDB table.
15
+ */
16
+ export interface TableReadWriteCapacity {
17
+ readonly readCapacity: number;
18
+ readonly writeCapacity: number;
19
+ }
20
+ export interface IChainNextable extends IChainable, INextable {
21
+ }
22
+ /**
23
+ * Sets up up the DynamoDB table that stores the State Machine semaphores.
24
+ * Call `generateSemaphoredJob` to generate semaphored jobs.
25
+ */
26
+ export declare class SemaphoreGenerator extends Construct {
27
+ /**
28
+ * The DynamoDB table used to store semaphores.
29
+ */
30
+ private semaphoreTable;
31
+ /**
32
+ * The names and associated concurrency limits and number of uses of the sempahores.
33
+ */
34
+ private semaphoreTracker;
35
+ constructor(scope: Construct, id: string, props?: SemaphoreGeneratorProps);
36
+ /**
37
+ * Generates a semaphore for a StepFunction job (or chained set of jobs) to limit parallelism across executions.
38
+ * @param lockName The name of the semaphore.
39
+ * @param limit The maximum number of concurrent executions for the given lock.
40
+ * @param job The job (or chained jobs) to be semaphored.
41
+ * @param nextState The State to go to after the semaphored job completes.
42
+ * @param reuseLock Explicility allow the reuse of a named lock from a previously generated job. Throws an error if a different `limit` is specified. Default: false.
43
+ * @param comments Adds detailed comments to lock related states. Significantly increases CloudFormation template size. Default: false.
44
+ * @returns A StateMachineFragment that can chained to other states in the State Machine.
45
+ */
46
+ generateSemaphoredJob(lockName: string, limit: number, job: IChainNextable, nextState: State, reuseLock?: boolean, comments?: boolean): StateMachineFragment;
47
+ }
package/lib/index.js ADDED
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var _a;
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.SemaphoreGenerator = void 0;
5
+ const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
6
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
7
+ const aws_dynamodb_1 = require("aws-cdk-lib/aws-dynamodb");
8
+ const aws_stepfunctions_1 = require("aws-cdk-lib/aws-stepfunctions");
9
+ const aws_stepfunctions_tasks_1 = require("aws-cdk-lib/aws-stepfunctions-tasks");
10
+ const constructs_1 = require("constructs");
11
+ /**
12
+ * Sets up up the DynamoDB table that stores the State Machine semaphores.
13
+ * Call `generateSemaphoredJob` to generate semaphored jobs.
14
+ */
15
+ class SemaphoreGenerator extends constructs_1.Construct {
16
+ constructor(scope, id, props) {
17
+ super(scope, id);
18
+ this.semaphoreTracker = new Map();
19
+ this.semaphoreTable = new aws_dynamodb_1.Table(this, 'StateMachineSempahoreTable', {
20
+ partitionKey: {
21
+ name: 'LockName',
22
+ type: aws_dynamodb_1.AttributeType.STRING,
23
+ },
24
+ readCapacity: props?.tableReadWriteCapacity?.readCapacity,
25
+ writeCapacity: props?.tableReadWriteCapacity?.writeCapacity,
26
+ billingMode: props?.tableReadWriteCapacity ? aws_dynamodb_1.BillingMode.PROVISIONED : aws_dynamodb_1.BillingMode.PAY_PER_REQUEST,
27
+ });
28
+ }
29
+ /**
30
+ * Generates a semaphore for a StepFunction job (or chained set of jobs) to limit parallelism across executions.
31
+ * @param lockName The name of the semaphore.
32
+ * @param limit The maximum number of concurrent executions for the given lock.
33
+ * @param job The job (or chained jobs) to be semaphored.
34
+ * @param nextState The State to go to after the semaphored job completes.
35
+ * @param reuseLock Explicility allow the reuse of a named lock from a previously generated job. Throws an error if a different `limit` is specified. Default: false.
36
+ * @param comments Adds detailed comments to lock related states. Significantly increases CloudFormation template size. Default: false.
37
+ * @returns A StateMachineFragment that can chained to other states in the State Machine.
38
+ */
39
+ generateSemaphoredJob(lockName, limit, job, nextState, reuseLock, comments) {
40
+ let lockInfo = this.semaphoreTracker.get(lockName);
41
+ if (lockInfo) {
42
+ if (reuseLock) {
43
+ if (lockInfo.limit != limit) {
44
+ throw new Error(`The reused \`lockName\` "${lockName}" was given a different \`limit\` than previously defined. Given: ${limit}, Previous: ${lockInfo.limit}.`);
45
+ }
46
+ else {
47
+ lockInfo = { limit: lockInfo.limit, timesUsed: lockInfo.timesUsed + 1 };
48
+ this.semaphoreTracker.set(lockName, lockInfo);
49
+ }
50
+ }
51
+ else {
52
+ throw new Error(`The \`lockName\` "${lockName}" was reused without explicitly allowing reuse. Set \`reuseLock\` to \`true\` if you want to reuse the lock.`);
53
+ }
54
+ }
55
+ else {
56
+ lockInfo = { limit: limit, timesUsed: 1 };
57
+ this.semaphoreTracker.set(lockName, lockInfo);
58
+ }
59
+ const getLock = new aws_stepfunctions_1.Parallel(this, `Get ${lockName} Lock: ${lockInfo.timesUsed}`, { resultPath: aws_stepfunctions_1.JsonPath.DISCARD });
60
+ const acquireLock = new aws_stepfunctions_tasks_1.DynamoUpdateItem(this, `Acquire ${lockName} Lock: ${lockInfo.timesUsed}`, {
61
+ comment: comments ? `Acquire a lock using a conditional update to DynamoDB. This update will do two things:
62
+ 1) increment a counter for the number of held locks
63
+ 2) add an attribute to the DynamoDB Item with a unique key for this execution and with a value of the time when the lock was Acquired
64
+ The Update includes a conditional expression that will fail under two circumstances:
65
+ 1) if the maximum number of locks have already been distributed
66
+ 2) if the current execution already owns a lock. The latter check is important to ensure the same execution doesn't increase the counter more than once
67
+ If either of these conditions are not met, then the task will fail with a DynamoDB.ConditionalCheckFailedException error, retry a few times, then if it is still not successful \
68
+ it will move off to another branch of the workflow. If this is the first time that a given lockname has been used, there will not be a row in DynamoDB \
69
+ so the update will fail with DynamoDB.AmazonDynamoDBException. In that case, this state sends the workflow to state that will create that row to initialize.
70
+ ` : undefined,
71
+ table: this.semaphoreTable,
72
+ key: { LockName: aws_stepfunctions_tasks_1.DynamoAttributeValue.fromString(lockName) },
73
+ expressionAttributeNames: {
74
+ '#currentlockcount': 'currentlockcount',
75
+ '#lockownerid.$': '$$.Execution.Id',
76
+ },
77
+ expressionAttributeValues: {
78
+ ':increase': aws_stepfunctions_tasks_1.DynamoAttributeValue.fromNumber(1),
79
+ ':limit': aws_stepfunctions_tasks_1.DynamoAttributeValue.fromNumber(limit),
80
+ ':lockacquiredtime': aws_stepfunctions_tasks_1.DynamoAttributeValue.fromString(aws_stepfunctions_1.JsonPath.stringAt('$$.State.EnteredTime')),
81
+ },
82
+ updateExpression: 'SET #currentlockcount = #currentlockcount + :increase, #lockownerid = :lockacquiredtime',
83
+ conditionExpression: 'currentlockcount <> :limit and attribute_not_exists(#lockownerid)',
84
+ returnValues: aws_stepfunctions_tasks_1.DynamoReturnValues.UPDATED_NEW,
85
+ resultPath: '$.lockinfo.acquirelock',
86
+ });
87
+ const initializeLockItem = new aws_stepfunctions_tasks_1.DynamoPutItem(this, `Initialize ${lockName} Lock Item: ${lockInfo.timesUsed}`, {
88
+ comment: comments ? `This state handles the case where an item hasn't been created for this lock yet. \
89
+ In that case, it will insert an initial item that includes the lock name as the key and currentlockcount of 0. \
90
+ The Put to DynamoDB includes a conditonal expression to fail if the an item with that key already exists, which avoids a race condition if multiple executions start at the same time. \
91
+ There are other reasons that the previous state could fail and end up here, so this is safe in those cases too.` : undefined,
92
+ table: this.semaphoreTable,
93
+ item: {
94
+ LockName: aws_stepfunctions_tasks_1.DynamoAttributeValue.fromString(lockName),
95
+ currentlockcount: aws_stepfunctions_tasks_1.DynamoAttributeValue.fromNumber(0),
96
+ },
97
+ conditionExpression: 'LockName <> :lockname',
98
+ expressionAttributeValues: {
99
+ ':lockname': aws_stepfunctions_tasks_1.DynamoAttributeValue.fromString(lockName),
100
+ },
101
+ resultPath: aws_stepfunctions_1.JsonPath.DISCARD,
102
+ });
103
+ const getCurrentLockRecord = new aws_stepfunctions_tasks_1.DynamoGetItem(this, `Get Current ${lockName} Lock Record: ${lockInfo.timesUsed}`, {
104
+ comment: comments ? 'This state is called when the execution is unable to acquire a lock because there limit has either been exceeded or because this execution already holds a lock. \
105
+ In that case, this task loads info from DDB for the current lock item so that the right decision can be made in subsequent states.' : undefined,
106
+ table: this.semaphoreTable,
107
+ key: { LockName: aws_stepfunctions_tasks_1.DynamoAttributeValue.fromString(lockName) },
108
+ expressionAttributeNames: { '#lockownerid.$': '$$.Execution.Id' },
109
+ projectionExpression: [new aws_stepfunctions_tasks_1.DynamoProjectionExpression().withAttribute('#lockownerid')],
110
+ resultSelector: {
111
+ 'Item.$': '$.Item',
112
+ 'ItemString.$': 'States.JsonToString($.Item)',
113
+ },
114
+ resultPath: '$.lockinfo.currentlockitem',
115
+ });
116
+ const checkIfLockAcquired = new aws_stepfunctions_1.Choice(this, `Check if ${lockName} Lock Already Acquired: ${lockInfo.timesUsed}`, {
117
+ comment: comments ? `This state checks to see if the current execution already holds a lock. It can tell that by looking for Z, which will be indicative of the timestamp value. \
118
+ That will only be there in the stringified version of the data returned from DDB if this execution holds a lock.` : undefined,
119
+ });
120
+ const continueBecauseLockWasAlreadyAcquired = new aws_stepfunctions_1.Pass(this, `Continue Because ${lockName} Lock Was Already Acquired: ${lockInfo.timesUsed}`, {
121
+ comment: comments ? 'In this state, we have confimed that lock is already held, so we pass the original execution input into the the function that does the work.' : undefined,
122
+ });
123
+ const waitToGetLock = new aws_stepfunctions_1.Wait(this, `Wait to Get ${lockName} Lock: ${lockInfo.timesUsed}`, {
124
+ comment: comments ? 'If the lock indeed not been succesfully Acquired, then wait for a bit before trying again.' : undefined,
125
+ time: aws_stepfunctions_1.WaitTime.duration(aws_cdk_lib_1.Duration.seconds(3)),
126
+ });
127
+ acquireLock.addRetry({ errors: ['DynamoDB.AmazonDynamoDBException'], maxAttempts: 0 })
128
+ .addRetry({ maxAttempts: 6, backoffRate: 2 })
129
+ .addCatch(initializeLockItem, { errors: ['DynamoDB.AmazonDynamoDBException'], resultPath: '$.lockinfo.acquisitionerror' })
130
+ .addCatch(getCurrentLockRecord, { errors: ['DynamoDB.ConditionalCheckFailedException'], resultPath: '$.lockinfo.acquisitionerror' });
131
+ initializeLockItem.addCatch(acquireLock, { resultPath: aws_stepfunctions_1.JsonPath.DISCARD });
132
+ getCurrentLockRecord.next(checkIfLockAcquired);
133
+ checkIfLockAcquired.when(aws_stepfunctions_1.Condition.and(aws_stepfunctions_1.Condition.isPresent('$.lockinfo.currentlockitem.ItemString'), aws_stepfunctions_1.Condition.stringMatches('$.lockinfo.currentlockitem.ItemString', '*Z')), continueBecauseLockWasAlreadyAcquired);
134
+ checkIfLockAcquired.otherwise(waitToGetLock);
135
+ waitToGetLock.next(acquireLock);
136
+ const releaseLock = new aws_stepfunctions_tasks_1.DynamoUpdateItem(this, `Release ${lockName} Lock: ${lockInfo.timesUsed}`, {
137
+ table: this.semaphoreTable,
138
+ key: { LockName: aws_stepfunctions_tasks_1.DynamoAttributeValue.fromString(lockName) },
139
+ expressionAttributeNames: {
140
+ '#currentlockcount': 'currentlockcount',
141
+ '#lockownerid.$': '$$.Execution.Id',
142
+ },
143
+ expressionAttributeValues: {
144
+ ':decrease': aws_stepfunctions_tasks_1.DynamoAttributeValue.fromNumber(1),
145
+ },
146
+ updateExpression: 'SET #currentlockcount = #currentlockcount - :decrease REMOVE #lockownerid',
147
+ conditionExpression: 'attribute_exists(#lockownerid)',
148
+ returnValues: aws_stepfunctions_tasks_1.DynamoReturnValues.UPDATED_NEW,
149
+ resultPath: aws_stepfunctions_1.JsonPath.DISCARD,
150
+ });
151
+ releaseLock.addRetry({ errors: ['DynamoDB.ConditionalCheckFailedException'], maxAttempts: 0 })
152
+ .addRetry({ maxAttempts: 5, backoffRate: 1.5 })
153
+ .addCatch(nextState, { errors: ['DynamoDB.ConditionalCheckFailedException'], resultPath: '$.lockinfo.acquisitionerror' })
154
+ .next(nextState);
155
+ getLock.branch(acquireLock);
156
+ getLock.endStates.forEach(j => j.next(job));
157
+ job.next(releaseLock);
158
+ class SemaphoredJob extends aws_stepfunctions_1.StateMachineFragment {
159
+ constructor() {
160
+ super(...arguments);
161
+ this.startState = getLock;
162
+ this.endStates = nextState.endStates;
163
+ }
164
+ }
165
+ return new SemaphoredJob(this, `${lockName}${lockInfo.timesUsed}`);
166
+ }
167
+ }
168
+ exports.SemaphoreGenerator = SemaphoreGenerator;
169
+ _a = JSII_RTTI_SYMBOL_1;
170
+ SemaphoreGenerator[_a] = { fqn: "@dontirun/state-machine-semaphore.SemaphoreGenerator", version: "0.0.0" };
171
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSw2Q0FBdUM7QUFDdkMsMkRBQTZFO0FBQzdFLHFFQUFnSztBQUNoSyxpRkFBMks7QUFDM0ssMkNBQXVDO0FBNEJ2Qzs7O0dBR0c7QUFDSCxNQUFhLGtCQUFtQixTQUFRLHNCQUFTO0lBWS9DLFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBK0I7UUFDdkUsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNqQixJQUFJLENBQUMsZ0JBQWdCLEdBQUcsSUFBSSxHQUFHLEVBQXdCLENBQUM7UUFDeEQsSUFBSSxDQUFDLGNBQWMsR0FBRyxJQUFJLG9CQUFLLENBQUMsSUFBSSxFQUFFLDRCQUE0QixFQUFFO1lBQ2xFLFlBQVksRUFBRTtnQkFDWixJQUFJLEVBQUUsVUFBVTtnQkFDaEIsSUFBSSxFQUFFLDRCQUFhLENBQUMsTUFBTTthQUMzQjtZQUNELFlBQVksRUFBRSxLQUFLLEVBQUUsc0JBQXNCLEVBQUUsWUFBWTtZQUN6RCxhQUFhLEVBQUUsS0FBSyxFQUFFLHNCQUFzQixFQUFFLGFBQWE7WUFDM0QsV0FBVyxFQUFFLEtBQUssRUFBRSxzQkFBc0IsQ0FBQyxDQUFDLENBQUMsMEJBQVcsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLDBCQUFXLENBQUMsZUFBZTtTQUNuRyxDQUFDLENBQUM7SUFFTCxDQUFDO0lBQ0Q7Ozs7Ozs7OztPQVNHO0lBQ0kscUJBQXFCLENBQzFCLFFBQWdCLEVBQUUsS0FBYSxFQUFFLEdBQW1CLEVBQUUsU0FBZ0IsRUFBRSxTQUFtQixFQUFFLFFBQWtCO1FBRS9HLElBQUksUUFBUSxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDbkQsSUFBSSxRQUFRLEVBQUU7WUFDWixJQUFJLFNBQVMsRUFBRTtnQkFDYixJQUFJ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QUFTLEdBQUcsU0FBUyxDQUFDLFNBQVMsQ0FBQztZQUNsRCxDQUFDO1NBQUE7UUFDRCxPQUFPLElBQUksYUFBYSxDQUFDLElBQUksRUFBRSxHQUFHLFFBQVEsR0FBRyxRQUFRLENBQUMsU0FBUyxFQUFFLENBQUMsQ0FBQztJQUNyRSxDQUFDOztBQXhLSCxnREF5S0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBEdXJhdGlvbiB9IGZyb20gJ2F3cy1jZGstbGliJztcbmltcG9ydCB7IFRhYmxlLCBCaWxsaW5nTW9kZSwgQXR0cmlidXRlVHlwZSB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1keW5hbW9kYic7XG5pbXBvcnQgeyBQYXJhbGxlbCwgU3RhdGVNYWNoaW5lRnJhZ21lbnQsIEpzb25QYXRoLCBDaG9pY2UsIFBhc3MsIFdhaXQsIFdhaXRUaW1lLCBDb25kaXRpb24sIFN0YXRlLCBJQ2hhaW5hYmxlLCBJTmV4dGFibGUgfSBmcm9tICdhd3MtY2RrLWxpYi9hd3Mtc3RlcGZ1bmN0aW9ucyc7XG5pbXBvcnQgeyBEeW5hbW9BdHRyaWJ1dGVWYWx1ZSwgRHluYW1vR2V0SXRlbSwgRHluYW1vUHJvamVjdGlvbkV4cHJlc3Npb24sIER5bmFtb1B1dEl0ZW0sIER5bmFtb1JldHVyblZhbHVlcywgRHluYW1vVXBkYXRlSXRlbSB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1zdGVwZnVuY3Rpb25zLXRhc2tzJztcbmltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gJ2NvbnN0cnVjdHMnO1xuXG4vKipcbiAqIEludGVyZmFjZSBmb3IgY3JlYXRpbmcgYSBTZW1hcGhvcmVHZW5lcmF0b3JcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTZW1hcGhvcmVHZW5lcmF0b3JQcm9wcyB7XG4gIC8qKlxuICAgKiBPcHRpb25hbGx5IHNldCB0aGUgRHluYW1vREIgdGFibGUgdG8gaGF2ZSBhIHNwZWNpZmljIHJlYWQvd3JpdGUgY2FwYWNpdHkgd2l0aCBQUk9WSVNJT05FRCBiaWxsaW5nLlxuICAgKiBAZGVmYXVsdCBQQVlfUEVSX1JFUVVFU1RcbiAgICovXG4gIHJlYWRvbmx5IHRhYmxlUmVhZFdyaXRlQ2FwYWNpdHk/OiBUYWJsZVJlYWRXcml0ZUNhcGFjaXR5O1xufVxuXG4vKipcbiAqIFJlYWQgYW5kIHdyaXRlIGNhcGFjaXR5IGZvciBhIFBST1ZJU0lPTkVEIGJpbGxpbmcgRHluYW1vREIgdGFibGUuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgVGFibGVSZWFkV3JpdGVDYXBhY2l0eSB7XG4gIHJlYWRvbmx5IHJlYWRDYXBhY2l0eTogbnVtYmVyO1xuICByZWFkb25seSB3cml0ZUNhcGFjaXR5OiBudW1iZXI7XG59XG5cbmludGVyZmFjZSBVc2FnZVRyYWNrZXIge1xuICByZWFkb25seSBsaW1pdDogbnVtYmVyO1xuICByZWFkb25seSB0aW1lc1VzZWQ6IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJQ2hhaW5OZXh0YWJsZSBleHRlbmRzIElDaGFpbmFibGUsIElOZXh0YWJsZSB7IH1cblxuLyoqXG4gKiBTZXRzIHVwIHVwIHRoZSBEeW5hbW9EQiB0YWJsZSB0aGF0IHN0b3JlcyB0aGUgU3RhdGUgTWFjaGluZSBzZW1hcGhvcmVzLlxuICogQ2FsbCBgZ2VuZXJhdGVTZW1hcGhvcmVkSm9iYCB0byBnZW5lcmF0ZSBzZW1hcGhvcmVkIGpvYnMuXG4gKi9cbmV4cG9ydCBjbGFzcyBTZW1hcGhvcmVHZW5lcmF0b3IgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuXG4gIC8qKlxuICAgKiBUaGUgRHluYW1vREIgdGFibGUgdXNlZCB0byBzdG9yZSBzZW1hcGhvcmVzLlxuICAgKi9cbiAgcHJpdmF0ZSBzZW1hcGhvcmVUYWJsZTogVGFibGU7XG5cbiAgLyoqXG4gICAqIFRoZSBuYW1lcyBhbmQgYXNzb2NpYXRlZCBjb25jdXJyZW5jeSBsaW1pdHMgYW5kIG51bWJlciBvZiB1c2VzIG9mIHRoZSBzZW1wYWhvcmVzLlxuICAgKi9cbiAgcHJpdmF0ZSBzZW1hcGhvcmVUcmFja2VyOiBNYXA8c3RyaW5nLCBVc2FnZVRyYWNrZXI+O1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzPzogU2VtYXBob3JlR2VuZXJhdG9yUHJvcHMpIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuICAgIHRoaXMuc2VtYXBob3JlVHJhY2tlciA9IG5ldyBNYXA8c3RyaW5nLCBVc2FnZVRyYWNrZXI+KCk7XG4gICAgdGhpcy5zZW1hcGhvcmVUYWJsZSA9IG5ldyBUYWJsZSh0aGlzLCAnU3RhdGVNYWNoaW5lU2VtcGFob3JlVGFibGUnLCB7XG4gICAgICBwYXJ0aXRpb25LZXk6IHtcbiAgICAgICAgbmFtZTogJ0xvY2tOYW1lJyxcbiAgICAgICAgdHlwZTogQXR0cmlidXRlVHlwZS5TVFJJTkcsXG4gICAgICB9LFxuICAgICAgcmVhZENhcGFjaXR5OiBwcm9wcz8udGFibGVSZWFkV3JpdGVDYXBhY2l0eT8ucmVhZENhcGFjaXR5LFxuICAgICAgd3JpdGVDYXBhY2l0eTogcHJvcHM/LnRhYmxlUmVhZFdyaXRlQ2FwYWNpdHk/LndyaXRlQ2FwYWNpdHksXG4gICAgICBiaWxsaW5nTW9kZTogcHJvcHM/LnRhYmxlUmVhZFdyaXRlQ2FwYWNpdHkgPyBCaWxsaW5nTW9kZS5QUk9WSVNJT05FRCA6IEJpbGxpbmdNb2RlLlBBWV9QRVJfUkVRVUVTVCxcbiAgICB9KTtcblxuICB9XG4gIC8qKlxuICAgKiBHZW5lcmF0ZXMgYSBzZW1hcGhvcmUgZm9yIGEgU3RlcEZ1bmN0aW9uIGpvYiAob3IgY2hhaW5lZCBzZXQgb2Ygam9icykgdG8gbGltaXQgcGFyYWxsZWxpc20gYWNyb3NzIGV4ZWN1dGlvbnMuXG4gICAqIEBwYXJhbSBsb2NrTmFtZSBUaGUgbmFtZSBvZiB0aGUgc2VtYXBob3JlLlxuICAgKiBAcGFyYW0gbGltaXQgVGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbmN1cnJlbnQgZXhlY3V0aW9ucyBmb3IgdGhlIGdpdmVuIGxvY2suXG4gICAqIEBwYXJhbSBqb2IgVGhlIGpvYiAob3IgY2hhaW5lZCBqb2JzKSB0byBiZSBzZW1hcGhvcmVkLlxuICAgKiBAcGFyYW0gbmV4dFN0YXRlIFRoZSBTdGF0ZSB0byBnbyB0byBhZnRlciB0aGUgc2VtYXBob3JlZCBqb2IgY29tcGxldGVzLlxuICAgKiBAcGFyYW0gcmV1c2VMb2NrIEV4cGxpY2lsaXR5IGFsbG93IHRoZSByZXVzZSBvZiBhIG5hbWVkIGxvY2sgZnJvbSBhIHByZXZpb3VzbHkgZ2VuZXJhdGVkIGpvYi4gVGhyb3dzIGFuIGVycm9yIGlmIGEgZGlmZmVyZW50IGBsaW1pdGAgaXMgc3BlY2lmaWVkLiBEZWZhdWx0OiBmYWxzZS5cbiAgICogQHBhcmFtIGNvbW1lbnRzIEFkZHMgZGV0YWlsZWQgY29tbWVudHMgdG8gbG9jayByZWxhdGVkIHN0YXRlcy4gU2lnbmlmaWNhbnRseSBpbmNyZWFzZXMgQ2xvdWRGb3JtYXRpb24gdGVtcGxhdGUgc2l6ZS4gRGVmYXVsdDogZmFsc2UuXG4gICAqIEByZXR1cm5zIEEgU3RhdGVNYWNoaW5lRnJhZ21lbnQgdGhhdCBjYW4gY2hhaW5lZCB0byBvdGhlciBzdGF0ZXMgaW4gdGhlIFN0YXRlIE1hY2hpbmUuXG4gICAqL1xuICBwdWJsaWMgZ2VuZXJhdGVTZW1hcGhvcmVkSm9iKFxuICAgIGxvY2tOYW1lOiBzdHJpbmcsIGxpbWl0OiBudW1iZXIsIGpvYjogSUNoYWluTmV4dGFibGUsIG5leHRTdGF0ZTogU3RhdGUsIHJldXNlTG9jaz86IGJvb2xlYW4sIGNvbW1lbnRzPzogYm9vbGVhbixcbiAgKTogU3RhdGVNYWNoaW5lRnJhZ21lbnQge1xuICAgIGxldCBsb2NrSW5mbyA9IHRoaXMuc2VtYXBob3JlVHJhY2tlci5nZXQobG9ja05hbWUpO1xuICAgIGlmIChsb2NrSW5mbykge1xuICAgICAgaWYgKHJldXNlTG9jaykge1xuICAgICAgICBpZiAobG9ja0luZm8ubGltaXQgIT0gbGltaXQpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFRoZSByZXVzZWQgXFxgbG9ja05hbWVcXGAgXCIke2xvY2tOYW1lfVwiIHdhcyBnaXZlbiBhIGRpZmZlcmVudCBcXGBsaW1pdFxcYCB0aGFuIHByZXZpb3VzbHkgZGVmaW5lZC4gR2l2ZW46ICR7bGltaXR9LCBQcmV2aW91czogJHtsb2NrSW5mby5saW1pdH0uYCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgbG9ja0luZm8gPSB7IGxpbWl0OiBsb2NrSW5mby5saW1pdCwgdGltZXNVc2VkOiBsb2NrSW5mby50aW1lc1VzZWQgKyAxIH07XG4gICAgICAgICAgdGhpcy5zZW1hcGhvcmVUcmFja2VyLnNldChsb2NrTmFtZSwgbG9ja0luZm8pO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFRoZSBcXGBsb2NrTmFtZVxcYCBcIiR7bG9ja05hbWV9XCIgd2FzIHJldXNlZCB3aXRob3V0IGV4cGxpY2l0bHkgYWxsb3dpbmcgcmV1c2UuIFNldCBcXGByZXVzZUxvY2tcXGAgdG8gXFxgdHJ1ZVxcYCBpZiB5b3Ugd2FudCB0byByZXVzZSB0aGUgbG9jay5gKTtcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgbG9ja0luZm8gPSB7IGxpbWl0OiBsaW1pdCwgdGltZXNVc2VkOiAxIH07XG4gICAgICB0aGlzLnNlbWFwaG9yZVRyYWNrZXIuc2V0KGxvY2tOYW1lLCBsb2NrSW5mbyk7XG4gICAgfVxuXG4gICAgY29uc3QgZ2V0TG9jayA9IG5ldyBQYXJhbGxlbCh0aGlzLCBgR2V0ICR7bG9ja05hbWV9IExvY2s6ICR7bG9ja0luZm8udGltZXNVc2VkfWAsIHsgcmVzdWx0UGF0aDogSnNvblBhdGguRElTQ0FSRCB9KTtcbiAgICBjb25zdCBhY3F1aXJlTG9jayA9IG5ldyBEeW5hbW9VcGRhdGVJdGVtKHRoaXMsIGBBY3F1aXJlICR7bG9ja05hbWV9IExvY2s6ICR7bG9ja0luZm8udGltZXNVc2VkfWAsXG4gICAgICB7XG4gICAgICAgIGNvbW1lbnQ6IGNvbW1lbnRzID8gYEFjcXVpcmUgYSBsb2NrIHVzaW5nIGEgY29uZGl0aW9uYWwgdXBkYXRlIHRvIER5bmFtb0RCLiBUaGlzIHVwZGF0ZSB3aWxsIGRvIHR3byB0aGluZ3M6XG4gICAgICAgICAgMSkgaW5jcmVtZW50IGEgY291bnRlciBmb3IgdGhlIG51bWJlciBvZiBoZWxkIGxvY2tzXG4gICAgICAgICAgMikgYWRkIGFuIGF0dHJpYnV0ZSB0byB0aGUgRHluYW1vREIgSXRlbSB3aXRoIGEgdW5pcXVlIGtleSBmb3IgdGhpcyBleGVjdXRpb24gYW5kIHdpdGggYSB2YWx1ZSBvZiB0aGUgdGltZSB3aGVuIHRoZSBsb2NrIHdhcyBBY3F1aXJlZFxuICAgICAgICAgIFRoZSBVcGRhdGUgaW5jbHVkZXMgYSBjb25kaXRpb25hbCBleHByZXNzaW9uIHRoYXQgd2lsbCBmYWlsIHVuZGVyIHR3byBjaXJjdW1zdGFuY2VzOlxuICAgICAgICAgIDEpIGlmIHRoZSBtYXhpbXVtIG51bWJlciBvZiBsb2NrcyBoYXZlIGFscmVhZHkgYmVlbiBkaXN0cmlidXRlZFxuICAgICAgICAgIDIpIGlmIHRoZSBjdXJyZW50IGV4ZWN1dGlvbiBhbHJlYWR5IG93bnMgYSBsb2NrLiBUaGUgbGF0dGVyIGNoZWNrIGlzIGltcG9ydGFudCB0byBlbnN1cmUgdGhlIHNhbWUgZXhlY3V0aW9uIGRvZXNuJ3QgaW5jcmVhc2UgdGhlIGNvdW50ZXIgbW9yZSB0aGFuIG9uY2VcbiAgICAgICAgICBJZiBlaXRoZXIgb2YgdGhlc2UgY29uZGl0aW9ucyBhcmUgbm90IG1ldCwgdGhlbiB0aGUgdGFzayB3aWxsIGZhaWwgd2l0aCBhIER5bmFtb0RCLkNvbmRpdGlvbmFsQ2hlY2tGYWlsZWRFeGNlcHRpb24gZXJyb3IsIHJldHJ5IGEgZmV3IHRpbWVzLCB0aGVuIGlmIGl0IGlzIHN0aWxsIG5vdCBzdWNjZXNzZnVsIFxcXG4gICAgICAgICAgaXQgd2lsbCBtb3ZlIG9mZiB0byBhbm90aGVyIGJyYW5jaCBvZiB0aGUgd29ya2Zsb3cuIElmIHRoaXMgaXMgdGhlIGZpcnN0IHRpbWUgdGhhdCBhIGdpdmVuIGxvY2tuYW1lIGhhcyBiZWVuIHVzZWQsIHRoZXJlIHdpbGwgbm90IGJlIGEgcm93IGluIER5bmFtb0RCIFxcXG4gICAgICAgICAgc28gdGhlIHVwZGF0ZSB3aWxsIGZhaWwgd2l0aCBEeW5hbW9EQi5BbWF6b25EeW5hbW9EQkV4Y2VwdGlvbi4gSW4gdGhhdCBjYXNlLCB0aGlzIHN0YXRlIHNlbmRzIHRoZSB3b3JrZmxvdyB0byBzdGF0ZSB0aGF0IHdpbGwgY3JlYXRlIHRoYXQgcm93IHRvIGluaXRpYWxpemUuXG4gICAgICAgICAgYCA6IHVuZGVmaW5lZCxcbiAgICAgICAgdGFibGU6IHRoaXMuc2VtYXBob3JlVGFibGUsXG4gICAgICAgIGtleTogeyBMb2NrTmFtZTogRHluYW1vQXR0cmlidXRlVmFsdWUuZnJvbVN0cmluZyhsb2NrTmFtZSkgfSxcbiAgICAgICAgZXhwcmVzc2lvbkF0dHJpYnV0ZU5hbWVzOiB7XG4gICAgICAgICAgJyNjdXJyZW50bG9ja2NvdW50JzogJ2N1cnJlbnRsb2NrY291bnQnLFxuICAgICAgICAgICcjbG9ja293bmVyaWQuJCc6ICckJC5FeGVjdXRpb24uSWQnLFxuICAgICAgICB9LFxuICAgICAgICBleHByZXNzaW9uQXR0cmlidXRlVmFsdWVzOiB7XG4gICAgICAgICAgJzppbmNyZWFzZSc6IER5bmFtb0F0dHJpYnV0ZVZhbHVlLmZyb21OdW1iZXIoMSksXG4gICAgICAgICAgJzpsaW1pdCc6IER5bmFtb0F0dHJpYnV0ZVZhbHVlLmZyb21OdW1iZXIobGltaXQpLFxuICAgICAgICAgICc6bG9ja2FjcXVpcmVkdGltZSc6IER5bmFtb0F0dHJpYnV0ZVZhbHVlLmZyb21TdHJpbmcoSnNvblBhdGguc3RyaW5nQXQoJyQkLlN0YXRlLkVudGVyZWRUaW1lJykpLFxuICAgICAgICB9LFxuICAgICAgICB1cGRhdGVFeHByZXNzaW9uOiAnU0VUICNjdXJyZW50bG9ja2NvdW50ID0gI2N1cnJlbnRsb2NrY291bnQgKyA6aW5jcmVhc2UsICNsb2Nrb3duZXJpZCA9IDpsb2NrYWNxdWlyZWR0aW1lJyxcbiAgICAgICAgY29uZGl0aW9uRXhwcmVzc2lvbjogJ2N1cnJlbnRsb2NrY291bnQgPD4gOmxpbWl0IGFuZCBhdHRyaWJ1dGVfbm90X2V4aXN0cygjbG9ja293bmVyaWQpJyxcbiAgICAgICAgcmV0dXJuVmFsdWVzOiBEeW5hbW9SZXR1cm5WYWx1ZXMuVVBEQVRFRF9ORVcsXG4gICAgICAgIHJlc3VsdFBhdGg6ICckLmxvY2tpbmZvLmFjcXVpcmVsb2NrJyxcbiAgICAgIH0sXG4gICAgKTtcbiAgICBjb25zdCBpbml0aWFsaXplTG9ja0l0ZW0gPSBuZXcgRHluYW1vUHV0SXRlbSh0aGlzLCBgSW5pdGlhbGl6ZSAke2xvY2tOYW1lfSBMb2NrIEl0ZW06ICR7bG9ja0luZm8udGltZXNVc2VkfWAsIHtcbiAgICAgIGNvbW1lbnQ6IGNvbW1lbnRzID8gYFRoaXMgc3RhdGUgaGFuZGxlcyB0aGUgY2FzZSB3aGVyZSBhbiBpdGVtIGhhc24ndCBiZWVuIGNyZWF0ZWQgZm9yIHRoaXMgbG9jayB5ZXQuIFxcXG4gICAgICBJbiB0aGF0IGNhc2UsIGl0IHdpbGwgaW5zZXJ0IGFuIGluaXRpYWwgaXRlbSB0aGF0IGluY2x1ZGVzIHRoZSBsb2NrIG5hbWUgYXMgdGhlIGtleSBhbmQgY3VycmVudGxvY2tjb3VudCBvZiAwLiBcXCBcbiAgICAgIFRoZSBQdXQgdG8gRHluYW1vREIgaW5jbHVkZXMgYSBjb25kaXRvbmFsIGV4cHJlc3Npb24gdG8gZmFpbCBpZiB0aGUgYW4gaXRlbSB3aXRoIHRoYXQga2V5IGFscmVhZHkgZXhpc3RzLCB3aGljaCBhdm9pZHMgYSByYWNlIGNvbmRpdGlvbiBpZiBtdWx0aXBsZSBleGVjdXRpb25zIHN0YXJ0IGF0IHRoZSBzYW1lIHRpbWUuIFxcIFxuICAgICAgVGhlcmUgYXJlIG90aGVyIHJlYXNvbnMgdGhhdCB0aGUgcHJldmlvdXMgc3RhdGUgY291bGQgZmFpbCBhbmQgZW5kIHVwIGhlcmUsIHNvIHRoaXMgaXMgc2FmZSBpbiB0aG9zZSBjYXNlcyB0b28uYCA6IHVuZGVmaW5lZCxcbiAgICAgIHRhYmxlOiB0aGlzLnNlbWFwaG9yZVRhYmxlLFxuICAgICAgaXRlbToge1xuICAgICAgICBMb2NrTmFtZTogRHluYW1vQXR0cmlidXRlVmFsdWUuZnJvbVN0cmluZyhsb2NrTmFtZSksXG4gICAgICAgIGN1cnJlbnRsb2NrY291bnQ6IER5bmFtb0F0dHJpYnV0ZVZhbHVlLmZyb21OdW1iZXIoMCksXG4gICAgICB9LFxuICAgICAgY29uZGl0aW9uRXhwcmVzc2lvbjogJ0xvY2tOYW1lIDw+IDpsb2NrbmFtZScsXG4gICAgICBleHByZXNzaW9uQXR0cmlidXRlVmFsdWVzOiB7XG4gICAgICAgICc6bG9ja25hbWUnOiBEeW5hbW9BdHRyaWJ1dGVWYWx1ZS5mcm9tU3RyaW5nKGxvY2tOYW1lKSxcbiAgICAgIH0sXG4gICAgICByZXN1bHRQYXRoOiBKc29uUGF0aC5ESVNDQVJELFxuICAgIH0pO1xuXG4gICAgY29uc3QgZ2V0Q3VycmVudExvY2tSZWNvcmQgPSBuZXcgRHluYW1vR2V0SXRlbSh0aGlzLCBgR2V0IEN1cnJlbnQgJHtsb2NrTmFtZX0gTG9jayBSZWNvcmQ6ICR7bG9ja0luZm8udGltZXNVc2VkfWAsIHtcbiAgICAgIGNvbW1lbnQ6IGNvbW1lbnRzID8gJ1RoaXMgc3RhdGUgaXMgY2FsbGVkIHdoZW4gdGhlIGV4ZWN1dGlvbiBpcyB1bmFibGUgdG8gYWNxdWlyZSBhIGxvY2sgYmVjYXVzZSB0aGVyZSBsaW1pdCBoYXMgZWl0aGVyIGJlZW4gZXhjZWVkZWQgb3IgYmVjYXVzZSB0aGlzIGV4ZWN1dGlvbiBhbHJlYWR5IGhvbGRzIGEgbG9jay4gXFxcbiAgICAgIEluIHRoYXQgY2FzZSwgdGhpcyB0YXNrIGxvYWRzIGluZm8gZnJvbSBEREIgZm9yIHRoZSBjdXJyZW50IGxvY2sgaXRlbSBzbyB0aGF0IHRoZSByaWdodCBkZWNpc2lvbiBjYW4gYmUgbWFkZSBpbiBzdWJzZXF1ZW50IHN0YXRlcy4nOiB1bmRlZmluZWQsXG4gICAgICB0YWJsZTogdGhpcy5zZW1hcGhvcmVUYWJsZSxcbiAgICAgIGtleTogeyBMb2NrTmFtZTogRHluYW1vQXR0cmlidXRlVmFsdWUuZnJvbVN0cmluZyhsb2NrTmFtZSkgfSxcbiAgICAgIGV4cHJlc3Npb25BdHRyaWJ1dGVOYW1lczogeyAnI2xvY2tvd25lcmlkLiQnOiAnJCQuRXhlY3V0aW9uLklkJyB9LFxuICAgICAgcHJvamVjdGlvbkV4cHJlc3Npb246IFtuZXcgRHluYW1vUHJvamVjdGlvbkV4cHJlc3Npb24oKS53aXRoQXR0cmlidXRlKCcjbG9ja293bmVyaWQnKV0sXG4gICAgICByZXN1bHRTZWxlY3Rvcjoge1xuICAgICAgICAnSXRlbS4kJzogJyQuSXRlbScsXG4gICAgICAgICdJdGVtU3RyaW5nLiQnOiAnU3RhdGVzLkpzb25Ub1N0cmluZygkLkl0ZW0pJyxcbiAgICAgIH0sXG4gICAgICByZXN1bHRQYXRoOiAnJC5sb2NraW5mby5jdXJyZW50bG9ja2l0ZW0nLFxuICAgIH0pO1xuICAgIGNvbnN0IGNoZWNrSWZMb2NrQWNxdWlyZWQgPSBuZXcgQ2hvaWNlKHRoaXMsIGBDaGVjayBpZiAke2xvY2tOYW1lfSBMb2NrIEFscmVhZHkgQWNxdWlyZWQ6ICR7bG9ja0luZm8udGltZXNVc2VkfWAsIHtcbiAgICAgIGNvbW1lbnQ6IGNvbW1lbnRzID8gYFRoaXMgc3RhdGUgY2hlY2tzIHRvIHNlZSBpZiB0aGUgY3VycmVudCBleGVjdXRpb24gYWxyZWFkeSBob2xkcyBhIGxvY2suIEl0IGNhbiB0ZWxsIHRoYXQgYnkgbG9va2luZyBmb3IgWiwgd2hpY2ggd2lsbCBiZSBpbmRpY2F0aXZlIG9mIHRoZSB0aW1lc3RhbXAgdmFsdWUuIFxcIFxuICAgICAgVGhhdCB3aWxsIG9ubHkgYmUgdGhlcmUgaW4gdGhlIHN0cmluZ2lmaWVkIHZlcnNpb24gb2YgdGhlIGRhdGEgcmV0dXJuZWQgZnJvbSBEREIgaWYgdGhpcyBleGVjdXRpb24gaG9sZHMgYSBsb2NrLmA6IHVuZGVmaW5lZCxcbiAgICB9KTtcbiAgICBjb25zdCBjb250aW51ZUJlY2F1c2VMb2NrV2FzQWxyZWFkeUFjcXVpcmVkID0gbmV3IFBhc3ModGhpcywgYENvbnRpbnVlIEJlY2F1c2UgJHtsb2NrTmFtZX0gTG9jayBXYXMgQWxyZWFkeSBBY3F1aXJlZDogJHtsb2NrSW5mby50aW1lc1VzZWR9YCwge1xuICAgICAgY29tbWVudDogY29tbWVudHMgPyAnSW4gdGhpcyBzdGF0ZSwgd2UgaGF2ZSBjb25maW1lZCB0aGF0IGxvY2sgaXMgYWxyZWFkeSBoZWxkLCBzbyB3ZSBwYXNzIHRoZSBvcmlnaW5hbCBleGVjdXRpb24gaW5wdXQgaW50byB0aGUgdGhlIGZ1bmN0aW9uIHRoYXQgZG9lcyB0aGUgd29yay4nIDogdW5kZWZpbmVkLFxuICAgIH0pO1xuICAgIGNvbnN0IHdhaXRUb0dldExvY2sgPSBuZXcgV2FpdCh0aGlzLCBgV2FpdCB0byBHZXQgJHtsb2NrTmFtZX0gTG9jazogJHtsb2NrSW5mby50aW1lc1VzZWR9YCwge1xuICAgICAgY29tbWVudDogY29tbWVudHMgPyAnSWYgdGhlIGxvY2sgaW5kZWVkIG5vdCBiZWVuIHN1Y2Nlc2Z1bGx5IEFjcXVpcmVkLCB0aGVuIHdhaXQgZm9yIGEgYml0IGJlZm9yZSB0cnlpbmcgYWdhaW4uJyA6IHVuZGVmaW5lZCxcbiAgICAgIHRpbWU6IFdhaXRUaW1lLmR1cmF0aW9uKER1cmF0aW9uLnNlY29uZHMoMykpLFxuICAgIH0pO1xuICAgIGFjcXVpcmVMb2NrLmFkZFJldHJ5KHsgZXJyb3JzOiBbJ0R5bmFtb0RCLkFtYXpvbkR5bmFtb0RCRXhjZXB0aW9uJ10sIG1heEF0dGVtcHRzOiAwIH0pXG4gICAgICAuYWRkUmV0cnkoeyBtYXhBdHRlbXB0czogNiwgYmFja29mZlJhdGU6IDIgfSlcbiAgICAgIC5hZGRDYXRjaChpbml0aWFsaXplTG9ja0l0ZW0sIHsgZXJyb3JzOiBbJ0R5bmFtb0RCLkFtYXpvbkR5bmFtb0RCRXhjZXB0aW9uJ10sIHJlc3VsdFBhdGg6ICckLmxvY2tpbmZvLmFjcXVpc2l0aW9uZXJyb3InIH0pXG4gICAgICAuYWRkQ2F0Y2goZ2V0Q3VycmVudExvY2tSZWNvcmQsIHsgZXJyb3JzOiBbJ0R5bmFtb0RCLkNvbmRpdGlvbmFsQ2hlY2tGYWlsZWRFeGNlcHRpb24nXSwgcmVzdWx0UGF0aDogJyQubG9ja2luZm8uYWNxdWlzaXRpb25lcnJvcicgfSk7XG4gICAgaW5pdGlhbGl6ZUxvY2tJdGVtLmFkZENhdGNoKGFjcXVpcmVMb2NrLCB7IHJlc3VsdFBhdGg6IEpzb25QYXRoLkRJU0NBUkQgfSk7XG4gICAgZ2V0Q3VycmVudExvY2tSZWNvcmQubmV4dChjaGVja0lmTG9ja0FjcXVpcmVkKTtcbiAgICBjaGVja0lmTG9ja0FjcXVpcmVkLndoZW4oQ29uZGl0aW9uLmFuZChcbiAgICAgIENvbmRpdGlvbi5pc1ByZXNlbnQoJyQubG9ja2luZm8uY3VycmVudGxvY2tpdGVtLkl0ZW1TdHJpbmcnKSxcbiAgICAgIENvbmRpdGlvbi5zdHJpbmdNYXRjaGVzKCckLmxvY2tpbmZvLmN1cnJlbnRsb2NraXRlbS5JdGVtU3RyaW5nJywgJypaJykpLCBjb250aW51ZUJlY2F1c2VMb2NrV2FzQWxyZWFkeUFjcXVpcmVkLFxuICAgICk7XG4gICAgY2hlY2tJZkxvY2tBY3F1aXJlZC5vdGhlcndpc2Uod2FpdFRvR2V0TG9jayk7XG4gICAgd2FpdFRvR2V0TG9jay5uZXh0KGFjcXVpcmVMb2NrKTtcblxuICAgIGNvbnN0IHJlbGVhc2VMb2NrID0gbmV3IER5bmFtb1VwZGF0ZUl0ZW0odGhpcywgYFJlbGVhc2UgJHtsb2NrTmFtZX0gTG9jazogJHtsb2NrSW5mby50aW1lc1VzZWR9YCwge1xuICAgICAgdGFibGU6IHRoaXMuc2VtYXBob3JlVGFibGUsXG4gICAgICBrZXk6IHsgTG9ja05hbWU6IER5bmFtb0F0dHJpYnV0ZVZhbHVlLmZyb21TdHJpbmcobG9ja05hbWUpIH0sXG4gICAgICBleHByZXNzaW9uQXR0cmlidXRlTmFtZXM6IHtcbiAgICAgICAgJyNjdXJyZW50bG9ja2NvdW50JzogJ2N1cnJlbnRsb2NrY291bnQnLFxuICAgICAgICAnI2xvY2tvd25lcmlkLiQnOiAnJCQuRXhlY3V0aW9uLklkJyxcbiAgICAgIH0sXG4gICAgICBleHByZXNzaW9uQXR0cmlidXRlVmFsdWVzOiB7XG4gICAgICAgICc6ZGVjcmVhc2UnOiBEeW5hbW9BdHRyaWJ1dGVWYWx1ZS5mcm9tTnVtYmVyKDEpLFxuICAgICAgfSxcbiAgICAgIHVwZGF0ZUV4cHJlc3Npb246ICdTRVQgI2N1cnJlbnRsb2NrY291bnQgPSAjY3VycmVudGxvY2tjb3VudCAtIDpkZWNyZWFzZSBSRU1PVkUgI2xvY2tvd25lcmlkJyxcbiAgICAgIGNvbmRpdGlvbkV4cHJlc3Npb246ICdhdHRyaWJ1dGVfZXhpc3RzKCNsb2Nrb3duZXJpZCknLFxuICAgICAgcmV0dXJuVmFsdWVzOiBEeW5hbW9SZXR1cm5WYWx1ZXMuVVBEQVRFRF9ORVcsXG4gICAgICByZXN1bHRQYXRoOiBKc29uUGF0aC5ESVNDQVJELFxuICAgIH0pO1xuXG4gICAgcmVsZWFzZUxvY2suYWRkUmV0cnkoeyBlcnJvcnM6IFsnRHluYW1vREIuQ29uZGl0aW9uYWxDaGVja0ZhaWxlZEV4Y2VwdGlvbiddLCBtYXhBdHRlbXB0czogMCB9KVxuICAgICAgLmFkZFJldHJ5KHsgbWF4QXR0ZW1wdHM6IDUsIGJhY2tvZmZSYXRlOiAxLjUgfSlcbiAgICAgIC5hZGRDYXRjaChuZXh0U3RhdGUsIHsgZXJyb3JzOiBbJ0R5bmFtb0RCLkNvbmRpdGlvbmFsQ2hlY2tGYWlsZWRFeGNlcHRpb24nXSwgcmVzdWx0UGF0aDogJyQubG9ja2luZm8uYWNxdWlzaXRpb25lcnJvcicgfSlcbiAgICAgIC5uZXh0KG5leHRTdGF0ZSk7XG4gICAgZ2V0TG9jay5icmFuY2goYWNxdWlyZUxvY2spO1xuICAgIGdldExvY2suZW5kU3RhdGVzLmZvckVhY2goaiA9PiBqLm5leHQoam9iKSk7XG4gICAgam9iLm5leHQocmVsZWFzZUxvY2spO1xuICAgIGNsYXNzIFNlbWFwaG9yZWRKb2IgZXh0ZW5kcyBTdGF0ZU1hY2hpbmVGcmFnbWVudCB7XG4gICAgICBwdWJsaWMgcmVhZG9ubHkgc3RhcnRTdGF0ZSA9IGdldExvY2s7XG4gICAgICBwdWJsaWMgcmVhZG9ubHkgZW5kU3RhdGVzID0gbmV4dFN0YXRlLmVuZFN0YXRlcztcbiAgICB9XG4gICAgcmV0dXJuIG5ldyBTZW1hcGhvcmVkSm9iKHRoaXMsIGAke2xvY2tOYW1lfSR7bG9ja0luZm8udGltZXNVc2VkfWApO1xuICB9XG59XG4iXX0=
package/package.json ADDED
@@ -0,0 +1,141 @@
1
+ {
2
+ "name": "@dontirun/state-machine-semaphore",
3
+ "description": "Create distributed semaphores using AWS Step Functions and Amazon DynamoDB to control concurrent invocations of contentious work.",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/dontirun/state-machine-semaphore.git"
7
+ },
8
+ "scripts": {
9
+ "build": "npx projen build",
10
+ "bump": "npx projen bump",
11
+ "clobber": "npx projen clobber",
12
+ "compat": "npx projen compat",
13
+ "compile": "npx projen compile",
14
+ "default": "npx projen default",
15
+ "docgen": "npx projen docgen",
16
+ "eject": "npx projen eject",
17
+ "eslint": "npx projen eslint",
18
+ "package": "npx projen package",
19
+ "package-all": "npx projen package-all",
20
+ "package:dotnet": "npx projen package:dotnet",
21
+ "package:js": "npx projen package:js",
22
+ "package:python": "npx projen package:python",
23
+ "post-compile": "npx projen post-compile",
24
+ "post-upgrade": "npx projen post-upgrade",
25
+ "pre-compile": "npx projen pre-compile",
26
+ "release": "npx projen release",
27
+ "test": "npx projen test",
28
+ "test:update": "npx projen test:update",
29
+ "test:watch": "npx projen test:watch",
30
+ "unbump": "npx projen unbump",
31
+ "upgrade": "npx projen upgrade",
32
+ "watch": "npx projen watch",
33
+ "projen": "npx projen"
34
+ },
35
+ "author": {
36
+ "name": "Arun Donti",
37
+ "email": "dontirun@gmail.com",
38
+ "organization": false
39
+ },
40
+ "devDependencies": {
41
+ "@types/jest": "^27",
42
+ "@types/node": "^14",
43
+ "@typescript-eslint/eslint-plugin": "^5",
44
+ "@typescript-eslint/parser": "^5",
45
+ "aws-cdk-lib": "2.22.0",
46
+ "cdk-nag": "^2.15.32",
47
+ "constructs": "10.0.5",
48
+ "eslint": "^8",
49
+ "eslint-import-resolver-node": "^0.3.6",
50
+ "eslint-import-resolver-typescript": "^3.4.0",
51
+ "eslint-plugin-import": "^2.26.0",
52
+ "jest": "^27",
53
+ "jest-junit": "^13",
54
+ "jsii": "^1.63.2",
55
+ "jsii-diff": "^1.63.2",
56
+ "jsii-docgen": "^7.0.61",
57
+ "jsii-pacmak": "^1.63.2",
58
+ "json-schema": "^0.4.0",
59
+ "npm-check-updates": "^15",
60
+ "projen": "^0.60.10",
61
+ "standard-version": "^9",
62
+ "ts-jest": "^27",
63
+ "typescript": "^4.7.4"
64
+ },
65
+ "peerDependencies": {
66
+ "aws-cdk-lib": "^2.22.0",
67
+ "constructs": "^10.0.5"
68
+ },
69
+ "keywords": [
70
+ "cdk",
71
+ "dynamodb",
72
+ "state machine",
73
+ "step functions"
74
+ ],
75
+ "main": "lib/index.js",
76
+ "license": "Apache-2.0",
77
+ "version": "0.0.0",
78
+ "jest": {
79
+ "testMatch": [
80
+ "<rootDir>/src/**/__tests__/**/*.ts?(x)",
81
+ "<rootDir>/(test|src)/**/*(*.)@(spec|test).ts?(x)"
82
+ ],
83
+ "clearMocks": true,
84
+ "collectCoverage": true,
85
+ "coverageReporters": [
86
+ "json",
87
+ "lcov",
88
+ "clover",
89
+ "cobertura",
90
+ "text"
91
+ ],
92
+ "coverageDirectory": "coverage",
93
+ "coveragePathIgnorePatterns": [
94
+ "/node_modules/"
95
+ ],
96
+ "testPathIgnorePatterns": [
97
+ "/node_modules/"
98
+ ],
99
+ "watchPathIgnorePatterns": [
100
+ "/node_modules/"
101
+ ],
102
+ "reporters": [
103
+ "default",
104
+ [
105
+ "jest-junit",
106
+ {
107
+ "outputDirectory": "test-reports"
108
+ }
109
+ ]
110
+ ],
111
+ "preset": "ts-jest",
112
+ "globals": {
113
+ "ts-jest": {
114
+ "tsconfig": "tsconfig.dev.json"
115
+ }
116
+ }
117
+ },
118
+ "types": "lib/index.d.ts",
119
+ "stability": "stable",
120
+ "jsii": {
121
+ "outdir": "dist",
122
+ "targets": {
123
+ "python": {
124
+ "distName": "state-machine-semaphore",
125
+ "module": "state_machine_semaphore"
126
+ },
127
+ "dotnet": {
128
+ "namespace": "Dontirun.StateMachineSemaphore",
129
+ "packageId": "Dontirun.StateMachineSemaphore"
130
+ }
131
+ },
132
+ "tsc": {
133
+ "outDir": "lib",
134
+ "rootDir": "src"
135
+ }
136
+ },
137
+ "resolutions": {
138
+ "@types/prettier": "2.6.0"
139
+ },
140
+ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"."
141
+ }