8000 feat: support `list()` in local server by eduardoboucas · Pull Request #83 · netlify/blobs · GitHub
[go: up one dir, main page]

Skip to content

feat: support list() in local server #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: add list() method
  • Loading branch information
eduardoboucas committed Oct 24, 2023
commit 5b74ace55750871e36aaf97b6c47172e4219b67f
33 changes: 27 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ second parameter, with one of the following values:
If an object with the given key is not found, `null` is returned.

```javascript
const entry = await blobs.get('some-key', { type: 'json' })
const entry = await store.get('some-key', { type: 'json' })

console.log(entry)
```
Expand All @@ -209,7 +209,7 @@ second parameter, with one of the following values:
If an object with the given key is not found, `null` is returned.

```javascript
const blob = await blobs.getWithMetadata('some-key', { type: 'json' })
const blob = await store.getWithMetadata('some-key', { type: 'json' })

console.log(blob.data, blob.etag, blob.metadata)
```
Expand All @@ -223,7 +223,7 @@ const cachedETag = getFromMockCache('my-key')

// Get entry from the blob store only if its ETag is different from the one you
// have locally, which means the entry has changed since you last obtained it
const { data, etag, fresh } = await blobs.getWithMetadata('some-key', { etag: cachedETag })
const { data, etag, fresh } = await store.getWithMetadata('some-key', { etag: cachedETag })

if (fresh) {
// `data` is `null` because the local blob is fresh
Expand All @@ -240,7 +240,7 @@ Creates an object with the given key and value.
If an entry with the given key already exists, its value is overwritten.

```javascript
await blobs.set('some-key', 'This is a string value')
await store.set('some-key', 'This is a string value')
```

### `setJSON(key: string, value: any, { metadata?: object }): Promise<void>`
Expand All @@ -250,7 +250,7 @@ Convenience method for creating a JSON-serialized object with the given key.
If an entry with the given key already exists, its value is overwritten.

```javascript
await blobs.setJSON('some-key', {
await store.setJSON('some-key', {
foo: 'bar',
})
```
Expand All @@ -260,7 +260,28 @@ await blobs.setJSON('some-key', {
Deletes an object with the given key, if one exists.

```javascript
await blobs.delete('my-key')
await store.delete('my-key')
```

### `list(options?: { cursor?: string, paginate?: boolean. prefix?: string }): Promise<{ blobs: BlobResult[] }>`

Returns a list of blobs in a given store.

```javascript
const { blobs } = await store.list()

// [ { etag: 'etag1', key: 'some-key' }, { etag: 'etag2', key: 'another-key' } ]
console.log(blobs)
```

To filter down the entries that should be returned, an optional `prefix` parameter can be supplied. When used, only the
entries whose key starts with that prefix are returned.

```javascript
const { blobs } = await store.list({ prefix: 'some' })

// [ { etag: 'etag1', key: 'some-key' } ]
console.log(blobs)
```

## Contributing
Expand Down
11 changes: 11 additions & 0 deletions src/backend/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ListResponse {
blobs?: ListResponseBlob[]
next_cursor?: string
}

export interface ListResponseBlob {
etag: string
last_modified: string
size: number
key: string
}
79 changes: 56 additions & 23 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { BlobInput, Fetcher, HTTPMethod } from './types.ts'
interface MakeStoreRequestOptions {
body?: BlobInput | null
headers?: Record<string, string>
key: string
key?: string
metadata?: Metadata
method: HTTPMethod
parameters?: Record<string, string>
storeName: string
}

Expand All @@ -20,6 +21,14 @@ export interface ClientOptions {
token: string
}

interface GetFinalRequestOptions {
key: string | undefined
metadata?: Metadata
method: string
parameters?: Record<string, string>
storeName: string
}

export class Client {
private apiURL?: string
private edgeURL?: string
Expand All @@ -41,7 +50,7 @@ export class Client {
}
}

private async getFinalRequest(storeName: string, key: string, method: string, metadata?: Metadata) {
private async getFinalRequest({ key, metadata, method, parameters = {}, storeName }: GetFinalRequestOptions) {
const encodedMetadata = encodeMetadata(metadata)

if (this.edgeURL) {
Expand All @@ -53,38 +62,72 @@ export class Client {
headers[METADATA_HEADER_EXTERNAL] = encodedMetadata
}

const path = key ? `/${this.siteID}/${storeName}/${key}` : `/${this.siteID}/${storeName}`
const url = new URL(path, this.edgeURL)

for (const key in parameters) {
url.searchParams.set(key, parameters[key])
}

return {
headers,
url: `${this.edgeURL}/${this.siteID}/${storeName}/${key}`,
url: url.toString(),
}
}

const apiURL = `${this.apiURL ?? 'https://api.netlify.com'}/api/v1/sites/${
this.siteID
}/blobs/${key}?context=${storeName}`
const apiHeaders: Record<string, string> = { authorization: `Bearer ${this.token}` }
const url = new URL(`/api/v1/sites/${this.siteID}/blobs`, this.apiURL ?? 'https://api.netlify.com')

for (const key in parameters) {
url.searchParams.set(key, parameters[key])
}

url.searchParams.set('context', storeName)

if (key === undefined) {
return {
headers: apiHeaders,
url: url.toString(),
}
}

url.pathname += `/${key}`

if (encodedMetadata) {
apiHeaders[METADATA_HEADER_EXTERNAL] = encodedMetadata
}

const res = await this.fetch(apiURL, { headers: apiHeaders, method })
const res = await this.fetch(url.toString(), { headers: apiHeaders, method })

if (res.status !== 200) {
throw new Error(`${method} operation has failed: API returned a ${res.status} response`)
throw new Error(`Netlify Blobs has generated an internal error: ${res.status} response`)
}

const { url } = await res.json()
const { url: signedURL } = await res.json()
const userHeaders = encodedMetadata ? { [METADATA_HEADER_INTERNAL]: encodedMetadata } : undefined

return {
headers: userHeaders,
url,
url: signedURL,
}
}

async makeRequest({ body, headers: extraHeaders, key, metadata, method, storeName }: MakeStoreRequestOptions) {
const { headers: baseHeaders = {}, url } = await this.getFinalRequest(storeName, key, method, metadata)
async makeRequest({
body,
headers: extraHeaders,
key,
metadata,
method,
parameters,
storeName,
}: MakeStoreRequestOptions) {
const { headers: baseHeaders = {}, url } = await this.getFinalRequest({
key,
metadata,
method,
parameters,
storeName,
})
const headers: Record<string, string> = {
...baseHeaders,
...extraHeaders,
Expand All @@ -106,17 +149,7 @@ export class Client {
options.duplex = 'half'
}

const res = await fetchAndRetry(this.fetch, url, options)

if (res.status === 404 && method === HTTPMethod.GET) {
return null
}

if (res.status !== 200 && res.status !== 304) {
throw new Error(`${method} operation has failed: store returned a ${res.status} response`)
}

return res
return fetchAndRetry(this.fetch, url, options)
}
}

Expand Down
Loading
0