typescript
Yanick Champoux 2021-10-15 12:41:58 -04:00
parent 3394a00419
commit 440c76d408
32 changed files with 203 additions and 223 deletions

6
babel.config.cjs Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
};

0
docs/API/assets/main.d.ts vendored Normal file
View File

0
docs/API/assets/search.d.ts vendored Normal file
View File

33
docs/assets/js/search.d.ts vendored Normal file
View File

@ -0,0 +1,33 @@
declare namespace typedoc {
namespace search {
namespace data {
const kinds: {
"32": string;
"64": string;
"128": string;
"256": string;
"512": string;
"1024": string;
"2048": string;
"65536": string;
"262144": string;
"4194304": string;
};
const rows: ({
id: number;
kind: number;
name: string;
url: string;
classes: string;
parent?: undefined;
} | {
id: number;
kind: number;
name: string;
url: string;
classes: string;
parent: string;
})[];
}
}
}

0
docs/scripts/linenumber.d.ts vendored Normal file
View File

0
docs/scripts/prettify/lang-css.d.ts vendored Normal file
View File

1
docs/scripts/prettify/prettify.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare var q: any;

0
docs/scripts/search.d.ts vendored Normal file
View File

3
jest.config.ts Normal file
View File

@ -0,0 +1,3 @@
export default {
roots: [ './src' ]
}

0
out/scripts/linenumber.d.ts vendored Normal file
View File

0
out/scripts/prettify/lang-css.d.ts vendored Normal file
View File

1
out/scripts/prettify/prettify.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare var q: any;

View File

@ -1,5 +1,4 @@
{ {
"type": "module",
"dependencies": { "dependencies": {
"@yanick/updeep": "link:../updeep", "@yanick/updeep": "link:../updeep",
"lodash": "^4.17.15", "lodash": "^4.17.15",
@ -7,7 +6,6 @@
"moize": "^6.1.0", "moize": "^6.1.0",
"redux": "^4.0.5", "redux": "^4.0.5",
"ts-action": "^11.0.0", "ts-action": "^11.0.0",
"ts-node": "^8.6.2",
"updeep": "^1.2.1" "updeep": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
@ -15,6 +13,8 @@
"@babel/core": "^7.15.5", "@babel/core": "^7.15.5",
"@babel/plugin-transform-modules-commonjs": "^7.15.4", "@babel/plugin-transform-modules-commonjs": "^7.15.4",
"@babel/preset-env": "^7.8.7", "@babel/preset-env": "^7.8.7",
"@babel/preset-typescript": "^7.15.0",
"@types/jest": "^27.0.2",
"@types/lodash": "^4.14.149", "@types/lodash": "^4.14.149",
"@types/sinon": "^7.5.2", "@types/sinon": "^7.5.2",
"@typescript-eslint/eslint-plugin": "^2.23.0", "@typescript-eslint/eslint-plugin": "^2.23.0",
@ -29,12 +29,14 @@
"eslint-plugin-import": "^2.20.1", "eslint-plugin-import": "^2.20.1",
"eslint-plugin-prettier": "^3.1.2", "eslint-plugin-prettier": "^3.1.2",
"glob": "^7.1.6", "glob": "^7.1.6",
"jest": "^27.2.5",
"jsdoc": "^3.6.7", "jsdoc": "^3.6.7",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"promake": "^3.1.3", "promake": "^3.1.3",
"sinon": "^9.0.1", "sinon": "^9.0.1",
"standard-version": "^8.0.0", "standard-version": "^8.0.0",
"tap": "15", "tap": "15",
"ts-node": "^8.10.2",
"tsd": "^0.17.0", "tsd": "^0.17.0",
"typedoc": "0.22.5", "typedoc": "0.22.5",
"typescript": "^4.4.3" "typescript": "^4.4.3"

View File

@ -2,17 +2,73 @@
import moize from 'moize'; import moize from 'moize';
import u from '@yanick/updeep'; import u from '@yanick/updeep';
import { createStore as reduxCreateStore, applyMiddleware } from 'redux'; import { createStore as reduxCreateStore, applyMiddleware } from 'redux';
import { get, map, mapValues, merge, difference } from 'lodash-es'; import { get, map, mapValues, merge, difference } from 'lodash';
import { buildInitial } from './buildInitial/index.js'; import { buildInitial } from './buildInitial';
import { buildActions } from './buildActions/index.js'; import { buildActions } from './buildActions';
import { buildSelectors } from './buildSelectors/index.js'; import { buildSelectors } from './buildSelectors';
import { action } from './actions.js'; import { action } from './actions';
import { buildUpreducer } from './buildUpreducer.js'; import { buildUpreducer } from './buildUpreducer';
import { import {
buildMiddleware, buildMiddleware,
augmentMiddlewareApi, augmentMiddlewareApi,
} from './buildMiddleware/index.js'; } from './buildMiddleware';
import { Dict } from './types';
/**
* Configuration object typically passed to the constructor of the class Updux.
*/
export interface UpduxConfig<TState = unknown> {
/**
* Local initial state.
* @default {}
*/
initial?: TState;
/**
* Subduxes to be merged to this dux.
*/
subduxes?: Dict<Updux | UpduxConfig>;
/**
* Local actions.
*/
actions?: Record<string, any>;
/**
* Local selectors.
*/
selectors?: Record<string, Function>;
/**
* Local mutations
*/
mutations?: Record<string, Function>;
/**
* Selectors to apply to the mapped subduxes. Only
* applicable if the dux is a mapping dux.
*/
mappedSelectors?: Record<string, Function>;
/**
* Local effects.
*/
effects?: Record<string, Function>;
/**
* 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;
}
export class Updux { export class Updux {
/** @type { unknown } */ /** @type { unknown } */
@ -24,11 +80,11 @@ export class Updux {
#selectors = {}; #selectors = {};
#mutations = {}; #mutations = {};
#effects = []; #effects = [];
#subscriptions = []; #reactions = [];
#splatSelector = undefined; #mappedSelectors = undefined;
#splatReaction = undefined; #mappedReaction = undefined;
constructor(config) { constructor(config: UpduxConfig) {
this.#initial = config.initial ?? {}; this.#initial = config.initial ?? {};
this.#subduxes = config.subduxes ?? {}; this.#subduxes = config.subduxes ?? {};
@ -49,7 +105,7 @@ export class Updux {
} }
this.#selectors = config.selectors ?? {}; this.#selectors = config.selectors ?? {};
this.#splatSelector = config.splatSelector; this.#mappedSelectors = config.mappedSelectors;
this.#mutations = config.mutations ?? {}; this.#mutations = config.mutations ?? {};
@ -64,9 +120,9 @@ export class Updux {
this.#effects = Object.entries(config.effects); this.#effects = Object.entries(config.effects);
} }
this.#subscriptions = config.subscriptions ?? []; this.#reactions = config.reactions ?? [];
this.#splatReaction = config.splatReaction; this.#mappedReaction = config.mappedReaction;
} }
#memoInitial = moize(buildInitial); #memoInitial = moize(buildInitial);
@ -75,12 +131,11 @@ export class Updux {
#memoUpreducer = moize(buildUpreducer); #memoUpreducer = moize(buildUpreducer);
#memoMiddleware = moize(buildMiddleware); #memoMiddleware = moize(buildMiddleware);
get subscriptions() { setMappedSelector(name, f) {
return this.#subscriptions; this.#mappedSelectors = {
} ...this.#mappedSelectors,
[name]: f,
setSplatSelector(name, f) { }
this.#splatSelector = [name, f];
} }
get middleware() { get middleware() {
@ -107,7 +162,7 @@ export class Updux {
get selectors() { get selectors() {
return this.#memoSelectors( return this.#memoSelectors(
this.#selectors, this.#selectors,
this.#splatSelector, this.#mappedSelectors,
this.#subduxes this.#subduxes
); );
} }
@ -125,7 +180,7 @@ export class Updux {
} }
addSubscription(subscription) { addSubscription(subscription) {
this.#subscriptions = [...this.#subscriptions, subscription]; this.#reactions = [...this.#reactions, subscription];
} }
setAction(type, payloadFunc) { setAction(type, payloadFunc) {
@ -235,7 +290,7 @@ export class Updux {
} }
subscribeAll(store) { subscribeAll(store) {
let results = this.#subscriptions.map((sub) => let results = this.#reactions.map((sub) =>
this.subscribeTo(store, sub) this.subscribeTo(store, sub)
); );
@ -249,14 +304,14 @@ export class Updux {
} }
} }
if (this.#splatReaction) { if (this.#mappedReaction) {
results.push( results.push(
this.subscribeTo( this.subscribeTo(
store, store,
this.splatSubscriber( this.splatSubscriber(
store, store,
this.#subduxes['*'], this.#subduxes['*'],
this.#splatReaction this.#mappedReaction
) )
) )
); );
@ -271,11 +326,16 @@ export class Updux {
} }
createStore(initial) { createStore(initial) {
const store = reduxCreateStore( const store : {
getState: Function,
dispatch: Function,
selectors: Record<string,Function>,
actions: Record<string,Function>,
} = reduxCreateStore(
this.reducer, this.reducer,
initial ?? this.initial, initial ?? this.initial,
applyMiddleware(this.middleware) applyMiddleware(this.middleware)
); ) as any;
store.actions = this.actions; store.actions = this.actions;

View File

@ -1,5 +1,5 @@
import { isPlainObject, mapValues } from 'lodash-es'; import { isPlainObject, mapValues } from 'lodash';
import u from '@yanick/updeep'; import u from 'updeep';
export function buildInitial(initial, subduxes = {}) { export function buildInitial(initial, subduxes = {}) {
if (!isPlainObject(initial) && Object.keys(subduxes).length > 0) if (!isPlainObject(initial) && Object.keys(subduxes).length > 0)

View File

@ -1,6 +1,6 @@
import { test } from 'tap'; import { test } from 'tap';
import { buildInitial } from './index.js'; import { buildInitial } from './index.ts';
test('basic', async (t) => { test('basic', async (t) => {
t.same(buildInitial({ a: 1 }, { b: { initial: { c: 2 } } }), { t.same(buildInitial({ a: 1 }, { b: { initial: { c: 2 } } }), {

10
src/buildInitial/test.ts Normal file
View File

@ -0,0 +1,10 @@
import { buildInitial } from '.';
test('basic', () => {
expect(
buildInitial({ a: 1 }, { b: { initial: { c: 2 } } })
).toMatchObject({
a: 1,
b: { c: 2 },
});
});

View File

@ -1,5 +1,5 @@
import u from '@yanick/updeep'; import u from 'updeep';
import { mapValues, map, get } from 'lodash-es'; import { mapValues, map, get } from 'lodash';
import { Updux } from '../Updux.js'; import { Updux } from '../Updux.js';
const middlewareFor = (type, middleware) => (api) => (next) => (action) => { const middlewareFor = (type, middleware) => (api) => (next) => (action) => {

View File

@ -1,4 +1,4 @@
import { map, mapValues, merge } from 'lodash-es'; import { map, mapValues, merge } from 'lodash';
export function buildSelectors(localSelectors, splatSelector, subduxes) { export function buildSelectors(localSelectors, splatSelector, subduxes) {
const subSelectors = map(subduxes, ({ selectors }, slice) => { const subSelectors = map(subduxes, ({ selectors }, slice) => {

View File

@ -1,5 +1,5 @@
import u from '@yanick/updeep'; import u from 'updeep';
import { mapValues } from 'lodash-es'; import { mapValues } from 'lodash';
export function buildUpreducer(initial, mutations, subduxes = {}) { export function buildUpreducer(initial, mutations, subduxes = {}) {
const subReducers = const subReducers =

View File

@ -1,50 +0,0 @@
import { test } from 'tap';
import u from 'updeep';
import add from 'lodash/fp/add.js';
import { Updux } from './index.js';
test('README.md', async (t) => {
const otherDux = new Updux({});
const dux = new Updux({
initial: {
counter: 0,
},
actions: {
inc: null,
},
subduxes: {
otherDux,
},
});
dux.setMutation('inc', (increment) => u({ counter: add(increment) }));
dux.addEffect('*', (api) => (next) => (action) => {
next(action);
});
const store = dux.createStore();
store.dispatch.inc(1);
t.equal(store.getState().counter, 1);
});
test('tutorial', async (t) => {
const todosDux = new Updux({
initial: {
next_id: 1,
todos: [],
}
});
todosDux.setAction( 'addTodo' );
todosDux.setAction( 'todoDone' );
t.same( todosDux.actions.addTodo('write tutorial') , { type: 'addTodo', payload: 'write tutorial' });
})

1
src/index.d.ts vendored
View File

@ -1 +0,0 @@
export { Updux };

View File

@ -1,2 +1,2 @@
export { Updux } from './Updux.js'; export { Updux } from './Updux';
export { action } from './actions.js'; export { action } from './actions';

View File

@ -1,25 +1,24 @@
import { test } from 'tap'; import u from 'updeep';
import u from '@yanick/updeep';
import { Updux } from './Updux.js'; import { Updux } from './Updux';
test('basic reducer', async (t) => { test('basic reducer', () => {
const dux = new Updux({}); const dux = new Updux({});
t.type(dux.reducer, 'function'); expect(typeof dux.reducer).toBe('function');
t.same(dux.reducer({ a: 1 }, { type: 'foo' }), { a: 1 }, 'noop'); expect(dux.reducer({ a: 1 }, { type: 'foo' })).toMatchObject({a:1}); // noop
}); });
test('basic upreducer', async (t) => { test('basic upreducer', () => {
const dux = new Updux({}); const dux = new Updux({});
t.type(dux.upreducer, 'function'); expect(typeof dux.upreducer).toBe('function');
t.same(dux.upreducer({ type: 'foo' })({ a: 1 }), { a: 1 }, 'noop'); expect(dux.upreducer({type:'foo'})({ a: 1 })).toMatchObject({a:1}); // noop
}); });
test('reducer with action', async (t) => { test('reducer with action', () => {
const dux = new Updux({ const dux = new Updux({
actions: { actions: {
inc: null, inc: null,
@ -29,5 +28,5 @@ test('reducer with action', async (t) => {
}, },
}); });
t.same(dux.reducer({ a: 1 }, { type: 'inc' }), { a: 2 }); expect(dux.reducer({ a: 1 }, { type: 'inc' })).toMatchObject({a:2});
}); });

2
src/types.ts Normal file
View File

@ -0,0 +1,2 @@
export type Dict<T> = Record<string, T>;

View File

@ -1,74 +1,19 @@
{ {
"include": ["./types/*.d.ts"], "include": [ "./src" ],
"exclude": [ "types/index.test-d.ts" ], "exclude": [ "./docs", "./dist" ],
"compilerOptions": { "compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */ "rootDir": "src",
"outDir": "dist",
/* Basic Options */ "target": "es2020",
"incremental": false, /* Enable incremental compilation */ "lib": [
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ "es2020"
"module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ ],
// "lib": [], /* Specify library files to be included in the compilation. */ "module": "ES2020",
"allowJs": true, /* Allow javascript files to be compiled. */ "moduleResolution": "Node",
// "checkJs": true, /* Report errors in .js files. */ "strict": false,
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ "sourceMap": true,
"declaration": true, /* Generates corresponding '.d.ts' file. */ "allowSyntheticDefaultImports": true,
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ "declaration": true,
// "sourceMap": true, /* Generates corresponding '.map' file. */ "allowJs": true
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./ts-out", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
"noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"rootDirs": ["./types"], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": ["./types"], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "./types", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
} }
} }

52
types/index.d.ts vendored
View File

@ -7,58 +7,6 @@ type Mutation<TState = unknown> = (
export * from './actions'; export * from './actions';
/**
* Configuration object typically passed to the constructor of the class Updux.
*/
export interface UpduxConfig<TState = unknown> {
/**
* Local initial state.
* @default {}
*/
initial?: TState;
/**
* Subduxes to be merged to this dux.
*/
subduxes?: Dict<Updux | UpduxConfig>;
/**
* Local actions.
*/
actions?: Record<string, any>;
/**
* Local selectors.
*/
selectors?: Record<string, Function>;
/**
* Local mutations
*/
mutations?: Record<string, Function>;
/**
* Selectors to apply to the mapped subduxes. Only
* applicable if the dux is a mapping dux.
*/
mappedSelectors?: Record<string, Function>;
/**
* Local effects.
*/
effects?: Record<string, Function>;
/**
* Local reactions.
*/
reactions?: Record<string, 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;
}
export class Updux<TState = unknown> { export class Updux<TState = unknown> {
constructor(config: Partial<UpduxConfig<TState>>); constructor(config: Partial<UpduxConfig<TState>>);

1
types/index.test-d.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export {};

18
types/index.test-d.js Normal file
View File

@ -0,0 +1,18 @@
import { expectAssignable, expectType } from 'tsd';
import { Updux, action } from '.';
const dux = new Updux({});
expectType(dux.initial);
() => {
const dux = new Updux({});
expectAssignable(dux);
};
// ActionGenerator
() => {
let a = action('a');
expectAssignable(a);
expectAssignable(a);
let b = action('b', (() => ({})));
const c = b("foo");
expectAssignable(c);
};
//# sourceMappingURL=index.test-d.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.test-d.js","sourceRoot":"","sources":["index.test-d.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,gBAAgB,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAE9D,OAAO,EAAE,KAAK,EAAmB,MAAM,EAAE,MAAM,GAAG,CAAC;AAEnD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC;AAC1B,UAAU,CAAW,GAAG,CAAC,OAAO,CAAE,CAAC;AAGnC,GAAG,EAAE;IAED,MAAM,GAAG,GAAG,IAAI,KAAK,CAAc,EAAE,CAAC,CAAC;IAEvC,gBAAgB,CAAwB,GAAG,CAAC,CAAC;AACjD,CAAC,CAAA;AAED,kBAAkB;AAClB,GAAG,EAAE;IACD,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACpB,gBAAgB,CAAiB,CAAC,CAAC,CAAC;IACpC,gBAAgB,CAA4B,CAAC,CAAC,CAAC;IAE/C,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAkD,CAAE,CAAC;IACpF,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IACnB,gBAAgB,CAA+B,CAAC,CAAC,CAAC;AAGtD,CAAC,CAAA"}

1
types/test.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export var __esModule: boolean;