Skip to content
This repository was archived by the owner on May 24, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8a59985
FrequencyObservable as a function returning a observable
amaury1093 Sep 7, 2018
f113991
Fix tests
amaury1093 Sep 7, 2018
5e613d4
Test a new api
amaury1093 Sep 12, 2018
9ecae21
Make frequency observables work
amaury1093 Sep 13, 2018
660c5ef
Fix tests
amaury1093 Sep 13, 2018
6e99b67
Remove overview
amaury1093 Sep 13, 2018
e4298fa
Don't cover index.ts
amaury1093 Sep 13, 2018
bb5509c
Fix a lot of stuff
amaury1093 Sep 13, 2018
1008d5a
Remove useless import
amaury1093 Sep 13, 2018
3aa360b
Change testRegex for jest
amaury1093 Sep 13, 2018
ea75324
Add ambient for packages with no typigns
amaury1093 Sep 13, 2018
7c03c5c
Generate docs
amaury1093 Sep 13, 2018
80b283f
Remove NullProvider
amaury1093 Sep 13, 2018
a46b19a
Remove NullProvider
amaury1093 Sep 13, 2018
3447e95
Regenerate docs
amaury1093 Sep 13, 2018
1f0c25a
Silent jest on CI
amaury1093 Sep 13, 2018
b4404e2
Fix makeContract and post
amaury1093 Sep 13, 2018
165df16
Regen docs
amaury1093 Sep 13, 2018
9b8edd8
Update summary
amaury1093 Sep 13, 2018
fb89a94
Update ambient
amaury1093 Sep 13, 2018
439d3bd
Export post$ too
amaury1093 Sep 13, 2018
18e640f
Fix makeContract
amaury1093 Sep 13, 2018
431fe51
Fix memoization for frequency observables
amaury1093 Sep 17, 2018
44dde4a
Don't lint lib
amaury1093 Sep 17, 2018
15caa62
Fix bugs with rpc$
amaury1093 Sep 17, 2018
0cbe22a
Remove useless packages
amaury1093 Sep 17, 2018
85f8fb9
Generate docs
amaury1093 Sep 17, 2018
c5e0c56
Update withoutLoading syntax
amaury1093 Sep 17, 2018
82fca45
Use json.stringify for normalizer
amaury1093 Sep 17, 2018
4893e97
Fix bug normalizer
amaury1093 Sep 17, 2018
e449a57
Remove withApi in docs
amaury1093 Sep 17, 2018
3a885fe
Fix bug memoization
amaury1093 Sep 17, 2018
c6ee32e
Remove onEvery2Blocks
amaury1093 Sep 17, 2018
041441b
Options then args
amaury1093 Sep 17, 2018
8a066db
CreateRpc in makeContract fix
amaury1093 Sep 17, 2018
d2bb3c7
Fix getContract memoization
amaury1093 Sep 17, 2018
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
Prev Previous commit
Next Next commit
Fix memoization for frequency observables
  • Loading branch information
amaury1093 committed Sep 17, 2018
commit 431fe51c9407888d4a290e40279cc483987abac9
26 changes: 15 additions & 11 deletions packages/light.js/src/frequency/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

import BigNumber from 'bignumber.js';
import { filter } from 'rxjs/operators';
import * as memoizee from 'memoizee';

import { createApiFromProvider, getApi } from '../api';
import createPubsubObservable from './utils/createPubsubObservable';
import { FrequencyObservableOptions } from '../types';

Expand All @@ -19,23 +21,25 @@ export function onEveryBlock$(options?: FrequencyObservableOptions) {
}

/**
* Observable that emits on every 2nd block.
* Given an api object, return Observable that emits on every 2nd block.
* Pure function version of {@link onEvery2Blocks}.
*
* @param options - Options to pass to {@link FrequencyObservable}.
* @param api - The Api object.
*/
export function onEvery2Blocks$(options?: FrequencyObservableOptions) {
return onEveryBlock$(options).pipe(
const onEvery2BlocksWithApi$ = memoizee((api: any) =>
onEveryBlock$({ provider: api.provider }).pipe(
filter(n => +n % 2 === 0) // Around ~30s on mainnet // TODO Use isEqualTo and mod from bignumber.js
);
}
)
);

/**
* Observable that emits on every 4th block.
* Observable that emits on every 2nd block.
*
* @param options - Options to pass to {@link FrequencyObservable}.
*/
export function onEvery4Blocks$(options?: FrequencyObservableOptions) {
return onEveryBlock$(options).pipe(
filter(n => +n % 4 === 0) // Around ~1min on mainnet // TODO Use isEqualTo and mod from bignumber.js
);
export function onEvery2Blocks$(options: FrequencyObservableOptions = {}) {
const { provider } = options;
const api = provider ? createApiFromProvider(provider) : getApi();

return onEvery2BlocksWithApi$(api);
}
22 changes: 19 additions & 3 deletions packages/light.js/src/frequency/frequency.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { take } from 'rxjs/operators';
import frequency from './frequency';
import { FrequencyObservable, FrequencyKey, FrequencyMap } from '../types';
import isObservable from '../utils/isObservable';
import { resolveApi } from '../utils/testHelpers/mockApi';
import { rejectApi, resolveApi } from '../utils/testHelpers/mockApi';
import { setApi } from '@parity/light.js/src/api';

/**
Expand All @@ -22,7 +22,7 @@ const testFrequency = (
resolveWith: any = 'foo'
) =>
describe(`${name} frequency`, () => {
beforeAll(() => {
beforeEach(() => {
setApi(resolveApi(resolveWith));
});

Expand All @@ -34,11 +34,27 @@ const testFrequency = (
expect(() => frequency$().subscribe()).not.toThrow();
});

it('function should return the same Observable upon re-running (memoization)', () => {
it('should return the same Observable upon re-running (memoization)', () => {
const initial$ = frequency$();
expect(frequency$()).toBe(initial$);
});

if (
[
'onAccountsChanged',
'onAccountsInfoChanged',
'onEveryBlock$',
'onEvery2Blocks$',
'onSyncingChanged'
].includes(name)
) {
it('should not return the same Observable if we change Api in between', () => {
const initial$ = frequency$();
setApi(rejectApi());
expect(frequency$()).not.toBe(initial$);
});
}

it('should return values', done => {
frequency$()
.pipe(take(1))
Expand Down
5 changes: 2 additions & 3 deletions packages/light.js/src/frequency/frequency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@
import * as accounts from './accounts';
import * as blocks from './blocks';
import * as health from './health';
import { memoizeAll } from '../utils/memoizeAll';
import * as other from './other';
import * as time from './time';

const frequency = memoizeAll({
const frequency = {
...accounts,
...blocks,
...health,
...other,
...time
});
};

export default frequency;
9 changes: 7 additions & 2 deletions packages/light.js/src/frequency/other.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
// SPDX-License-Identifier: MIT

import { of } from 'rxjs';
import * as memoizee from 'memoizee';

import { FrequencyObservable, FrequencyObservableOptions } from '../types';
import { FrequencyObservableOptions } from '../types';

/**
* Observable that emits only once.
*
* @param options - Options to pass to {@link FrequencyObservable}.
*/
export function onStartup$(_?: FrequencyObservableOptions) {
function onStartup$(_?: FrequencyObservableOptions) {
return of(0);
}
// @ts-ignore
onStartup$ = memoizee(onStartup$);

export { onStartup$ };
32 changes: 19 additions & 13 deletions packages/light.js/src/frequency/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,35 @@
// SPDX-License-Identifier: MIT

import { timer } from 'rxjs';
import * as memoizee from 'memoizee';

import { FrequencyObservable, FrequencyObservableOptions } from '../types';
import { FrequencyObservableOptions } from '../types';

/**
* Observable that emits on every second.
*/
export const onEverySecond$: FrequencyObservable<number> = (
_?: FrequencyObservableOptions
) => timer(0, 1000);
onEverySecond$.metadata = { name: 'onEverySecond$' };
function onEverySecond$(_?: FrequencyObservableOptions) {
return timer(0, 1000);
}
// @ts-ignore
onEverySecond$ = memoizee(onEverySecond$);

/**
* Observable that emits on every other second.
*/
export const onEvery2Seconds$: FrequencyObservable<number> = (
_?: FrequencyObservableOptions
) => timer(0, 2000);
onEvery2Seconds$.metadata = { name: 'onEvery2Seconds$' };
function onEvery2Seconds$(_?: FrequencyObservableOptions) {
return timer(0, 2000);
}
// @ts-ignore
onEvery2Seconds$ = memoizee(onEvery2Seconds$);

/**
* Observable that emits every five seconds.
*/
export const onEvery5Seconds$: FrequencyObservable<number> = (
_?: FrequencyObservableOptions
) => timer(0, 5000);
onEvery5Seconds$.metadata = { name: 'onEvery5Seconds$' };
function onEvery5Seconds$(_?: FrequencyObservableOptions) {
return timer(0, 5000);
}
// @ts-ignore
onEvery5Seconds$ = memoizee(onEvery5Seconds$);

export { onEverySecond$, onEvery2Seconds$, onEvery5Seconds$ };
80 changes: 47 additions & 33 deletions packages/light.js/src/frequency/utils/createPubsubObservable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,59 @@

import * as debug from 'debug';
import { FrequencyObservableOptions } from '../../types';
import * as memoizee from 'memoizee';
import { Observable, Observer, timer } from 'rxjs';
import { switchMap } from 'rxjs/operators';

import { createApiFromProvider, getApi } from '../../api';
import { distinctReplayRefCount } from '../../utils/operators/distinctReplayRefCount';

/**
* Observable that emits on each pubsub event.
* Given an api, returns an Observable that emits on each pubsub event.
* Pure function version of {@link createPubsubObservable}.
*
* @ignore
*/
const createPubsubObservableWithApi = memoizee(
<T>(pubsub: string, api: any) => {
const [namespace, method] = pubsub.split('_');

// There's a chance the provider doesn't support pubsub, for example
// MetaMaskProvider. In this case, as suggested on their Github, the best
// solution for now is to poll.
if (!api.isPubSub) {
debug('@parity/light.js:api')(
`Pubsub not available for ${
api.provider ? api.provider.constructor.name : 'current Api'
} provider, polling "${pubsub}" every second.`
);

return timer(0, 1000).pipe(
switchMap(api[namespace][method])
) as Observable<T>;
}

return Observable.create((observer: Observer<T>) => {
const subscription = api.pubsub[namespace][method](
(error: Error, result: any) => {
// TODO use @parity/api type for result
if (error) {
observer.error(error);
} else {
observer.next(result);
}
}
);
return () =>
subscription.then((subscriptionId: string) =>
api.pubsub.unsubscribe(subscriptionId)
);
}).pipe(distinctReplayRefCount()) as Observable<T>;
}
);

/**
* Given a provider, returns an Observable that emits on each pubsub event.
*
* @ignore
* @example onAccountsChanged$, onEveryBlock$...
Expand All @@ -21,40 +66,9 @@ const createPubsubObservable = <T>(
pubsub: string,
{ provider }: FrequencyObservableOptions = {}
) => {
const [namespace, method] = pubsub.split('_');
const api = provider ? createApiFromProvider(provider) : getApi();

Copy link
Contributor

Choose a reason for hiding this comment

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

we need to memoize at this point based on pubsub and api

Copy link
Contributor

Choose a reason for hiding this comment

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

for example

const createPubsubObservable = <T>(
  pubsub: string,
  { provider }: FrequencyObservableOptions = {}
) => {
  const [namespace, method] = pubsub.split('_');
  const api = provider ? createApiFromProvider(provider) : getApi();

  return memoize(createPubsubObservableWithApi)(pubsub, api);
};

and put the rest of the code in createPubsubObservableWithApi

// There's a chance the provider doesn't support pubsub, for example
// MetaMaskProvider. In this case, as suggested on their Github, the best
// solution for now is to poll.
if (!api.isPubSub) {
debug('@parity/light.js:api')(
`Pubsub not available for ${
api.provider ? api.provider.constructor.name : 'current Api'
} provider, polling "${pubsub}" every second.`
);

return timer(0, 1000).pipe(
switchMap(() => api[namespace][method]())
) as Observable<T>;
}

return Observable.create((observer: Observer<T>) => {
const subscription = api.pubsub[namespace][method](
(error: Error, result: any) => {
// TODO use @parity/api type for result
if (error) {
observer.error(error);
} else {
observer.next(result);
}
}
);
return () =>
subscription.then((subscriptionId: string) =>
api.pubsub.unsubscribe(subscriptionId)
);
}).pipe(distinctReplayRefCount()) as Observable<T>;
return createPubsubObservableWithApi<T>(pubsub, api);
};

export default createPubsubObservable;
48 changes: 35 additions & 13 deletions packages/light.js/src/rpc/other/makeContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,35 +29,37 @@ interface MakeContract {
*
* @param address - The contract address.
* @param abiJson - The contract abi.
* @param api - The api Object.
* @return - The contract object as defined in @parity/api.
*/
const getContract = memoizee(
(address: Address, abiJson: any[], provider: any) => {
const api = provider ? createApiFromProvider(provider) : getApi();
return api.newContract(abiJson, address);
}, // use types from @parity/abi
(address: Address, abiJson: any[], api: any) =>
api.newContract(abiJson, address), // use types from @parity/abi
{ length: 1 } // Only memoize by address
Copy link
Contributor

Choose a reason for hiding this comment

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

memoization needs to be done based on address & api (provider ? createApiFromProvider(provider) : getApi()), otherwise if we do setApi() getContract() setApi() getContract() the last getContract() will return the memoized return value from the first getContract() call with the old api

Copy link
Contributor

Choose a reason for hiding this comment

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

does the memoization here needs to be based on api also or not?

Copy link
Collaborator Author

@amaury1093 amaury1093 Sep 17, 2018

Choose a reason for hiding this comment

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

Yes, api.newContract(...).contractMethod.call() makes a rpc call with that api object, fixed.

);

/**
* Create a contract.
* Create a contract, givan an api object.
* Pure function version of {@link makeContract}.
*
* @ignore
* @param address - The contract address.
* @param - The contract abi.
* @param abiJson - The contract abi.
* @param api - The api Object.
* @return - An object whose keys are all the functions of the
* contract, and each function return an Observable which will fire when the
* function resolves.
*/
export const makeContract = memoizee(
(address: Address, abiJson: any[], options: { provider?: any } = {}) => {
const { provider } = options;
const makeContractWithApi = memoizee(
(address: Address, abiJson: any[], api: any) => {
const abi = new Abi(abiJson); // use types from @parity/abi
Copy link
Contributor

Choose a reason for hiding this comment

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

memoization needs to be done based on address and api (same as above)

Copy link
Contributor

@axelchalon axelchalon Sep 14, 2018

Choose a reason for hiding this comment

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

so it's a bit more tricky here
we could have getContract require an api/provider to be passed as parameter; put const api = provider ? createApiFromProvider(provider) : getApi(); here (in makeContract) and at that point memoize based on address and api


// Variable result will hold the final object to return
const result: MakeContract = {
abi,
address,
get contractObject() {
return getContract(address, abiJson, provider);
return getContract(address, abiJson, api);
}
};

Expand All @@ -69,7 +71,7 @@ export const makeContract = memoizee(
// We only get the contract when the function is called for the 1st
// time. Note: getContract is memoized, won't create contract on each
// call.
const contract = getContract(address, abiJson, provider);
const contract = getContract(address, abiJson, api);
const method = contract.instance[name]; // Hold the method from the Abi

// The last arguments in args can be an options object
Expand Down Expand Up @@ -101,6 +103,26 @@ export const makeContract = memoizee(
});

return result;
},
{ length: 1 } // Only memoize by address
}
);

/**
* Create a contract.
*
* @param address - The contract address.
* @param abiJson - The contract abi.
* @param options - The options to pass in when creating the contract.
* @return - An object whose keys are all the functions of the
* contract, and each function return an Observable which will fire when the
* function resolves.
*/
export const makeContract = (
address: Address,
abiJson: any[],
options: { provider?: any } = {}
) => {
const { provider } = options;
const api = provider ? createApiFromProvider(provider) : getApi();

return makeContractWithApi(address, abiJson, api);
};
3 changes: 1 addition & 2 deletions packages/light.js/src/rpc/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
// SPDX-License-Identifier: MIT

import * as eth from './eth';
import { memoizeAll } from '../utils/memoizeAll';
import * as net from './net';
import { post$ } from './other';
import * as parity from './parity';

const rpc = { ...eth, ...net, ...parity, post$ };

export default memoizeAll(rpc);
export default rpc;