Skip to content

Commit

Permalink
feat: add plugin and refactor ReduxDevtoolsPlugin
Browse files Browse the repository at this point in the history
  • Loading branch information
why520crazy authored Nov 3, 2022
1 parent 453ea2d commit 3192f46
Show file tree
Hide file tree
Showing 27 changed files with 645 additions and 298 deletions.
10 changes: 10 additions & 0 deletions .docgeni/app/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { isDevMode } from '@angular/core';
import { ThyStoreModule, ReduxDevtoolsPlugin } from '@tethys/store';

export default {
imports: [
ThyStoreModule.forRoot([], {
plugins: isDevMode() ? [ReduxDevtoolsPlugin] : []
})
]
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
"@angular/compiler-cli": "^14.1.2",
"@commitlint/cli": "^12.0.1",
"@commitlint/config-angular": "^12.0.1",
"@docgeni/cli": "^1.2.0-next.25",
"@docgeni/template": "^1.2.0-next.25",
"@docgeni/cli": "^1.2.0-next.27",
"@docgeni/template": "^1.2.0-next.27",
"@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1",
"@typescript-eslint/eslint-plugin": "^5.29.0",
Expand Down
12 changes: 0 additions & 12 deletions packages/store/src/action-state.ts

This file was deleted.

23 changes: 0 additions & 23 deletions packages/store/src/action.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { findAndCreateStoreMetadata } from './utils';
import { InternalDispatcher } from './internals/dispatcher';
import { ActionState } from './action-state';
// import { Observable, of, throwError } from 'rxjs';
// import { catchError, exhaustMap, shareReplay} from 'rxjs/operators';
// import { ActionContext, ActionStatus } from './actions-stream';
Expand Down Expand Up @@ -57,30 +56,8 @@ export function Action(action?: DecoratorActionOptions | string) {
descriptor.value = function (...args: any[]) {
const storeId = this.getStoreInstanceId();
return InternalDispatcher.instance.dispatch(storeId, metadata.actions[type], () => {
ActionState.changeAction(`${target.constructor.name}-${name}`);
return originalFn.call(this, ...args);
});

// ActionState.changeAction(`${target.constructor.name}-${name}`);
// let result = originalFn.call(this, ...args);
// if (result instanceof Observable) {
// result = result.pipe(
// catchError((error) => {
// return of({ status: ActionStatus.Errored, action: action, error: error });
// }),
// // shareReplay(),
// exhaustMap((result: ActionContext | any) => {
// if (result && result.status === ActionStatus.Errored) {
// return throwError(result.error);
// } else {
// return of(result);
// }
// }),
// shareReplay()
// );
// result.subscribe();
// }
// return result;
};
};
}
25 changes: 15 additions & 10 deletions packages/store/src/entity-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,11 @@ export class EntityStore<TState extends EntityState<TEntity, TReferences>, TEnti
*
*/
initialize(entities: TEntity[], pagination?: PaginationInfo) {
const state = this.snapshot;
state.entities = entities || [];
state.pagination = pagination;
this.next(state);
this.setState({
...this.snapshot,
entities: entities || [],
pagination
});
}

/**
Expand All @@ -125,12 +126,16 @@ export class EntityStore<TState extends EntityState<TEntity, TReferences>, TEnti
*
*/
initializeWithReferences(entities: TEntity[], references: TReferences, pagination?: PaginationInfo) {
const state = this.snapshot;
state.entities = entities || [];
state.pagination = pagination;
state.references = references;
this.snapshot.references = references;
this.buildReferencesIdMap();
this.next(state);
this.setState((state) => {
return {
...state,
entities: entities || [],
pagination,
references
};
});
}

/**
Expand Down Expand Up @@ -165,7 +170,7 @@ export class EntityStore<TState extends EntityState<TEntity, TReferences>, TEnti
}
}

this.next(state);
this.next({ ...state });
}

/**
Expand Down
32 changes: 16 additions & 16 deletions packages/store/src/internals/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
import { CancelUncompleted } from '../action';
import { ActionContext, ActionStatus } from '../actions-stream';
import { SafeAny, ActionMetadata } from '../inner-types';
import { PluginContext } from '../plugin';
import { PluginContext, StorePluginFn } from '../plugin';
import { StorePluginManager } from '../plugin-manager';
import { compose, findAndCreateStoreMetadata, generateIdWithTime } from '../utils';
import { StoreFactory } from './store-factory';

Expand Down Expand Up @@ -170,14 +171,9 @@ export class InternalDispatcher {
public dispatch(storeId: string, action: ActionMetadata, originActionFn: () => Observable<unknown> | void) {
const storeInstance = StoreFactory.instance.get(storeId);
const dispatchId = `${action.type}-${generateIdWithTime()}`;
let returnResult = undefined;
const result$ = compose([
// (ctx: PluginContext, next) => {
// return next(ctx).pipe(
// tap((state) => {
// console.log(`new state`, state);
// })
// );
// },
...(StorePluginManager.instance?.rootPluginHandlers || []),
(ctx: PluginContext) => {
const originActionResult = originActionFn();
if (originActionResult instanceof Observable) {
Expand Down Expand Up @@ -208,24 +204,28 @@ export class InternalDispatcher {
shareReplay()
);
} else {
return originActionResult;
returnResult = originActionResult;
return of(originActionResult).pipe(shareReplay());
}
}
])({
state: StoreFactory.instance.get(storeId).getState(),
storeInstance: storeInstance,
action: action.type
});
if (result$ instanceof Observable) {
state: storeInstance.getState(),
getState: () => storeInstance.getState(),
getAllState: () => StoreFactory.instance.getAllState(),
store: storeInstance,
action: `${storeInstance.getStoreInstanceId()}@${action.type}`
}).pipe(shareReplay());
if (returnResult) {
return returnResult;
} else {
result$.subscribe({
error: (error: Error) => {
// this._errorHandler = this._errorHandler || this._injector.get(ErrorHandler);
// this._errorHandler.handleError(error);
}
});
return result$;
}

return result$;
}

private getActionResult$(storeId: string, dispatchId: string): Observable<ActionContext> {
Expand Down
9 changes: 7 additions & 2 deletions packages/store/src/internals/store-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export class StoreFactory implements OnDestroy {

private storeInstancesMap = new Map<string, Store>();

private storeInstancesMap$ = new BehaviorSubject<Map<string, Store>>(this.storeInstancesMap);

public state$ = new Subject<{ storeId: string; state: unknown }>();

register(store: Store) {
Expand All @@ -28,6 +26,13 @@ export class StoreFactory implements OnDestroy {
return this.storeInstancesMap.get(id);
}

getAllState() {
return Array.from(this.storeInstancesMap.entries()).reduce((state, [storeId, store]) => {
state[storeId] = store.getState();
return state;
}, {});
}

// eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method
ngOnDestroy(): void {}
}
35 changes: 27 additions & 8 deletions packages/store/src/module.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
import { NgModule, ModuleWithProviders, Type, Injector, NgModuleRef } from '@angular/core';
import { ROOT_STATE_TOKEN, FEATURE_STATE_TOKEN } from './types';
import { ROOT_STORES_TOKEN, FEATURE_STORES_TOKEN } from './types';
import { Store } from './store';
import { clearInjector, setInjector } from './internals/static-injector';
import { PLUGINS_TOKEN, StorePlugin } from './plugin';
import { StorePluginManager } from './plugin-manager';

@NgModule()
export class ThyRootStoreModule {
constructor(ngModuleRef: NgModuleRef<any>) {
constructor(ngModuleRef: NgModuleRef<any>, private storePluginManager: StorePluginManager) {
setInjector(ngModuleRef.injector);
ngModuleRef.onDestroy(clearInjector);
}
}

@NgModule()
export class ThyFeatureStoreModule {}
export class ThyFeatureStoreModule {
constructor(private storePluginManager: StorePluginManager) {}
}

@NgModule({})
export class ThyStoreModule {
static forRoot(stores: Type<Store>[] = []): ModuleWithProviders<ThyRootStoreModule> {
static forRoot(
stores: Type<Store>[] = [],
options?: {
plugins: Type<StorePlugin>[];
}
): ModuleWithProviders<ThyRootStoreModule> {
const pluginProviders = (options?.plugins || []).map((PluginClass) => {
return {
provide: PLUGINS_TOKEN,
useClass: PluginClass,
multi: true
};
});
return {
ngModule: ThyRootStoreModule,
providers: [
...stores,
{
provide: ROOT_STATE_TOKEN,
provide: ROOT_STORES_TOKEN,
useValue: stores
}
},
...pluginProviders,
StorePluginManager
]
};
}
Expand All @@ -35,10 +53,11 @@ export class ThyStoreModule {
providers: [
...stores,
{
provide: FEATURE_STATE_TOKEN,
provide: FEATURE_STORES_TOKEN,
multi: true,
useValue: stores
}
},
StorePluginManager
]
};
}
Expand Down
41 changes: 41 additions & 0 deletions packages/store/src/plugin-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Inject, Injectable, Optional, SkipSelf, Type } from '@angular/core';
import { PLUGINS_TOKEN, StorePlugin, StorePluginFn } from './plugin';

@Injectable()
export class StorePluginManager {
private static pluginManager: StorePluginManager;

private pluginHandlers: StorePluginFn[] = [];

static get instance() {
return this.pluginManager;
}

get rootPluginHandlers(): StorePluginFn[] {
return (this.parentManager && this.parentManager.pluginHandlers) || this.pluginHandlers;
}

constructor(
@Optional()
@SkipSelf()
private parentManager: StorePluginManager,
@Optional()
@Inject(PLUGINS_TOKEN)
private plugins: StorePlugin[]
) {
StorePluginManager.pluginManager = this.parentManager || this;
if (!this.parentManager) {
this.registerPlugins();
}
}

private registerPlugins(): void {
const pluginHandlers: StorePluginFn[] = this.getPluginHandlers();
this.rootPluginHandlers.push(...pluginHandlers);
}

private getPluginHandlers(): StorePluginFn[] {
const plugins: StorePlugin[] = this.plugins || [];
return plugins.map((plugin: StorePlugin) => (plugin.handle ? plugin.handle.bind(plugin) : plugin) as StorePluginFn);
}
}
37 changes: 34 additions & 3 deletions packages/store/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
export interface PluginContext {
store: string;
import { Store } from './store';
import { Observable } from 'rxjs';
import { InjectionToken } from '@angular/core';

export interface PluginContext<TState = unknown> {
/**
* 调用此 Action 的 Store
*/
store: Store<TState>;
/**
* Action 名称
*/
action: string;
state: unknown;
/**
* 当前 Store 调用此 Action 之前的状态
*/
state: TState;
/**
* 获取当前 Store 的状态
*/
getState: () => TState;
/**
* 获取当前所有 Store 实例的状态
*/
getAllState: () => Record<string, TState>;
}

export type StorePluginNextFn<T> = (ctx: PluginContext) => Observable<T>;

export type StorePluginFn<T = unknown> = (ctx: PluginContext, next: StorePluginNextFn<T>) => Observable<T>;

export interface StorePlugin {
handle<T>(ctx: PluginContext, next: StorePluginNextFn<T>): Observable<T>;
}

export const PLUGINS_TOKEN = new InjectionToken('TETHYS_STORE_PLUGINS');
Loading

0 comments on commit 3192f46

Please sign in to comment.