Skip to content

Commit 2fa2a31

Browse files
committed
feat: add setFiles method
1 parent 30b5785 commit 2fa2a31

File tree

4 files changed

+115
-2
lines changed

4 files changed

+115
-2
lines changed

package-lock.json

Lines changed: 18 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"node": "^14.16.0 || >=16.0.0"
6464
},
6565
"dependencies": {
66-
"esbuild": "0.18.16"
66+
"esbuild": "0.18.16",
67+
"p-map": "^6.0.0"
6768
}
6869
}

src/main.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,86 @@ describe('set', () => {
295295

296296
await cleanup()
297297
})
298+
299+
test('Accepts multiple files concurrently', async () => {
300+
const contents = ['Hello from key-0', 'Hello from key-1', 'Hello from key-2']
301+
const signedURLs = ['https://signed-url.aws/0', 'https://signed-url.aws/1', 'https://signed-url.aws/2']
302+
303+
const store = new MockFetch()
304+
.put({
305+
headers: { authorization: `Bearer ${apiToken}` },
306+
response: new Response(JSON.stringify({ url: signedURLs[0] })),
307+
url: `https://api.netlify.com/api/v1/sites/${siteID}/blobs/key-0?context=production`,
308+
})
309+
.put({
310+
body: async (body) => {
311+
expect(await streamToString(body as unknown as NodeJS.ReadableStream)).toBe(contents[0])
312+
},
313+
headers: {
314+
'cache-control': 'max-age=0, stale-while-revalidate=60',
315+
},
316+
response: new Response(null),
317+
url: signedURLs[0],
318+
})
319+
.put({
320+
headers: { authorization: `Bearer ${apiToken}` },
321+
response: new Response(JSON.stringify({ url: signedURLs[1] })),
322+
url: `https://api.netlify.com/api/v1/sites/${siteID}/blobs/key-1?context=production`,
323+
})
324+
.put({
325+
body: async (body) => {
326+
expect(await streamToString(body as unknown as NodeJS.ReadableStream)).toBe(contents[1])
327+
},
328+
headers: {
329+
'cache-control': 'max-age=0, stale-while-revalidate=60',
330+
},
331+
response: new Response(null),
332+
url: signedURLs[1],
333+
})
334+
.put({
335+
headers: { authorization: `Bearer ${apiToken}` },
336+
response: new Response(JSON.stringify({ url: signedURLs[2] })),
337+
url: `https://api.netlify.com/api/v1/sites/${siteID}/blobs/key-2?context=production`,
338+
})
339+
.put({
340+
body: async (body) => {
341+
expect(await streamToString(body as unknown as NodeJS.ReadableStream)).toBe(contents[2])
342+
},
343+
headers: {
344+
'cache-control': 'max-age=0, stale-while-revalidate=60',
345+
},
346+
response: new Response(null),
347+
url: signedURLs[2],
348+
})
349+
350+
const writes = await Promise.all(
351+
contents.map(async (content) => {
352+
const { cleanup, path } = await tmp.file()
353+
354+
await writeFile(path, content)
355+
356+
return { cleanup, path }
357+
}),
358+
)
359+
const files = writes.map(({ path }, idx) => ({
360+
key: `key-${idx}`,
361+
path,
362+
}))
363+
364+
const blobs = new Blobs({
365+
authentication: {
366+
token: apiToken,
367+
},
368+
fetcher: store.fetcher,
369+
siteID,
370+
})
371+
372+
await blobs.setFiles(files)
373+
374+
expect(store.fulfilled).toBeTruthy()
375+
376+
await Promise.all(writes.map(({ cleanup }) => cleanup()))
377+
})
298378
}
299379

300380
test('Throws when the API returns a non-200 status code', async () => {

src/main.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { createReadStream } from 'node:fs'
22
import { stat } from 'node:fs/promises'
33
import { Readable } from 'node:stream'
44

5+
import pMap from 'p-map'
6+
57
import { fetchAndRetry } from './retry.ts'
68

79
interface APICredentials {
@@ -31,6 +33,15 @@ interface SetOptions {
3133
ttl?: Date | number
3234
}
3335

36+
interface SetFilesItem extends SetOptions {
37+
key: string
38+
path: string
39+
}
40+
41+
interface SetFilesOptions {
42+
concurrency?: number
43+
}
44+
3445
type BlobInput = ReadableStream | string | ArrayBuffer | Blob
3546

3647
const EXPIRY_HEADER = 'x-nf-expires-at'
@@ -229,6 +240,10 @@ export class Blobs {
229240
await this.makeStoreRequest(key, HTTPMethod.Put, headers, file as ReadableStream)
230241
}
231242

243+
setFiles(files: SetFilesItem[], { concurrency = 5 }: SetFilesOptions = {}) {
244+
return pMap(files, ({ key, path, ...options }) => this.setFile(key, path, options), { concurrency })
245+
}
246+
232247
async setJSON(key: string, data: unknown, { ttl }: SetOptions = {}) {
233248
const payload = JSON.stringify(data)
234249
const headers = {

0 commit comments

Comments
 (0)