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 moize from 'moize';
|
||||||
import u from '@yanick/updeep';
|
import u from '@yanick/updeep';
|
||||||
import { createStore as reduxCreateStore, applyMiddleware } from 'redux';
|
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 { buildInitial } from './buildInitial/index.js';
|
||||||
import { buildActions } from './buildActions/index.js';
|
import { buildActions } from './buildActions/index.js';
|
||||||
import { buildSelectors } from './buildSelectors/index.js';
|
import { buildSelectors } from './buildSelectors/index.js';
|
||||||
import { action } from './actions.js';
|
import { action } from './actions.js';
|
||||||
import { buildUpreducer } from './buildUpreducer.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
|
* @public
|
||||||
@ -26,6 +62,7 @@ export class Updux {
|
|||||||
#selectors = {};
|
#selectors = {};
|
||||||
#mutations = {};
|
#mutations = {};
|
||||||
#effects = [];
|
#effects = [];
|
||||||
|
#subscriptions = [];
|
||||||
|
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
this.#initial = config.initial ?? {};
|
this.#initial = config.initial ?? {};
|
||||||
@ -45,6 +82,8 @@ export class Updux {
|
|||||||
if (config.effects) {
|
if (config.effects) {
|
||||||
this.#effects = Object.entries(config.effects);
|
this.#effects = Object.entries(config.effects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.#subscriptions = config.subscriptions ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
#memoInitial = moize(buildInitial);
|
#memoInitial = moize(buildInitial);
|
||||||
@ -53,6 +92,19 @@ export class Updux {
|
|||||||
#memoUpreducer = moize(buildUpreducer);
|
#memoUpreducer = moize(buildUpreducer);
|
||||||
#memoMiddleware = moize(buildMiddleware);
|
#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() {
|
get middleware() {
|
||||||
return this.#memoMiddleware(
|
return this.#memoMiddleware(
|
||||||
this.#effects,
|
this.#effects,
|
||||||
@ -89,6 +141,10 @@ export class Updux {
|
|||||||
return (state, action) => this.upreducer(action)(state);
|
return (state, action) => this.upreducer(action)(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addSubscription(subscription) {
|
||||||
|
this.#subscriptions = [...this.#subscriptions, subscription];
|
||||||
|
}
|
||||||
|
|
||||||
addAction(type, payloadFunc) {
|
addAction(type, payloadFunc) {
|
||||||
const theAction = action(type, payloadFunc);
|
const theAction = action(type, payloadFunc);
|
||||||
|
|
||||||
@ -145,6 +201,8 @@ export class Updux {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_subscribeToStore(store, this.subscriptions);
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ const sliceMw = (slice, mw) => (api) => {
|
|||||||
return mw({ ...api, getState: getSliceState });
|
return mw({ ...api, getState: getSliceState });
|
||||||
};
|
};
|
||||||
|
|
||||||
function augmentMiddlewareApi(api, actions, selectors) {
|
export function augmentMiddlewareApi(api, actions, selectors) {
|
||||||
const getState = () => api.getState();
|
const getState = () => api.getState();
|
||||||
const dispatch = (action) => api.dispatch(action);
|
const dispatch = (action) => api.dispatch(action);
|
||||||
|
|
||||||
|
@ -1,61 +1,60 @@
|
|||||||
import tap from 'tap';
|
import tap from 'tap';
|
||||||
import Updux from '.';
|
|
||||||
import u from '@yanick/updeep';
|
import u from '@yanick/updeep';
|
||||||
|
|
||||||
tap.test( 'subscriptions', async() => {
|
import { Updux } from './Updux.js';
|
||||||
|
import { action } from './actions.js';
|
||||||
|
|
||||||
const inc = action('inc');
|
tap.test('subscriptions', async () => {
|
||||||
const set_copy = action('set_copy');
|
const inc = action('inc');
|
||||||
|
const set_copy = action('set_copy');
|
||||||
|
|
||||||
const dux = new Updux({
|
const dux = new Updux({
|
||||||
initial: {
|
initial: {
|
||||||
x: 0,
|
x: 0,
|
||||||
copy: 0,
|
copy: 0,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
inc,
|
inc,
|
||||||
},
|
set_copy,
|
||||||
mutations: {
|
},
|
||||||
inc: payload => u({ x: x => x + 1 }),
|
mutations: {
|
||||||
set_copy: copy => u({ copy }),
|
inc: (payload) => u({ x: (x) => x + 1 }),
|
||||||
},
|
set_copy: (copy) => u({ copy }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
});
|
});
|
||||||
|
|
||||||
dux.addSubscription(store => (state, unsubscribe) => {
|
tap.test('subduxes subscriptions', async (t) => {
|
||||||
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');
|
|
||||||
|
|
||||||
|
|
||||||
} );
|
|
||||||
|
|
||||||
tap.test('subduxes subscriptions', async t => {
|
|
||||||
const inc_top = action('inc_top');
|
const inc_top = action('inc_top');
|
||||||
const inc_bar = action('inc_bar');
|
const inc_bar = action('inc_bar');
|
||||||
const transform_bar = action('transform_bar');
|
const transform_bar = action('transform_bar');
|
||||||
|
|
||||||
const bar = new Updux({
|
const bar = new Updux({
|
||||||
initial: 'a',
|
initial: 'a',
|
||||||
mutations: [
|
actions: { inc_bar, transform_bar },
|
||||||
[inc_bar, () => state => state + 'a'],
|
mutations: {
|
||||||
[transform_bar, outcome => () => outcome],
|
inc_bar: () => (state) => state + 'a',
|
||||||
],
|
transform_bar: (outcome) => () => outcome,
|
||||||
|
},
|
||||||
subscriptions: [
|
subscriptions: [
|
||||||
store => (state, unsubscribe) => {
|
(store) => (state, previous, unsubscribe) => {
|
||||||
console.log({ state });
|
|
||||||
|
|
||||||
if (state.length <= 2) return;
|
if (state.length <= 2) return;
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
store.dispatch(transform_bar('look at ' + state));
|
store.dispatch(transform_bar('look at ' + state));
|
||||||
@ -67,27 +66,24 @@ tap.test('subduxes subscriptions', async t => {
|
|||||||
initial: {
|
initial: {
|
||||||
count: 0,
|
count: 0,
|
||||||
},
|
},
|
||||||
subduxes: {
|
subduxes: { bar },
|
||||||
bar: bar.asDux,
|
actions: {
|
||||||
|
inc_top,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
inc_top: () => u({ count: (count) => count + 1 }),
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
'*': () => (next) => (action) => {
|
||||||
|
next(action);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mutations: [[inc_top, () => u({ count: count => count + 1 })]],
|
|
||||||
effects: [
|
|
||||||
[
|
|
||||||
'*',
|
|
||||||
() => next => action => {
|
|
||||||
console.log('before ', action.type);
|
|
||||||
next(action);
|
|
||||||
console.log({ action });
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
subscriptions: [
|
subscriptions: [
|
||||||
store => {
|
(store) => {
|
||||||
let previous;
|
return ({ count }, { count: previous } = {}) => {
|
||||||
return ({ count }) => {
|
|
||||||
if (count !== previous) {
|
if (count !== previous) {
|
||||||
previous = count;
|
previous = count;
|
||||||
store.dispatch(inc_bar());
|
store.dispatch.inc_bar();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user