|
@@ -0,0 +1,70 @@ |
|
|
|
|
|
import { Deserializer, Serializer } from '../../core/src/storage' |
|
|
|
|
|
|
|
|
|
|
|
const CREATED = 201 |
|
|
|
|
|
const NO_CONTENT = 204 |
|
|
|
|
|
const NOT_FOUND = 404 |
|
|
|
|
|
const GONE = 410 |
|
|
|
|
|
|
|
|
|
|
|
export default class RemoteStorage<U, T> { |
|
|
|
|
|
constructor( |
|
|
|
|
|
private readonly baseUrl: string, |
|
|
|
|
|
private readonly getItemId = item => item['id'], |
|
|
|
|
|
private readonly serializers: Map<string, Serializer> = new Map([ |
|
|
|
|
|
['*/*', JSON.stringify], |
|
|
|
|
|
['application/json', JSON.stringify], |
|
|
|
|
|
['text/json', JSON.stringify], |
|
|
|
|
|
]), |
|
|
|
|
|
private readonly deserializers: Map<string, Deserializer> = new Map([ |
|
|
|
|
|
['*/*', JSON.parse], |
|
|
|
|
|
['application/json', JSON.parse], |
|
|
|
|
|
['text/json', JSON.parse], |
|
|
|
|
|
]), |
|
|
|
|
|
) {} |
|
|
|
|
|
|
|
|
|
|
|
async getCollection(collectionId: string) { |
|
|
|
|
|
const response = await window.fetch([this.baseUrl, collectionId].join('/')) |
|
|
|
|
|
const contentType = response.headers.get('content-type') |
|
|
|
|
|
const { [contentType]: deserializer = this.deserializers.get('*/*'), } = Object.fromEntries(this.deserializers.entries()) |
|
|
|
|
|
const payload = await response.text() |
|
|
|
|
|
return deserializer(payload) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async setItem(collectionId: string, item: U, contentType = 'application/json') { |
|
|
|
|
|
const { [contentType]: serializer = this.serializers.get('application/json'), } = Object.fromEntries(this.serializers.entries()) |
|
|
|
|
|
const response = await window.fetch([this.baseUrl, collectionId, this.getItemId(item)].join('/'), { |
|
|
|
|
|
method: 'put', |
|
|
|
|
|
body: serializer(item), |
|
|
|
|
|
}) |
|
|
|
|
|
// resource is created |
|
|
|
|
|
if (response.status === CREATED) { |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
if (100 <= response.status && response.status <= 399) { |
|
|
|
|
|
console.warn(`Expected response is ${CREATED}, got ${response.status}.`) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
throw new Error(response.statusText) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async removeItem(collectionId: string, item: U) { |
|
|
|
|
|
const response = await window.fetch([this.baseUrl, collectionId, this.getItemId(item)].join('/'), { |
|
|
|
|
|
method: 'delete', |
|
|
|
|
|
}) |
|
|
|
|
|
// resource is deleted |
|
|
|
|
|
if (response.status === NO_CONTENT) { |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
// resource is already deleted |
|
|
|
|
|
if (response.status === NOT_FOUND || response.status === GONE) { |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
if (100 <= response.status && response.status <= 399) { |
|
|
|
|
|
console.warn(`Expected response is ${NO_CONTENT}, got ${response.status}.`) |
|
|
|
|
|
// assume there's a change in the collection |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
throw new Error(response.statusText) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// TODO do removeCollection for account closing? |
|
|
|
|
|
} |