Skip to content

Commit

Permalink
fix: asyncify action
Browse files Browse the repository at this point in the history
  • Loading branch information
izatop committed Apr 22, 2022
1 parent 870927e commit d724f66
Show file tree
Hide file tree
Showing 25 changed files with 97 additions and 59 deletions.
3 changes: 2 additions & 1 deletion packages/app/src/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ export class Application<C extends Context> {
Object.freeze(state);
await request.linkState?.(state);

return this.#unit.run(route.action, state);
// @todo
return this.#unit.run(route.action as any, state);
}

public getRoutes(): IRoute<ActionAny<C>>[] {
Expand Down
23 changes: 18 additions & 5 deletions packages/app/src/Route/Route.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import {ActionAny, ActionFactory} from "@bunt/unit";
import {Ctor, ILogable, isFunction, isString} from "@bunt/util";
import {IRoute, IRouteMatcher, RouteFactory, RouteMatcherFactory, RouteRuleArg, RouteRuleVariants} from "./interfaces";
import {ILogable, isFunction, isString} from "@bunt/util";
import {
IRoute,
IRouteMatcher,
RouteFactory,
RouteMatcherFactory,
RouteRuleArg,
RouteRuleVariants,
} from "./interfaces";
import {RouteRule} from "./RouteRule";

export class Route<A extends ActionAny> implements IRoute<A>, ILogable<{route: string}> {
public readonly route: string;
public readonly action: ActionFactory<any>;
public readonly action: ActionFactory<A>;
public readonly payload?: RouteRule<A>;
readonly #matcher: IRouteMatcher;

constructor(matcher: RouteMatcherFactory, action: ActionFactory<any>, rule: RouteRuleArg<A>) {
constructor(matcher: RouteMatcherFactory, action: ActionFactory<A>, rule: RouteRuleArg<A>) {
const {route, payload} = this.getRuleArgs(rule);
this.route = route;
this.action = action;
Expand All @@ -18,8 +25,14 @@ export class Route<A extends ActionAny> implements IRoute<A>, ILogable<{route: s
this.#matcher = isFunction(matcher) ? matcher(this.route) : matcher;
}

/**
* @deprecated
*
* @param matcher RouteMatcherFactory
* @returns RouteFactory
*/
public static create(matcher: RouteMatcherFactory): RouteFactory {
return <A extends ActionAny>(action: Ctor<A>, rule: RouteRuleArg<A>) => (
return <A extends ActionAny>(action: ActionFactory<A>, rule: RouteRuleArg<A>) => (
new Route<A>(matcher, action, rule)
);
}
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/Route/fn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {ActionAny, ActionFactory} from "@bunt/unit";
import {RouteRuleArg} from "./interfaces";
import {RegexpMatcher} from "./Matcher/RegexpMatcher";
import {Route} from "./Route";

export function route<A extends ActionAny>(action: ActionFactory<A>, rule: RouteRuleArg<A>): Route<A> {
return new Route<A>(RegexpMatcher.factory, action, rule);
}
1 change: 1 addition & 0 deletions packages/app/src/Route/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from "./Matcher/EqualMatcher";
export * from "./RouteNotFound";
export * from "./RouteRule";
export * from "./Route";
export * from "./fn";
5 changes: 2 additions & 3 deletions packages/app/src/Route/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {ActionAny, ActionContext, ActionFactory, ActionState, ApplyContext} from "@bunt/unit";
import {Ctor} from "@bunt/util";
import {IRequest} from "../interfaces";
import {Payload} from "../Payload";
import {Route} from "./Route";
Expand All @@ -8,7 +7,7 @@ import {RouteRule} from "./RouteRule";
export interface IRoute<A extends ActionAny> {
readonly route: string;

readonly action: ActionFactory<any>;
readonly action: ActionFactory<A>;

readonly payload?: Payload<A>;

Expand All @@ -25,7 +24,7 @@ export type RouteRuleVariants<A extends ActionAny> = {route: string; payload: un
export type RouteRuleArg<A extends ActionAny> = ActionState<A> extends null
? string : RouteRule<A>;

export type RouteFactory = <A extends ActionAny>(action: Ctor<A>, rule: RouteRuleArg<A>) => Route<A>;
export type RouteFactory = <A extends ActionAny>(action: ActionFactory<A>, rule: RouteRuleArg<A>) => Route<A>;

export interface IRouteContext<A extends ActionAny> {
request: IRequest;
Expand Down
7 changes: 7 additions & 0 deletions packages/app/test/src/Action.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Application, RouteNotFound} from "../../src";
import BaseTestAsyncRoute from "./app/Action/BaseTestAsyncRoute";
import HelloWorldRoute from "./app/Action/HelloWorldRoute";
import {BaseContext} from "./app/Context/BaseContext";
import {Request} from "./app/Request/Request";
Expand Down Expand Up @@ -36,4 +37,10 @@ describe("Route", () => {
const request = new Request("/wrong-uri", {});
await expect(app.run(request)).rejects.toThrowError(RouteNotFound);
});

test("Async route", async () => {
const app = await Application.factory(new BaseContext(), [BaseTestAsyncRoute]);
const request = new Request("GET /async/test", {});
await expect(app.run(request)).resolves.toBe("Hello, async!");
});
});
2 changes: 2 additions & 0 deletions packages/app/test/src/app/Action/BaseTestAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export class BaseTestAction extends Action<BaseContext, {name: string}> {
return `Hello, ${this.state.name}!`;
}
}

export default BaseTestAction;
12 changes: 12 additions & 0 deletions packages/app/test/src/app/Action/BaseTestAsyncRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {Fields, Text} from "@bunt/input";
import {asyncify} from "@bunt/unit";
import {Resolver, route, RouteRule} from "../../../../src";

export default route(
asyncify(() => import("./BaseTestAction")),
new RouteRule(
"GET /async/test",
new Fields({name: Text}),
new Resolver({name: () => "async"}),
),
);
3 changes: 1 addition & 2 deletions packages/app/test/src/app/Action/HelloWorldRoute.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {Bool, Fields, Text, ToNumber} from "@bunt/input";
import {Action} from "@bunt/unit";
import {Resolver, RouteRule} from "../../../../src";
import {route} from "../../route";
import {Resolver, route, RouteRule} from "../../../../src";
import {BaseContext} from "../Context/BaseContext";

interface IHelloWorldActionState {
Expand Down
3 changes: 0 additions & 3 deletions packages/app/test/src/route.ts

This file was deleted.

5 changes: 3 additions & 2 deletions packages/cli/src/Commander.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ export class Commander<C extends CommandContext> {
}

public static async execute<C extends CommandContext>(
context: ContextArg<C>, routes: IRoute<Action<C, any, IRunnable>>[] = []): Promise<IRunnable | undefined> {
context: ContextArg<C>,
routes: IRoute<Action<C, any, IRunnable | void>>[] = []): Promise<IRunnable | void> {
const command = new this<C>(await Application.factory<C>(context, routes));

return command.handle();
}

public async handle(): Promise<IRunnable | undefined> {
public async handle(): Promise<IRunnable | void> {
const {context} = this.#application;
const request = new RequestCommand(context.program.args);

Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/fn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {EqualMatcher, Route, RouteRuleArg} from "@bunt/app";
import {ActionAny, ActionFactory} from "@bunt/unit";

export function command<A extends ActionAny>(action: ActionFactory<A>, rule: RouteRuleArg<A>): Route<A> {
return new Route<A>(EqualMatcher.factory, action, rule);
}
1 change: 1 addition & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./Request";
export * from "./Commander";
export * from "./Action/Command";
export * from "./Context/CommandContext";
export * from "./fn";
3 changes: 1 addition & 2 deletions packages/cli/test/src/Command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import {Resolver, RouteRule} from "@bunt/app";
import {Fields, Nullable, Text} from "@bunt/input";
import {dispose, Heartbeat, isDisposable, isRunnable} from "@bunt/unit";
import {ok} from "assert";
import {Commander} from "../../src";
import {command, Commander} from "../../src";
import {BaseTestCommand} from "./app/Action/BaseTestCommand";
import {command} from "./app/command";
import {BaseContext} from "./app/Context/BaseContext";

describe("Command", () => {
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/test/src/RunnableCommand.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {dispose, isDisposable, isRunnable} from "@bunt/unit";
import {ok} from "assert";
import {Commander} from "../../src";
import {command, Commander} from "../../src";
import {RunnableTestCommand} from "./app/Action/RunnableTestCommand";
import {command} from "./app/command";
import {BaseContext} from "./app/Context/BaseContext";

test("Runnable Command", async () => {
Expand Down
3 changes: 0 additions & 3 deletions packages/cli/test/src/app/command.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/project/src/Command/UpdateLintCommand.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {command} from "../route";
import {command} from "@bunt/cli";
import {BaseCommand} from "./BaseCommand";

class UpdateLintCommand extends BaseCommand {
Expand Down
4 changes: 2 additions & 2 deletions packages/queue/src/Dispatcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ActionCtor, Context, ContextArg, Disposer, Heartbeat, IRunnable, unit, Unit} from "@bunt/unit";
import {ActionFactory, Context, ContextArg, Disposer, Heartbeat, IRunnable, unit, Unit} from "@bunt/unit";
import {Ctor, Defer, logger, Logger} from "@bunt/util";
import {Handler} from "./Handler";
import {ITransport} from "./interfaces";
Expand All @@ -10,7 +10,7 @@ export class Dispatcher<C extends Context> extends Disposer implements IRunnable

readonly #unit: Unit<C>;
readonly #queue: QueueAbstract<ITransport>;
readonly #route = new Map<MessageCtor<any>, ActionCtor<C>>();
readonly #route = new Map<MessageCtor<any>, ActionFactory<C>>();

protected constructor(u: Unit<C>, queue: QueueAbstract<ITransport>) {
super();
Expand Down
16 changes: 8 additions & 8 deletions packages/unit/src/Unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import {assert, isFunction, isInstanceOf, logger, Logger} from "@bunt/util";
import {ApplyContext, Context} from "./Context";
import {Action} from "./Action";
import {
ActionAny,
ActionCtor,
ActionCtorImport,
ActionFactory,
ActionReturn,
ActionState,
AsyncActionFactory,
ContextArg,
StateType,
} from "./interfaces";

export class Unit<C extends Context> {
Expand Down Expand Up @@ -44,10 +44,10 @@ export class Unit<C extends Context> {
return Context.apply(syncContext);
}

public async run<A extends Action<C, S, R>, S extends StateType, R = unknown>(
factory: ActionFactory<C, S, R, A>,
state: ActionState<A>): Promise<ActionReturn<Action<C, S, R>>> {
const ctor = await Unit.getAction(factory) as ActionCtor<C, S, R, A>;
public async run<A extends ActionAny<C>>(
factory: ActionFactory<A>,
state: ActionState<A>): Promise<ActionReturn<A>> {
const ctor = await Unit.getAction(factory);
assert(Action.isPrototypeOf(ctor), "The 'ctor' hasn't prototype of the Action class");

const finish = this.logger.perf("run", {action: ctor.name});
Expand All @@ -58,15 +58,15 @@ export class Unit<C extends Context> {

public static async getAction(action: ActionFactory<any>): Promise<ActionCtor<any>> {
if (this.isActionFactory(action)) {
const {default: ctor} = await action();
const {default: ctor} = await action.factory();

return ctor;
}

return action;
}

private static isActionFactory(action: ActionFactory<any>): action is ActionCtorImport<any> {
private static isActionFactory(action: ActionFactory<any>): action is AsyncActionFactory<any> {
return !Action.isPrototypeOf(action);
}
}
Expand Down
5 changes: 5 additions & 0 deletions packages/unit/src/fn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {ActionAny, AsyncActionFactory, ActionImport} from "./interfaces";

export function asyncify<A extends ActionAny>(factory: ActionImport<A>): AsyncActionFactory<A> {
return {factory};
}
1 change: 1 addition & 0 deletions packages/unit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./Context";
export * from "./Service";
export * from "./Runtime";
export * from "./Dispose";
export * from "./fn";
28 changes: 9 additions & 19 deletions packages/unit/src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
import {Promisify} from "@bunt/util";
import {Fn, Promisify} from "@bunt/util";
import {Action} from "./Action";
import {ApplyContext, Context} from "./Context";

export type ActionAny<C extends Context = Context, S extends StateType | null = any, R = any> = Action<C, S, R>;

export type ContextArg<C extends Context> = (() => Promisify<C>) | Promisify<C>;

export type ActionCtor<C extends Context,
S extends StateType | null = any,
R = unknown,
A extends Action<C, S, R> = Action<C, S, R>> = {
new(context: ApplyContext<C>, state: S): A;

prototype: A;
};

export type ActionCtorImport<C extends Context,
S extends StateType | null = any,
R = unknown,
A extends Action<C, S, R> = Action<C, S, R>> = () => Promise<{default: ActionCtor<C, S, R, A>}>;

export type ActionFactory<C extends Context,
S extends StateType | null = any,
R = unknown,
A extends Action<C, S, R> = Action<C, S, R>> = ActionCtor<C, S, R, A> | ActionCtorImport<C, S, R, A>;
export type ActionCtor<A extends ActionAny> = {
new(context: ApplyContext<ActionContext<A>>, state: ActionState<A>): A;
prototype: A;
};

export type ActionImport<A extends ActionAny> = Fn<[], Promise<{default: ActionCtor<A>}>>;
export type AsyncActionFactory<A extends ActionAny> = {factory: ActionImport<A>};
export type ActionFactory<A extends ActionAny> = ActionCtor<A> | AsyncActionFactory<A>;

export type ActionContext<A> = A extends ActionAny<infer T> ? T : never;
export type ActionState<A> = A extends ActionAny<any, infer T> ? T : never;
Expand All @@ -41,3 +30,4 @@ export type StateType = Record<string, any> | null;
export interface IShadowState<T> {
getShadowState(): T;
}

7 changes: 3 additions & 4 deletions packages/unit/test/src/Unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import {unit, Unit} from "../../src";
import {asyncify, unit, Unit} from "../../src";
import {BaseTestAction} from "./actions/BaseTestAction";
import {ProfileTestAction} from "./actions/ProfileTestAction";
import {TestExceptionAction} from "./actions/TestExceptionAction";
import {BaseContext} from "./context/BaseContext";

const ActionImport = () => import("./actions/BaseTestAction");

test("Unit", async () => {
const app = await unit(new BaseContext());

Expand All @@ -18,7 +16,8 @@ test("Unit", async () => {
const helloWorldRun: string = await app.run(BaseTestAction, {name});
expect(helloWorldRun).toBe(`Hello, ${name}!`);

const asyncTest = await app.run(ActionImport, {name: "AsyncTest"});
const AsyncBaseTestAction = asyncify(() => import("./actions/BaseTestAction"));
const asyncTest = await app.run(AsyncBaseTestAction, {name: "AsyncTest"});
expect(asyncTest).toBe("Hello, AsyncTest!");

await app.run(ProfileTestAction, null);
Expand Down
3 changes: 2 additions & 1 deletion packages/ws/src/WebSocketServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ export class WebSocketServer<C extends Context> extends Disposer implements IRun
ws.emit("connection", connection, req);
ShadowState.set(state, connection);

this.handle(connection, () => this.#unit.run(route.action, state));
// @todo
this.handle(connection, () => this.#unit.run(route.action as any, state));
});
} catch (error) {
this.logger.error("Unexpected error", error);
Expand Down
2 changes: 1 addition & 1 deletion run-tests
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/sh

docker ps --filter name=redis-test -q || \
test "$(docker ps --filter name=redis-test -q)" = "" && \
docker run --rm --name redis-test -p 6379:6379 -d redis:alpine || exit 1

yarn jest $@
Expand Down

0 comments on commit d724f66

Please sign in to comment.