lambder 3.1.1 → 3.1.2
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/dist/LambderDdbCache.js +60 -34
- package/package.json +1 -1
package/dist/LambderDdbCache.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { BatchWriteItemCommand, DeleteItemCommand, DynamoDBClient, GetItemCommand, PutItemCommand, QueryCommand, } from "@aws-sdk/client-dynamodb";
|
|
2
|
-
import {
|
|
3
|
-
import { brotliCompress, brotliDecompress, constants as zlibConstants, } from "zlib";
|
|
2
|
+
import { getCrypto, getZlib } from "./node-polyfills.js";
|
|
4
3
|
import { LRUCache } from "lru-cache";
|
|
5
4
|
const DEFAULT_TTL_SECONDS = 365 * 24 * 60 * 60;
|
|
6
5
|
const DEFAULT_CHUNK_BYTES = 350 * 1024;
|
|
@@ -11,29 +10,56 @@ const META_SORT_KEY = "meta";
|
|
|
11
10
|
const LOCK_SORT_KEY = "lock";
|
|
12
11
|
const BATCH_WRITE_LIMIT = 25;
|
|
13
12
|
const MAX_BATCH_RETRIES = 8;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
// Node builtins are loaded lazily through node-polyfills so this module can
|
|
14
|
+
// sit in a frontend bundle's import graph (via the package root) without
|
|
15
|
+
// breaking; using the cache at runtime still requires Node.
|
|
16
|
+
const requireZlib = async () => {
|
|
17
|
+
const zlib = await getZlib();
|
|
18
|
+
if (!zlib)
|
|
19
|
+
throw new Error("LambderDdbCache requires a Node.js environment.");
|
|
20
|
+
return zlib;
|
|
21
|
+
};
|
|
22
|
+
const requireCrypto = async () => {
|
|
23
|
+
const crypto = await getCrypto();
|
|
24
|
+
if (!crypto)
|
|
25
|
+
throw new Error("LambderDdbCache requires a Node.js environment.");
|
|
26
|
+
return crypto;
|
|
27
|
+
};
|
|
28
|
+
const compress = async (input, quality) => {
|
|
29
|
+
const zlib = await requireZlib();
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
zlib.brotliCompress(input, {
|
|
32
|
+
params: {
|
|
33
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: quality,
|
|
34
|
+
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT,
|
|
35
|
+
},
|
|
36
|
+
}, (error, output) => {
|
|
37
|
+
if (error)
|
|
38
|
+
reject(error);
|
|
39
|
+
else
|
|
40
|
+
resolve(output);
|
|
41
|
+
});
|
|
26
42
|
});
|
|
27
|
-
}
|
|
28
|
-
const decompress = (input, maxOutputLength) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
};
|
|
44
|
+
const decompress = async (input, maxOutputLength) => {
|
|
45
|
+
const zlib = await requireZlib();
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
zlib.brotliDecompress(input, { maxOutputLength }, (error, output) => {
|
|
48
|
+
if (error)
|
|
49
|
+
reject(error);
|
|
50
|
+
else
|
|
51
|
+
resolve(output);
|
|
52
|
+
});
|
|
34
53
|
});
|
|
35
|
-
}
|
|
36
|
-
const sha256 = (value) =>
|
|
54
|
+
};
|
|
55
|
+
const sha256 = async (value) => {
|
|
56
|
+
const crypto = await requireCrypto();
|
|
57
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
58
|
+
};
|
|
59
|
+
const randomUUID = async () => {
|
|
60
|
+
const crypto = await requireCrypto();
|
|
61
|
+
return crypto.randomUUID();
|
|
62
|
+
};
|
|
37
63
|
const positiveInteger = (value, name) => {
|
|
38
64
|
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
39
65
|
throw new Error(`${name} must be a positive safe integer`);
|
|
@@ -105,7 +131,7 @@ export class LambderDdbCache {
|
|
|
105
131
|
}
|
|
106
132
|
if (cached)
|
|
107
133
|
this.memory?.delete(normalizedKey);
|
|
108
|
-
const pk = this.partitionKey(normalizedKey);
|
|
134
|
+
const pk = await this.partitionKey(normalizedKey);
|
|
109
135
|
const manifest = await this.readManifest(pk);
|
|
110
136
|
if (!manifest || manifest.expiresAt <= nowSeconds)
|
|
111
137
|
return undefined;
|
|
@@ -114,7 +140,7 @@ export class LambderDdbCache {
|
|
|
114
140
|
if (compressed.length !== manifest.compressedBytes) {
|
|
115
141
|
throw new Error("compressed byte length does not match manifest");
|
|
116
142
|
}
|
|
117
|
-
if (sha256(compressed) !== manifest.checksum) {
|
|
143
|
+
if (await sha256(compressed) !== manifest.checksum) {
|
|
118
144
|
throw new Error("compressed checksum does not match manifest");
|
|
119
145
|
}
|
|
120
146
|
const output = await decompress(compressed, this.maxValueBytes);
|
|
@@ -140,7 +166,7 @@ export class LambderDdbCache {
|
|
|
140
166
|
return true;
|
|
141
167
|
if (cached)
|
|
142
168
|
this.memory?.delete(normalizedKey);
|
|
143
|
-
const manifest = await this.readManifest(this.partitionKey(normalizedKey));
|
|
169
|
+
const manifest = await this.readManifest(await this.partitionKey(normalizedKey));
|
|
144
170
|
return !!manifest && manifest.expiresAt > nowSeconds;
|
|
145
171
|
}
|
|
146
172
|
async set(key, value, options = {}) {
|
|
@@ -157,8 +183,8 @@ export class LambderDdbCache {
|
|
|
157
183
|
if (compressed.length > this.maxValueBytes) {
|
|
158
184
|
throw new Error(`Compressed cache value exceeds maxValueBytes (${compressed.length} > ${this.maxValueBytes})`);
|
|
159
185
|
}
|
|
160
|
-
const pk = this.partitionKey(normalizedKey);
|
|
161
|
-
const version = `${Date.now().toString(36)}-${randomUUID()}`;
|
|
186
|
+
const pk = await this.partitionKey(normalizedKey);
|
|
187
|
+
const version = `${Date.now().toString(36)}-${await randomUUID()}`;
|
|
162
188
|
const expiresAt = this.nowSeconds() + ttlSeconds;
|
|
163
189
|
const chunks = [];
|
|
164
190
|
const inline = compressed.length <= this.chunkBytes;
|
|
@@ -187,7 +213,7 @@ export class LambderDdbCache {
|
|
|
187
213
|
chunkCount: { N: String(chunks.length) },
|
|
188
214
|
compressedBytes: { N: String(compressed.length) },
|
|
189
215
|
uncompressedBytes: { N: String(input.length) },
|
|
190
|
-
checksum: { S: sha256(compressed) },
|
|
216
|
+
checksum: { S: await sha256(compressed) },
|
|
191
217
|
encoding: { S: "br" },
|
|
192
218
|
createdAt: { N: String(this.nowSeconds()) },
|
|
193
219
|
expiresAt: { N: String(expiresAt) },
|
|
@@ -198,7 +224,7 @@ export class LambderDdbCache {
|
|
|
198
224
|
}
|
|
199
225
|
async delete(key) {
|
|
200
226
|
const normalizedKey = this.normalizeKey(key);
|
|
201
|
-
const pk = this.partitionKey(normalizedKey);
|
|
227
|
+
const pk = await this.partitionKey(normalizedKey);
|
|
202
228
|
this.memory?.delete(normalizedKey);
|
|
203
229
|
const keys = [];
|
|
204
230
|
let cursor;
|
|
@@ -264,8 +290,8 @@ export class LambderDdbCache {
|
|
|
264
290
|
async fill(key, factory, options) {
|
|
265
291
|
const leaseSeconds = positiveInteger(options.leaseSeconds ?? 15, "leaseSeconds");
|
|
266
292
|
const waitForFillMs = positiveInteger(options.waitForFillMs ?? 5_000, "waitForFillMs");
|
|
267
|
-
const pk = this.partitionKey(key);
|
|
268
|
-
const owner = randomUUID();
|
|
293
|
+
const pk = await this.partitionKey(key);
|
|
294
|
+
const owner = await randomUUID();
|
|
269
295
|
if (await this.acquireLease(pk, owner, leaseSeconds)) {
|
|
270
296
|
try {
|
|
271
297
|
const value = await factory();
|
|
@@ -465,8 +491,8 @@ export class LambderDdbCache {
|
|
|
465
491
|
}
|
|
466
492
|
return key;
|
|
467
493
|
}
|
|
468
|
-
partitionKey(key) {
|
|
469
|
-
return `${this.namespace}#${sha256(key)}`;
|
|
494
|
+
async partitionKey(key) {
|
|
495
|
+
return `${this.namespace}#${await sha256(key)}`;
|
|
470
496
|
}
|
|
471
497
|
chunkSortKey(version, index) {
|
|
472
498
|
return `chunk#${version}#${String(index).padStart(6, "0")}`;
|