add subscriptions

typescript
Yanick Champoux 2021-10-08 19:56:55 -04:00
parent 5247aa2255
commit d61c9478a2
3 changed files with 118 additions and 64 deletions

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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({
initial: {
x: 0,
copy: 0,
},
actions: {
inc,
},
mutations: {
inc: payload => u({ x: x => x + 1 }),
set_copy: copy => u({ copy }),
},
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 }),
},
});
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) => {
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 => {
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) => {
next(action);
},
},
mutations: [[inc_top, () => u({ count: count => count + 1 })]],
effects: [
[
'*',
() => next => action => {
console.log('before ', action.type);
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();
}
};
},