@vercel/kv2 0.0.16 → 0.0.18

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.
Files changed (47) hide show
  1. package/README.md +4 -7
  2. package/SKILL.md +15 -4
  3. package/dist/blob-stores/disk-blob-store.d.ts +20 -0
  4. package/dist/blob-stores/disk-blob-store.d.ts.map +1 -0
  5. package/dist/blob-stores/disk-blob-store.js +270 -0
  6. package/dist/blob-stores/disk-blob-store.js.map +1 -0
  7. package/dist/blob-stores/in-memory-blob-store.d.ts +30 -0
  8. package/dist/blob-stores/in-memory-blob-store.d.ts.map +1 -0
  9. package/dist/blob-stores/in-memory-blob-store.js +187 -0
  10. package/dist/blob-stores/in-memory-blob-store.js.map +1 -0
  11. package/dist/blob-stores/index.d.ts +4 -0
  12. package/dist/blob-stores/index.d.ts.map +1 -0
  13. package/dist/blob-stores/index.js +4 -0
  14. package/dist/blob-stores/index.js.map +1 -0
  15. package/dist/blob-stores/vercel-blob-store.d.ts +11 -0
  16. package/dist/blob-stores/vercel-blob-store.d.ts.map +1 -0
  17. package/dist/blob-stores/vercel-blob-store.js +34 -0
  18. package/dist/blob-stores/vercel-blob-store.js.map +1 -0
  19. package/dist/cached-kv.d.ts +12 -0
  20. package/dist/cached-kv.d.ts.map +1 -1
  21. package/dist/cached-kv.js +18 -0
  22. package/dist/cached-kv.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/readme.test.js +2 -0
  28. package/dist/readme.test.js.map +1 -1
  29. package/dist/testing/test-index.d.ts +3 -0
  30. package/dist/testing/test-index.d.ts.map +1 -1
  31. package/dist/testing/test-index.js +4 -0
  32. package/dist/testing/test-index.js.map +1 -1
  33. package/dist/typed-kv.d.ts +59 -3
  34. package/dist/typed-kv.d.ts.map +1 -1
  35. package/dist/typed-kv.js +69 -3
  36. package/dist/typed-kv.js.map +1 -1
  37. package/dist/types.d.ts +1 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/upstream-kv.d.ts +1 -0
  40. package/dist/upstream-kv.d.ts.map +1 -1
  41. package/dist/upstream-kv.js +6 -6
  42. package/dist/upstream-kv.js.map +1 -1
  43. package/docs/api-reference.md +31 -0
  44. package/docs/getting-started.md +4 -0
  45. package/docs/indexes.md +7 -3
  46. package/docs/typed-stores.md +4 -0
  47. package/package.json +1 -1
package/README.md CHANGED
@@ -31,6 +31,10 @@ if (result.exists) {
31
31
  console.log((await result.value).name); // "Alice"
32
32
  }
33
33
 
34
+ // Or use getValue() for a simpler read (returns undefined if not found)
35
+ const user = await users.getValue("alice");
36
+ console.log(user?.name); // "Alice"
37
+
34
38
  // Delete, iterate keys, entries, getMany — see docs
35
39
  await users.delete("alice");
36
40
  ```
@@ -75,13 +79,6 @@ BLOB_READ_WRITE_TOKEN=vercel_blob_...
75
79
 
76
80
  See [Getting Started](https://github.com/vercel-labs/KV2/blob/main/docs/getting-started.md) for full environment setup.
77
81
 
78
- ## Testing
79
-
80
- ```bash
81
- pnpm test # Unit tests (fake blob store)
82
- pnpm test:integration # Integration tests (real Vercel Blob)
83
- ```
84
-
85
82
  ## License
86
83
 
87
84
  ISC
package/SKILL.md CHANGED
@@ -22,7 +22,16 @@ await users.set("alice", { name: "Alice", email: "alice@example.com" });
22
22
 
23
23
  ### Reading values
24
24
 
25
- `get()` returns a discriminated union always check `exists` before accessing `value`. Note that `value` is a `Promise` (lazy-parsed), so it needs `await`.
25
+ For simple reads where you just need the value, use `getValue()` it returns `V | undefined`:
26
+
27
+ ```typescript
28
+ const user = await users.getValue("alice");
29
+ if (user) {
30
+ console.log(user.name);
31
+ }
32
+ ```
33
+
34
+ When you need metadata, version, stream, or `update()`, use `get()` — it returns a discriminated union:
26
35
 
27
36
  ```typescript
28
37
  const result = await users.get("alice");
@@ -34,13 +43,15 @@ if (result.exists) {
34
43
 
35
44
  ### Indexes
36
45
 
37
- Define indexes in `getStore()` to enable lookups by secondary keys. The third type parameter is a union of index names.
46
+ Define indexes in `getStore()` to enable lookups by secondary keys. Use `defineIndexes<V>()` to let TypeScript infer index names automatically:
38
47
 
39
48
  ```typescript
40
- const users = kv.getStore<User, undefined, "byEmail" | "byRole">("users/", {
49
+ import { defineIndexes } from "@vercel/kv2";
50
+
51
+ const users = kv.getStore("users/", defineIndexes<User>()({
41
52
  byEmail: { key: (u) => u.email, unique: true },
42
53
  byRole: { key: (u) => u.role },
43
- });
54
+ }));
44
55
 
45
56
  // Exact match on unique index — returns single result
46
57
  const result = await users.get({ byEmail: "alice@example.com" });
@@ -0,0 +1,20 @@
1
+ import { type GetBlobResult, type GetCommandOptions, type ListBlobResult, type ListCommandOptions, type PutBlobResult, type PutCommandOptions } from "@vercel/blob";
2
+ import type { BlobStore, PutBody } from "../types.js";
3
+ export declare class DiskBlobStore implements BlobStore {
4
+ private rootDir;
5
+ private locks;
6
+ constructor(rootDir: string);
7
+ private filePath;
8
+ private metaPath;
9
+ private withLock;
10
+ private readMeta;
11
+ get(pathname: string, _options: GetCommandOptions): Promise<GetBlobResult | null>;
12
+ put(pathname: string, body: PutBody, options: PutCommandOptions): Promise<PutBlobResult>;
13
+ del(urlOrPathname: string | string[]): Promise<void>;
14
+ list(options?: ListCommandOptions): Promise<ListBlobResult>;
15
+ private walkDir;
16
+ clear(): Promise<void>;
17
+ has(pathname: string): Promise<boolean>;
18
+ getContent(pathname: string): Promise<Buffer | undefined>;
19
+ }
20
+ //# sourceMappingURL=disk-blob-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disk-blob-store.d.ts","sourceRoot":"","sources":["../../src/blob-stores/disk-blob-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA4CtD,qBAAa,aAAc,YAAW,SAAS;IAC7C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAoC;gBAErC,OAAO,EAAE,MAAM;IAI3B,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,QAAQ;YAIF,QAAQ;YAsBR,QAAQ;IAUhB,GAAG,CACP,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAwC1B,GAAG,CACP,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,aAAa,CAAC;IA6CnB,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBpD,IAAI,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;YA8CnD,OAAO;IA+Bf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASvC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAOhE"}
@@ -0,0 +1,270 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { BlobPreconditionFailedError, } from "@vercel/blob";
4
+ let etagCounter = 0;
5
+ function generateEtag() {
6
+ return `"disk-etag-${++etagCounter}-${Date.now()}"`;
7
+ }
8
+ async function toBuffer(body) {
9
+ if (typeof body === "string") {
10
+ return Buffer.from(body, "utf-8");
11
+ }
12
+ if (body instanceof Buffer) {
13
+ return body;
14
+ }
15
+ if (body instanceof Blob) {
16
+ const arrayBuffer = await body.arrayBuffer();
17
+ return Buffer.from(arrayBuffer);
18
+ }
19
+ if (body instanceof ArrayBuffer) {
20
+ return Buffer.from(body);
21
+ }
22
+ if (ArrayBuffer.isView(body)) {
23
+ return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
24
+ }
25
+ if (body instanceof ReadableStream) {
26
+ const chunks = [];
27
+ const reader = body.getReader();
28
+ while (true) {
29
+ const { done, value } = await reader.read();
30
+ if (done)
31
+ break;
32
+ chunks.push(value);
33
+ }
34
+ return Buffer.concat(chunks);
35
+ }
36
+ throw new Error("Unsupported body type");
37
+ }
38
+ export class DiskBlobStore {
39
+ rootDir;
40
+ locks = new Map();
41
+ constructor(rootDir) {
42
+ this.rootDir = rootDir;
43
+ }
44
+ filePath(pathname) {
45
+ return path.join(this.rootDir, pathname);
46
+ }
47
+ metaPath(pathname) {
48
+ return `${path.join(this.rootDir, pathname)}.meta`;
49
+ }
50
+ async withLock(pathname, fn) {
51
+ while (this.locks.has(pathname)) {
52
+ await this.locks.get(pathname);
53
+ }
54
+ let resolve;
55
+ const lock = new Promise((r) => {
56
+ resolve = r;
57
+ });
58
+ this.locks.set(pathname, lock);
59
+ try {
60
+ return await fn();
61
+ }
62
+ finally {
63
+ this.locks.delete(pathname);
64
+ resolve();
65
+ }
66
+ }
67
+ async readMeta(pathname) {
68
+ try {
69
+ const raw = await fs.readFile(this.metaPath(pathname), "utf-8");
70
+ return JSON.parse(raw);
71
+ }
72
+ catch (e) {
73
+ if (e.code === "ENOENT")
74
+ return null;
75
+ throw e;
76
+ }
77
+ }
78
+ async get(pathname, _options) {
79
+ const meta = await this.readMeta(pathname);
80
+ if (!meta)
81
+ return null;
82
+ let content;
83
+ try {
84
+ content = await fs.readFile(this.filePath(pathname));
85
+ }
86
+ catch (e) {
87
+ if (e.code === "ENOENT")
88
+ return null;
89
+ throw e;
90
+ }
91
+ const stream = new ReadableStream({
92
+ start(controller) {
93
+ controller.enqueue(new Uint8Array(content));
94
+ controller.close();
95
+ },
96
+ });
97
+ return {
98
+ stream,
99
+ blob: {
100
+ url: `disk://${pathname}`,
101
+ downloadUrl: `disk://${pathname}?download=1`,
102
+ pathname,
103
+ contentType: meta.contentType,
104
+ contentDisposition: `attachment; filename="${pathname.split("/").pop()}"`,
105
+ cacheControl: "public, max-age=31536000, immutable",
106
+ size: meta.size,
107
+ uploadedAt: new Date(meta.uploadedAt),
108
+ etag: meta.etag,
109
+ },
110
+ headers: new Headers({
111
+ "content-type": meta.contentType,
112
+ "content-length": String(meta.size),
113
+ etag: meta.etag,
114
+ }),
115
+ };
116
+ }
117
+ async put(pathname, body, options) {
118
+ const content = await toBuffer(body);
119
+ return this.withLock(pathname, async () => {
120
+ const existingMeta = await this.readMeta(pathname);
121
+ const allowOverwrite = options.allowOverwrite ?? true;
122
+ if (!allowOverwrite && existingMeta) {
123
+ throw new BlobPreconditionFailedError();
124
+ }
125
+ if (options.ifMatch !== undefined) {
126
+ if (!existingMeta) {
127
+ throw new BlobPreconditionFailedError();
128
+ }
129
+ if (existingMeta.etag !== options.ifMatch) {
130
+ throw new BlobPreconditionFailedError();
131
+ }
132
+ }
133
+ const etag = generateEtag();
134
+ const contentType = options.contentType ?? "application/octet-stream";
135
+ const meta = {
136
+ etag,
137
+ contentType,
138
+ uploadedAt: new Date().toISOString(),
139
+ size: content.length,
140
+ };
141
+ const fp = this.filePath(pathname);
142
+ await fs.mkdir(path.dirname(fp), { recursive: true });
143
+ await fs.writeFile(fp, content);
144
+ await fs.writeFile(this.metaPath(pathname), JSON.stringify(meta));
145
+ return {
146
+ url: `disk://${pathname}`,
147
+ downloadUrl: `disk://${pathname}?download=1`,
148
+ pathname,
149
+ contentType,
150
+ contentDisposition: `attachment; filename="${pathname.split("/").pop()}"`,
151
+ etag,
152
+ };
153
+ });
154
+ }
155
+ async del(urlOrPathname) {
156
+ const paths = Array.isArray(urlOrPathname)
157
+ ? urlOrPathname
158
+ : [urlOrPathname];
159
+ for (const p of paths) {
160
+ const pathname = p.startsWith("disk://") ? p.slice(7) : p;
161
+ try {
162
+ await fs.unlink(this.filePath(pathname));
163
+ }
164
+ catch (e) {
165
+ if (e.code !== "ENOENT")
166
+ throw e;
167
+ }
168
+ try {
169
+ await fs.unlink(this.metaPath(pathname));
170
+ }
171
+ catch (e) {
172
+ if (e.code !== "ENOENT")
173
+ throw e;
174
+ }
175
+ }
176
+ }
177
+ async list(options) {
178
+ // Scope the walk to the deepest directory implied by the prefix
179
+ // e.g. prefix "users/posts/" walks <rootDir>/users/posts/ instead of <rootDir>/
180
+ const prefix = options?.prefix ?? "";
181
+ const prefixDir = prefix.includes("/")
182
+ ? prefix.slice(0, prefix.lastIndexOf("/") + 1)
183
+ : "";
184
+ const walkRoot = prefixDir
185
+ ? path.join(this.rootDir, prefixDir)
186
+ : this.rootDir;
187
+ const allFiles = await this.walkDir(walkRoot);
188
+ // Filter to content files (skip .meta), compute relative pathnames
189
+ const entries = [];
190
+ for (const absPath of allFiles) {
191
+ if (absPath.endsWith(".meta"))
192
+ continue;
193
+ const pathname = path.relative(this.rootDir, absPath);
194
+ if (prefix && !pathname.startsWith(prefix))
195
+ continue;
196
+ const meta = await this.readMeta(pathname);
197
+ if (meta)
198
+ entries.push({ pathname, meta });
199
+ }
200
+ entries.sort((a, b) => a.pathname.localeCompare(b.pathname));
201
+ const limit = options?.limit ?? 1000;
202
+ const cursorIndex = options?.cursor
203
+ ? Number.parseInt(options.cursor, 10)
204
+ : 0;
205
+ const slice = entries.slice(cursorIndex, cursorIndex + limit);
206
+ const hasMore = cursorIndex + limit < entries.length;
207
+ return {
208
+ blobs: slice.map((e) => ({
209
+ url: `disk://${e.pathname}`,
210
+ downloadUrl: `disk://${e.pathname}?download=1`,
211
+ pathname: e.pathname,
212
+ size: e.meta.size,
213
+ uploadedAt: new Date(e.meta.uploadedAt),
214
+ etag: e.meta.etag,
215
+ })),
216
+ hasMore,
217
+ cursor: hasMore ? String(cursorIndex + limit) : undefined,
218
+ };
219
+ }
220
+ async walkDir(dir, concurrency = 16) {
221
+ let names;
222
+ try {
223
+ names = await fs.readdir(dir);
224
+ }
225
+ catch (e) {
226
+ if (e.code === "ENOENT")
227
+ return [];
228
+ throw e;
229
+ }
230
+ const results = [];
231
+ // Process entries in batches to limit open file descriptors
232
+ for (let i = 0; i < names.length; i += concurrency) {
233
+ const batch = names.slice(i, i + concurrency);
234
+ const settled = await Promise.all(batch.map(async (name) => {
235
+ const full = path.join(dir, name);
236
+ const stat = await fs.stat(full);
237
+ if (stat.isDirectory()) {
238
+ return this.walkDir(full, concurrency);
239
+ }
240
+ return [full];
241
+ }));
242
+ for (const files of settled) {
243
+ results.push(...files);
244
+ }
245
+ }
246
+ return results;
247
+ }
248
+ // Test helpers
249
+ async clear() {
250
+ await fs.rm(this.rootDir, { recursive: true, force: true });
251
+ }
252
+ async has(pathname) {
253
+ try {
254
+ await fs.access(this.filePath(pathname));
255
+ return true;
256
+ }
257
+ catch {
258
+ return false;
259
+ }
260
+ }
261
+ async getContent(pathname) {
262
+ try {
263
+ return await fs.readFile(this.filePath(pathname));
264
+ }
265
+ catch {
266
+ return undefined;
267
+ }
268
+ }
269
+ }
270
+ //# sourceMappingURL=disk-blob-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disk-blob-store.js","sourceRoot":"","sources":["../../src/blob-stores/disk-blob-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { type GetBlobResult, type GetCommandOptions, type ListBlobResult, type ListCommandOptions, type PutBlobResult, type PutCommandOptions } from "@vercel/blob";
2
+ import type { BlobStore, PutBody } from "../types.js";
3
+ interface StoredBlob {
4
+ pathname: string;
5
+ content: Buffer;
6
+ contentType: string;
7
+ uploadedAt: Date;
8
+ size: number;
9
+ etag: string;
10
+ }
11
+ export declare class InMemoryBlobStore implements BlobStore {
12
+ private blobs;
13
+ private locks;
14
+ /**
15
+ * Serialize writes to the same key to prevent race conditions
16
+ * where concurrent callers all pass precondition checks before
17
+ * any write lands.
18
+ */
19
+ private withLock;
20
+ get(pathname: string, _options: GetCommandOptions): Promise<GetBlobResult | null>;
21
+ put(pathname: string, body: PutBody, options: PutCommandOptions): Promise<PutBlobResult>;
22
+ del(urlOrPathname: string | string[]): Promise<void>;
23
+ list(options?: ListCommandOptions): Promise<ListBlobResult>;
24
+ clear(): void;
25
+ has(pathname: string): boolean;
26
+ getContent(pathname: string): Buffer | undefined;
27
+ getAll(): Map<string, StoredBlob>;
28
+ }
29
+ export {};
30
+ //# sourceMappingURL=in-memory-blob-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-blob-store.d.ts","sourceRoot":"","sources":["../../src/blob-stores/in-memory-blob-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtD,UAAU,UAAU;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAqCD,qBAAa,iBAAkB,YAAW,SAAS;IACjD,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,KAAK,CAAoC;IAEjD;;;;OAIG;YACW,QAAQ;IAuBhB,GAAG,CACP,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAmC1B,GAAG,CACP,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,aAAa,CAAC;IA+CnB,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpD,IAAI,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;IAoCjE,KAAK,IAAI,IAAI;IAIb,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAI9B,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIhD,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC;CAGlC"}
@@ -0,0 +1,187 @@
1
+ import { BlobPreconditionFailedError, } from "@vercel/blob";
2
+ let etagCounter = 0;
3
+ function generateEtag() {
4
+ return `"fake-etag-${++etagCounter}-${Date.now()}"`;
5
+ }
6
+ async function toBuffer(body) {
7
+ if (typeof body === "string") {
8
+ return Buffer.from(body, "utf-8");
9
+ }
10
+ if (body instanceof Buffer) {
11
+ return body;
12
+ }
13
+ if (body instanceof Blob) {
14
+ const arrayBuffer = await body.arrayBuffer();
15
+ return Buffer.from(arrayBuffer);
16
+ }
17
+ if (body instanceof ArrayBuffer) {
18
+ return Buffer.from(body);
19
+ }
20
+ if (ArrayBuffer.isView(body)) {
21
+ return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
22
+ }
23
+ if (body instanceof ReadableStream) {
24
+ const chunks = [];
25
+ const reader = body.getReader();
26
+ while (true) {
27
+ const { done, value } = await reader.read();
28
+ if (done)
29
+ break;
30
+ chunks.push(value);
31
+ }
32
+ return Buffer.concat(chunks);
33
+ }
34
+ throw new Error("Unsupported body type");
35
+ }
36
+ export class InMemoryBlobStore {
37
+ blobs = new Map();
38
+ locks = new Map();
39
+ /**
40
+ * Serialize writes to the same key to prevent race conditions
41
+ * where concurrent callers all pass precondition checks before
42
+ * any write lands.
43
+ */
44
+ async withLock(pathname, fn) {
45
+ // Wait for any in-flight write to this key
46
+ while (this.locks.has(pathname)) {
47
+ await this.locks.get(pathname);
48
+ }
49
+ let resolve;
50
+ const lock = new Promise((r) => {
51
+ resolve = r;
52
+ });
53
+ this.locks.set(pathname, lock);
54
+ try {
55
+ return await fn();
56
+ }
57
+ finally {
58
+ this.locks.delete(pathname);
59
+ resolve();
60
+ }
61
+ }
62
+ async get(pathname, _options) {
63
+ const blob = this.blobs.get(pathname);
64
+ if (!blob) {
65
+ return null;
66
+ }
67
+ const content = blob.content;
68
+ const stream = new ReadableStream({
69
+ start(controller) {
70
+ controller.enqueue(new Uint8Array(content));
71
+ controller.close();
72
+ },
73
+ });
74
+ return {
75
+ stream,
76
+ blob: {
77
+ url: `fake://${pathname}`,
78
+ downloadUrl: `fake://${pathname}?download=1`,
79
+ pathname,
80
+ contentType: blob.contentType,
81
+ contentDisposition: `attachment; filename="${pathname.split("/").pop()}"`,
82
+ cacheControl: "public, max-age=31536000, immutable",
83
+ size: blob.size,
84
+ uploadedAt: blob.uploadedAt,
85
+ etag: blob.etag,
86
+ },
87
+ headers: new Headers({
88
+ "content-type": blob.contentType,
89
+ "content-length": String(blob.size),
90
+ etag: blob.etag,
91
+ }),
92
+ };
93
+ }
94
+ async put(pathname, body, options) {
95
+ // Buffer the body outside the lock to avoid holding the lock
96
+ // during potentially slow stream reads, matching real blob store
97
+ // behavior where the server handles serialization.
98
+ const content = await toBuffer(body);
99
+ return this.withLock(pathname, () => {
100
+ const existingBlob = this.blobs.get(pathname);
101
+ // Check allowOverwrite option (default: true based on @vercel/blob behavior)
102
+ const allowOverwrite = options.allowOverwrite ?? true;
103
+ if (!allowOverwrite && existingBlob) {
104
+ throw new BlobPreconditionFailedError();
105
+ }
106
+ // Check ifMatch (optimistic locking)
107
+ if (options.ifMatch !== undefined) {
108
+ if (!existingBlob) {
109
+ // Real @vercel/blob throws "The specified key does not exist"
110
+ throw new BlobPreconditionFailedError();
111
+ }
112
+ if (existingBlob.etag !== options.ifMatch) {
113
+ throw new BlobPreconditionFailedError();
114
+ }
115
+ }
116
+ const etag = generateEtag();
117
+ this.blobs.set(pathname, {
118
+ pathname,
119
+ content,
120
+ contentType: options.contentType ?? "application/octet-stream",
121
+ uploadedAt: new Date(),
122
+ size: content.length,
123
+ etag,
124
+ });
125
+ return {
126
+ url: `fake://${pathname}`,
127
+ downloadUrl: `fake://${pathname}?download=1`,
128
+ pathname,
129
+ contentType: options.contentType ?? "application/octet-stream",
130
+ contentDisposition: `attachment; filename="${pathname.split("/").pop()}"`,
131
+ etag,
132
+ };
133
+ });
134
+ }
135
+ async del(urlOrPathname) {
136
+ const paths = Array.isArray(urlOrPathname)
137
+ ? urlOrPathname
138
+ : [urlOrPathname];
139
+ for (const p of paths) {
140
+ // Handle both URLs and pathnames
141
+ const pathname = p.startsWith("fake://") ? p.slice(7) : p;
142
+ this.blobs.delete(pathname);
143
+ }
144
+ }
145
+ async list(options) {
146
+ let entries = [...this.blobs.values()];
147
+ // Filter by prefix
148
+ if (options?.prefix) {
149
+ entries = entries.filter((b) => b.pathname.startsWith(options.prefix));
150
+ }
151
+ // Sort by pathname for consistent ordering
152
+ entries.sort((a, b) => a.pathname.localeCompare(b.pathname));
153
+ // Handle pagination
154
+ const limit = options?.limit ?? 1000;
155
+ const cursorIndex = options?.cursor
156
+ ? Number.parseInt(options.cursor, 10)
157
+ : 0;
158
+ const slice = entries.slice(cursorIndex, cursorIndex + limit);
159
+ const hasMore = cursorIndex + limit < entries.length;
160
+ return {
161
+ blobs: slice.map((b) => ({
162
+ url: `fake://${b.pathname}`,
163
+ downloadUrl: `fake://${b.pathname}?download=1`,
164
+ pathname: b.pathname,
165
+ size: b.size,
166
+ uploadedAt: b.uploadedAt,
167
+ etag: b.etag,
168
+ })),
169
+ hasMore,
170
+ cursor: hasMore ? String(cursorIndex + limit) : undefined,
171
+ };
172
+ }
173
+ // Test helpers
174
+ clear() {
175
+ this.blobs.clear();
176
+ }
177
+ has(pathname) {
178
+ return this.blobs.has(pathname);
179
+ }
180
+ getContent(pathname) {
181
+ return this.blobs.get(pathname)?.content;
182
+ }
183
+ getAll() {
184
+ return new Map(this.blobs);
185
+ }
186
+ }
187
+ //# sourceMappingURL=in-memory-blob-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-blob-store.js","sourceRoot":"","sources":["../../src/blob-stores/in-memory-blob-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { InMemoryBlobStore } from "./in-memory-blob-store.js";
2
+ export { VercelBlobStore } from "./vercel-blob-store.js";
3
+ export { DiskBlobStore } from "./disk-blob-store.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blob-stores/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { InMemoryBlobStore } from "./in-memory-blob-store.js";
2
+ export { VercelBlobStore } from "./vercel-blob-store.js";
3
+ export { DiskBlobStore } from "./disk-blob-store.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/blob-stores/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type GetBlobResult, type GetCommandOptions, type ListBlobResult, type ListCommandOptions, type PutBlobResult, type PutCommandOptions } from "@vercel/blob";
2
+ import type { BlobStore, PutBody } from "../types.js";
3
+ export declare class VercelBlobStore implements BlobStore {
4
+ private token?;
5
+ constructor(token?: string);
6
+ get(pathname: string, options: GetCommandOptions): Promise<GetBlobResult | null>;
7
+ put(pathname: string, body: PutBody, options: PutCommandOptions): Promise<PutBlobResult>;
8
+ del(urlOrPathname: string | string[]): Promise<void>;
9
+ list(options?: ListCommandOptions): Promise<ListBlobResult>;
10
+ }
11
+ //# sourceMappingURL=vercel-blob-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel-blob-store.d.ts","sourceRoot":"","sources":["../../src/blob-stores/vercel-blob-store.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EAKvB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtD,qBAAa,eAAgB,YAAW,SAAS;IAC/C,OAAO,CAAC,KAAK,CAAC,CAAS;gBAEX,KAAK,CAAC,EAAE,MAAM;IAIpB,GAAG,CACP,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAQ1B,GAAG,CACP,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,aAAa,CAAC;IAOnB,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpD,IAAI,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;CAMlE"}