import type { WatchCallback, WatchOptions } from '@vue/reactivity' import type { ClassMethodDecorator } from './decorator-fills' import { createDecoratorTypeChecker } from './adm' import { createDecorator, type ReactiveDecorator } from './decorator' import { getEffectScope } from './effectScope' import { isObservable } from './observable' import { watch } from './reactivity' const WATCH = 'watch' export const isWatch = createDecoratorTypeChecker( (decorator: ReactiveDecorator) => decorator.decoratorType === WATCH, ) type WatchSourceFunction = (this: TTarget) => TValue type WatchSourceProperty = TKey type DecoratorWatchSource = | WatchSourceFunction | WatchSourceProperty interface CreateWatchOptions { source: DecoratorWatchSource watchOptions?: WatchOptions } interface EffectDescriptor extends PropertyDescriptor { value?: WatchCallback } function make< TTarget extends object, TKey extends keyof TTarget, TValue, >(target: TTarget, key: PropertyKey, descriptor: EffectDescriptor, options: CreateWatchOptions) { if ( __DEV__ && (!('value' in descriptor) || typeof descriptor.value !== 'function') ) { throw new Error(`@watch can only be used with methods.`) } const effectScope = getEffectScope(target) const { watchOptions } = options const source = typeof options.source === 'function' ? options.source.bind(target) : options.source if (!source) { throw new Error(`@watch source is not defined.`) } const callback = descriptor.value?.bind(target) if (callback === undefined) { throw new Error(`@watch callback is not defined.`) } if (typeof source == 'function') { effectScope.run(() => { watch(source, callback, watchOptions) }) } else if (typeof source == 'string' || typeof source == 'symbol' || typeof source == 'number') { if (!(source in target)) { throw new Error(`@watch source '${source.toString()}' is not defined in the target.`) } if (!isObservable(target, source)) { throw new Error(`@watch source '${source.toString()}' is not observable.`) } effectScope.run(() => { watch(() => target[source], callback, watchOptions) }) } } function createWatchDecorator< TTarget extends object, TKey extends keyof TTarget, TValue, >(name: string, options: CreateWatchOptions): ReactiveDecorator { return { decoratorType: name, make: (target: TTarget, key: PropertyKey, descriptor: EffectDescriptor) => make(target, key, descriptor, options), options, } } export function Watch< TTarget extends object, TKey extends keyof TTarget, TValue = TTarget[TKey], TCallback extends WatchCallback = WatchCallback, >(source: TKey, options?: WatchOptions): ClassMethodDecorator export function Watch< TTarget extends object, TValue, TCallback extends WatchCallback = WatchCallback, >(source: ((this: TTarget) => TValue), options?: WatchOptions): ClassMethodDecorator export function Watch< TTarget extends object, TKey extends keyof TTarget, TValue = TTarget[TKey], TCallback extends WatchCallback = WatchCallback, >(source: TKey | ((this: TTarget) => TValue), options?: WatchOptions): ClassMethodDecorator { return createDecorator>( createWatchDecorator( WATCH, { source, watchOptions: options }, ), ) }