@tslock/dynamodb 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dan Maas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DynamoDBLockProvider: () => DynamoDBLockProvider
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/dynamodb-lock-provider.ts
28
+ var import_client_dynamodb2 = require("@aws-sdk/client-dynamodb");
29
+
30
+ // src/dynamodb-accessor.ts
31
+ var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
32
+ var import_core2 = require("@tslock/core");
33
+
34
+ // src/dynamodb-lock.ts
35
+ var import_core = require("@tslock/core");
36
+ var DynamoDBLock = class extends import_core.AbstractSimpleLock {
37
+ constructor(config, accessor) {
38
+ super(config);
39
+ this.accessor = accessor;
40
+ }
41
+ accessor;
42
+ async doUnlock() {
43
+ await this.accessor.unlock(this.config);
44
+ }
45
+ async doExtend(newConfig) {
46
+ return await this.accessor.extend(newConfig);
47
+ }
48
+ };
49
+
50
+ // src/dynamodb-accessor.ts
51
+ var DynamoDBAccessor = class {
52
+ constructor(client, tableName, partitionKey, sortKey) {
53
+ this.client = client;
54
+ this.tableName = tableName;
55
+ this.partitionKey = partitionKey;
56
+ this.sortKey = sortKey;
57
+ }
58
+ client;
59
+ tableName;
60
+ partitionKey;
61
+ sortKey;
62
+ buildKey(name) {
63
+ const key = { [this.partitionKey]: { S: name } };
64
+ if (this.sortKey) {
65
+ key[this.sortKey.name] = { S: this.sortKey.value };
66
+ }
67
+ return key;
68
+ }
69
+ async lock(config) {
70
+ const now = import_core2.ClockProvider.now();
71
+ const isoNow = import_core2.Utils.toIsoString(now);
72
+ const isoLockAtMostUntil = import_core2.Utils.toIsoString((0, import_core2.lockAtMostUntil)(config));
73
+ const hostname = import_core2.Utils.getHostname();
74
+ try {
75
+ await this.client.send(
76
+ new import_client_dynamodb.UpdateItemCommand({
77
+ TableName: this.tableName,
78
+ Key: this.buildKey(config.name),
79
+ UpdateExpression: "SET lockUntil = :lockUntil, lockedAt = :lockedAt, lockedBy = :lockedBy",
80
+ ConditionExpression: "lockUntil <= :lockedAt OR attribute_not_exists(lockUntil)",
81
+ ExpressionAttributeValues: {
82
+ ":lockUntil": { S: isoLockAtMostUntil },
83
+ ":lockedAt": { S: isoNow },
84
+ ":lockedBy": { S: hostname }
85
+ }
86
+ })
87
+ );
88
+ return new DynamoDBLock(config, this);
89
+ } catch (e) {
90
+ if (e instanceof import_client_dynamodb.ConditionalCheckFailedException) return void 0;
91
+ throw e;
92
+ }
93
+ }
94
+ async extend(config) {
95
+ const now = import_core2.ClockProvider.now();
96
+ const isoNow = import_core2.Utils.toIsoString(now);
97
+ const isoNewLockAtMostUntil = import_core2.Utils.toIsoString((0, import_core2.lockAtMostUntil)(config));
98
+ const hostname = import_core2.Utils.getHostname();
99
+ try {
100
+ await this.client.send(
101
+ new import_client_dynamodb.UpdateItemCommand({
102
+ TableName: this.tableName,
103
+ Key: this.buildKey(config.name),
104
+ UpdateExpression: "SET lockUntil = :lockUntil",
105
+ ConditionExpression: "lockedBy = :lockedBy AND lockUntil > :now",
106
+ ExpressionAttributeValues: {
107
+ ":lockUntil": { S: isoNewLockAtMostUntil },
108
+ ":lockedBy": { S: hostname },
109
+ ":now": { S: isoNow }
110
+ }
111
+ })
112
+ );
113
+ return new DynamoDBLock(config, this);
114
+ } catch (e) {
115
+ if (e instanceof import_client_dynamodb.ConditionalCheckFailedException) return void 0;
116
+ throw e;
117
+ }
118
+ }
119
+ async unlock(config) {
120
+ const isoUnlock = import_core2.Utils.toIsoString((0, import_core2.unlockTime)(config));
121
+ try {
122
+ await this.client.send(
123
+ new import_client_dynamodb.UpdateItemCommand({
124
+ TableName: this.tableName,
125
+ Key: this.buildKey(config.name),
126
+ UpdateExpression: "SET lockUntil = :unlockTime",
127
+ ConditionExpression: `attribute_exists(${this.partitionKey})`,
128
+ ExpressionAttributeValues: {
129
+ ":unlockTime": { S: isoUnlock }
130
+ }
131
+ })
132
+ );
133
+ } catch (e) {
134
+ if (e instanceof import_client_dynamodb.ConditionalCheckFailedException) return;
135
+ throw e;
136
+ }
137
+ }
138
+ };
139
+
140
+ // src/dynamodb-lock-provider-options.ts
141
+ function validateOptions(options) {
142
+ if (!options.tableName) throw new Error("tableName is required");
143
+ return {
144
+ tableName: options.tableName,
145
+ partitionKey: options.partitionKey ?? "_id",
146
+ sortKey: options.sortKey
147
+ };
148
+ }
149
+
150
+ // src/dynamodb-lock-provider.ts
151
+ var DynamoDBLockProvider = class {
152
+ accessor;
153
+ constructor(options) {
154
+ const opts = validateOptions(options);
155
+ this.accessor = new DynamoDBAccessor(
156
+ options.client ?? new import_client_dynamodb2.DynamoDBClient({}),
157
+ opts.tableName,
158
+ opts.partitionKey,
159
+ opts.sortKey
160
+ );
161
+ }
162
+ async lock(config) {
163
+ return this.accessor.lock(config);
164
+ }
165
+ };
166
+ // Annotate the CommonJS export names for ESM import in node:
167
+ 0 && (module.exports = {
168
+ DynamoDBLockProvider
169
+ });
170
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/dynamodb-lock-provider.ts","../src/dynamodb-accessor.ts","../src/dynamodb-lock.ts","../src/dynamodb-lock-provider-options.ts"],"sourcesContent":["export { DynamoDBLockProvider } from './dynamodb-lock-provider.js';\nexport type { DynamoDBLockProviderOptions } from './dynamodb-lock-provider-options.js';\n","import { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport type { ExtensibleLockProvider, LockConfiguration, SimpleLock } from '@tslock/core';\nimport { DynamoDBAccessor } from './dynamodb-accessor.js';\nimport { type DynamoDBLockProviderOptions, validateOptions } from './dynamodb-lock-provider-options.js';\n\nexport class DynamoDBLockProvider implements ExtensibleLockProvider {\n private readonly accessor: DynamoDBAccessor;\n\n constructor(options: DynamoDBLockProviderOptions) {\n const opts = validateOptions(options);\n this.accessor = new DynamoDBAccessor(\n options.client ?? new DynamoDBClient({}),\n opts.tableName,\n opts.partitionKey,\n opts.sortKey,\n );\n }\n\n async lock(config: LockConfiguration): Promise<SimpleLock | undefined> {\n return this.accessor.lock(config);\n }\n}\n","import type { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport { ConditionalCheckFailedException, UpdateItemCommand } from '@aws-sdk/client-dynamodb';\nimport { ClockProvider, type LockConfiguration, Utils, lockAtMostUntil, unlockTime } from '@tslock/core';\nimport { DynamoDBLock } from './dynamodb-lock.js';\n\nexport class DynamoDBAccessor {\n constructor(\n private readonly client: DynamoDBClient,\n private readonly tableName: string,\n private readonly partitionKey: string,\n private readonly sortKey?: { name: string; value: string },\n ) {}\n\n private buildKey(name: string): Record<string, { S: string }> {\n const key: Record<string, { S: string }> = { [this.partitionKey]: { S: name } };\n if (this.sortKey) {\n key[this.sortKey.name] = { S: this.sortKey.value };\n }\n return key;\n }\n\n async lock(config: LockConfiguration): Promise<DynamoDBLock | undefined> {\n const now = ClockProvider.now();\n const isoNow = Utils.toIsoString(now);\n const isoLockAtMostUntil = Utils.toIsoString(lockAtMostUntil(config));\n const hostname = Utils.getHostname();\n\n try {\n await this.client.send(\n new UpdateItemCommand({\n TableName: this.tableName,\n Key: this.buildKey(config.name),\n UpdateExpression: 'SET lockUntil = :lockUntil, lockedAt = :lockedAt, lockedBy = :lockedBy',\n ConditionExpression: 'lockUntil <= :lockedAt OR attribute_not_exists(lockUntil)',\n ExpressionAttributeValues: {\n ':lockUntil': { S: isoLockAtMostUntil },\n ':lockedAt': { S: isoNow },\n ':lockedBy': { S: hostname },\n },\n }),\n );\n return new DynamoDBLock(config, this);\n } catch (e) {\n if (e instanceof ConditionalCheckFailedException) return undefined;\n throw e;\n }\n }\n\n async extend(config: LockConfiguration): Promise<DynamoDBLock | undefined> {\n const now = ClockProvider.now();\n const isoNow = Utils.toIsoString(now);\n const isoNewLockAtMostUntil = Utils.toIsoString(lockAtMostUntil(config));\n const hostname = Utils.getHostname();\n\n try {\n await this.client.send(\n new UpdateItemCommand({\n TableName: this.tableName,\n Key: this.buildKey(config.name),\n UpdateExpression: 'SET lockUntil = :lockUntil',\n ConditionExpression: 'lockedBy = :lockedBy AND lockUntil > :now',\n ExpressionAttributeValues: {\n ':lockUntil': { S: isoNewLockAtMostUntil },\n ':lockedBy': { S: hostname },\n ':now': { S: isoNow },\n },\n }),\n );\n return new DynamoDBLock(config, this);\n } catch (e) {\n if (e instanceof ConditionalCheckFailedException) return undefined;\n throw e;\n }\n }\n\n async unlock(config: LockConfiguration): Promise<void> {\n const isoUnlock = Utils.toIsoString(unlockTime(config));\n\n try {\n await this.client.send(\n new UpdateItemCommand({\n TableName: this.tableName,\n Key: this.buildKey(config.name),\n UpdateExpression: 'SET lockUntil = :unlockTime',\n ConditionExpression: `attribute_exists(${this.partitionKey})`,\n ExpressionAttributeValues: {\n ':unlockTime': { S: isoUnlock },\n },\n }),\n );\n } catch (e) {\n if (e instanceof ConditionalCheckFailedException) return;\n throw e;\n }\n }\n}\n","import { AbstractSimpleLock, type LockConfiguration, type SimpleLock } from '@tslock/core';\nimport type { DynamoDBAccessor } from './dynamodb-accessor.js';\n\nexport class DynamoDBLock extends AbstractSimpleLock {\n constructor(\n config: LockConfiguration,\n private readonly accessor: DynamoDBAccessor,\n ) {\n super(config);\n }\n\n protected override async doUnlock(): Promise<void> {\n await this.accessor.unlock(this.config);\n }\n\n protected override async doExtend(newConfig: LockConfiguration): Promise<SimpleLock | undefined> {\n return await this.accessor.extend(newConfig);\n }\n}\n","import type { DynamoDBClient } from '@aws-sdk/client-dynamodb';\n\nexport interface DynamoDBLockProviderOptions {\n client?: DynamoDBClient;\n tableName: string;\n partitionKey?: string;\n sortKey?: { name: string; value: string };\n}\n\nexport interface ResolvedOptions {\n tableName: string;\n partitionKey: string;\n sortKey?: { name: string; value: string };\n}\n\nexport function validateOptions(options: DynamoDBLockProviderOptions): ResolvedOptions {\n if (!options.tableName) throw new Error('tableName is required');\n return {\n tableName: options.tableName,\n partitionKey: options.partitionKey ?? '_id',\n sortKey: options.sortKey,\n };\n}\n"],"mappings":"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","names":["import_client_dynamodb","import_core"]}
@@ -0,0 +1,20 @@
1
+ import { ExtensibleLockProvider, LockConfiguration, SimpleLock } from '@tslock/core';
2
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
3
+
4
+ interface DynamoDBLockProviderOptions {
5
+ client?: DynamoDBClient;
6
+ tableName: string;
7
+ partitionKey?: string;
8
+ sortKey?: {
9
+ name: string;
10
+ value: string;
11
+ };
12
+ }
13
+
14
+ declare class DynamoDBLockProvider implements ExtensibleLockProvider {
15
+ private readonly accessor;
16
+ constructor(options: DynamoDBLockProviderOptions);
17
+ lock(config: LockConfiguration): Promise<SimpleLock | undefined>;
18
+ }
19
+
20
+ export { DynamoDBLockProvider, type DynamoDBLockProviderOptions };
@@ -0,0 +1,20 @@
1
+ import { ExtensibleLockProvider, LockConfiguration, SimpleLock } from '@tslock/core';
2
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
3
+
4
+ interface DynamoDBLockProviderOptions {
5
+ client?: DynamoDBClient;
6
+ tableName: string;
7
+ partitionKey?: string;
8
+ sortKey?: {
9
+ name: string;
10
+ value: string;
11
+ };
12
+ }
13
+
14
+ declare class DynamoDBLockProvider implements ExtensibleLockProvider {
15
+ private readonly accessor;
16
+ constructor(options: DynamoDBLockProviderOptions);
17
+ lock(config: LockConfiguration): Promise<SimpleLock | undefined>;
18
+ }
19
+
20
+ export { DynamoDBLockProvider, type DynamoDBLockProviderOptions };
package/dist/index.js ADDED
@@ -0,0 +1,143 @@
1
+ // src/dynamodb-lock-provider.ts
2
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
3
+
4
+ // src/dynamodb-accessor.ts
5
+ import { ConditionalCheckFailedException, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
6
+ import { ClockProvider, Utils, lockAtMostUntil, unlockTime } from "@tslock/core";
7
+
8
+ // src/dynamodb-lock.ts
9
+ import { AbstractSimpleLock } from "@tslock/core";
10
+ var DynamoDBLock = class extends AbstractSimpleLock {
11
+ constructor(config, accessor) {
12
+ super(config);
13
+ this.accessor = accessor;
14
+ }
15
+ accessor;
16
+ async doUnlock() {
17
+ await this.accessor.unlock(this.config);
18
+ }
19
+ async doExtend(newConfig) {
20
+ return await this.accessor.extend(newConfig);
21
+ }
22
+ };
23
+
24
+ // src/dynamodb-accessor.ts
25
+ var DynamoDBAccessor = class {
26
+ constructor(client, tableName, partitionKey, sortKey) {
27
+ this.client = client;
28
+ this.tableName = tableName;
29
+ this.partitionKey = partitionKey;
30
+ this.sortKey = sortKey;
31
+ }
32
+ client;
33
+ tableName;
34
+ partitionKey;
35
+ sortKey;
36
+ buildKey(name) {
37
+ const key = { [this.partitionKey]: { S: name } };
38
+ if (this.sortKey) {
39
+ key[this.sortKey.name] = { S: this.sortKey.value };
40
+ }
41
+ return key;
42
+ }
43
+ async lock(config) {
44
+ const now = ClockProvider.now();
45
+ const isoNow = Utils.toIsoString(now);
46
+ const isoLockAtMostUntil = Utils.toIsoString(lockAtMostUntil(config));
47
+ const hostname = Utils.getHostname();
48
+ try {
49
+ await this.client.send(
50
+ new UpdateItemCommand({
51
+ TableName: this.tableName,
52
+ Key: this.buildKey(config.name),
53
+ UpdateExpression: "SET lockUntil = :lockUntil, lockedAt = :lockedAt, lockedBy = :lockedBy",
54
+ ConditionExpression: "lockUntil <= :lockedAt OR attribute_not_exists(lockUntil)",
55
+ ExpressionAttributeValues: {
56
+ ":lockUntil": { S: isoLockAtMostUntil },
57
+ ":lockedAt": { S: isoNow },
58
+ ":lockedBy": { S: hostname }
59
+ }
60
+ })
61
+ );
62
+ return new DynamoDBLock(config, this);
63
+ } catch (e) {
64
+ if (e instanceof ConditionalCheckFailedException) return void 0;
65
+ throw e;
66
+ }
67
+ }
68
+ async extend(config) {
69
+ const now = ClockProvider.now();
70
+ const isoNow = Utils.toIsoString(now);
71
+ const isoNewLockAtMostUntil = Utils.toIsoString(lockAtMostUntil(config));
72
+ const hostname = Utils.getHostname();
73
+ try {
74
+ await this.client.send(
75
+ new UpdateItemCommand({
76
+ TableName: this.tableName,
77
+ Key: this.buildKey(config.name),
78
+ UpdateExpression: "SET lockUntil = :lockUntil",
79
+ ConditionExpression: "lockedBy = :lockedBy AND lockUntil > :now",
80
+ ExpressionAttributeValues: {
81
+ ":lockUntil": { S: isoNewLockAtMostUntil },
82
+ ":lockedBy": { S: hostname },
83
+ ":now": { S: isoNow }
84
+ }
85
+ })
86
+ );
87
+ return new DynamoDBLock(config, this);
88
+ } catch (e) {
89
+ if (e instanceof ConditionalCheckFailedException) return void 0;
90
+ throw e;
91
+ }
92
+ }
93
+ async unlock(config) {
94
+ const isoUnlock = Utils.toIsoString(unlockTime(config));
95
+ try {
96
+ await this.client.send(
97
+ new UpdateItemCommand({
98
+ TableName: this.tableName,
99
+ Key: this.buildKey(config.name),
100
+ UpdateExpression: "SET lockUntil = :unlockTime",
101
+ ConditionExpression: `attribute_exists(${this.partitionKey})`,
102
+ ExpressionAttributeValues: {
103
+ ":unlockTime": { S: isoUnlock }
104
+ }
105
+ })
106
+ );
107
+ } catch (e) {
108
+ if (e instanceof ConditionalCheckFailedException) return;
109
+ throw e;
110
+ }
111
+ }
112
+ };
113
+
114
+ // src/dynamodb-lock-provider-options.ts
115
+ function validateOptions(options) {
116
+ if (!options.tableName) throw new Error("tableName is required");
117
+ return {
118
+ tableName: options.tableName,
119
+ partitionKey: options.partitionKey ?? "_id",
120
+ sortKey: options.sortKey
121
+ };
122
+ }
123
+
124
+ // src/dynamodb-lock-provider.ts
125
+ var DynamoDBLockProvider = class {
126
+ accessor;
127
+ constructor(options) {
128
+ const opts = validateOptions(options);
129
+ this.accessor = new DynamoDBAccessor(
130
+ options.client ?? new DynamoDBClient({}),
131
+ opts.tableName,
132
+ opts.partitionKey,
133
+ opts.sortKey
134
+ );
135
+ }
136
+ async lock(config) {
137
+ return this.accessor.lock(config);
138
+ }
139
+ };
140
+ export {
141
+ DynamoDBLockProvider
142
+ };
143
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dynamodb-lock-provider.ts","../src/dynamodb-accessor.ts","../src/dynamodb-lock.ts","../src/dynamodb-lock-provider-options.ts"],"sourcesContent":["import { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport type { ExtensibleLockProvider, LockConfiguration, SimpleLock } from '@tslock/core';\nimport { DynamoDBAccessor } from './dynamodb-accessor.js';\nimport { type DynamoDBLockProviderOptions, validateOptions } from './dynamodb-lock-provider-options.js';\n\nexport class DynamoDBLockProvider implements ExtensibleLockProvider {\n private readonly accessor: DynamoDBAccessor;\n\n constructor(options: DynamoDBLockProviderOptions) {\n const opts = validateOptions(options);\n this.accessor = new DynamoDBAccessor(\n options.client ?? new DynamoDBClient({}),\n opts.tableName,\n opts.partitionKey,\n opts.sortKey,\n );\n }\n\n async lock(config: LockConfiguration): Promise<SimpleLock | undefined> {\n return this.accessor.lock(config);\n }\n}\n","import type { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport { ConditionalCheckFailedException, UpdateItemCommand } from '@aws-sdk/client-dynamodb';\nimport { ClockProvider, type LockConfiguration, Utils, lockAtMostUntil, unlockTime } from '@tslock/core';\nimport { DynamoDBLock } from './dynamodb-lock.js';\n\nexport class DynamoDBAccessor {\n constructor(\n private readonly client: DynamoDBClient,\n private readonly tableName: string,\n private readonly partitionKey: string,\n private readonly sortKey?: { name: string; value: string },\n ) {}\n\n private buildKey(name: string): Record<string, { S: string }> {\n const key: Record<string, { S: string }> = { [this.partitionKey]: { S: name } };\n if (this.sortKey) {\n key[this.sortKey.name] = { S: this.sortKey.value };\n }\n return key;\n }\n\n async lock(config: LockConfiguration): Promise<DynamoDBLock | undefined> {\n const now = ClockProvider.now();\n const isoNow = Utils.toIsoString(now);\n const isoLockAtMostUntil = Utils.toIsoString(lockAtMostUntil(config));\n const hostname = Utils.getHostname();\n\n try {\n await this.client.send(\n new UpdateItemCommand({\n TableName: this.tableName,\n Key: this.buildKey(config.name),\n UpdateExpression: 'SET lockUntil = :lockUntil, lockedAt = :lockedAt, lockedBy = :lockedBy',\n ConditionExpression: 'lockUntil <= :lockedAt OR attribute_not_exists(lockUntil)',\n ExpressionAttributeValues: {\n ':lockUntil': { S: isoLockAtMostUntil },\n ':lockedAt': { S: isoNow },\n ':lockedBy': { S: hostname },\n },\n }),\n );\n return new DynamoDBLock(config, this);\n } catch (e) {\n if (e instanceof ConditionalCheckFailedException) return undefined;\n throw e;\n }\n }\n\n async extend(config: LockConfiguration): Promise<DynamoDBLock | undefined> {\n const now = ClockProvider.now();\n const isoNow = Utils.toIsoString(now);\n const isoNewLockAtMostUntil = Utils.toIsoString(lockAtMostUntil(config));\n const hostname = Utils.getHostname();\n\n try {\n await this.client.send(\n new UpdateItemCommand({\n TableName: this.tableName,\n Key: this.buildKey(config.name),\n UpdateExpression: 'SET lockUntil = :lockUntil',\n ConditionExpression: 'lockedBy = :lockedBy AND lockUntil > :now',\n ExpressionAttributeValues: {\n ':lockUntil': { S: isoNewLockAtMostUntil },\n ':lockedBy': { S: hostname },\n ':now': { S: isoNow },\n },\n }),\n );\n return new DynamoDBLock(config, this);\n } catch (e) {\n if (e instanceof ConditionalCheckFailedException) return undefined;\n throw e;\n }\n }\n\n async unlock(config: LockConfiguration): Promise<void> {\n const isoUnlock = Utils.toIsoString(unlockTime(config));\n\n try {\n await this.client.send(\n new UpdateItemCommand({\n TableName: this.tableName,\n Key: this.buildKey(config.name),\n UpdateExpression: 'SET lockUntil = :unlockTime',\n ConditionExpression: `attribute_exists(${this.partitionKey})`,\n ExpressionAttributeValues: {\n ':unlockTime': { S: isoUnlock },\n },\n }),\n );\n } catch (e) {\n if (e instanceof ConditionalCheckFailedException) return;\n throw e;\n }\n }\n}\n","import { AbstractSimpleLock, type LockConfiguration, type SimpleLock } from '@tslock/core';\nimport type { DynamoDBAccessor } from './dynamodb-accessor.js';\n\nexport class DynamoDBLock extends AbstractSimpleLock {\n constructor(\n config: LockConfiguration,\n private readonly accessor: DynamoDBAccessor,\n ) {\n super(config);\n }\n\n protected override async doUnlock(): Promise<void> {\n await this.accessor.unlock(this.config);\n }\n\n protected override async doExtend(newConfig: LockConfiguration): Promise<SimpleLock | undefined> {\n return await this.accessor.extend(newConfig);\n }\n}\n","import type { DynamoDBClient } from '@aws-sdk/client-dynamodb';\n\nexport interface DynamoDBLockProviderOptions {\n client?: DynamoDBClient;\n tableName: string;\n partitionKey?: string;\n sortKey?: { name: string; value: string };\n}\n\nexport interface ResolvedOptions {\n tableName: string;\n partitionKey: string;\n sortKey?: { name: string; value: string };\n}\n\nexport function validateOptions(options: DynamoDBLockProviderOptions): ResolvedOptions {\n if (!options.tableName) throw new Error('tableName is required');\n return {\n tableName: options.tableName,\n partitionKey: options.partitionKey ?? '_id',\n sortKey: options.sortKey,\n };\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@tslock/dynamodb",
3
+ "version": "1.0.0",
4
+ "description": "TSLock provider for Amazon DynamoDB",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "peerDependencies": {
24
+ "@aws-sdk/client-dynamodb": "^3.0.0",
25
+ "@tslock/core": "1.0.0"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "@tslock/core": {
29
+ "optional": false
30
+ },
31
+ "@aws-sdk/client-dynamodb": {
32
+ "optional": false
33
+ }
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "^5.5.0",
37
+ "tsup": "^8.0.0",
38
+ "vitest": "^2.0.0",
39
+ "@aws-sdk/client-dynamodb": "^3.0.0",
40
+ "@tslock/core": "1.0.0"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/drmaas/tslock.git",
48
+ "directory": "packages/dynamodb"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "test": "vitest run",
53
+ "test:watch": "vitest",
54
+ "typecheck": "tsc --noEmit"
55
+ }
56
+ }