Skip to content
This repository has been archived by the owner on May 17, 2019. It is now read-only.

Commit

Permalink
Add compound plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
saspre committed Sep 3, 2018
1 parent c44c780 commit e78ef24
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 6 deletions.
163 changes: 163 additions & 0 deletions src/__tests__/compound-plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/* @flow */
import tape from 'tape-cup';
import ClientAppFactory from '../client-app';
import ServerAppFactory from '../server-app';
import {createPlugin} from '../create-plugin';
import {createToken, createArrayToken} from '../create-token';
import type {FusionPlugin, Token, ArrayToken} from '../types.js';

const App = __BROWSER__ ? ClientAppFactory() : ServerAppFactory();
type DepsType = {
dep: string,
};
type AType = {
a: string,
};

const TokenA: ArrayToken<AType> = createArrayToken('TokenA');
const TokenDep1: Token<DepsType> = createToken('TokenDep1');
const TokenDep2: Token<DepsType> = createToken('TokenDep2');

tape('compound tokens support dependencies', t => {
const app = new App('el', el => el);
t.ok(app, 'creates an app');
const counters = {
deps: 0,
a: 0,
b: 0,
};
const PluginDeps: FusionPlugin<{}, DepsType> = createPlugin({
provides: () => {
counters.deps++;
t.equal(counters.deps, 1, 'only instantiates once');
return {
dep: 'PluginDep',
};
},
});

const PluginA: FusionPlugin<void, AType> = createPlugin({
provides: () => {
counters.a++;
t.equal(counters.a, 1, 'only instantiates once');
return {
a: 'PluginA',
};
},
});

type PluginBType = FusionPlugin<{dep: Token<DepsType>}, AType>;
const PluginB: PluginBType = createPlugin({
deps: {dep: TokenDep1},
provides: deps => {
counters.b++;
t.equal(deps.dep.dep, 'PluginDep');
t.equal(counters.b, 1, 'only instantiates once');
return {
a: 'PluginB',
};
},
});

app.register(TokenA, PluginA);
app.register(TokenA, PluginB);
// $FlowFixMe
app.register(TokenA, 'value');
app.register(TokenDep1, PluginDeps);
app.register(
createPlugin({
deps: {a: TokenA},
provides: deps => {
t.equal(deps.a[0].a, 'PluginA');
t.equal(deps.a[1].a, 'PluginB');
t.equal(deps.a[2], 'value');
},
})
);
t.equal(counters.a, 0, 'does not instantiate until resolve is called');
t.equal(counters.b, 0, 'does not instantiate until resolve is called');
t.equal(counters.deps, 0, 'does not instantiate until resolve is called');
app.resolve();
t.equal(counters.a, 1, 'only instantiates once');
t.equal(counters.b, 1, 'only instantiates once');
t.equal(counters.deps, 1, 'only instantiates once');
t.end();
});

tape('dependency registration with aliases', t => {
const app = new App('el', el => el);
t.ok(app, 'creates an app');
const counters = {
dep1: 0,
dep2: 0,
a: 0,
b: 0,
};
const PluginDep1: FusionPlugin<{}, DepsType> = createPlugin({
provides: () => {
counters.dep1++;
t.equal(counters.dep1, 1, 'only instantiates once');
return {
dep: 'PluginDep1',
};
},
});
const PluginDep2: FusionPlugin<{}, DepsType> = createPlugin({
provides: () => {
counters.dep2++;
t.equal(counters.dep2, 1, 'only instantiates once');
return {
dep: 'PluginDep2',
};
},
});

const PluginA: FusionPlugin<{dep: Token<DepsType>}, AType> = createPlugin({
deps: {dep: TokenDep1},
provides: deps => {
counters.a++;
t.equal(deps.dep.dep, 'PluginDep2');
t.equal(counters.a, 1, 'only instantiates once');
return {
a: 'PluginA',
};
},
});

type PluginBType = FusionPlugin<{dep: Token<DepsType>}, AType>;
const PluginB: PluginBType = createPlugin({
deps: {dep: TokenDep1},
provides: deps => {
counters.b++;
t.equal(deps.dep.dep, 'PluginDep2');
t.equal(counters.b, 1, 'only instantiates once');
return {
a: 'PluginB',
};
},
});

app.register(TokenA, PluginA);
app.register(TokenA, PluginB).alias(TokenDep1, TokenDep2);
app.register(TokenDep1, PluginDep1);
app.register(TokenDep2, PluginDep2);
app.register(
createPlugin({
deps: {a: TokenA},
provides: deps => {
t.equal(deps.a[0].a, 'PluginA');
t.equal(deps.a[1].a, 'PluginB');
},
})
);
t.equal(counters.a, 0, 'does not instantiate until resolve is called');
t.equal(counters.b, 0, 'does not instantiate until resolve is called');
t.equal(counters.dep1, 0, 'does not instantiate until resolve is called');
t.equal(counters.dep2, 0, 'does not instantiate until resolve is called');
app.resolve();
t.equal(counters.a, 1, 'only instantiates once');
t.equal(counters.b, 1, 'only instantiates once');
t.equal(counters.dep1, 1, 'only instantiates once');
t.equal(counters.dep2, 1, 'only instantiates once');
t.end();
});
43 changes: 39 additions & 4 deletions src/base-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,42 @@ class FusionApp {
return this._register(token, value);
}
_register<TResolved>(token: Token<TResolved>, value: *) {
this.plugins.push(token);
const {aliases, enhancers} = this.registered.get(getTokenRef(token)) || {
const {value: registeredValue, aliases, enhancers} = this.registered.get(
getTokenRef(token)
) || {
value: null,
aliases: new Map(),
enhancers: [],
};
this.registered.set(getTokenRef(token), {value, aliases, enhancers, token});
if (token.isCompound) {
if (!registeredValue) {
// Initial value is set as an empty array
this.registered.set(getTokenRef(token), {
// $FlowFixMe
value: [],
aliases,
enhancers,
token,
});
}
this.enhance(token, originalValue => {
return createPlugin({
deps: {...value.deps},
provides: (...args) => {
value = value.provides ? value.provides(...args) : value;
return [...originalValue, value];
},
});
});
} else {
this.registered.set(getTokenRef(token), {
value,
aliases,
enhancers,
token,
});
}
this.plugins.push(token);
function alias(sourceToken: *, destToken: *) {
if (aliases) {
aliases.set(sourceToken, destToken);
Expand Down Expand Up @@ -101,7 +131,12 @@ class FusionApp {
if (enhancers && Array.isArray(enhancers)) {
enhancers.push(enhancer);
}
this.registered.set(getTokenRef(token), {value, aliases, enhancers, token});
this.registered.set(getTokenRef(token), {
value,
aliases,
enhancers,
token,
});
}
cleanup() {
return Promise.all(this.cleanups.map(fn => fn()));
Expand Down
6 changes: 6 additions & 0 deletions src/base-app.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
FusionPlugin,
Middleware,
Token,
ArrayToken,
} from './types.js';

declare class FusionApp {
Expand All @@ -26,11 +27,16 @@ declare class FusionApp {
register<TDeps, TProvides>(
Plugin: FusionPlugin<TDeps, TProvides>
): aliaser<Token<*>>;
register<TVal, TDeps>(
token: ArrayToken<TVal>,
Plugin: FusionPlugin<TDeps, TVal>
): aliaser<Token<*>>;
register<TVal, TDeps>(
token: Token<TVal>,
Plugin: FusionPlugin<TDeps, TVal>
): aliaser<Token<*>>;
register<TVal>(token: Token<TVal>, val: TVal): aliaser<Token<*>>;
register<TVal>(token: ArrayToken<TVal>, val: TVal): aliaser<Token<*>>;
middleware<TDeps>(
deps: TDeps,
middleware: (Deps: $ObjMap<TDeps, ExtractReturnType>) => Middleware
Expand Down
12 changes: 11 additions & 1 deletion src/create-token.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* @flow
*/

import type {Token} from './types.js';
import type {Token, ArrayToken} from './types.js';

export const TokenType = {
Required: 0,
Expand All @@ -18,6 +18,7 @@ export class TokenImpl<TResolved> {
ref: mixed;
type: $Values<typeof TokenType>;
optional: ?TokenImpl<TResolved>;
isCompound = false;

constructor(name: string, ref: mixed) {
this.name = name;
Expand All @@ -33,3 +34,12 @@ export function createToken<TResolvedType>(name: string): Token<TResolvedType> {
// $FlowFixMe
return new TokenImpl(name);
}

export function createArrayToken<TResolvedType>(
name: string
): ArrayToken<TResolvedType> {
const token = new TokenImpl(name);
token.isCompound = true;
// $FlowFixMe
return token;
}
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export {
HttpServerToken,
} from './tokens';
export {createPlugin} from './create-plugin';
export {createToken} from './create-token';
export {createToken, createArrayToken} from './create-token';
export {getEnv};

type FusionApp = typeof BaseApp;
Expand Down
8 changes: 8 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import type {Context as KoaContext} from 'koa';
export type Token<T> = {
(): T,
optional: () => void | T,
isCompound: false,
};

export type ArrayToken<T> = {
(): T,
} & {
...Token<Array<T>>,
isCompound: true,
};

type ExtendedKoaContext = KoaContext & {memoized: Map<Object, mixed>};
Expand Down

0 comments on commit e78ef24

Please sign in to comment.