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

Add timers api #312

Closed
wants to merge 6 commits into from
Closed
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
6 changes: 3 additions & 3 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const input = 'dist/index.cjs';
export default [
{
input: 'dist/index.js',
external: ['effector'],
external: ['effector', 'timers'],
plugins,
output: {
file: './dist/patronum.js',
Expand All @@ -25,7 +25,7 @@ export default [
},
{
input,
external: ['effector'],
external: ['effector', 'timers'],
plugins,
output: {
file: './dist/patronum.cjs',
Expand All @@ -38,7 +38,7 @@ export default [
},
{
input,
external: ['effector'],
external: ['effector', 'timers'],
plugins,
output: {
name: 'patronum',
Expand Down
4 changes: 3 additions & 1 deletion scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,14 @@ async function main() {
),
);


await directory.write('index.cjs', createCommonJsIndex(names));
await directory.write('index.js', createMjsIndex(names));
await directory.write('index.d.ts', createTypingsIndex(names));
await directory.write('macro.d.ts', 'export * from "./index";');

const productionMethods = names.filter((method) => method !== 'debug');
const productionMethods = names.filter((method) => method !== 'debug' && method !== 'testing-library');

const factoriesJson = createFactoriesJson(library, productionMethods);
const fileName = 'babel-plugin-factories.json';
await writeFile(`./src/${fileName}`, JSON.stringify(factoriesJson, null, 2));
Expand Down
19 changes: 14 additions & 5 deletions scripts/libraries.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,28 @@ function resolveMethods() {
return names;
}

function resolveExportName(name) {
switch (name) {
case 'timers':
return '$timers';
case 'testing-library':
return 'setupTimers';
default:
return camelCase(name);
}
}

function createCommonJsIndex(names) {
const imports = names.sort().map((name) => {
const camel = camelCase(name);
return `module.exports.${camel} = require('./${name}/index.cjs').${camel};`;
return `module.exports.${resolveExportName(name)} = require('./${name}/index.cjs').${resolveExportName(name)};`;
});

return imports.join('\n') + '\n';
}

function createMjsIndex(names) {
const imports = names.sort().map((name) => {
const camel = camelCase(name);
return `export { ${camel} } from './${name}/index.js'`;
return `export { ${resolveExportName(name)} } from './${name}/index.js'`;
});

return imports.join('\n') + '\n';
Expand All @@ -41,7 +50,7 @@ function createMjsIndex(names) {
function createTypingsIndex(names) {
const types = names
.sort()
.map((name) => `export { ${camelCase(name)} } from './${name}';`);
.map((name) => `export { ${resolveExportName(name)} } from './${name}';`);

return types.join('\n') + '\n';
}
Expand Down
8 changes: 6 additions & 2 deletions src/babel-plugin-factories.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"patronum/in-flight",
"patronum/interval",
"patronum/not",
"patronum/now",
"patronum/once",
"patronum/or",
"patronum/pending",
Expand All @@ -26,7 +27,8 @@
"patronum/spread",
"patronum/status",
"patronum/throttle",
"patronum/time"
"patronum/time",
"patronum/timers"
],
"mapping": {
"and": "and",
Expand All @@ -42,6 +44,7 @@
"inFlight": "in-flight",
"interval": "interval",
"not": "not",
"now": "now",
"once": "once",
"or": "or",
"pending": "pending",
Expand All @@ -54,6 +57,7 @@
"spread": "spread",
"status": "status",
"throttle": "throttle",
"time": "time"
"time": "time",
"timers": "timers"
}
}
12 changes: 7 additions & 5 deletions src/delay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
UnitValue,
UnitTargetable,
} from 'effector';
import { $timers, Timers } from '../timers'

type TimeoutType<Payload> = ((payload: Payload) => number) | Store<number> | number;

Expand Down Expand Up @@ -58,24 +59,25 @@ export function delay<
const ms = validateTimeout(timeout);

const timerFx = createEffect<
{ payload: UnitValue<Source>; milliseconds: number },
{ payload: UnitValue<Source>; milliseconds: number; timers: Timers },
UnitValue<Source>
>(
({ payload, milliseconds }) =>
({ payload, milliseconds, timers }) =>
new Promise((resolve) => {
setTimeout(resolve, milliseconds, payload);
timers.setTimeout(resolve, milliseconds, payload);
}),
);

sample({
// ms can be Store<number> | number
// converts object of stores or object of values to store
source: combine({ milliseconds: ms }),
source: combine({ milliseconds: ms, timers: $timers }),
clock: source as Unit<any>,
fn: ({ milliseconds }, payload) => ({
fn: ({ milliseconds, timers }, payload) => ({
payload,
milliseconds:
typeof milliseconds === 'function' ? milliseconds(payload) : milliseconds,
timers,
}),
target: timerFx,
});
Expand Down
6 changes: 6 additions & 0 deletions src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ All methods split into categories.
- [interval](./interval/readme.md) — Creates a dynamic interval with any timeout.
- [throttle](./throttle/readme.md) — Creates event which triggers at most once per timeout.
- [time](./time/readme.md) — Allows reading current timestamp by triggering clock.
- [now](./now/readme.md) — Allows reading current timestamp by triggering clock from [timers](testing-library/readme.md).

### Combination/Decomposition

Expand All @@ -48,3 +49,8 @@ All methods split into categories.
### Debug

- [debug](./debug/readme.md) — Log triggers of passed units.


### Testing

- [setupTimers](testing-library/readme.md) — Provide custom implementations of setTimeout/clearTimeout
25 changes: 25 additions & 0 deletions src/now/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
combine,
createStore,
sample,
Store,
Unit
} from 'effector'
import { $timers } from '../timers'

export function now({ clock }: { clock?: Unit<any> } = {}): Store<number> {
if (clock) {
const $counter = createStore(0);

sample({
clock,
source: $counter,
fn: (count) => count + 1,
target: $counter,
});

return combine({ timers: $timers, $counter }, ({ timers }) => timers.now());
}

return $timers.map((timers) => timers.now());
}
56 changes: 56 additions & 0 deletions src/now/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# now

```ts
import { now } from 'patronum';
// or
import { now } from 'patronum/now';
```

### Motivation

The method allow to read current time from
custom timers implementation just as an effector method.

Use this for easy testing of business logic based on time.

### Formulae

```ts
$now = now({ clock });
// or
$now = now();
```

- Initialize `$now` with `$timers.now`
- When `clock` is triggered, call `$timers.now` to update `$now` with results

### Arguments

1. `clock: Event<any> | Effect<any, any, any> | Store<any>` — The unit triggers time reading and updates `$now` store

### Returns

`$now: Store<number>` — Store contains unix timestamp snapshot, updates when `clock` triggers.

### Example

```ts
import { now } from 'patronum';
import { setupTimers } from 'patronum/testing-library'
import { allSettled, fork } from 'effector'

const tick = createEvent();
const $now = now({ clock: tick });

const scope = fork();

await allSettled(setupTimers, { scope, params: { now: () => 1000 } });

$now.watch((time) => console.log('time', time));
// => time 1000

await allSettled(setupTimers, { scope, params: { now: () => 2000 } });
await allSettled(tick, { scope });

// => time 2000
```
11 changes: 11 additions & 0 deletions src/testing-library/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createEvent, sample } from 'effector'
import { $timers, Timers } from '../timers'

export const setupTimers = createEvent<Partial<Timers>>();

sample({
clock: setupTimers,
source: $timers,
fn: (timers, overrides) => ({ ...timers, ...overrides }),
target: $timers,
});
62 changes: 62 additions & 0 deletions src/testing-library/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# setupTimers

```ts
import { setupTimers } from 'patronum/testing';
```

### Motivation

The event allow to manually set implementations of setTimeout/clearTimeout, which used in delay/interval/throttle/debounce

### Formulae

```ts
setupTimers({ setTimeout: () => {} })
```

- Reassign implementations of setTimeout/clearTimeout in current scope
- If doesn't called then delay/interval/throttle/debounce uses global setTimeout/clearTimeout

### Arguments

```typescript
Partial<{
setTimeout: (handler: (...args: any[]) => void, timeout?: number, ...args: any[]) => NodeJS.Timeout;
clearTimeout: (handle: NodeJS.Timeout) => void;
now: () => number;
}>
```

### Example

```ts
import { setupTimers, interval } from 'patronum';
import { createEvent, sample } from 'effector';

const fakeTimers = {
setTimeout: (handler, timeout) => setTimeout(handler, timeout * 2), // your fake implementation
clearTimeout: clearTimeout, // your fake implementation
now: () => Date.now() * 2 // your fake implementation
};

sample({
clock: appStarted,
fn: () => fakeTimers,
target: setupTimers,
});

// ...

const startInterval = createEvent();
const stopInterval = createEvent();
const tick = interval({ start: startInterval, stop: stopInterval, timeout: 400 });

tick.watch(() => console.log('Called after 800ms'));

const someUIEvent = createEvent();

sample({
clock: someUIEvent,
target: startInterval,
});
```
7 changes: 4 additions & 3 deletions src/throttle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Unit,
UnitTargetable,
} from 'effector';
import { $timers, Timers } from '../timers';

type EventAsReturnType<Payload> = any extends Payload ? Event<Payload> : never;

Expand Down Expand Up @@ -46,9 +47,9 @@ export function throttle<T>(

const $timeout = toStoreNumber(timeout);

const timerFx = createEffect<number, void>({
const timerFx = createEffect<{ timeout: number; timers: Timers }, void>({
name: `throttle(${(source as Event<T>).shortName || source.kind}) effect`,
handler: (timeout) => new Promise((resolve) => setTimeout(resolve, timeout)),
handler: ({ timeout, timers }) => new Promise((resolve) => timers.setTimeout(resolve, timeout)),
});

// It's ok - nothing will ever start unless source is triggered
Expand All @@ -70,7 +71,7 @@ export function throttle<T>(
});

sample({
source: $timeout,
source: { timeout: $timeout, timers: $timers },
clock: triggerTick as Unit<any>,
target: timerFx,
});
Expand Down
16 changes: 16 additions & 0 deletions src/timers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createStore } from 'effector'
import { clearTimeout } from 'timers'

type Handler = (...args: any[]) => void;

export interface Timers {
setTimeout(handler: Handler, timeout?: number, ...args: any[]): NodeJS.Timeout;
clearTimeout(handle: NodeJS.Timeout): void;
now(): number;
}

export const $timers = createStore<Timers>({
setTimeout: (handler, timeout, ...args) => setTimeout(handler, timeout, ...args) as unknown as NodeJS.Timeout,
clearTimeout: (handle) => clearTimeout(handle),
now: () => Date.now(),
});
Loading