add subscriptions
This commit is contained in:
parent
5247aa2255
commit
d61c9478a2
62
src/Updux.js
62
src/Updux.js
@ -1,14 +1,50 @@
|
||||
import moize from 'moize';
|
||||
import u from '@yanick/updeep';
|
||||
import { createStore as reduxCreateStore, applyMiddleware } from 'redux';
|
||||
import { mapValues } from 'lodash-es';
|
||||
import { map, mapValues } from 'lodash-es';
|
||||
|
||||
import { buildInitial } from './buildInitial/index.js';
|
||||
import { buildActions } from './buildActions/index.js';
|
||||
import { buildSelectors } from './buildSelectors/index.js';
|
||||
import { action } from './actions.js';
|
||||
import { buildUpreducer } from './buildUpreducer.js';
|
||||
import { buildMiddleware } from './buildMiddleware/index.js';
|
||||
import {
|
||||
buildMiddleware,
|
||||
augmentMiddlewareApi,
|
||||
} from './buildMiddleware/index.js';
|
||||
|
||||
function _subscribeToStore(store, subscriptions) {
|
||||
for (const sub of subscriptions) {
|
||||
const subscriber = sub(store);
|
||||
|
||||
let unsub = store.subscribe(() => subscriber(store.getState(), unsub));
|
||||
}
|
||||
}
|
||||
|
||||
const sliceSubscriber = (slice, subdux) => (subscription) => (store) => {
|
||||
let localStore = augmentMiddlewareApi(
|
||||
{
|
||||
...store,
|
||||
getState: () => store.getState()[slice],
|
||||
},
|
||||
subdux.actions,
|
||||
subdux.selectors
|
||||
);
|
||||
|
||||
return (state, unsub) => subscription(localStore)(state[slice], unsub);
|
||||
};
|
||||
|
||||
const memoizeSubscription = (subscription) => (store) => {
|
||||
let previous = undefined;
|
||||
const subscriber = subscription(store);
|
||||
|
||||
return (state, unsub) => {
|
||||
if (state === previous) return;
|
||||
let p = previous;
|
||||
previous = state;
|
||||
subscriber(state, p, unsub);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -26,6 +62,7 @@ export class Updux {
|
||||
#selectors = {};
|
||||
#mutations = {};
|
||||
#effects = [];
|
||||
#subscriptions = [];
|
||||
|
||||
constructor(config) {
|
||||
this.#initial = config.initial ?? {};
|
||||
@ -45,6 +82,8 @@ export class Updux {
|
||||
if (config.effects) {
|
||||
this.#effects = Object.entries(config.effects);
|
||||
}
|
||||
|
||||
this.#subscriptions = config.subscriptions ?? [];
|
||||
}
|
||||
|
||||
#memoInitial = moize(buildInitial);
|
||||
@ -53,6 +92,19 @@ export class Updux {
|
||||
#memoUpreducer = moize(buildUpreducer);
|
||||
#memoMiddleware = moize(buildMiddleware);
|
||||
|
||||
get subscriptions() {
|
||||
const subscriptions = [...this.#subscriptions].map((s) =>
|
||||
memoizeSubscription(s)
|
||||
);
|
||||
|
||||
return [
|
||||
...subscriptions,
|
||||
...map(this.#subduxes, (v, k) =>
|
||||
v.subscriptions.map(sliceSubscriber(k, v))
|
||||
).flat(),
|
||||
];
|
||||
}
|
||||
|
||||
get middleware() {
|
||||
return this.#memoMiddleware(
|
||||
this.#effects,
|
||||
@ -89,6 +141,10 @@ export class Updux {
|
||||
return (state, action) => this.upreducer(action)(state);
|
||||
}
|
||||
|
||||
addSubscription(subscription) {
|
||||
this.#subscriptions = [...this.#subscriptions, subscription];
|
||||
}
|
||||
|
||||
addAction(type, payloadFunc) {
|
||||
const theAction = action(type, payloadFunc);
|
||||
|
||||
@ -145,6 +201,8 @@ export class Updux {
|
||||
};
|
||||
}
|
||||
|
||||
_subscribeToStore(store, this.subscriptions);
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ const sliceMw = (slice, mw) => (api) => {
|
||||
return mw({ ...api, getState: getSliceState });
|
||||
};
|
||||
|
||||
function augmentMiddlewareApi(api, actions, selectors) {
|
||||
export function augmentMiddlewareApi(api, actions, selectors) {
|
||||
const getState = () => api.getState();
|
||||
const dispatch = (action) => api.dispatch(action);
|
||||
|
||||
|
@ -1,61 +1,60 @@
|
||||
import tap from 'tap';
|
||||
import Updux from '.';
|
||||
import u from '@yanick/updeep';
|
||||
|
||||
tap.test( 'subscriptions', async() => {
|
||||
import { Updux } from './Updux.js';
|
||||
import { action } from './actions.js';
|
||||
|
||||
const inc = action('inc');
|
||||
const set_copy = action('set_copy');
|
||||
tap.test('subscriptions', async () => {
|
||||
const inc = action('inc');
|
||||
const set_copy = action('set_copy');
|
||||
|
||||
const dux = new Updux({
|
||||
const dux = new Updux({
|
||||
initial: {
|
||||
x: 0,
|
||||
copy: 0,
|
||||
},
|
||||
actions: {
|
||||
inc,
|
||||
set_copy,
|
||||
},
|
||||
mutations: {
|
||||
inc: payload => u({ x: x => x + 1 }),
|
||||
set_copy: copy => u({ copy }),
|
||||
inc: (payload) => u({ x: (x) => x + 1 }),
|
||||
set_copy: (copy) => u({ copy }),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
dux.addSubscription(store => (state, unsubscribe) => {
|
||||
dux.addSubscription((store) => (state, previous, unsubscribe) => {
|
||||
if (state.x > 2) return unsubscribe();
|
||||
|
||||
store.dispatch(set_copy(state.x));
|
||||
});
|
||||
|
||||
const store = dux.createStore();
|
||||
|
||||
store.dispatch(inc());
|
||||
|
||||
tap.same(store.getState(), { x: 1, copy: 1 });
|
||||
|
||||
store.dispatch(inc());
|
||||
store.dispatch(inc());
|
||||
|
||||
tap.same(store.getState(), { x: 3, copy: 2 }, 'we unsubscribed');
|
||||
});
|
||||
|
||||
const store = dux.createStore();
|
||||
|
||||
store.dispatch(inc());
|
||||
|
||||
tap.same(store.getState(), { x: 1, copy: 1 });
|
||||
|
||||
store.dispatch(inc());
|
||||
store.dispatch(inc());
|
||||
|
||||
tap.same(store.getState(), { x: 3, copy: 2 }, 'we unsubscribed');
|
||||
|
||||
|
||||
} );
|
||||
|
||||
tap.test('subduxes subscriptions', async t => {
|
||||
tap.test('subduxes subscriptions', async (t) => {
|
||||
const inc_top = action('inc_top');
|
||||
const inc_bar = action('inc_bar');
|
||||
const transform_bar = action('transform_bar');
|
||||
|
||||
const bar = new Updux({
|
||||
initial: 'a',
|
||||
mutations: [
|
||||
[inc_bar, () => state => state + 'a'],
|
||||
[transform_bar, outcome => () => outcome],
|
||||
],
|
||||
actions: { inc_bar, transform_bar },
|
||||
mutations: {
|
||||
inc_bar: () => (state) => state + 'a',
|
||||
transform_bar: (outcome) => () => outcome,
|
||||
},
|
||||
subscriptions: [
|
||||
store => (state, unsubscribe) => {
|
||||
console.log({ state });
|
||||
|
||||
(store) => (state, previous, unsubscribe) => {
|
||||
if (state.length <= 2) return;
|
||||
unsubscribe();
|
||||
store.dispatch(transform_bar('look at ' + state));
|
||||
@ -67,27 +66,24 @@ tap.test('subduxes subscriptions', async t => {
|
||||
initial: {
|
||||
count: 0,
|
||||
},
|
||||
subduxes: {
|
||||
bar: bar.asDux,
|
||||
subduxes: { bar },
|
||||
actions: {
|
||||
inc_top,
|
||||
},
|
||||
mutations: [[inc_top, () => u({ count: count => count + 1 })]],
|
||||
effects: [
|
||||
[
|
||||
'*',
|
||||
() => next => action => {
|
||||
console.log('before ', action.type);
|
||||
mutations: {
|
||||
inc_top: () => u({ count: (count) => count + 1 }),
|
||||
},
|
||||
effects: {
|
||||
'*': () => (next) => (action) => {
|
||||
next(action);
|
||||
console.log({ action });
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
subscriptions: [
|
||||
store => {
|
||||
let previous;
|
||||
return ({ count }) => {
|
||||
(store) => {
|
||||
return ({ count }, { count: previous } = {}) => {
|
||||
if (count !== previous) {
|
||||
previous = count;
|
||||
store.dispatch(inc_bar());
|
||||
store.dispatch.inc_bar();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user