Last active
May 1, 2021 08:30
-
-
Save hypeJunction/58bdfb536072cd8a927ff122ba67300d to your computer and use it in GitHub Desktop.
Solid Design Systems - Data Fetching
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { RequestOptions, Transport } from "./HttpTransportProvider"; | |
import { | |
createMutationRequest, | |
createQueryRequest, | |
} from "./HttpTransportService"; | |
export class ApiClient { | |
readonly baseUrl: string; | |
readonly transport: Transport; | |
constructor(baseUrl: string, transport: Transport) { | |
this.baseUrl = baseUrl; | |
this.transport = transport; | |
} | |
send<T>(request: Request): Promise<T> { | |
return this.transport<T>(request); | |
} | |
get<T>( | |
url: URL, | |
options: Partial<Pick<RequestOptions, "headers" | "bearerToken">> = {}, | |
method: "GET" | "HEAD" | "OPTIONS" = "GET" | |
): Promise<T> { | |
return this.send(createQueryRequest("GET", url, options)); | |
} | |
post<T>( | |
url: URL, | |
options: Partial<RequestOptions> = {}, | |
method: "POST" | "PUT" | "PATCH" | "DELETE" = "POST" | |
): Promise<T> { | |
return this.send(createMutationRequest(method, url, options)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ServiceProvider } from "./ServiceProvider"; | |
import { Services, services } from "./Services"; | |
import React from "react"; | |
import { CocktailsController } from "./CocktailsController"; | |
export function CocktailApp() { | |
return ( | |
<ServiceProvider<Services> services={services}> | |
<CocktailsController /> | |
</ServiceProvider> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { createContext, ReactNode, useContext } from "react"; | |
import { AsyncSelector, AsyncState } from "./AsyncState"; | |
const AsyncContext = createContext<Partial<AsyncSelector<any>>>({ | |
state: { | |
loading: false, | |
refreshing: false, | |
result: null, | |
error: null, | |
}, | |
}); | |
export type ChildNode<S extends AsyncState<any>> = | |
| ReactNode | |
| ((props: S) => ReactNode); | |
export interface AsyncProps<S extends AsyncSelector<any>> { | |
selector: S; | |
children: ChildNode<S["state"]>; | |
} | |
export function renderChildren<S extends AsyncState<any>>( | |
children: ChildNode<S>, | |
state: S | |
) { | |
if (typeof children === "function") { | |
return children(state); | |
} | |
return children; | |
} | |
export function Async<S>({ selector, children }: AsyncProps<AsyncSelector<S>>) { | |
return ( | |
<AsyncContext.Provider value={selector}> | |
{renderChildren(children, selector.state)} | |
</AsyncContext.Provider> | |
); | |
} | |
export function AsyncLoading({ children }: { children: ChildNode<any> }) { | |
const { state } = useContext(AsyncContext); | |
return state?.loading && renderChildren(children, state); | |
} | |
export function AsyncRefreshing({ children }: { children: ChildNode<any> }) { | |
const { state } = useContext(AsyncContext); | |
return state?.refreshing && renderChildren(children, state); | |
} | |
export function AsyncError({ children }: { children: ChildNode<any> }) { | |
const { state } = useContext(AsyncContext); | |
return state?.error && renderChildren(children, state); | |
} | |
export function AsyncResult({ children }: { children: ChildNode<any> }) { | |
const { state } = useContext(AsyncContext); | |
return state?.result && renderChildren(children, state); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useEffect, useReducer } from "react"; | |
export type Selector<S> = (...args: Array<any>) => Promise<S>; | |
export interface AsyncState<S> { | |
result: S | null; | |
error: Error | null; | |
loading: boolean; | |
refreshing: boolean; | |
} | |
export interface AsyncSelector<S> { | |
state: AsyncState<S>; | |
execute: () => Promise<S | void>; | |
} | |
export function transform<T, O>( | |
res: Promise<T>, | |
transformer: (res: T) => O | |
): Promise<O> { | |
return res.then(transformer); | |
} | |
export function useAsyncCallback<S>( | |
fn: Selector<S>, | |
deps: Array<any> = [] | |
): AsyncSelector<S> { | |
const [state, dispatch] = useReducer(asyncStateReducer, { | |
loading: false, | |
refreshing: false, | |
result: null, | |
error: null, | |
}); | |
const execute = useCallback(() => { | |
dispatch({ | |
type: "SET_LOADING", | |
payload: true, | |
}); | |
Promise.resolve(fn(...deps)) | |
.then((result) => { | |
dispatch({ | |
type: "SET_RESULT", | |
payload: result, | |
}); | |
}) | |
.catch((err) => { | |
dispatch({ | |
type: "SET_ERROR", | |
payload: err, | |
}); | |
}); | |
}, [fn, deps]); | |
return { | |
state, | |
execute, | |
} as AsyncSelector<S>; | |
} | |
export function useSelector<S>( | |
fn: Selector<S>, | |
deps: Array<any> = [] | |
): AsyncSelector<S> { | |
const selector = useAsyncCallback(fn, deps); | |
useEffect(() => { | |
selector.execute(); | |
}, deps); | |
return selector; | |
} | |
type AsyncStateAction<S> = | |
| { | |
type: "SET_LOADING"; | |
payload: boolean; | |
} | |
| { | |
type: "SET_RESULT"; | |
payload: S | null; | |
} | |
| { | |
type: "SET_ERROR"; | |
payload: Error | null; | |
}; | |
function asyncStateReducer<S>( | |
state: AsyncState<S>, | |
action: AsyncStateAction<S> | |
): AsyncState<S> { | |
switch (action.type) { | |
case "SET_LOADING": { | |
return { | |
...state, | |
loading: state.result ? false : action.payload, | |
refreshing: state.result ? action.payload : false, | |
}; | |
} | |
case "SET_RESULT": { | |
return { | |
...state, | |
result: action.payload, | |
loading: false, | |
refreshing: false, | |
}; | |
} | |
case "SET_ERROR": { | |
return { | |
...state, | |
error: action.payload, | |
loading: false, | |
refreshing: false, | |
}; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Filters } from "./QueryBuilder"; | |
import React from "react"; | |
export function CocktailsFilter({ | |
filters, | |
onChange, | |
}: { | |
filters: Filters; | |
onChange: (filters: Filters) => void; | |
}) { | |
const categories = ["Cocktail", "Ordinary Drink", "Shot"]; | |
return ( | |
<div> | |
{categories.map((c) => { | |
return ( | |
<button | |
key={c} | |
onClick={() => onChange({ c })} | |
disabled={filters.c === c} | |
> | |
{c} | |
</button> | |
); | |
})} | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import { Cocktail } from "./CocktailsController"; | |
export function CocktailCard({ cocktail }: { cocktail: Cocktail }) { | |
return ( | |
<div> | |
<img src={cocktail.image} alt={cocktail.name} /> | |
<h3>{cocktail.name}</h3> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import { Cocktail } from "./CocktailsController"; | |
import { CocktailCard } from "./CocktailCard"; | |
export function CocktailList({ items }: { items: Array<Cocktail> }) { | |
return ( | |
<div> | |
{items.map((item) => ( | |
<CocktailCard key={item.id} cocktail={item} /> | |
))} | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { act, render } from "@testing-library/react"; | |
import { ServiceProvider } from "./ServiceProvider"; | |
import { SinonSpy, stub } from "sinon"; | |
import { Services, services as defaultServices } from "./Services"; | |
import { ApiClient } from "./ApiClient"; | |
import { CocktailsController } from "./CocktailsController"; | |
import { expect } from "chai"; | |
import { Transport } from "./HttpTransportProvider"; | |
import { CocktailDto } from "./CocktailsDb"; | |
describe("CocktailsController", () => { | |
describe("given a pending request", () => { | |
it("should display a loading state", () => { | |
const promise = new Promise(() => {}); | |
const fetcher: Transport & SinonSpy = stub().returns(promise); | |
const apiClient = new ApiClient("http://example.com", fetcher); | |
const services = { | |
...defaultServices, | |
cocktailsDb: () => apiClient, | |
}; | |
const { getByText } = render( | |
<ServiceProvider<Services> services={services}> | |
<CocktailsController /> | |
</ServiceProvider> | |
); | |
expect(getByText("Loading...")).to.be.visible; | |
}); | |
}); | |
describe("given a failed request", () => { | |
it("should display an error state", async () => { | |
const error = new Error("Failed request"); | |
const fetcher: Transport & SinonSpy = stub().returns( | |
Promise.reject(error) | |
); | |
const apiClient = new ApiClient("http://example.com", fetcher); | |
const services = { | |
...defaultServices, | |
cocktailsDb: () => apiClient, | |
}; | |
const { getByText } = render( | |
<ServiceProvider<Services> services={services}> | |
<CocktailsController /> | |
</ServiceProvider> | |
); | |
try { | |
await act(async () => await fetcher()); | |
} catch (err) { | |
expect(err).to.equal(error); | |
} | |
expect(getByText(error.message)).to.be.visible; | |
}); | |
}); | |
describe("given a successful request", () => { | |
it("should display a list", async () => { | |
const error = new Error("Failed request"); | |
const fetcher: Transport & SinonSpy = stub().returns( | |
Promise.resolve({ | |
drinks: [ | |
{ | |
strDrink: "Test Cocktail", | |
strDrinkThumb: "image.jpg", | |
idDrink: "test-cocktail", | |
}, | |
], | |
} as { drinks: Array<CocktailDto> }) | |
); | |
const apiClient = new ApiClient("http://example.com", fetcher); | |
const services = { | |
...defaultServices, | |
cocktailsDb: () => apiClient, | |
}; | |
const { getByText } = render( | |
<ServiceProvider<Services> services={services}> | |
<CocktailsController /> | |
</ServiceProvider> | |
); | |
await act(async () => await fetcher()); | |
expect(getByText("Test Cocktail")).to.be.visible; | |
}); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { CocktailDto, CocktailsService, fetchCocktails } from "./CocktailsDb"; | |
import { CollectionQuery, useQueryBuilder } from "./QueryBuilder"; | |
import { useServiceProvider } from "./ServiceProvider"; | |
import { transform, useSelector } from "./AsyncState"; | |
import { | |
Async, | |
AsyncError, | |
AsyncLoading, | |
AsyncRefreshing, | |
AsyncResult, | |
} from "./Async"; | |
import { CocktailsFilter } from "./CockitalsFilter"; | |
import { CocktailList } from "./CocktailList"; | |
import React from "react"; | |
export interface Cocktail { | |
name: string; | |
image: string; | |
id: string; | |
} | |
export function cocktailMapper(e: CocktailDto): Cocktail { | |
return { | |
id: e.idDrink, | |
name: e.strDrink, | |
image: e.strDrinkThumb, | |
}; | |
} | |
export function useCocktails(query: CollectionQuery) { | |
const { cocktailsDb } = useServiceProvider<CocktailsService>(); | |
return useSelector( | |
() => | |
transform(fetchCocktails(cocktailsDb, query), (res) => | |
res.drinks.map(cocktailMapper) | |
), | |
[query] | |
); | |
} | |
export function CocktailsController() { | |
const { query, setFilters } = useQueryBuilder({ | |
filters: { | |
c: "Cocktail", | |
}, | |
}); | |
const selector = useCocktails(query); | |
return ( | |
<Async selector={selector}> | |
<AsyncLoading>Loading...</AsyncLoading> | |
<AsyncRefreshing>Refreshing...</AsyncRefreshing> | |
<AsyncError>{(state) => state.error.message}</AsyncError> | |
<AsyncResult> | |
{(state) => ( | |
<> | |
<CocktailsFilter filters={query.filters} onChange={setFilters} /> | |
<CocktailList items={state.result} /> | |
</> | |
)} | |
</AsyncResult> | |
</Async> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { buildUrl } from "./HttpTransportService"; | |
import { CollectionQuery } from "./QueryBuilder"; | |
import { Identity } from "./HttpTransportProvider"; | |
import { ServiceFactoryFn, ServiceFactoryMap } from "./ServiceProvider"; | |
import { ApiClient } from "./ApiClient"; | |
export type CocktailsDb = ApiClient; | |
export interface CocktailsService extends ServiceFactoryMap { | |
cocktailsDb: ServiceFactoryFn<CocktailsDb>; | |
} | |
export interface CocktailDto { | |
strDrink: string; | |
strDrinkThumb: string; | |
idDrink: string; | |
} | |
export const fetchCocktails = ( | |
client: ApiClient, | |
query: CollectionQuery, | |
identity?: Identity | |
): Promise<{ drinks: Array<CocktailDto> }> => { | |
return client.get( | |
buildUrl(client.baseUrl, "/v1/1/filter.php", { ...query.filters }), | |
{ | |
bearerToken: identity?.bearerToken, | |
} | |
); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ServiceFactoryFn, ServiceFactoryMap } from "./ServiceProvider"; | |
export type Transport = <T>(request: Request) => Promise<T>; | |
export type BearerToken = string | undefined; | |
export type Header = Record<string, string>; | |
export interface RequestOptions { | |
bearerToken: BearerToken; | |
headers: Header; | |
body: BodyInit | null | undefined; | |
} | |
export interface Identity { | |
bearerToken: BearerToken; | |
} | |
export interface IdentityService extends ServiceFactoryMap { | |
identity: ServiceFactoryFn<Identity>; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
BearerToken, | |
Header, | |
RequestOptions, | |
Transport, | |
} from "./HttpTransportProvider"; | |
export function createQueryRequest( | |
method: "GET" | "HEAD" | "OPTIONS", | |
url: URL, | |
options: Partial<Pick<RequestOptions, "headers" | "bearerToken">> = {} | |
) { | |
return new Request(url.toString(), { | |
method, | |
headers: buildHttpHeaders(options.bearerToken, options.headers || {}), | |
}); | |
} | |
export function createMutationRequest( | |
method: "POST" | "PUT" | "PATCH" | "DELETE", | |
url: URL, | |
options: Partial<RequestOptions> = {} | |
) { | |
return new Request(url.toString(), { | |
method, | |
cache: "no-cache", | |
headers: buildHttpHeaders(options.bearerToken, options.headers || {}), | |
body: options.body, | |
}); | |
} | |
export const send: Transport = <T>(request: Request): Promise<T> => { | |
return fetch(request).then((res) => parseFetchResponse<T>(res)); | |
}; | |
export const parseFetchResponse = async <T>(response: Response): Promise<T> => { | |
const getData = async () => { | |
try { | |
return await response.json(); | |
} catch (err) { | |
return {}; | |
} | |
}; | |
const data = await getData(); | |
if (response.ok && !data?.errors) { | |
return Promise.resolve(data); | |
} | |
throw new HttpError(response.status || 500, data || {}); | |
}; | |
// Modified from https://stackoverflow.com/a/42604801 | |
export function serializeUrlSearchParams( | |
params: { [key: string]: any }, | |
prefix?: string | |
): string { | |
const query = Object.keys(params).map((key) => { | |
const value = params[key]; | |
if (params.constructor === Array) { | |
key = `${prefix}[]`; | |
} else if (params.constructor === Object) { | |
key = prefix ? `${prefix}[${key}]` : key; | |
} | |
if (typeof value === "object") { | |
return serializeUrlSearchParams(value, key); | |
} else { | |
return `${key}=${encodeURIComponent(value)}`; | |
} | |
}); | |
return query.join("&"); | |
} | |
export function buildUrl( | |
baseUrl: string, | |
endpoint: string, | |
query: object = {} | |
): URL { | |
const queryString = serializeUrlSearchParams(query); | |
const glue = | |
endpoint.indexOf("?") === -1 && queryString.length > 0 ? "?" : ""; | |
return new URL(`${baseUrl}${endpoint}${glue}${queryString}`); | |
} | |
export const buildHttpHeaders = ( | |
bearerToken: BearerToken, | |
...parts: Array<Header> | |
): Headers => { | |
return new Headers( | |
Object.assign( | |
{}, | |
bearerToken && { | |
Authorization: `Bearer ${bearerToken}`, | |
}, | |
...parts | |
) | |
); | |
}; | |
export function buildPostRequestOptions( | |
format: "json" | "FormData", | |
payload: object | |
): Pick<RequestOptions, "headers" | "body"> { | |
switch (format) { | |
case "json": { | |
return { | |
headers: { | |
"Content-Type": "application/json; charset=utf-8", | |
}, | |
body: payload ? JSON.stringify(payload) : null, | |
}; | |
} | |
case "FormData": { | |
const fd = new FormData(); | |
Object.entries(payload).forEach(([value, key]) => { | |
fd.append(key, value); | |
}); | |
return { | |
headers: {}, | |
body: fd, | |
}; | |
} | |
} | |
} | |
export class HttpError implements Error { | |
name: string; | |
message: string; | |
readonly code: string | number; | |
readonly payload: object; | |
constructor(code: string | number, payload: object) { | |
this.code = code; | |
this.payload = payload; | |
this.name = "HttpError"; | |
this.message = `Error Response [${code}]: ${JSON.stringify(payload)}`; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import ReactDOM from "react-dom"; | |
import { CocktailApp } from "./App"; | |
ReactDOM.render( | |
<React.StrictMode> | |
<CocktailApp /> | |
</React.StrictMode>, | |
document.getElementById("root") | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Reducer, useCallback, useReducer, useRef } from "react"; | |
export type Filters = Record<string, any>; | |
export type Sorts = Record<string, "asc" | "desc">; | |
export interface CollectionQuery { | |
filters: Filters | null; | |
sorts: Sorts | null; | |
page: number | null; | |
perPage: number | null; | |
} | |
type QueryBuilderAction = | |
| { | |
type: "SET_FILTERS"; | |
payload: Filters; | |
} | |
| { | |
type: "SET_SORTS"; | |
payload: Sorts; | |
} | |
| { | |
type: "SET_PAGE"; | |
payload: number; | |
} | |
| { | |
type: "SET_PER_PAGE"; | |
payload: number; | |
}; | |
function queryBuilderActionReducer( | |
query: CollectionQuery, | |
action: QueryBuilderAction | |
): CollectionQuery { | |
switch (action.type) { | |
case "SET_FILTERS": { | |
return { | |
...query, | |
filters: action.payload, | |
}; | |
} | |
case "SET_SORTS": { | |
return { | |
...query, | |
sorts: action.payload, | |
}; | |
} | |
case "SET_PAGE": { | |
return { | |
...query, | |
page: action.payload, | |
}; | |
} | |
case "SET_PER_PAGE": { | |
return { | |
...query, | |
perPage: action.payload, | |
}; | |
} | |
} | |
} | |
export function useResettableReducer( | |
reducer: Reducer<any, any>, | |
initialState: any | |
) { | |
const { current: initial } = useRef(initialState); | |
const resettableReducer = useCallback( | |
(state, action) => { | |
if (action.type === "RESET") { | |
return initial; | |
} | |
return reducer(state, action); | |
}, | |
[reducer, initial] | |
); | |
return useReducer(resettableReducer, initialState); | |
} | |
export function useQueryBuilder(initialState: Partial<CollectionQuery>) { | |
const [query, dispatch] = useResettableReducer(queryBuilderActionReducer, { | |
perPage: null, | |
page: null, | |
sorts: null, | |
filters: null, | |
...initialState, | |
} as CollectionQuery); | |
return { | |
query, | |
setFilters: (payload: Filters) => | |
dispatch({ | |
type: "SET_FILTERS", | |
payload, | |
}), | |
setSorts: (payload: Sorts) => | |
dispatch({ | |
type: "SET_SORTS", | |
payload, | |
}), | |
setPage: (payload: number) => | |
dispatch({ | |
type: "SET_PAGE", | |
payload, | |
}), | |
setPerPage: (payload: number) => | |
dispatch({ | |
type: "SET_PER_PAGE", | |
payload, | |
}), | |
reset: () => { | |
dispatch({ | |
type: "RESET", | |
payload: null, | |
}); | |
}, | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createContext, ReactNode, useContext, useRef } from "react"; | |
export type Container<S extends ServiceFactoryMap> = { | |
[P in keyof S]: ReturnType<S[P]>; | |
}; | |
export type ServiceFactoryFn<S> = (container: Container<any>) => S; | |
export type ServiceFactoryMap = Record<string | symbol, ServiceFactoryFn<any>>; | |
const ContainerContext = createContext< | |
Partial<{ services: Container<ServiceFactoryMap> }> | |
>({}); | |
interface ServiceProviderProps<S extends ServiceFactoryMap> { | |
services: S; | |
children: ReactNode; | |
} | |
export function ServiceProvider<S extends ServiceFactoryMap>({ | |
services, | |
children, | |
}: ServiceProviderProps<S>) { | |
const setup = (): Container<S> => { | |
const singletons: Partial<Container<S>> = {}; | |
return new Proxy(services, { | |
get(instance: S, property: keyof S, receiver: any) { | |
if (!singletons?.[property]) { | |
singletons[property] = instance[property](receiver); | |
} | |
return singletons[property]; | |
}, | |
}) as Container<S>; | |
}; | |
const { current } = useRef(setup()); | |
return ( | |
<ContainerContext.Provider value={{ services: current }}> | |
{children} | |
</ContainerContext.Provider> | |
); | |
} | |
export function useServiceProvider< | |
S extends ServiceFactoryMap | |
>(): Container<S> { | |
return useContext(ContainerContext).services as Container<S>; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Container, | |
ServiceFactoryFn, | |
ServiceFactoryMap, | |
} from "./ServiceProvider"; | |
import { send } from "./HttpTransportService"; | |
import { CocktailsDb, CocktailsService } from "./CocktailsDb"; | |
import { ApiClient } from "./ApiClient"; | |
const config = { | |
cocktailsDbBaseUrl: | |
process.env.COCKTAILS_DB_BASE_URL || | |
"https://www.thecocktaildb.com/api/json", | |
}; | |
export interface ConfigService extends ServiceFactoryMap { | |
config: ServiceFactoryFn<typeof config>; | |
} | |
export type Services = ConfigService & CocktailsService; | |
export const services: Services = { | |
config: () => config, | |
cocktailsDb: (c: Container<Services>) => | |
new ApiClient(c.config.cocktailsDbBaseUrl, send) as CocktailsDb, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment