diff --git a/core/package.json b/core/package.json index 0d03a561..33e4a03b 100644 --- a/core/package.json +++ b/core/package.json @@ -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" } } diff --git a/core/src/index.ts b/core/src/index.ts index 232d6004..d8562afa 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -107,6 +107,7 @@ export const once = eventEmitter.once.bind(eventEmitter); export function createStore[]>(name: string, state: TState, options?: Partial>): Store & ExtendedStore { const { allowOverwrite, + providers, extensions, } = { allowOverwrite: true, @@ -118,11 +119,13 @@ export function createStore { store.emit(EVENTS.store.destroyed, SENDER, state); stores.delete(name); + store.destroy(); }; const getMutationHook = (eventName: string) => { diff --git a/core/src/store.ts b/core/src/store.ts index e5b22fbf..beed7bd9 100644 --- a/core/src/store.ts +++ b/core/src/store.ts @@ -9,7 +9,9 @@ import { reactive, readonly, computed, + effectScope, ComputedRef, + EffectScope, } from 'vue'; import { @@ -31,6 +33,8 @@ import type { WriteState, StoreProvider, StoreProviders, + StoreRegistrations, + RegistrationValueProducer, } from './types'; function localiseHandler(name: string, handler: EventHandler): EventHandler { @@ -43,42 +47,55 @@ function localiseHandler(name: string, handler: EventHandler): EventHandler { export default class Store implements InternalStore { - private options: InternalStoreOptions; + private options: InternalStoreOptions; + private scope: EffectScope; private stack: Set; private readState: ReadState; private writeState: WriteState; - private providers: StoreProviders; public name: string; - public getters: Map unknown>; - public mutations: Map>; + public registrations: StoreRegistrations; - constructor(name: string, state: TState, options?: Partial) { + constructor(name: string, state: TState, options?: Partial>) { 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; this.readState = readonly(this.writeState) as ReadState; - 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 { + return { + read: value => value, + write: value => value, + payload: value => value, + ...this.options.providers, + }; + } + public get state(): ReadState { - return this.providers.read(this.readState); + return this.providers.read(this.readState) ?? this.readState; } public emit(event: string, sender: string, data: any): void { @@ -100,17 +117,41 @@ export default class Store implements InternalSt } public provider>(key: TKey, value: StoreProviders[TKey]): void { - this.providers[key] = value; + this.options.providers[key] = value; + } + + public track(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(name: string, getter: Getter): ComputedRef { - 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; } @@ -131,8 +172,10 @@ export default class Store 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; @@ -149,7 +192,7 @@ export default class Store implements InternalSt } public mutation(name: string, mutator: Mutator): Mutation { - if (!this.allowsOverwrite && this.mutations.has(name)) { + if (!this.allowsOverwrite && this.hasRegistration('mutations', name)) { raiseOverwriteError('mutation', name); } @@ -157,13 +200,13 @@ export default class Store implements InternalSt return this.mutate(name, SENDER, mutator, payload); }) as Mutation; - this.mutations.set(name, mutation); + this.register('mutations', name, () => mutation); return mutation; } public exec(name: string, payload?: any): TResult { - const mutation = this.mutations.get(name) as Mutation; + const mutation = this.getRegistration('mutations', name) as Mutation; if (!mutation) { throw new Error(`No mutation found for ${name}`); @@ -176,4 +219,8 @@ export default class Store implements InternalSt return this.mutate(name, sender, mutator, undefined); } + public destroy(): void { + this.scope.stop(); + } + } \ No newline at end of file diff --git a/core/src/types.ts b/core/src/types.ts index d0d7e62f..ce65e11d 100644 --- a/core/src/types.ts +++ b/core/src/types.ts @@ -50,27 +50,35 @@ export interface StoreBase { export interface StoreProviders { read(state: ReadState): ReadState; write(state: WriteState): WriteState; + payload(payload: TPayload): TPayload; } export interface InternalStore extends StoreBase { readonly allowsOverwrite: boolean; + readonly providers: StoreProviders; readonly state: ReadState; name: string; - getters: Map unknown>; - mutations: Map>; + 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(name: string, payload?: any): TResult; + track(callback: () => TResult): TResult; provider>(key: TKey, value: StoreProviders[TKey]): void; write(name: string, sender: string, mutator: Mutator): TResult; + destroy(): void; } -export interface InternalStoreOptions { +export interface InternalStoreOptions { allowOverwrite: boolean; + providers: Partial>; } -export interface StoreOptions[]> extends InternalStoreOptions { +export interface StoreOptions[]> extends InternalStoreOptions { extensions?: TExtensions; } @@ -92,3 +100,11 @@ export interface HarlemPlugin { export interface PluginOptions { plugins?: HarlemPlugin[]; } + +export type RegistrationValueProducer = () => unknown; + +export interface StoreRegistrations { + [key: string]: Map; + getters: Map; + mutations: Map; +}