UNPKG

ketting

Version:

Opinionated HATEOAS / Rest client.

121 lines 3.89 kB
import qs from 'query-string'; export class SimpleAction { /** * What url to post the form to. */ uri; /** * Action name. * * Some formats call this the 'rel' */ name; /** * Form title. * * Should be human-friendly. */ title; /** * The HTTP method to use */ method; /** * The contentType to use for the form submission */ contentType; /** * Returns the list of fields associated to an action */ fields; /** * Reference to client */ client; constructor(client, formInfo) { this.client = client; for (const [k, v] of Object.entries(formInfo)) { this[k] = v; } } /** * Execute the action or submit the form. */ async submit(formData) { const uri = new URL(this.uri); const newFormData = this.validateForm(formData); if (this.method === 'GET') { uri.search = qs.stringify(newFormData); const resource = this.client.go(uri.toString()); return resource.get(); } const response = await this.fetchOrThrowWithBody(uri, newFormData); const state = this.client.getStateForResponse(uri.toString(), response); return state; } async submitFollow(formData) { const uri = new URL(this.uri); const newFormData = this.validateForm(formData); if (this.method === 'GET') { uri.search = qs.stringify(newFormData); return this.client.go(uri.toString()); } const response = await this.fetchOrThrowWithBody(uri, newFormData); switch (response.status) { case 201: if (response.headers.has('location')) { return this.client.go(response.headers.get('location')); } throw new Error('Could not follow after a 201 request, because the server did not reply with a Location header. If you sent a Location header, check if your service is returning "Access-Control-Expose-Headers: Location".'); case 204: case 205: return this.client.go(uri.toString()); default: throw new Error('Did not receive a 201, 204 or 205 status code so we could not follow to the next resource'); } } validateForm(formData) { const newFormData = { ...formData }; for (const field of this.fields) { if (!(field.name in formData)) { if (field.value) { // We don't have perfect types for fields vs. FormData and how they // related, so 'any' is needed here. newFormData[field.name] = field.value; } else if (field.required) { throw new Error(`The ${field.name} field is required in this form`); } } } return newFormData; } fetchOrThrowWithBody(uri, formData) { let body; switch (this.contentType) { case 'application/x-www-form-urlencoded': body = qs.stringify(formData); break; case 'application/json': body = JSON.stringify(formData); break; default: throw new Error(`Serializing mimetype ${this.contentType} is not yet supported in actions`); } return this.client.fetcher.fetchOrThrow(uri.toString(), { method: this.method, body, headers: { 'Content-Type': this.contentType } }); } field(name) { return this.fields.find(field => field.name === name); } } export class ActionNotFound extends Error { } //# sourceMappingURL=action.js.map