diff --git a/.prettierignore b/.prettierignore index 121f06b..7b893ae 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,7 @@ +dist +out +pnpm-lock.yaml +types +docs Changes .prettierignore diff --git a/.travis.yml b/.travis.yml index 24d97a7..cf91d1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,16 @@ language: node_js node_js: - - 'node' - - 'lts/*' + - 'node' + - 'lts/*' install: - - npm uninstall typescript --no-save - - npm install + - npm uninstall typescript --no-save + - npm install cache: - directories: - - node_modules + directories: + - node_modules git: - depth: 1 + depth: 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fc34ab..60ea61f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,54 +6,58 @@ All notable changes to this project will be documented in this file. See [standa ### ⚠ BREAKING CHANGES -* Upgrade to Typescript 4. -* Switch from 'updeep' to '@yanick/updeep'. - +- Upgrade to Typescript 4. +- Switch from 'updeep' to '@yanick/updeep'. ## [2.1.0](https://github.com/yanick/updux/compare/v2.0.0...v2.1.0) (2020-06-19) ### Features -* add support for subscriptions ([9c45ee7](https://github.com/yanick/updux/commit/9c45ee7efcb623defb9da5d01165fbad0e4424f9)) +- add support for subscriptions ([9c45ee7](https://github.com/yanick/updux/commit/9c45ee7efcb623defb9da5d01165fbad0e4424f9)) ## [2.0.0](https://github.com/yanick/updux/compare/v1.2.0...v2.0.0) (2020-06-13) ### ⚠ BREAKING CHANGES -* use ts-action for action creation -* middleware support refined +- use ts-action for action creation +- middleware support refined ### Features -* allow adding actionCreators via addAction() ([27ae46d](https://github.com/yanick/updux/commit/27ae46dbab289b27ea99aca149aaa3b7c90ee7d0)) -* middleware support refined ([d90d721](https://github.com/yanick/updux/commit/d90d72148c2d4ba186a19650d961c64df5791c55)) -* moving documentation to docsify ([fa55762](https://github.com/yanick/updux/commit/fa55762efcbd4db356150f6022fd62750adc27a9)) -* use ts-action for action creation ([6349d72](https://github.com/yanick/updux/commit/6349d720b8aba4b443a7225d6a377c5c929a3021)) - +- allow adding actionCreators via addAction() ([27ae46d](https://github.com/yanick/updux/commit/27ae46dbab289b27ea99aca149aaa3b7c90ee7d0)) +- middleware support refined ([d90d721](https://github.com/yanick/updux/commit/d90d72148c2d4ba186a19650d961c64df5791c55)) +- moving documentation to docsify ([fa55762](https://github.com/yanick/updux/commit/fa55762efcbd4db356150f6022fd62750adc27a9)) +- use ts-action for action creation ([6349d72](https://github.com/yanick/updux/commit/6349d720b8aba4b443a7225d6a377c5c929a3021)) ### Bug Fixes -* state is a PreloadedState ([93bebc5](https://github.com/yanick/updux/commit/93bebc5acf193752aa6b4857507f05d52b1b7665)) +- state is a PreloadedState ([93bebc5](https://github.com/yanick/updux/commit/93bebc5acf193752aa6b4857507f05d52b1b7665)) ## 1.2.0 2019-11-06 + - The middleware's 'getState' returns the local state of its updux, instead of the root state. Plus we add `getRootState` to get the root state. ## 1.1.0 2019-11-05 + - Document mapping behavior of the '*' subdux. - add subduxUpreducer. - add sink mutations. ## 1.0.0 2019-11-04 + - Pretty big rework. - Better documentation. ## 0.2.0 2019-10-24 + - Converted everything to Typescript. ## 0.1.0 2019-10-22 + - Add 'actions' in the config. ## 0.0.1 2019-10-22 + - Initial release. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 8466400..92fae6b 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -17,24 +17,24 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Enforcement Responsibilities @@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within @@ -126,4 +126,3 @@ enforcement ladder](https://github.com/mozilla/diversity). For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. - diff --git a/Taskfile.yml b/Taskfile.yml index 906bf7c..337dafb 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -3,9 +3,23 @@ version: '3' vars: - GREETING: Hello, World! + GREETING: Hello, World! tasks: + tsc: + cmds: + - tsc + - echo 🙌 typescript compilation successful + + test: jest + + 'check:prettier': prettier --check . + + 'check:prettier:fix': prettier --write . + + check: + deps: [tsc, test, 'check:prettier'] + 'test:types': tsd docs: cmds: diff --git a/babel.config.cjs b/babel.config.cjs index 79d2391..faa86eb 100644 --- a/babel.config.cjs +++ b/babel.config.cjs @@ -1,6 +1,6 @@ module.exports = { - presets: [ - ['@babel/preset-env', {targets: {node: 'current'}}], - '@babel/preset-typescript', - ], + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + '@babel/preset-typescript', + ], }; diff --git a/jest.config.ts b/jest.config.ts index 3f11958..5e48057 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,3 +1,3 @@ export default { - roots: [ './src' ] -} + roots: ['./src'], +}; diff --git a/jsdoc.json b/jsdoc.json index 41ca59f..c233153 100644 --- a/jsdoc.json +++ b/jsdoc.json @@ -8,7 +8,7 @@ "sourceType": "module", "tags": { "allowUnknownTags": true, - "dictionaries": ["jsdoc","closure"] + "dictionaries": ["jsdoc", "closure"] }, "templates": { "cleverLinks": false, @@ -19,4 +19,3 @@ "tutorials": "./tutorials" } } - diff --git a/src/Updux.d.ts b/src/Updux.d.ts index 8e0af6f..949ff65 100644 --- a/src/Updux.d.ts +++ b/src/Updux.d.ts @@ -1,19 +1,17 @@ type UpduxConfig = Partial<{ initial: TState; - subduxes: Record; + subduxes: Record; actions: Record; selectors: Record; mutations: Record; - mappedSelectors: Record; - effects: Record; - reactions: Record; + mappedSelectors: Record; + effects: Record; + reactions: Record; mappedReaction: Function; -}> +}>; - -export class Updux { - - constructor( config: UpduxConfig ); +export class Updux { + constructor(config: UpduxConfig); get initial(): TState; get selectors(): unknown; diff --git a/src/Updux.ts b/src/Updux.ts index 82b0d88..fbed23d 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -9,17 +9,19 @@ import { buildActions } from './buildActions'; import { buildSelectors } from './buildSelectors'; import { action } from './actions'; import { buildUpreducer } from './buildUpreducer'; -import { - buildMiddleware, - augmentMiddlewareApi, -} from './buildMiddleware'; +import { buildMiddleware, augmentMiddlewareApi } from './buildMiddleware'; -import { Dict } from './types'; +import { AggregateDuxActions, AggregateDuxState, Dict } from './types'; /** * Configuration object typically passed to the constructor of the class Updux. */ -export interface UpduxConfig { +export interface UpduxConfig< + TState = any, + TActions = {}, + TSelectors = {}, + TSubduxes = {} +> { /** * Local initial state. * @default {} @@ -29,12 +31,12 @@ export interface UpduxConfig { /** * Subduxes to be merged to this dux. */ - subduxes?: Dict; + subduxes?: TSubduxes; /** * Local actions. */ - actions?: Record; + actions?: TActions; /** * Local selectors. @@ -70,7 +72,12 @@ export interface UpduxConfig { mappedReaction?: Function | boolean; } -export class Updux { +export class Updux< + TState extends any = {}, + TActions extends object = {}, + TSelectors = {}, + TSubduxes extends object = {} +> { /** @type { unknown } */ #initial = {}; #subduxes = {}; @@ -84,7 +91,7 @@ export class Updux { #mappedSelectors = undefined; #mappedReaction = undefined; - constructor(config: UpduxConfig) { + constructor(config: UpduxConfig) { this.#initial = config.initial ?? {}; this.#subduxes = config.subduxes ?? {}; @@ -135,7 +142,7 @@ export class Updux { this.#mappedSelectors = { ...this.#mappedSelectors, [name]: f, - } + }; } get middleware() { @@ -148,12 +155,12 @@ export class Updux { } /** @member { unknown } */ - get initial() { + get initial(): AggregateDuxState { return this.#memoInitial(this.#initial, this.#subduxes); } - get actions(): Record { - return this.#memoActions(this.#actions, this.#subduxes); + get actions(): AggregateDuxActions { + return this.#memoActions(this.#actions, this.#subduxes) as any; } get selectors() { @@ -323,14 +330,15 @@ export class Updux { } createStore(initial?: unknown, enhancerGenerator?: Function) { + const enhancer = (enhancerGenerator ?? applyMiddleware)( + this.middleware + ); - const enhancer = (enhancerGenerator ?? applyMiddleware)(this.middleware); - - const store : { - getState: Function & Record, - dispatch: Function & Record, - selectors: Record, - actions: Record, + const store: { + getState: Function & Record; + dispatch: Function & Record; + selectors: Record; + actions: AggregateDuxActions; } = reduxCreateStore( this.reducer, initial ?? this.initial, diff --git a/src/actions.test.js b/src/actions.test.js index 094ac1a..a5accb6 100644 --- a/src/actions.test.js +++ b/src/actions.test.js @@ -1,13 +1,13 @@ -import { action } from './actions.js'; +import { action } from './actions'; test('action generators', () => { const foo = action('foo'); - expect(foo.type).toEqual( 'foo'); - expect(foo()).toMatchObject( { type: 'foo' }); + expect(foo.type).toEqual('foo'); + expect(foo()).toMatchObject({ type: 'foo' }); const bar = action('bar'); - expect(bar.type).toEqual( 'bar'); - expect(bar()).toMatchObject( { type: 'bar' }); + expect(bar.type).toEqual('bar'); + expect(bar()).toMatchObject({ type: 'bar' }); }); diff --git a/src/actions.ts b/src/actions.ts index c265700..03d1328 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -1,11 +1,16 @@ -export type Action = { - type: T; meta?: Record; } & ( - { payload?: TPayload } -) +export type Action = { + type: T; + meta?: Record; +} & { + payload?: TPayload; +}; -export type ActionGenerator = { +export type ActionGenerator< + TType extends string = string, + TPayloadGen = undefined +> = { type: TType; -} & (TPayloadGen extends (...args:any) => any +} & (TPayloadGen extends (...args: any) => any ? (...args: Parameters) => { type: TType; payload: ReturnType; @@ -22,7 +27,7 @@ export type ActionGenerator { - expect( - buildInitial({ a: 1 }, { b: { initial: { c: 2 } } }) - ).toMatchObject({ + expect(buildInitial({ a: 1 }, { b: { initial: { c: 2 } } })).toMatchObject({ a: 1, b: { c: 2 }, }); diff --git a/src/buildSelectors/index.ts b/src/buildSelectors/index.ts index 82c0904..36cbb35 100644 --- a/src/buildSelectors/index.ts +++ b/src/buildSelectors/index.ts @@ -1,16 +1,23 @@ import { map, mapValues, merge } from 'lodash'; -export function buildSelectors(localSelectors, splatSelector = {}, subduxes ={}) { +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])); + return mapValues( + selectors, + (func: Function) => (state) => func(state[slice]) + ); }); let splat = {}; - for ( const name in splatSelector ) { + for (const name in splatSelector) { splat[name] = (state) => (...args) => { @@ -25,7 +32,7 @@ export function buildSelectors(localSelectors, splatSelector = {}, subduxes ={}) ) ); }; - } + } return merge({}, ...subSelectors, localSelectors, splat); } diff --git a/src/buildUpreducer.js b/src/buildUpreducer.js index 6d6bb54..e544a74 100644 --- a/src/buildUpreducer.js +++ b/src/buildUpreducer.js @@ -8,7 +8,8 @@ export function buildUpreducer(initial, mutations, subduxes = {}) { : null; return (action) => (state) => { - if( !action?.type ) throw new Error("upreducer called with a bad action"); + if (!action?.type) + throw new Error('upreducer called with a bad action'); let newState = state ?? initial; diff --git a/src/documentation.test.ts b/src/documentation.test.ts index b0669d9..319440f 100644 --- a/src/documentation.test.ts +++ b/src/documentation.test.ts @@ -32,7 +32,7 @@ test('README.md', () => { }); test('tutorial', () => { - const todosDux = new Updux({ + const todosDux = new Updux({ initial: { next_id: 1, todos: [], diff --git a/src/initial.test.js b/src/initial.test.ts similarity index 100% rename from src/initial.test.js rename to src/initial.test.ts diff --git a/src/mutations.test.js b/src/mutations.test.js index 8a09503..fde726f 100644 --- a/src/mutations.test.js +++ b/src/mutations.test.js @@ -14,9 +14,11 @@ test('basic', () => { }, }); - expect(dux.reducer(undefined, dux.actions.doIt())).toEqual( 'bingo'); + expect(dux.reducer(undefined, dux.actions.doIt())).toEqual('bingo'); - expect(dux.reducer(undefined, dux.actions.thisToo())).toEqual( 'straight type'); + expect(dux.reducer(undefined, dux.actions.thisToo())).toEqual( + 'straight type' + ); }); test('override', () => { @@ -48,8 +50,7 @@ test('override', () => { undefined ); - expect(state).toMatchObject( - { + expect(state).toMatchObject({ alpha: ['foo', 'bar'], subbie: 1, }); @@ -77,8 +78,9 @@ test('order of processing', () => { }, }); - expect(dux.reducer(undefined, foo())) - .toMatchObject({ x: ['subdux', 'main'] }); + expect(dux.reducer(undefined, foo())).toMatchObject({ + x: ['subdux', 'main'], + }); }); test('setMutation', () => { @@ -89,21 +91,20 @@ test('setMutation', () => { }); // noop - expect(dux.reducer(undefined, foo())).toEqual( ''); + expect(dux.reducer(undefined, foo())).toEqual(''); dux.setMutation('foo', () => () => 'foo'); - expect(dux.reducer(undefined, foo())).toEqual( 'foo'); - + expect(dux.reducer(undefined, foo())).toEqual('foo'); }); test('setMutation, name as function', () => { - const bar = action('bar'); + const bar = action('bar'); - const dux = new Updux({ - initial: '', - }); - dux.setMutation(bar, () => () => 'bar'); - - expect(dux.reducer(undefined, bar())).toEqual( 'bar'); + const dux = new Updux({ + initial: '', }); + dux.setMutation(bar, () => () => 'bar'); + + expect(dux.reducer(undefined, bar())).toEqual('bar'); +}); diff --git a/src/reducer.test.js b/src/reducer.test.js index 62cd0e7..f475cc8 100644 --- a/src/reducer.test.js +++ b/src/reducer.test.js @@ -7,7 +7,7 @@ test('basic reducer', () => { expect(typeof dux.reducer).toBe('function'); - expect(dux.reducer({ a: 1 }, { type: 'foo' })).toMatchObject({a:1}); // noop + expect(dux.reducer({ a: 1 }, { type: 'foo' })).toMatchObject({ a: 1 }); // noop }); test('basic upreducer', () => { @@ -15,7 +15,7 @@ test('basic upreducer', () => { expect(typeof dux.upreducer).toBe('function'); - expect(dux.upreducer({type:'foo'})({ a: 1 })).toMatchObject({a:1}); // noop + expect(dux.upreducer({ type: 'foo' })({ a: 1 })).toMatchObject({ a: 1 }); // noop }); test('reducer with action', () => { @@ -28,5 +28,5 @@ test('reducer with action', () => { }, }); - expect(dux.reducer({ a: 1 }, { type: 'inc' })).toMatchObject({a:2}); + expect(dux.reducer({ a: 1 }, { type: 'inc' })).toMatchObject({ a: 2 }); }); diff --git a/src/splat.test.js b/src/splat.test.js index 5220460..bb2ab45 100644 --- a/src/splat.test.js +++ b/src/splat.test.js @@ -135,8 +135,7 @@ test('splat subscriptions, more', () => { initial: { a: 1 }, actions: { foo: null, incAll: null }, mutations: { - foo: (id) => (state) => - state.a === id ? { ...state, b: 1 } : state, + foo: (id) => (state) => state.a === id ? { ...state, b: 1 } : state, incAll: () => (state) => ({ ...state, a: state.a + 1 }), }, reactions: [() => snitch], @@ -163,16 +162,8 @@ test('splat subscriptions, more', () => { expect(snitch).toHaveBeenCalledTimes(2); - expect(snitch).toHaveBeenCalledWith( - { a: 1 }, - undefined, - expect.anything() - ); - expect(snitch).toHaveBeenCalledWith( - { a: 2 }, - undefined, - expect.anything() - ); + expect(snitch).toHaveBeenCalledWith({ a: 1 }, undefined, expect.anything()); + expect(snitch).toHaveBeenCalledWith({ a: 2 }, undefined, expect.anything()); snitch.mockReset(); @@ -194,11 +185,7 @@ test('splat subscriptions, more', () => { expect(snitch).toHaveBeenCalledTimes(1); - expect(snitch).toHaveBeenCalledWith( - undefined, - { a: 1 }, - expect.anything() - ); + expect(snitch).toHaveBeenCalledWith(undefined, { a: 1 }, expect.anything()); // only one subscriber left snitch.mockReset(); @@ -246,9 +233,7 @@ test('many levels down', () => { mutations: { add: () => (x) => x + 1, }, - reactions: [ - (store) => (state) => snitch(state, store), - ], + reactions: [(store) => (state) => snitch(state, store)], }, }, }, diff --git a/src/subscriptions.test.js b/src/subscriptions.test.js index 870d4f1..ef89012 100644 --- a/src/subscriptions.test.js +++ b/src/subscriptions.test.js @@ -109,8 +109,7 @@ test('subscription within subduxes', () => { let innerState = jest.fn(() => null); let outerState = jest.fn(() => null); - const resetMocks = () => - [innerState, outerState].map((f) => f.mockReset()); + const resetMocks = () => [innerState, outerState].map((f) => f.mockReset()); const inner = new Updux({ initial: 1, diff --git a/src/types.ts b/src/types.ts index 8cfddb8..d969524 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,2 +1,37 @@ - export type Dict = Record; + +export type UnionToIntersection = ( + U extends any ? (k: U) => void : never +) extends (k: infer I) => void + ? I + : never; + +type Subdux = { + initial: TState; +}; + +type StateOf = D extends { initial: infer I } ? I : unknown; +type ActionsOf = C extends { actions: infer A } + ? { + [K in keyof A]: Function; + } + : {}; + +type Subduxes = Record; + +export type DuxStateSubduxes = C extends { '*': infer I } + ? { + [key: string]: StateOf; + [index: number]: StateOf; + } + : { [K in keyof C]: StateOf }; + +export type AggregateDuxState = TState & + DuxStateSubduxes; + +type DuxActionsSubduxes = C extends object ? ActionsOf : unknown; + +type ItemsOf = C extends object ? C[keyof C] : unknown; + +export type AggregateDuxActions = TActions & + UnionToIntersection>>; diff --git a/tsconfig.json b/tsconfig.json index daa9702..a48c5c5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,19 +1,17 @@ { - "include": [ "./src" ], - "exclude": [ "./docs", "./dist" ], - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "target": "es2020", - "lib": [ - "es2020" - ], - "module": "ES2020", - "moduleResolution": "Node", - "strict": false, - "sourceMap": true, - "allowSyntheticDefaultImports": true, - "declaration": true, - "allowJs": true - } + "include": ["./src"], + "exclude": ["./docs", "./dist"], + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "target": "es2020", + "lib": ["es2020"], + "module": "ES2020", + "moduleResolution": "Node", + "strict": false, + "sourceMap": true, + "allowSyntheticDefaultImports": true, + "declaration": true, + "allowJs": true + } } diff --git a/typedoc.json b/typedoc.json index 91a2cf5..ecaa99a 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,15 +1,13 @@ { - "name": "Updux", - "entryPoints": [ - "./types/index.d.ts" - ], - "out": "docs/API", - "excludeExternals": true, - "excludePrivate": true, - "excludeProtected": true, - "disableSources": true, - "listInvalidSymbolLinks": true, - "markedOptions": { - "mangle": false - } + "name": "Updux", + "entryPoints": ["./types/index.d.ts"], + "out": "docs/API", + "excludeExternals": true, + "excludePrivate": true, + "excludeProtected": true, + "disableSources": true, + "listInvalidSymbolLinks": true, + "markedOptions": { + "mangle": false + } }