Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add id to state wires #61

Merged
merged 1 commit into from
Feb 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 30 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ declare interface FnsWireGuard<Fns> {
' fns-wire': StrictMethodsGuard<Fns>;
}

export declare function getLinkIds(wire: undefined): undefined;

export declare function getLinkIds(wire: WireId): LinkIds;

export declare function getLinkIds(
wire: WireId | undefined,
): LinkIds | undefined;

export declare function getWireId(wire: undefined): undefined;

export declare function getWireId(wire: WireId): string;

export declare function getWireId(wire: WireId | undefined): string | undefined;

declare type GetWireValue = <V>(wire: ReadonlyStateWire<V>) => V;

declare type InitializerOrValue<V> = V | (() => V);
Expand All @@ -61,6 +75,8 @@ declare type IsNever<T> = [T] extends [never] ? true : false;

declare type KeyOfMethods<T> = NonNever<keyof Methods<T>>;

export declare type LinkIds = Array<string | LinkIds>;

declare type MethodKeys<T> = {
[P in keyof T]: T[P] extends Function ? P : never;
}[keyof T];
Expand All @@ -74,7 +90,8 @@ export declare interface ReadOnlySelectorOptions<V> {
}

export declare interface ReadonlyStateWire<V>
extends ReadonlyStateWireGuard<V> {
extends ReadonlyStateWireGuard<V>,
WireId {
/**
* get current value
* @returns current value
Expand All @@ -97,7 +114,7 @@ export declare type ReadonlyWire<V, Fns = {}> = ReadonlyStateWire<V> &

declare type SetWireValue = <V>(wire: StateWire<V>, value: Defined<V>) => void;

export declare interface StateWire<V> extends StateWireGuard<V> {
export declare interface StateWire<V> extends StateWireGuard<V>, WireId {
/**
* get current value
* @returns current value
Expand Down Expand Up @@ -273,6 +290,17 @@ export declare type WireFns<W extends FnsWire<any>> = W extends FnsWire<
? Fns
: never;

export declare interface WireId {
/**
* @internal
*/
_getId(): string;
/**
* @internal
*/
_getLinkIds(): LinkIds;
}

export declare type WireState<
W extends ReadonlyStateWire<any>
> = W extends ReadonlyStateWire<infer V> ? V : never;
Expand Down
4 changes: 4 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
createSelector,
createWire,
getLinkIds,
getWireId,
isDefined,
useFn,
useInterceptor,
Expand All @@ -23,5 +25,7 @@ describe('index file', () => {
expect(useSelector).toBeDefined();
expect(useSubscribe).toBeDefined();
expect(isDefined).toBeDefined();
expect(getLinkIds).toBeDefined();
expect(getWireId).toBeDefined();
});
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export { useSelector } from './selector/use-selector';
export { useSubscribe } from './state-wire/use-subscribe';

export { isDefined } from './utils/type-utils';
export { getLinkIds, getWireId } from './utils/wire-id';
23 changes: 23 additions & 0 deletions src/state-selector/create-state-selector.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createStateWire } from '../state-wire/create-state-wire';
import { getLinkIds, getWireId } from '../utils/wire-id';
import { createStateSelector } from './create-state-selector';

const createDoubleValueSelector = () => {
Expand Down Expand Up @@ -323,4 +324,26 @@ describe('selector', () => {
expect(selector.getValue()).toBe(12);
});
});
describe('ids', function () {
it('should have same id', function () {
const [stateSelector] = createStateSelector({ get: () => 1 });
const id = getWireId(stateSelector);
expect(getWireId(stateSelector)).toBe(id);
});
it('should be last item of link ids', function () {
const [stateSelector] = createStateSelector({ get: () => 1 });
const id = getWireId(stateSelector);
const ids = getLinkIds(stateSelector);
expect(getLinkIds(stateSelector)).toBe(ids);
expect(getLinkIds(stateSelector)).toEqual([id]);
});
it('should be have up links', function () {
const { selector, wire } = createDoubleValueSelector();
const wireId = getWireId(wire);
const selectorId = getWireId(selector);
const ids = getLinkIds(selector);
expect(getLinkIds(selector)).toBe(ids);
expect(getLinkIds(selector)).toEqual([[wireId], selectorId]);
});
});
});
34 changes: 32 additions & 2 deletions src/state-selector/create-state-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
} from '../state-wire/readonly-state-wire';
import { createStateWireGuard } from '../state-wire/state-wire';
import { StateWire } from '../types';
import { memoize } from '../utils/memoize';
import { Defined } from '../utils/type-utils';
import { createId } from '../utils/wire-id';

type ReconnectFunction<O> = (options?: O) => void;
type ConnectFunction<O> = () => ReconnectFunction<O>;
Expand Down Expand Up @@ -146,9 +148,37 @@ export function createStateSelector<V>(
};
};

const id = createId('s-');
const _getId = () => id;

// it should memoize for each selector instance, do not move to top level
const memoizedGetLinkIds = memoize((wires: UnsubscribeMap) => {
return [
...Array.from(wires.keys()).map((wire) => wire._getLinkIds()),
_getId(),
];
});

const _getLinkIds = () => {
return memoizedGetLinkIds(activeWires.current);
};

const selectorContext: ReadonlyStateWire<V> | StateWire<V> = setOption
? { getValue, subscribe, setValue, ...createStateWireGuard() }
: { getValue, subscribe, ...createReadonlyStateWireGuard() };
? {
getValue,
subscribe,
setValue,
...createStateWireGuard(),
_getId,
_getLinkIds,
}
: {
getValue,
subscribe,
...createReadonlyStateWireGuard(),
_getId,
_getLinkIds,
};

return [selectorContext, connect];
}
35 changes: 35 additions & 0 deletions src/state-wire/create-state-wire.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { getLinkIds, getWireId } from '../utils/wire-id';
import { createStateWire } from './create-state-wire';

describe('create state wire', function () {
it('should create state wire', function () {
const stateWire = createStateWire({}, 1);
expect(stateWire).toBeDefined();
});
it('should have same id', function () {
const [stateWire] = createStateWire({}, 1);
const id = getWireId(stateWire);
expect(getWireId(stateWire)).toBe(id);
});
it('should be last item of link ids', function () {
const [stateWire] = createStateWire({}, 1);
const id = getWireId(stateWire);
expect(getLinkIds(stateWire)).toEqual([id]);
});
it('should have uplink id in link ids', function () {
const [upLink] = createStateWire({}, 1);
const [stateWire] = createStateWire(upLink, 1);
const uplinkId = getWireId(upLink);
const id = getWireId(stateWire);
expect(getLinkIds(stateWire)).toEqual([uplinkId, id]);
});
it('should have uplink id in link ids', function () {
const [upLink] = createStateWire({}, 1);
const [stateWire] = createStateWire(upLink, 1);
const uplinkId = getWireId(upLink);
const id = getWireId(stateWire);
const ids = getLinkIds(stateWire);
expect(getLinkIds(stateWire)).toBe(ids);
expect(getLinkIds(stateWire)).toEqual([uplinkId, id]);
});
});
17 changes: 16 additions & 1 deletion src/state-wire/create-state-wire.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import mitt, { Emitter } from 'mitt';
import { memoize } from '../utils/memoize';
import { Defined, isDefined } from '../utils/type-utils';
import { createId } from '../utils/wire-id';
import {
createStateWireGuard,
isWritableStateWire,
Expand Down Expand Up @@ -79,5 +81,18 @@ export function createStateWire<V>(
subscribe,
};

return [{ ...stateContext, ...createStateWireGuard() }, connect];
const id = createId('w-');
const _getId = () => id;
// it should memoize for each wire instance, do not move to top level
const memoizedGetLinkIds = memoize(
(upLink: StateWire<V | undefined> | null) => {
return upLink ? [...upLink._getLinkIds(), _getId()] : [_getId()];
},
);
const _getLinkIds = () => memoizedGetLinkIds(upLink);

return [
{ ...stateContext, ...createStateWireGuard(), _getId, _getLinkIds },
connect,
];
}
5 changes: 4 additions & 1 deletion src/state-wire/readonly-state-wire.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { CovarianceGuard, Defined } from '../utils/type-utils';
import { WireId } from '../utils/wire-id';

export interface ReadonlyStateWireGuard<V> {
' state-wire': CovarianceGuard<V>;
}

export interface ReadonlyStateWire<V> extends ReadonlyStateWireGuard<V> {
export interface ReadonlyStateWire<V>
extends ReadonlyStateWireGuard<V>,
WireId {
/**
* get current value
* @returns current value
Expand Down
3 changes: 2 additions & 1 deletion src/state-wire/state-wire.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Defined, StrictGuard } from '../utils/type-utils';
import { WireId } from '../utils/wire-id';

export interface StateWireGuard<V> {
' state-wire': StrictGuard<V>;
}

export interface StateWire<V> extends StateWireGuard<V> {
export interface StateWire<V> extends StateWireGuard<V>, WireId {
/**
* get current value
* @returns current value
Expand Down
1 change: 1 addition & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export {
} from './state-selector/create-state-selector';

export type { Defined } from './utils/type-utils';
export type { WireId, LinkIds } from './utils/wire-id';
43 changes: 43 additions & 0 deletions src/utils/memoize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { memoize } from './memoize';

describe('memoize', function () {
it('should return a function', function () {
expect(typeof memoize(() => {})).toBe('function');
});
it('should remember last call', function () {
const fn = jest.fn();
const memoized = memoize(fn);
memoized();
memoized();
expect(fn).toHaveBeenCalledTimes(1);
});
it('should remember last call with arguments', function () {
const fn = jest.fn();
const memoized = memoize(fn);
memoized(1, 2, 3);
memoized(1, 2, 3);
expect(fn).toHaveBeenCalledTimes(1);
});
it('should remember last call with different arguments', function () {
const fn = jest.fn();
const memoized = memoize(fn);
memoized(1, 2, 3);
memoized(4, 5, 6);
expect(fn).toHaveBeenCalledTimes(2);
});
it('should forget previous calls with different arguments', function () {
const fn = jest.fn();
const memoized = memoize(fn);
memoized(1, 2, 3);
memoized(4, 5, 6);
memoized(1, 2, 3);
expect(fn).toHaveBeenCalledTimes(3);
});
it('should returns memoized value', function () {
const fn = jest.fn(() => []);
const memoized = memoize(fn);
const result1 = memoized();
const result2 = memoized();
expect(result1).toBe(result2);
});
});
16 changes: 16 additions & 0 deletions src/utils/memoize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function memoize<F extends Function>(fn: F): F {
let lastArgs: any[] | undefined = undefined;
let lastResult: unknown = undefined;
return function (...args: any[]): any {
if (
lastArgs !== undefined &&
lastArgs.length === args.length &&
lastArgs.every((arg, i) => arg === args[i])
) {
return lastResult;
}
lastArgs = args;
lastResult = fn(...args);
return lastResult;
} as any;
}
41 changes: 41 additions & 0 deletions src/utils/wire-id.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*eslint @typescript-eslint/no-unused-vars: ["warn", { "varsIgnorePattern": "^_" }]*/
import { createId, getLinkIds, getWireId, LinkIds, WireId } from './wire-id';

describe('create id', function () {
it('should create a valid id', function () {
const id = createId();
expect(id).toBeDefined();
expect(id.length).toBeGreaterThan(0);
});
it('should accepts prefix', function () {
const id = createId('prefix');
expect(id).toBeDefined();
expect(id.length).toBeGreaterThan(0);
expect(id.startsWith('prefix')).toBeTruthy();
});
});

describe('get wire id', function () {
it('should returns undefined for undefined', function () {
expect(getWireId(undefined)).toBeUndefined();
});
it('have correct type', function () {
function _f0(w0: undefined, w1: WireId, w2: WireId | undefined) {
let _r0: undefined = getWireId(w0);
let _r1: string = getWireId(w1);
let _r2: string | undefined = getWireId(w2);
}
});
});
describe('get link ids', function () {
it('should returns undefined for undefined', function () {
expect(getLinkIds(undefined)).toBeUndefined();
});
it('have correct type', function () {
function _f0(w0: undefined, w1: WireId, w2: WireId | undefined) {
let _r0: undefined = getLinkIds(w0);
let _r1: LinkIds = getLinkIds(w1);
let _r2: LinkIds | undefined = getLinkIds(w2);
}
});
});
Loading