Skip to content

Commit

Permalink
Merge pull request #278 from kireevmp/feat/snapshot-allow-other-units
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeysova authored Mar 13, 2023
2 parents 84e5db4 + cd683cb commit 0a36142
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 10 deletions.
12 changes: 8 additions & 4 deletions src/snapshot/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { createStore, Event, sample, Store } from 'effector';
import { createStore, Effect, sample, Store, Unit, Event } from 'effector';

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

export function snapshot<SourceType, TargetType = SourceType>({
source,
clock,
fn = (value: SourceType) => value as unknown as TargetType,
}: {
source: Store<SourceType>;
clock?: Event<any>;
clock?: Event<any> | Effect<any, any, any> | Store<any>;
fn?(value: SourceType): TargetType;
}): Store<TargetType> {
}): Store<NoInfer<TargetType>> {
const defaultValue = fn(source.defaultState);
const onSnapshot = clock ? sample({ source, clock, fn }) : sample({ source, fn });
const onSnapshot = clock
? sample({ source, clock: clock as Unit<any>, fn })
: sample({ source, fn });
const $snapshot = createStore(defaultValue);

$snapshot.on(onSnapshot, (_, value) => value);
Expand Down
6 changes: 3 additions & 3 deletions src/snapshot/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { snapshot } from 'patronum/snapshot';

### Motivation

This method allows to copy any store on optional trigger event.
This method allows to copy any store on optional trigger unit.
It useful when you want to save previous state of store before some actions.

### Formulae
Expand All @@ -24,12 +24,12 @@ result = snapshot({ source, clock, fn });
```

- Call `fn` with data from `source` while `clock` triggered, and create store with the value
- If function in `shape` returns `undefined`, the update will be skipped.
- If `fn` returns `undefined`, the update will be skipped.

### Arguments

1. `source` ([_`Store`_]) — Source store, data from this unit passed to `fn`
2. `clock` ([_`Event`_]) — Trigger event
2. `clock` ([_`Event`_], [_`Effect`_], [_`Store`_]) — Trigger unit
3. `fn` `((value: T) => U)` — Transformation function

### Returns
Expand Down
35 changes: 34 additions & 1 deletion src/snapshot/snapshot.fork.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createDomain, allSettled, fork } from 'effector';
import { createDomain, allSettled, fork, createEvent, createStore } from 'effector';
import { snapshot } from './index';

test('works in forked scope', async () => {
Expand Down Expand Up @@ -74,3 +74,36 @@ test('does not affect original store state', async () => {
expect(scope.getState($copy)).toBe(2);
expect($copy.getState()).toBe(1);
});

test('store clock from one scope does not affect another', async () => {
const app = createDomain();

const updateOriginal = createEvent<string>({ domain: app });
const updateTrigger = createEvent<void>({ domain: app });

const $original = createStore('first', { domain: app }).on(
updateOriginal,
(_, newValue) => newValue,
);

const $trigger = createStore(0, { domain: app }).on(
updateTrigger,
(state) => state + 1,
);

const $copy = snapshot({ source: $original, clock: $trigger });

const scope1 = fork(app);
const scope2 = fork(app);

expect(scope1.getState($copy)).toBe('first');
expect(scope2.getState($copy)).toBe('first');

await allSettled(updateOriginal, { scope: scope1, params: 'second' });
await allSettled(updateOriginal, { scope: scope2, params: 'second' });

await allSettled(updateTrigger, { scope: scope1 });

expect(scope1.getState($copy)).toBe('second');
expect(scope2.getState($copy)).toBe('first');
});
39 changes: 38 additions & 1 deletion src/snapshot/snapshot.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createStore, createEvent } from 'effector';
import { createStore, createEvent, createEffect } from 'effector';
import { snapshot } from './index';

test('snapshot copy original store', () => {
Expand Down Expand Up @@ -78,3 +78,40 @@ test('snapshot copy original store with transformation on trigger', () => {

expect($copy.getState()).toBe(6);
});

test('snapshot supports effect as a clock', () => {
const updateOriginal = createEvent<string>();

const trigger = createEffect(() => undefined);

const $original = createStore('first').on(
updateOriginal,
(_, newValue) => newValue,
);
const $copy = snapshot({ source: $original, clock: trigger });

updateOriginal('second');
expect($copy.getState()).toBe('first');

trigger();
expect($copy.getState()).toBe('second');
});

test('snapshot supports store as a clock', () => {
const updateOriginal = createEvent<string>();
const updateTrigger = createEvent<void>();

const $trigger = createStore<number>(0).on(updateTrigger, (state) => state + 1);

const $original = createStore('first').on(
updateOriginal,
(_, newValue) => newValue,
);
const $copy = snapshot({ source: $original, clock: $trigger });

updateOriginal('second');
expect($copy.getState()).toBe('first');

updateTrigger();
expect($copy.getState()).toBe('second');
});
28 changes: 27 additions & 1 deletion test-typings/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { snapshot } from '../src/snapshot';
expectType<Store<string>>(snapshot({ source: $a }));

const $b = createStore<'a' | 'b'>('a');
expectType<Store<number>>(snapshot({ source: $b }));
expectType<Store<'a' | 'b'>>(snapshot({ source: $b }));
}

// Check invalid type for Clock
Expand Down Expand Up @@ -54,3 +54,29 @@ import { snapshot } from '../src/snapshot';
// @ts-expect-error
snapshot({ source: $a, fn: b });
}

// Check TargetType is not inferred from return value
{
const $a = createStore(0);

// @ts-expect-error
expectType<Store<string>>(snapshot({ source: $a }));
}

// Check effect is supported as a clock
{
const $a = createStore(0);

const clock = createEffect();

expectType<Store<number>>(snapshot({ source: $a, clock }));
}

// Check store is supported as a clock
{
const $a = createStore(0);

const $clock = createStore(0);

expectType<Store<number>>(snapshot({ source: $a, clock: $clock }));
}

1 comment on commit 0a36142

@github-actions
Copy link

Choose a reason for hiding this comment

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

🚛 size-compare report

Comparing 84e5db41...0a361422

File +/- Base Current +/- gzip Base gzip Current gzip
dist/and/index.cjs = 397 B 397 B = 251 B 251 B
dist/and/index.js = 413 B 413 B = 245 B 245 B
dist/babel-preset.cjs = 434 B 434 B = 249 B 249 B
dist/combine-events/index.cjs = 2.46 kB 2.46 kB = 840 B 840 B
dist/combine-events/index.js = 3.95 kB 3.95 kB = 1.35 kB 1.35 kB
dist/condition/index.cjs = 1.37 kB 1.37 kB = 505 B 505 B
dist/condition/index.js = 1.29 kB 1.29 kB = 461 B 461 B
dist/debounce/index.cjs = 3.99 kB 3.99 kB = 1.31 kB 1.31 kB
dist/debounce/index.js = 3.84 kB 3.84 kB = 1.29 kB 1.29 kB
dist/debug/index.cjs = 11.3 kB 11.3 kB = 3.16 kB 3.16 kB
dist/debug/index.js = 13.3 kB 13.3 kB = 3.79 kB 3.79 kB
dist/delay/index.cjs = 1.76 kB 1.76 kB = 708 B 708 B
dist/delay/index.js = 1.81 kB 1.81 kB = 702 B 702 B
dist/either/index.cjs = 600 B 600 B = 334 B 334 B
dist/either/index.js = 477 B 477 B = 270 B 270 B
dist/empty/index.cjs = 175 B 175 B = 151 B 151 B
dist/empty/index.js = 79 B 79 B = 89 B 89 B
dist/equals/index.cjs = 336 B 336 B = 249 B 249 B
dist/equals/index.js = 221 B 221 B = 179 B 179 B
dist/every/index.cjs = 1.22 kB 1.22 kB = 513 B 513 B
dist/every/index.js = 1.08 kB 1.08 kB = 442 B 442 B
dist/format/index.cjs = 642 B 642 B = 366 B 366 B
dist/format/index.js = 687 B 687 B = 368 B 368 B
dist/in-flight/index.cjs = 641 B 641 B = 357 B 357 B
dist/in-flight/index.js = 546 B 546 B = 305 B 305 B
dist/index.cjs = 1.57 kB 1.57 kB = 359 B 359 B
dist/index.js = 1.09 kB 1.09 kB = 279 B 279 B
dist/interval/index.cjs = 3.88 kB 3.88 kB = 1.14 kB 1.14 kB
dist/interval/index.js = 3.74 kB 3.74 kB = 1.12 kB 1.12 kB
dist/macro.cjs = 1.91 kB 1.91 kB = 808 B 808 B
dist/not/index.cjs = 161 B 161 B = 148 B 148 B
dist/not/index.js = 69 B 69 B = 81 B 81 B
dist/or/index.cjs = 393 B 393 B = 249 B 249 B
dist/or/index.js = 411 B 411 B = 245 B 245 B
dist/patronum.cjs = 18.8 kB 18.8 kB +0.02% 5.96 kB 5.96 kB
dist/patronum.js = 17.8 kB 17.8 kB +0.02% 6.04 kB 6.04 kB
dist/patronum.umd.js = 19.9 kB 19.9 kB +0.02% 6.07 kB 6.07 kB
dist/pending/index.cjs = 909 B 909 B = 495 B 495 B
dist/pending/index.js = 828 B 828 B = 444 B 444 B
dist/reset/index.cjs = 526 B 526 B = 312 B 312 B
dist/reset/index.js = 439 B 439 B = 256 B 256 B
dist/reshape/index.cjs = 419 B 419 B = 242 B 242 B
dist/reshape/index.js = 379 B 379 B = 201 B 201 B
dist/snapshot/index.cjs +0.93% 756 B 763 B +1.16% 346 B 350 B
dist/snapshot/index.js +1.09% 641 B 648 B +1.39% 288 B 292 B
dist/some/index.cjs = 1.16 kB 1.16 kB = 474 B 474 B
dist/some/index.js = 1.02 kB 1.02 kB = 407 B 407 B
dist/split-map/index.cjs = 628 B 628 B = 359 B 359 B
dist/split-map/index.js = 575 B 575 B = 318 B 318 B
dist/spread/index.cjs = 1.26 kB 1.26 kB = 534 B 534 B
dist/spread/index.js = 1.28 kB 1.28 kB = 516 B 516 B
dist/status/index.cjs = 426 B 426 B = 265 B 265 B
dist/status/index.js = 339 B 339 B = 208 B 208 B
dist/throttle/index.cjs = 2.1 kB 2.1 kB = 814 B 814 B
dist/throttle/index.js = 1.99 kB 1.99 kB = 775 B 775 B
dist/time/index.cjs = 719 B 719 B = 376 B 376 B
dist/time/index.js = 621 B 621 B = 323 B 323 B

Please sign in to comment.