UNPKG

@mobx-sentinel/core

Version:

A TypeScript library for non-intrusive model enhancement in MobX applications. Provides model change detection, validation, and form integration capabilities while maintaining the purity of domain models.

426 lines (418 loc) 16.4 kB
import { IEqualsComparer } from 'mobx'; declare namespace Decorator202112 { type ClassDecorator<TFunction extends Function> = (target: TFunction) => TFunction | void; type PropertyDecorator<TObject extends Object> = (target: TObject, propertyKey: string | symbol) => void; type MethodDecorator<TObject extends Object, T> = (target: TObject, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void; type ParameterDecorator<TObject extends Object> = (target: TObject, propertyKey: string | symbol | undefined, parameterIndex: number) => void; } declare namespace Decorator202203 { type ClassDecorator<Value extends abstract new (...args: any) => any = abstract new (...args: any) => any> = (value: Value, context: ClassDecoratorContext<Value>) => Value | void; type ClassAccessorDecorator<This = any, Value = any> = (value: ClassAccessorDecoratorTarget<This, Value>, context: ClassAccessorDecoratorContext) => ClassAccessorDecoratorResult<This, Value> | void; type ClassGetterDecorator<This = any, Value = any> = (value: (this: This) => Value, context: ClassGetterDecoratorContext) => ((this: This) => Value) | void; type ClassSetterDecorator<This = any, Value = any> = (value: (this: This, value: Value) => void, context: ClassSetterDecoratorContext) => ((this: This, value: Value) => void) | void; type ClassMethodDecorator<This = any, Value extends (...p: any[]) => any = any> = (value: Value, context: ClassMethodDecoratorContext<This, Value>) => Value | void; type ClassFieldDecorator<This = any, Value extends (...p: any[]) => any = any> = (value: undefined, context: ClassFieldDecoratorContext<This, Value>) => Value | void; } /** Key paths */ type KeyPath = KeyPath.Component | KeyPath.Self; declare namespace KeyPath { /** Branded type for key path components */ type Component = string & { __brand: "KeyPath.Component"; }; /** Branded type for a self-referencing key path */ type Self = symbol & { __brand: "KeyPath.Self"; }; /** Self path symbol */ const Self: Self; /** * Whether a key path is a self path * * Empty strings are also considered self paths. */ function isSelf(keyPath: KeyPath): keyPath is Self; /** Build a key path from an array of keys */ function build(...keys: (KeyPath | string | number | null)[]): KeyPath; /** * Get the relative key path from a prefix key path. * * @returns The relative key path or null if the key path is not a child of the prefix key path. */ function getRelative(keyPath: KeyPath, prefixKeyPath: KeyPath): KeyPath | null; /** * Get the parent key of a key path * * @returns The parent key or a self path if the key path is a self path */ function getParentKey(keyPath: KeyPath): KeyPath; /** * Iterate over all ancestors of a key path * * @returns The ancestors of the key path */ function getAncestors(keyPath: KeyPath, includeSelf?: boolean): Generator<KeyPath>; } /** * Read-only interface for {@link KeyPathMultiMap}. */ interface ReadonlyKeyPathMultiMap<T> extends Iterable<[KeyPath, T]> { /** The number of key paths */ readonly size: number; /** Whether the map contains a key path */ has(keyPath: KeyPath): boolean; /** Find exact matches for a key path */ findExact(keyPath: KeyPath): Generator<T>; /** Find prefix matches for a key path */ findPrefix(keyPath: KeyPath): Generator<T>; /** Get values for a key path */ get(keyPath: KeyPath, prefixMatch?: boolean): Set<T>; } /** * Annotation for nested objects */ declare const nested: Decorator202112.PropertyDecorator<object> & Decorator202203.ClassGetterDecorator<object, any> & Decorator202203.ClassAccessorDecorator<object, any> & Decorator202203.ClassFieldDecorator<object, any> & { /** * Annotation for nested objects that should be hoisted to the parent object * * Properties annotated with this will be added to the parent object * instead of being nested under their own key. * * @example `@nested.hoist private list: Sample[] = [];` * Key path for "list.0.value" becomes "0.value". */ hoist: Decorator202112.PropertyDecorator<object> & Decorator202203.ClassGetterDecorator<object, any> & Decorator202203.ClassAccessorDecorator<object, any> & Decorator202203.ClassFieldDecorator<object, any>; }; /** * A fetcher that returns all nested entries * * Annotation keys that are symbols are ignored. */ declare class StandardNestedFetcher<T extends object> implements Iterable<StandardNestedFetcher.Entry<T>> { #private; /** * @param target - The target object * @param transform - A function that transforms the entry to the desired type.\ * If the function returns `null`, the entry is ignored. */ constructor(target: object, transform: (entry: StandardNestedFetcher.Entry<any>) => T | null); /** Iterate over all entries */ [Symbol.iterator](): Generator<StandardNestedFetcher.Entry<T>, void, unknown>; /** Iterate over all entries for the given key path */ getForKey(keyPath: KeyPath): Generator<StandardNestedFetcher.Entry<T>, void, unknown>; /** Map of key paths to data */ get dataMap(): ReadonlyMap<KeyPath, T>; } declare namespace StandardNestedFetcher { /** Nested entry */ type Entry<T extends object> = { /** Name of the field that has the nested annotation */ readonly key: KeyPath; /** Key path to the nested entry */ readonly keyPath: KeyPath; /** Data */ readonly data: T; }; } declare const createUnwatch: Decorator202112.PropertyDecorator<object> & Decorator202203.ClassGetterDecorator<object, any> & Decorator202203.ClassAccessorDecorator<object, any> & Decorator202203.ClassFieldDecorator<object, any>; declare function runInUnwatch(action: () => void): void; /** * Annotation for watching changes to a property and a getter * * `@watch` by default unwraps boxed observables, arrays, sets, and maps — meaning it uses shallow comparison. * If you don't want this behavior, use `@watch.ref` instead. * * - `@observable` and `@computed` (and their variants) are automatically assumed to be `@watched`,\ * unless `@unwatch` or `@unwatch.ref` is specified. * - `@nested` (and its variants) are considered `@watched` unless `@unwatch` is specified. * - If `@watch` and `@watch.ref` are specified for the same key (in the same inheritance chain),\ * the last annotation prevails. */ declare const watch: Decorator202112.PropertyDecorator<object> & Decorator202203.ClassGetterDecorator<object, any> & Decorator202203.ClassAccessorDecorator<object, any> & Decorator202203.ClassFieldDecorator<object, any> & { /** * Annotation for watching values with identity comparison * * It has no effect when combined with `@nested`. */ ref: Decorator202112.PropertyDecorator<object> & Decorator202203.ClassGetterDecorator<object, any> & Decorator202203.ClassAccessorDecorator<object, any> & Decorator202203.ClassFieldDecorator<object, any>; }; /** * Annotation for unwatching changes to a property and a getter * * When used as an annotation: * - Combine with `@observable`, `@computed` or `@nested` (and their variants) to stop watching changes. * - You cannot re-enable watching once `@unwatch` is specified. * * When used as a function: * - Runs a piece of code without changes being detected by Watcher. * ```typescript * unwatch(() => (model.field = "value")); * ``` * - Warning: When used inside a transaction, it only becomes 'watching' when the outermost transaction completes. * ```typescript * runInAction(() => { * unwatch(() => { * // isWatching === false * }); * // isWatching === FALSE <-- already in a transaction * unwatch(() => { * // isWatching === false * }); * }); * // isWatching === TRUE * ``` */ declare const unwatch: typeof runInUnwatch & typeof createUnwatch; declare const internalToken: unique symbol; /** Watcher for changes to a target object */ declare class Watcher { #private; readonly id: string; /** * Get a watcher for a target object * * @throws TypeError when the target is not an object. */ static get<T extends object>(target: T): Watcher; /** * Get a watcher for a target object * * Same as {@link Watcher.get} but returns null instead of throwing an error. */ static getSafe(target: any): Watcher | null; /** Whether Watcher is enabled in the current transaction */ static get isWatching(): boolean; private constructor(); /** * The total number of changes have been processed * * The value is incremented for each change and each key. * Observe this value to react to changes. */ get changedTick(): bigint; /** Whether changes have been made */ get changed(): boolean; /** * The keys that have changed * * Keys of nested objects are NOT included. */ get changedKeys(): ReadonlySet<KeyPath>; /** * The key paths that have changed * * Keys of nested objects are included. */ get changedKeyPaths(): ReadonlySet<KeyPath>; /** Nested watchers */ get nested(): ReadonlyMap<KeyPath, Watcher>; /** Reset the changed keys */ reset(): void; /** * Assume some changes have been made * * It only changes {@link changed} to true and does not increment {@link changedTick}. */ assumeChanged(): void; /** @internal @ignore */ [internalToken](): { didChange: (key: KeyPath) => void; }; } declare class ValidationError extends Error { readonly key: KeyPath; readonly keyPath: KeyPath; readonly message: string; readonly cause?: Error; constructor(args: { keyPath: KeyPath; reason: string | Error; }); } declare class ValidationErrorMapBuilder<T> { #private; /** @internal @ignore */ static build(instance: ValidationErrorMapBuilder<any>): ReadonlyKeyPathMultiMap<ValidationError>; /** * Invalidate the key * * Multiple reasons can be added for the same key. */ invalidate(key: keyof T & string, reason: string | Error): void; /** * Invalidate the target object itself * * This is useful with `@nested.hoist` to invalidate the parent object. */ invalidateSelf(reason: string | Error): void; /** Whether the builder has any errors */ get hasError(): boolean; } /** * Make a target object validatable * * It's just a shorthand of: * ```typescript * Validator.get(target).addAsyncHandler(expr, handler, opt) * ``` * * @remarks If you're using `make(Auto)Observable`, make sure to call `makeValidatable` * after `make(Auto)Observable`. * * @param target The target object * @param expr The expression to observe * @param handler The async handler to call when the expression changes * @param opt The handler options * * @returns A function to remove the handler */ declare function makeValidatable<T extends object, Expr>(target: T, expr: () => Expr, handler: Validator.AsyncHandler<T, NoInfer<Expr>>, opt?: Validator.HandlerOptions<NoInfer<Expr>>): () => void; /** * Make a target object validatable * * It's just a shorthand of: * ```typescript * Validator.get(target).addSyncHandler(handler, opt) * ``` * * @remarks If you're using `make(Auto)Observable`, make sure to call `makeValidatable` * after `make(Auto)Observable`. * * @param target The target object * @param handler The sync handler containing observable expressions * @param opt The handler options * * @returns A function to remove the handler */ declare function makeValidatable<T extends object>(target: T, handler: Validator.SyncHandler<T>, opt?: Validator.HandlerOptions): () => void; declare class Validator<T> { #private; static defaultDelayMs: number; readonly id: string; /** * Get a validator for a target object * * @throws TypeError when the target is not an object. */ static get<T extends object>(target: T): Validator<T>; /** * Get a validator for a target object * * Same as {@link Validator.get} but returns null instead of throwing an error. */ static getSafe<T>(target: T): Validator<T> | null; private constructor(); /** Whether no errors are found */ get isValid(): boolean; /** The number of invalid keys */ get invalidKeyCount(): number; /** * The keys that have errors * * Keys of nested objects are NOT included. */ get invalidKeys(): ReadonlySet<KeyPath>; /** The number of invalid key paths */ get invalidKeyPathCount(): number; /** * The key paths that have errors * * Keys of nested objects are included. */ get invalidKeyPaths(): ReadonlySet<KeyPath>; /** Get the first error message (including nested objects) */ get firstErrorMessage(): string | null; /** Get error messages for the key path */ getErrorMessages(keyPath: KeyPath, prefixMatch?: boolean): Set<string>; /** Check if the validator has errors for the key path */ hasErrors(keyPath: KeyPath, prefixMatch?: boolean): boolean; /** Find errors for the key path */ findErrors(searchKeyPath: KeyPath, prefixMatch?: boolean): Generator<[keyPath: KeyPath, error: ValidationError], void, unknown>; /** * The number of pending/running reactions. */ get reactionState(): number; /** * The number of pending/running async jobs. */ get asyncState(): number; /** Whether the validator is computing errors */ get isValidating(): boolean; /** Nested validators */ get nested(): ReadonlyMap<KeyPath, Validator<any>>; /** * Reset the validator * * Use with caution.\ * Since validation is reactive, errors won't reappear until you make some changes. */ reset(): void; /** * Update the errors immediately * * @returns A function to remove the errors */ updateErrors(key: symbol, handler: Validator.InstantHandler<T>): () => void; /** * Add a sync handler * * @param handler The sync handler containing observable expressions * * @returns A function to remove the handler */ addSyncHandler(handler: Validator.SyncHandler<T>, opt?: Validator.HandlerOptions): () => void; /** * Add an async handler * * @param expr The expression to observe * @param handler The async handler to call when the expression changes * @param opt The handler options * * @returns A function to remove the handler */ addAsyncHandler<Expr>(expr: () => Expr, handler: Validator.AsyncHandler<T, NoInfer<Expr>>, opt?: Validator.HandlerOptions<NoInfer<Expr>>): () => void; } declare namespace Validator { /** * Async handler * * @param expr The expression observed * @param builder The builder to build the errors * @param abortSignal The abort signal */ type AsyncHandler<T, Expr> = (expr: Expr, builder: ValidationErrorMapBuilder<T>, abortSignal: AbortSignal) => Promise<void>; /** * Sync handler * * @param builder The builder to build the errors */ type SyncHandler<T> = (builder: ValidationErrorMapBuilder<T>) => void; /** * Instant handler * * @param builder The builder to build the errors */ type InstantHandler<T> = (builder: ValidationErrorMapBuilder<T>) => void; /** Handler options */ type HandlerOptions<Expr = unknown> = { /** * Whether to run the handler immediately * * @default true */ initialRun?: boolean; /** * Throttle reaction. [milliseconds] * * @default 100 */ delayMs?: number; /** * The equality comparer for the expression. * * Only effective for async handlers. */ equals?: IEqualsComparer<Expr>; }; } export { KeyPath, StandardNestedFetcher, ValidationError, ValidationErrorMapBuilder, Validator, Watcher, makeValidatable, nested, unwatch, watch };