@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
text/typescript
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 };