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.
@@ -1,6 +1,5 @@
1
1
  import { BatchWriteItemCommand, DeleteItemCommand, DynamoDBClient, GetItemCommand, PutItemCommand, QueryCommand, } from "@aws-sdk/client-dynamodb";
2
- import { createHash, randomUUID } from "crypto";
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
- const compress = (input, quality) => new Promise((resolve, reject) => {
15
- const options = {
16
- params: {
17
- [zlibConstants.BROTLI_PARAM_QUALITY]: quality,
18
- [zlibConstants.BROTLI_PARAM_MODE]: zlibConstants.BROTLI_MODE_TEXT,
19
- },
20
- };
21
- brotliCompress(input, options, (error, output) => {
22
- if (error)
23
- reject(error);
24
- else
25
- resolve(output);
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) => new Promise((resolve, reject) => {
29
- brotliDecompress(input, { maxOutputLength }, (error, output) => {
30
- if (error)
31
- reject(error);
32
- else
33
- resolve(output);
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) => createHash("sha256").update(value).digest("hex");
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")}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",