Skip to content

Commit

Permalink
Merge pull request #45 from metsavaht/feature/redux-saga-resource-abo…
Browse files Browse the repository at this point in the history
…rtcontroller

Add AbortSignal handling for `@thorgate/redux-saga-router`
  • Loading branch information
jorgenader authored Dec 31, 2018
2 parents 3e8d60e + 2d8cda1 commit dd76b36
Show file tree
Hide file tree
Showing 19 changed files with 248 additions and 55 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ endpoints. It's still possible to use Resources without a router(see [Resource a
source of `ResponseWrapper`, `SuperagentResponse` and `Resource::ensureStatusAndJson` for guidance.
- ``withCredentials`` *(bool)*: Allow request backend to send cookies/authentication headers, useful when using same API for server-side rendering.
- ``allowAttachments`` *(bool)*: Allow POST like methods to send attachments.
- ``signal``: *(AbortSignal)*: Pass in an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) object to abort the request when desired. Default: [null].
- ``signal``: *(AbortSignal)*: Pass in an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) object to abort the request when desired.
**Only supported via request config.** Default: [null].

## Error handling

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"run-examples": "lerna exec --scope=run-examples yarn start",
"lint": "lerna --ignore=examples/* exec yarn lint",
"test": "jest",
"test:coverage": "yarn test --maxWorkers 16 --coverage",
"precoveralls": "yarn test:coverage --no-cache --maxWorkers 16",
"test:coverage": "yarn test --maxWorkers 20 --coverage",
"precoveralls": "yarn test:coverage --no-cache --maxWorkers 20",
"coveralls": "cat ./coverage/lcov.info | coveralls",
"check-packages": "yarn clean && yarn build && yarn test && yarn lint",
"bump-version": "lerna --ignore=examples/* version --no-push",
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/createRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { isArray, isObject, isString } from '@tg-resources/is';

import { Resource } from './resource';
import { Router } from './router';
import { ConfigType, ObjectMap, OptionalMap, RequestConfig } from './types';
import { ConfigType, ObjectMap, OptionalMap, RouteConfig, RouteConfigType } from './types';


export type ResourceTuple<Config = OptionalMap<ConfigType>> = [string, Config];
Expand All @@ -12,7 +12,7 @@ export const isResourceTuple = (value: any): value is ResourceTuple => (
);


export interface ResourceConstructorObject extends OptionalMap<ConfigType> {
export interface ResourceConstructorObject extends OptionalMap<RouteConfigType> {
apiEndpoint: string;
}

Expand All @@ -32,28 +32,28 @@ export type ResourceOrExtendedRouter<T, Klass extends Resource> = {


export interface ResourceClassConstructor<Klass> {
new(apiEndpoint: string, config?: RequestConfig | null): Klass;
new(apiEndpoint: string, config?: RouteConfig | null): Klass;
}


export type CreateResourceFactory = <
Klass extends Resource
>(
resourceKlass: ResourceClassConstructor<Klass>, apiEndpoint: string, config?: RequestConfig, options?: ObjectMap
resourceKlass: ResourceClassConstructor<Klass>, apiEndpoint: string, config?: RouteConfig, options?: ObjectMap
) => any;


export const createResource: CreateResourceFactory = <
Klass extends Resource
>(resourceKlass: ResourceClassConstructor<Klass>, apiEndpoint: string, config?: RequestConfig, _0?: ObjectMap): Klass => (
>(resourceKlass: ResourceClassConstructor<Klass>, apiEndpoint: string, config?: RouteConfig, _0?: ObjectMap): Klass => (
new resourceKlass(apiEndpoint, config)
);


export function createRouter<
Klass extends Resource, T extends ObjectMap = {}
>(
routes: T, config: RequestConfig | null, resourceKlass: ResourceClassConstructor<Klass>,
routes: T, config: RouteConfig, resourceKlass: ResourceClassConstructor<Klass>,
createResourceFactory: CreateResourceFactory = createResource
) {
const routeMap: {
Expand Down Expand Up @@ -83,7 +83,7 @@ export function createRouter<
'string',
'[string, config]',
'{apiEndpoint: string, ..config}',
'{ ...any }',
'Router',
];
throw new Error(`Unknown type used "${key}", one of [${types.join(',')}] is allowed`);
}
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { Route } from './route';
import {
AllowedFetchMethods,
AllowedPostMethods,
Attachments, ConfigType,
Attachments,
ConfigType,
ObjectMap,
Query,
RequestConfig,
ResourceErrorInterface,
ResourceInterface,
ResponseInterface,
RouteConfig,
} from './types';
import { mergeConfig, serializeCookies } from './util';

Expand All @@ -24,7 +26,7 @@ export abstract class Resource extends Route implements ResourceInterface {
* @param apiEndpoint Endpoint used for this resource. Supports ES6 token syntax, e.g: "/foo/bar/${pk}"
* @param config Customize config for this resource (see `Router.config`)
*/
public constructor(apiEndpoint: string, config: RequestConfig = null) {
public constructor(apiEndpoint: string, config: RouteConfig = null) {
super(config);
this._apiEndpoint = apiEndpoint;
}
Expand Down
16 changes: 11 additions & 5 deletions packages/core/src/route.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import {
ConfigType, ObjectMap,
ObjectMap,
RequestConfig,
RouteConfig,
RouteConfigType,
RouteInterface,
RouterInterface,
} from './types';


export abstract class Route implements RouteInterface {
protected _customConfig: RequestConfig = null;
protected _customConfig: RouteConfig = null;
protected _routeName: string = '';
protected _parent: RouterInterface | null = null;
protected _config: RequestConfig = null;

protected constructor(config: RequestConfig = null) {
protected constructor(config: RouteConfig = null) {
this._customConfig = config;

if (config && 'signal' in config) {
throw new Error('AbortSignal is not supported at top-level.');
}
}

public get parent() {
Expand Down Expand Up @@ -66,9 +72,9 @@ export abstract class Route implements RouteInterface {
return this._config;
}

public abstract config(): ConfigType;
public abstract config(): RouteConfigType;

public setConfig(config: RequestConfig) {
public setConfig(config: RouteConfig) {
// Update _customConfig
this._customConfig = {
...this._customConfig,
Expand Down
13 changes: 6 additions & 7 deletions packages/core/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import DEFAULTS from './constants';
import { Resource } from './resource';
import { Route } from './route';
import {
ConfigType,
Optional,
OptionalMap,
RequestConfig,
ResourceInterface,
RouteConfig,
RouteConfigType,
RouteInterface,
RouteMap,
RouterInterface
RouterInterface,
} from './types';
import { mergeConfig } from './util';

Expand Down Expand Up @@ -69,12 +68,12 @@ export function bindResources(routes: RouteMap, $this: RouterInterface) {

export class Router extends Route implements RouterInterface {
public static defaultRoutes: Optional<RouteMap> = null;
public static defaultConfig: Optional<OptionalMap<ConfigType>> = null;
public static defaultConfig: RouteConfig = null;
public _childKeys: string[] = [];

[key: string]: ResourceInterface | RouterInterface | any;

public constructor(routes: Optional<RouteMap> = null, config?: RequestConfig) {
public constructor(routes: Optional<RouteMap> = null, config: RouteConfig = null) {
super(config);

const defaultRoutes = (this.constructor as typeof Router).defaultRoutes;
Expand Down Expand Up @@ -118,7 +117,7 @@ export class Router extends Route implements RouterInterface {
);
}

return this._config as ConfigType;
return this._config as RouteConfigType;
}

public clearConfigCache() {
Expand Down
18 changes: 16 additions & 2 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Omit } from '@tg-resources/is';


export type Optional<T> = T | null;

export type OptionalMap<T> = {
Expand Down Expand Up @@ -91,6 +94,15 @@ export interface ConfigType {
}


/**
* Router and Resource config
*/
export type RouteConfigType = Omit<ConfigType, 'signal'>;


export type RouteConfig = Optional<OptionalMap<RouteConfigType>>;


export type RequestConfig = Optional<OptionalMap<ConfigType>>;


Expand Down Expand Up @@ -229,8 +241,8 @@ export interface RouteInterface {
getHeaders(): ObjectMap<string | null>;
getCookies(): ObjectMap<string | null>;

config(requestConfig?: RequestConfig): ConfigType;
setConfig(config: RequestConfig): void;
config(requestConfig?: RequestConfig): RouteConfigType;
setConfig(config: RouteConfigType): void;
clearConfigCache(): void;
}

Expand All @@ -253,6 +265,8 @@ export type ResourcePostMethod<
export interface ResourceInterface extends RouteInterface {
readonly apiEndpoint: string;

config(requestConfig?: RequestConfig): ConfigType;

fetch<
R = any, Params extends { [K in keyof Params]?: string } = {}
>(kwargs?: Params | null, query?: Query | null, requestConfig?: RequestConfig | null): Promise<R> | any;
Expand Down
6 changes: 6 additions & 0 deletions packages/core/test/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ describe('routers work', () => {
},
} as any);
}).toThrow(/All routes must be instances of Router or Resource/);

expect(() => {
new Router(null, {
signal: new Error('fake') as any,
});
}).toThrow(/AbortSignal is not supported at top-level/);
});

test('rebind fails', () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/is/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ export const isAbortSignal = (signal: any): signal is AbortSignal => {
export interface Constructable<T> {
new(...args: any[]): T;
}


export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
1 change: 1 addition & 0 deletions packages/saga-router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ This package adds extra configuration methods for `Router` and `Resource`.
- ``onRequestError`` *(Function)*: Optional function with signature `(error: ErrorType, resource: Resource, options: ResourceSagaRunnerConfig) => void | SagaIterator`.
This can be used to handle Sentry missing error handling.
- ``initializeSaga`` *(bool)*: **Advanced usage:** Initialize Saga iterator. This option disables usage of ``call`` effect.
- ``signal`` *(AbortSignal|null)*: **Advanced usage:** Manually provide signal to abort request. If it is not provided then AbortController is created internally and triggered when task is cancelled.

For additional configuration, see [Configuration](https:/thorgate/tg-resources/tree/master/README.md#configuration).

Expand Down
13 changes: 9 additions & 4 deletions packages/saga-router/src/SagaResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import {
Resource,
ResourceFetchMethods,
ResourcePostMethods,
RouterInterface } from 'tg-resources';
RouterInterface,
} from 'tg-resources';

import { DEFAULT_CONFIG } from './constants';
import { resourceSagaRunner } from './resourceSagaRunner';
import { ResourceSagaRunnerConfig, SagaConfigType, SagaRequestConfig } from './types';
import { ResourceSagaRunnerConfig, SagaConfigType, SagaRequestConfig, SagaRouteConfig } from './types';


export function isSagaResource<Klass extends Resource>(obj: any): obj is SagaResource<Klass> {
Expand All @@ -29,7 +31,7 @@ export function isSagaResourceInitialized(obj: any, config: SagaRequestConfig):
export class SagaResource<Klass extends Resource> extends Resource {
public constructor(
apiEndpoint: string,
config: SagaRequestConfig = null,
config: SagaRouteConfig = null,
resourceKlass: { new(apiEndpoint: string, config?: RequestConfig): Klass; },
) {
super(apiEndpoint, config as Pick<typeof config, keyof RequestConfig>);
Expand Down Expand Up @@ -82,7 +84,10 @@ export class SagaResource<Klass extends Resource> extends Resource {
}

public config(requestConfig?: SagaRequestConfig): SagaConfigType {
return this._resource.config(requestConfig) as SagaConfigType;
return {
...DEFAULT_CONFIG,
...this._resource.config(requestConfig),
} as SagaConfigType;
}

/* istanbul ignore next: not in use directly */
Expand Down
10 changes: 10 additions & 0 deletions packages/saga-router/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SagaConfigTypeBase } from './types';


export const DEFAULT_CONFIG: SagaConfigTypeBase = {
initializeSaga: false,

mutateRequestConfig: null,

onRequestError: null,
};
10 changes: 5 additions & 5 deletions packages/saga-router/src/createSagaRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import {
CreateResourceFactory,
createRouter,
ObjectMap,
RequestConfig,
Resource,
ResourceClassConstructor,
ResourceConstructorObject,
ResourceTuple,
Router
RouteConfig,
Router,
} from 'tg-resources';

import { SagaResource } from './SagaResource';
import { SagaRequestConfig } from './types';
import { SagaRouteConfig } from './types';


// Was required to copy this here as well - Type matching did not work correctly otherwise
Expand All @@ -26,15 +26,15 @@ export type ResourceOrExtendedRouter<T, Klass extends Resource> = {


export const createSagaResource: CreateResourceFactory = <Klass extends Resource>(
resourceKlass: ResourceClassConstructor<Klass>, apiEndpoint: string, config?: RequestConfig
resourceKlass: ResourceClassConstructor<Klass>, apiEndpoint: string, config?: RouteConfig
) => {
return new SagaResource<Klass>(apiEndpoint, config, resourceKlass);
};

export function createSagaRouter<
Klass extends Resource, T extends ObjectMap = {}
>(
routes: T, config: SagaRequestConfig | null, resourceKlass: ResourceClassConstructor<Klass>
routes: T, config: SagaRouteConfig | null, resourceKlass: ResourceClassConstructor<Klass>
) {
const router = createRouter(routes, config, resourceKlass, createSagaResource) as any;

Expand Down
Loading

0 comments on commit dd76b36

Please sign in to comment.