Skip to content
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

Add custom fetcher config option #90

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
30 changes: 20 additions & 10 deletions src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -65,6 +66,7 @@ export interface ModelConfiguration {
keyCase: KeyCase
strictAttributes: boolean
logger: ILogger
fetcher: Fetcher
}
export type ModelConfigurationOptions = Partial<ModelConfiguration>

Expand Down Expand Up @@ -147,7 +149,7 @@ export const applyModelConfig = <T extends typeof SpraypaintBase>(

for (k in config) {
if (config.hasOwnProperty(k)) {
ModelClass[k] = config[k]
;(ModelClass[k] as any) = config[k]
}
}

Expand Down Expand Up @@ -179,6 +181,8 @@ export class SpraypaintBase {
static currentClass: typeof SpraypaintBase = SpraypaintBase
static beforeFetch: BeforeFilter | undefined
static afterFetch: AfterFilter | undefined
static fetcher: Fetcher = (input: string, init?: RequestInit) =>
fetch(input, init)

private static _typeRegistry: JsonapiTypeRegistry
private static _IDMap: IDMap
Expand Down Expand Up @@ -687,10 +691,7 @@ export class SpraypaintBase {
)
}

return {
id: this.id,
type: this.klass.jsonapiType
}
return { id: this.id, type: this.klass.jsonapiType }
}

get errors(): ValidationErrors<this> {
Expand Down Expand Up @@ -950,7 +951,11 @@ export class SpraypaintBase {
async destroy(): Promise<boolean> {
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.fetcher,
this.klass.logger
)
let response: any

try {
Expand Down Expand Up @@ -980,9 +985,14 @@ export class SpraypaintBase {
): Promise<boolean> {
let url = this.klass.url()
let verb: RequestVerbs = "post"
const request = new Request(this._middleware(), this.klass.logger, {
patchAsPost: this.klass.patchAsPost
})
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

Expand Down
9 changes: 7 additions & 2 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,26 @@ export interface JsonapiResponse extends Response {
jsonPayload: JsonapiResponseDoc
}

export interface RequestConfig {
export type Fetcher = (url: string, options: RequestInit) => Promise<any>

interface RequestConfig {
patchAsPost: boolean
}

export class Request {
middleware: MiddlewareStack
fetcher: Fetcher
config: RequestConfig
private logger: ILogger

constructor(
middleware: MiddlewareStack,
fetcher: Fetcher,
logger: ILogger,
config?: RequestConfig
) {
this.middleware = middleware
this.fetcher = fetcher
this.logger = logger
this.config = Object.assign({ patchAsPost: false }, config)
}
Expand Down Expand Up @@ -104,7 +109,7 @@ export class Request {
let response

try {
response = await fetch(url, options)
response = await this.fetcher(url, options)
} catch (e) {
throw new ResponseError(null, e.message, e)
}
Expand Down
6 changes: 5 additions & 1 deletion src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,11 @@ export class Scope<T extends SpraypaintBase = SpraypaintBase> {
if (qp) {
url = `${url}?${qp}`
}
const request = new Request(this.model.middlewareStack, this.model.logger)
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
Expand Down
70 changes: 70 additions & 0 deletions test/integration/custom-fetcher.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { expect, sinon } from "../test-helper"
import fetchMock = require("fetch-mock")
import { Model, SpraypaintBase } from "../../src"

describe("Custom fetcher", () => {
let spy1: sinon.SinonSpy
let spy2: sinon.SinonSpy

let ApplicationRecord: typeof SpraypaintBase
let Author: typeof SpraypaintBase
let Person: typeof SpraypaintBase

beforeEach(() => {
const fetcher = (a: any, b: any) =>
Promise.resolve(
new Response(
JSON.stringify({
data: {
id: "1",
type: "people",
attributes: {
firstName: "John"
}
}
})
)
)

spy1 = sinon.spy(fetcher)
spy2 = sinon.spy(fetcher)

@Model({
baseUrl: "http://example.com",
apiNamespace: "/api/v1"
})
class Base extends SpraypaintBase {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind adding a spec which ensures that setting a single fetcher in the base class will allow both subclasses to use it?

ApplicationRecord = Base

@Model({
jsonapiType: "authors",
fetcher: spy1
})
class A extends ApplicationRecord {}
Author = A

@Model({ jsonapiType: "people" })
class B extends ApplicationRecord {
static fetcher = spy2
}
Person = B
})

describe("uses custom fetchers", () => {
it("calls a custom fetcher set via config", async () => {
await Author.find(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"
)
})
})
})