add splatReaction support
This commit is contained in:
parent
f206026087
commit
82f5d53df2
106
src/Updux.js
106
src/Updux.js
@ -14,6 +14,7 @@ import { action, isActionGen } from './actions.js';
|
||||
*/
|
||||
|
||||
export class Updux {
|
||||
#name = 'unknown';
|
||||
#localInitial = {};
|
||||
#subduxes = {};
|
||||
#actions;
|
||||
@ -23,10 +24,15 @@ export class Updux {
|
||||
#effects = [];
|
||||
#localReactions = [];
|
||||
#middlewareWrapper;
|
||||
#splatReactionMapper;
|
||||
|
||||
constructor(config = {}) {
|
||||
this.#config = config;
|
||||
|
||||
this.#name = config.name || 'unknown';
|
||||
|
||||
this.#splatReactionMapper = config.splatReactionMapper;
|
||||
|
||||
this.#middlewareWrapper = config.middlewareWrapper;
|
||||
|
||||
this.#localInitial = config.initial;
|
||||
@ -58,6 +64,10 @@ export class Updux {
|
||||
this.#localReactions = config.reactions ?? [];
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
#addSubduxActions(_slice, subdux) {
|
||||
if (!subdux.actions) return;
|
||||
// TODO action 'blah' defined multiple times: <where>
|
||||
@ -206,16 +216,15 @@ export class Updux {
|
||||
}
|
||||
|
||||
subscribeTo(store, subscription) {
|
||||
const localStore = this.augmentMiddlewareApi(
|
||||
{
|
||||
const localStore = this.augmentMiddlewareApi({
|
||||
...store,
|
||||
subscribe: (subscriber) => this.subscribeTo(store, subscriber), // TODO not sure
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const subscriber = subscription(localStore);
|
||||
|
||||
let previous;
|
||||
let unsub;
|
||||
|
||||
const memoSub = () => {
|
||||
const state = store.getState();
|
||||
@ -225,38 +234,81 @@ export class Updux {
|
||||
subscriber(state, p, unsub);
|
||||
};
|
||||
|
||||
let ret = store.subscribe(memoSub);
|
||||
const unsub = typeof ret === 'function' ? ret : ret.unsub;
|
||||
return {
|
||||
unsub,
|
||||
subscriberMemoized: memoSub,
|
||||
subscriber,
|
||||
return store.subscribe(memoSub);
|
||||
}
|
||||
|
||||
createSplatReaction() {
|
||||
const subdux = this.#subduxes['*'];
|
||||
const mapper = this.#splatReactionMapper;
|
||||
|
||||
return (api) => {
|
||||
const cache = {};
|
||||
|
||||
return (state, previousState, unsubscribe) => {
|
||||
const gone = { ...cache };
|
||||
|
||||
// TODO assuming object here
|
||||
for (const key in state) {
|
||||
if (cache[key]) {
|
||||
delete gone[key];
|
||||
} else {
|
||||
const dux = new Updux({
|
||||
initial: null,
|
||||
actions: { update: null },
|
||||
mutations: {
|
||||
update: (payload) => () => payload,
|
||||
},
|
||||
});
|
||||
const store = dux.createStore();
|
||||
// TODO need to change the store to have the
|
||||
// subscribe pointing to the right slice?
|
||||
const context = {
|
||||
...(api.context ?? {}),
|
||||
[subdux.name]: key,
|
||||
};
|
||||
const unsub = subdux.subscribeAll({
|
||||
...store,
|
||||
context,
|
||||
});
|
||||
cache[key] = { store, unsub };
|
||||
}
|
||||
cache[key].store.dispatch.update(state[key]);
|
||||
}
|
||||
|
||||
for (const key in gone) {
|
||||
cache[key].store.dispatch.update(null);
|
||||
cache[key].unsub();
|
||||
delete cache[key];
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
subscribeSplatReaction(store) {
|
||||
return this.subscribeTo(store, this.createSplatReaction());
|
||||
}
|
||||
|
||||
subscribeAll(store) {
|
||||
let results = this.#localReactions.map((sub) =>
|
||||
let unsubs = this.#localReactions.map((sub) =>
|
||||
this.subscribeTo(store, sub),
|
||||
);
|
||||
|
||||
for (const subdux in this.#subduxes) {
|
||||
if (subdux !== '*') {
|
||||
const localStore = {
|
||||
...store,
|
||||
getState: () => store.getState()[subdux],
|
||||
};
|
||||
results.push(this.#subduxes[subdux].subscribeAll(localStore));
|
||||
}
|
||||
if (this.#splatReactionMapper) {
|
||||
unsubs.push(this.subscribeSplatReaction(store));
|
||||
}
|
||||
|
||||
return {
|
||||
unsub: () => results.forEach(({ unsub }) => unsub()),
|
||||
subscriberMemoized: () =>
|
||||
results.forEach(({ subscriberMemoized }) =>
|
||||
subscriberMemoized(),
|
||||
),
|
||||
subscriber: () => results.forEach(({ subscriber }) => subscriber()),
|
||||
};
|
||||
unsubs.push(
|
||||
...Object.entries(this.#subduxes)
|
||||
.filter(([slice]) => slice !== '*')
|
||||
.map(([slice, subdux]) => {
|
||||
subdux.subscribeAll({
|
||||
...store,
|
||||
getState: () => store.getState()[slice],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return () => unsubs.forEach((u) => u());
|
||||
}
|
||||
}
|
||||
|
||||
|
127
src/splatReactions.test.js
Normal file
127
src/splatReactions.test.js
Normal file
@ -0,0 +1,127 @@
|
||||
import { test, expect, vi, describe } from 'vitest';
|
||||
import u from 'updeep';
|
||||
|
||||
import { Updux } from './Updux.js';
|
||||
import { matches } from './utils';
|
||||
|
||||
const reactionSnitch = vi.fn();
|
||||
const thingReactionSnitch = vi.fn();
|
||||
|
||||
const subThing = new Updux({
|
||||
name: 'subThing',
|
||||
initial: 0,
|
||||
});
|
||||
|
||||
subThing.addReaction((api) => (state, previousState, unsubscribe) => {
|
||||
reactionSnitch({ ...api, state, previousState });
|
||||
});
|
||||
|
||||
const thing = new Updux({
|
||||
name: 'thing',
|
||||
initial: {},
|
||||
subduxes: {
|
||||
'*': subThing,
|
||||
},
|
||||
actions: {
|
||||
setSubThing: (id, value, thingId) => ({ thingId, id, value }),
|
||||
deleteSubThing: (id) => id,
|
||||
},
|
||||
mutations: {
|
||||
setSubThing: ({ id, value }) => u.updateIn(id, value),
|
||||
deleteSubThing: (id) => u.updateIn(id, u.omitted),
|
||||
},
|
||||
splatReactionMapper: ({ id }) => id,
|
||||
});
|
||||
|
||||
thing.addReaction((api) => (state, previousState, unsubscribe) => {
|
||||
thingReactionSnitch({ ...api, state, previousState });
|
||||
});
|
||||
|
||||
const things = new Updux({
|
||||
subduxes: {
|
||||
'*': thing,
|
||||
},
|
||||
initial: {},
|
||||
actions: { newThing: (id) => id },
|
||||
splatReactionMapper: ({ id }) => id,
|
||||
mutations: {
|
||||
newThing: (id) => (state) => ({ ...state, [id]: thing.initial }),
|
||||
},
|
||||
});
|
||||
|
||||
things.setMutation(
|
||||
'setSubThing',
|
||||
({ thingId }, action) => u.updateIn(thingId, thing.upreducer(action)),
|
||||
true,
|
||||
);
|
||||
|
||||
describe('just one level', () => {
|
||||
const store = thing.createStore();
|
||||
|
||||
test('set', async () => {
|
||||
store.dispatch.setSubThing('a', 13);
|
||||
|
||||
expect(reactionSnitch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ state: 13 }),
|
||||
);
|
||||
});
|
||||
|
||||
test('other key', async () => {
|
||||
reactionSnitch.mockReset();
|
||||
|
||||
store.dispatch.setSubThing('b', 23);
|
||||
|
||||
expect(reactionSnitch).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ state: 13 }),
|
||||
);
|
||||
expect(reactionSnitch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ state: 23 }),
|
||||
);
|
||||
});
|
||||
|
||||
test('delete', async () => {
|
||||
reactionSnitch.mockReset();
|
||||
|
||||
store.dispatch.deleteSubThing('a');
|
||||
|
||||
expect(reactionSnitch).toHaveBeenCalledOnce();
|
||||
|
||||
expect(reactionSnitch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ state: null }),
|
||||
);
|
||||
});
|
||||
|
||||
test('context', async () => {
|
||||
expect(reactionSnitch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
context: { subThing: 'a' },
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('two levels', async () => {
|
||||
const store = things.createStore();
|
||||
|
||||
reactionSnitch.mockReset();
|
||||
thingReactionSnitch.mockReset();
|
||||
|
||||
store.dispatch.newThing('alpha');
|
||||
store.dispatch.newThing('beta');
|
||||
store.dispatch.setSubThing('a', 13, 'alpha');
|
||||
|
||||
expect(reactionSnitch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
context: { thing: 'alpha', subThing: 'a' },
|
||||
state: 13,
|
||||
}),
|
||||
);
|
||||
expect(thingReactionSnitch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
context: {
|
||||
thing: 'alpha',
|
||||
},
|
||||
state: { a: 13 },
|
||||
}),
|
||||
);
|
||||
});
|
Loading…
Reference in New Issue
Block a user