Compare commits

..

12 Commits

Author SHA1 Message Date
be505ee915 1.0.21 2020-11-29 23:51:06 +00:00
9584f403b1 fix(core): update 2020-11-29 23:51:05 +00:00
993a100eae 1.0.20 2020-11-29 23:33:56 +00:00
29f0307ef8 fix(core): update 2020-11-29 23:33:55 +00:00
9413c5bcdc 1.0.19 2020-11-29 23:31:32 +00:00
1d5fb320f4 fix(core): update 2020-11-29 23:31:31 +00:00
69e905ad76 1.0.18 2020-11-29 23:28:45 +00:00
7b9ed9ee78 fix(core): update 2020-11-29 23:28:44 +00:00
f884807f33 1.0.17 2020-07-27 18:17:53 +00:00
f167800113 fix(core): update 2020-07-27 18:17:52 +00:00
c549d06a6b 1.0.16 2020-05-27 20:47:16 +00:00
23061c86eb fix(core): update 2020-05-27 20:47:15 +00:00
13 changed files with 11430 additions and 1850 deletions

View File

@ -19,23 +19,35 @@ mirror:
stage: security
script:
- npmci git mirror
only:
- tags
tags:
- lossless
- docker
- notpriv
audit:
auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --production --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=prod --production
tags:
- docker
auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high
- npmci command npm audit --audit-level=high --only=dev
tags:
- lossless
- docker
- notpriv
allow_failure: true
# ====================
# test stage
@ -50,9 +62,7 @@ testStable:
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- lossless
- docker
- priv
testBuild:
stage: test
@ -63,9 +73,7 @@ testBuild:
- npmci command npm run build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- lossless
- docker
- notpriv
release:
stage: release
@ -85,6 +93,8 @@ release:
codequality:
stage: metadata
allow_failure: true
only:
- tags
script:
- npmci command npm install -g tslint typescript
- npmci npm prepare

View File

@ -15,7 +15,7 @@
"properties": {
"projectType": {
"type": "string",
"enum": ["website", "element", "service", "npm"]
"enum": ["website", "element", "service", "npm", "wcc"]
}
}
}

12672
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@pushrocks/smartstate",
"version": "1.0.15",
"version": "1.0.21",
"private": false,
"description": "a package that handles state in a good way",
"main": "dist_ts/index.js",
@ -9,21 +9,22 @@
"license": "MIT",
"scripts": {
"test": "(tstest test/)",
"build": "(tsbuild)",
"format": "(gitzone format)"
"build": "(tsbuild --web && tsbundle npm)"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.1.24",
"@gitzone/tstest": "^1.0.28",
"@pushrocks/tapbundle": "^3.2.1",
"@types/node": "^14.0.1",
"tslint": "^6.1.2",
"@gitzone/tsbuild": "^2.1.25",
"@gitzone/tsbundle": "^1.0.78",
"@gitzone/tstest": "^1.0.52",
"@pushrocks/tapbundle": "^3.2.9",
"@types/node": "^14.14.10",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0"
},
"dependencies": {
"@pushrocks/lik": "^4.0.0",
"@pushrocks/smartpromise": "^3.0.6",
"rxjs": "^6.5.5"
"@pushrocks/lik": "^4.0.20",
"@pushrocks/smartpromise": "^3.1.3",
"@pushrocks/smartrx": "^2.0.19",
"rxjs": "^6.6.3"
},
"files": [
"ts/**/*",
@ -36,5 +37,8 @@
"cli.js",
"npmextra.json",
"readme.md"
],
"browserslist": [
"last 1 chrome versions"
]
}

View File

@ -8,19 +8,25 @@ a package that handles state in a good way
* [docs (typedoc)](https://pushrocks.gitlab.io/smartstate/)
## Status for master
[![pipeline status](https://gitlab.com/pushrocks/smartstate/badges/master/pipeline.svg)](https://gitlab.com/pushrocks/smartstate/commits/master)
[![coverage report](https://gitlab.com/pushrocks/smartstate/badges/master/coverage.svg)](https://gitlab.com/pushrocks/smartstate/commits/master)
[![npm downloads per month](https://img.shields.io/npm/dm/@pushrocks/smartstate.svg)](https://www.npmjs.com/package/@pushrocks/smartstate)
[![Known Vulnerabilities](https://snyk.io/test/npm/@pushrocks/smartstate/badge.svg)](https://snyk.io/test/npm/@pushrocks/smartstate)
[![TypeScript](https://img.shields.io/badge/TypeScript->=%203.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
[![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-prettier-ff69b4.svg)](https://prettier.io/)
Status Category | Status Badge
-- | --
GitLab Pipelines | [![pipeline status](https://gitlab.com/pushrocks/smartstate/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/pushrocks/smartstate/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@pushrocks/smartstate)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/pushrocks/smartstate)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/smartstate)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/smartstate)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/smartstate)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
## Usage
Use TypeScript for best in class intellisense.
## Contribution
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)

View File

@ -1,58 +1,58 @@
import { expect, tap } from '@pushrocks/tapbundle';
import * as smartstate from '../ts/index';
type TMyStateParts = 'testStatePart';
interface TStatePartPayload {
currentFavorites: string[];
deep: {
hi: number;
};
}
let testState: smartstate.Smartstate<TMyStateParts>;
let testStatePart: smartstate.StatePart<TMyStateParts, TStatePartPayload>;
tap.test('should create a new SmartState', async () => {
testState = new smartstate.Smartstate<TMyStateParts>();
expect(testState).to.be.instanceOf(smartstate.Smartstate);
});
tap.test('should create a new StatePart', async () => {
testStatePart = testState.getStatePart<TStatePartPayload>('testStatePart', {
currentFavorites: [],
deep: {
hi: 2
}
});
expect(testStatePart).to.be.instanceOf(smartstate.StatePart);
console.log(testStatePart);
});
tap.test('should select something', async () => {
testStatePart
.select(state => state.deep.hi)
.subscribe(substate => {
expect(substate).to.equal(2);
});
});
tap.test('should dispatch a state action', async tools => {
const done = tools.defer();
const addFavourite = testStatePart.createAction<string>(async (statePart, payload) => {
const currentState = statePart.getState();
currentState.currentFavorites.push(payload);
return currentState;
});
testStatePart
.waitUntilPresent(state => {
return state.currentFavorites[0];
})
.then(() => {
done.resolve();
});
await testStatePart.dispatchAction(addFavourite, 'my favourite things');
expect(testStatePart.getState().currentFavorites).to.include('my favourite things');
await done.promise;
});
tap.start();
import { expect, tap } from '@pushrocks/tapbundle';
import * as smartstate from '../ts/index';
type TMyStateParts = 'testStatePart';
interface TStatePartPayload {
currentFavorites: string[];
deep: {
hi: number;
};
}
let testState: smartstate.Smartstate<TMyStateParts>;
let testStatePart: smartstate.StatePart<TMyStateParts, TStatePartPayload>;
tap.test('should create a new SmartState', async () => {
testState = new smartstate.Smartstate<TMyStateParts>();
expect(testState).to.be.instanceOf(smartstate.Smartstate);
});
tap.test('should create a new StatePart', async () => {
testStatePart = testState.getStatePart<TStatePartPayload>('testStatePart', {
currentFavorites: [],
deep: {
hi: 2,
},
});
expect(testStatePart).to.be.instanceOf(smartstate.StatePart);
console.log(testStatePart);
});
tap.test('should select something', async () => {
testStatePart
.select((state) => state.deep.hi)
.subscribe((substate) => {
expect(substate).to.equal(2);
});
});
tap.test('should dispatch a state action', async (tools) => {
const done = tools.defer();
const addFavourite = testStatePart.createAction<string>(async (statePart, payload) => {
const currentState = statePart.getState();
currentState.currentFavorites.push(payload);
return currentState;
});
testStatePart
.waitUntilPresent((state) => {
return state.currentFavorites[0];
})
.then(() => {
done.resolve();
});
await testStatePart.dispatchAction(addFavourite, 'my favourite things');
expect(testStatePart.getState().currentFavorites).to.include('my favourite things');
await done.promise;
});
tap.start();

View File

@ -1,5 +1,5 @@
export * from './smartstate.classes.smartstate';
export * from './smartstate.classes.statepart';
export * from './smartstate.classes.statecollection';
export * from './smartstate.classes.stateaction';
export * from './smartstate.classes.stateobservable';
export * from './smartstate.classes.smartstate';
export * from './smartstate.classes.statepart';
export * from './smartstate.classes.statecollection';
export * from './smartstate.classes.stateaction';
export * from './smartstate.classes.stateobservable';

View File

@ -1,52 +1,57 @@
import * as plugins from './smartstate.plugins';
import { StatePart } from './smartstate.classes.statepart';
/**
* Smartstate takes care of providing state
*/
export class Smartstate<StatePartNameType> {
public statePartMap: { [key: string]: StatePart<StatePartNameType, any> } = {};
constructor() {}
public getStatePart<PayloadType>(
statePartNameArg: StatePartNameType,
initialArg?: PayloadType
): StatePart<StatePartNameType, PayloadType> {
if (this.statePartMap[statePartNameArg as any]) {
if (initialArg) {
throw new Error(
`${statePartNameArg} already exists, yet you try to set an initial state again`
);
}
return this.statePartMap[statePartNameArg as any];
} else {
if (!initialArg) {
throw new Error(
`${statePartNameArg} does not yet exist, yet you don't provide an initial state`
);
}
return this.createStatePart<PayloadType>(statePartNameArg, initialArg);
}
}
/**
* creates a statepart
* @param statePartName
* @param initialPayloadArg
*/
private createStatePart<PayloadType>(
statePartName: StatePartNameType,
initialPayloadArg: PayloadType
): StatePart<StatePartNameType, PayloadType> {
const newState = new StatePart<StatePartNameType, PayloadType>(statePartName);
newState.setState(initialPayloadArg);
this.statePartMap[statePartName as any] = newState;
return newState;
}
/**
* dispatches an action on the main level
*/
public dispatch() {}
}
import * as plugins from './smartstate.plugins';
import { StatePart } from './smartstate.classes.statepart';
/**
* Smartstate takes care of providing state
*/
export class Smartstate<StatePartNameType> {
public statePartMap: { [key: string]: StatePart<StatePartNameType, unknown> } = {};
constructor() {}
/**
* Allows getting and initializing a new statepart
* initMode === 'soft' it will allow existing stateparts
* initMode === 'mandatory' will fail if there is an exiting statepart
* initMode === 'force' will overwrite any existing statepart
* @param statePartNameArg
* @param initialArg
* @param initMode
*/
public getStatePart<PayloadType>(
statePartNameArg: string & StatePartNameType,
initialArg?: PayloadType,
initMode?: 'soft' | 'mandatory' | 'force'
): StatePart<StatePartNameType, PayloadType> {
if (this.statePartMap[statePartNameArg as any]) {
if (initialArg && (!initMode || initMode !== 'soft')) {
throw new Error(
`${statePartNameArg} already exists, yet you try to set an initial state again`
);
}
return this.statePartMap[statePartNameArg] as StatePart<StatePartNameType, PayloadType>;
} else {
if (!initialArg) {
throw new Error(
`${statePartNameArg} does not yet exist, yet you don't provide an initial state`
);
}
return this.createStatePart<PayloadType>(statePartNameArg, initialArg);
}
}
/**
* creates a statepart
* @param statePartName
* @param initialPayloadArg
*/
private createStatePart<PayloadType>(
statePartName: StatePartNameType,
initialPayloadArg: PayloadType
): StatePart<StatePartNameType, PayloadType> {
const newState = new StatePart<StatePartNameType, PayloadType>(statePartName);
newState.setState(initialPayloadArg);
this.statePartMap[statePartName as any] = newState;
return newState;
}
}

View File

@ -1,20 +1,20 @@
import * as plugins from './smartstate.plugins';
import { StatePart } from './smartstate.classes.statepart';
export interface IActionDef<TStateType, TActionPayloadType> {
(stateArg: StatePart<any, TStateType>, actionPayload: TActionPayloadType): Promise<TStateType>;
}
/**
* an actionmodifier for the state
*/
export class StateAction<TStateType, TActionPayloadType> {
constructor(
public statePartRef: StatePart<any, any>,
public actionDef: IActionDef<TStateType, TActionPayloadType>
) {}
public trigger(payload: TActionPayloadType) {
this.statePartRef.dispatchAction(this, payload);
}
}
import * as plugins from './smartstate.plugins';
import { StatePart } from './smartstate.classes.statepart';
export interface IActionDef<TStateType, TActionPayloadType> {
(stateArg: StatePart<any, TStateType>, actionPayload: TActionPayloadType): Promise<TStateType>;
}
/**
* an actionmodifier for the state
*/
export class StateAction<TStateType, TActionPayloadType> {
constructor(
public statePartRef: StatePart<any, any>,
public actionDef: IActionDef<TStateType, TActionPayloadType>
) {}
public trigger(payload: TActionPayloadType) {
this.statePartRef.dispatchAction(this, payload);
}
}

View File

@ -1,12 +1,12 @@
import * as plugins from './smartstate.plugins';
import { StatePart } from './smartstate.classes.statepart';
/**
* A StatePartCollection is a collection of StateParts.
* It can be used for expressing interest in a certain set of StateParts.
*/
export class StatePartCollection<StatePartNameType, T> extends StatePart<StatePartNameType, T> {
constructor(nameArg: StatePartNameType) {
super(nameArg);
}
}
import * as plugins from './smartstate.plugins';
import { StatePart } from './smartstate.classes.statepart';
/**
* A StatePartCollection is a collection of StateParts.
* It can be used for expressing interest in a certain set of StateParts.
*/
export class StatePartCollection<StatePartNameType, T> extends StatePart<StatePartNameType, T> {
constructor(nameArg: StatePartNameType) {
super(nameArg);
}
}

View File

@ -1,13 +1,13 @@
import * as plugins from './smartstate.plugins';
/**
* State observable observes a StatePart and notifies everyone interested
*/
export class StateObservable {
/**
* creates an observable from a StateCollection
*/
public static fromStatePartCollection(filterArg?: () => any) {}
constructor() {}
}
import * as plugins from './smartstate.plugins';
/**
* State observable observes a StatePart and notifies everyone interested
*/
export class StateObservable {
/**
* creates an observable from a StateCollection
*/
public static fromStatePartCollection(filterArg?: () => any) {}
constructor() {}
}

View File

@ -1,97 +1,95 @@
import * as plugins from './smartstate.plugins';
import { Observable, Subject } from 'rxjs';
import { startWith, takeUntil, map } from 'rxjs/operators';
import { StateAction, IActionDef } from './smartstate.classes.stateaction';
export class StatePart<TStatePartName, TStatePayload> {
public name: TStatePartName;
public state = new Subject<TStatePayload>();
public stateStore: TStatePayload;
constructor(nameArg: TStatePartName) {
this.name = nameArg;
}
/**
* gets the state from the state store
*/
public getState(): TStatePayload {
return this.stateStore;
}
/**
* sets the stateStore to the new state
* @param newStateArg
*/
public setState(newStateArg: TStatePayload) {
this.stateStore = newStateArg;
this.notifyChange();
}
/**
* notifies of a change on the state
*/
public notifyChange() {
this.state.next(this.stateStore);
}
/**
* selects a state or a substate
*/
public select<T = TStatePayload>(selectorFn?: (state: TStatePayload) => T): Observable<T> {
if (!selectorFn) {
selectorFn = (state: TStatePayload) => <T>(<any>state);
}
const mapped = this.state.pipe(
startWith(this.getState()),
map(stateArg => {
try {
return selectorFn(stateArg);
} catch (e) {
// Nothing here
}
})
);
return mapped;
}
/**
* creates an action capable of modifying the state
*/
public createAction<TActionPayload>(
actionDef: IActionDef<TStatePayload, TActionPayload>
): StateAction<TStatePayload, TActionPayload> {
return new StateAction(this, actionDef);
}
/**
* dispatches an action on the statepart level
*/
public async dispatchAction<T>(stateAction: StateAction<TStatePayload, T>, actionPayload: T) {
const newState = await stateAction.actionDef(this, actionPayload);
this.setState(newState);
}
/**
* waits until a certain part of the state becomes available
* @param selectorFn
*/
public async waitUntilPresent<T = TStatePayload>(
selectorFn?: (state: TStatePayload) => T
): Promise<T> {
const done = plugins.smartpromise.defer<T>();
const selectedObservable = this.select(selectorFn);
const subscription = selectedObservable.subscribe(async value => {
if (value) {
done.resolve(value);
}
});
const result = await done.promise;
subscription.unsubscribe();
return result;
}
}
import * as plugins from './smartstate.plugins';
import { StateAction, IActionDef } from './smartstate.classes.stateaction';
export class StatePart<TStatePartName, TStatePayload> {
public name: TStatePartName;
public state = new plugins.smartrx.rxjs.Subject<TStatePayload>();
public stateStore: TStatePayload;
constructor(nameArg: TStatePartName) {
this.name = nameArg;
}
/**
* gets the state from the state store
*/
public getState(): TStatePayload {
return this.stateStore;
}
/**
* sets the stateStore to the new state
* @param newStateArg
*/
public setState(newStateArg: TStatePayload) {
this.stateStore = newStateArg;
this.notifyChange();
}
/**
* notifies of a change on the state
*/
public notifyChange() {
this.state.next(this.stateStore);
}
/**
* selects a state or a substate
*/
public select<T = TStatePayload>(
selectorFn?: (state: TStatePayload) => T
): plugins.smartrx.rxjs.Observable<T> {
if (!selectorFn) {
selectorFn = (state: TStatePayload) => <T>(<any>state);
}
const mapped = this.state.pipe(
plugins.smartrx.rxjs.ops.startWith(this.getState()),
plugins.smartrx.rxjs.ops.map((stateArg) => {
try {
return selectorFn(stateArg);
} catch (e) {
// Nothing here
}
})
);
return mapped;
}
/**
* creates an action capable of modifying the state
*/
public createAction<TActionPayload>(
actionDef: IActionDef<TStatePayload, TActionPayload>
): StateAction<TStatePayload, TActionPayload> {
return new StateAction(this, actionDef);
}
/**
* dispatches an action on the statepart level
*/
public async dispatchAction<T>(stateAction: StateAction<TStatePayload, T>, actionPayload: T) {
const newState = await stateAction.actionDef(this, actionPayload);
this.setState(newState);
}
/**
* waits until a certain part of the state becomes available
* @param selectorFn
*/
public async waitUntilPresent<T = TStatePayload>(
selectorFn?: (state: TStatePayload) => T
): Promise<T> {
const done = plugins.smartpromise.defer<T>();
const selectedObservable = this.select(selectorFn);
const subscription = selectedObservable.subscribe(async (value) => {
if (value) {
done.resolve(value);
}
});
const result = await done.promise;
subscription.unsubscribe();
return result;
}
}

View File

@ -1,11 +1,4 @@
import { Observable } from 'rxjs';
const rxjsPart = {
Observable
};
export { rxjsPart };
import * as smartpromise from '@pushrocks/smartpromise';
export { smartpromise };
import * as smartpromise from '@pushrocks/smartpromise';
import * as smartrx from '@pushrocks/smartrx';
export { smartpromise, smartrx };