Skip to content

Commit

Permalink
feat(core): added effect scope to store
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewcourtice committed Aug 4, 2021
1 parent 2b02242 commit 9902107
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 28 deletions.
4 changes: 2 additions & 2 deletions core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
"prepublish": "yarn build"
},
"peerDependencies": {
"vue": "^3.0.0"
"vue": "^3.2.0-beta.7"
},
"devDependencies": {
"vue": "^3.0.0"
"vue": "^3.2.0-beta.7"
}
}
3 changes: 3 additions & 0 deletions core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const once = eventEmitter.once.bind(eventEmitter);
export function createStore<TState extends BaseState, TExtensions extends Extension<TState>[]>(name: string, state: TState, options?: Partial<StoreOptions<TState, TExtensions>>): Store<TState> & ExtendedStore<TExtensions> {
const {
allowOverwrite,
providers,
extensions,
} = {
allowOverwrite: true,
Expand All @@ -118,11 +119,13 @@ export function createStore<TState extends BaseState, TExtensions extends Extens

const store = new InternalStore(name, state, {
allowOverwrite,
providers,
});

const destroy = () => {
store.emit(EVENTS.store.destroyed, SENDER, state);
stores.delete(name);
store.destroy();
};

const getMutationHook = (eventName: string) => {
Expand Down
91 changes: 69 additions & 22 deletions core/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
reactive,
readonly,
computed,
effectScope,
ComputedRef,
EffectScope,
} from 'vue';

import {
Expand All @@ -31,6 +33,8 @@ import type {
WriteState,
StoreProvider,
StoreProviders,
StoreRegistrations,
RegistrationValueProducer,
} from './types';

function localiseHandler(name: string, handler: EventHandler): EventHandler {
Expand All @@ -43,42 +47,55 @@ function localiseHandler(name: string, handler: EventHandler): EventHandler {

export default class Store<TState extends BaseState = any> implements InternalStore<TState> {

private options: InternalStoreOptions;
private options: InternalStoreOptions<TState>;
private scope: EffectScope;
private stack: Set<string>;
private readState: ReadState<TState>;
private writeState: WriteState<TState>;
private providers: StoreProviders<TState>;

public name: string;
public getters: Map<string, () => unknown>;
public mutations: Map<string, Mutation<any>>;
public registrations: StoreRegistrations;

constructor(name: string, state: TState, options?: Partial<InternalStoreOptions>) {
constructor(name: string, state: TState, options?: Partial<InternalStoreOptions<TState>>) {
this.options = {
allowOverwrite: true,
...options,

providers: {
read: value => value,
write: value => value,
payload: value => value,
...options?.providers,
},
};

this.name = name;
this.stack = new Set();
this.scope = effectScope();
this.writeState = reactive(state) as WriteState<TState>;
this.readState = readonly(this.writeState) as ReadState<TState>;

this.providers = {
read: value => value,
write: value => value,
this.registrations = {
getters: new Map(),
mutations: new Map(),
};

this.name = name;
this.getters = new Map();
this.mutations = new Map();
}

public get allowsOverwrite(): boolean {
return this.options.allowOverwrite;
}

public get providers(): StoreProviders<TState> {
return {
read: value => value,
write: value => value,
payload: value => value,
...this.options.providers,
};
}

public get state(): ReadState<TState> {
return this.providers.read(this.readState);
return this.providers.read(this.readState) ?? this.readState;
}

public emit(event: string, sender: string, data: any): void {
Expand All @@ -100,17 +117,41 @@ export default class Store<TState extends BaseState = any> implements InternalSt
}

public provider<TKey extends StoreProvider<TState>>(key: TKey, value: StoreProviders<TState>[TKey]): void {
this.providers[key] = value;
this.options.providers[key] = value;
}

public track<TResult>(callback: () => TResult): TResult {
return this.scope.run(callback)!;
}

public hasRegistration(type: string, name: string): boolean {
return !!this.registrations[type]?.has(name);
}

public getRegistration(type: string, name: string): RegistrationValueProducer | undefined {
return this.registrations[type]?.get(name);
}

public register(type: string, name: string, valueProducer: RegistrationValueProducer): void {
if (!(type in this.registrations)) {
this.registrations[type] = new Map();
}

this.registrations[type].set(name, valueProducer);
}

public unregister(type: string, name: string): void {
this.registrations[type]?.delete(name);
}

public getter<TResult>(name: string, getter: Getter<TState, TResult>): ComputedRef<TResult> {
if (!this.allowsOverwrite && this.getters.has(name)) {
if (!this.allowsOverwrite && this.hasRegistration('getters', name)) {
raiseOverwriteError('getter', name);
}

const output = computed(() => getter(this.state));
const output = this.track(() => computed(() => getter(this.state)));

this.getters.set(name, () => output.value);
this.register('getters', name, () => output.value);

return output;
}
Expand All @@ -131,8 +172,10 @@ export default class Store<TState extends BaseState = any> implements InternalSt
this.emit(EVENTS.mutation.before, sender, eventData);

try {
const state = this.providers.write(this.writeState);
result = mutator(state, payload);
const _state = this.providers.write(this.writeState) ?? this.writeState;
const _payload = this.providers.payload(payload) ?? payload;

result = mutator(_state, _payload);
} catch (error) {
this.emit(EVENTS.mutation.error, sender, eventData);
throw error;
Expand All @@ -149,21 +192,21 @@ export default class Store<TState extends BaseState = any> implements InternalSt
}

public mutation<TPayload, TResult = void>(name: string, mutator: Mutator<TState, TPayload, TResult>): Mutation<TPayload, TResult> {
if (!this.allowsOverwrite && this.mutations.has(name)) {
if (!this.allowsOverwrite && this.hasRegistration('mutations', name)) {
raiseOverwriteError('mutation', name);
}

const mutation = ((payload: TPayload) => {
return this.mutate(name, SENDER, mutator, payload);
}) as Mutation<TPayload, TResult>;

this.mutations.set(name, mutation);
this.register('mutations', name, () => mutation);

return mutation;
}

public exec<TResult = void>(name: string, payload?: any): TResult {
const mutation = this.mutations.get(name) as Mutation<any, TResult>;
const mutation = this.getRegistration('mutations', name) as Mutation<any, TResult>;

if (!mutation) {
throw new Error(`No mutation found for ${name}`);
Expand All @@ -176,4 +219,8 @@ export default class Store<TState extends BaseState = any> implements InternalSt
return this.mutate(name, sender, mutator, undefined);
}

public destroy(): void {
this.scope.stop();
}

}
24 changes: 20 additions & 4 deletions core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,35 @@ export interface StoreBase<TState extends BaseState> {
export interface StoreProviders<TState extends BaseState> {
read(state: ReadState<TState>): ReadState<TState>;
write(state: WriteState<TState>): WriteState<TState>;
payload<TPayload>(payload: TPayload): TPayload;
}

export interface InternalStore<TState extends BaseState = any> extends StoreBase<TState> {
readonly allowsOverwrite: boolean;
readonly providers: StoreProviders<TState>;
readonly state: ReadState<TState>;
name: string;
getters: Map<string, () => unknown>;
mutations: Map<string, Mutator<TState, any, any>>;
registrations: StoreRegistrations;
hasRegistration(type: string, name: string): boolean;
getRegistration(type: string, name: string): RegistrationValueProducer | undefined;
register(type: string, name: string, valueProducer: RegistrationValueProducer): void;
unregister(type: string, name: string): void;
emit(event: string, sender: string, data: any): void;
on(event: string, handler: EventHandler): EventListener;
once(event: string, handler: EventHandler): EventListener
exec<TResult = void>(name: string, payload?: any): TResult;
track<TResult>(callback: () => TResult): TResult;
provider<TKey extends StoreProvider<TState>>(key: TKey, value: StoreProviders<TState>[TKey]): void;
write<TResult = void>(name: string, sender: string, mutator: Mutator<TState, undefined, TResult>): TResult;
destroy(): void;
}

export interface InternalStoreOptions {
export interface InternalStoreOptions<TState extends BaseState> {
allowOverwrite: boolean;
providers: Partial<StoreProviders<TState>>;
}

export interface StoreOptions<TState extends BaseState, TExtensions extends Extension<TState>[]> extends InternalStoreOptions {
export interface StoreOptions<TState extends BaseState, TExtensions extends Extension<TState>[]> extends InternalStoreOptions<TState> {
extensions?: TExtensions;
}

Expand All @@ -92,3 +100,11 @@ export interface HarlemPlugin {
export interface PluginOptions {
plugins?: HarlemPlugin[];
}

export type RegistrationValueProducer = () => unknown;

export interface StoreRegistrations {
[key: string]: Map<string, RegistrationValueProducer>;
getters: Map<string, RegistrationValueProducer>;
mutations: Map<string, RegistrationValueProducer>;
}

0 comments on commit 9902107

Please sign in to comment.