From 3273690fe2baa208058a3cdbe00c404caa3cd3e1 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 2 Mar 2023 11:10:12 -0500 Subject: [PATCH 01/46] rename all to TODO --- src/Updux.original | 457 ++++++++++++++++++ src/{Updux.js => Updux.todo} | 7 +- src/{actions.test.js => actions.test.todo} | 0 src/{actions.js => actions.todo} | 0 src/buildMiddleware/index.ts | 85 ++++ src/buildSelectors/index.ts | 38 ++ ...{buildUpreducer.js => buildUpreducer.todo} | 0 ...ateStore.test.js => createStore.test.todo} | 0 ...ectors.test.js => dux-selectors.test.todo} | 0 src/{index.js => index.todo} | 0 src/{initial.test.js => initial.test.todo} | 0 ...iddleware.test.js => middleware.test.todo} | 0 src/{middleware.js => middleware.todo} | 0 ...{mutations.test.js => mutations.test.todo} | 0 ...{reactions.test.js => reactions.test.todo} | 0 src/{reducer.test.js => reducer.test.todo} | 0 src/{selectors.js => selectors.todo} | 0 ...tions.test.js => splatReactions.test.todo} | 0 src/{upreducer.js => upreducer.todo} | 0 src/{utils.js => utils.todo} | 0 20 files changed, 585 insertions(+), 2 deletions(-) create mode 100644 src/Updux.original rename src/{Updux.js => Updux.todo} (97%) rename src/{actions.test.js => actions.test.todo} (100%) rename src/{actions.js => actions.todo} (100%) create mode 100644 src/buildMiddleware/index.ts create mode 100644 src/buildSelectors/index.ts rename src/{buildUpreducer.js => buildUpreducer.todo} (100%) rename src/{createStore.test.js => createStore.test.todo} (100%) rename src/{dux-selectors.test.js => dux-selectors.test.todo} (100%) rename src/{index.js => index.todo} (100%) rename src/{initial.test.js => initial.test.todo} (100%) rename src/{middleware.test.js => middleware.test.todo} (100%) rename src/{middleware.js => middleware.todo} (100%) rename src/{mutations.test.js => mutations.test.todo} (100%) rename src/{reactions.test.js => reactions.test.todo} (100%) rename src/{reducer.test.js => reducer.test.todo} (100%) rename src/{selectors.js => selectors.todo} (100%) rename src/{splatReactions.test.js => splatReactions.test.todo} (100%) rename src/{upreducer.js => upreducer.todo} (100%) rename src/{utils.js => utils.todo} (100%) diff --git a/src/Updux.original b/src/Updux.original new file mode 100644 index 0000000..586b803 --- /dev/null +++ b/src/Updux.original @@ -0,0 +1,457 @@ +/* TODO change * for leftovers to +, change subscriptions to reactions */ +import moize from 'moize'; +import u from 'updeep'; +import { createStore as reduxCreateStore, applyMiddleware } from 'redux'; +import { get, map, mapValues, merge, difference } 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, + augmentMiddlewareApi, + effectToMiddleware, +} from './buildMiddleware/index.js'; + +import { + AggregateDuxActions, + AggregateDuxState, + Dict, + ItemsOf, + Reducer, + Upreducer, +} from './types.js'; + +type Mutation = (payload:TAction['payload'], action:TAction) => (state: TState) => TState; + +/** + * Configuration object typically passed to the constructor of the class Updux. + */ +export interface UpduxConfig< + TState = any, + TActions = {}, + TSelectors = {}, + TSubduxes = {} +> { + /** + * Local initial state. + * @default {} + */ + initial?: TState; + + /** + * Subduxes to be merged to this dux. + */ + subduxes?: TSubduxes; + + /** + * Local actions. + */ + actions?: TActions; + + /** + * Local selectors. + */ + selectors?: Record; + + /** + * Local mutations + */ + mutations?: Record; + + /** + * Selectors to apply to the mapped subduxes. Only + * applicable if the dux is a mapping dux. + */ + mappedSelectors?: Record; + + /** + * Local effects. + */ + effects?: Record; + + /** + * Local reactions. + */ + reactions?: Function[]; + + /** + * If true, enables mapped reactions. Additionally, it can be + * a reaction function, which will treated as a regular + * reaction for the mapped dux. + */ + mappedReaction?: Function | boolean; + + /** + * Wrapping function for the upreducer to provides full customization. + * @example + * // if an action has the 'dontDoIt' meta flag, don't do anything + * const dux = new Updux({ + * ..., + * upreducerWrapper: (upreducer) => action => { + * if( action?.meta?.dontDoIt ) return state => state; + * return upreducer(action); + * } + * }) + */ + upreducerWrapper?: ( + upreducer: Upreducer< + AggregateDuxState, + ItemsOf> + > + ) => Upreducer< + AggregateDuxState, + ItemsOf> + >; + + middlewareWrapper?: Function; +} + +export class Updux< + TState extends any = {}, + TActions extends object = {}, + TSelectors = {}, + TSubduxes extends object = {} +> { + /** @type { unknown } */ + #initial = {}; + #subduxes = {}; + + /** @type Record */ + #actions = {}; + #selectors = {}; + #mutations = {}; + #effects = []; + #reactions = []; + #mappedSelectors = undefined; + #mappedReaction = undefined; + #upreducerWrapper = undefined; + + #middlewareWrapper = undefined; + + constructor( + config: UpduxConfig + ) { + this.#initial = config.initial ?? {}; + this.#subduxes = config.subduxes ?? {}; + + if (config.subduxes) { + this.#subduxes = mapValues(config.subduxes, (sub) => + sub instanceof Updux ? sub : new Updux(sub) + ); + } + + if (config.actions) { + for (const [type, actionArg] of Object.entries(config.actions)) { + if (typeof actionArg === 'function' && actionArg.type) { + this.#actions[type] = actionArg; + } else { + const args = Array.isArray(actionArg) + ? actionArg + : [actionArg]; + this.#actions[type] = action(type, ...args); + } + } + } + + this.#selectors = config.selectors ?? {}; + this.#mappedSelectors = config.mappedSelectors; + + this.#mutations = config.mutations ?? {}; + + Object.keys(this.#mutations) + .filter((action) => action !== '+') + .filter((action) => !this.actions.hasOwnProperty(action)) + .forEach((action) => { + throw new Error(`action '${action}' is not defined`); + }); + + if (config.effects) { + this.#effects = Object.entries(config.effects); + } + + this.#reactions = config.reactions ?? []; + + this.#mappedReaction = config.mappedReaction; + + this.#upreducerWrapper = config.upreducerWrapper; + + this.#middlewareWrapper = config.middlewareWrapper; + } + + #memoInitial = moize(buildInitial); + #memoActions = moize(buildActions); + #memoSelectors = moize(buildSelectors); + #memoUpreducer = moize(buildUpreducer); + #memoMiddleware = moize(buildMiddleware); + + setMappedSelector(name, f) { + this.#mappedSelectors = { + ...this.#mappedSelectors, + [name]: f, + }; + } + + get middleware() { + return this.#memoMiddleware( + this.#effects, + this.actions, + this.selectors, + this.#subduxes, + this.#middlewareWrapper, + this + ); + } + + setMiddlewareWrapper(wrapper: Function) { + this.#middlewareWrapper = wrapper; + } + + /** @member { unknown } */ + get initial(): AggregateDuxState { + return this.#memoInitial(this.#initial, this.#subduxes); + } + + get actions(): AggregateDuxActions { + return this.#memoActions(this.#actions, this.#subduxes) as any; + } + + get selectors() { + return this.#memoSelectors( + this.#selectors, + this.#mappedSelectors, + this.#subduxes + ); + } + + get subduxes() { return this.#subduxes } + + get upreducer(): Upreducer< + AggregateDuxState, + ItemsOf> + > { + return this.#memoUpreducer( + this.initial, + this.#mutations, + this.#subduxes, + this.#upreducerWrapper + ); + } + + get reducer(): Reducer< + AggregateDuxState, + ItemsOf> + > { + return (state, action) => this.upreducer(action)(state); + } + + addSubscription(subscription) { + this.#reactions = [...this.#reactions, subscription]; + } + + addReaction(reaction) { + this.#reactions = [...this.#reactions, reaction]; + } + + + setAction(type, payloadFunc?: (...args: any) => any) { + const theAction = action(type, payloadFunc); + + this.#actions = { ...this.#actions, [type]: theAction }; + + return theAction; + } + + setSelector(name, func) { + // TODO selector already exists? Complain! + this.#selectors = { + ...this.#selectors, + [name]: func, + }; + return func; + } + + setMutation>(name: TAction, mutation: Mutation, + ReturnType[TAction]>>) { + if (typeof name === 'function') name = name.type; + + this.#mutations = { + ...this.#mutations, + [name]: mutation, + }; + + return mutation; + } + + addEffect(action: TType, effect: E): E { + this.#effects = [...this.#effects, [action, effect]]; + return effect; + } + + augmentMiddlewareApi(api) { + return augmentMiddlewareApi(api, this.actions, this.selectors); + } + + splatSubscriber(store, inner, splatReaction) { + const cache = {}; + + return () => (state, previous, unsub) => { + const cacheKeys = Object.keys(cache); + + const newKeys = difference(Object.keys(state), cacheKeys); + + for (const slice of newKeys) { + let localStore = { + ...store, + getState: () => store.getState()[slice], + }; + + cache[slice] = []; + + if (typeof splatReaction === 'function') { + localStore = { + ...localStore, + ...splatReaction(localStore, slice), + }; + } + + const { unsub, subscriber, subscriberRaw } = + inner.subscribeAll(localStore); + + cache[slice].push({ unsub, subscriber, subscriberRaw }); + subscriber(); + } + + const deletedKeys = difference(cacheKeys, Object.keys(state)); + + for (const deleted of deletedKeys) { + for (const inner of cache[deleted]) { + inner.subscriber(); + inner.unsub(); + } + delete cache[deleted]; + } + }; + } + + subscribeTo(store, subscription, setupArgs = []) { + const localStore = augmentMiddlewareApi( + { + ...store, + subscribe: (subscriber) => + this.subscribeTo(store, () => subscriber), + }, + this.actions, + this.selectors + ); + + const subscriber = subscription(localStore, ...setupArgs); + + let previous; + + const memoSub = () => { + const state = store.getState(); + if (state === previous) return; + let p = previous; + previous = state; + subscriber(state, p, unsub); + }; + + let ret = store.subscribe(memoSub); + const unsub = typeof ret === 'function' ? ret : ret.unsub; + return { + unsub, + subscriber: memoSub, + subscriberRaw: subscriber, + }; + } + + subscribeAll(store) { + let results = this.#reactions.map((sub) => + this.subscribeTo(store, sub) + ); + + for (const subdux in this.#subduxes) { + if (subdux !== '*') { + const localStore = { + ...store, + getState: () => get(store.getState(), subdux), + }; + results.push(this.#subduxes[subdux].subscribeAll(localStore)); + } + } + + if (this.#mappedReaction) { + results.push( + this.subscribeTo( + store, + this.splatSubscriber( + store, + this.#subduxes['*'], + this.#mappedReaction + ) + ) + ); + } + + return { + unsub: () => results.forEach(({ unsub }) => unsub()), + subscriber: () => + results.forEach(({ subscriber }) => subscriber()), + subscriberRaw: (...args) => + results.forEach(({ subscriberRaw }) => + subscriberRaw(...args) + ), + }; + } + + createStore(initial?: unknown, enhancerGenerator?: Function) { + const enhancer = (enhancerGenerator ?? applyMiddleware)( + this.middleware + ); + + const store: { + getState: Function & Record; + dispatch: Function & Record; + selectors: Record; + actions: AggregateDuxActions; + } = reduxCreateStore( + this.reducer as any, + initial ?? this.initial, + enhancer + ) as any; + + store.actions = this.actions; + + store.selectors = this.selectors; + + merge( + store.getState, + mapValues(this.selectors, (selector) => { + return (...args) => { + let result = selector(store.getState()); + + if (typeof result === 'function') return result(...args); + + return result; + }; + }) + ); + + for (const action in this.actions) { + store.dispatch[action] = (...args) => { + return store.dispatch(this.actions[action](...(args as any))); + }; + } + + this.subscribeAll(store); + + return store; + } + + effectToMiddleware(effect) { + return effectToMiddleware(effect, this.actions, this.selectors); + } +} diff --git a/src/Updux.js b/src/Updux.todo similarity index 97% rename from src/Updux.js rename to src/Updux.todo index d36f395..31e9b04 100644 --- a/src/Updux.js +++ b/src/Updux.todo @@ -247,8 +247,11 @@ export class Updux { return (state, previousState, unsubscribe) => { const gone = { ...cache }; - // TODO assuming object here - for (const key in state) { + const mappedState = Array.isArray(state)? Object.fromEntries( + state.map( s => [ mapper(s), s ] ) + ) : state; + + for (const key in mappedState) { if (cache[key]) { delete gone[key]; } else { diff --git a/src/actions.test.js b/src/actions.test.todo similarity index 100% rename from src/actions.test.js rename to src/actions.test.todo diff --git a/src/actions.js b/src/actions.todo similarity index 100% rename from src/actions.js rename to src/actions.todo diff --git a/src/buildMiddleware/index.ts b/src/buildMiddleware/index.ts new file mode 100644 index 0000000..433c531 --- /dev/null +++ b/src/buildMiddleware/index.ts @@ -0,0 +1,85 @@ +import u from 'updeep'; +import { mapValues, map, get } from 'lodash-es'; + +const middlewareFor = (type, middleware) => (api) => (next) => (action) => { + if (type !== '*' && action.type !== type) return next(action); + + return middleware(api)(next)(action); +}; + +const sliceMw = (slice, mw) => (api) => { + const getSliceState = () => get(api.getState(), slice); + return mw({ ...api, getState: getSliceState }); +}; + +export function augmentMiddlewareApi(api, actions, selectors) { + const getState = () => api.getState(); + const dispatch = (action) => api.dispatch(action); + + Object.assign( + getState, + mapValues(selectors, (selector) => { + return (...args) => { + let result = selector(api.getState()); + + if (typeof result === 'function') return result(...args); + + return result; + }; + }) + ); + + Object.assign( + dispatch, + mapValues(actions, (action) => { + return (...args) => api.dispatch(action(...args)); + }) + ); + + return { + ...api, + getState, + dispatch, + actions, + selectors, + }; +} + +export const effectToMiddleware = (effect, actions, selectors) => { + let mw = effect; + let action = '*'; + + if (Array.isArray(effect)) { + action = effect[0]; + mw = effect[1]; + mw = middlewareFor(action, mw); + } + + return (api) => mw(augmentMiddlewareApi(api, actions, selectors)); +}; + +const composeMw = (mws) => (api) => (original_next) => + mws.reduceRight((next, mw) => mw(api)(next), original_next); + +export function buildMiddleware( + effects = [], + actions = {}, + selectors = {}, + sub = {}, + wrapper = undefined, + dux = undefined, +) { + let inner = map(sub, ({ middleware }, slice) => + slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined + ).filter((x) => x); + + const local = effects.map((effect) => + effectToMiddleware(effect, actions, selectors) + ); + + let mws = [...local, ...inner]; + + if( wrapper ) mws = wrapper(mws,dux); + + return composeMw(mws); +} diff --git a/src/buildSelectors/index.ts b/src/buildSelectors/index.ts new file mode 100644 index 0000000..3b58af7 --- /dev/null +++ b/src/buildSelectors/index.ts @@ -0,0 +1,38 @@ +import { map, mapValues, merge } from 'lodash-es'; + +export function buildSelectors( + localSelectors, + splatSelector = {}, + subduxes = {} +) { + const subSelectors = map(subduxes, ({ selectors }, slice) => { + if (!selectors) return {}; + if (slice === '*') return {}; + + return mapValues( + selectors, + (func: Function) => (state) => func(state[slice]) + ); + }); + + let splat = {}; + + for (const name in splatSelector) { + splat[name] = + (state) => + (...args) => { + const value = splatSelector[name](state)(...args); + + const res = () => value; + return merge( + res, + mapValues( + subduxes['*'].selectors, + (selector) => () => selector(value) + ) + ); + }; + } + + return merge({}, ...subSelectors, localSelectors, splat); +} diff --git a/src/buildUpreducer.js b/src/buildUpreducer.todo similarity index 100% rename from src/buildUpreducer.js rename to src/buildUpreducer.todo diff --git a/src/createStore.test.js b/src/createStore.test.todo similarity index 100% rename from src/createStore.test.js rename to src/createStore.test.todo diff --git a/src/dux-selectors.test.js b/src/dux-selectors.test.todo similarity index 100% rename from src/dux-selectors.test.js rename to src/dux-selectors.test.todo diff --git a/src/index.js b/src/index.todo similarity index 100% rename from src/index.js rename to src/index.todo diff --git a/src/initial.test.js b/src/initial.test.todo similarity index 100% rename from src/initial.test.js rename to src/initial.test.todo diff --git a/src/middleware.test.js b/src/middleware.test.todo similarity index 100% rename from src/middleware.test.js rename to src/middleware.test.todo diff --git a/src/middleware.js b/src/middleware.todo similarity index 100% rename from src/middleware.js rename to src/middleware.todo diff --git a/src/mutations.test.js b/src/mutations.test.todo similarity index 100% rename from src/mutations.test.js rename to src/mutations.test.todo diff --git a/src/reactions.test.js b/src/reactions.test.todo similarity index 100% rename from src/reactions.test.js rename to src/reactions.test.todo diff --git a/src/reducer.test.js b/src/reducer.test.todo similarity index 100% rename from src/reducer.test.js rename to src/reducer.test.todo diff --git a/src/selectors.js b/src/selectors.todo similarity index 100% rename from src/selectors.js rename to src/selectors.todo diff --git a/src/splatReactions.test.js b/src/splatReactions.test.todo similarity index 100% rename from src/splatReactions.test.js rename to src/splatReactions.test.todo diff --git a/src/upreducer.js b/src/upreducer.todo similarity index 100% rename from src/upreducer.js rename to src/upreducer.todo diff --git a/src/utils.js b/src/utils.todo similarity index 100% rename from src/utils.js rename to src/utils.todo From ad88599dd95c9d0cbde52fd2544ff52e10882a45 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 2 Mar 2023 11:10:25 -0500 Subject: [PATCH 02/46] it begins --- docs/recipes.md | 15 +- docs/recipes.test.js | 1 - docs/tutorial-reactions.test.js | 1 - docs/tutorial.md | 9 +- foo.ts | 6 + out/Updux.html | 160 ++++++++++----------- out/Updux.js.html | 4 +- out/fonts/OpenSans-Bold-webfont.svg | 2 +- out/fonts/OpenSans-BoldItalic-webfont.svg | 2 +- out/fonts/OpenSans-Italic-webfont.svg | 2 +- out/fonts/OpenSans-Light-webfont.svg | 2 +- out/fonts/OpenSans-LightItalic-webfont.svg | 2 +- out/fonts/OpenSans-Regular-webfont.svg | 2 +- out/index.html | 8 +- package.json | 1 + pnpm-lock.yaml | 8 ++ src/Updux.ts | 1 + src/buildMiddleware/index.ts | 10 +- src/buildSelectors/index.ts | 8 +- src/index.ts | 3 + src/tutorial.test.ts | 22 +++ tsconfig.json | 105 ++++++++++++++ vitest.config.ts | 8 ++ 23 files changed, 265 insertions(+), 117 deletions(-) create mode 100644 foo.ts create mode 100644 src/Updux.ts create mode 100644 src/index.ts create mode 100644 src/tutorial.test.ts create mode 100644 tsconfig.json create mode 100644 vitest.config.ts diff --git a/docs/recipes.md b/docs/recipes.md index 3dd308d..ef7369d 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -4,7 +4,7 @@ Say you have a `todos` state that is an array of `todo` sub-states, with some actions that should percolate to all todos, and some that should only -percolate to one. One way to model this is via updux's splat subduxes +percolate to one. One way to model this is via updux's splat subduxes (backed by `updeep`'s own '*'-key behavior). ``` @@ -55,7 +55,7 @@ const updux = new Updux({ add: (inc=1) => state => ({ counter: state.counter + inc }) } }); - + ``` Converting it to Immer would look like: @@ -68,10 +68,10 @@ import { produce } from 'immer'; const updux = new Updux({ initial: { counter: 0 }, mutations: { - add: (inc=1) => produce( draft => draft.counter += inc ) } + add: (inc=1) => produce( draft => draft.counter += inc ) } } }); - + ``` But since typing `produce` over and over is no fun, `groomMutations` @@ -86,11 +86,8 @@ const updux = new Updux({ initial: { counter: 0 }, groomMutations: mutation => (...args) => produce( mutation(...args) ), mutations: { - add: (inc=1) => draft => draft.counter += inc + add: (inc=1) => draft => draft.counter += inc } }); - + ``` - - - diff --git a/docs/recipes.test.js b/docs/recipes.test.js index 8560675..ef0a13a 100644 --- a/docs/recipes.test.js +++ b/docs/recipes.test.js @@ -51,4 +51,3 @@ test( "tutorial", async () => { ]); }); - diff --git a/docs/tutorial-reactions.test.js b/docs/tutorial-reactions.test.js index 61dfe85..cdba1d9 100644 --- a/docs/tutorial-reactions.test.js +++ b/docs/tutorial-reactions.test.js @@ -37,4 +37,3 @@ test( "basic tests", async () => { expect(store.getState().nbrTodos).toEqual(2); }); - diff --git a/docs/tutorial.md b/docs/tutorial.md index 6ecb348..61d87d1 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -15,7 +15,7 @@ plain JavaScript. To begin with, let's define that has nothing but an initial state. ```js -import { Updux } from 'updux'; +import Updux from 'updux'; const todosDux = new Updux({ initial: { @@ -158,9 +158,9 @@ todosDux.setSelector('getTodoById', getTodoById); ### Accessing selectors -The `getState` method of a dux store is augmented +The `getState` method of a dux store is augmented with its selectors, with the first call for the state already -called in for you. +called in for you. ```js const store = todosDux.createStore(); @@ -204,7 +204,7 @@ const todosDux = new Updux({ addTodoWithId: todo => u.updateIn( 'todos', todos => [ ...todos, todo] ), incrementNextId: () => u({ nextId: fp.add(1) }), - todoDone: (id) => u.updateIn('todos', + todoDone: (id) => u.updateIn('todos', u.map( u.if( fp.matches({id}), todo => u({done: true}, todo) ) ) ), }, @@ -404,4 +404,3 @@ const myDux = new Updux({ [immer]: https://www.npmjs.com/package/immer [lodash]: https://www.npmjs.com/package/lodash [ts-action]: https://www.npmjs.com/package/ts-action - diff --git a/foo.ts b/foo.ts new file mode 100644 index 0000000..589e79a --- /dev/null +++ b/foo.ts @@ -0,0 +1,6 @@ +const foo = (x: Partial<{ a: A; b: B }>) => x as B; + +const y = foo({ + a: 'potato', + b: 2, +}); diff --git a/out/Updux.html b/out/Updux.html index 44126a5..c1e8090 100644 --- a/out/Updux.html +++ b/out/Updux.html @@ -19,7 +19,7 @@

Class: Updux

- + @@ -27,24 +27,24 @@
- +

Updux()

- - + +
- - - - + + + +

new Updux()

- - + + @@ -62,42 +62,42 @@
- - - - - - - - - - - - - + + + + + + + + + + + + +
Source:
- - - - + + + +
@@ -119,33 +119,33 @@ - +
- - - + + +

Classes

Updux
- - - - - + + + + +

Members

- - + +

actions

@@ -159,42 +159,42 @@
- - - - - - - - - - - - - + + + + + + + + + + + + +
Source:
- - - - + + + +
@@ -202,8 +202,8 @@ - - + +

initial

@@ -217,42 +217,42 @@
- - - - - - - - - - - - - + + + + + + + + + + + + +
Source:
- - - - + + + +
@@ -260,14 +260,14 @@ - - - - - + + + + +
@@ -290,4 +290,4 @@ - \ No newline at end of file + diff --git a/out/Updux.js.html b/out/Updux.js.html index 3541151..7456b35 100644 --- a/out/Updux.js.html +++ b/out/Updux.js.html @@ -19,11 +19,11 @@

Source: Updux.js

- - + +
/* TODO change * for leftovers to +, change subscriptions to reactions */
diff --git a/out/fonts/OpenSans-Bold-webfont.svg b/out/fonts/OpenSans-Bold-webfont.svg
index 3ed7be4..8ad2a60 100644
--- a/out/fonts/OpenSans-Bold-webfont.svg
+++ b/out/fonts/OpenSans-Bold-webfont.svg
@@ -1827,4 +1827,4 @@
 
 
 
- 
\ No newline at end of file
+
diff --git a/out/fonts/OpenSans-BoldItalic-webfont.svg b/out/fonts/OpenSans-BoldItalic-webfont.svg
index 6a2607b..d4f4960 100644
--- a/out/fonts/OpenSans-BoldItalic-webfont.svg
+++ b/out/fonts/OpenSans-BoldItalic-webfont.svg
@@ -1827,4 +1827,4 @@
 
 
 
- 
\ No newline at end of file
+
diff --git a/out/fonts/OpenSans-Italic-webfont.svg b/out/fonts/OpenSans-Italic-webfont.svg
index e1075dc..2ee9499 100644
--- a/out/fonts/OpenSans-Italic-webfont.svg
+++ b/out/fonts/OpenSans-Italic-webfont.svg
@@ -1827,4 +1827,4 @@
 
 
 
- 
\ No newline at end of file
+
diff --git a/out/fonts/OpenSans-Light-webfont.svg b/out/fonts/OpenSans-Light-webfont.svg
index 11a472c..42152bc 100644
--- a/out/fonts/OpenSans-Light-webfont.svg
+++ b/out/fonts/OpenSans-Light-webfont.svg
@@ -1828,4 +1828,4 @@
 
 
 
- 
\ No newline at end of file
+
diff --git a/out/fonts/OpenSans-LightItalic-webfont.svg b/out/fonts/OpenSans-LightItalic-webfont.svg
index 431d7e3..e9bfbc3 100644
--- a/out/fonts/OpenSans-LightItalic-webfont.svg
+++ b/out/fonts/OpenSans-LightItalic-webfont.svg
@@ -1832,4 +1832,4 @@
 
 
 
- 
\ No newline at end of file
+
diff --git a/out/fonts/OpenSans-Regular-webfont.svg b/out/fonts/OpenSans-Regular-webfont.svg
index 25a3952..bad5cca 100644
--- a/out/fonts/OpenSans-Regular-webfont.svg
+++ b/out/fonts/OpenSans-Regular-webfont.svg
@@ -1828,4 +1828,4 @@
 
 
 
- 
\ No newline at end of file
+
diff --git a/out/index.html b/out/index.html
index 790e731..d623206 100644
--- a/out/index.html
+++ b/out/index.html
@@ -19,11 +19,11 @@
 
     

Home

- - + +

@@ -37,7 +37,7 @@ - + @@ -62,4 +62,4 @@ - \ No newline at end of file + diff --git a/package.json b/package.json index 2b14539..7f13d80 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "eslint-plugin-todo-plz": "^1.2.1", "jsdoc-to-markdown": "^7.1.1", "prettier": "^2.7.1", + "typescript": "^4.9.5", "vitest": "0.23.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5ea925..2226279 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,7 @@ specifiers: prettier: ^2.7.1 redux: ^4.2.0 remeda: ^1.0.1 + typescript: ^4.9.5 updeep: ^1.2.1 vitest: 0.23.1 @@ -30,6 +31,7 @@ devDependencies: eslint-plugin-todo-plz: 1.2.1_eslint@8.22.0 jsdoc-to-markdown: 7.1.1 prettier: 2.7.1 + typescript: 4.9.5 vitest: 0.23.1_kkczkm7y7wgspdnr2rpymavxge packages: @@ -2028,6 +2030,12 @@ packages: engines: {node: '>=10'} dev: true + /typescript/4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + /typical/2.6.1: resolution: {integrity: sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==} dev: true diff --git a/src/Updux.ts b/src/Updux.ts new file mode 100644 index 0000000..eeb5984 --- /dev/null +++ b/src/Updux.ts @@ -0,0 +1 @@ +export default class Updux { } diff --git a/src/buildMiddleware/index.ts b/src/buildMiddleware/index.ts index 433c531..e1a2bd1 100644 --- a/src/buildMiddleware/index.ts +++ b/src/buildMiddleware/index.ts @@ -26,14 +26,14 @@ export function augmentMiddlewareApi(api, actions, selectors) { return result; }; - }) + }), ); Object.assign( dispatch, mapValues(actions, (action) => { return (...args) => api.dispatch(action(...args)); - }) + }), ); return { @@ -70,16 +70,16 @@ export function buildMiddleware( dux = undefined, ) { let inner = map(sub, ({ middleware }, slice) => - slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined + slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined, ).filter((x) => x); const local = effects.map((effect) => - effectToMiddleware(effect, actions, selectors) + effectToMiddleware(effect, actions, selectors), ); let mws = [...local, ...inner]; - if( wrapper ) mws = wrapper(mws,dux); + if (wrapper) mws = wrapper(mws, dux); return composeMw(mws); } diff --git a/src/buildSelectors/index.ts b/src/buildSelectors/index.ts index 3b58af7..2fba5f8 100644 --- a/src/buildSelectors/index.ts +++ b/src/buildSelectors/index.ts @@ -3,7 +3,7 @@ import { map, mapValues, merge } from 'lodash-es'; export function buildSelectors( localSelectors, splatSelector = {}, - subduxes = {} + subduxes = {}, ) { const subSelectors = map(subduxes, ({ selectors }, slice) => { if (!selectors) return {}; @@ -11,7 +11,7 @@ export function buildSelectors( return mapValues( selectors, - (func: Function) => (state) => func(state[slice]) + (func: Function) => (state) => func(state[slice]), ); }); @@ -28,8 +28,8 @@ export function buildSelectors( res, mapValues( subduxes['*'].selectors, - (selector) => () => selector(value) - ) + (selector) => () => selector(value), + ), ); }; } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5528203 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +import Updux from './Updux'; + +export default Updux; diff --git a/src/tutorial.test.ts b/src/tutorial.test.ts new file mode 100644 index 0000000..e8264a3 --- /dev/null +++ b/src/tutorial.test.ts @@ -0,0 +1,22 @@ +import Updux from './index.js'; + +const expectType = (value: T) => value; + +test('initial state', () => { + const { initial } = new Updux({ + initial: { + next_id: 1, + todos: [], + }, + }); + + expectType<{ + next_id: number; + todos: unknown[]; + }>(initial); + + expect(initial).toEqual({ + next_id: 1, + todos: [], + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7820d85 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,105 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ES2020" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + "types": [ + "vitest/globals" + ] /* Specify type package names to be included without being referenced in a source file. */, + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..1c015a0 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + isolate: false, + }, +}); From 7bb988aa541b8841f8be8ee6aab7da9873e91c86 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 2 Mar 2023 11:42:33 -0500 Subject: [PATCH 03/46] initialState --- src/Updux.ts | 18 +++++++++++++++++- src/initial.test.ts | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/initial.test.ts diff --git a/src/Updux.ts b/src/Updux.ts index eeb5984..e77f598 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -1 +1,17 @@ -export default class Updux { } +export default class Updux> { + #local_initial: T_Local_State; + + constructor( + config: Partial<{ + initial: T_Local_State; + }>, + ) { + // TODO test that we default to {} + this.#local_initial = config.initial ?? ({} as T_Local_State); + } + + // TODO memoize? + get initial() { + return this.#local_initial; + } +} diff --git a/src/initial.test.ts b/src/initial.test.ts new file mode 100644 index 0000000..082a40e --- /dev/null +++ b/src/initial.test.ts @@ -0,0 +1,15 @@ +import Updux from './Updux.js'; + +test('default', () => { + const { initial } = new Updux({}); + + expect(initial).toBeTypeOf('object'); + expect(initial).toEqual({}); +}); + +test('number', () => { + const { initial } = new Updux({ initial: 3 }); + + expect(initial).toBeTypeOf('number'); + expect(initial).toEqual(3); +}); From 455002453bd55c4101fd9594edab5f1f9e04da8b Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 2 Mar 2023 12:32:27 -0500 Subject: [PATCH 04/46] createStore and initialState --- .gitignore | 1 + Taskfile.yaml | 3 +++ package.json | 2 ++ src/Updux.ts | 29 +++++++++++++++++++++++++---- src/tutorial.test.ts | 36 +++++++++++++++++++++++++++--------- tsconfig.json | 2 +- 6 files changed, 59 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index c56c6e5..31e46d1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ pnpm-debug.log yarn-error.log GPUCache/ updux-2.0.0.tgz +pnpm-lock.yaml diff --git a/Taskfile.yaml b/Taskfile.yaml index e97b9a2..def9a12 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -3,6 +3,9 @@ version: '3' tasks: + test: vitest run src + test:dev: vitest src + lint:fix:delta: vars: FILES: diff --git a/package.json b/package.json index 7f13d80..5f5155c 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ }, "homepage": "https://github.com/yanick/updux#readme", "devDependencies": { + "@reduxjs/toolkit": "^1.9.3", "@vitest/browser": "^0.23.1", "@vitest/ui": "^0.23.1", "eslint": "^8.22.0", @@ -36,6 +37,7 @@ "eslint-plugin-todo-plz": "^1.2.1", "jsdoc-to-markdown": "^7.1.1", "prettier": "^2.7.1", + "redux-toolkit": "^1.1.2", "typescript": "^4.9.5", "vitest": "0.23.1" } diff --git a/src/Updux.ts b/src/Updux.ts index e77f598..19a5d02 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -1,17 +1,38 @@ -export default class Updux> { +import { + createStore as reduxCreateStore, + applyMiddleware, + DeepPartial, +} from 'redux'; +import { configureStore, Reducer } from '@reduxjs/toolkit'; + +export default class Updux> { #local_initial: T_Local_State; constructor( config: Partial<{ - initial: T_Local_State; + initial: T_LocalState; }>, ) { - // TODO test that we default to {} - this.#local_initial = config.initial ?? ({} as T_Local_State); + // TODO check that we can't alter the initial after the fact + this.#local_initial = config.initial ?? ({} as T_LocalState); } // TODO memoize? get initial() { return this.#local_initial; } + + createStore( + options: Partial<{ + initial: T_LocalState; + }> = {}, + ) { + const preloadedState = options.initial ?? this.initial; + const store = configureStore({ + reducer: ((state) => state) as Reducer, + preloadedState, + }); + + return store; + } } diff --git a/src/tutorial.test.ts b/src/tutorial.test.ts index e8264a3..e1dce0e 100644 --- a/src/tutorial.test.ts +++ b/src/tutorial.test.ts @@ -3,20 +3,38 @@ import Updux from './index.js'; const expectType = (value: T) => value; test('initial state', () => { - const { initial } = new Updux({ - initial: { - next_id: 1, - todos: [], - }, + const initial = { + next_id: 1, + todos: [], + }; + const dux = new Updux({ + initial, }); expectType<{ next_id: number; todos: unknown[]; - }>(initial); + }>(dux.initial); - expect(initial).toEqual({ - next_id: 1, - todos: [], + expect(dux.initial).toEqual(initial); + + const store = dux.createStore(); + + expect(store.getState()).toEqual(initial); +}); + +test('initial to createStore', () => { + const initial = { + a: 1, + b: 2, + }; + + const dux = new Updux({ + initial, + }); + + expect(dux.createStore({ initial: { a: 3, b: 4 } }).getState()).toEqual({ + a: 3, + b: 4, }); }); diff --git a/tsconfig.json b/tsconfig.json index 7820d85..d62408e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,7 @@ /* Modules */ "module": "ES2020" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ From 0cb445be3a8cc63ba9099ed968178ef678894d4a Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 2 Mar 2023 12:41:39 -0500 Subject: [PATCH 05/46] update tutorial --- docs/tutorial.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index 61d87d1..c48c9d5 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -38,7 +38,6 @@ console.log(store.getState()); // prints { next_id: 1, todos: [] } ## Add actions This is all good, but a little static. Let's add actions! - ```js const todosDux = new Updux({ initial: { From ad20f908a73099786a71829bf45d0839f4524d9f Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 2 Mar 2023 12:41:51 -0500 Subject: [PATCH 06/46] remove pnpm lock --- pnpm-lock.yaml | 2205 ------------------------------------------------ 1 file changed, 2205 deletions(-) delete mode 100644 pnpm-lock.yaml diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 2226279..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,2205 +0,0 @@ -lockfileVersion: 5.4 - -specifiers: - '@vitest/browser': ^0.23.1 - '@vitest/ui': ^0.23.1 - eslint: ^8.22.0 - eslint-plugin-no-only-tests: ^3.0.0 - eslint-plugin-todo-plz: ^1.2.1 - immer: ^9.0.15 - jsdoc-to-markdown: ^7.1.1 - json-schema-shorthand: ^2.0.0 - prettier: ^2.7.1 - redux: ^4.2.0 - remeda: ^1.0.1 - typescript: ^4.9.5 - updeep: ^1.2.1 - vitest: 0.23.1 - -dependencies: - immer: 9.0.15 - json-schema-shorthand: 2.0.0 - redux: 4.2.0 - remeda: 1.0.1 - updeep: 1.2.1 - -devDependencies: - '@vitest/browser': 0.23.1 - '@vitest/ui': 0.23.1 - eslint: 8.22.0 - eslint-plugin-no-only-tests: 3.0.0 - eslint-plugin-todo-plz: 1.2.1_eslint@8.22.0 - jsdoc-to-markdown: 7.1.1 - prettier: 2.7.1 - typescript: 4.9.5 - vitest: 0.23.1_kkczkm7y7wgspdnr2rpymavxge - -packages: - - /@babel/helper-string-parser/7.18.10: - resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-validator-identifier/7.18.6: - resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/parser/7.18.13: - resolution: {integrity: sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.18.13 - dev: true - - /@babel/runtime/7.18.9: - resolution: {integrity: sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.13.9 - dev: false - - /@babel/types/7.18.13: - resolution: {integrity: sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.18.10 - '@babel/helper-validator-identifier': 7.18.6 - to-fast-properties: 2.0.0 - dev: true - - /@esbuild/linux-loong64/0.14.54: - resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64/0.15.7: - resolution: {integrity: sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@eslint/eslintrc/1.3.0: - resolution: {integrity: sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.3.3 - globals: 13.17.0 - ignore: 5.2.0 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/config-array/0.10.4: - resolution: {integrity: sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/gitignore-to-minimatch/1.0.2: - resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==} - dev: true - - /@humanwhocodes/object-schema/1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - dev: true - - /@jspm/core/2.0.0-beta.24: - resolution: {integrity: sha512-a4Bo/80Z6CoJNor5ldgs6002utmmbttP4JYd/FJ0Ob2fVdf6O6ha5SORBCqrnDnBvMc1TlrHY7dCfat5+H0a6A==} - dev: true - - /@nodelib/fs.scandir/2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - - /@nodelib/fs.stat/2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - dev: true - - /@nodelib/fs.walk/1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.13.0 - dev: true - - /@polka/url/1.0.0-next.21: - resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} - dev: true - - /@rollup/plugin-inject/4.0.4_rollup@2.77.3: - resolution: {integrity: sha512-4pbcU4J/nS+zuHk+c+OL3WtmEQhqxlZ9uqfjQMQDOHOPld7PsCd8k5LWs8h5wjwJN7MgnAn768F2sDxEP4eNFQ==} - peerDependencies: - rollup: ^1.20.0 || ^2.0.0 - dependencies: - '@rollup/pluginutils': 3.1.0_rollup@2.77.3 - estree-walker: 2.0.2 - magic-string: 0.25.9 - rollup: 2.77.3 - dev: true - - /@rollup/pluginutils/3.1.0_rollup@2.77.3: - resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} - engines: {node: '>= 8.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0 - dependencies: - '@types/estree': 0.0.39 - estree-walker: 1.0.1 - picomatch: 2.3.1 - rollup: 2.77.3 - dev: true - - /@types/chai-subset/1.3.3: - resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} - dependencies: - '@types/chai': 4.3.3 - dev: true - - /@types/chai/4.3.3: - resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==} - dev: true - - /@types/estree/0.0.39: - resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} - dev: true - - /@types/linkify-it/3.0.2: - resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==} - dev: true - - /@types/markdown-it/12.2.3: - resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} - dependencies: - '@types/linkify-it': 3.0.2 - '@types/mdurl': 1.0.2 - dev: true - - /@types/mdurl/1.0.2: - resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} - dev: true - - /@types/node/18.7.15: - resolution: {integrity: sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==} - dev: true - - /@vitest/browser/0.23.1: - resolution: {integrity: sha512-8QLrMSlBjiRLbANid/hPmtkYViyuxLgqt1jCXvD6dnBIhghdxu5uZyg0x7xUPzDQDNts+9o+yq1WPDZbar7KNg==} - dependencies: - local-pkg: 0.4.2 - mlly: 0.5.14 - modern-node-polyfills: 0.0.9 - rollup-plugin-node-polyfills: 0.2.1 - sirv: 2.0.2 - dev: true - - /@vitest/ui/0.23.1: - resolution: {integrity: sha512-W1ygPK4aTyLTPsf6NX3gZFbH0X1ipNzlHhxTRnZ4/HpQXs/qKw5NDY45/U2yC66Fevj5kXHI1tdrNfN00NouFQ==} - dependencies: - sirv: 2.0.2 - dev: true - - /acorn-jsx/5.3.2_acorn@8.8.0: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.8.0 - dev: true - - /acorn/8.8.0: - resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /ajv/6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: true - - /ansi-escape-sequences/4.1.0: - resolution: {integrity: sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==} - engines: {node: '>=8.0.0'} - dependencies: - array-back: 3.1.0 - dev: true - - /ansi-regex/5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - dev: true - - /ansi-styles/4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: true - - /argparse/2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - - /array-back/1.0.4: - resolution: {integrity: sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==} - engines: {node: '>=0.12.0'} - dependencies: - typical: 2.6.1 - dev: true - - /array-back/2.0.0: - resolution: {integrity: sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==} - engines: {node: '>=4'} - dependencies: - typical: 2.6.1 - dev: true - - /array-back/3.1.0: - resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} - engines: {node: '>=6'} - dev: true - - /array-back/4.0.2: - resolution: {integrity: sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==} - engines: {node: '>=8'} - dev: true - - /array-back/5.0.0: - resolution: {integrity: sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==} - engines: {node: '>=10'} - dev: true - - /array-back/6.2.2: - resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} - engines: {node: '>=12.17'} - dev: true - - /array-union/2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true - - /assertion-error/1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - dev: true - - /balanced-match/1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - - /bluebird/3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - dev: true - - /brace-expansion/1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - - /braces/3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - - /cache-point/2.0.0: - resolution: {integrity: sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==} - engines: {node: '>=8'} - dependencies: - array-back: 4.0.2 - fs-then-native: 2.0.0 - mkdirp2: 1.0.5 - dev: true - - /callsites/3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true - - /catharsis/0.9.0: - resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==} - engines: {node: '>= 10'} - dependencies: - lodash: 4.17.21 - dev: true - - /chai/4.3.6: - resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==} - engines: {node: '>=4'} - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.2 - deep-eql: 3.0.1 - get-func-name: 2.0.0 - loupe: 2.3.4 - pathval: 1.1.1 - type-detect: 4.0.8 - dev: true - - /chalk/4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - - /check-error/1.0.2: - resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} - dev: true - - /collect-all/1.0.4: - resolution: {integrity: sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==} - engines: {node: '>=0.10.0'} - dependencies: - stream-connect: 1.0.2 - stream-via: 1.0.4 - dev: true - - /color-convert/2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: true - - /color-name/1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true - - /command-line-args/5.2.1: - resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} - engines: {node: '>=4.0.0'} - dependencies: - array-back: 3.1.0 - find-replace: 3.0.0 - lodash.camelcase: 4.3.0 - typical: 4.0.0 - dev: true - - /command-line-tool/0.8.0: - resolution: {integrity: sha512-Xw18HVx/QzQV3Sc5k1vy3kgtOeGmsKIqwtFFoyjI4bbcpSgnw2CWVULvtakyw4s6fhyAdI6soQQhXc2OzJy62g==} - engines: {node: '>=4.0.0'} - dependencies: - ansi-escape-sequences: 4.1.0 - array-back: 2.0.0 - command-line-args: 5.2.1 - command-line-usage: 4.1.0 - typical: 2.6.1 - dev: true - - /command-line-usage/4.1.0: - resolution: {integrity: sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==} - engines: {node: '>=4.0.0'} - dependencies: - ansi-escape-sequences: 4.1.0 - array-back: 2.0.0 - table-layout: 0.4.5 - typical: 2.6.1 - dev: true - - /common-sequence/2.0.2: - resolution: {integrity: sha512-jAg09gkdkrDO9EWTdXfv80WWH3yeZl5oT69fGfedBNS9pXUKYInVJ1bJ+/ht2+Moeei48TmSbQDYMc8EOx9G0g==} - engines: {node: '>=8'} - dev: true - - /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} - dev: true - - /config-master/3.1.0: - resolution: {integrity: sha512-n7LBL1zBzYdTpF1mx5DNcZnZn05CWIdsdvtPL4MosvqbBUK3Rq6VWEtGUuF3Y0s9/CIhMejezqlSkP6TnCJ/9g==} - dependencies: - walk-back: 2.0.1 - dev: true - - /cross-spawn/7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - - /debug/4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: true - - /deep-eql/3.0.1: - resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==} - engines: {node: '>=0.12'} - dependencies: - type-detect: 4.0.8 - dev: true - - /deep-extend/0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - dev: true - - /deep-is/0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - - /dir-glob/3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dependencies: - path-type: 4.0.0 - dev: true - - /dmd/6.1.0: - resolution: {integrity: sha512-0zQIJ873gay1scCTFZvHPWM9mVJBnaylB2NQDI8O9u8O32m00Jb6uxDKexZm8hjTRM7RiWe0FJ32pExHoXdwoQ==} - engines: {node: '>=12'} - dependencies: - array-back: 6.2.2 - cache-point: 2.0.0 - common-sequence: 2.0.2 - file-set: 4.0.2 - handlebars: 4.7.7 - marked: 4.1.0 - object-get: 2.1.1 - reduce-flatten: 3.0.1 - reduce-unique: 2.0.1 - reduce-without: 1.0.1 - test-value: 3.0.0 - walk-back: 5.1.0 - dev: true - - /doctrine/3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /entities/2.1.0: - resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} - dev: true - - /esbuild-android-64/0.14.54: - resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-64/0.15.7: - resolution: {integrity: sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-arm64/0.14.54: - resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-arm64/0.15.7: - resolution: {integrity: sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-64/0.14.54: - resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-64/0.15.7: - resolution: {integrity: sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-arm64/0.14.54: - resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-arm64/0.15.7: - resolution: {integrity: sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-64/0.14.54: - resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-64/0.15.7: - resolution: {integrity: sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-arm64/0.14.54: - resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-arm64/0.15.7: - resolution: {integrity: sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-32/0.14.54: - resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-32/0.15.7: - resolution: {integrity: sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-64/0.14.54: - resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-64/0.15.7: - resolution: {integrity: sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm/0.14.54: - resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm/0.15.7: - resolution: {integrity: sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm64/0.14.54: - resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm64/0.15.7: - resolution: {integrity: sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-mips64le/0.14.54: - resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-mips64le/0.15.7: - resolution: {integrity: sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-ppc64le/0.14.54: - resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-ppc64le/0.15.7: - resolution: {integrity: sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-riscv64/0.14.54: - resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-riscv64/0.15.7: - resolution: {integrity: sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-s390x/0.14.54: - resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-s390x/0.15.7: - resolution: {integrity: sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-netbsd-64/0.14.54: - resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-netbsd-64/0.15.7: - resolution: {integrity: sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-openbsd-64/0.14.54: - resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-openbsd-64/0.15.7: - resolution: {integrity: sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-sunos-64/0.14.54: - resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /esbuild-sunos-64/0.15.7: - resolution: {integrity: sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-32/0.14.54: - resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-32/0.15.7: - resolution: {integrity: sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-64/0.14.54: - resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-64/0.15.7: - resolution: {integrity: sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-arm64/0.14.54: - resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-arm64/0.15.7: - resolution: {integrity: sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild/0.14.54: - resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/linux-loong64': 0.14.54 - esbuild-android-64: 0.14.54 - esbuild-android-arm64: 0.14.54 - esbuild-darwin-64: 0.14.54 - esbuild-darwin-arm64: 0.14.54 - esbuild-freebsd-64: 0.14.54 - esbuild-freebsd-arm64: 0.14.54 - esbuild-linux-32: 0.14.54 - esbuild-linux-64: 0.14.54 - esbuild-linux-arm: 0.14.54 - esbuild-linux-arm64: 0.14.54 - esbuild-linux-mips64le: 0.14.54 - esbuild-linux-ppc64le: 0.14.54 - esbuild-linux-riscv64: 0.14.54 - esbuild-linux-s390x: 0.14.54 - esbuild-netbsd-64: 0.14.54 - esbuild-openbsd-64: 0.14.54 - esbuild-sunos-64: 0.14.54 - esbuild-windows-32: 0.14.54 - esbuild-windows-64: 0.14.54 - esbuild-windows-arm64: 0.14.54 - dev: true - - /esbuild/0.15.7: - resolution: {integrity: sha512-7V8tzllIbAQV1M4QoE52ImKu8hT/NLGlGXkiDsbEU5PS6K8Mn09ZnYoS+dcmHxOS9CRsV4IRAMdT3I67IyUNXw==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/linux-loong64': 0.15.7 - esbuild-android-64: 0.15.7 - esbuild-android-arm64: 0.15.7 - esbuild-darwin-64: 0.15.7 - esbuild-darwin-arm64: 0.15.7 - esbuild-freebsd-64: 0.15.7 - esbuild-freebsd-arm64: 0.15.7 - esbuild-linux-32: 0.15.7 - esbuild-linux-64: 0.15.7 - esbuild-linux-arm: 0.15.7 - esbuild-linux-arm64: 0.15.7 - esbuild-linux-mips64le: 0.15.7 - esbuild-linux-ppc64le: 0.15.7 - esbuild-linux-riscv64: 0.15.7 - esbuild-linux-s390x: 0.15.7 - esbuild-netbsd-64: 0.15.7 - esbuild-openbsd-64: 0.15.7 - esbuild-sunos-64: 0.15.7 - esbuild-windows-32: 0.15.7 - esbuild-windows-64: 0.15.7 - esbuild-windows-arm64: 0.15.7 - dev: true - - /escape-string-regexp/2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - dev: true - - /escape-string-regexp/4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true - - /eslint-plugin-no-only-tests/3.0.0: - resolution: {integrity: sha512-I0PeXMs1vu21ap45hey4HQCJRqpcoIvGcNTPJe+UhUm8TwjQ6//mCrDqF8q0WS6LgmRDwQ4ovQej0AQsAHb5yg==} - engines: {node: '>=5.0.0'} - dev: true - - /eslint-plugin-todo-plz/1.2.1_eslint@8.22.0: - resolution: {integrity: sha512-yhmbfrAOvSupcoWcfm9V6tOXihPNAGp9NiUD4xiMgxUyARSIQRBpi0cOFgcmj1GXC9KCoxFX7oMAGfdSovY60A==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=7.3.0' - dependencies: - eslint: 8.22.0 - dev: true - - /eslint-scope/7.1.1: - resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - - /eslint-utils/3.0.0_eslint@8.22.0: - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} - peerDependencies: - eslint: '>=5' - dependencies: - eslint: 8.22.0 - eslint-visitor-keys: 2.1.0 - dev: true - - /eslint-visitor-keys/2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - dev: true - - /eslint-visitor-keys/3.3.0: - resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /eslint/8.22.0: - resolution: {integrity: sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint/eslintrc': 1.3.0 - '@humanwhocodes/config-array': 0.10.4 - '@humanwhocodes/gitignore-to-minimatch': 1.0.2 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.22.0 - eslint-visitor-keys: 3.3.0 - espree: 9.3.3 - esquery: 1.4.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - functional-red-black-tree: 1.0.1 - glob-parent: 6.0.2 - globals: 13.17.0 - globby: 11.1.0 - grapheme-splitter: 1.0.4 - ignore: 5.2.0 - import-fresh: 3.3.0 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.1 - regexpp: 3.2.0 - strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 - text-table: 0.2.0 - v8-compile-cache: 2.3.0 - transitivePeerDependencies: - - supports-color - dev: true - - /espree/9.3.3: - resolution: {integrity: sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.8.0 - acorn-jsx: 5.3.2_acorn@8.8.0 - eslint-visitor-keys: 3.3.0 - dev: true - - /esquery/1.4.0: - resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - - /esrecurse/4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - - /estraverse/5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true - - /estree-walker/0.6.1: - resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} - dev: true - - /estree-walker/1.0.1: - resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} - dev: true - - /estree-walker/2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true - - /esutils/2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - - /fast-deep-equal/3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true - - /fast-glob/3.2.11: - resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==} - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - dev: true - - /fast-json-stable-stringify/2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true - - /fast-levenshtein/2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true - - /fastq/1.13.0: - resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} - dependencies: - reusify: 1.0.4 - dev: true - - /file-entry-cache/6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.0.4 - dev: true - - /file-set/4.0.2: - resolution: {integrity: sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==} - engines: {node: '>=10'} - dependencies: - array-back: 5.0.0 - glob: 7.2.3 - dev: true - - /fill-range/7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - - /find-replace/3.0.0: - resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} - engines: {node: '>=4.0.0'} - dependencies: - array-back: 3.1.0 - dev: true - - /find-up/5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true - - /flat-cache/3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flatted: 3.2.7 - rimraf: 3.0.2 - dev: true - - /flatted/3.2.7: - resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} - dev: true - - /fs-then-native/2.0.0: - resolution: {integrity: sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==} - engines: {node: '>=4.0.0'} - dev: true - - /fs.realpath/1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true - - /fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /function-bind/1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: true - - /functional-red-black-tree/1.0.1: - resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} - dev: true - - /get-func-name/2.0.0: - resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} - dev: true - - /glob-parent/5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob-parent/6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob/7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /globals/13.17.0: - resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true - - /globby/11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.2.11 - ignore: 5.2.0 - merge2: 1.4.1 - slash: 3.0.0 - dev: true - - /graceful-fs/4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - dev: true - - /grapheme-splitter/1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - dev: true - - /handlebars/4.7.7: - resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} - engines: {node: '>=0.4.7'} - hasBin: true - dependencies: - minimist: 1.2.6 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.17.0 - dev: true - - /has-flag/4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - dev: true - - /has/1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - dev: true - - /ignore/5.2.0: - resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} - engines: {node: '>= 4'} - dev: true - - /immer/9.0.15: - resolution: {integrity: sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==} - dev: false - - /import-fresh/3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: true - - /imurmurhash/0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true - - /inflight/1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true - - /inherits/2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - - /is-core-module/2.10.0: - resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} - dependencies: - has: 1.0.3 - dev: true - - /is-extglob/2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: true - - /is-glob/4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - - /is-number/7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true - - /isexe/2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - - /js-yaml/4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - - /js2xmlparser/4.0.2: - resolution: {integrity: sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==} - dependencies: - xmlcreate: 2.0.4 - dev: true - - /jsdoc-api/7.1.1: - resolution: {integrity: sha512-0pkuPCzVXiqsDAsVrNFXCkHzlyNepBIDVtwwehry4RJAnZmXtlAz7rh8F9FRz53u3NeynGbex+bpYWwi8lE66A==} - engines: {node: '>=12.17'} - dependencies: - array-back: 6.2.2 - cache-point: 2.0.0 - collect-all: 1.0.4 - file-set: 4.0.2 - fs-then-native: 2.0.0 - jsdoc: 3.6.11 - object-to-spawn-args: 2.0.1 - temp-path: 1.0.0 - walk-back: 5.1.0 - dev: true - - /jsdoc-parse/6.1.0: - resolution: {integrity: sha512-n/hDGQJa69IBun1yZAjqzV4gVR41+flZ3bIlm9fKvNe2Xjsd1/+zCo2+R9ls8LxtePgIWbpA1jU7xkB2lRdLLg==} - engines: {node: '>=12'} - dependencies: - array-back: 6.2.2 - lodash.omit: 4.5.0 - lodash.pick: 4.4.0 - reduce-extract: 1.0.0 - sort-array: 4.1.5 - test-value: 3.0.0 - dev: true - - /jsdoc-to-markdown/7.1.1: - resolution: {integrity: sha512-CI86d63xAVNO+ENumWwmJ034lYe5iGU5GwjtTA11EuphP9tpnoi4hrKgR/J8uME0D+o4KUpVfwX1fjZhc8dEtg==} - engines: {node: '>=12.17'} - hasBin: true - dependencies: - array-back: 6.2.2 - command-line-tool: 0.8.0 - config-master: 3.1.0 - dmd: 6.1.0 - jsdoc-api: 7.1.1 - jsdoc-parse: 6.1.0 - walk-back: 5.1.0 - dev: true - - /jsdoc/3.6.11: - resolution: {integrity: sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==} - engines: {node: '>=12.0.0'} - hasBin: true - dependencies: - '@babel/parser': 7.18.13 - '@types/markdown-it': 12.2.3 - bluebird: 3.7.2 - catharsis: 0.9.0 - escape-string-regexp: 2.0.0 - js2xmlparser: 4.0.2 - klaw: 3.0.0 - markdown-it: 12.3.2 - markdown-it-anchor: 8.6.4_2zb4u3vubltivolgu556vv4aom - marked: 4.1.0 - mkdirp: 1.0.4 - requizzle: 0.2.3 - strip-json-comments: 3.1.1 - taffydb: 2.6.2 - underscore: 1.13.4 - dev: true - - /json-schema-shorthand/2.0.0: - resolution: {integrity: sha512-tA1EoSDLDdQEv396fLdlzU3WPSVdN2MVMivfNX0Fdm6woEH4mNX3nRGj//wi/VvWDDOpLiN5w9TSlKIXbItiHg==} - dependencies: - lodash: 4.17.21 - updeep: 1.2.1 - dev: false - - /json-schema-traverse/0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true - - /json-stable-stringify-without-jsonify/1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true - - /jsonc-parser/3.2.0: - resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} - dev: true - - /klaw/3.0.0: - resolution: {integrity: sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==} - dependencies: - graceful-fs: 4.2.10 - dev: true - - /levn/0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /linkify-it/3.0.3: - resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} - dependencies: - uc.micro: 1.0.6 - dev: true - - /local-pkg/0.4.2: - resolution: {integrity: sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==} - engines: {node: '>=14'} - dev: true - - /locate-path/6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - dev: true - - /lodash.camelcase/4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - dev: true - - /lodash.merge/4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true - - /lodash.omit/4.5.0: - resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==} - dev: true - - /lodash.padend/4.6.1: - resolution: {integrity: sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==} - dev: true - - /lodash.pick/4.4.0: - resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} - dev: true - - /lodash/4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - /loupe/2.3.4: - resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==} - dependencies: - get-func-name: 2.0.0 - dev: true - - /magic-string/0.25.9: - resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - dependencies: - sourcemap-codec: 1.4.8 - dev: true - - /markdown-it-anchor/8.6.4_2zb4u3vubltivolgu556vv4aom: - resolution: {integrity: sha512-Ul4YVYZNxMJYALpKtu+ZRdrryYt/GlQ5CK+4l1bp/gWXOG2QWElt6AqF3Mih/wfUKdZbNAZVXGR73/n6U/8img==} - peerDependencies: - '@types/markdown-it': '*' - markdown-it: '*' - dependencies: - '@types/markdown-it': 12.2.3 - markdown-it: 12.3.2 - dev: true - - /markdown-it/12.3.2: - resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} - hasBin: true - dependencies: - argparse: 2.0.1 - entities: 2.1.0 - linkify-it: 3.0.3 - mdurl: 1.0.1 - uc.micro: 1.0.6 - dev: true - - /marked/4.1.0: - resolution: {integrity: sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==} - engines: {node: '>= 12'} - hasBin: true - dev: true - - /mdurl/1.0.1: - resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} - dev: true - - /merge2/1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - dev: true - - /micromatch/4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - dev: true - - /minimatch/3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: true - - /minimist/1.2.6: - resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} - dev: true - - /mkdirp/1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - dev: true - - /mkdirp2/1.0.5: - resolution: {integrity: sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==} - dev: true - - /mlly/0.5.14: - resolution: {integrity: sha512-DgRgNUSX9NIxxCxygX4Xeg9C7GX7OUx1wuQ8cXx9o9LE0e9wrH+OZ9fcnrlEedsC/rtqry3ZhUddC759XD/L0w==} - dependencies: - acorn: 8.8.0 - pathe: 0.3.7 - pkg-types: 0.3.5 - ufo: 0.8.5 - dev: true - - /modern-node-polyfills/0.0.9: - resolution: {integrity: sha512-Z7sFaWAHCEAw44Ww1L0JEt4BaQ7/LVTbbqTtm3bNSfdWs0ZW7QwRN7Xy8RjSPOlZ9ZSkoXAa54neuvAC6KGRFg==} - dependencies: - '@jspm/core': 2.0.0-beta.24 - '@rollup/plugin-inject': 4.0.4_rollup@2.77.3 - acorn: 8.8.0 - esbuild: 0.14.54 - local-pkg: 0.4.2 - rollup: 2.77.3 - dev: true - - /mrmime/1.0.1: - resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} - engines: {node: '>=10'} - dev: true - - /ms/2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true - - /nanoid/3.3.4: - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - - /natural-compare/1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true - - /neo-async/2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - dev: true - - /object-get/2.1.1: - resolution: {integrity: sha512-7n4IpLMzGGcLEMiQKsNR7vCe+N5E9LORFrtNUVy4sO3dj9a3HedZCxEL2T7QuLhcHN1NBuBsMOKaOsAYI9IIvg==} - dev: true - - /object-to-spawn-args/2.0.1: - resolution: {integrity: sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==} - engines: {node: '>=8.0.0'} - dev: true - - /once/1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: true - - /optionator/0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} - engines: {node: '>= 0.8.0'} - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.3 - dev: true - - /p-limit/3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - dev: true - - /p-locate/5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - dev: true - - /parent-module/1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: true - - /path-exists/4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true - - /path-is-absolute/1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true - - /path-key/3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true - - /path-parse/1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true - - /path-type/4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: true - - /pathe/0.3.7: - resolution: {integrity: sha512-yz7GK+kSsS27x727jtXpd5VT4dDfP/JDIQmaowfxyWCnFjOWtE1VIh7i6TzcSfzW0n4+bRQztj1VdKnITNq/MA==} - dev: true - - /pathval/1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - dev: true - - /picocolors/1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true - - /picomatch/2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: true - - /pkg-types/0.3.5: - resolution: {integrity: sha512-VkxCBFVgQhNHYk9subx+HOhZ4jzynH11ah63LZsprTKwPCWG9pfWBlkElWFbvkP9BVR0dP1jS9xPdhaHQNK74Q==} - dependencies: - jsonc-parser: 3.2.0 - mlly: 0.5.14 - pathe: 0.3.7 - dev: true - - /postcss/8.4.16: - resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.4 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - - /prelude-ls/1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true - - /prettier/2.7.1: - resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: true - - /punycode/2.1.1: - resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} - engines: {node: '>=6'} - dev: true - - /queue-microtask/1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true - - /reduce-extract/1.0.0: - resolution: {integrity: sha512-QF8vjWx3wnRSL5uFMyCjDeDc5EBMiryoT9tz94VvgjKfzecHAVnqmXAwQDcr7X4JmLc2cjkjFGCVzhMqDjgR9g==} - engines: {node: '>=0.10.0'} - dependencies: - test-value: 1.1.0 - dev: true - - /reduce-flatten/1.0.1: - resolution: {integrity: sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==} - engines: {node: '>=0.10.0'} - dev: true - - /reduce-flatten/3.0.1: - resolution: {integrity: sha512-bYo+97BmUUOzg09XwfkwALt4PQH1M5L0wzKerBt6WLm3Fhdd43mMS89HiT1B9pJIqko/6lWx3OnV4J9f2Kqp5Q==} - engines: {node: '>=8'} - dev: true - - /reduce-unique/2.0.1: - resolution: {integrity: sha512-x4jH/8L1eyZGR785WY+ePtyMNhycl1N2XOLxhCbzZFaqF4AXjLzqSxa2UHgJ2ZVR/HHyPOvl1L7xRnW8ye5MdA==} - engines: {node: '>=6'} - dev: true - - /reduce-without/1.0.1: - resolution: {integrity: sha512-zQv5y/cf85sxvdrKPlfcRzlDn/OqKFThNimYmsS3flmkioKvkUGn2Qg9cJVoQiEvdxFGLE0MQER/9fZ9sUqdxg==} - engines: {node: '>=0.10.0'} - dependencies: - test-value: 2.1.0 - dev: true - - /redux/4.2.0: - resolution: {integrity: sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==} - dependencies: - '@babel/runtime': 7.18.9 - dev: false - - /regenerator-runtime/0.13.9: - resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} - dev: false - - /regexpp/3.2.0: - resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} - engines: {node: '>=8'} - dev: true - - /remeda/1.0.1: - resolution: {integrity: sha512-W/YDzHzIDDFnD9g5fKmvKSnV7uFvDV6EnM+dfd2iCQdhTyttvhcBkK3YTaxIikefr9UkAsxHyxdZWjhbWZ7x8g==} - dev: false - - /requizzle/0.2.3: - resolution: {integrity: sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==} - dependencies: - lodash: 4.17.21 - dev: true - - /resolve-from/4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true - - /resolve/1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} - hasBin: true - dependencies: - is-core-module: 2.10.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - - /reusify/1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true - - /rimraf/3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - - /rollup-plugin-inject/3.0.2: - resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} - deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. - dependencies: - estree-walker: 0.6.1 - magic-string: 0.25.9 - rollup-pluginutils: 2.8.2 - dev: true - - /rollup-plugin-node-polyfills/0.2.1: - resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==} - dependencies: - rollup-plugin-inject: 3.0.2 - dev: true - - /rollup-pluginutils/2.8.2: - resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} - dependencies: - estree-walker: 0.6.1 - dev: true - - /rollup/2.77.3: - resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==} - engines: {node: '>=10.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /rollup/2.78.1: - resolution: {integrity: sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==} - engines: {node: '>=10.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /run-parallel/1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: true - - /shebang-command/2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true - - /shebang-regex/3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true - - /sirv/2.0.2: - resolution: {integrity: sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==} - engines: {node: '>= 10'} - dependencies: - '@polka/url': 1.0.0-next.21 - mrmime: 1.0.1 - totalist: 3.0.0 - dev: true - - /slash/3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: true - - /sort-array/4.1.5: - resolution: {integrity: sha512-Ya4peoS1fgFN42RN1REk2FgdNOeLIEMKFGJvs7VTP3OklF8+kl2SkpVliZ4tk/PurWsrWRsdNdU+tgyOBkB9sA==} - engines: {node: '>=10'} - dependencies: - array-back: 5.0.0 - typical: 6.0.1 - dev: true - - /source-map-js/1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - dev: true - - /source-map/0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - dev: true - - /sourcemap-codec/1.4.8: - resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} - dev: true - - /stream-connect/1.0.2: - resolution: {integrity: sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==} - engines: {node: '>=0.10.0'} - dependencies: - array-back: 1.0.4 - dev: true - - /stream-via/1.0.4: - resolution: {integrity: sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==} - engines: {node: '>=0.10.0'} - dev: true - - /strip-ansi/6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - dev: true - - /strip-json-comments/3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true - - /strip-literal/0.4.0: - resolution: {integrity: sha512-ql/sBDoJOybTKSIOWrrh8kgUEMjXMwRAkZTD0EwiwxQH/6tTPkZvMIEjp0CRlpi6V5FMiJyvxeRkEi1KrGISoA==} - dependencies: - acorn: 8.8.0 - dev: true - - /supports-color/7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - dev: true - - /supports-preserve-symlinks-flag/1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: true - - /table-layout/0.4.5: - resolution: {integrity: sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==} - engines: {node: '>=4.0.0'} - dependencies: - array-back: 2.0.0 - deep-extend: 0.6.0 - lodash.padend: 4.6.1 - typical: 2.6.1 - wordwrapjs: 3.0.0 - dev: true - - /taffydb/2.6.2: - resolution: {integrity: sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=} - dev: true - - /temp-path/1.0.0: - resolution: {integrity: sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==} - dev: true - - /test-value/1.1.0: - resolution: {integrity: sha512-wrsbRo7qP+2Je8x8DsK8ovCGyxe3sYfQwOraIY/09A2gFXU9DYKiTF14W4ki/01AEh56kMzAmlj9CaHGDDUBJA==} - engines: {node: '>=0.10.0'} - dependencies: - array-back: 1.0.4 - typical: 2.6.1 - dev: true - - /test-value/2.1.0: - resolution: {integrity: sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==} - engines: {node: '>=0.10.0'} - dependencies: - array-back: 1.0.4 - typical: 2.6.1 - dev: true - - /test-value/3.0.0: - resolution: {integrity: sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==} - engines: {node: '>=4.0.0'} - dependencies: - array-back: 2.0.0 - typical: 2.6.1 - dev: true - - /text-table/0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true - - /tinybench/2.1.5: - resolution: {integrity: sha512-ak+PZZEuH3mw6CCFOgf5S90YH0MARnZNhxjhjguAmoJimEMAJuNip/rJRd6/wyylHItomVpKTzZk9zrhTrQCoQ==} - dev: true - - /tinypool/0.2.4: - resolution: {integrity: sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==} - engines: {node: '>=14.0.0'} - dev: true - - /tinyspy/1.0.2: - resolution: {integrity: sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==} - engines: {node: '>=14.0.0'} - dev: true - - /to-fast-properties/2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - dev: true - - /to-regex-range/5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: true - - /totalist/3.0.0: - resolution: {integrity: sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==} - engines: {node: '>=6'} - dev: true - - /type-check/0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: true - - /type-detect/4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - dev: true - - /type-fest/0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true - - /typescript/4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true - dev: true - - /typical/2.6.1: - resolution: {integrity: sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==} - dev: true - - /typical/4.0.0: - resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} - engines: {node: '>=8'} - dev: true - - /typical/6.0.1: - resolution: {integrity: sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==} - engines: {node: '>=10'} - dev: true - - /uc.micro/1.0.6: - resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} - dev: true - - /ufo/0.8.5: - resolution: {integrity: sha512-e4+UtA5IRO+ha6hYklwj6r7BjiGMxS0O+UaSg9HbaTefg4kMkzj4tXzEBajRR+wkxf+golgAWKzLbytCUDMJAA==} - dev: true - - /uglify-js/3.17.0: - resolution: {integrity: sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg==} - engines: {node: '>=0.8.0'} - hasBin: true - requiresBuild: true - dev: true - optional: true - - /underscore/1.13.4: - resolution: {integrity: sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==} - dev: true - - /updeep/1.2.1: - resolution: {integrity: sha512-cqWsgE1DVNkUeKW+1OfnftBNtSXnxep4aj8GS5oI0dkSfOIU1T6N3vADLhp9EtFPpmmCBHKMQAtsr2b2KY9Lyg==} - dependencies: - lodash: 4.17.21 - dev: false - - /uri-js/4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.1.1 - dev: true - - /v8-compile-cache/2.3.0: - resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} - dev: true - - /vite/3.1.0: - resolution: {integrity: sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - peerDependencies: - less: '*' - sass: '*' - stylus: '*' - terser: ^5.4.0 - peerDependenciesMeta: - less: - optional: true - sass: - optional: true - stylus: - optional: true - terser: - optional: true - dependencies: - esbuild: 0.15.7 - postcss: 8.4.16 - resolve: 1.22.1 - rollup: 2.78.1 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /vitest/0.23.1_kkczkm7y7wgspdnr2rpymavxge: - resolution: {integrity: sha512-kn9pG+h6VA3yj/xRvwgLKEd33rOlzMqJEg3tl5HSm3WUPlkY1Lr1FK8RN1uIqVKvFxmz6HGU3EQW+xW2kazRkQ==} - engines: {node: '>=v14.16.0'} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - dependencies: - '@types/chai': 4.3.3 - '@types/chai-subset': 1.3.3 - '@types/node': 18.7.15 - '@vitest/browser': 0.23.1 - '@vitest/ui': 0.23.1 - chai: 4.3.6 - debug: 4.3.4 - local-pkg: 0.4.2 - strip-literal: 0.4.0 - tinybench: 2.1.5 - tinypool: 0.2.4 - tinyspy: 1.0.2 - vite: 3.1.0 - transitivePeerDependencies: - - less - - sass - - stylus - - supports-color - - terser - dev: true - - /walk-back/2.0.1: - resolution: {integrity: sha512-Nb6GvBR8UWX1D+Le+xUq0+Q1kFmRBIWVrfLnQAOmcpEzA9oAxwJ9gIr36t9TWYfzvWRvuMtjHiVsJYEkXWaTAQ==} - engines: {node: '>=0.10.0'} - dev: true - - /walk-back/5.1.0: - resolution: {integrity: sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==} - engines: {node: '>=12.17'} - dev: true - - /which/2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - - /word-wrap/1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} - engines: {node: '>=0.10.0'} - dev: true - - /wordwrap/1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - dev: true - - /wordwrapjs/3.0.0: - resolution: {integrity: sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==} - engines: {node: '>=4.0.0'} - dependencies: - reduce-flatten: 1.0.1 - typical: 2.6.1 - dev: true - - /wrappy/1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true - - /xmlcreate/2.0.4: - resolution: {integrity: sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==} - dev: true - - /yocto-queue/0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true From 90a2171cc775d7fd49337ffb7d62a8fb87600d7b Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 2 Mar 2023 13:10:20 -0500 Subject: [PATCH 07/46] add actions test --- docs/tutorial.md | 32 ++++++++++++++++++++------------ src/index.ts | 2 ++ src/tutorial.test.ts | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index c48c9d5..83e7dc6 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -4,22 +4,23 @@ This tutorial walks you through the features of `Updux` using the time-honored example of the implementation of Todo list store. We'll be using -[updeep](https://www.npmjs.com/package/updeep) to +[@yanick/updeep-remeda](https://www.npmjs.com/package/@yanick/updeep-remeda) to help with immutability and deep merging, but that's totally optional. If `updeep` is not your bag, -it can easily be substitued with, say, [immer][], [lodash][], or even +it can easily be substitued with, say, [immer][], +[remeda][], +[lodash][], or even plain JavaScript. ## Definition of the state To begin with, let's define that has nothing but an initial state. - ```js import Updux from 'updux'; const todosDux = new Updux({ initial: { - next_id: 1, + nextId: 1, todos: [], } }); @@ -32,21 +33,26 @@ initial state will be automatically set: ```js const store = todosDux.createStore(); -console.log(store.getState()); // prints { next_id: 1, todos: [] } +console.log(store.getState()); // prints { nextId: 1, todos: [] } ``` ## Add actions This is all good, but a little static. Let's add actions! ```js +import { createAction } from 'updux'; + +const addTodo = createAction('addTodo'); +const todoDone = createAction('todoDone'); + const todosDux = new Updux({ initial: { - next_id: 1, + nextId: 1, todos: [], }, - { - addTodo: null, - todoDone: null, + actions: { + addTodo, + todoDone, } }); ``` @@ -54,10 +60,11 @@ const todosDux = new Updux({ ### Accessing actions Once an action is defined, its creator is accessible via the `actions` accessor. - +This is not yet terribly exciting, but it'll get better once we begin using +subduxes. ```js console.log( todosDux.actions.addTodo('write tutorial') ); - // prints { type: 'addTodo', payload: 'write tutorial' } + // => { type: 'addTodo', payload: 'write tutorial' } ``` ### Adding a mutation @@ -65,7 +72,7 @@ Mutations are the reducing functions associated to actions. They are defined via the `setMutation` method: - ```js +```js todosDux.setMutation( 'addTodo', description => ({next_id: id, todos}) => ({ next_id: 1 + id, todos: [...todos, { description, id, done: false }] @@ -403,3 +410,4 @@ const myDux = new Updux({ [immer]: https://www.npmjs.com/package/immer [lodash]: https://www.npmjs.com/package/lodash [ts-action]: https://www.npmjs.com/package/ts-action +[remeda]: remedajs.com/ diff --git a/src/index.ts b/src/index.ts index 5528203..9cb6805 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ import Updux from './Updux'; +export { createAction } from '@reduxjs/toolkit'; + export default Updux; diff --git a/src/tutorial.test.ts b/src/tutorial.test.ts index e1dce0e..86585dc 100644 --- a/src/tutorial.test.ts +++ b/src/tutorial.test.ts @@ -38,3 +38,20 @@ test('initial to createStore', () => { b: 4, }); }); + +test.todo('actions', () => { + const addTodo = createAction('addTodo'); + const todoDone = createAction('todoDone'); + + const todosDux = new Updux({ + actions: { + addTodo, + todoDone, + }, + }); + + expect(todosDux.actions.addTodo('write tutorial')).toEqual({ + type: 'addTodo', + payload: 'write tutorial', + }); +}); From ea724ab73b4f561d0d4dbcb730015a2e883c468b Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 2 Mar 2023 13:19:31 -0500 Subject: [PATCH 08/46] actions are in --- src/Updux.ts | 20 +++++++++++++++----- src/actions.ts | 5 +++++ src/index.ts | 2 +- src/initial.test.ts | 16 ++++++++++++++++ src/tutorial.test.ts | 22 +++------------------- 5 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 src/actions.ts diff --git a/src/Updux.ts b/src/Updux.ts index 19a5d02..56b9e4c 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -5,21 +5,31 @@ import { } from 'redux'; import { configureStore, Reducer } from '@reduxjs/toolkit'; -export default class Updux> { - #local_initial: T_Local_State; +export default class Updux< + T_LocalState = Record, + T_LocalActions = {}, +> { + #localInitial: T_LocalState; + #localActions: T_LocalActions; constructor( config: Partial<{ initial: T_LocalState; + actions: T_LocalActions; }>, ) { // TODO check that we can't alter the initial after the fact - this.#local_initial = config.initial ?? ({} as T_LocalState); + this.#localInitial = config.initial ?? ({} as T_LocalState); + this.#localActions = config.actions ?? ({} as T_LocalActions); } // TODO memoize? get initial() { - return this.#local_initial; + return this.#localInitial; + } + + get actions() { + return this.#localActions; } createStore( @@ -27,7 +37,7 @@ export default class Updux> { initial: T_LocalState; }> = {}, ) { - const preloadedState = options.initial ?? this.initial; + const preloadedState: any = options.initial ?? this.initial; const store = configureStore({ reducer: ((state) => state) as Reducer, preloadedState, diff --git a/src/actions.ts b/src/actions.ts new file mode 100644 index 0000000..f19ced8 --- /dev/null +++ b/src/actions.ts @@ -0,0 +1,5 @@ +export { createAction } from '@reduxjs/toolkit'; + +export const withPayload = +

() => + (payload: P) => ({ payload }); diff --git a/src/index.ts b/src/index.ts index 9cb6805..e542a2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import Updux from './Updux'; -export { createAction } from '@reduxjs/toolkit'; +export { withPayload, createAction } from './actions'; export default Updux; diff --git a/src/initial.test.ts b/src/initial.test.ts index 082a40e..32194e6 100644 --- a/src/initial.test.ts +++ b/src/initial.test.ts @@ -13,3 +13,19 @@ test('number', () => { expect(initial).toBeTypeOf('number'); expect(initial).toEqual(3); }); + +test('initial to createStore', () => { + const initial = { + a: 1, + b: 2, + }; + + const dux = new Updux({ + initial, + }); + + expect(dux.createStore({ initial: { a: 3, b: 4 } }).getState()).toEqual({ + a: 3, + b: 4, + }); +}); diff --git a/src/tutorial.test.ts b/src/tutorial.test.ts index 86585dc..d1992af 100644 --- a/src/tutorial.test.ts +++ b/src/tutorial.test.ts @@ -1,4 +1,4 @@ -import Updux from './index.js'; +import Updux, { createAction, withPayload } from './index.js'; const expectType = (value: T) => value; @@ -23,24 +23,8 @@ test('initial state', () => { expect(store.getState()).toEqual(initial); }); -test('initial to createStore', () => { - const initial = { - a: 1, - b: 2, - }; - - const dux = new Updux({ - initial, - }); - - expect(dux.createStore({ initial: { a: 3, b: 4 } }).getState()).toEqual({ - a: 3, - b: 4, - }); -}); - -test.todo('actions', () => { - const addTodo = createAction('addTodo'); +test.only('actions', () => { + const addTodo = createAction('addTodo', withPayload()); const todoDone = createAction('todoDone'); const todosDux = new Updux({ From 29d3d9a2aa11f1d3a0c56074c191ae8263fabf91 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 2 Mar 2023 14:03:01 -0500 Subject: [PATCH 09/46] doc mutations --- docs/tutorial.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index 83e7dc6..6ab9599 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -69,11 +69,10 @@ console.log( todosDux.actions.addTodo('write tutorial') ); ### Adding a mutation Mutations are the reducing functions associated to actions. They -are defined via the `setMutation` method: - +are defined via the `mutation` method: ```js -todosDux.setMutation( 'addTodo', description => ({next_id: id, todos}) => ({ +todosDux.mutation( addTodo, description => ({next_id: id, todos}) => ({ next_id: 1 + id, todos: [...todos, { description, id, done: false }] })); From 80d18703c476841deb95e0d979453e0d25a1fc31 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 6 Mar 2023 10:09:39 -0500 Subject: [PATCH 10/46] wip --- docs/tutorial.md | 8 ++++---- package.json | 1 + src/Updux.ts | 26 +++++++++++++++++++++++++- src/tutorial.test.ts | 22 +++++++++++++++++++++- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index 6ab9599..387200e 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -72,10 +72,10 @@ Mutations are the reducing functions associated to actions. They are defined via the `mutation` method: ```js -todosDux.mutation( addTodo, description => ({next_id: id, todos}) => ({ - next_id: 1 + id, - todos: [...todos, { description, id, done: false }] -})); + dux.mutation(addTodo, (state, description) => { + state.todos.unshift({ description, id: state.nextId, done: false }); + state.nextId++; + }); ``` ## Effects diff --git a/package.json b/package.json index 5f5155c..2a05480 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "type": "module", "dependencies": { + "@yanick/updeep-remeda": "^2.1.0", "immer": "^9.0.15", "json-schema-shorthand": "^2.0.0", "redux": "^4.2.0", diff --git a/src/Updux.ts b/src/Updux.ts index 56b9e4c..59c1456 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -2,8 +2,20 @@ import { createStore as reduxCreateStore, applyMiddleware, DeepPartial, + Action, } from 'redux'; -import { configureStore, Reducer } from '@reduxjs/toolkit'; +import { configureStore, Reducer, createAction } from '@reduxjs/toolkit'; +import { withPayload } from './actions'; + +type ActionCreator = ReturnType; + +type AggregateState = L; + +type Mutation = ( + state: S, + payload: ReturnType['payload'], + action: ReturnType, +) => S | void; export default class Updux< T_LocalState = Record, @@ -11,6 +23,10 @@ export default class Updux< > { #localInitial: T_LocalState; #localActions: T_LocalActions; + #localMutations: Record< + string, + Mutation> + > = {}; constructor( config: Partial<{ @@ -45,4 +61,12 @@ export default class Updux< return store; } + + // TODO force the actionCreator to be one of the actions? + mutation( + actionCreator: A, + mutation: Mutation>, + ) { + this.#localMutations[actionCreator.type] = mutation; + } } diff --git a/src/tutorial.test.ts b/src/tutorial.test.ts index d1992af..753e746 100644 --- a/src/tutorial.test.ts +++ b/src/tutorial.test.ts @@ -1,4 +1,5 @@ import Updux, { createAction, withPayload } from './index.js'; +import u from '@yanick/updeep-remeda'; const expectType = (value: T) => value; @@ -23,7 +24,7 @@ test('initial state', () => { expect(store.getState()).toEqual(initial); }); -test.only('actions', () => { +test('actions', () => { const addTodo = createAction('addTodo', withPayload()); const todoDone = createAction('todoDone'); @@ -39,3 +40,22 @@ test.only('actions', () => { payload: 'write tutorial', }); }); + +test('mutation', () => { + const addTodo = createAction('addTodo', withPayload()); + + type Todo = { + description: string; + id: number; + done: boolean; + }; + + const dux = new Updux({ + initial: { nextId: 0, todos: [] as Todo[] }, + }); + + dux.mutation(addTodo, (state, description) => { + state.todos.unshift({ description, id: state.nextId, done: false }); + state.nextId++; + }); +}); From a27865d36558de67046481c5dcab496c8f57b62d Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 6 Mar 2023 12:14:25 -0500 Subject: [PATCH 11/46] move index.ts to todos --- src/buildMiddleware/index.js | 48 +++++++++++++++++++ .../{index.ts => index.ts.todo} | 0 src/buildSelectors/index.js | 20 ++++++++ .../{index.ts => index.ts.todo} | 0 4 files changed, 68 insertions(+) create mode 100644 src/buildMiddleware/index.js rename src/buildMiddleware/{index.ts => index.ts.todo} (100%) create mode 100644 src/buildSelectors/index.js rename src/buildSelectors/{index.ts => index.ts.todo} (100%) diff --git a/src/buildMiddleware/index.js b/src/buildMiddleware/index.js new file mode 100644 index 0000000..2085412 --- /dev/null +++ b/src/buildMiddleware/index.js @@ -0,0 +1,48 @@ +import { mapValues, map, get } from 'lodash-es'; +const middlewareFor = (type, middleware) => (api) => (next) => (action) => { + if (type !== '*' && action.type !== type) + return next(action); + return middleware(api)(next)(action); +}; +const sliceMw = (slice, mw) => (api) => { + const getSliceState = () => get(api.getState(), slice); + return mw(Object.assign(Object.assign({}, api), { getState: getSliceState })); +}; +export function augmentMiddlewareApi(api, actions, selectors) { + const getState = () => api.getState(); + const dispatch = (action) => api.dispatch(action); + Object.assign(getState, mapValues(selectors, (selector) => { + return (...args) => { + let result = selector(api.getState()); + if (typeof result === 'function') + return result(...args); + return result; + }; + })); + Object.assign(dispatch, mapValues(actions, (action) => { + return (...args) => api.dispatch(action(...args)); + })); + return Object.assign(Object.assign({}, api), { getState, + dispatch, + actions, + selectors }); +} +export const effectToMiddleware = (effect, actions, selectors) => { + let mw = effect; + let action = '*'; + if (Array.isArray(effect)) { + action = effect[0]; + mw = effect[1]; + mw = middlewareFor(action, mw); + } + return (api) => mw(augmentMiddlewareApi(api, actions, selectors)); +}; +const composeMw = (mws) => (api) => (original_next) => mws.reduceRight((next, mw) => mw(api)(next), original_next); +export function buildMiddleware(effects = [], actions = {}, selectors = {}, sub = {}, wrapper = undefined, dux = undefined) { + let inner = map(sub, ({ middleware }, slice) => slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined).filter((x) => x); + const local = effects.map((effect) => effectToMiddleware(effect, actions, selectors)); + let mws = [...local, ...inner]; + if (wrapper) + mws = wrapper(mws, dux); + return composeMw(mws); +} diff --git a/src/buildMiddleware/index.ts b/src/buildMiddleware/index.ts.todo similarity index 100% rename from src/buildMiddleware/index.ts rename to src/buildMiddleware/index.ts.todo diff --git a/src/buildSelectors/index.js b/src/buildSelectors/index.js new file mode 100644 index 0000000..dfd7550 --- /dev/null +++ b/src/buildSelectors/index.js @@ -0,0 +1,20 @@ +import { map, mapValues, merge } from 'lodash-es'; +export function buildSelectors(localSelectors, splatSelector = {}, subduxes = {}) { + const subSelectors = map(subduxes, ({ selectors }, slice) => { + if (!selectors) + return {}; + if (slice === '*') + return {}; + return mapValues(selectors, (func) => (state) => func(state[slice])); + }); + let splat = {}; + for (const name in splatSelector) { + splat[name] = + (state) => (...args) => { + const value = splatSelector[name](state)(...args); + const res = () => value; + return merge(res, mapValues(subduxes['*'].selectors, (selector) => () => selector(value))); + }; + } + return merge({}, ...subSelectors, localSelectors, splat); +} diff --git a/src/buildSelectors/index.ts b/src/buildSelectors/index.ts.todo similarity index 100% rename from src/buildSelectors/index.ts rename to src/buildSelectors/index.ts.todo From bf152d6f051de7b3cd76b3dcd89facb082ec05d9 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 6 Mar 2023 12:15:16 -0500 Subject: [PATCH 12/46] move vitest config to js --- vitest.config.ts => vitest.config.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vitest.config.ts => vitest.config.js (100%) diff --git a/vitest.config.ts b/vitest.config.js similarity index 100% rename from vitest.config.ts rename to vitest.config.js From d304fc0fccd3ccc4247a1bae8e8199e058e2b9fa Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 6 Mar 2023 12:15:44 -0500 Subject: [PATCH 13/46] remove work file --- foo.ts | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 foo.ts diff --git a/foo.ts b/foo.ts deleted file mode 100644 index 589e79a..0000000 --- a/foo.ts +++ /dev/null @@ -1,6 +0,0 @@ -const foo = (x: Partial<{ a: A; b: B }>) => x as B; - -const y = foo({ - a: 'potato', - b: 2, -}); From 5e39c2b162b86373d3a6419abce0c7d2225d44bd Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 6 Mar 2023 12:16:06 -0500 Subject: [PATCH 14/46] add subdux actions --- src/Updux.ts | 21 ++++++++++++++++----- src/actions.test.todo | 17 ----------------- src/actions.test.ts | 27 +++++++++++++++++++++++++++ src/actions.ts | 4 +++- src/index.ts | 4 ++-- src/types.ts | 21 +++++++++++++++++++++ 6 files changed, 69 insertions(+), 25 deletions(-) create mode 100644 src/actions.test.ts create mode 100644 src/types.ts diff --git a/src/Updux.ts b/src/Updux.ts index 59c1456..7aad5e3 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -1,3 +1,4 @@ +import * as R from 'remeda'; import { createStore as reduxCreateStore, applyMiddleware, @@ -5,7 +6,8 @@ import { Action, } from 'redux'; import { configureStore, Reducer, createAction } from '@reduxjs/toolkit'; -import { withPayload } from './actions'; +import { withPayload } from './actions.js'; +import { AggregateActions, Dux, UnionToIntersection } from './types.js'; type ActionCreator = ReturnType; @@ -20,6 +22,7 @@ type Mutation = ( export default class Updux< T_LocalState = Record, T_LocalActions = {}, + SUBDUXES extends Record = {}, > { #localInitial: T_LocalState; #localActions: T_LocalActions; @@ -27,16 +30,28 @@ export default class Updux< string, Mutation> > = {}; + #subduxes: SUBDUXES; + + #actions: Record; constructor( config: Partial<{ initial: T_LocalState; actions: T_LocalActions; + subduxes: SUBDUXES; }>, ) { // TODO check that we can't alter the initial after the fact this.#localInitial = config.initial ?? ({} as T_LocalState); this.#localActions = config.actions ?? ({} as T_LocalActions); + this.#subduxes = config.subduxes ?? ({} as SUBDUXES); + } + + get actions(): AggregateActions { + return R.mergeAll([ + this.#localActions, + ...Object.values(this.#subduxes).map(R.pathOr(['actions'], {})), + ]) as any; } // TODO memoize? @@ -44,10 +59,6 @@ export default class Updux< return this.#localInitial; } - get actions() { - return this.#localActions; - } - createStore( options: Partial<{ initial: T_LocalState; diff --git a/src/actions.test.todo b/src/actions.test.todo index eb6e22f..c442f36 100644 --- a/src/actions.test.todo +++ b/src/actions.test.todo @@ -34,23 +34,6 @@ test('Updux config accepts actions', () => { }); }); -test('subduxes actions', () => { - const foo = new Updux({ - actions: { - foo: null, - }, - subduxes: { - beta: { - actions: { - bar: null, - }, - }, - }, - }); - - expect(foo.actions).toHaveProperty('foo'); - expect(foo.actions).toHaveProperty('bar'); -}); test('throw if double action', () => { expect( diff --git a/src/actions.test.ts b/src/actions.test.ts new file mode 100644 index 0000000..76cdb07 --- /dev/null +++ b/src/actions.test.ts @@ -0,0 +1,27 @@ +import Updux, { createAction } from './index.js'; + +test('subduxes actions', () => { + const bar = createAction('bar'); + const baz = createAction('baz'); + + const foo = new Updux({ + actions: { + bar, + }, + subduxes: { + beta: { + actions: { + baz, + }, + }, + // to check if we can deal with empty actions + gamma: {}, + }, + }); + + expect(foo.actions).toHaveProperty('bar'); + expect(foo.actions).toHaveProperty('baz'); + + expect(foo.actions.bar(2)).toHaveProperty('type', 'bar'); + expect(foo.actions.baz()).toHaveProperty('type', 'baz'); +}); diff --git a/src/actions.ts b/src/actions.ts index f19ced8..d36171b 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -1,5 +1,7 @@ +import { createAction } from '@reduxjs/toolkit'; + export { createAction } from '@reduxjs/toolkit'; export const withPayload =

() => - (payload: P) => ({ payload }); + (payload: P) => ({ payload }); diff --git a/src/index.ts b/src/index.ts index e542a2f..6488bac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ -import Updux from './Updux'; +import Updux from './Updux.js'; -export { withPayload, createAction } from './actions'; +export { withPayload, createAction } from './actions.js'; export default Updux; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..3ef2a48 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,21 @@ +import { Action, ActionCreator } from 'redux'; + +export type Dux< + STATE = any, + ACTIONS extends Record> = {}, +> = Partial<{ + initial: STATE; + actions: ACTIONS; +}>; + +type ActionsOf = DUX extends { actions: infer A } ? A : {}; + +export type AggregateActions = UnionToIntersection< + ActionsOf | A +>; + +export type UnionToIntersection = ( + U extends any ? (k: U) => void : never +) extends (k: infer I) => void + ? I + : never; From 9bac1de62c9fb54ee2708d64f98c70d3a49a45e7 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 6 Mar 2023 12:16:17 -0500 Subject: [PATCH 15/46] add new checks task --- Taskfile.yaml | 5 +++++ tsconfig.json | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Taskfile.yaml b/Taskfile.yaml index def9a12..677cab8 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -3,6 +3,11 @@ version: '3' tasks: + build: tsc + + checks: + deps: [test, build] + test: vitest run src test:dev: vitest src diff --git a/tsconfig.json b/tsconfig.json index d62408e..4baa5e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,9 +25,9 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "ES2020" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, + "module": "nodenext" /* Specify what module code is generated. */, + "rootDir": "./src" /* Specify the root folder within your source files. */, + "moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */, // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ @@ -51,7 +51,7 @@ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, // "removeComments": true, /* Disable emitting comments. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ @@ -78,7 +78,7 @@ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, + "strict": false /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ From 13eeb86e05bdb8666ede2d597127f05cd1c7306a Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 6 Mar 2023 13:02:40 -0500 Subject: [PATCH 16/46] basic action test --- src/actions.test.todo | 10 ---------- src/actions.test.ts | 16 +++++++++++++++- src/actions.ts | 14 +++++++++++--- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/actions.test.todo b/src/actions.test.todo index c442f36..d1e45ee 100644 --- a/src/actions.test.todo +++ b/src/actions.test.todo @@ -4,16 +4,6 @@ import { action } from './actions.js'; import { Updux } from './Updux.js'; -test('basic action', () => { - const foo = action('foo', (thing) => ({ thing })); - - expect(foo('bar')).toEqual({ - type: 'foo', - payload: { - thing: 'bar', - }, - }); -}); test('Updux config accepts actions', () => { const foo = new Updux({ diff --git a/src/actions.test.ts b/src/actions.test.ts index 76cdb07..c051266 100644 --- a/src/actions.test.ts +++ b/src/actions.test.ts @@ -1,4 +1,18 @@ -import Updux, { createAction } from './index.js'; +import Updux, { createAction, withPayload } from './index.js'; + +test('basic action', () => { + const foo = createAction( + 'foo', + withPayload((thing: string) => ({ thing })), + ); + + expect(foo('bar')).toEqual({ + type: 'foo', + payload: { + thing: 'bar', + }, + }); +}); test('subduxes actions', () => { const bar = createAction('bar'); diff --git a/src/actions.ts b/src/actions.ts index d36171b..9bd5453 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -2,6 +2,14 @@ import { createAction } from '@reduxjs/toolkit'; export { createAction } from '@reduxjs/toolkit'; -export const withPayload = -

() => - (payload: P) => ({ payload }); +interface WithPayload { + ():

(input: P) => { payload: P }; + (prepare: (...args: A) => P): (...input: A) => { + payload: P; + }; +} + +export const withPayload: WithPayload = ((prepare) => + (...input) => ({ + payload: prepare ? prepare(...input) : input, + })) as any; From d1ed23de2c4358c905a4672fffa33acacdee612e Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 6 Mar 2023 13:04:51 -0500 Subject: [PATCH 17/46] test updux takes in actions --- src/actions.test.todo | 18 ------------------ src/actions.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/actions.test.todo b/src/actions.test.todo index d1e45ee..174cfa2 100644 --- a/src/actions.test.todo +++ b/src/actions.test.todo @@ -5,24 +5,6 @@ import { action } from './actions.js'; import { Updux } from './Updux.js'; -test('Updux config accepts actions', () => { - const foo = new Updux({ - actions: { - one: action('one', (x) => ({ x })), - two: action('two', (x) => x), - }, - }); - - expect(Object.keys(foo.actions)).toHaveLength(2); - - expect(foo.actions.one).toBeTypeOf('function'); - expect(foo.actions.one('potato')).toEqual({ - type: 'one', - payload: { - x: 'potato', - }, - }); -}); test('throw if double action', () => { diff --git a/src/actions.test.ts b/src/actions.test.ts index c051266..99fdeb9 100644 --- a/src/actions.test.ts +++ b/src/actions.test.ts @@ -39,3 +39,28 @@ test('subduxes actions', () => { expect(foo.actions.bar(2)).toHaveProperty('type', 'bar'); expect(foo.actions.baz()).toHaveProperty('type', 'baz'); }); + +test('Updux config accepts actions', () => { + const foo = new Updux({ + actions: { + one: createAction( + 'one', + withPayload((x) => ({ x })), + ), + two: createAction( + 'two', + withPayload((x) => x), + ), + }, + }); + + expect(Object.keys(foo.actions)).toHaveLength(2); + + expect(foo.actions.one).toBeTypeOf('function'); + expect(foo.actions.one('potato')).toEqual({ + type: 'one', + payload: { + x: 'potato', + }, + }); +}); From 000ca9871ab2bc132c3f8800b5872d379f98a5c5 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 6 Mar 2023 15:07:24 -0500 Subject: [PATCH 18/46] duplicate actions --- src/Updux.ts | 14 ++++++++------ src/actions.test.todo | 18 ------------------ src/actions.test.ts | 36 ++++++++++++++++++++++++++++++++++++ src/actions.ts | 4 ++-- src/buildActions.ts | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 src/buildActions.ts diff --git a/src/Updux.ts b/src/Updux.ts index 7aad5e3..9fd7513 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -8,6 +8,7 @@ import { import { configureStore, Reducer, createAction } from '@reduxjs/toolkit'; import { withPayload } from './actions.js'; import { AggregateActions, Dux, UnionToIntersection } from './types.js'; +import { buildActions } from './buildActions.js'; type ActionCreator = ReturnType; @@ -32,7 +33,9 @@ export default class Updux< > = {}; #subduxes: SUBDUXES; - #actions: Record; + #name: string; + + #actions: AggregateActions; constructor( config: Partial<{ @@ -45,13 +48,12 @@ export default class Updux< this.#localInitial = config.initial ?? ({} as T_LocalState); this.#localActions = config.actions ?? ({} as T_LocalActions); this.#subduxes = config.subduxes ?? ({} as SUBDUXES); + + this.#actions = buildActions(this.#localActions, this.#subduxes); } - get actions(): AggregateActions { - return R.mergeAll([ - this.#localActions, - ...Object.values(this.#subduxes).map(R.pathOr(['actions'], {})), - ]) as any; + get actions() { + return this.#actions; } // TODO memoize? diff --git a/src/actions.test.todo b/src/actions.test.todo index 174cfa2..e17edf4 100644 --- a/src/actions.test.todo +++ b/src/actions.test.todo @@ -7,24 +7,6 @@ import { Updux } from './Updux.js'; -test('throw if double action', () => { - expect( - () => - new Updux({ - actions: { - foo: action('foo'), - }, - subduxes: { - beta: { - actions: { - foo: action('foo'), - }, - }, - }, - }), - ).toThrow(/action 'foo' already defined/); -}); - test('action definition shortcut', () => { const foo = new Updux({ actions: { diff --git a/src/actions.test.ts b/src/actions.test.ts index 99fdeb9..c39b8be 100644 --- a/src/actions.test.ts +++ b/src/actions.test.ts @@ -64,3 +64,39 @@ test('Updux config accepts actions', () => { }, }); }); + +test('throw if double action', () => { + expect( + () => + new Updux({ + actions: { + foo: createAction('foo'), + }, + subduxes: { + beta: { + actions: { + foo: createAction('foo'), + }, + }, + }, + }), + ).toThrow(/action 'foo' defined both locally and in subdux 'beta'/); + + expect( + () => + new Updux({ + subduxes: { + gamma: { + actions: { + foo: createAction('foo'), + }, + }, + beta: { + actions: { + foo: createAction('foo'), + }, + }, + }, + }), + ).toThrow(/action 'foo' defined both in subduxes 'gamma' and 'beta'/); +}); diff --git a/src/actions.ts b/src/actions.ts index 9bd5453..f8d7dcf 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -3,7 +3,7 @@ import { createAction } from '@reduxjs/toolkit'; export { createAction } from '@reduxjs/toolkit'; interface WithPayload { - ():

(input: P) => { payload: P }; +

(): (input: P) => { payload: P }; (prepare: (...args: A) => P): (...input: A) => { payload: P; }; @@ -11,5 +11,5 @@ interface WithPayload { export const withPayload: WithPayload = ((prepare) => (...input) => ({ - payload: prepare ? prepare(...input) : input, + payload: prepare ? prepare(...input) : input[0], })) as any; diff --git a/src/buildActions.ts b/src/buildActions.ts new file mode 100644 index 0000000..21f0cef --- /dev/null +++ b/src/buildActions.ts @@ -0,0 +1,33 @@ +import * as R from 'remeda'; + +export function buildActions(localActions, subduxes) { + let actions: Record = {}; + + for (const slice in subduxes) { + const subdux = subduxes[slice].actions; + + if (!subdux) continue; + + for (const a in subdux) { + if (actions[a]) { + throw new Error( + `action '${a}' defined both in subduxes '${actions[a]}' and '${slice}'`, + ); + } + actions[a] = slice; + } + } + + for (const a in localActions) { + if (actions[a]) { + throw new Error( + `action '${a}' defined both locally and in subdux '${actions[a]}'`, + ); + } + } + + return R.mergeAll([ + localActions, + ...Object.values(subduxes).map(R.pathOr(['actions'], {})), + ]) as any; +} From 88808507ad9c9652e05143163e864e435b45b444 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 6 Mar 2023 16:07:22 -0500 Subject: [PATCH 19/46] all action tests are done --- src/Updux.ts | 7 ++++++- src/actions.test.todo | 23 ----------------------- src/actions.test.ts | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 24 deletions(-) delete mode 100644 src/actions.test.todo diff --git a/src/Updux.ts b/src/Updux.ts index 9fd7513..3932ec8 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -5,7 +5,12 @@ import { DeepPartial, Action, } from 'redux'; -import { configureStore, Reducer, createAction } from '@reduxjs/toolkit'; +import { + configureStore, + Reducer, + createAction, + PrepareAction, +} from '@reduxjs/toolkit'; import { withPayload } from './actions.js'; import { AggregateActions, Dux, UnionToIntersection } from './types.js'; import { buildActions } from './buildActions.js'; diff --git a/src/actions.test.todo b/src/actions.test.todo deleted file mode 100644 index e17edf4..0000000 --- a/src/actions.test.todo +++ /dev/null @@ -1,23 +0,0 @@ -import { test, expect } from 'vitest'; - -import { action } from './actions.js'; - -import { Updux } from './Updux.js'; - - - - -test('action definition shortcut', () => { - const foo = new Updux({ - actions: { - foo: null, - bar: (x) => ({ x }), - }, - }); - - expect(foo.actions.foo('hello')).toEqual({ type: 'foo', payload: 'hello' }); - expect(foo.actions.bar('hello')).toEqual({ - type: 'bar', - payload: { x: 'hello' }, - }); -}); diff --git a/src/actions.test.ts b/src/actions.test.ts index c39b8be..b74eb21 100644 --- a/src/actions.test.ts +++ b/src/actions.test.ts @@ -100,3 +100,18 @@ test('throw if double action', () => { }), ).toThrow(/action 'foo' defined both in subduxes 'gamma' and 'beta'/); }); + +test.todo('action definition shortcut', () => { + const foo = new Updux({ + actions: { + foo: undefined, + bar: (x) => ({ x }), + }, + }); + + expect(foo.actions.foo('hello')).toEqual({ type: 'foo', payload: 'hello' }); + expect(foo.actions.bar('hello')).toEqual({ + type: 'bar', + payload: { x: 'hello' }, + }); +}); From 9ac3032a7f8eb937eca47752494d07e694a1a5b5 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Tue, 7 Mar 2023 10:33:05 -0500 Subject: [PATCH 20/46] holy heck the action shortcuts work --- src/Updux.ts | 47 +++++++++++++++++++++++++++++++++++++-------- src/actions.test.ts | 17 ++++++++++------ src/buildActions.ts | 12 ++++++++++++ 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/Updux.ts b/src/Updux.ts index 3932ec8..20221a2 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -9,17 +9,46 @@ import { configureStore, Reducer, createAction, + ActionCreator, PrepareAction, + ActionCreatorWithPayload, + ActionCreatorWithoutPayload, + ActionCreatorWithPreparedPayload, } from '@reduxjs/toolkit'; import { withPayload } from './actions.js'; import { AggregateActions, Dux, UnionToIntersection } from './types.js'; import { buildActions } from './buildActions.js'; -type ActionCreator = ReturnType; - type AggregateState = L; -type Mutation = ( +type MyActionCreator = { type: string } & ((...args: any) => any); + +type F = null extends any ? 'u' : 'n'; + +type ResolveAction< + ActionType extends string, + ActionArg extends any, +> = ActionArg extends MyActionCreator + ? ActionArg + : ActionArg extends (...args: any) => any + ? ActionCreatorWithPreparedPayload< + Parameters, + ReturnType, + ActionType + > + : ActionCreatorWithoutPayload; + +type ResolveActions< + A extends { + [key: string]: any; + }, +> = { + [ActionType in keyof A]: ActionType extends string + ? ResolveAction + : never; +}; + +type Mutation = ActionCreator, S = any> = ( state: S, payload: ReturnType['payload'], action: ReturnType, @@ -27,20 +56,22 @@ type Mutation = ( export default class Updux< T_LocalState = Record, - T_LocalActions = {}, + T_LocalActions extends { + [actionType: string]: any; + } = {}, SUBDUXES extends Record = {}, > { #localInitial: T_LocalState; #localActions: T_LocalActions; #localMutations: Record< string, - Mutation> + Mutation, AggregateState> > = {}; #subduxes: SUBDUXES; #name: string; - #actions: AggregateActions; + #actions: AggregateActions, SUBDUXES>; constructor( config: Partial<{ @@ -81,10 +112,10 @@ export default class Updux< } // TODO force the actionCreator to be one of the actions? - mutation( + mutation>( actionCreator: A, mutation: Mutation>, ) { - this.#localMutations[actionCreator.type] = mutation; + this.#localMutations[(actionCreator as any).type] = mutation; } } diff --git a/src/actions.test.ts b/src/actions.test.ts index b74eb21..34651d0 100644 --- a/src/actions.test.ts +++ b/src/actions.test.ts @@ -101,17 +101,22 @@ test('throw if double action', () => { ).toThrow(/action 'foo' defined both in subduxes 'gamma' and 'beta'/); }); -test.todo('action definition shortcut', () => { +test('action definition shortcut', () => { const foo = new Updux({ actions: { - foo: undefined, - bar: (x) => ({ x }), + foo: 0, + bar: (x: number) => ({ x }), + baz: createAction('baz', withPayload()), }, }); - expect(foo.actions.foo('hello')).toEqual({ type: 'foo', payload: 'hello' }); - expect(foo.actions.bar('hello')).toEqual({ + expect(foo.actions.foo()).toEqual({ type: 'foo', payload: undefined }); + expect(foo.actions.baz(false)).toEqual({ + type: 'baz', + payload: false, + }); + expect(foo.actions.bar(2)).toEqual({ type: 'bar', - payload: { x: 'hello' }, + payload: { x: 2 }, }); }); diff --git a/src/buildActions.ts b/src/buildActions.ts index 21f0cef..bee0ebb 100644 --- a/src/buildActions.ts +++ b/src/buildActions.ts @@ -1,6 +1,18 @@ +import { createAction } from '@reduxjs/toolkit'; import * as R from 'remeda'; +import { withPayload } from './actions.js'; + +function resolveActions(configActions) { + return R.mapValues(configActions, (prepare, type: string) => { + if (typeof prepare === 'function' && prepare.type) return prepare; + + return createAction(type, withPayload(prepare)); + }); +} export function buildActions(localActions, subduxes) { + localActions = resolveActions(localActions); + let actions: Record = {}; for (const slice in subduxes) { From e9778f98e8088b3987e746fbd01da148e238468b Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Tue, 7 Mar 2023 10:52:19 -0500 Subject: [PATCH 21/46] initial state and subduxes --- src/Updux.ts | 7 ++++- src/buildInitial.test.ts | 21 +++++++++++++ src/buildInitial.ts | 12 +++++++ src/initial.test.todo | 46 --------------------------- src/initial.test.ts | 67 ++++++++++++++++++++++++++++++++++++++++ src/tutorial.test.ts | 2 +- 6 files changed, 107 insertions(+), 48 deletions(-) create mode 100644 src/buildInitial.test.ts create mode 100644 src/buildInitial.ts delete mode 100644 src/initial.test.todo diff --git a/src/Updux.ts b/src/Updux.ts index 20221a2..3a70c08 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -18,6 +18,7 @@ import { import { withPayload } from './actions.js'; import { AggregateActions, Dux, UnionToIntersection } from './types.js'; import { buildActions } from './buildActions.js'; +import { buildInitial } from './buildInitial.js'; type AggregateState = L; @@ -73,6 +74,8 @@ export default class Updux< #actions: AggregateActions, SUBDUXES>; + #initial: any; + constructor( config: Partial<{ initial: T_LocalState; @@ -86,6 +89,8 @@ export default class Updux< this.#subduxes = config.subduxes ?? ({} as SUBDUXES); this.#actions = buildActions(this.#localActions, this.#subduxes); + + this.#initial = buildInitial(this.#localInitial, this.#subduxes); } get actions() { @@ -94,7 +99,7 @@ export default class Updux< // TODO memoize? get initial() { - return this.#localInitial; + return this.#initial; } createStore( diff --git a/src/buildInitial.test.ts b/src/buildInitial.test.ts new file mode 100644 index 0000000..7e8767e --- /dev/null +++ b/src/buildInitial.test.ts @@ -0,0 +1,21 @@ +import { test, expect } from 'vitest'; +import { buildInitial } from './buildInitial.js'; + +test('basic', () => { + expect( + buildInitial( + { a: 1 }, + { b: { initial: { c: 2 } }, d: { initial: 'e' } }, + ), + ).toEqual({ + a: 1, + b: { c: 2 }, + d: 'e', + }); +}); + +test('throw if subduxes and initial is not an object', () => { + expect(() => { + buildInitial(3, { bar: 'foo' }); + }).toThrow(); +}); diff --git a/src/buildInitial.ts b/src/buildInitial.ts new file mode 100644 index 0000000..7e2330e --- /dev/null +++ b/src/buildInitial.ts @@ -0,0 +1,12 @@ +import u from '@yanick/updeep-remeda'; +import * as R from 'remeda'; + +export function buildInitial(localInitial, subduxes) { + if (Object.keys(subduxes).length > 0 && typeof localInitial !== 'object') { + throw new Error( + "can't have subduxes when the initial value is not an object", + ); + } + + return u(localInitial, R.mapValues(subduxes, R.pathOr(['initial'], {}))); +} diff --git a/src/initial.test.todo b/src/initial.test.todo deleted file mode 100644 index 0aa2a85..0000000 --- a/src/initial.test.todo +++ /dev/null @@ -1,46 +0,0 @@ -import { test, expect } from 'vitest'; - -import { Updux } from './Updux.js'; - -const bar = new Updux({ initial: 123 }); - -const foo = new Updux({ - initial: { root: 'abc' }, - subduxes: { - bar, - }, -}); - -test('single dux', () => { - const foo = new Updux({ - initial: { a: 1 }, - }); - - expect(foo.initial).toEqual({ a: 1 }); -}); - -test('initial value', () => { - expect(foo.initial).toEqual({ - root: 'abc', - bar: 123, - }); -}); - -test('splat initial', async () => { - const bar = new Updux({ - initial: { id: 0 }, - }); - - const foo = new Updux({ - subduxes: { '*': bar }, - }); - - expect(foo.initial).toEqual([]); - - expect( - new Updux({ - initial: 'overriden', - subduxes: { '*': bar }, - }).initial, - ).toEqual('overriden'); -}); diff --git a/src/initial.test.ts b/src/initial.test.ts index 32194e6..74ef278 100644 --- a/src/initial.test.ts +++ b/src/initial.test.ts @@ -1,5 +1,15 @@ +import { expectType } from './tutorial.test.js'; import Updux from './Updux.js'; +const bar = new Updux({ initial: 123 }); + +const foo = new Updux({ + initial: { root: 'abc' }, + subduxes: { + bar, + }, +}); + test('default', () => { const { initial } = new Updux({}); @@ -29,3 +39,60 @@ test('initial to createStore', () => { b: 4, }); }); + +test('single dux', () => { + const foo = new Updux({ + initial: { a: 1 }, + }); + + expect(foo.initial).toEqual({ a: 1 }); +}); + +// TODO add 'check for no todo eslint rule' +test.only('initial value', () => { + expect(foo.initial).toEqual({ + root: 'abc', + bar: 123, + }); + + expectType<{ + root: string; + bar: number; + }>(foo.initial); +}); + +test('no initial', () => { + const dux = new Updux({}); + expectType<{}>(dux.initial); + expect(dux.initial).toEqual({}); +}); + +test('no initial for subdux', () => { + const dux = new Updux({ + subduxes: { + bar: new Updux({}), + baz: new Updux({ initial: 'potato' }), + }, + }); + expectType<{ bar: {}; baz: string }>(dux.initial); + expect(dux.initial).toEqual({ bar: {}, baz: 'potato' }); +}); + +test('splat initial', async () => { + const bar = new Updux({ + initial: { id: 0 }, + }); + + const foo = new Updux({ + subduxes: { '*': bar }, + }); + + expect(foo.initial).toEqual([]); + + expect( + new Updux({ + initial: 'overriden', + subduxes: { '*': bar }, + }).initial, + ).toEqual('overriden'); +}); diff --git a/src/tutorial.test.ts b/src/tutorial.test.ts index 753e746..e763eb4 100644 --- a/src/tutorial.test.ts +++ b/src/tutorial.test.ts @@ -1,7 +1,7 @@ import Updux, { createAction, withPayload } from './index.js'; import u from '@yanick/updeep-remeda'; -const expectType = (value: T) => value; +export const expectType = (value: T) => value; test('initial state', () => { const initial = { From 042f5b8f13925265c66ce1b395abf6f51a3e5c1f Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Tue, 7 Mar 2023 11:53:44 -0500 Subject: [PATCH 22/46] initial state and subduxes tests are passing --- src/Updux.ts | 17 +++++++++++++---- src/initial.test.ts | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Updux.ts b/src/Updux.ts index 3a70c08..2352b17 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -20,7 +20,16 @@ import { AggregateActions, Dux, UnionToIntersection } from './types.js'; import { buildActions } from './buildActions.js'; import { buildInitial } from './buildInitial.js'; -type AggregateState = L; +type SubduxState = 'initial' extends keyof S ? S['initial'] : {}; + +type AggregateState> = LOCAL & + (keyof SUBDUXES extends never + ? {} + : { + [Slice in keyof SUBDUXES]: Slice extends string + ? SubduxState + : never; + }); type MyActionCreator = { type: string } & ((...args: any) => any); @@ -66,7 +75,7 @@ export default class Updux< #localActions: T_LocalActions; #localMutations: Record< string, - Mutation, AggregateState> + Mutation, AggregateState> > = {}; #subduxes: SUBDUXES; @@ -74,7 +83,7 @@ export default class Updux< #actions: AggregateActions, SUBDUXES>; - #initial: any; + #initial: AggregateState; constructor( config: Partial<{ @@ -119,7 +128,7 @@ export default class Updux< // TODO force the actionCreator to be one of the actions? mutation>( actionCreator: A, - mutation: Mutation>, + mutation: Mutation>, ) { this.#localMutations[(actionCreator as any).type] = mutation; } diff --git a/src/initial.test.ts b/src/initial.test.ts index 74ef278..245632a 100644 --- a/src/initial.test.ts +++ b/src/initial.test.ts @@ -49,7 +49,7 @@ test('single dux', () => { }); // TODO add 'check for no todo eslint rule' -test.only('initial value', () => { +test('initial value', () => { expect(foo.initial).toEqual({ root: 'abc', bar: 123, @@ -78,7 +78,7 @@ test('no initial for subdux', () => { expect(dux.initial).toEqual({ bar: {}, baz: 'potato' }); }); -test('splat initial', async () => { +test.todo('splat initial', async () => { const bar = new Updux({ initial: { id: 0 }, }); From 736377effd7bad27b67cd2e33ce1cc50afc80c75 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Tue, 7 Mar 2023 11:57:41 -0500 Subject: [PATCH 23/46] rename SUBDUXES to T_Subduxes --- src/Updux.ts | 31 +++++++++-------------------- src/buildInitial.test.ts | 2 +- src/{buildInitial.ts => initial.ts} | 11 ++++++++++ 3 files changed, 21 insertions(+), 23 deletions(-) rename src/{buildInitial.ts => initial.ts} (52%) diff --git a/src/Updux.ts b/src/Updux.ts index 2352b17..1bc0d37 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -18,23 +18,10 @@ import { import { withPayload } from './actions.js'; import { AggregateActions, Dux, UnionToIntersection } from './types.js'; import { buildActions } from './buildActions.js'; -import { buildInitial } from './buildInitial.js'; - -type SubduxState = 'initial' extends keyof S ? S['initial'] : {}; - -type AggregateState> = LOCAL & - (keyof SUBDUXES extends never - ? {} - : { - [Slice in keyof SUBDUXES]: Slice extends string - ? SubduxState - : never; - }); +import { buildInitial, AggregateState } from './initial.js'; type MyActionCreator = { type: string } & ((...args: any) => any); -type F = null extends any ? 'u' : 'n'; - type ResolveAction< ActionType extends string, ActionArg extends any, @@ -69,33 +56,33 @@ export default class Updux< T_LocalActions extends { [actionType: string]: any; } = {}, - SUBDUXES extends Record = {}, + T_Subduxes extends Record = {}, > { #localInitial: T_LocalState; #localActions: T_LocalActions; #localMutations: Record< string, - Mutation, AggregateState> + Mutation, AggregateState> > = {}; - #subduxes: SUBDUXES; + #subduxes: T_Subduxes; #name: string; - #actions: AggregateActions, SUBDUXES>; + #actions: AggregateActions, T_Subduxes>; - #initial: AggregateState; + #initial: AggregateState; constructor( config: Partial<{ initial: T_LocalState; actions: T_LocalActions; - subduxes: SUBDUXES; + subduxes: T_Subduxes; }>, ) { // TODO check that we can't alter the initial after the fact this.#localInitial = config.initial ?? ({} as T_LocalState); this.#localActions = config.actions ?? ({} as T_LocalActions); - this.#subduxes = config.subduxes ?? ({} as SUBDUXES); + this.#subduxes = config.subduxes ?? ({} as T_Subduxes); this.#actions = buildActions(this.#localActions, this.#subduxes); @@ -128,7 +115,7 @@ export default class Updux< // TODO force the actionCreator to be one of the actions? mutation>( actionCreator: A, - mutation: Mutation>, + mutation: Mutation>, ) { this.#localMutations[(actionCreator as any).type] = mutation; } diff --git a/src/buildInitial.test.ts b/src/buildInitial.test.ts index 7e8767e..63743e8 100644 --- a/src/buildInitial.test.ts +++ b/src/buildInitial.test.ts @@ -1,5 +1,5 @@ import { test, expect } from 'vitest'; -import { buildInitial } from './buildInitial.js'; +import { buildInitial } from './initial.js'; test('basic', () => { expect( diff --git a/src/buildInitial.ts b/src/initial.ts similarity index 52% rename from src/buildInitial.ts rename to src/initial.ts index 7e2330e..36aaf92 100644 --- a/src/buildInitial.ts +++ b/src/initial.ts @@ -1,6 +1,17 @@ import u from '@yanick/updeep-remeda'; import * as R from 'remeda'; +type SubduxState = 'initial' extends keyof S ? S['initial'] : {}; + +export type AggregateState> = LOCAL & + (keyof SUBDUXES extends never + ? {} + : { + [Slice in keyof SUBDUXES]: Slice extends string + ? SubduxState + : never; + }); + export function buildInitial(localInitial, subduxes) { if (Object.keys(subduxes).length > 0 && typeof localInitial !== 'object') { throw new Error( From b3088334a475033d2f4664e17865c6ebd9a4785e Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Tue, 7 Mar 2023 19:07:17 -0500 Subject: [PATCH 24/46] remove unused imports --- src/Updux.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Updux.ts b/src/Updux.ts index 1bc0d37..d275700 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -8,15 +8,11 @@ import { import { configureStore, Reducer, - createAction, ActionCreator, - PrepareAction, - ActionCreatorWithPayload, ActionCreatorWithoutPayload, ActionCreatorWithPreparedPayload, } from '@reduxjs/toolkit'; -import { withPayload } from './actions.js'; -import { AggregateActions, Dux, UnionToIntersection } from './types.js'; +import { AggregateActions, Dux } from './types.js'; import { buildActions } from './buildActions.js'; import { buildInitial, AggregateState } from './initial.js'; From 4c28a9ad057c96df94fb7ea3d08059d19b8e1088 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Wed, 8 Mar 2023 11:47:21 -0500 Subject: [PATCH 25/46] initial reducer --- src/Updux.ts | 8 +++-- src/reducer.test.todo | 19 ----------- src/reducer.test.ts | 31 +++++++++++++++++ src/reducer.ts | 79 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 22 deletions(-) delete mode 100644 src/reducer.test.todo create mode 100644 src/reducer.test.ts create mode 100644 src/reducer.ts diff --git a/src/Updux.ts b/src/Updux.ts index d275700..abb2155 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -41,11 +41,13 @@ type ResolveActions< : never; }; -type Mutation = ActionCreator, S = any> = ( - state: S, +export type Mutation< + A extends ActionCreator = ActionCreator, + S = any, +> = ( payload: ReturnType['payload'], action: ReturnType, -) => S | void; +) => (state: S) => S | void; export default class Updux< T_LocalState = Record, diff --git a/src/reducer.test.todo b/src/reducer.test.todo deleted file mode 100644 index 2b5ff18..0000000 --- a/src/reducer.test.todo +++ /dev/null @@ -1,19 +0,0 @@ -import { test, expect } from 'vitest'; - -import { Updux } from './Updux.js'; - -test('basic reducer', () => { - const dux = new Updux({ initial: { a: 3 } }); - - expect(dux.reducer).toBeTypeOf('function'); - - expect(dux.reducer({ a: 1 }, { type: 'foo' })).toMatchObject({ a: 1 }); // noop -}); - -test('basic upreducer', () => { - const dux = new Updux({ initial: { a: 3 } }); - - expect(dux.upreducer).toBeTypeOf('function'); - - expect(dux.upreducer({ type: 'foo' })({ a: 1 })).toMatchObject({ a: 1 }); // noop -}); diff --git a/src/reducer.test.ts b/src/reducer.test.ts new file mode 100644 index 0000000..9ce0ba8 --- /dev/null +++ b/src/reducer.test.ts @@ -0,0 +1,31 @@ +import { test, expect } from 'vitest'; + +import { buildReducer } from './reducer.js'; +import Updux from './Updux.js'; + +test('buildReducer, initial state', () => { + const reducer = buildReducer({ a: 1 }); + + expect(reducer(undefined, { type: 'foo' })).toEqual({ a: 1 }); +}); + +test('buildReducer, mutation', () => { + const reducer = buildReducer(1, [ + { + matcher: ({ type }) => type === 'inc', + mutation: () => (state) => state + 1, + terminal: false, + }, + ]); + + expect(reducer(undefined, { type: 'foo' })).toEqual(1); + expect(reducer(undefined, { type: 'inc' })).toEqual(2); +}); + +test.todo('basic reducer', () => { + const dux = new Updux({ initial: { a: 3 } }); + + expect(dux.reducer).toBeTypeOf('function'); + + expect(dux.reducer({ a: 1 }, { type: 'foo' })).toMatchObject({ a: 1 }); // noop +}); diff --git a/src/reducer.ts b/src/reducer.ts new file mode 100644 index 0000000..0d87941 --- /dev/null +++ b/src/reducer.ts @@ -0,0 +1,79 @@ +import { Action, ActionCreator, createAction } from '@reduxjs/toolkit'; +import { BaseActionCreator } from '@reduxjs/toolkit/dist/createAction.js'; +import * as R from 'remeda'; +import { Dux } from './types.js'; +import { Mutation } from './Updux.js'; + +type MutationCase = { + matcher: (action: Action) => boolean; + mutation: Mutation; + terminal: boolean; +}; + +export function buildReducer( + initialState: any, + mutations: MutationCase[] = [], + subduxes: Record = {}, +) { + // const subReducers = + // ? R.mapValues(subduxes, R.prop('reducer')); + + // TODO matcherMutation + // TODO defaultMutation + + const reducer = (state = initialState, action: Action) => { + if (!action?.type) + throw new Error('upreducer called with a bad action'); + + let terminal = false; + let didSomething = false; + + const foo = createAction('foo'); + + const localMutation = mutations.find(({ matcher }) => matcher(action)); + + if (localMutation) { + didSomething = true; + if (localMutation.terminal) terminal = true; + + // TODO wrap mutations in immer + state = localMutation.mutation( + (action as any).payload, + action, + )(state); + } + + // TODO defaultMutation + + return state; + }; + + return reducer; + + /* + if (subReducers) { + if (subduxes['*']) { + newState = u.updateIn( + '*', + subduxes['*'].upreducer(action), + newState, + ); + } else { + const update = mapValues(subReducers, (upReducer) => + upReducer(action), + ); + + newState = u(update, newState); + } + } + + const a = mutations[action.type] || mutations['+']; + + if (!a) return newState; + + return a(action.payload, action)(newState); + }; + + return wrapper ? wrapper(upreducer) : upreducer; + */ +} From 2e03a51e1585ec8949886537ff62632262019f5c Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 9 Mar 2023 10:41:15 -0500 Subject: [PATCH 26/46] mutation generic matcher --- src/Updux.ts | 55 ++++++++++++++++++++++++++++++----------- src/mutations.test.todo | 42 ------------------------------- src/mutations.test.ts | 39 +++++++++++++++++++++++++++++ src/reducer.ts | 26 ++++++++----------- src/types.ts | 6 ++++- 5 files changed, 94 insertions(+), 74 deletions(-) create mode 100644 src/mutations.test.ts diff --git a/src/Updux.ts b/src/Updux.ts index abb2155..5a4d969 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -15,6 +15,7 @@ import { import { AggregateActions, Dux } from './types.js'; import { buildActions } from './buildActions.js'; import { buildInitial, AggregateState } from './initial.js'; +import { buildReducer } from './reducer.js'; type MyActionCreator = { type: string } & ((...args: any) => any); @@ -41,12 +42,13 @@ type ResolveActions< : never; }; -export type Mutation< - A extends ActionCreator = ActionCreator, - S = any, -> = ( - payload: ReturnType['payload'], - action: ReturnType, +export type Mutation = Action, S = any> = ( + payload: A extends { + payload: infer P; + } + ? P + : undefined, + action: A, ) => (state: S) => S | void; export default class Updux< @@ -58,10 +60,7 @@ export default class Updux< > { #localInitial: T_LocalState; #localActions: T_LocalActions; - #localMutations: Record< - string, - Mutation, AggregateState> - > = {}; + #localMutations: MutationCase[] = []; #subduxes: T_Subduxes; #name: string; @@ -110,11 +109,37 @@ export default class Updux< return store; } - // TODO force the actionCreator to be one of the actions? - mutation>( - actionCreator: A, + // TODO memoize this sucker + get reducer() { + return buildReducer(this.initial, this.#localMutations) as any as ( + state: undefined | typeof this.initial, + action: Action, + ) => typeof this.initial; + } + + addMutation>( + matcher: (action: A) => boolean, mutation: Mutation>, - ) { - this.#localMutations[(actionCreator as any).type] = mutation; + terminal?: boolean, + ); + addMutation>( + actionCreator: A, + mutation: Mutation< + ReturnType, + AggregateState + >, + terminal?: boolean, + ); + addMutation(matcher, mutation, terminal = false) { + if (typeof matcher === 'function' && matcher.match) { + // matcher, matcher man... + matcher = matcher.match; + } + + this.#localMutations.push({ + terminal, + matcher, + mutation, + }); } } diff --git a/src/mutations.test.todo b/src/mutations.test.todo index da6fb13..7e72b34 100644 --- a/src/mutations.test.todo +++ b/src/mutations.test.todo @@ -1,4 +1,3 @@ -import { test, expect } from 'vitest'; import schema from 'json-schema-shorthand'; import u from 'updeep'; @@ -6,31 +5,6 @@ import { action } from './actions.js'; import { Updux, dux } from './Updux.js'; -test('set a mutation', () => { - const dux = new Updux({ - initial: { - x: 'potato', - }, - actions: { - foo: action('foo', (x) => ({ x })), - bar: action('bar'), - }, - }); - - dux.setMutation(dux.actions.foo, (payload, action) => { - expect(payload).toEqual({ x: 'hello ' }); - expect(action).toEqual(dux.actions.foo('hello ')); - return u({ - x: payload.x + action.type, - }); - }); - - const result = dux.reducer(undefined, dux.actions.foo('hello ')); - expect(result).toEqual({ - x: 'hello foo', - }); -}); - test('mutation of a subdux', async () => { const bar = dux({ actions: { @@ -72,19 +46,3 @@ test('strings and generators', async () => { expect(foo.actions.d).toBeTypeOf('function'); }); -test('splat mutation', () => { - const myDux = new Updux({ - initial: [], - actions: { one: null, two: null }, - mutations: { - '*': (payload) => (state) => payload ? [...state, payload] : state, - }, - }); - const store = myDux.createStore(); - expect(store.getState()).toEqual([]); - - store.dispatch.one(11); - store.dispatch.two(22); - - expect(store.getState()).toEqual([11, 22]); -}); diff --git a/src/mutations.test.ts b/src/mutations.test.ts new file mode 100644 index 0000000..93e6025 --- /dev/null +++ b/src/mutations.test.ts @@ -0,0 +1,39 @@ +import { test, expect } from 'vitest'; + +import Updux from './index.js'; + +test('set a mutation', () => { + const dux = new Updux({ + initial: 'potato', + actions: { + foo: (x) => ({ x }), + bar: 0, + }, + }); + + let didIt = false; + + dux.addMutation(dux.actions.foo, (payload, action) => () => { + didIt = true; + expect(payload).toEqual({ x: 'hello ' }); + expect(action).toEqual(dux.actions.foo('hello ')); + return payload.x + action.type; + }); + + const result = dux.reducer(undefined, dux.actions.foo('hello ')); + expect(didIt).toBeTruthy(); + expect(result).toEqual('hello foo'); +}); + +test('catch-all mutation', () => { + const dux = new Updux({ + initial: '', + }); + + dux.addMutation( + () => true, + (payload, action) => () => 'got it', + ); + + expect(dux.reducer(undefined, { type: 'foo' })).toEqual('got it'); +}); diff --git a/src/reducer.ts b/src/reducer.ts index 0d87941..67a8ee5 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -4,7 +4,7 @@ import * as R from 'remeda'; import { Dux } from './types.js'; import { Mutation } from './Updux.js'; -type MutationCase = { +export type MutationCase = { matcher: (action: Action) => boolean; mutation: Mutation; terminal: boolean; @@ -20,7 +20,7 @@ export function buildReducer( // TODO matcherMutation // TODO defaultMutation - + // const reducer = (state = initialState, action: Action) => { if (!action?.type) throw new Error('upreducer called with a bad action'); @@ -28,20 +28,14 @@ export function buildReducer( let terminal = false; let didSomething = false; - const foo = createAction('foo'); - - const localMutation = mutations.find(({ matcher }) => matcher(action)); - - if (localMutation) { - didSomething = true; - if (localMutation.terminal) terminal = true; - - // TODO wrap mutations in immer - state = localMutation.mutation( - (action as any).payload, - action, - )(state); - } + mutations + .filter(({ matcher }) => matcher(action)) + .forEach(({ mutation, terminal: t }) => { + if (t) terminal = true; + // + // TODO wrap mutations in immer + state = mutation((action as any).payload, action)(state); + }); // TODO defaultMutation diff --git a/src/types.ts b/src/types.ts index 3ef2a48..fb4a0a6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { Action, ActionCreator } from 'redux'; +import { Action, ActionCreator, Reducer } from 'redux'; export type Dux< STATE = any, @@ -6,6 +6,10 @@ export type Dux< > = Partial<{ initial: STATE; actions: ACTIONS; + reducer: ( + state: STATE, + action: ReturnType, + ) => STATE; }>; type ActionsOf = DUX extends { actions: infer A } ? A : {}; From 76ccd0d14a9b02cc7680cc2ce87f55b78aed5f81 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 9 Mar 2023 10:59:00 -0500 Subject: [PATCH 27/46] defaultMutation --- src/Updux.ts | 20 ++++++++++++++++++-- src/mutations.test.ts | 16 ++++++++++++++++ src/reducer.ts | 11 ++++++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/Updux.ts b/src/Updux.ts index 5a4d969..b9358a9 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -15,7 +15,7 @@ import { import { AggregateActions, Dux } from './types.js'; import { buildActions } from './buildActions.js'; import { buildInitial, AggregateState } from './initial.js'; -import { buildReducer } from './reducer.js'; +import { buildReducer, MutationCase } from './reducer.js'; type MyActionCreator = { type: string } & ((...args: any) => any); @@ -61,6 +61,7 @@ export default class Updux< #localInitial: T_LocalState; #localActions: T_LocalActions; #localMutations: MutationCase[] = []; + #defaultMutation: Omit; #subduxes: T_Subduxes; #name: string; @@ -111,12 +112,17 @@ export default class Updux< // TODO memoize this sucker get reducer() { - return buildReducer(this.initial, this.#localMutations) as any as ( + return buildReducer( + this.initial, + this.#localMutations, + this.#defaultMutation, + ) as any as ( state: undefined | typeof this.initial, action: Action, ) => typeof this.initial; } + // TODO be smarter with the guard? addMutation>( matcher: (action: A) => boolean, mutation: Mutation>, @@ -142,4 +148,14 @@ export default class Updux< mutation, }); } + + addDefaultMutation( + mutation: Mutation< + Action, + AggregateState + >, + terminal = false, + ) { + this.#defaultMutation = { mutation, terminal }; + } } diff --git a/src/mutations.test.ts b/src/mutations.test.ts index 93e6025..09c3000 100644 --- a/src/mutations.test.ts +++ b/src/mutations.test.ts @@ -37,3 +37,19 @@ test('catch-all mutation', () => { expect(dux.reducer(undefined, { type: 'foo' })).toEqual('got it'); }); + +test('default mutation', () => { + const dux = new Updux({ + initial: '', + actions: { + foo: 0, + }, + }); + + dux.addMutation(dux.actions.foo, () => () => 'got it'); + + dux.addDefaultMutation((_payload, action) => () => action.type); + + expect(dux.reducer(undefined, { type: 'foo' })).toEqual('got it'); + expect(dux.reducer(undefined, { type: 'bar' })).toEqual('bar'); +}); diff --git a/src/reducer.ts b/src/reducer.ts index 67a8ee5..12b431c 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -13,6 +13,7 @@ export type MutationCase = { export function buildReducer( initialState: any, mutations: MutationCase[] = [], + defaultMutation?: Omit, subduxes: Record = {}, ) { // const subReducers = @@ -32,12 +33,20 @@ export function buildReducer( .filter(({ matcher }) => matcher(action)) .forEach(({ mutation, terminal: t }) => { if (t) terminal = true; + didSomething = true; // // TODO wrap mutations in immer state = mutation((action as any).payload, action)(state); }); - // TODO defaultMutation + if (!didSomething && defaultMutation) { + if (defaultMutation.terminal) terminal = true; + + state = defaultMutation.mutation( + (action as any).payload, + action, + )(state); + } return state; }; From e1cb50100f05a4d9ea0f236d099fcedee37987cc Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 9 Mar 2023 14:23:50 -0500 Subject: [PATCH 28/46] mutation test are passing --- src/tutorial.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tutorial.test.ts b/src/tutorial.test.ts index e763eb4..74b4f09 100644 --- a/src/tutorial.test.ts +++ b/src/tutorial.test.ts @@ -54,7 +54,7 @@ test('mutation', () => { initial: { nextId: 0, todos: [] as Todo[] }, }); - dux.mutation(addTodo, (state, description) => { + dux.addMutation(addTodo, (description) => (state) => { state.todos.unshift({ description, id: state.nextId, done: false }); state.nextId++; }); From 1555e4d289e26ed0a233449597571c564f207f23 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 9 Mar 2023 14:48:11 -0500 Subject: [PATCH 29/46] add subdux test --- src/mutations.test.todo | 16 ---------------- src/mutations.test.ts | 20 +++++++++++++++++++- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/mutations.test.todo b/src/mutations.test.todo index 7e72b34..dccf11b 100644 --- a/src/mutations.test.todo +++ b/src/mutations.test.todo @@ -5,22 +5,6 @@ import { action } from './actions.js'; import { Updux, dux } from './Updux.js'; -test('mutation of a subdux', async () => { - const bar = dux({ - actions: { - baz: null, - }, - }); - bar.setMutation('baz', () => (state) => ({ ...state, x: 1 })); - - const foo = dux({ - subduxes: { bar }, - }); - - const store = foo.createStore(); - store.dispatch.baz(); - expect(store.getState()).toMatchObject({ bar: { x: 1 } }); -}); test('strings and generators', async () => { const actionA = action('a'); diff --git a/src/mutations.test.ts b/src/mutations.test.ts index 09c3000..9f19df8 100644 --- a/src/mutations.test.ts +++ b/src/mutations.test.ts @@ -1,6 +1,6 @@ import { test, expect } from 'vitest'; -import Updux from './index.js'; +import Updux, { createAction } from './index.js'; test('set a mutation', () => { const dux = new Updux({ @@ -53,3 +53,21 @@ test('default mutation', () => { expect(dux.reducer(undefined, { type: 'foo' })).toEqual('got it'); expect(dux.reducer(undefined, { type: 'bar' })).toEqual('bar'); }); + +test('mutation of a subdux', () => { + const baz = createAction('baz'); + + const bar = new Updux({ + initial: 0, + actions: { + baz, + }, + }); + bar.addMutation(baz, () => () => 1); + + const foo = new Updux({ + subduxes: { bar }, + }); + + expect(foo.reducer(undefined, baz())).toHaveProperty('bar', 1); +}); From 769b70dfcea5f2238bbe345acc0a55aaaecf21c6 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 9 Mar 2023 15:13:13 -0500 Subject: [PATCH 30/46] test is passing --- src/Updux.ts | 1 + src/mutations.test.ts | 7 +++++++ src/reducer.ts | 13 +++++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Updux.ts b/src/Updux.ts index b9358a9..d35b81c 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -116,6 +116,7 @@ export default class Updux< this.initial, this.#localMutations, this.#defaultMutation, + this.#subduxes, ) as any as ( state: undefined | typeof this.initial, action: Action, diff --git a/src/mutations.test.ts b/src/mutations.test.ts index 9f19df8..5858b63 100644 --- a/src/mutations.test.ts +++ b/src/mutations.test.ts @@ -56,18 +56,25 @@ test('default mutation', () => { test('mutation of a subdux', () => { const baz = createAction('baz'); + const noop = createAction('noop'); + const stopit = createAction('stopit'); const bar = new Updux({ initial: 0, actions: { baz, + stopit, }, }); bar.addMutation(baz, () => () => 1); + bar.addMutation(stopit, () => () => 2); const foo = new Updux({ subduxes: { bar }, }); + foo.addMutation(stopit, () => (state) => state, true); + expect(foo.reducer(undefined, noop())).toHaveProperty('bar', 0); expect(foo.reducer(undefined, baz())).toHaveProperty('bar', 1); + expect(foo.reducer(undefined, stopit())).toHaveProperty('bar', 0); }); diff --git a/src/reducer.ts b/src/reducer.ts index 12b431c..9ca2571 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -16,8 +16,7 @@ export function buildReducer( defaultMutation?: Omit, subduxes: Record = {}, ) { - // const subReducers = - // ? R.mapValues(subduxes, R.prop('reducer')); + const subReducers = R.mapValues(subduxes, R.prop('reducer')); // TODO matcherMutation // TODO defaultMutation @@ -48,6 +47,16 @@ export function buildReducer( )(state); } + if (!terminal && Object.keys(subduxes).length > 0) { + // subduxes + state = R.merge( + state, + R.mapValues(subReducers, (reducer, slice) => + (reducer as any)(state[slice], action), + ), + ); + } + return state; }; From 1df6ac5329373042106da881cad3c8b07a56418c Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 9 Mar 2023 15:32:41 -0500 Subject: [PATCH 31/46] subdux mutations are go --- src/buildUpreducer.todo | 45 ----------------------------------------- src/mutations.test.todo | 32 ----------------------------- 2 files changed, 77 deletions(-) delete mode 100644 src/buildUpreducer.todo delete mode 100644 src/mutations.test.todo diff --git a/src/buildUpreducer.todo b/src/buildUpreducer.todo deleted file mode 100644 index 39672ac..0000000 --- a/src/buildUpreducer.todo +++ /dev/null @@ -1,45 +0,0 @@ -import u from 'updeep'; -import { mapValues } from 'lodash-es'; - -export function buildUpreducer( - initial, - mutations, - subduxes = {}, - wrapper = undefined, -) { - const subReducers = - Object.keys(subduxes).length > 0 - ? mapValues(subduxes, ({ upreducer }) => upreducer) - : null; - - const upreducer = (action) => (state) => { - if (!action?.type) - throw new Error('upreducer called with a bad action'); - - let newState = state ?? initial; - - if (subReducers) { - if (subduxes['*']) { - newState = u.updateIn( - '*', - subduxes['*'].upreducer(action), - newState, - ); - } else { - const update = mapValues(subReducers, (upReducer) => - upReducer(action), - ); - - newState = u(update, newState); - } - } - - const a = mutations[action.type] || mutations['+']; - - if (!a) return newState; - - return a(action.payload, action)(newState); - }; - - return wrapper ? wrapper(upreducer) : upreducer; -} diff --git a/src/mutations.test.todo b/src/mutations.test.todo deleted file mode 100644 index dccf11b..0000000 --- a/src/mutations.test.todo +++ /dev/null @@ -1,32 +0,0 @@ -import schema from 'json-schema-shorthand'; -import u from 'updeep'; - -import { action } from './actions.js'; - -import { Updux, dux } from './Updux.js'; - - -test('strings and generators', async () => { - const actionA = action('a'); - - const foo = dux({ - actions: { - b: null, - a: actionA, - }, - }); - - // as a string and defined - expect(() => foo.setMutation('a', () => {})).not.toThrow(); - - // as a generator and defined - expect(() => foo.setMutation(actionA, () => {})).not.toThrow(); - - // as a string, not defined - expect(() => foo.setMutation('c', () => {})).toThrow(); - - foo.setMutation(action('d'), () => {}); - - expect(foo.actions.d).toBeTypeOf('function'); -}); - From c660b7be1dbcba09aa28999ed60fd22c98a979ee Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Fri, 10 Mar 2023 10:31:30 -0500 Subject: [PATCH 32/46] beginning on selectors --- src/Updux.ts | 35 +++++++++++++++++++++++++++++------ src/selectors.test.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 src/selectors.test.ts diff --git a/src/Updux.ts b/src/Updux.ts index d35b81c..b54ab15 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -26,10 +26,10 @@ type ResolveAction< ? ActionArg : ActionArg extends (...args: any) => any ? ActionCreatorWithPreparedPayload< - Parameters, - ReturnType, - ActionType - > + Parameters, + ReturnType, + ActionType + > : ActionCreatorWithoutPayload; type ResolveActions< @@ -37,10 +37,10 @@ type ResolveActions< [key: string]: any; }, > = { - [ActionType in keyof A]: ActionType extends string + [ActionType in keyof A]: ActionType extends string ? ResolveAction : never; -}; + }; export type Mutation = Action, S = any> = ( payload: A extends { @@ -51,12 +51,22 @@ export type Mutation = Action, S = any> = ( action: A, ) => (state: S) => S | void; +export type AggregateSelectors = S; + +type SelectorForState = (state: S) => unknown; +type SelectorsForState = { + [key: string]: SelectorForState; +}; + export default class Updux< T_LocalState = Record, T_LocalActions extends { [actionType: string]: any; } = {}, T_Subduxes extends Record = {}, + T_LocalSelectors extends SelectorsForState< + AggregateState + > = {}, > { #localInitial: T_LocalState; #localActions: T_LocalActions; @@ -70,11 +80,19 @@ export default class Updux< #initial: AggregateState; + #localSelectors: Record< + string, + (state: AggregateState) => any + >; + + #selectors: AggregateSelectors; + constructor( config: Partial<{ initial: T_LocalState; actions: T_LocalActions; subduxes: T_Subduxes; + selectors: T_LocalSelectors; }>, ) { // TODO check that we can't alter the initial after the fact @@ -85,6 +103,7 @@ export default class Updux< this.#actions = buildActions(this.#localActions, this.#subduxes); this.#initial = buildInitial(this.#localInitial, this.#subduxes); + this.#localSelectors = config.selectors; } get actions() { @@ -110,6 +129,10 @@ export default class Updux< return store; } + get selectors(): T_LocalSelectors { + return this.#localSelectors as any; + } + // TODO memoize this sucker get reducer() { return buildReducer( diff --git a/src/selectors.test.ts b/src/selectors.test.ts new file mode 100644 index 0000000..86430c3 --- /dev/null +++ b/src/selectors.test.ts @@ -0,0 +1,27 @@ +import { test, expect } from 'vitest'; + +import Updux, { createAction } from './index.js'; + +test('basic selectors', () => { + type State = { x: number }; + + const foo = new Updux({ + initial: { + x: 1, + }, + selectors: { + getX: ({ x }: State) => x, + }, + subduxes: { + bar: new Updux({ + initial: { y: 2 }, + selectors: { + getY: ({ y }: { y: number }) => y, + }, + }), + }, + }); + + expect(foo.selectors.getY({ bar: { y: 3 } } as typeof foo.initial)).toBe(3); + expect(foo.selectors.getX({ x: 4 } as typeof foo.initial)).toBe(4); +}); From 7a5733425e1a950894197573940087c3e70d72d1 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Fri, 10 Mar 2023 12:04:19 -0500 Subject: [PATCH 33/46] selector test is passing --- src/Updux.ts | 25 ++++++++++++++++++------- src/selectors.test.ts | 9 +++++++-- src/types.ts | 15 +++++++++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/Updux.ts b/src/Updux.ts index b54ab15..d6d7790 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -12,7 +12,7 @@ import { ActionCreatorWithoutPayload, ActionCreatorWithPreparedPayload, } from '@reduxjs/toolkit'; -import { AggregateActions, Dux } from './types.js'; +import { AggregateActions, AggregateSelectors, Dux } from './types.js'; import { buildActions } from './buildActions.js'; import { buildInitial, AggregateState } from './initial.js'; import { buildReducer, MutationCase } from './reducer.js'; @@ -51,8 +51,6 @@ export type Mutation = Action, S = any> = ( action: A, ) => (state: S) => S | void; -export type AggregateSelectors = S; - type SelectorForState = (state: S) => unknown; type SelectorsForState = { [key: string]: SelectorForState; @@ -84,8 +82,7 @@ export default class Updux< string, (state: AggregateState) => any >; - - #selectors: AggregateSelectors; + #selectors: any; constructor( config: Partial<{ @@ -104,6 +101,16 @@ export default class Updux< this.#initial = buildInitial(this.#localInitial, this.#subduxes); this.#localSelectors = config.selectors; + + const basedSelectors = R.mergeAll( + Object.entries(this.#subduxes) + .filter(([slice, { selectors }]) => selectors) + .map(([slice, { selectors }]) => + R.mapValues(selectors, (s) => (state) => s(state[slice])), + ), + ); + + this.#selectors = R.merge(basedSelectors, this.#localSelectors); } get actions() { @@ -129,8 +136,12 @@ export default class Updux< return store; } - get selectors(): T_LocalSelectors { - return this.#localSelectors as any; + get selectors(): AggregateSelectors< + T_LocalSelectors, + T_Subduxes, + AggregateState + > { + return this.#selectors as any; } // TODO memoize this sucker diff --git a/src/selectors.test.ts b/src/selectors.test.ts index 86430c3..11fe428 100644 --- a/src/selectors.test.ts +++ b/src/selectors.test.ts @@ -22,6 +22,11 @@ test('basic selectors', () => { }, }); - expect(foo.selectors.getY({ bar: { y: 3 } } as typeof foo.initial)).toBe(3); - expect(foo.selectors.getX({ x: 4 } as typeof foo.initial)).toBe(4); + const sample = { + x: 4, + bar: { y: 3 }, + }; + + expect(foo.selectors.getY(sample)).toBe(3); + expect(foo.selectors.getX(sample)).toBe(4); }); diff --git a/src/types.ts b/src/types.ts index fb4a0a6..cca0034 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,7 @@ export type Dux< > = Partial<{ initial: STATE; actions: ACTIONS; + selectors: Record any>; reducer: ( state: STATE, action: ReturnType, @@ -13,11 +14,25 @@ export type Dux< }>; type ActionsOf = DUX extends { actions: infer A } ? A : {}; +type SelectorsOf = DUX extends { selectors: infer A } ? A : {}; export type AggregateActions = UnionToIntersection< ActionsOf | A >; +type BaseSelector any, STATE> = ( + state: STATE, +) => ReturnType; + +type BaseSelectors = { + [key in keyof S]: BaseSelector; +}; + +export type AggregateSelectors = BaseSelectors< + UnionToIntersection | S>, + STATE +>; + export type UnionToIntersection = ( U extends any ? (k: U) => void : never ) extends (k: infer I) => void From 95768706fa031aa5b672ea10dee6651ac589ee82 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Sat, 11 Mar 2023 11:06:38 -0500 Subject: [PATCH 34/46] effectsMiddleware --- src/Updux.ts | 47 +++++++++++++++++++++++++-- src/effects.test.ts | 79 +++++++++++++++++++++++++++++++++++++++++++++ src/effects.ts | 47 +++++++++++++++++++++++++++ src/selectors.todo | 43 ------------------------ 4 files changed, 171 insertions(+), 45 deletions(-) create mode 100644 src/effects.test.ts create mode 100644 src/effects.ts delete mode 100644 src/selectors.todo diff --git a/src/Updux.ts b/src/Updux.ts index d6d7790..6063217 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -4,6 +4,8 @@ import { applyMiddleware, DeepPartial, Action, + MiddlewareAPI, + AnyAction, } from 'redux'; import { configureStore, @@ -16,6 +18,8 @@ import { AggregateActions, AggregateSelectors, Dux } from './types.js'; import { buildActions } from './buildActions.js'; import { buildInitial, AggregateState } from './initial.js'; import { buildReducer, MutationCase } from './reducer.js'; +import { buildEffectsMiddleware, EffectMiddleware } from './effects.js'; +import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore.js'; type MyActionCreator = { type: string } & ((...args: any) => any); @@ -84,6 +88,8 @@ export default class Updux< >; #selectors: any; + #effects: any[] = []; + constructor( config: Partial<{ initial: T_LocalState; @@ -129,11 +135,31 @@ export default class Updux< ) { const preloadedState: any = options.initial ?? this.initial; const store = configureStore({ - reducer: ((state) => state) as Reducer, + reducer: ((state) => state) as Reducer< + AggregateState, + AnyAction + >, preloadedState, + middleware: [this.effectsMiddleware], }); - return store; + const dispatch: any = store.dispatch; + for (const a in this.actions) { + dispatch[a] = (...args) => { + const action = (this.actions as any)[a](...args); + dispatch(action); + return action; + } + } + + return store as ToolkitStore< + AggregateState + > & { + dispatch: AggregateActions< + ResolveActions, + T_Subduxes + >; + }; } get selectors(): AggregateSelectors< @@ -193,4 +219,21 @@ export default class Updux< ) { this.#defaultMutation = { mutation, terminal }; } + + addEffect(effect: EffectMiddleware) { + this.#effects.push(effect); + } + + get effects() { + return this.#effects; + } + + get effectsMiddleware() { + return buildEffectsMiddleware( + this.effects, + this.actions, + this.selectors, + ); + + } } diff --git a/src/effects.test.ts b/src/effects.test.ts new file mode 100644 index 0000000..906d83b --- /dev/null +++ b/src/effects.test.ts @@ -0,0 +1,79 @@ +import { buildEffectsMiddleware } from './effects.js'; +import Updux, { createAction } from './index.js'; + +test('buildEffectsMiddleware', () => { + let seen = 0; + + const mw = buildEffectsMiddleware( + [ + (api) => (next) => (action) => { + seen++; + expect(api).toHaveProperty('getState'); + expect(api.getState).toBeTypeOf('function'); + + expect(api.getState()).toEqual('the state'); + + expect(action).toHaveProperty('type'); + expect(next).toBeTypeOf('function'); + + expect(api).toHaveProperty('actions'); + expect(api.actions.action1()).toHaveProperty('type', 'action1'); + + api.dispatch.action1(); + + expect(api.selectors.getFoo(2)).toBe(2); + expect(api.getState.getFoo()).toBe('the state'); + expect(api.getState.getBar(2)).toBe('the state2'); + + next(action); + }, + ], + { + action1: createAction('action1'), + }, + { + getFoo: (state) => state, + getBar: (state) => (i) => state + i, + }, + ); + + expect(seen).toEqual(0); + const dispatch = vi.fn(); + mw({ getState: () => 'the state', dispatch })(() => { })({ + type: 'noop', + }); + expect(seen).toEqual(1); + expect(dispatch).toHaveBeenCalledWith({ type: 'action1' }); +}); + +test('basic', () => { + const dux = new Updux({ + initial: { + loaded: true, + }, + actions: { + foo: 0, + }, + }); + + let seen = 0; + dux.addEffect((api) => (next) => (action) => { + seen++; + expect(api).toHaveProperty('getState'); + expect(api.getState()).toHaveProperty('loaded'); + expect(action).toHaveProperty('type'); + expect(next).toBeTypeOf('function'); + next(action); + }); + + const store = dux.createStore(); + + expect(seen).toEqual(0); + + store.dispatch.foo(); + + expect(seen).toEqual(1); +}); + +// TODO subdux effects +// TODO allow to subscribe / unsubscribe effects? diff --git a/src/effects.ts b/src/effects.ts new file mode 100644 index 0000000..489e715 --- /dev/null +++ b/src/effects.ts @@ -0,0 +1,47 @@ +import { AnyAction } from '@reduxjs/toolkit'; +import { MiddlewareAPI } from '@reduxjs/toolkit'; +import { Dispatch } from '@reduxjs/toolkit'; + +export interface EffectMiddleware { + (api: MiddlewareAPI): ( + next: Dispatch, + ) => (action: AnyAction) => any; +} + +const composeMw = (mws) => (api) => (originalNext) => + mws.reduceRight((next, mw) => mw(api)(next), originalNext); + +export function buildEffectsMiddleware( + effects = [], + actions = {}, + selectors = {}, +) { + return ({ + getState: originalGetState, + dispatch: originalDispatch, + ...rest + }) => { + const dispatch = (action) => originalDispatch(action); + + for (const a in actions) { + dispatch[a] = (...args) => dispatch(actions[a](...args)); + } + + const getState = () => originalGetState(); + for (const s in selectors) { + getState[s] = (...args) => { + let result = selectors[s](originalGetState()); + if (typeof result === 'function') return result(...args); + return result; + }; + } + + let mws = effects.map((e) => + e({ getState, dispatch, actions, selectors, ...rest }), + ); + + return (originalNext) => { + return mws.reduceRight((next, mw) => mw(next), originalNext); + }; + }; +} diff --git a/src/selectors.todo b/src/selectors.todo deleted file mode 100644 index e90b4e9..0000000 --- a/src/selectors.todo +++ /dev/null @@ -1,43 +0,0 @@ -import R from 'remeda'; - -export function buildSelectors( - localSelectors, - findSelectors = {}, - subduxes = {}, -) { - const subSelectors = Object.entries(subduxes).map( - ([slice, { selectors }]) => { - if (!selectors) return {}; - if (slice === '*') return {}; - - return R.mapValues( - selectors, - (func) => (state) => func(state[slice]), - ); - }, - ); - - let splat = {}; - - for (const name in findSelectors) { - splat[name] = - (mainState) => - (...args) => { - const state = findSelectors[name](mainState)(...args); - - return R.merge( - { state }, - R.mapValues( - subduxes['*']?.selectors ?? {}, - (selector) => - (...args) => { - let value = selector(state); - if (typeof value !== 'function') return value; - return value(...args); - }, - ), - ); - }; - } - return R.mergeAll([...subSelectors, localSelectors, splat]); -} From daa8421251e6f12783fb9b4c012c68bfa1b4c843 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Sat, 11 Mar 2023 13:37:33 -0500 Subject: [PATCH 35/46] subdux effects --- src/Updux.ts | 34 +++++++++++++++++++++++++++------- src/effects.test.ts | 31 +++++++++++++++++++++++++++++++ src/types.ts | 3 ++- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/Updux.ts b/src/Updux.ts index 6063217..8045e73 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -6,6 +6,7 @@ import { Action, MiddlewareAPI, AnyAction, + Middleware, } from 'redux'; import { configureStore, @@ -88,7 +89,7 @@ export default class Updux< >; #selectors: any; - #effects: any[] = []; + #localEffects: Middleware[] = []; constructor( config: Partial<{ @@ -128,19 +129,42 @@ export default class Updux< return this.#initial; } + get effects() { + return [ + ...this.#localEffects, + ...Object.entries(this.#subduxes).flatMap( + ([slice, { effects }]) => { + if (!effects) return []; + return effects.map(effect => (api) => effect({ + ...api, + getState: () => api.getState()[slice], + })) + } + ) + ] + } + createStore( options: Partial<{ initial: T_LocalState; }> = {}, ) { const preloadedState: any = options.initial ?? this.initial; + + const effects = buildEffectsMiddleware( + this.effects, + this.actions, + this.selectors, + ); + + const store = configureStore({ reducer: ((state) => state) as Reducer< AggregateState, AnyAction >, preloadedState, - middleware: [this.effectsMiddleware], + middleware: [effects], }); const dispatch: any = store.dispatch; @@ -221,11 +245,7 @@ export default class Updux< } addEffect(effect: EffectMiddleware) { - this.#effects.push(effect); - } - - get effects() { - return this.#effects; + this.#localEffects.push(effect); } get effectsMiddleware() { diff --git a/src/effects.test.ts b/src/effects.test.ts index 906d83b..f500cae 100644 --- a/src/effects.test.ts +++ b/src/effects.test.ts @@ -75,5 +75,36 @@ test('basic', () => { expect(seen).toEqual(1); }); +test('subdux', () => { + const bar = new Updux({ + initial: 'bar state', + actions: { foo: 0 }, + }); + + let seen = 0; + bar.addEffect((api) => next => action => { + seen++; + expect(api.getState()).toBe('bar state'); + next(action); + }); + + const dux = new Updux({ + initial: { + loaded: true, + }, + subduxes: { + bar + }, + }); + + const store = dux.createStore(); + + expect(seen).toEqual(0); + + store.dispatch.foo(); + + expect(seen).toEqual(1); +}); + // TODO subdux effects // TODO allow to subscribe / unsubscribe effects? diff --git a/src/types.ts b/src/types.ts index cca0034..2420635 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { Action, ActionCreator, Reducer } from 'redux'; +import { Action, ActionCreator, Middleware, Reducer } from 'redux'; export type Dux< STATE = any, @@ -11,6 +11,7 @@ export type Dux< state: STATE, action: ReturnType, ) => STATE; + effects: Middleware[]; }>; type ActionsOf = DUX extends { actions: infer A } ? A : {}; From 5edbc688bebf85cb5d1b170f6671d4d3c6483177 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Sat, 11 Mar 2023 13:51:23 -0500 Subject: [PATCH 36/46] make tsc happy --- src/types.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index 2420635..ee58fa3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,11 +25,15 @@ type BaseSelector any, STATE> = ( state: STATE, ) => ReturnType; -type BaseSelectors = { +type BaseSelectors, STATE> = { [key in keyof S]: BaseSelector; }; -export type AggregateSelectors = BaseSelectors< +export type AggregateSelectors< + S extends Record any>, + SUBS extends Record, + STATE = {}, +> = BaseSelectors< UnionToIntersection | S>, STATE >; From 17cd3bec463ce7628fe1325feda52229efa93bd1 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Sat, 11 Mar 2023 16:47:26 -0500 Subject: [PATCH 37/46] remove actions.todo --- src/actions.todo | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 src/actions.todo diff --git a/src/actions.todo b/src/actions.todo deleted file mode 100644 index 51ad508..0000000 --- a/src/actions.todo +++ /dev/null @@ -1,26 +0,0 @@ -export function isActionGen(action) { - return typeof action === 'function' && action.type; -} - -export function action(type, payloadFunction, transformer) { - let generator = function (...payloadArg) { - const result = { type }; - - if (payloadFunction) { - result.payload = payloadFunction(...payloadArg); - } else { - if (payloadArg[0] !== undefined) result.payload = payloadArg[0]; - } - - return result; - }; - - if (transformer) { - const orig = generator; - generator = (...args) => transformer(orig(...args), args); - } - - generator.type = type; - - return generator; -} From f32269190694e58337fd4553573ec334b52e0696 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Sat, 11 Mar 2023 17:10:38 -0500 Subject: [PATCH 38/46] selectors and store --- src/Updux.ts | 18 +++++++++- src/buildMiddleware/index.js | 68 ++++++++++++++++++++++++------------ src/effects.ts | 11 ++++++ src/selectors.test.ts | 10 ++++++ 4 files changed, 83 insertions(+), 24 deletions(-) diff --git a/src/Updux.ts b/src/Updux.ts index 8045e73..cd27cca 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -19,11 +19,18 @@ import { AggregateActions, AggregateSelectors, Dux } from './types.js'; import { buildActions } from './buildActions.js'; import { buildInitial, AggregateState } from './initial.js'; import { buildReducer, MutationCase } from './reducer.js'; -import { buildEffectsMiddleware, EffectMiddleware } from './effects.js'; +import { augmentGetState, buildEffectsMiddleware, EffectMiddleware } from './effects.js'; import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore.js'; type MyActionCreator = { type: string } & ((...args: any) => any); +type XSel = R extends Function ? R : () => R; +type CurriedSelector = S extends (...args: any) => infer R ? XSel : never; + +type CurriedSelectors = { + [key in keyof S]: CurriedSelector +} + type ResolveAction< ActionType extends string, ActionArg extends any, @@ -176,6 +183,8 @@ export default class Updux< } } + store.getState = augmentGetState(store.getState, this.selectors); + return store as ToolkitStore< AggregateState > & { @@ -183,6 +192,12 @@ export default class Updux< ResolveActions, T_Subduxes >; + } & { + getState: CurriedSelectors + >> }; } @@ -257,3 +272,4 @@ export default class Updux< } } + diff --git a/src/buildMiddleware/index.js b/src/buildMiddleware/index.js index 2085412..4e2d78d 100644 --- a/src/buildMiddleware/index.js +++ b/src/buildMiddleware/index.js @@ -1,31 +1,42 @@ import { mapValues, map, get } from 'lodash-es'; const middlewareFor = (type, middleware) => (api) => (next) => (action) => { - if (type !== '*' && action.type !== type) - return next(action); + if (type !== '*' && action.type !== type) return next(action); return middleware(api)(next)(action); }; const sliceMw = (slice, mw) => (api) => { const getSliceState = () => get(api.getState(), slice); - return mw(Object.assign(Object.assign({}, api), { getState: getSliceState })); + return mw( + Object.assign(Object.assign({}, api), { getState: getSliceState }), + ); }; + +export const augmentGetState = (getState, selectors) => + Object.assign( + getState, + mapValues(selectors, (selector) => { + return (...args) => { + let result = selector(api.getState()); + if (typeof result === 'function') return result(...args); + return result; + }; + }), + ); + export function augmentMiddlewareApi(api, actions, selectors) { - const getState = () => api.getState(); + const getState = augmentGetState(() => api.getState(), selectors); const dispatch = (action) => api.dispatch(action); - Object.assign(getState, mapValues(selectors, (selector) => { - return (...args) => { - let result = selector(api.getState()); - if (typeof result === 'function') - return result(...args); - return result; - }; - })); - Object.assign(dispatch, mapValues(actions, (action) => { - return (...args) => api.dispatch(action(...args)); - })); - return Object.assign(Object.assign({}, api), { getState, + Object.assign( + dispatch, + mapValues(actions, (action) => { + return (...args) => api.dispatch(action(...args)); + }), + ); + return Object.assign(Object.assign({}, api), { + getState, dispatch, actions, - selectors }); + selectors, + }); } export const effectToMiddleware = (effect, actions, selectors) => { let mw = effect; @@ -37,12 +48,23 @@ export const effectToMiddleware = (effect, actions, selectors) => { } return (api) => mw(augmentMiddlewareApi(api, actions, selectors)); }; -const composeMw = (mws) => (api) => (original_next) => mws.reduceRight((next, mw) => mw(api)(next), original_next); -export function buildMiddleware(effects = [], actions = {}, selectors = {}, sub = {}, wrapper = undefined, dux = undefined) { - let inner = map(sub, ({ middleware }, slice) => slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined).filter((x) => x); - const local = effects.map((effect) => effectToMiddleware(effect, actions, selectors)); +const composeMw = (mws) => (api) => (original_next) => + mws.reduceRight((next, mw) => mw(api)(next), original_next); +export function buildMiddleware( + effects = [], + actions = {}, + selectors = {}, + sub = {}, + wrapper = undefined, + dux = undefined, +) { + let inner = map(sub, ({ middleware }, slice) => + slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined, + ).filter((x) => x); + const local = effects.map((effect) => + effectToMiddleware(effect, actions, selectors), + ); let mws = [...local, ...inner]; - if (wrapper) - mws = wrapper(mws, dux); + if (wrapper) mws = wrapper(mws, dux); return composeMw(mws); } diff --git a/src/effects.ts b/src/effects.ts index 489e715..4d76595 100644 --- a/src/effects.ts +++ b/src/effects.ts @@ -11,6 +11,17 @@ export interface EffectMiddleware { const composeMw = (mws) => (api) => (originalNext) => mws.reduceRight((next, mw) => mw(api)(next), originalNext); +export const augmentGetState = (originalGetState, selectors) => { + const getState = () => originalGetState(); + for (const s in selectors) { + getState[s] = (...args) => { + let result = selectors[s](originalGetState()); + if (typeof result === 'function') return result(...args); + return result; + }; + } + return getState; +}; export function buildEffectsMiddleware( effects = [], actions = {}, diff --git a/src/selectors.test.ts b/src/selectors.test.ts index 11fe428..d0695b9 100644 --- a/src/selectors.test.ts +++ b/src/selectors.test.ts @@ -17,6 +17,10 @@ test('basic selectors', () => { initial: { y: 2 }, selectors: { getY: ({ y }: { y: number }) => y, + getYPlus: + ({ y }) => + (incr: number) => + (y + incr) as number, }, }), }, @@ -29,4 +33,10 @@ test('basic selectors', () => { expect(foo.selectors.getY(sample)).toBe(3); expect(foo.selectors.getX(sample)).toBe(4); + + expect(foo.selectors.getYPlus(sample)(3)).toBe(6); + + const store = foo.createStore(); + expect(store.getState.getY()).toBe(2); + expect(store.getState.getYPlus(3)).toBe(5); }); From 5297aecba6ea3e2440c1e8b2f59156660ed44c76 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Sat, 11 Mar 2023 17:23:25 -0500 Subject: [PATCH 39/46] augmentMiddlewareApi --- src/effects.ts | 45 +++++++++++++++++++------------------ src/middleware.test.todo | 48 ---------------------------------------- 2 files changed, 23 insertions(+), 70 deletions(-) delete mode 100644 src/middleware.test.todo diff --git a/src/effects.ts b/src/effects.ts index 4d76595..ad5076a 100644 --- a/src/effects.ts +++ b/src/effects.ts @@ -22,34 +22,35 @@ export const augmentGetState = (originalGetState, selectors) => { } return getState; }; + +const augmentDispatch = (originalDispatch, actions) => { + const dispatch = (action) => originalDispatch(action); + + for (const a in actions) { + dispatch[a] = (...args) => dispatch(actions[a](...args)); + } + return dispatch; +}; + +const augmentMiddlewareApi = (api, actions, selectors) => { + return { + ...api, + getState: augmentGetState(api.getState, selectors), + dispatch: augmentDispatch(api.dispatch, actions), + actions, + selectors, + }; +}; + export function buildEffectsMiddleware( effects = [], actions = {}, selectors = {}, ) { - return ({ - getState: originalGetState, - dispatch: originalDispatch, - ...rest - }) => { - const dispatch = (action) => originalDispatch(action); + return (api) => { + const newApi = augmentMiddlewareApi(api, actions, selectors); - for (const a in actions) { - dispatch[a] = (...args) => dispatch(actions[a](...args)); - } - - const getState = () => originalGetState(); - for (const s in selectors) { - getState[s] = (...args) => { - let result = selectors[s](originalGetState()); - if (typeof result === 'function') return result(...args); - return result; - }; - } - - let mws = effects.map((e) => - e({ getState, dispatch, actions, selectors, ...rest }), - ); + let mws = effects.map((e) => e(newApi)); return (originalNext) => { return mws.reduceRight((next, mw) => mw(next), originalNext); diff --git a/src/middleware.test.todo b/src/middleware.test.todo deleted file mode 100644 index 8e11cfa..0000000 --- a/src/middleware.test.todo +++ /dev/null @@ -1,48 +0,0 @@ -import { test, expect, vi } from 'vitest'; - -import { buildMiddleware } from './middleware.js'; -import { action } from './actions.js'; - -test('buildMiddleware, effects', async () => { - const effectMock = vi.fn(); - - const mw = buildMiddleware([ - ['*', (api) => (next) => (action) => effectMock()], - ]); - - mw({})(() => {})({}); - - expect(effectMock).toHaveBeenCalledOnce(); -}); - -test('buildMiddleware, augmented api', async () => { - const myAction = action('myAction'); - - const mw = buildMiddleware( - [ - [ - '*', - (api) => (next) => (action) => { - expect(api.getState.mySelector()).toEqual(13); - api.dispatch(myAction()); - next(); - }, - ], - ], - { - myAction, - }, - { - mySelector: (state) => state?.selected, - }, - ); - - const dispatch = vi.fn(); - const getState = vi.fn(() => ({ selected: 13 })); - const next = vi.fn(); - - mw({ dispatch, getState })(next)(myAction()); - - expect(next).toHaveBeenCalledOnce(); - expect(dispatch).toHaveBeenCalledWith(myAction()); -}); From 56625c508343868631b24d10c67cc5f1e1e43499 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Tue, 14 Mar 2023 14:00:11 -0400 Subject: [PATCH 40/46] reactions... work? --- src/Updux.ts | 108 ++++++++++++++++++++++++++++++++++++---- src/effects.ts | 2 +- src/reactions.test.todo | 20 -------- src/reactions.test.ts | 82 ++++++++++++++++++++++++++++++ src/types.ts | 1 + 5 files changed, 183 insertions(+), 30 deletions(-) create mode 100644 src/reactions.test.ts diff --git a/src/Updux.ts b/src/Updux.ts index cd27cca..34000e2 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -7,6 +7,7 @@ import { MiddlewareAPI, AnyAction, Middleware, + Dispatch, } from 'redux'; import { configureStore, @@ -19,7 +20,7 @@ import { AggregateActions, AggregateSelectors, Dux } from './types.js'; import { buildActions } from './buildActions.js'; import { buildInitial, AggregateState } from './initial.js'; import { buildReducer, MutationCase } from './reducer.js'; -import { augmentGetState, buildEffectsMiddleware, EffectMiddleware } from './effects.js'; +import { augmentGetState, augmentMiddlewareApi, buildEffectsMiddleware, EffectMiddleware } from './effects.js'; import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore.js'; type MyActionCreator = { type: string } & ((...args: any) => any); @@ -54,6 +55,17 @@ type ResolveActions< : never; }; +type Reaction = + (api: M, state: S, previousState: S, unsubscribe: () => void) => void; + +type AugmentedMiddlewareAPI = + MiddlewareAPI, S> & { + dispatch: A, + getState: CurriedSelectors, + actions: A, + selectors: SELECTORS, + }; + export type Mutation = Action, S = any> = ( payload: A extends { payload: infer P; @@ -151,6 +163,19 @@ export default class Updux< ] } + get reactions(): any { + return [...this.#localReactions, + ...Object.entries(this.#subduxes).flatMap( + ([slice, { reactions }]) => reactions.map( + (r) => (api, unsub) => r({ + ...api, + getState: () => api.getState()[slice], + }, unsub) + ) + ) + ]; + } + createStore( options: Partial<{ initial: T_LocalState; @@ -166,7 +191,7 @@ export default class Updux< const store = configureStore({ - reducer: ((state) => state) as Reducer< + reducer: this.reducer as Reducer< AggregateState, AnyAction >, @@ -185,20 +210,27 @@ export default class Updux< store.getState = augmentGetState(store.getState, this.selectors); + for (const reaction of this.reactions) { + let unsub; + const r = reaction(store); + + unsub = store.subscribe(() => r(unsub)); + } + + return store as ToolkitStore< AggregateState - > & { - dispatch: AggregateActions< + > & AugmentedMiddlewareAPI< + AggregateState, + AggregateActions< ResolveActions, T_Subduxes - >; - } & { - getState: CurriedSelectors, AggregateSelectors< T_LocalSelectors, T_Subduxes, AggregateState - >> - }; + > + >; } get selectors(): AggregateSelectors< @@ -271,5 +303,63 @@ export default class Updux< ); } + + #localReactions: any[] = []; + addReaction(reaction: Reaction, + AugmentedMiddlewareAPI< + AggregateState, + AggregateActions< + ResolveActions, + T_Subduxes + >, AggregateSelectors< + T_LocalSelectors, + T_Subduxes, + AggregateState + > + > + >) { + + let previous: any; + + const memoized = (api: any) => { + api = augmentMiddlewareApi(api, + this.actions, + this.selectors + ); + return (unsub: () => void) => { + const state = api.getState(); + if (state === previous) return; + let p = previous; + previous = state; + reaction(api, state, p, unsub); + } + } + ; + + this.#localReactions.push(memoized); + } + + // internal method REMOVE + subscribeTo(store, subscription) { + const localStore = augmentMiddlewareApi({ + ...store, + subscribe: (subscriber) => this.subscribeTo(store, subscriber), // TODO not sure + }, this.actions, this.selectors); + + const subscriber = subscription(localStore); + + let previous; + let unsub; + + const memoSub = () => { + const state = store.getState(); + if (state === previous) return; + let p = previous; + previous = state; + subscriber(state, p, unsub); + }; + + return store.subscribe(memoSub); + } } diff --git a/src/effects.ts b/src/effects.ts index ad5076a..59cad76 100644 --- a/src/effects.ts +++ b/src/effects.ts @@ -32,7 +32,7 @@ const augmentDispatch = (originalDispatch, actions) => { return dispatch; }; -const augmentMiddlewareApi = (api, actions, selectors) => { +export const augmentMiddlewareApi = (api, actions, selectors) => { return { ...api, getState: augmentGetState(api.getState, selectors), diff --git a/src/reactions.test.todo b/src/reactions.test.todo index 8b17efb..8f006f8 100644 --- a/src/reactions.test.todo +++ b/src/reactions.test.todo @@ -2,26 +2,6 @@ import { test, expect, vi } from 'vitest'; import { Updux } from './Updux.js'; -test('basic reactions', async () => { - const spyA = vi.fn(); - const spyB = vi.fn(); - const foo = new Updux({ - initial: { i: 0 }, - reactions: [() => spyA], - actions: { inc: null }, - mutations: { - inc: () => (state) => ({ ...state, i: state.i + 1 }), - }, - }); - - foo.addReaction((api) => spyB); - - const store = foo.createStore(); - store.dispatch.inc(); - - expect(spyA).toHaveBeenCalledOnce(); - expect(spyB).toHaveBeenCalledOnce(); -}); test('subduxes reactions', async () => { const spyA = vi.fn(); diff --git a/src/reactions.test.ts b/src/reactions.test.ts new file mode 100644 index 0000000..a6cf647 --- /dev/null +++ b/src/reactions.test.ts @@ -0,0 +1,82 @@ +import { test, expect, vi } from 'vitest'; +import Updux from './index.js'; + +test('basic reactions', () => { + const foo = new Updux({ + initial: 0, + actions: { inc: 0, reset: 0 }, + }); + + // TODO immer that stuff + foo.addMutation(foo.actions.inc, () => (state) => state + 1); + foo.addMutation(foo.actions.reset, () => (state) => 0); + + foo.addReaction((api, state, _previous, unsubscribe) => { + if (state < 3) return; + unsubscribe(); + api.dispatch.reset(); + }); + + // TODO + //reaction: (api) => (state,previous,unsubscribe) + + const store = foo.createStore(); + + store.dispatch.inc(); + expect(store.getState()).toEqual(1); + store.dispatch.inc(); + store.dispatch.inc(); + + expect(store.getState()).toEqual(0); // we've been reset + + store.dispatch.inc(); + store.dispatch.inc(); + store.dispatch.inc(); + store.dispatch.inc(); + + expect(store.getState()).toEqual(4); // we've unsubscribed +}); + +test('subdux reactions', () => { + const bar = new Updux({ + initial: 0, + actions: { inc: 0, reset: 0 }, + selectors: { + getIt: (x) => x, + }, + }); + + const foo = new Updux({ actions: { notInBar: 0 }, subduxes: { bar } }); + + // TODO immer that stuff + bar.addMutation(foo.actions.inc, () => (state) => state + 1); + bar.addMutation(foo.actions.reset, () => (state) => 0); + + let seen = 0; + bar.addReaction((api, state, _previous, unsubscribe) => { + seen++; + expect(api.actions).not.toHaveProperty('notInBar'); + expect(state).toBeTypeOf('number'); + if (state < 3) return; + unsubscribe(); + api.dispatch.reset(); + }); + + const store = foo.createStore(); + + store.dispatch.inc(); + expect(seen).toEqual(1); + expect(store.getState()).toEqual({ bar: 1 }); + expect(store.getState.getIt()).toEqual(1); + store.dispatch.inc(); + store.dispatch.inc(); + + expect(store.getState.getIt()).toEqual(0); // we've been reset + + store.dispatch.inc(); + store.dispatch.inc(); + store.dispatch.inc(); + store.dispatch.inc(); + + expect(store.getState.getIt()).toEqual(4); // we've unsubscribed +}); diff --git a/src/types.ts b/src/types.ts index ee58fa3..54db08d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,6 +12,7 @@ export type Dux< action: ReturnType, ) => STATE; effects: Middleware[]; + reactions: ((...args: any[]) => void)[]; }>; type ActionsOf = DUX extends { actions: infer A } ? A : {}; From 0e894b7db1648969043b2371dec2aa01a0da8f2c Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 20 Mar 2023 10:25:31 -0400 Subject: [PATCH 41/46] remove obsolete todos --- src/createStore.test.todo | 18 ------------------ src/reactions.test.todo | 30 ------------------------------ src/utils.todo | 6 ------ 3 files changed, 54 deletions(-) delete mode 100644 src/createStore.test.todo delete mode 100644 src/reactions.test.todo delete mode 100644 src/utils.todo diff --git a/src/createStore.test.todo b/src/createStore.test.todo deleted file mode 100644 index 286de5e..0000000 --- a/src/createStore.test.todo +++ /dev/null @@ -1,18 +0,0 @@ -import { test, expect } from 'vitest'; -import { Updux } from './Updux.js'; - -test('basic createStore', async () => { - const foo = new Updux({ - initial: { a: 1 }, - actions: { - a1: null, - }, - }); - - const store = foo.createStore(); - - expect(store.getState).toBeTypeOf('function'); - expect(store.getState()).toEqual({ a: 1 }); - - expect(store.actions.a1).toBeTypeOf('function'); -}); diff --git a/src/reactions.test.todo b/src/reactions.test.todo deleted file mode 100644 index 8f006f8..0000000 --- a/src/reactions.test.todo +++ /dev/null @@ -1,30 +0,0 @@ -import { test, expect, vi } from 'vitest'; - -import { Updux } from './Updux.js'; - - -test('subduxes reactions', async () => { - const spyA = vi.fn(); - const spyB = vi.fn(); - const foo = new Updux({ - subduxes: { - a: new Updux({ - initial: 1, - reactions: [() => (state) => spyA(state)], - actions: { inc: null }, - mutations: { - inc: () => (state) => state + 1, - }, - }), - b: new Updux({ initial: 10, reactions: [() => spyB] }), - }, - }); - - const store = foo.createStore(); - store.dispatch.inc(); - store.dispatch.inc(); - - expect(spyA).toHaveBeenCalledTimes(2); - expect(spyA).toHaveBeenCalledWith(3); - expect(spyB).toHaveBeenCalledOnce(); // the original inc initialized the state -}); diff --git a/src/utils.todo b/src/utils.todo deleted file mode 100644 index 956a5ff..0000000 --- a/src/utils.todo +++ /dev/null @@ -1,6 +0,0 @@ -export const matches = (conditions) => (target) => - Object.entries(conditions).every(([key, value]) => - typeof value === 'function' - ? value(target[key]) - : target[key] === value, - ); From bde9cac0530602e0f31db50b62b6a4d54ae12f32 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Wed, 22 Mar 2023 12:04:07 -0400 Subject: [PATCH 42/46] change reaction api --- package.json | 1 + src/Updux.ts | 7 +++++-- src/reactions.test.ts | 4 ++-- src/reducer.ts | 4 +++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2a05480..ea2b77c 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "prettier": "^2.7.1", "redux-toolkit": "^1.1.2", "typescript": "^4.9.5", + "vite": "^4.2.1", "vitest": "0.23.1" } } diff --git a/src/Updux.ts b/src/Updux.ts index 34000e2..835a840 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -56,7 +56,7 @@ type ResolveActions< }; type Reaction = - (api: M, state: S, previousState: S, unsubscribe: () => void) => void; + (api: M) => (state: S, previousState: S, unsubscribe: () => void) => any; type AugmentedMiddlewareAPI = MiddlewareAPI, S> & { @@ -217,6 +217,8 @@ export default class Updux< unsub = store.subscribe(() => r(unsub)); } + (store as any).actions = this.actions; + return store as ToolkitStore< AggregateState @@ -326,12 +328,13 @@ export default class Updux< this.actions, this.selectors ); + const r = reaction(api); return (unsub: () => void) => { const state = api.getState(); if (state === previous) return; let p = previous; previous = state; - reaction(api, state, p, unsub); + r(state, p, unsub); } } ; diff --git a/src/reactions.test.ts b/src/reactions.test.ts index a6cf647..8dc5d18 100644 --- a/src/reactions.test.ts +++ b/src/reactions.test.ts @@ -11,7 +11,7 @@ test('basic reactions', () => { foo.addMutation(foo.actions.inc, () => (state) => state + 1); foo.addMutation(foo.actions.reset, () => (state) => 0); - foo.addReaction((api, state, _previous, unsubscribe) => { + foo.addReaction((api) => (state, _previous, unsubscribe) => { if (state < 3) return; unsubscribe(); api.dispatch.reset(); @@ -53,7 +53,7 @@ test('subdux reactions', () => { bar.addMutation(foo.actions.reset, () => (state) => 0); let seen = 0; - bar.addReaction((api, state, _previous, unsubscribe) => { + bar.addReaction((api) => (state, _previous, unsubscribe) => { seen++; expect(api.actions).not.toHaveProperty('notInBar'); expect(state).toBeTypeOf('number'); diff --git a/src/reducer.ts b/src/reducer.ts index 9ca2571..2f92f5a 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -3,6 +3,7 @@ import { BaseActionCreator } from '@reduxjs/toolkit/dist/createAction.js'; import * as R from 'remeda'; import { Dux } from './types.js'; import { Mutation } from './Updux.js'; +import u from '@yanick/updeep-remeda'; export type MutationCase = { matcher: (action: Action) => boolean; @@ -22,6 +23,7 @@ export function buildReducer( // TODO defaultMutation // const reducer = (state = initialState, action: Action) => { + const orig = state; if (!action?.type) throw new Error('upreducer called with a bad action'); @@ -49,7 +51,7 @@ export function buildReducer( if (!terminal && Object.keys(subduxes).length > 0) { // subduxes - state = R.merge( + state = u.update( state, R.mapValues(subReducers, (reducer, slice) => (reducer as any)(state[slice], action), From a9cb4085214bbf6b1eb7ee5820cb580e875178ae Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 23 Mar 2023 18:10:33 -0400 Subject: [PATCH 43/46] add createPayloadAction --- src/Updux.test.ts | 38 ++++++++++++++++++++++++++++++++++++++ src/actions.ts | 13 +++++++++++++ src/index.ts | 2 +- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/Updux.test.ts diff --git a/src/Updux.test.ts b/src/Updux.test.ts new file mode 100644 index 0000000..822aaa2 --- /dev/null +++ b/src/Updux.test.ts @@ -0,0 +1,38 @@ +import { test, expect } from 'vitest'; +import Updux from './Updux'; + +test('subdux idempotency', () => { + // const c = new Updux({ + // initial: 2, + // }); + // const b = new Updux({ + // subduxes: { + // c, + // }, + // }); + const foo = new Updux({ + subduxes: { + a: new Updux({ initial: 2 }), + }, + }); + + let fooState = foo.reducer(undefined, { type: 'noop' }); + expect(foo.reducer(fooState, { type: 'noop' })).toBe(fooState); + + return; + const store = foo.createStore(); + + const s1 = store.getState(); + console.log(s1); + + store.dispatch({ type: 'noop' }); + const s2 = store.getState(); + + expect(s2.a).toBe(s1.a); + + let bState = b.reducer(undefined, { type: 'noop' }); + expect(b.reducer(bState, { type: 'noop' })).toBe(bState); + + expect(s2.b).toBe(s1.b); + expect(s2).toBe(s1); +}); diff --git a/src/actions.ts b/src/actions.ts index f8d7dcf..cf559b6 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -13,3 +13,16 @@ export const withPayload: WithPayload = ((prepare) => (...input) => ({ payload: prepare ? prepare(...input) : input[0], })) as any; + +export const createPayloadAction = < + P extends any = any, + T extends string = string, + F extends (...args: any[]) => P = (input: P) => P, +>( + type: T, + prepare?: F, +) => + createAction( + type, + withPayload, Parameters>(prepare ?? (id as any)), + ); diff --git a/src/index.ts b/src/index.ts index 6488bac..6e14d5a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import Updux from './Updux.js'; -export { withPayload, createAction } from './actions.js'; +export { withPayload, createAction, createPayloadAction } from './actions.js'; export default Updux; From 5ff07b2e2f35139e6f38fed427d9aea71d6af5ad Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 23 Mar 2023 18:15:32 -0400 Subject: [PATCH 44/46] rename initial as initialState --- src/Updux.original | 18 +++++------ src/Updux.test.ts | 4 +-- src/Updux.todo | 12 ++++---- src/Updux.ts | 24 +++++++-------- src/actions.ts | 1 + src/buildInitial.test.ts | 4 +-- src/dux-selectors.test.todo | 8 ++--- src/effects.test.ts | 6 ++-- src/initial.test.ts | 58 ++++++++++++++++++------------------ src/initial.ts | 6 ++-- src/mutations.test.ts | 8 ++--- src/reactions.test.ts | 4 +-- src/reducer.test.ts | 4 +-- src/reducer.ts | 4 +-- src/selectors.test.ts | 4 +-- src/splatReactions.test.todo | 8 ++--- src/tutorial.test.ts | 14 ++++----- src/types.ts | 2 +- 18 files changed, 95 insertions(+), 94 deletions(-) diff --git a/src/Updux.original b/src/Updux.original index 586b803..51ca3b9 100644 --- a/src/Updux.original +++ b/src/Updux.original @@ -36,10 +36,10 @@ export interface UpduxConfig< TSubduxes = {} > { /** - * Local initial state. + * Local initialState state. * @default {} */ - initial?: TState; + initialState?: TState; /** * Subduxes to be merged to this dux. @@ -116,7 +116,7 @@ export class Updux< TSubduxes extends object = {} > { /** @type { unknown } */ - #initial = {}; + #initialState = {}; #subduxes = {}; /** @type Record */ @@ -134,7 +134,7 @@ export class Updux< constructor( config: UpduxConfig ) { - this.#initial = config.initial ?? {}; + this.#initialState = config.initialState ?? {}; this.#subduxes = config.subduxes ?? {}; if (config.subduxes) { @@ -210,8 +210,8 @@ export class Updux< } /** @member { unknown } */ - get initial(): AggregateDuxState { - return this.#memoInitial(this.#initial, this.#subduxes); + get initialState(): AggregateDuxState { + return this.#memoInitial(this.#initialState, this.#subduxes); } get actions(): AggregateDuxActions { @@ -233,7 +233,7 @@ export class Updux< ItemsOf> > { return this.#memoUpreducer( - this.initial, + this.initialState, this.#mutations, this.#subduxes, this.#upreducerWrapper @@ -407,7 +407,7 @@ export class Updux< }; } - createStore(initial?: unknown, enhancerGenerator?: Function) { + createStore(initialState?: unknown, enhancerGenerator?: Function) { const enhancer = (enhancerGenerator ?? applyMiddleware)( this.middleware ); @@ -419,7 +419,7 @@ export class Updux< actions: AggregateDuxActions; } = reduxCreateStore( this.reducer as any, - initial ?? this.initial, + initialState ?? this.initialState, enhancer ) as any; diff --git a/src/Updux.test.ts b/src/Updux.test.ts index 822aaa2..1e5f0ea 100644 --- a/src/Updux.test.ts +++ b/src/Updux.test.ts @@ -3,7 +3,7 @@ import Updux from './Updux'; test('subdux idempotency', () => { // const c = new Updux({ - // initial: 2, + // initialState: 2, // }); // const b = new Updux({ // subduxes: { @@ -12,7 +12,7 @@ test('subdux idempotency', () => { // }); const foo = new Updux({ subduxes: { - a: new Updux({ initial: 2 }), + a: new Updux({ initialState: 2 }), }, }); diff --git a/src/Updux.todo b/src/Updux.todo index 31e9b04..dd22be2 100644 --- a/src/Updux.todo +++ b/src/Updux.todo @@ -35,7 +35,7 @@ export class Updux { this.#middlewareWrapper = config.middlewareWrapper; - this.#localInitial = config.initial; + this.#localInitial = config.initialState; this.#subduxes = config.subduxes ?? {}; this.#actions = R.mapValues(config.actions ?? {}, (arg, name) => @@ -89,7 +89,7 @@ export class Updux { return this.#actions; } - get initial() { + get initialState() { if (Object.keys(this.#subduxes).length === 0) return this.#localInitial ?? {}; @@ -101,7 +101,7 @@ export class Updux { return Object.assign( {}, this.#localInitial ?? {}, - R.mapValues(this.#subduxes, ({ initial }) => initial), + R.mapValues(this.#subduxes, ({ initialState }) => initialState), ); } @@ -167,14 +167,14 @@ export class Updux { ); } - createStore(initial = undefined, enhancerGenerator = undefined) { + createStore(initialState = undefined, enhancerGenerator = undefined) { const enhancer = (enhancerGenerator ?? applyMiddleware)( this.middleware, ); const store = reduxCreateStore( this.reducer, - initial ?? this.initial, + initialState ?? this.initialState, enhancer, ); @@ -256,7 +256,7 @@ export class Updux { delete gone[key]; } else { const dux = new Updux({ - initial: null, + initialState: null, actions: { update: null }, mutations: { update: (payload) => () => payload, diff --git a/src/Updux.ts b/src/Updux.ts index 835a840..b3c8952 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -100,7 +100,7 @@ export default class Updux< #actions: AggregateActions, T_Subduxes>; - #initial: AggregateState; + #initialState: AggregateState; #localSelectors: Record< string, @@ -112,20 +112,20 @@ export default class Updux< constructor( config: Partial<{ - initial: T_LocalState; + initialState: T_LocalState; actions: T_LocalActions; subduxes: T_Subduxes; selectors: T_LocalSelectors; }>, ) { - // TODO check that we can't alter the initial after the fact - this.#localInitial = config.initial ?? ({} as T_LocalState); + // TODO check that we can't alter the initialState after the fact + this.#localInitial = config.initialState ?? ({} as T_LocalState); this.#localActions = config.actions ?? ({} as T_LocalActions); this.#subduxes = config.subduxes ?? ({} as T_Subduxes); this.#actions = buildActions(this.#localActions, this.#subduxes); - this.#initial = buildInitial(this.#localInitial, this.#subduxes); + this.#initialState = buildInitial(this.#localInitial, this.#subduxes); this.#localSelectors = config.selectors; const basedSelectors = R.mergeAll( @@ -144,8 +144,8 @@ export default class Updux< } // TODO memoize? - get initial() { - return this.#initial; + get initialState() { + return this.#initialState; } get effects() { @@ -178,10 +178,10 @@ export default class Updux< createStore( options: Partial<{ - initial: T_LocalState; + initialState: T_LocalState; }> = {}, ) { - const preloadedState: any = options.initial ?? this.initial; + const preloadedState: any = options.initialState ?? this.initialState; const effects = buildEffectsMiddleware( this.effects, @@ -246,14 +246,14 @@ export default class Updux< // TODO memoize this sucker get reducer() { return buildReducer( - this.initial, + this.initialState, this.#localMutations, this.#defaultMutation, this.#subduxes, ) as any as ( - state: undefined | typeof this.initial, + state: undefined | typeof this.initialState, action: Action, - ) => typeof this.initial; + ) => typeof this.initialState; } // TODO be smarter with the guard? diff --git a/src/actions.ts b/src/actions.ts index cf559b6..5712b0a 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -14,6 +14,7 @@ export const withPayload: WithPayload = ((prepare) => payload: prepare ? prepare(...input) : input[0], })) as any; +const id = (x) => x; export const createPayloadAction = < P extends any = any, T extends string = string, diff --git a/src/buildInitial.test.ts b/src/buildInitial.test.ts index 63743e8..8cc1d15 100644 --- a/src/buildInitial.test.ts +++ b/src/buildInitial.test.ts @@ -5,7 +5,7 @@ test('basic', () => { expect( buildInitial( { a: 1 }, - { b: { initial: { c: 2 } }, d: { initial: 'e' } }, + { b: { initialState: { c: 2 } }, d: { initialState: 'e' } }, ), ).toEqual({ a: 1, @@ -14,7 +14,7 @@ test('basic', () => { }); }); -test('throw if subduxes and initial is not an object', () => { +test('throw if subduxes and initialState is not an object', () => { expect(() => { buildInitial(3, { bar: 'foo' }); }).toThrow(); diff --git a/src/dux-selectors.test.todo b/src/dux-selectors.test.todo index 011b796..c6f659d 100644 --- a/src/dux-selectors.test.todo +++ b/src/dux-selectors.test.todo @@ -6,7 +6,7 @@ import { matches } from './utils.js'; test('basic selectors', () => { const foo = dux({ - initial: { + initialState: { x: 1, }, selectors: { @@ -14,7 +14,7 @@ test('basic selectors', () => { }, subduxes: { bar: { - initial: { y: 2 }, + initialState: { y: 2 }, selectors: { getY: ({ y }) => y, }, @@ -27,7 +27,7 @@ test('basic selectors', () => { test('splat selector', async () => { const bar = new Updux({ - initial: { id: 0, label: '' }, + initialState: { id: 0, label: '' }, selectors: { getLabel: R.prop('label'), getLabelAppended: (state) => (suffix) => state.label + ' ' + suffix, @@ -35,7 +35,7 @@ test('splat selector', async () => { }); const foo = new Updux({ - initial: [], + initialState: [], findSelectors: { getBar: (state) => (id) => { return state.find(matches({ id })); diff --git a/src/effects.test.ts b/src/effects.test.ts index f500cae..a6cde57 100644 --- a/src/effects.test.ts +++ b/src/effects.test.ts @@ -48,7 +48,7 @@ test('buildEffectsMiddleware', () => { test('basic', () => { const dux = new Updux({ - initial: { + initialState: { loaded: true, }, actions: { @@ -77,7 +77,7 @@ test('basic', () => { test('subdux', () => { const bar = new Updux({ - initial: 'bar state', + initialState: 'bar state', actions: { foo: 0 }, }); @@ -89,7 +89,7 @@ test('subdux', () => { }); const dux = new Updux({ - initial: { + initialState: { loaded: true, }, subduxes: { diff --git a/src/initial.test.ts b/src/initial.test.ts index 245632a..4446ee7 100644 --- a/src/initial.test.ts +++ b/src/initial.test.ts @@ -1,40 +1,40 @@ import { expectType } from './tutorial.test.js'; import Updux from './Updux.js'; -const bar = new Updux({ initial: 123 }); +const bar = new Updux({ initialState: 123 }); const foo = new Updux({ - initial: { root: 'abc' }, + initialState: { root: 'abc' }, subduxes: { bar, }, }); test('default', () => { - const { initial } = new Updux({}); + const { initialState } = new Updux({}); - expect(initial).toBeTypeOf('object'); - expect(initial).toEqual({}); + expect(initialState).toBeTypeOf('object'); + expect(initialState).toEqual({}); }); test('number', () => { - const { initial } = new Updux({ initial: 3 }); + const { initialState } = new Updux({ initialState: 3 }); - expect(initial).toBeTypeOf('number'); - expect(initial).toEqual(3); + expect(initialState).toBeTypeOf('number'); + expect(initialState).toEqual(3); }); -test('initial to createStore', () => { - const initial = { +test('initialState to createStore', () => { + const initialState = { a: 1, b: 2, }; const dux = new Updux({ - initial, + initialState, }); - expect(dux.createStore({ initial: { a: 3, b: 4 } }).getState()).toEqual({ + expect(dux.createStore({ initialState: { a: 3, b: 4 } }).getState()).toEqual({ a: 3, b: 4, }); @@ -42,15 +42,15 @@ test('initial to createStore', () => { test('single dux', () => { const foo = new Updux({ - initial: { a: 1 }, + initialState: { a: 1 }, }); - expect(foo.initial).toEqual({ a: 1 }); + expect(foo.initialState).toEqual({ a: 1 }); }); // TODO add 'check for no todo eslint rule' -test('initial value', () => { - expect(foo.initial).toEqual({ +test('initialState value', () => { + expect(foo.initialState).toEqual({ root: 'abc', bar: 123, }); @@ -58,41 +58,41 @@ test('initial value', () => { expectType<{ root: string; bar: number; - }>(foo.initial); + }>(foo.initialState); }); -test('no initial', () => { +test('no initialState', () => { const dux = new Updux({}); - expectType<{}>(dux.initial); - expect(dux.initial).toEqual({}); + expectType<{}>(dux.initialState); + expect(dux.initialState).toEqual({}); }); -test('no initial for subdux', () => { +test('no initialState for subdux', () => { const dux = new Updux({ subduxes: { bar: new Updux({}), - baz: new Updux({ initial: 'potato' }), + baz: new Updux({ initialState: 'potato' }), }, }); - expectType<{ bar: {}; baz: string }>(dux.initial); - expect(dux.initial).toEqual({ bar: {}, baz: 'potato' }); + expectType<{ bar: {}; baz: string }>(dux.initialState); + expect(dux.initialState).toEqual({ bar: {}, baz: 'potato' }); }); -test.todo('splat initial', async () => { +test.todo('splat initialState', async () => { const bar = new Updux({ - initial: { id: 0 }, + initialState: { id: 0 }, }); const foo = new Updux({ subduxes: { '*': bar }, }); - expect(foo.initial).toEqual([]); + expect(foo.initialState).toEqual([]); expect( new Updux({ - initial: 'overriden', + initialState: 'overriden', subduxes: { '*': bar }, - }).initial, + }).initialState, ).toEqual('overriden'); }); diff --git a/src/initial.ts b/src/initial.ts index 36aaf92..41eb872 100644 --- a/src/initial.ts +++ b/src/initial.ts @@ -1,7 +1,7 @@ import u from '@yanick/updeep-remeda'; import * as R from 'remeda'; -type SubduxState = 'initial' extends keyof S ? S['initial'] : {}; +type SubduxState = 'initialState' extends keyof S ? S['initialState'] : {}; export type AggregateState> = LOCAL & (keyof SUBDUXES extends never @@ -15,9 +15,9 @@ export type AggregateState> = LOCAL & export function buildInitial(localInitial, subduxes) { if (Object.keys(subduxes).length > 0 && typeof localInitial !== 'object') { throw new Error( - "can't have subduxes when the initial value is not an object", + "can't have subduxes when the initialState value is not an object", ); } - return u(localInitial, R.mapValues(subduxes, R.pathOr(['initial'], {}))); + return u(localInitial, R.mapValues(subduxes, R.pathOr(['initialState'], {}))); } diff --git a/src/mutations.test.ts b/src/mutations.test.ts index 5858b63..73ae207 100644 --- a/src/mutations.test.ts +++ b/src/mutations.test.ts @@ -4,7 +4,7 @@ import Updux, { createAction } from './index.js'; test('set a mutation', () => { const dux = new Updux({ - initial: 'potato', + initialState: 'potato', actions: { foo: (x) => ({ x }), bar: 0, @@ -27,7 +27,7 @@ test('set a mutation', () => { test('catch-all mutation', () => { const dux = new Updux({ - initial: '', + initialState: '', }); dux.addMutation( @@ -40,7 +40,7 @@ test('catch-all mutation', () => { test('default mutation', () => { const dux = new Updux({ - initial: '', + initialState: '', actions: { foo: 0, }, @@ -60,7 +60,7 @@ test('mutation of a subdux', () => { const stopit = createAction('stopit'); const bar = new Updux({ - initial: 0, + initialState: 0, actions: { baz, stopit, diff --git a/src/reactions.test.ts b/src/reactions.test.ts index 8dc5d18..b807f8a 100644 --- a/src/reactions.test.ts +++ b/src/reactions.test.ts @@ -3,7 +3,7 @@ import Updux from './index.js'; test('basic reactions', () => { const foo = new Updux({ - initial: 0, + initialState: 0, actions: { inc: 0, reset: 0 }, }); @@ -39,7 +39,7 @@ test('basic reactions', () => { test('subdux reactions', () => { const bar = new Updux({ - initial: 0, + initialState: 0, actions: { inc: 0, reset: 0 }, selectors: { getIt: (x) => x, diff --git a/src/reducer.test.ts b/src/reducer.test.ts index 9ce0ba8..7e7b36c 100644 --- a/src/reducer.test.ts +++ b/src/reducer.test.ts @@ -3,7 +3,7 @@ import { test, expect } from 'vitest'; import { buildReducer } from './reducer.js'; import Updux from './Updux.js'; -test('buildReducer, initial state', () => { +test('buildReducer, initialState state', () => { const reducer = buildReducer({ a: 1 }); expect(reducer(undefined, { type: 'foo' })).toEqual({ a: 1 }); @@ -23,7 +23,7 @@ test('buildReducer, mutation', () => { }); test.todo('basic reducer', () => { - const dux = new Updux({ initial: { a: 3 } }); + const dux = new Updux({ initialState: { a: 3 } }); expect(dux.reducer).toBeTypeOf('function'); diff --git a/src/reducer.ts b/src/reducer.ts index 2f92f5a..c11c60b 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -12,7 +12,7 @@ export type MutationCase = { }; export function buildReducer( - initialState: any, + initialStateState: any, mutations: MutationCase[] = [], defaultMutation?: Omit, subduxes: Record = {}, @@ -22,7 +22,7 @@ export function buildReducer( // TODO matcherMutation // TODO defaultMutation // - const reducer = (state = initialState, action: Action) => { + const reducer = (state = initialStateState, action: Action) => { const orig = state; if (!action?.type) throw new Error('upreducer called with a bad action'); diff --git a/src/selectors.test.ts b/src/selectors.test.ts index d0695b9..b7efb51 100644 --- a/src/selectors.test.ts +++ b/src/selectors.test.ts @@ -6,7 +6,7 @@ test('basic selectors', () => { type State = { x: number }; const foo = new Updux({ - initial: { + initialState: { x: 1, }, selectors: { @@ -14,7 +14,7 @@ test('basic selectors', () => { }, subduxes: { bar: new Updux({ - initial: { y: 2 }, + initialState: { y: 2 }, selectors: { getY: ({ y }: { y: number }) => y, getYPlus: diff --git a/src/splatReactions.test.todo b/src/splatReactions.test.todo index 7fbe918..11da9a1 100644 --- a/src/splatReactions.test.todo +++ b/src/splatReactions.test.todo @@ -9,7 +9,7 @@ const thingReactionSnitch = vi.fn(); const subThing = new Updux({ name: 'subThing', - initial: 0, + initialState: 0, }); subThing.addReaction((api) => (state, previousState, unsubscribe) => { @@ -18,7 +18,7 @@ subThing.addReaction((api) => (state, previousState, unsubscribe) => { const thing = new Updux({ name: 'thing', - initial: {}, + initialState: {}, subduxes: { '*': subThing, }, @@ -41,11 +41,11 @@ const things = new Updux({ subduxes: { '*': thing, }, - initial: {}, + initialState: {}, actions: { newThing: (id) => id }, splatReactionMapper: ({ id }) => id, mutations: { - newThing: (id) => (state) => ({ ...state, [id]: thing.initial }), + newThing: (id) => (state) => ({ ...state, [id]: thing.initialState }), }, }); diff --git a/src/tutorial.test.ts b/src/tutorial.test.ts index 74b4f09..67c5c48 100644 --- a/src/tutorial.test.ts +++ b/src/tutorial.test.ts @@ -3,25 +3,25 @@ import u from '@yanick/updeep-remeda'; export const expectType = (value: T) => value; -test('initial state', () => { - const initial = { +test('initialState state', () => { + const initialState = { next_id: 1, todos: [], }; const dux = new Updux({ - initial, + initialState, }); expectType<{ next_id: number; todos: unknown[]; - }>(dux.initial); + }>(dux.initialState); - expect(dux.initial).toEqual(initial); + expect(dux.initialState).toEqual(initialState); const store = dux.createStore(); - expect(store.getState()).toEqual(initial); + expect(store.getState()).toEqual(initialState); }); test('actions', () => { @@ -51,7 +51,7 @@ test('mutation', () => { }; const dux = new Updux({ - initial: { nextId: 0, todos: [] as Todo[] }, + initialState: { nextId: 0, todos: [] as Todo[] }, }); dux.addMutation(addTodo, (description) => (state) => { diff --git a/src/types.ts b/src/types.ts index 54db08d..87f93a2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,7 +4,7 @@ export type Dux< STATE = any, ACTIONS extends Record> = {}, > = Partial<{ - initial: STATE; + initialState: STATE; actions: ACTIONS; selectors: Record any>; reducer: ( From bb0bc148736a8b42d2d1d55df67f3d1ace7d56d9 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Fri, 21 Apr 2023 13:57:51 -0400 Subject: [PATCH 45/46] add immer to the mix --- src/Updux.ts | 10 ++++++++-- src/buildActions.ts | 2 +- src/reducer.ts | 8 +++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Updux.ts b/src/Updux.ts index b3c8952..fd3ac49 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -22,6 +22,7 @@ import { buildInitial, AggregateState } from './initial.js'; import { buildReducer, MutationCase } from './reducer.js'; import { augmentGetState, augmentMiddlewareApi, buildEffectsMiddleware, EffectMiddleware } from './effects.js'; import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore.js'; +import prepare from 'immer'; type MyActionCreator = { type: string } & ((...args: any) => any); @@ -132,7 +133,9 @@ export default class Updux< Object.entries(this.#subduxes) .filter(([slice, { selectors }]) => selectors) .map(([slice, { selectors }]) => - R.mapValues(selectors, (s) => (state) => s(state[slice])), + R.mapValues(selectors, (s) => (state = {}) => { + return s(state?.[slice]) + }), ), ); @@ -218,6 +221,7 @@ export default class Updux< } (store as any).actions = this.actions; + (store as any).selectors = this.selectors; return store as ToolkitStore< @@ -276,10 +280,12 @@ export default class Updux< matcher = matcher.match; } + const immerMutation = (...args) => prepare(mutation(...args)); + this.#localMutations.push({ terminal, matcher, - mutation, + mutation: immerMutation, }); } diff --git a/src/buildActions.ts b/src/buildActions.ts index bee0ebb..f93ac42 100644 --- a/src/buildActions.ts +++ b/src/buildActions.ts @@ -21,7 +21,7 @@ export function buildActions(localActions, subduxes) { if (!subdux) continue; for (const a in subdux) { - if (actions[a]) { + if (actions[a] && subduxes[actions[a]].actions[a] !== subdux[a]) { throw new Error( `action '${a}' defined both in subduxes '${actions[a]}' and '${slice}'`, ); diff --git a/src/reducer.ts b/src/reducer.ts index c11c60b..1f7b956 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -4,6 +4,7 @@ import * as R from 'remeda'; import { Dux } from './types.js'; import { Mutation } from './Updux.js'; import u from '@yanick/updeep-remeda'; +import prepare from 'immer'; export type MutationCase = { matcher: (action: Action) => boolean; @@ -35,9 +36,10 @@ export function buildReducer( .forEach(({ mutation, terminal: t }) => { if (t) terminal = true; didSomething = true; - // - // TODO wrap mutations in immer - state = mutation((action as any).payload, action)(state); + state = prepare( + state, + mutation((action as any).payload, action), + ); }); if (!didSomething && defaultMutation) { From d405c90a0df2d3553f49294cd489517eb9f3a18a Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Fri, 21 Apr 2023 14:01:12 -0400 Subject: [PATCH 46/46] prettier --- Taskfile.yaml | 14 ++- src/Updux.test.ts | 27 +----- src/Updux.ts | 167 +++++++++++++++++++----------------- src/buildSelectors/index.js | 23 +++-- src/effects.test.ts | 6 +- src/initial.test.ts | 4 +- src/initial.ts | 5 +- src/reducer.ts | 4 +- src/selectors.test.ts | 4 +- 9 files changed, 132 insertions(+), 122 deletions(-) diff --git a/Taskfile.yaml b/Taskfile.yaml index 677cab8..a9d7b7c 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -2,11 +2,23 @@ version: '3' +vars: + PARENT_BRANCH: main + tasks: build: tsc checks: - deps: [test, build] + deps: [lint, test, build] + + integrate: + deps: [checks] + cmds: + - git is-clean + # did we had tests? + - git diff-ls {{.PARENT_BRANCH}} | grep test + - git checkout {{.PARENT_BRANCH}} + - git weld - test: vitest run src test:dev: vitest src diff --git a/src/Updux.test.ts b/src/Updux.test.ts index 1e5f0ea..4aab369 100644 --- a/src/Updux.test.ts +++ b/src/Updux.test.ts @@ -1,15 +1,7 @@ import { test, expect } from 'vitest'; -import Updux from './Updux'; +import Updux from './Updux.js'; test('subdux idempotency', () => { - // const c = new Updux({ - // initialState: 2, - // }); - // const b = new Updux({ - // subduxes: { - // c, - // }, - // }); const foo = new Updux({ subduxes: { a: new Updux({ initialState: 2 }), @@ -18,21 +10,4 @@ test('subdux idempotency', () => { let fooState = foo.reducer(undefined, { type: 'noop' }); expect(foo.reducer(fooState, { type: 'noop' })).toBe(fooState); - - return; - const store = foo.createStore(); - - const s1 = store.getState(); - console.log(s1); - - store.dispatch({ type: 'noop' }); - const s2 = store.getState(); - - expect(s2.a).toBe(s1.a); - - let bState = b.reducer(undefined, { type: 'noop' }); - expect(b.reducer(bState, { type: 'noop' })).toBe(bState); - - expect(s2.b).toBe(s1.b); - expect(s2).toBe(s1); }); diff --git a/src/Updux.ts b/src/Updux.ts index fd3ac49..baa5a6e 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -20,9 +20,14 @@ import { AggregateActions, AggregateSelectors, Dux } from './types.js'; import { buildActions } from './buildActions.js'; import { buildInitial, AggregateState } from './initial.js'; import { buildReducer, MutationCase } from './reducer.js'; -import { augmentGetState, augmentMiddlewareApi, buildEffectsMiddleware, EffectMiddleware } from './effects.js'; +import { + augmentGetState, + augmentMiddlewareApi, + buildEffectsMiddleware, + EffectMiddleware, +} from './effects.js'; import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore.js'; -import prepare from 'immer'; +import { produce } from 'immer'; type MyActionCreator = { type: string } & ((...args: any) => any); @@ -30,8 +35,8 @@ type XSel = R extends Function ? R : () => R; type CurriedSelector = S extends (...args: any) => infer R ? XSel : never; type CurriedSelectors = { - [key in keyof S]: CurriedSelector -} + [key in keyof S]: CurriedSelector; +}; type ResolveAction< ActionType extends string, @@ -40,10 +45,10 @@ type ResolveAction< ? ActionArg : ActionArg extends (...args: any) => any ? ActionCreatorWithPreparedPayload< - Parameters, - ReturnType, - ActionType - > + Parameters, + ReturnType, + ActionType + > : ActionCreatorWithoutPayload; type ResolveActions< @@ -51,21 +56,24 @@ type ResolveActions< [key: string]: any; }, > = { - [ActionType in keyof A]: ActionType extends string + [ActionType in keyof A]: ActionType extends string ? ResolveAction : never; - }; +}; -type Reaction = - (api: M) => (state: S, previousState: S, unsubscribe: () => void) => any; +type Reaction = ( + api: M, +) => (state: S, previousState: S, unsubscribe: () => void) => any; -type AugmentedMiddlewareAPI = - MiddlewareAPI, S> & { - dispatch: A, - getState: CurriedSelectors, - actions: A, - selectors: SELECTORS, - }; +type AugmentedMiddlewareAPI = MiddlewareAPI< + Dispatch, + S +> & { + dispatch: A; + getState: CurriedSelectors; + actions: A; + selectors: SELECTORS; +}; export type Mutation = Action, S = any> = ( payload: A extends { @@ -134,7 +142,7 @@ export default class Updux< .filter(([slice, { selectors }]) => selectors) .map(([slice, { selectors }]) => R.mapValues(selectors, (s) => (state = {}) => { - return s(state?.[slice]) + return s(state?.[slice]); }), ), ); @@ -157,25 +165,34 @@ export default class Updux< ...Object.entries(this.#subduxes).flatMap( ([slice, { effects }]) => { if (!effects) return []; - return effects.map(effect => (api) => effect({ - ...api, - getState: () => api.getState()[slice], - })) - } - ) - ] + return effects.map( + (effect) => (api) => + effect({ + ...api, + getState: () => api.getState()[slice], + }), + ); + }, + ), + ]; } get reactions(): any { - return [...this.#localReactions, - ...Object.entries(this.#subduxes).flatMap( - ([slice, { reactions }]) => reactions.map( - (r) => (api, unsub) => r({ - ...api, - getState: () => api.getState()[slice], - }, unsub) - ) - ) + return [ + ...this.#localReactions, + ...Object.entries(this.#subduxes).flatMap( + ([slice, { reactions }]) => + reactions.map( + (r) => (api, unsub) => + r( + { + ...api, + getState: () => api.getState()[slice], + }, + unsub, + ), + ), + ), ]; } @@ -192,7 +209,6 @@ export default class Updux< this.selectors, ); - const store = configureStore({ reducer: this.reducer as Reducer< AggregateState, @@ -208,7 +224,7 @@ export default class Updux< const action = (this.actions as any)[a](...args); dispatch(action); return action; - } + }; } store.getState = augmentGetState(store.getState, this.selectors); @@ -223,20 +239,16 @@ export default class Updux< (store as any).actions = this.actions; (store as any).selectors = this.selectors; - - return store as ToolkitStore< - AggregateState - > & AugmentedMiddlewareAPI< - AggregateState, - AggregateActions< - ResolveActions, - T_Subduxes - >, AggregateSelectors< - T_LocalSelectors, - T_Subduxes, - AggregateState - > - >; + return store as ToolkitStore> & + AugmentedMiddlewareAPI< + AggregateState, + AggregateActions, T_Subduxes>, + AggregateSelectors< + T_LocalSelectors, + T_Subduxes, + AggregateState + > + >; } get selectors(): AggregateSelectors< @@ -280,7 +292,7 @@ export default class Updux< matcher = matcher.match; } - const immerMutation = (...args) => prepare(mutation(...args)); + const immerMutation = (...args) => produce(mutation(...args)); this.#localMutations.push({ terminal, @@ -309,31 +321,27 @@ export default class Updux< this.actions, this.selectors, ); - } #localReactions: any[] = []; - addReaction(reaction: Reaction, - AugmentedMiddlewareAPI< + addReaction( + reaction: Reaction< AggregateState, - AggregateActions< - ResolveActions, - T_Subduxes - >, AggregateSelectors< - T_LocalSelectors, - T_Subduxes, - AggregateState + AugmentedMiddlewareAPI< + AggregateState, + AggregateActions, T_Subduxes>, + AggregateSelectors< + T_LocalSelectors, + T_Subduxes, + AggregateState + > > - > - >) { - + >, + ) { let previous: any; const memoized = (api: any) => { - api = augmentMiddlewareApi(api, - this.actions, - this.selectors - ); + api = augmentMiddlewareApi(api, this.actions, this.selectors); const r = reaction(api); return (unsub: () => void) => { const state = api.getState(); @@ -341,19 +349,21 @@ export default class Updux< let p = previous; previous = state; r(state, p, unsub); - } - } - ; - + }; + }; this.#localReactions.push(memoized); } // internal method REMOVE subscribeTo(store, subscription) { - const localStore = augmentMiddlewareApi({ - ...store, - subscribe: (subscriber) => this.subscribeTo(store, subscriber), // TODO not sure - }, this.actions, this.selectors); + const localStore = augmentMiddlewareApi( + { + ...store, + subscribe: (subscriber) => this.subscribeTo(store, subscriber), // TODO not sure + }, + this.actions, + this.selectors, + ); const subscriber = subscription(localStore); @@ -371,4 +381,3 @@ export default class Updux< return store.subscribe(memoSub); } } - diff --git a/src/buildSelectors/index.js b/src/buildSelectors/index.js index dfd7550..ff489f7 100644 --- a/src/buildSelectors/index.js +++ b/src/buildSelectors/index.js @@ -1,19 +1,28 @@ import { map, mapValues, merge } from 'lodash-es'; -export function buildSelectors(localSelectors, splatSelector = {}, subduxes = {}) { +export function buildSelectors( + localSelectors, + splatSelector = {}, + subduxes = {}, +) { const subSelectors = map(subduxes, ({ selectors }, slice) => { - if (!selectors) - return {}; - if (slice === '*') - return {}; + if (!selectors) return {}; + if (slice === '*') return {}; return mapValues(selectors, (func) => (state) => func(state[slice])); }); let splat = {}; for (const name in splatSelector) { splat[name] = - (state) => (...args) => { + (state) => + (...args) => { const value = splatSelector[name](state)(...args); const res = () => value; - return merge(res, mapValues(subduxes['*'].selectors, (selector) => () => selector(value))); + return merge( + res, + mapValues( + subduxes['*'].selectors, + (selector) => () => selector(value), + ), + ); }; } return merge({}, ...subSelectors, localSelectors, splat); diff --git a/src/effects.test.ts b/src/effects.test.ts index a6cde57..007abb4 100644 --- a/src/effects.test.ts +++ b/src/effects.test.ts @@ -39,7 +39,7 @@ test('buildEffectsMiddleware', () => { expect(seen).toEqual(0); const dispatch = vi.fn(); - mw({ getState: () => 'the state', dispatch })(() => { })({ + mw({ getState: () => 'the state', dispatch })(() => {})({ type: 'noop', }); expect(seen).toEqual(1); @@ -82,7 +82,7 @@ test('subdux', () => { }); let seen = 0; - bar.addEffect((api) => next => action => { + bar.addEffect((api) => (next) => (action) => { seen++; expect(api.getState()).toBe('bar state'); next(action); @@ -93,7 +93,7 @@ test('subdux', () => { loaded: true, }, subduxes: { - bar + bar, }, }); diff --git a/src/initial.test.ts b/src/initial.test.ts index 4446ee7..5dc76b2 100644 --- a/src/initial.test.ts +++ b/src/initial.test.ts @@ -34,7 +34,9 @@ test('initialState to createStore', () => { initialState, }); - expect(dux.createStore({ initialState: { a: 3, b: 4 } }).getState()).toEqual({ + expect( + dux.createStore({ initialState: { a: 3, b: 4 } }).getState(), + ).toEqual({ a: 3, b: 4, }); diff --git a/src/initial.ts b/src/initial.ts index 41eb872..91aa451 100644 --- a/src/initial.ts +++ b/src/initial.ts @@ -19,5 +19,8 @@ export function buildInitial(localInitial, subduxes) { ); } - return u(localInitial, R.mapValues(subduxes, R.pathOr(['initialState'], {}))); + return u( + localInitial, + R.mapValues(subduxes, R.pathOr(['initialState'], {})), + ); } diff --git a/src/reducer.ts b/src/reducer.ts index 1f7b956..c592d86 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -4,7 +4,7 @@ import * as R from 'remeda'; import { Dux } from './types.js'; import { Mutation } from './Updux.js'; import u from '@yanick/updeep-remeda'; -import prepare from 'immer'; +import { produce } from 'immer'; export type MutationCase = { matcher: (action: Action) => boolean; @@ -36,7 +36,7 @@ export function buildReducer( .forEach(({ mutation, terminal: t }) => { if (t) terminal = true; didSomething = true; - state = prepare( + state = produce( state, mutation((action as any).payload, action), ); diff --git a/src/selectors.test.ts b/src/selectors.test.ts index b7efb51..f637c88 100644 --- a/src/selectors.test.ts +++ b/src/selectors.test.ts @@ -19,8 +19,8 @@ test('basic selectors', () => { getY: ({ y }: { y: number }) => y, getYPlus: ({ y }) => - (incr: number) => - (y + incr) as number, + (incr: number) => + (y + incr) as number, }, }), },