@keyv/dynamo 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -35,6 +35,76 @@ e.g:
35
35
  const keyv = new KeyvDynamo({ tableName: 'cacheTable' });
36
36
  ```
37
37
 
38
+ ## Usage with NestJS
39
+
40
+ Since DynamoDB has a 400KB limit per item, compressing data can help in some cases.
41
+
42
+ ### With a payload less than or equal to 400KB
43
+
44
+ ```js
45
+ import { Keyv } from 'keyv'
46
+ import { KeyvDynamo } from '@keyv/dynamo'
47
+ import { DynamoModule } from '@lyfe/dynamo-module'
48
+ import { CacheModule } from '@nestjs/cache-manager'
49
+ import { Module } from '@nestjs/common'
50
+
51
+ @Module({
52
+ imports: [
53
+ CacheModule.registerAsync({
54
+ isGlobal: true,
55
+ useFactory: async () => {
56
+ return {
57
+ stores: [
58
+ new Keyv({
59
+ store: new KeyvDynamo({
60
+ tableName: 'TableName',
61
+ }),
62
+ }),
63
+ ],
64
+ }
65
+ },
66
+ }),
67
+ ],
68
+ exports: [DynamoModule],
69
+ })
70
+ export class InfrastructureModule {}
71
+
72
+ ```
73
+
74
+ ### With a payload greater than 400KB
75
+
76
+ ```js
77
+ import { Keyv } from 'keyv'
78
+ import KeyvBrotli from '@keyv/compress-brotli'
79
+ import { KeyvDynamo } from '@keyv/dynamo'
80
+ import { DynamoModule } from '@lyfe/dynamo-module'
81
+ import { CacheModule } from '@nestjs/cache-manager'
82
+ import { Module } from '@nestjs/common'
83
+
84
+ @Module({
85
+ imports: [
86
+ CacheModule.registerAsync({
87
+ isGlobal: true,
88
+ useFactory: async () => {
89
+ return {
90
+ stores: [
91
+ new Keyv({
92
+ store: new KeyvDynamo({
93
+ tableName: 'TableName',
94
+ }),
95
+ compression: new KeyvBrotli(),
96
+ }),
97
+ ],
98
+ }
99
+ },
100
+ }),
101
+ ],
102
+ exports: [DynamoModule],
103
+ })
104
+ export class InfrastructureModule {}
105
+
106
+ ```
107
+
38
108
  ## License
39
109
 
40
110
  [MIT © Jared Wray](LISCENCE)
package/dist/index.cjs CHANGED
@@ -43,6 +43,7 @@ var KeyvDynamo = class extends import_events.default {
43
43
  namespace;
44
44
  opts;
45
45
  client;
46
+ tableReady;
46
47
  constructor(options) {
47
48
  super();
48
49
  options ??= {};
@@ -55,14 +56,12 @@ var KeyvDynamo = class extends import_events.default {
55
56
  ...options
56
57
  };
57
58
  this.client = import_lib_dynamodb.DynamoDBDocument.from(new import_client_dynamodb.DynamoDB(this.opts));
58
- this.checkTableExists(this.opts.tableName).catch((error) => {
59
+ this.tableReady = this.ensureTable(this.opts.tableName).catch((error) => {
59
60
  this.emit("error", error);
60
61
  });
61
62
  }
62
- async checkTableExists(tableName) {
63
- await this.client.send(new import_client_dynamodb.DescribeTableCommand({ TableName: tableName }));
64
- }
65
63
  async set(key, value, ttl) {
64
+ await this.tableReady;
66
65
  const sixHoursFromNowEpoch = Math.floor((Date.now() + this.sixHoursInMilliseconds) / 1e3);
67
66
  const expiresAt = typeof ttl === "number" ? Math.floor((Date.now() + (ttl + 1e3)) / 1e3) : sixHoursFromNowEpoch;
68
67
  const putInput = {
@@ -76,6 +75,7 @@ var KeyvDynamo = class extends import_events.default {
76
75
  await this.client.put(putInput);
77
76
  }
78
77
  async get(key) {
78
+ await this.tableReady;
79
79
  const getInput = {
80
80
  TableName: this.opts.tableName,
81
81
  Key: {
@@ -86,6 +86,7 @@ var KeyvDynamo = class extends import_events.default {
86
86
  return Item?.value;
87
87
  }
88
88
  async delete(key) {
89
+ await this.tableReady;
89
90
  const deleteInput = {
90
91
  TableName: this.opts.tableName,
91
92
  Key: {
@@ -97,6 +98,7 @@ var KeyvDynamo = class extends import_events.default {
97
98
  return Boolean(Attributes);
98
99
  }
99
100
  async getMany(keys) {
101
+ await this.tableReady;
100
102
  const batchGetInput = {
101
103
  RequestItems: {
102
104
  [this.opts.tableName]: {
@@ -110,6 +112,7 @@ var KeyvDynamo = class extends import_events.default {
110
112
  return keys.map((key) => items.find((item) => item?.id === key)?.value);
111
113
  }
112
114
  async deleteMany(keys) {
115
+ await this.tableReady;
113
116
  if (keys.length === 0) {
114
117
  return false;
115
118
  }
@@ -133,6 +136,7 @@ var KeyvDynamo = class extends import_events.default {
133
136
  return Boolean(response);
134
137
  }
135
138
  async clear() {
139
+ await this.tableReady;
136
140
  const scanResult = await this.client.scan({
137
141
  TableName: this.opts.tableName
138
142
  });
@@ -142,6 +146,47 @@ var KeyvDynamo = class extends import_events.default {
142
146
  extractKey(output, keyProperty = "id") {
143
147
  return (output.Items ?? []).map((item) => item[keyProperty]).filter((key) => key.startsWith(this.namespace ?? ""));
144
148
  }
149
+ async ensureTable(tableName) {
150
+ try {
151
+ await this.client.send(new import_client_dynamodb.DescribeTableCommand({ TableName: tableName }));
152
+ } catch (error) {
153
+ if (error instanceof import_client_dynamodb.ResourceNotFoundException) {
154
+ await this.createTable(tableName);
155
+ } else {
156
+ throw error;
157
+ }
158
+ }
159
+ }
160
+ async createTable(tableName) {
161
+ try {
162
+ await this.client.send(new import_client_dynamodb.CreateTableCommand({
163
+ TableName: tableName,
164
+ KeySchema: [{ AttributeName: "id", KeyType: "HASH" }],
165
+ AttributeDefinitions: [{ AttributeName: "id", AttributeType: "S" }],
166
+ BillingMode: "PAY_PER_REQUEST"
167
+ }));
168
+ await (0, import_client_dynamodb.waitUntilTableExists)(
169
+ { client: this.client, maxWaitTime: 60 },
170
+ { TableName: tableName }
171
+ );
172
+ await this.client.send(new import_client_dynamodb.UpdateTimeToLiveCommand({
173
+ TableName: tableName,
174
+ TimeToLiveSpecification: {
175
+ AttributeName: "expiresAt",
176
+ Enabled: true
177
+ }
178
+ }));
179
+ } catch (error) {
180
+ if (error instanceof import_client_dynamodb.ResourceInUseException) {
181
+ await (0, import_client_dynamodb.waitUntilTableExists)(
182
+ { client: this.client, maxWaitTime: 60 },
183
+ { TableName: tableName }
184
+ );
185
+ } else {
186
+ throw error;
187
+ }
188
+ }
189
+ }
145
190
  };
146
191
  var index_default = KeyvDynamo;
147
192
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -10,8 +10,8 @@ declare class KeyvDynamo extends EventEmitter implements KeyvStoreAdapter {
10
10
  tableName: string;
11
11
  };
12
12
  private readonly client;
13
+ private readonly tableReady;
13
14
  constructor(options: KeyvDynamoOptions | string);
14
- checkTableExists(tableName: string): Promise<void>;
15
15
  set(key: string, value: unknown, ttl?: number): Promise<void>;
16
16
  get<Value>(key: string): Promise<StoredData<Value>>;
17
17
  delete(key: string): Promise<boolean>;
@@ -19,6 +19,8 @@ declare class KeyvDynamo extends EventEmitter implements KeyvStoreAdapter {
19
19
  deleteMany(keys: string[]): Promise<boolean>;
20
20
  clear(): Promise<void>;
21
21
  private extractKey;
22
+ private ensureTable;
23
+ private createTable;
22
24
  }
23
25
 
24
26
  type KeyvDynamoOptions = {
package/dist/index.d.ts CHANGED
@@ -10,8 +10,8 @@ declare class KeyvDynamo extends EventEmitter implements KeyvStoreAdapter {
10
10
  tableName: string;
11
11
  };
12
12
  private readonly client;
13
+ private readonly tableReady;
13
14
  constructor(options: KeyvDynamoOptions | string);
14
- checkTableExists(tableName: string): Promise<void>;
15
15
  set(key: string, value: unknown, ttl?: number): Promise<void>;
16
16
  get<Value>(key: string): Promise<StoredData<Value>>;
17
17
  delete(key: string): Promise<boolean>;
@@ -19,6 +19,8 @@ declare class KeyvDynamo extends EventEmitter implements KeyvStoreAdapter {
19
19
  deleteMany(keys: string[]): Promise<boolean>;
20
20
  clear(): Promise<void>;
21
21
  private extractKey;
22
+ private ensureTable;
23
+ private createTable;
22
24
  }
23
25
 
24
26
  type KeyvDynamoOptions = {
package/dist/index.js CHANGED
@@ -1,8 +1,13 @@
1
1
  // src/index.ts
2
2
  import EventEmitter from "events";
3
3
  import {
4
+ CreateTableCommand,
4
5
  DescribeTableCommand,
5
- DynamoDB
6
+ DynamoDB,
7
+ ResourceNotFoundException,
8
+ ResourceInUseException,
9
+ UpdateTimeToLiveCommand,
10
+ waitUntilTableExists
6
11
  } from "@aws-sdk/client-dynamodb";
7
12
  import {
8
13
  DynamoDBDocument
@@ -13,6 +18,7 @@ var KeyvDynamo = class extends EventEmitter {
13
18
  namespace;
14
19
  opts;
15
20
  client;
21
+ tableReady;
16
22
  constructor(options) {
17
23
  super();
18
24
  options ??= {};
@@ -25,14 +31,12 @@ var KeyvDynamo = class extends EventEmitter {
25
31
  ...options
26
32
  };
27
33
  this.client = DynamoDBDocument.from(new DynamoDB(this.opts));
28
- this.checkTableExists(this.opts.tableName).catch((error) => {
34
+ this.tableReady = this.ensureTable(this.opts.tableName).catch((error) => {
29
35
  this.emit("error", error);
30
36
  });
31
37
  }
32
- async checkTableExists(tableName) {
33
- await this.client.send(new DescribeTableCommand({ TableName: tableName }));
34
- }
35
38
  async set(key, value, ttl) {
39
+ await this.tableReady;
36
40
  const sixHoursFromNowEpoch = Math.floor((Date.now() + this.sixHoursInMilliseconds) / 1e3);
37
41
  const expiresAt = typeof ttl === "number" ? Math.floor((Date.now() + (ttl + 1e3)) / 1e3) : sixHoursFromNowEpoch;
38
42
  const putInput = {
@@ -46,6 +50,7 @@ var KeyvDynamo = class extends EventEmitter {
46
50
  await this.client.put(putInput);
47
51
  }
48
52
  async get(key) {
53
+ await this.tableReady;
49
54
  const getInput = {
50
55
  TableName: this.opts.tableName,
51
56
  Key: {
@@ -56,6 +61,7 @@ var KeyvDynamo = class extends EventEmitter {
56
61
  return Item?.value;
57
62
  }
58
63
  async delete(key) {
64
+ await this.tableReady;
59
65
  const deleteInput = {
60
66
  TableName: this.opts.tableName,
61
67
  Key: {
@@ -67,6 +73,7 @@ var KeyvDynamo = class extends EventEmitter {
67
73
  return Boolean(Attributes);
68
74
  }
69
75
  async getMany(keys) {
76
+ await this.tableReady;
70
77
  const batchGetInput = {
71
78
  RequestItems: {
72
79
  [this.opts.tableName]: {
@@ -80,6 +87,7 @@ var KeyvDynamo = class extends EventEmitter {
80
87
  return keys.map((key) => items.find((item) => item?.id === key)?.value);
81
88
  }
82
89
  async deleteMany(keys) {
90
+ await this.tableReady;
83
91
  if (keys.length === 0) {
84
92
  return false;
85
93
  }
@@ -103,6 +111,7 @@ var KeyvDynamo = class extends EventEmitter {
103
111
  return Boolean(response);
104
112
  }
105
113
  async clear() {
114
+ await this.tableReady;
106
115
  const scanResult = await this.client.scan({
107
116
  TableName: this.opts.tableName
108
117
  });
@@ -112,6 +121,47 @@ var KeyvDynamo = class extends EventEmitter {
112
121
  extractKey(output, keyProperty = "id") {
113
122
  return (output.Items ?? []).map((item) => item[keyProperty]).filter((key) => key.startsWith(this.namespace ?? ""));
114
123
  }
124
+ async ensureTable(tableName) {
125
+ try {
126
+ await this.client.send(new DescribeTableCommand({ TableName: tableName }));
127
+ } catch (error) {
128
+ if (error instanceof ResourceNotFoundException) {
129
+ await this.createTable(tableName);
130
+ } else {
131
+ throw error;
132
+ }
133
+ }
134
+ }
135
+ async createTable(tableName) {
136
+ try {
137
+ await this.client.send(new CreateTableCommand({
138
+ TableName: tableName,
139
+ KeySchema: [{ AttributeName: "id", KeyType: "HASH" }],
140
+ AttributeDefinitions: [{ AttributeName: "id", AttributeType: "S" }],
141
+ BillingMode: "PAY_PER_REQUEST"
142
+ }));
143
+ await waitUntilTableExists(
144
+ { client: this.client, maxWaitTime: 60 },
145
+ { TableName: tableName }
146
+ );
147
+ await this.client.send(new UpdateTimeToLiveCommand({
148
+ TableName: tableName,
149
+ TimeToLiveSpecification: {
150
+ AttributeName: "expiresAt",
151
+ Enabled: true
152
+ }
153
+ }));
154
+ } catch (error) {
155
+ if (error instanceof ResourceInUseException) {
156
+ await waitUntilTableExists(
157
+ { client: this.client, maxWaitTime: 60 },
158
+ { TableName: tableName }
159
+ );
160
+ } else {
161
+ throw error;
162
+ }
163
+ }
164
+ }
115
165
  };
116
166
  var index_default = KeyvDynamo;
117
167
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyv/dynamo",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "DynamoDB storage adapter for Keyv",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -58,13 +58,13 @@
58
58
  },
59
59
  "homepage": "https://github.com/jaredwray/keyv",
60
60
  "dependencies": {
61
- "@aws-sdk/client-dynamodb": "^3.835.0",
62
- "@aws-sdk/lib-dynamodb": "^3.835.0"
61
+ "@aws-sdk/client-dynamodb": "^3.859.0",
62
+ "@aws-sdk/lib-dynamodb": "^3.859.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "vitest": "^3.2.4",
66
- "keyv": "^5.3.4",
67
- "@keyv/test-suite": "^2.0.9"
66
+ "@keyv/test-suite": "^2.1.1",
67
+ "keyv": "^5.5.0"
68
68
  },
69
69
  "tsd": {
70
70
  "directory": "test"