From dd25e4f8080aa9879fc5a4e86d95104a230f3018 Mon Sep 17 00:00:00 2001 From: Jake Correa Date: Fri, 5 Feb 2021 16:32:27 -0800 Subject: [PATCH 1/8] feat: add option for custom fetcher --- src/model.ts | 4 +++- src/request.ts | 12 ++++++++---- src/scope.ts | 4 +++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/model.ts b/src/model.ts index a5f424f..70d4aba 100644 --- a/src/model.ts +++ b/src/model.ts @@ -7,7 +7,8 @@ import { Request, RequestVerbs, JsonapiResponse, - ResponseError + ResponseError, + Fetcher } from "./request" import { WritePayload } from "./util/write-payload" import { flipEnumerable, getNonEnumerables } from "./util/enumerables" @@ -171,6 +172,7 @@ export class SpraypaintBase { static credentials: "same-origin" | "omit" | "include" | undefined static clientApplication: string | null = null static patchAsPost: boolean = false + static fetcher: Fetcher = fetch static attributeList: Record = {} static linkList: Array = [] diff --git a/src/request.ts b/src/request.ts index 907e34e..f9ad5e3 100644 --- a/src/request.ts +++ b/src/request.ts @@ -9,9 +9,13 @@ export interface JsonapiResponse extends Response { jsonPayload: JsonapiResponseDoc } -export interface RequestConfig { +export type Fetcher = typeof fetch + +interface RequestConfig { patchAsPost: boolean + fetcher: Fetcher } +type RequestConfigOptions = Partial export class Request { middleware: MiddlewareStack @@ -21,11 +25,11 @@ export class Request { constructor( middleware: MiddlewareStack, logger: ILogger, - config?: RequestConfig + config?: RequestConfigOptions ) { this.middleware = middleware this.logger = logger - this.config = Object.assign({ patchAsPost: false }, config) + this.config = Object.assign({ patchAsPost: false, fetcher: fetch }, config) } get(url: string, options: RequestInit): Promise { @@ -104,7 +108,7 @@ export class Request { let response try { - response = await fetch(url, options) + response = await this.config.fetcher(url, options) } catch (e) { throw new ResponseError(null, e.message, e) } diff --git a/src/scope.ts b/src/scope.ts index 7bb2893..9a5a33b 100644 --- a/src/scope.ts +++ b/src/scope.ts @@ -336,7 +336,9 @@ export class Scope { if (qp) { url = `${url}?${qp}` } - const request = new Request(this.model.middlewareStack, this.model.logger) + const request = new Request(this.model.middlewareStack, this.model.logger, { + fetcher: this.model.fetcher + }) const response = await request.get(url, this.fetchOptions()) refreshJWT(this.model, response) return response.jsonPayload From 5dbf19886295fdc28c82c35f50c9bf2d56073021 Mon Sep 17 00:00:00 2001 From: Jake Correa Date: Fri, 5 Feb 2021 16:37:03 -0800 Subject: [PATCH 2/8] fix(ts): suppress property assignment error --- src/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model.ts b/src/model.ts index 70d4aba..3295f8e 100644 --- a/src/model.ts +++ b/src/model.ts @@ -148,7 +148,7 @@ export const applyModelConfig = ( for (k in config) { if (config.hasOwnProperty(k)) { - ModelClass[k] = config[k] + ;(ModelClass[k] as any) = config[k] } } From fd8c601123a0c7a4bec537728f27775b3b54d4b6 Mon Sep 17 00:00:00 2001 From: Jake Correa Date: Fri, 5 Feb 2021 16:58:06 -0800 Subject: [PATCH 3/8] add custom fetcher to all requests --- src/model.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/model.ts b/src/model.ts index 3295f8e..7e2c5b0 100644 --- a/src/model.ts +++ b/src/model.ts @@ -952,7 +952,9 @@ export class SpraypaintBase { async destroy(): Promise { const url = this.klass.url(this.id) const verb = "delete" - const request = new Request(this._middleware(), this.klass.logger) + const request = new Request(this._middleware(), this.klass.logger, { + fetcher: this.klass.fetcher + }) let response: any try { @@ -983,7 +985,8 @@ export class SpraypaintBase { let url = this.klass.url() let verb: RequestVerbs = "post" const request = new Request(this._middleware(), this.klass.logger, { - patchAsPost: this.klass.patchAsPost + patchAsPost: this.klass.patchAsPost, + fetcher: this.klass.fetcher }) const payload = new WritePayload(this, options.with) let response: any From 5d3439dc0c5a73f6f8e15ed1b1b3d5116d8c5dcf Mon Sep 17 00:00:00 2001 From: Jake Correa Date: Fri, 5 Feb 2021 18:18:45 -0800 Subject: [PATCH 4/8] fix api for custom fetcher --- src/model.ts | 28 ++++++++++++++++------------ src/request.ts | 11 ++++++----- src/scope.ts | 8 +++++--- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/model.ts b/src/model.ts index 7e2c5b0..091f59d 100644 --- a/src/model.ts +++ b/src/model.ts @@ -172,7 +172,6 @@ export class SpraypaintBase { static credentials: "same-origin" | "omit" | "include" | undefined static clientApplication: string | null = null static patchAsPost: boolean = false - static fetcher: Fetcher = fetch static attributeList: Record = {} static linkList: Array = [] @@ -181,6 +180,8 @@ export class SpraypaintBase { static currentClass: typeof SpraypaintBase = SpraypaintBase static beforeFetch: BeforeFilter | undefined static afterFetch: AfterFilter | undefined + static fetcher: Fetcher = (input: RequestInfo, init?: RequestInit) => + fetch(input, init) private static _typeRegistry: JsonapiTypeRegistry private static _IDMap: IDMap @@ -689,10 +690,7 @@ export class SpraypaintBase { ) } - return { - id: this.id, - type: this.klass.jsonapiType - } + return { id: this.id, type: this.klass.jsonapiType } } get errors(): ValidationErrors { @@ -952,9 +950,11 @@ export class SpraypaintBase { async destroy(): Promise { const url = this.klass.url(this.id) const verb = "delete" - const request = new Request(this._middleware(), this.klass.logger, { - fetcher: this.klass.fetcher - }) + const request = new Request( + this._middleware(), + this.klass.fetcher, + this.klass.logger + ) let response: any try { @@ -984,10 +984,14 @@ export class SpraypaintBase { ): Promise { let url = this.klass.url() let verb: RequestVerbs = "post" - const request = new Request(this._middleware(), this.klass.logger, { - patchAsPost: this.klass.patchAsPost, - fetcher: this.klass.fetcher - }) + const request = new Request( + this._middleware(), + this.klass.fetcher, + this.klass.logger, + { + patchAsPost: this.klass.patchAsPost + } + ) const payload = new WritePayload(this, options.with) let response: any diff --git a/src/request.ts b/src/request.ts index f9ad5e3..ba1f0c7 100644 --- a/src/request.ts +++ b/src/request.ts @@ -13,23 +13,24 @@ export type Fetcher = typeof fetch interface RequestConfig { patchAsPost: boolean - fetcher: Fetcher } -type RequestConfigOptions = Partial export class Request { middleware: MiddlewareStack + fetcher: Fetcher config: RequestConfig private logger: ILogger constructor( middleware: MiddlewareStack, + fetcher: Fetcher, logger: ILogger, - config?: RequestConfigOptions + config?: RequestConfig ) { this.middleware = middleware + this.fetcher = fetcher this.logger = logger - this.config = Object.assign({ patchAsPost: false, fetcher: fetch }, config) + this.config = Object.assign({ patchAsPost: false }, config) } get(url: string, options: RequestInit): Promise { @@ -108,7 +109,7 @@ export class Request { let response try { - response = await this.config.fetcher(url, options) + response = await this.fetcher(url, options) } catch (e) { throw new ResponseError(null, e.message, e) } diff --git a/src/scope.ts b/src/scope.ts index 9a5a33b..941b92a 100644 --- a/src/scope.ts +++ b/src/scope.ts @@ -336,9 +336,11 @@ export class Scope { if (qp) { url = `${url}?${qp}` } - const request = new Request(this.model.middlewareStack, this.model.logger, { - fetcher: this.model.fetcher - }) + const request = new Request( + this.model.middlewareStack, + this.model.fetcher, + this.model.logger + ) const response = await request.get(url, this.fetchOptions()) refreshJWT(this.model, response) return response.jsonPayload From f7755798a84e29c50ad8f55bb3ebb3555228d764 Mon Sep 17 00:00:00 2001 From: Jake Correa Date: Fri, 5 Feb 2021 18:18:55 -0800 Subject: [PATCH 5/8] add custom fetcher tests --- test/integration/custom-fetcher.test.ts | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 test/integration/custom-fetcher.test.ts diff --git a/test/integration/custom-fetcher.test.ts b/test/integration/custom-fetcher.test.ts new file mode 100644 index 0000000..73507a2 --- /dev/null +++ b/test/integration/custom-fetcher.test.ts @@ -0,0 +1,40 @@ +import { expect, sinon } from "../test-helper" +import { Person } from "../fixtures" +import fetchMock = require("fetch-mock") + +describe("Custom fetcher", () => { + const oldFetch = Person.fetcher + let spy: sinon.SinonSpy + + beforeEach(() => { + spy = sinon.spy(() => + Promise.resolve( + new Response( + JSON.stringify({ + data: { + id: "1", + type: "people", + attributes: { + firstName: "John" + } + } + }) + ) + ) + ) + + Person.fetcher = spy + }) + + afterEach(() => { + Person.fetcher = oldFetch + }) + + it("calls the custom fetcher", async () => { + await Person.find(1) + + expect(spy).to.have.been.calledOnce.and.to.have.been.calledWith( + "http://example.com/api/v1/people/1" + ) + }) +}) From 9db346ce2db6b7c3a4f44de1bb5a12fea722a3b9 Mon Sep 17 00:00:00 2001 From: Jake Correa Date: Fri, 5 Feb 2021 19:01:20 -0800 Subject: [PATCH 6/8] add fetcher to model config --- src/model.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/model.ts b/src/model.ts index 091f59d..2174ada 100644 --- a/src/model.ts +++ b/src/model.ts @@ -66,6 +66,7 @@ export interface ModelConfiguration { keyCase: KeyCase strictAttributes: boolean logger: ILogger + fetcher: Fetcher } export type ModelConfigurationOptions = Partial From 9393050a9de212f7b15804dc1e8a46b99eabbb3d Mon Sep 17 00:00:00 2001 From: Jake Correa Date: Fri, 5 Feb 2021 19:02:37 -0800 Subject: [PATCH 7/8] add more custom fetcher tests --- test/integration/custom-fetcher.test.ts | 58 +++++++++++++++++++------ 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/test/integration/custom-fetcher.test.ts b/test/integration/custom-fetcher.test.ts index 73507a2..443a55b 100644 --- a/test/integration/custom-fetcher.test.ts +++ b/test/integration/custom-fetcher.test.ts @@ -1,13 +1,17 @@ import { expect, sinon } from "../test-helper" -import { Person } from "../fixtures" import fetchMock = require("fetch-mock") +import { Model, SpraypaintBase } from "../../src" describe("Custom fetcher", () => { - const oldFetch = Person.fetcher - let spy: sinon.SinonSpy + let spy1: sinon.SinonSpy + let spy2: sinon.SinonSpy + + let ApplicationRecord: typeof SpraypaintBase + let Author: typeof SpraypaintBase + let Person: typeof SpraypaintBase beforeEach(() => { - spy = sinon.spy(() => + const fetcher = (a: any, b: any) => Promise.resolve( new Response( JSON.stringify({ @@ -21,20 +25,46 @@ describe("Custom fetcher", () => { }) ) ) - ) - Person.fetcher = spy - }) + spy1 = sinon.spy(fetcher) + spy2 = sinon.spy(fetcher) + + @Model({ + baseUrl: "http://example.com", + apiNamespace: "/api/v1" + }) + class Base extends SpraypaintBase {} + ApplicationRecord = Base + + @Model({ + jsonapiType: "authors", + fetcher: spy1 + }) + class A extends ApplicationRecord {} + Author = A - afterEach(() => { - Person.fetcher = oldFetch + @Model({ jsonapiType: "people" }) + class B extends ApplicationRecord { + static fetcher = spy2 + } + Person = B }) - it("calls the custom fetcher", async () => { - await Person.find(1) + describe("uses custom fetchers", () => { + it("calls a custom fetcher set via config", async () => { + await Author.find(1) - expect(spy).to.have.been.calledOnce.and.to.have.been.calledWith( - "http://example.com/api/v1/people/1" - ) + expect(spy1).to.have.been.calledOnce.and.to.have.been.calledWith( + "http://example.com/api/v1/authors/1" + ) + }) + + it("calls a custom fetcher set via static props", async () => { + await Person.find(1) + + expect(spy2).to.have.been.calledOnce.and.to.have.been.calledWith( + "http://example.com/api/v1/people/1" + ) + }) }) }) From 8566f924049f959c5346cdb4b8385058239b3cbf Mon Sep 17 00:00:00 2001 From: Jake Correa Date: Fri, 5 Feb 2021 19:17:26 -0800 Subject: [PATCH 8/8] fix custom fetch types --- src/model.ts | 2 +- src/request.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model.ts b/src/model.ts index 2174ada..ba84755 100644 --- a/src/model.ts +++ b/src/model.ts @@ -181,7 +181,7 @@ export class SpraypaintBase { static currentClass: typeof SpraypaintBase = SpraypaintBase static beforeFetch: BeforeFilter | undefined static afterFetch: AfterFilter | undefined - static fetcher: Fetcher = (input: RequestInfo, init?: RequestInit) => + static fetcher: Fetcher = (input: string, init?: RequestInit) => fetch(input, init) private static _typeRegistry: JsonapiTypeRegistry diff --git a/src/request.ts b/src/request.ts index ba1f0c7..72062f6 100644 --- a/src/request.ts +++ b/src/request.ts @@ -9,7 +9,7 @@ export interface JsonapiResponse extends Response { jsonPayload: JsonapiResponseDoc } -export type Fetcher = typeof fetch +export type Fetcher = (url: string, options: RequestInit) => Promise interface RequestConfig { patchAsPost: boolean