Skip to content

Commit

Permalink
Snapshot method (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev authored Jun 22, 2021
1 parent 1cc58eb commit 2389fd5
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 21 deletions.
47 changes: 36 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- [Reshape](#reshape) — Destructure one store to different stores
- [SplitMap](#splitmap) — Split event to different events and map data.
- [Spread](#spread) — Send fields from object to same targets.
- [Snapshot](#snapshot) — Create store value snapshot.

### Debug

Expand Down Expand Up @@ -63,7 +64,7 @@ import { delay, inFlight } from 'patronum';
Just import from `patronum/macro`, and imports will be replaced to full qualified:

```ts
import { status, splitMap, combineEvents } from 'patronum/macro'
import { status, splitMap, combineEvents } from 'patronum/macro';
```

> Warning: babel-plugin-macros do not support `import * as name`!
Expand Down Expand Up @@ -192,9 +193,7 @@ import { createStore, createEvent, createEffect } from 'effector';
import { debug } from 'patronum/debug';

const event = createEvent();
const effect = createEffect().use((payload) =>
Promise.resolve('result' + payload),
);
const effect = createEffect().use((payload) => Promise.resolve('result' + payload));
const $store = createStore(0)
.on(event, (state, value) => state + value)
.on(effect.done, (state) => state * 10);
Expand Down Expand Up @@ -264,6 +263,37 @@ $second.watch(console.log); // => World

[Try it](https://share.effector.dev/DmiLrYAC)

## Snapshot

[Method documentation & API](/snapshot)

```ts
import { restore, createEvent } from 'effector';
import { snapshot } from 'patronum/snapshot';

const changeText = createEvent<string>();
const createSnapshot = createEvent();

const $original = restore(changeText, 'Example');

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

changeText('New text');

// $original -> Store with "New text"
// $snapshot -> Store with "Example"

createSnapshot();

// $original -> Store with "New text"
// $snapshot -> Store with "New text"
```

[Try it](https://share.effector.dev/HcsNyGfM)

## CombineEvents

[Method documentation & API](/combine-events)
Expand Down Expand Up @@ -442,12 +472,8 @@ const received = splitMap({
received.update.watch((payload) =>
console.info('update received with content:', payload),
);
received.created.watch((payload) =>
console.info('created with value:', payload),
);
received.__.watch((payload) =>
console.info('unknown action received:', payload),
);
received.created.watch((payload) => console.info('created with value:', payload));
received.__.watch((payload) => console.info('unknown action received:', payload));

serverActionReceived({ type: 'created', value: 1 });
// => created with value: 1
Expand All @@ -461,7 +487,6 @@ serverActionReceived({ type: 'another' });

[Try it](https://share.effector.dev/RRf57lK4)


# Development

## How to release
Expand Down
10 changes: 5 additions & 5 deletions every/every.fork.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ test('throttle do not affect another forks', async () => {

expect(serialize(scopeA)).toMatchInlineSnapshot(`
Object {
"-jtynwl": 0,
"-ks1woz": 0,
"5psybs": 2,
"y0d72u": 0,
}
`);
expect(serialize(scopeB)).toMatchInlineSnapshot(`
Object {
"-jtynwl": 0,
"-ks1woz": 0,
"5psybs": 200,
"y0d72u": 0,
}
Expand Down Expand Up @@ -105,9 +105,9 @@ test('throttle do not affect original store value', async () => {

expect(serialize(scope)).toMatchInlineSnapshot(`
Object {
"-640daw": 0,
"-x8qmko": 0,
"9ht6ne": 2,
"-hy4x5r": 0,
"-y6tvd2": 0,
"8jpxv0": 2,
}
`);

Expand Down
7 changes: 7 additions & 0 deletions snapshot/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Event, Store } from 'effector';

export function snapshot<SourceType, TargetType = SourceType>(_: {
source: Store<SourceType>;
clock?: Event<any>;
fn?(value: SourceType): TargetType;
}): Store<TargetType>;
9 changes: 9 additions & 0 deletions snapshot/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { restore, sample } = require('effector');

function snapshot({ source, clock, fn }) {
const defaultValue = fn ? fn(source.defaultState) : source.defaultState;

return restore(sample(source, clock, fn), defaultValue);
}

module.exports = { snapshot };
106 changes: 106 additions & 0 deletions snapshot/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Patronum/Snapshot

```ts
import { snapshot } from 'patronum/snapshot';
```

## `result = snapshot({ source, clock, fn })`

### Motivation

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

### Formulae

```ts
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.

### Arguments

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

### Returns

- `result` ([_`Store`_]) — Copied store

[_`event`_]: https://effector.dev/docs/api/effector/event
[_`effect`_]: https://effector.dev/docs/api/effector/effect
[_`store`_]: https://effector.dev/docs/api/effector/store

### Examples

#### Exact copy of store

```ts
import { createStore } from 'effector';
import { snapshot } from 'patronum/snapshot';

const $original = createStore<string>('Example');

const $copy = snapshot({ source: $original });
```

#### Exact copy on trigger

```ts
import { restore, createEvent } from 'effector';
import { snapshot } from 'patronum/snapshot';

const changeText = createEvent<string>();
const createSnapshot = createEvent();

const $original = restore(changeText, 'Example');

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

changeText('New text');

// $original -> Store with "New text"
// $snapshot -> Store with "Example"

createSnapshot();

// $original -> Store with "New text"
// $snapshot -> Store with "New text"
```

#### Copy on trigger with transformation

```ts
import { restore, createEvent } from 'effector';
import { snapshot } from 'patronum/snapshot';

const changeText = createEvent<string>();
const createSnapshot = createEvent();

const $original = restore(changeText, 'Example');

const $lengthSnapshot = snapshot({
source: $original,
clock: createSnapshot,
fn: (text) => text.length,
});

// $original -> Store with "Example"
// $lengthSnapshot -> Store with 7 (length of "Example")

changeText('New long text');

// $original -> Store with "New long text"
// $lengthSnapshot -> Store with 7 (length of "Example")

createSnapshot();

// $original -> Store with "New long text"
// $lengthSnapshot -> Store with 13 (length of "New long text")
```
76 changes: 76 additions & 0 deletions snapshot/snapshot.fork.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { createDomain, allSettled, fork } from 'effector';
import { snapshot } from './index';

test('works in forked scope', async () => {
const app = createDomain();

const changeValue = app.createEvent<number>();
const copy = app.createEvent();

const $original = app.createStore(1).on(changeValue, (_, newValue) => newValue);

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

const scope = fork(app);

expect(scope.getState($copy)).toBe(1);

await allSettled(changeValue, { scope, params: 2 });
await allSettled(copy, { scope });

expect(scope.getState($copy)).toBe(2);
});

test('does not affects another scope', async () => {
const app = createDomain();

const changeValue = app.createEvent<number>();
const copy = app.createEvent();

const $original = app.createStore(1).on(changeValue, (_, newValue) => newValue);

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

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

expect(scope1.getState($copy)).toBe(1);
expect(scope2.getState($copy)).toBe(1);

await allSettled(changeValue, { scope: scope1, params: 2 });
await allSettled(copy, { scope: scope1 });

expect(scope1.getState($copy)).toBe(2);
expect(scope2.getState($copy)).toBe(1);
});

test('does not affect original store state', async () => {
const app = createDomain();

const changeValue = app.createEvent<number>();
const copy = app.createEvent();

const $original = app.createStore(1).on(changeValue, (_, newValue) => newValue);

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

const scope = fork(app);

expect(scope.getState($copy)).toBe(1);
expect($copy.getState()).toBe(1);

await allSettled(changeValue, { scope, params: 2 });
await allSettled(copy, { scope });

expect(scope.getState($copy)).toBe(2);
expect($copy.getState()).toBe(1);
});
Loading

0 comments on commit 2389fd5

Please sign in to comment.