Giter Site home page Giter Site logo

effector / patronum Goto Github PK

View Code? Open in Web Editor NEW
293.0 16.0 40.0 5.62 MB

☄️ Effector operators library delivering modularity and convenience ✨

Home Page: https://patronum.effector.dev

License: MIT License

JavaScript 8.61% TypeScript 90.34% HTML 0.42% CSS 0.60% Shell 0.04%
effector patronum typescript debounce

patronum's Introduction

Effector Patronum logo

Conventional Commits Node.js CI Rate on Openbase LICENSE Stars Downloads

☄️ Effector operators library delivering modularity and convenience

💿 Install now

Please, review documentation for YOUR version of patronum not the latest. Find and open tag/release for your version and click on the tag vA.B.C to view repo and documentation for that version, or use "Switch branches/tags" selector.

npm install patronum
# or
yarn add patronum
# or
pnpm add patronum

Next just import methods from "patronum" and use it:

import { createEffect } from 'effector';
import { status } from 'patronum';

const userLoadFx = createEffect();
const $status = status({ effect: userLoadFx });

You can read more at documentation.

Migration guide

Patronum had 3 breaking changes: 1) from 0.14 to 0.100, 2) from 0.100 to 0.110, 3) from 0.110 to 1.0

We have migration guide.

Development

You can review CONTRIBUTING.md

Release process

  1. Check out the draft release.
  2. All PRs should have correct labels and useful titles. You can review available labels here.
  3. Update labels for PRs and titles, next manually run the release drafter action to regenerate the draft release.
  4. Review the new version and press "Publish"
  5. If required check "Create discussion for this release"

patronum's People

Contributors

aanation avatar ainursharaev avatar alexandrhoroshih avatar binjospookie avatar blednaya-luna avatar chshanovskiy avatar dadayada avatar dependabot[bot] avatar doasync avatar egoson avatar evgenyifedotov avatar igorkamyshev avatar ilyaagarkov avatar iposokhin avatar kelin2025 avatar kireevmp avatar kobzarvs avatar laiff avatar meff34 avatar minhir avatar mitapfer avatar ncor avatar sergey20x25 avatar sergeysova avatar spoki4 avatar victordidenko avatar yanlobat avatar yumauri avatar zarabotaet avatar zerobias avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

patronum's Issues

Add object form arguments for `delay`

const delayed = delay({
  source: unit,
  timeout: 100,
})

const delayed = delay({
  source: unit,
  timeout: (payload) => 100,
})

const delayed = delay({
  source: unit,
  timeout: $timeout,
})

Add `lastTwo` method to save last two values from store

function lastTwo<T>(c: { source: Store<T>, target: Unit<{ current: T, prev: T }> }) {
  const store = createStore({ current: c.source.getState(), prev: c.source.getState() })

  store.on(c.source, ({ current: prev }, current) => ({ current, prev }))

  forward({ from: store, to: c.target })
}

const source = createStore(1)
const update = createEvent()

source.on(update, (e) => e + 1)

const target = createEvent<{ current: number, prev: number }>()

target.watch(console.info)

lastTwo({ source, target })

update()
update()
update()

https://share.effector.dev/DyKYa9Wc

Add object form arguments for `splitMap`

Should support config normalization

const received = splitMap({
  source: nameReceived,
  shape: {
    firstName: (string) => string.split(' ')[0], // string | undefined
    lastName: (string) => string.split(' ')[1], // string | undefined
  },
});

Add `lastStates` method, that saves last states

const change = createEvent()
const $demo = restore(change, 0)
const $last = lastStates({ source: $demo, count: 3 })

$last.watch(console.info)

change(2)
change(50)
change(200)
change(5)

Console:

[0]
[2, 0]
[50, 2, 0]
[200, 50, 2]
[5, 200, 50]

Should be fixed:
https://share.effector.dev/HHVSrtqy


Option to prefill last states with initial state:

  • Can prefill have more readable name?
const change = createEvent()
const $demo = restore(change, 0)
const $last = lastStates({ source: $demo, count: 3, prefill: true })

$last.watch(console.info)

change(2)
change(50)
change(200)
change(5)

Console:

[0, 0, 0]
[2, 0, 0]
[50, 2, 0]
[200, 50, 2]
[5, 200, 50]

Option target to send last states to exist store:

  • Should it change .defaultState to prefilled, if prefill: true, or not?
  • If prefill: true update the target will trigger subscribers with new value, but what about initial trigger?
const app = createDomain()
const change = app.createEvent()
const $demo = restore(change, 0)

const $states = app.createStore([])
lastStates({ source: $demo, count: 3, target: $states })

$states.watch(console.info)

change(2)
change(50)
change(200)
change(5)

Console:

[0]
[2, 0]
[50, 2, 0]
[200, 50, 2]
[5, 200, 50]

Add `leading` and `trailing` for `throttle` like in lodash

Following this comment

To have more industry-like behaviour (lodash), throttle should provide options to indicate whether target should be triggered on the leading and/or trailing edge of the timeout.

leading [= true] (boolean): Specify triggering on the leading edge of the timeout
trailing [= true] (boolean): Specify triggering on the trailing edge of the timeout

If leading and trailing options are true, target is triggered on the trailing edge of the timeout only if the throttled source is triggered more than once during the timeout.

If timeout is 0 and leading is false, target triggering is deferred until to the next tick, similar to setTimeout with a timeout of 0.


const trigger = createEvent()

// by default `leading` is `true` and `trailing` is `true`
const throttled = throttle({ source: trigger, timeout: 100 })

trigger(1)

throttled should be triggered immediately, one time


const trigger = createEvent()

// by default `leading` is `true` and `trailing` is `true`
const throttled = throttle({ source: trigger, timeout: 100 })

trigger(1)
trigger(2)
trigger(3)

throttled should be triggered immediately with payload of 1 and second time after 100ms with payload of 3


const trigger = createEvent()
const throttled = throttle({ source: trigger, timeout: 100, leading: false })
trigger(1)

throttled should be triggered after 100ms, one time


const trigger = createEvent()
const throttled = throttle({ source: trigger, timeout: 100, leading: false })
trigger(1)
trigger(2)
trigger(3)

throttled should be triggered after 100ms, one time, with payload of 3 (just like current behaviour)


const trigger = createEvent()
const throttled = throttle({ source: trigger, timeout: 0, leading: false })
trigger(1)

throttled should be triggered on the next tick, one time (just like current behaviour)


const trigger = createEvent()
const throttled = throttle({ source: trigger, timeout: 100, trailing: false })

trigger(1)
await wait(75)
trigger(2)
await wait(75)
trigger(3)

throttled should be triggered immediately with payload of 1 and second time after 150ms with payload of 3


With combination leading: false, trailing: falsethrottled should not be triggered at all


Playground with lodash's throttle

Add `borrow` method to create inheritor of effect

  • should infer types from original fx without changing params
import { createEffect, attach, Effect } from 'effector'

function borrow<P, D, F>(p: { effect: Effect<P, D, F> }): Effect<P, D, F> {
    return attach({
        effect: p.effect,
        mapParams: (params: P) => params,
    })
}

const parent = createEffect<number, string>()

const local = borrow({ effect: parent })

open in playground

[status]: `Fail` generic type

Hello! Currently status is passing Param & Done generics to underlying effect. All my effects have a custom 'Fail' type, so typescript complains about Types of property 'fail' are incompatible.. It would be nice to be able to pass Fail type

Refactor README

Maybe:

  • Add logo
  • Add description/summary for each method (in TOC or in each section?)
  • Operator decision tree

Add support for `domain` in `pending`

import { createDomain } from 'effector';
import { pending } from 'patronum/pending';

const app = createDomain()
const loadFirst = app.createEffect().use(() => Promise.resolve(null));
const loadSecond = app.createEffect().use(() => Promise.resolve(2));
const $processing = pending({ domain: app });

$processing.watch((processing) => console.info(`processing: ${processing}`));
// => processing: false

loadFirst();
loadSecond();
// => processing: true

Pending strategies

// no second argument anyOf set as default
const $processing = pending({ effects: [loadFirst, loadSecond] });

and as well I suppose to have a possibility to pick all strategy like

const $processing = pending({ effects: [loadFirst, loadSecond] }, 'all');

best case scenario when we would be able to merge several pending in one resulting one

did not make up API for that case yet

useCase like: effect1.pending && effect2.pending || effect3.pending || effec4.pending

`splitMap` do not updates second store

test('from readme', () => {
  const watchFirst = jest.fn();
  const watchLast = jest.fn();

  const nameReceived = createEvent<string>();
  const received = splitMap({
    source: nameReceived,
    cases: {
      firstName: (string) => string.split(' ')[0], // string | undefined
      lastName: (string) => string.split(' ')[1], // string | undefined
    },
  });

  received.firstName.watch(watchFirst);
  received.lastName.watch(watchLast);

  nameReceived('Sergey');
  expect(argumentHistory(watchFirst)).toMatchInlineSnapshot(`
    Array [
      "Sergey",
    ]
  `);
  expect(argumentHistory(watchLast)).toMatchInlineSnapshot(`Array []`);

  watchFirst.mockReset();
  watchFirst.mockReset();
  nameReceived('Sergey Sova');
  expect(argumentHistory(watchFirst)).toMatchInlineSnapshot(`
    Array [
      "Sergey",
    ]
  `);

  // This check fails, now watchLast do not triggered but must
  expect(argumentHistory(watchLast)).toMatchInlineSnapshot(`
    Array [
      "Sova",
    ]
  `);
});

Add object form arguments for `reshape`

Should support config normalization

const parts = reshape({
  source: $original,
  shape: {
    length: (string) => string.length,
    first: (string) => string.split(' ')[0] || '',
    second: (string) => string.split(' ')[1] || '',
  },
});

Implement build system

With the support of:

  • Tree shaking when imports from the index file
  • ESModules
  • CommonJS
  • Import from effector repl (AMD?)
  • Import specified method. Like import {} from 'patronum/reshape'

Typescript: Ability to assign Unit<void> for condition then, else

Current behavior

const fxVoid = createEffect<void, void, void();

condition({
  source: $isLive,
  if: Boolean,
  then: fxVoid, // this fails, since it expects Unit<boolean>
  else: fxVoid
})

Expected behavior

Not to fail when you supply Unit<void>

Solution

Change Unit<State> to Unit<State | void> in typings

Add `postpone` method to delay triggering with abort support

postpone.ts
import { Effect, Event, Store, Unit, createEffect, forward } from 'effector';

type Node<T> = Event<T> | Store<T> | Effect<T, any, any>;

/**
 * Pause trigger from `source` on `delay` im ms before triggering `target`
 * It is like `forward`, but with delay
 */
export function postpone<T>(config: {
  source: Unit<T>;
  target: Unit<T>;
  delay: number;
  abort?: Node<any>;
}) {
  function abortable<P>(
    handler: (params: P) => Promise<P>,
    cancel?: Node<any>,
  ) {
    return (params: P) =>
      new Promise<P>((resolve, reject) => {
        const unsubscribe = cancel
          ? cancel.watch(() => {
              unsubscribe();
              reject(new Error('aborted'));
            })
          : () => {};

        handler(params)
          .then((done) => {
            unsubscribe();
            resolve(done);
          })
          .catch((error) => {
            unsubscribe();
            reject(error);
          });
      });
  }

  function timeout<P>(payload: P) {
    return new Promise<P>((resolve) =>
      setTimeout(resolve, config.delay, payload),
    );
  }

  const pauseFx = createEffect<T, T>();

  pauseFx.use(abortable(timeout, config.abort));

  forward({ from: config.source, to: pauseFx });
  forward({ from: pauseFx.doneData, to: config.target });
}
postpone.test.ts
import { allSettled, fork } from 'effector/fork';
import { createDomain } from 'effector';

import { postpone } from './postpone';

test('trigger target on source after delay', async () => {
  const app = createDomain();
  const source = app.createEvent<string>();
  const $target = app.createStore('');

  postpone({
    source,
    target: $target,
    delay: 10,
  });

  const scope = fork(app);
  const promise = allSettled(source, { scope, params: 'demo' });
  expect(scope.getState($target)).toBe('');

  await promise;
  expect(scope.getState($target)).toBe('demo');
});

test('abort triggering target', async () => {
  const app = createDomain();
  const source = app.createEvent<string>();
  const abort = app.createEvent();
  const $target = app.createStore('');

  postpone({
    source,
    target: $target,
    delay: 10,
    abort,
  });

  const scope = fork(app);
  const run = allSettled(source, { scope, params: 'demo' });
  expect(scope.getState($target)).toBe('');

  const cancel = allSettled(abort, { scope });

  await Promise.race([run, cancel]);
  expect(scope.getState($target)).toBe('');
});

test('abort correct scope', async () => {
  const app = createDomain();
  const source = app.createEvent<string>();
  const abort = app.createEvent();
  const $target = app.createStore('');

  postpone({
    source,
    target: $target,
    delay: 10,
    abort,
  });

  const scope1 = fork(app);
  const scope2 = fork(app);
  const run1 = allSettled(source, { scope: scope1, params: 'RUNNER 1' });
  const run2 = allSettled(source, { scope: scope2, params: 'SECOND' });

  expect(scope1.getState($target)).toBe('');
  expect(scope2.getState($target)).toBe('');

  const cancel1 = allSettled(abort, { scope: scope1 });

  await Promise.all([run1, run2, cancel1]);
  expect(scope1.getState($target)).toBe('');
  expect(scope2.getState($target)).toBe('SECOND');
});

Condition types fail

const fxVoid = createEffect<void, void, void>();
const fxOtherVoid = createEffect<void, void, void>();
const someEvent = createEvent<void>();
const boolStore = createStore<boolean>(true);

condition({
    source: someEvent,
    if: boolStore,
    then: fxVoid,
    else: fxOtherVoid
})

This example gives me following error:

Argument of type '{ source: Event<void>; if: Store<boolean>; then: Effect<void, void, void>; else: Effect<void, void, void>; }' is not assignable to parameter of type '{ if: Store<boolean> | ((payload: Store<boolean>) => boolean); then: Unit<void | Store<boolean>>; else: Unit<void | Store<boolean>>; }'. Object literal may only specify known properties, and 'source' does not exist in type '{ if: Store<boolean> | ((payload: Store<boolean>) => boolean); then: Unit<void | Store<boolean>>; else: Unit<void | Store<boolean>>; }'. 

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.