Skip to content

Commit 03ea7cd

Browse files
committed
feat!: add listStores method
BREAKING CHANGE: Site-scoped created with previous versions of the client will not be accessible.
1 parent 5ad27eb commit 03ea7cd

File tree

7 files changed

+397
-18
lines changed

7 files changed

+397
-18
lines changed

.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module.exports = {
2323
'unicorn/prefer-ternary': 'off',
2424
'no-unused-vars': 'off',
2525
'@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }],
26+
'func-style': 'off',
2627
},
2728
overrides: [
2829
...overrides,

src/backend/list_stores.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface ListStoresResponse {
2+
stores: string[]
3+
next_cursor?: string
4+
}

src/client.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface MakeStoreRequestOptions {
1212
metadata?: Metadata
1313
method: HTTPMethod
1414
parameters?: Record<string, string>
15-
storeName: string
15+
storeName?: string
1616
}
1717

1818
export interface ClientOptions {
@@ -31,7 +31,7 @@ interface GetFinalRequestOptions {
3131
metadata?: Metadata
3232
method: string
3333
parameters?: Record<string, string>
34-
storeName: string
34+
storeName?: string
3535
}
3636

3737
export class Client {
@@ -70,6 +70,16 @@ export class Client {
7070
const encodedMetadata = encodeMetadata(metadata)
7171
const consistency = opConsistency ?? this.consistency
7272

73+
let urlPath = `/${this.siteID}`
74+
75+
if (storeName) {
76+
urlPath += `/${storeName}`
77+
}
78+
79+
if (key) {
80+
urlPath += `/${key}`
81+
}
82+
7383
if (this.edgeURL) {
7484
if (consistency === 'strong' && !this.uncachedEdgeURL) {
7585
throw new BlobsConsistencyError()
@@ -83,8 +93,7 @@ export class Client {
8393
headers[METADATA_HEADER_INTERNAL] = encodedMetadata
8494
}
8595

86-
const path = key ? `/${this.siteID}/${storeName}/${key}` : `/${this.siteID}/${storeName}`
87-
const url = new URL(path, consistency === 'strong' ? this.uncachedEdgeURL : this.edgeURL)
96+
const url = new URL(urlPath, consistency === 'strong' ? this.uncachedEdgeURL : this.edgeURL)
8897

8998
for (const key in parameters) {
9099
url.searchParams.set(key, parameters[key])
@@ -97,23 +106,22 @@ export class Client {
97106
}
98107

99108
const apiHeaders: Record<string, string> = { authorization: `Bearer ${this.token}` }
100-
const url = new URL(`/api/v1/blobs/${this.siteID}/${storeName}`, this.apiURL ?? 'https://api.netlify.com')
109+
const url = new URL(`/api/v1/blobs${urlPath}`, this.apiURL ?? 'https://api.netlify.com')
101110

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

106-
// If there is no key, we're dealing with the list endpoint, which is
107-
// implemented directly in the Netlify API.
108-
if (key === undefined) {
115+
// If there is no store name, we're listing stores. If there's no key,
116+
// we're listing blobs. Both operations are implemented directly in the
117+
// Netlify API.
118+
if (storeName === undefined || key === undefined) {
109119
return {
110120
headers: apiHeaders,
111121
url: url.toString(),
112122
}
113123
}
114124

115-
url.pathname += `/${key}`
116-
117125
if (encodedMetadata) {
118126
apiHeaders[METADATA_HEADER_EXTERNAL] = encodedMetadata
119127
}

src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { getDeployStore, getStore } from './store_factory.ts'
2+
export { listStores } from './store_list.ts'
23
export { BlobsServer } from './server.ts'
34
export type {
45
Store,

src/store.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { getMetadataFromResponse, Metadata } from './metadata.ts'
77
import { BlobInput, HTTPMethod } from './types.ts'
88
import { BlobsInternalError, collectIterator } from './util.ts'
99

10+
export const DEPLOY_STORE_PREFIX = 'deploy:'
11+
export const SITE_STORE_PREFIX = 'site:'
12+
1013
interface BaseStoreOptions {
1114
client: Client
1215
consistency?: ConsistencyMode
@@ -64,21 +67,19 @@ export type BlobResponseType = 'arrayBuffer' | 'blob' | 'json' | 'stream' | 'tex
6467

6568
export class Store {
6669
private client: Client
67-
private consistency: ConsistencyMode
6870
private name: string
6971

7072
constructor(options: StoreOptions) {
7173
this.client = options.client
72-
this.consistency = options.consistency ?? 'eventual'
7374

7475
if ('deployID' in options) {
7576
Store.validateDeployID(options.deployID)
7677

77-
this.name = `deploy:${options.deployID}`
78+
this.name = DEPLOY_STORE_PREFIX + options.deployID
7879
} else {
7980
Store.validateStoreName(options.name)
8081

81-
this.name = options.name
82+
this.name = SITE_STORE_PREFIX + options.name
8283
}
8384
}
8485

@@ -349,10 +350,6 @@ export class Store {
349350
}
350351

351352
private static validateStoreName(name: string) {
352-
if (name.startsWith('deploy:') || name.startsWith('deploy%3A1')) {
353-
throw new Error('Store name must not start with the `deploy:` reserved keyword.')
354-
}
355-
356353
if (name.includes('/') || name.includes('%2F')) {
357354
throw new Error('Store name must not contain forward slashes (/).')
358355
}

0 commit comments

Comments
 (0)