Skip to content

Commit 14b6b39

Browse files
committed
feat: support TTL in setJSON
1 parent 74156d6 commit 14b6b39

File tree

2 files changed

+108
-11
lines changed

2 files changed

+108
-11
lines changed

src/main.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,86 @@ describe('set', () => {
366366
})
367367
})
368368

369+
describe('setJSON', () => {
370+
test('Writes to the blob store using API credentials', async () => {
371+
expect.assertions(5)
372+
373+
const fetcher = async (...args: Parameters<typeof globalThis.fetch>) => {
374+
const [url, options] = args
375+
const headers = options?.headers as Record<string, string>
376+
377+
expect(options?.method).toBe('put')
378+
379+
if (url === `https://api.netlify.com/api/v1/sites/${siteID}/blobs/${key}?context=production`) {
380+
const data = JSON.stringify({ url: signedURL })
381+
382+
expect(headers.authorization).toBe(`Bearer ${apiToken}`)
383+
384+
return new Response(data)
385+
}
386+
387+
if (url === signedURL) {
388+
expect(options?.body).toBe(JSON.stringify({ value }))
389+
expect(headers['cache-control']).toBe('max-age=0, stale-while-revalidate=60')
390+
391+
return new Response(value)
392+
}
393+
394+
throw new Error(`Unexpected fetch call: ${url}`)
395+
}
396+
397+
const blobs = new Blobs({
398+
authentication: {
399+
token: apiToken,
400+
},
401+
fetcher,
402+
siteID,
403+
})
404+
405+
await blobs.setJSON(key, { value })
406+
})
407+
408+
test('Accepts a TTL parameter', async () => {
409+
expect.assertions(6)
410+
411+
const ttl = new Date(Date.now() + 15_000)
412+
const fetcher = async (...args: Parameters<typeof globalThis.fetch>) => {
413+
const [url, options] = args
414+
const headers = options?.headers as Record<string, string>
415+
416+
expect(options?.method).toBe('put')
417+
418+
if (url === `https://api.netlify.com/api/v1/sites/${siteID}/blobs/${key}?context=production`) {
419+
const data = JSON.stringify({ url: signedURL })
420+
421+
expect(headers.authorization).toBe(`Bearer ${apiToken}`)
422+
423+
return new Response(data)
424+
}
425+
426+
if (url === signedURL) {
427+
expect(options?.body).toBe(JSON.stringify({ value }))
428+
expect(headers['cache-control']).toBe('max-age=0, stale-while-revalidate=60')
429+
expect(headers['x-nf-expires-at']).toBe(ttl.getTime().toString())
430+
431+
return new Response(value)
432+
}
433+
434+
throw new Error(`Unexpected fetch call: ${url}`)
435+
}
436+
437+
const blobs = new Blobs({
438+
authentication: {
439+
token: apiToken,
440+
},
441+
fetcher,
442+
siteID,
443+
})
444+
445+
await blobs.setJSON(key, { value }, { ttl })
446+
})
447+
})
448+
369449
describe('delete', () => {
370450
test('Deletes from the blob store using API credentials', async () => {
371451
expect.assertions(4)

src/main.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ enum ResponseType {
2929
Text = 'text',
3030
}
3131

32+
interface SetOptions {
33+
ttl?: Date | number
34+
}
35+
3236
type BlobInput = ReadableStream | string | ArrayBuffer | Blob
3337

3438
const EXPIRY_HEADER = 'x-nf-expires-at'
@@ -91,6 +95,26 @@ export class Blobs {
9195
}
9296
}
9397

98+
private static getTTLHeaders(ttl: Date | number | undefined): Record<string, string> {
99+
if (typeof ttl === 'number') {
100+
return {
101+
[EXPIRY_HEADER]: (Date.now() + ttl).toString(),
102+
}
103+
}
104+
105+
if (ttl instanceof Date) {
106+
return {
107+
[EXPIRY_HEADER]: ttl.getTime().toString(),
108+
}
109+
}
110+
111+
if (ttl === undefined) {
112+
return {}
113+
}
114+
115+
throw new TypeError(`'ttl' value must be a number or a Date, ${typeof ttl} found.`)
116+
}
117+
94118
private isConfigured() {
95119
return Boolean(this.authentication?.token) && Boolean(this.siteID)
96120
}
@@ -180,23 +204,16 @@ export class Blobs {
180204
throw new Error(`Invalid 'type' property: ${type}. Expected: arrayBuffer, blob, json, stream, or text.`)
181205
}
182206

183-
async set(key: string, data: BlobInput, { ttl }: { ttl?: Date | number } = {}) {
184-
const headers: Record<string, string> = {}
185-
186-
if (typeof ttl === 'number') {
187-
headers[EXPIRY_HEADER] = (Date.now() + ttl).toString()
188-
} else if (ttl instanceof Date) {
189-
headers[EXPIRY_HEADER] = ttl.getTime().toString()
190-
} else if (ttl !== undefined) {
191-
throw new TypeError(`'ttl' value must be a number or a Date, ${typeof ttl} found.`)
192-
}
207+
async set(key: string, data: BlobInput, { ttl }: SetOptions = {}) {
208+
const headers = Blobs.getTTLHeaders(ttl)
193209

194210
await this.makeStoreRequest(key, HTTPMethod.Put, headers, data)
195211
}
196212

197-
async setJSON(key: string, data: unknown) {
213+
async setJSON(key: string, data: unknown, { ttl }: SetOptions = {}) {
198214
const payload = JSON.stringify(data)
199215
const headers = {
216+
...Blobs.getTTLHeaders(ttl),
200217
'content-type': 'application/json',
201218
}
202219

0 commit comments

Comments
 (0)