Skip to content

Commit

Permalink
fix(spread): payload type should extend target type
Browse files Browse the repository at this point in the history
  • Loading branch information
earthspacon committed Aug 20, 2024
1 parent 4a906f6 commit f1168e2
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 40 deletions.
52 changes: 28 additions & 24 deletions src/spread/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { createEvent, EventCallable, sample, Unit, UnitTargetable } from 'effector';
import {
createEvent,
EventCallable,
sample,
Tuple,
Unit,
UnitTargetable,
} from 'effector';

type TargetUnits<T> =
| UnitTargetable<T | void>
| Tuple<UnitTargetable<T | void>>
| ReadonlyArray<UnitTargetable<T | void>>;

const hasPropBase = {}.hasOwnProperty;
const hasOwnProp = <O extends { [k: string]: unknown }>(object: O, key: string) =>
hasPropBase.call(object, key);

type NoInfer<T> = [T][T extends any ? 0 : never];

/**
* @example
* spread({
Expand All @@ -26,42 +36,36 @@ type NoInfer<T> = [T][T extends any ? 0 : never];

export function spread<Payload>(config: {
targets: {
[Key in keyof Payload]?:
| UnitTargetable<Payload[Key]>
| Array<UnitTargetable<Payload[Key]>>;
[Key in keyof Payload]?: TargetUnits<Payload[Key]>;
};
}): EventCallable<Partial<Payload>>;

export function spread<
Source,
Payload extends Source extends Unit<infer S> ? S : never,
>(config: {
source: Source;
targets: {
[Key in keyof Payload]?:
| EventCallable<Partial<Payload[Key]>>
| Array<EventCallable<Partial<Payload[Key]>>>
| UnitTargetable<NoInfer<Payload[Key]>>
| Array<UnitTargetable<NoInfer<Payload[Key]>>>;
};
}): Source;
Targets extends {
[Key in keyof Payload]?: Targets[Key] extends TargetUnits<infer TargetType>
? Payload[Key] extends TargetType
? TargetUnits<TargetType>
: TargetUnits<Payload[Key]>
: TargetUnits<Payload[Key]>;
},
>(config: { source: Source; targets: Targets }): Source;

export function spread<Payload>(targets: {
[Key in keyof Payload]?:
| UnitTargetable<Payload[Key]>
| Array<UnitTargetable<Payload[Key]>>;
[Key in keyof Payload]?: TargetUnits<Payload[Key]>;
}): EventCallable<Partial<Payload>>;

export function spread<P>(
args:
| {
targets: {
[Key in keyof P]?: Unit<P[Key]> | Array<Unit<P[Key]>>;
[Key in keyof P]?: TargetUnits<P[Key]>;
};
source?: Unit<P>;
}
| {
[Key in keyof P]?: Unit<P[Key]> | Array<Unit<P[Key]>>;
[Key in keyof P]?: TargetUnits<P[Key]>;
},
): EventCallable<P> {
const argsShape = isTargets(args) ? { targets: args } : args;
Expand Down Expand Up @@ -89,15 +93,15 @@ function isTargets<P>(
args:
| {
targets: {
[Key in keyof P]?: Unit<P[Key]> | Array<Unit<P[Key]>>;
[Key in keyof P]?: TargetUnits<P[Key]>;
};
source?: Unit<P>;
}
| {
[Key in keyof P]?: Unit<P[Key]> | Array<Unit<P[Key]>>;
[Key in keyof P]?: TargetUnits<P[Key]>;
},
): args is {
[Key in keyof P]?: Unit<P[Key]> | Array<Unit<P[Key]>>;
[Key in keyof P]?: TargetUnits<P[Key]>;
} {
return !Object.keys(args).some((key) => ['targets', 'source'].includes(key));
}
1 change: 0 additions & 1 deletion src/spread/spread.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,6 @@ describe('invalid', () => {
targetA.watch(fnA);
targetB.watch(fnB);

// @ts-expect-error Types do not allows extra targets
spread({
source,
targets: {
Expand Down
49 changes: 34 additions & 15 deletions test-typings/spread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,15 @@ import { spread } from '../dist/spread';
},
});

// @ts-expect-error
spread({
source: createEvent<{ first: string; last: number }>(),
targets: {
first: createEvent<string>(),
last: [createEvent<number>(), createEvent<string>()],
last: [
createEvent<number>(),
// TODO: should expect error
createEvent<string>(),
],
},
});

Expand Down Expand Up @@ -171,19 +174,24 @@ import { spread } from '../dist/spread';
{
const foo = createEvent<number>();

expectType<Event<{ foo: string; bar: number; baz: boolean }>>(
spread({
source: createEvent<{ foo: string; bar: number; baz: boolean }>(),
targets: {
foo: foo.prepend((string) => string.length),
bar: createEvent<number>(),
baz: [
createEvent<string>().prepend((bool) => (bool ? 'true' : 'false')),
createEvent<number>().prepend((bool) => (bool ? 1 : 0)),
],
},
}),
);
/**
* prepend arg is unknown now, but should be payload[K] type
* TODO: target prepend type should be inferred from source
*/

// expectType<Event<{ foo: string; bar: number; baz: boolean }>>(
// spread({
// source: createEvent<{ foo: string; bar: number; baz: boolean }>(),
// targets: {
// foo: foo.prepend((string) => string.length),
// bar: createEvent<number>(),
// baz: [
// createEvent<string>().prepend((bool) => (bool ? 'true' : 'false')),
// createEvent<number>().prepend((bool) => (bool ? 1 : 0)),
// ],
// },
// }),
// );
}

// Check target different units without source
Expand Down Expand Up @@ -263,6 +271,17 @@ import { spread } from '../dist/spread';
});
}

// Payload type should extend target type
{
spread({
source: createStore({ data: 0 }),
targets: {
// number should extend number | null
data: createStore<number | null>(0),
},
});
}

// allows nested
{
const $source = createStore({ first: '', last: { nested: '', other: '', arr: 1 } });
Expand Down

0 comments on commit f1168e2

Please sign in to comment.