import { IEqualsComparer, IComputedValueOptions, observable, computed, AnnotationMapEntry, IAtom } from 'mobx'; import { AnyObject, Maybe } from 'yummies/types'; /** * ---header-docs-section--- * # yummies/mobx * * ## Description * * **`annotation`** — factories for `makeObservable` maps and {@link applyObservable} tuples: * `observable.*` flavours, `computed` with shorthand `equals` (`struct`, `shallow`, reference), custom * comparators, and `false` to skip a field. * * ## Usage * * ```ts * import { annotation } from "yummies/mobx"; * ``` */ /** * How MobX should compare the previous and next computed value before notifying observers. * * - `'struct'` — structural comparison (`comparer.structural`). * - `'shallow'` — shallow comparison (`comparer.shallow`). * - `true` — reference equality (`comparer.default`). * - `false` — skip the annotation (handled by the `annotation.computed` / `annotation.observable` helpers). * - A custom `(a, b) => boolean` is allowed by the type for parity with `IComputedValueOptions.equals`. */ type ComputedEqualsValue = 'struct' | 'shallow' | boolean | IEqualsComparer; /** * Options forwarded to {@link computed}, except `equals` (that argument is passed separately). */ type ComputedOtherOptions = Omit, 'equals'>; /** * Observable flavour keys returned by {@link annotation.observable}: `ref`, `deep`, `shallow`, `struct`. * Also supported: `true` (base `observable`) and `false` (no annotation). */ type ObservableTypes = keyof Pick | boolean; /** * MobX annotation factories for `makeObservable` and tuple-style wiring ({@link applyObservable}). * * - **`annotation.observable(value?)`** — `observable.ref` / `deep` / `shallow` / `struct`; `true` or * omitted → base `observable` (deep by default); `false` → `false` (field omitted from the map). * - **`annotation.computed(value?, options?)`** — `computed({ ...options, equals })` with `equals` from * {@link ComputedEqualsValue} or a custom `(a, b) => boolean`. Omitted `value`, unknown literals, and * `true` resolve to `comparer.default`; `value === false` returns `false`. * * Other `computed` options (except `equals`) go in the second argument; see {@link ComputedOtherOptions}. * * @example Observable variants * ```ts * import { makeObservable } from 'mobx'; * import { annotation } from 'yummies/mobx'; * * class Store { * shallowMap = new Map(); * deep = { nested: { count: 0 } }; * * constructor() { * makeObservable(this, { * shallowMap: annotation.observable('shallow'), * deep: annotation.observable('deep'), * }); * } * } * ``` * * @example Skip a field * ```ts * makeObservable(this, { * plain: annotation.observable(false), // not decorated * }); * ``` * * @example Computed with structural equality * ```ts * makeObservable(this, { * fullName: annotation.computed('struct', { name: 'fullName' }), * }); * ``` * * @example Computed with default reference equality * ```ts * makeObservable(this, { * total: annotation.computed(true), * }); * ``` * * @example Omitted first argument or `true` — reference equality (`comparer.default`) * ```ts * makeObservable(this, { * n: annotation.computed(undefined, { name: 'n' }), * m: annotation.computed(true, { name: 'm' }), // same `equals` as omitted * }); * ``` * * @example Custom `equals` * ```ts * makeObservable(this, { * row: annotation.computed((a, b) => a.id === b.id), * }); * ``` * * @example With {@link applyObservable} * ```ts * applyObservable(store, [ * [annotation.observable('shallow'), 'cache', 'index'], * [annotation.computed('struct'), 'viewModel'], * ]); * ``` * * @example `observable.ref` and skipping `computed` * ```ts * makeObservable(this, { * handle: annotation.observable('ref'), * skipMe: annotation.computed(false), * }); * ``` */ type AnnotationObject = { computed(value: false): false; computed(value?: Exclude, options?: ComputedOtherOptions): typeof computed.struct; computed(value?: ComputedEqualsValue): false | typeof computed.struct; observable(value: Extract): false; observable(value?: Extract): typeof observable; observable>(value: TValue): (typeof observable)[TValue]; observable(value?: ObservableTypes): false | (typeof observable)[Exclude]; }; declare const annotation: AnnotationObject; /** * ---header-docs-section--- * # yummies/mobx * * ## Description * * Compact **MobX `makeObservable`** wiring from tuple lists of annotations and keys. Reduces boilerplate * when many fields share `observable`, `action`, or `computed` decorators and you want one call site * instead of sprawling annotation maps across large stores. * * ## Usage * * ```ts * import { applyObservable } from "yummies/mobx"; * ``` */ type ObservableAnnotationsArray = [ AnnotationMapEntry, ...(keyof T | (string & {}))[] ][]; /** * Applies a compact list of MobX annotations to an object using either * decorator-style invocation or the annotation map form accepted by `makeObservable`. * * @template T Target object type. * @param context Object that should become observable. * @param annotationsArray Tuples of annotation followed by annotated field names. * @param useDecorators Enables decorator-style application before calling `makeObservable`. * * @example * ```ts * applyObservable(store, [[observable, 'items'], [action, 'setItems']]); * ``` * * @example * ```ts * applyObservable(viewModel, [[computed, 'fullName']], true); * ``` */ declare const applyObservable: (context: T, annotationsArray: ObservableAnnotationsArray, useDecorators?: boolean) => void; /** * ---header-docs-section--- * # yummies/mobx * * ## Description * * **`createAtom` wrapper** that attaches arbitrary metadata and keeps MobX’s observed/unobserved * hooks in one place. Useful for custom reactive primitives, async resources, or debugging atoms * where the stock API is too bare. * * ## Usage * * ```ts * import { createEnhancedAtom } from "yummies/mobx"; * ``` */ interface IEnhancedAtom extends IAtom { meta: TMeta; } /** * Creates a MobX atom extended with metadata and bound reporting methods. * * @template TMeta Metadata object stored on the atom. * @param name Atom name used by MobX for debugging. * @param onBecomeObservedHandler Callback fired when the atom becomes observed. * @param onBecomeUnobservedHandler Callback fired when the atom is no longer observed. * @param meta Optional metadata attached to the atom. * @returns Atom instance with `meta`, `reportChanged` and `reportObserved`. * * @example * ```ts * const atom = createEnhancedAtom('user-status'); * atom.reportChanged(); * ``` * * @example * ```ts * const atom = createEnhancedAtom('cache', undefined, undefined, { scope: 'users' }); * atom.meta.scope; * ``` */ declare const createEnhancedAtom: (name: string, onBecomeObservedHandler?: (atom: IEnhancedAtom) => void, onBecomeUnobservedHandler?: (atom: IEnhancedAtom) => void, meta?: TMeta) => IEnhancedAtom; /** * ---header-docs-section--- * # yummies/mobx * * ## Description * * **Observable ref** pattern for MobX: boxed mutable references with change listeners, metadata, * and optional custom equality. Bridges React-style ref holders and MobX reactivity when a single * mutable cell must notify dependents without replacing the whole parent object graph. * * ## Usage * * ```ts * import { createRef } from "yummies/mobx"; * ``` */ /** * You can return `false` if you don't want to change the value in this ref */ type RefChangeListener = (value: T | null, prevValue: T | undefined) => void | false; /** * Alternative to React.createRef but works in MobX world. * Typically it the should be the same React.LegacyRef (fn style) */ interface Ref { /** * Setter function */ (value: Maybe): void; set(value: Maybe): void; listeners: Set>>; current: NoInfer | null; meta: TMeta; } interface CreateRefConfig { onSet?: (node: T, prevValue: T | undefined) => void; onUnset?: (lastValue: T | undefined) => void; onChange?: RefChangeListener; meta?: TMeta; initial?: Maybe; comparer?: IEqualsComparer; } /** * Creates a MobX-aware ref that behaves like a callback ref and exposes * observable `current` and `meta` fields. * * @template T Referenced value type. * @template TMeta Additional observable metadata stored on the ref. * @param cfg Optional callbacks, initial value and comparer configuration. * @returns Observable ref function object. * * @example * ```ts * const inputRef = createRef(); * inputRef.set(document.createElement('input')); * ``` * * @example * ```ts * const ref = createRef(); * ref(3); * ref.current; // 3 * ``` * * @example * ```ts * const nodeRef = createRef({ * onUnset: () => console.log('detached'), * meta: { mounted: false }, * }); * ``` */ declare const createRef: (cfg?: CreateRefConfig) => Ref; /** * Checks whether the provided value is a ref created by `createRef`. * * @template T Referenced value type. * @template TMeta Ref metadata type. * @param value Value to inspect. * @returns `true` when the value is a ref-like function with `current`. * * @example * ```ts * const ref = createRef(); * isRef(ref); // true * ``` * * @example * ```ts * isRef({ current: 1 }); // false * ``` */ declare const isRef: (value: T | Ref) => value is Ref; /** * Normalizes a plain value or an existing ref into a `Ref` instance. * * @template T Referenced value type. * @template TMeta Ref metadata type. * @param value Existing ref or initial plain value. * @param cfg Optional ref configuration applied when a new ref is created. * @returns Existing ref or a newly created ref initialized with `value`. * * @example * ```ts * const ref = toRef(document.body); * ref.current === document.body; * ``` * * @example * ```ts * const existingRef = createRef(); * const sameRef = toRef(existingRef); * ``` */ declare const toRef: (value: T | Ref, cfg?: Omit, "initial">) => Ref; /** * ---header-docs-section--- * # yummies/mobx * * ## Description * * **Deep observable object** with structural `set` patches that reuse nested observables when keys * overlap. Helps store trees (forms, filters, entities) under MobX without wholesale replacement * and without manual `observable.map` wiring for every level. * * ## Usage * * ```ts * import { DeepObservableStruct } from "yummies/mobx"; * ``` */ /** * Wraps a plain object into a deeply observable structure and allows * patch-like updates while preserving nested observable references where possible. * * @template TData Observable object shape. * * @example * ```ts * const state = new DeepObservableStruct({ user: { name: 'Ann' } }); * state.set({ user: { name: 'Bob' } }); * ``` * * @example * ```ts * const state = new DeepObservableStruct({ filters: { active: true } }); * state.set({ filters: { active: false, archived: true } }); * ``` */ declare class DeepObservableStruct { data: TData; constructor(data: TData); set(newData: Partial): void; } /** * Synchronously runs MobX reactions from the internal `pendingReactions` queue when they piled up * during a batch (`inBatch > 0`, e.g. inside `runInAction`). * * While a batch is open, MobX only enqueues reactions; this call temporarily resets the batch * counter, drains the queue, and restores state—useful in tests and when you need side effects * before leaving the action. * * If there are no pending reactions, a reaction run is already in progress (`isRunningReactions`), * or the iteration cap is hit (cycle guard), there is no extra work; when the cap is exceeded the * queue is cleared, matching MobX's internal safety behavior. * * @param maxCount - Maximum iterations of the outer drain loop (default 100, same idea as MobX's internal limit). * Pass `Number.POSITIVE_INFINITY` to disable this cap only when you trust the reaction graph to settle; * a non-converging cycle will then keep looping until the queue empties (or effectively hang). * * @example * ```ts * import { observable, reaction, runInAction } from "mobx"; * import { flushPendingReactions } from "yummies/mobx"; * * const state = observable({ count: 0 }); * const log: number[] = []; * reaction(() => state.count, (n) => log.push(n)); * * runInAction(() => { * state.count = 1; * flushPendingReactions(); * }); * * // log === [1] — the reaction ran before the action finished * ``` */ declare function flushPendingReactions(maxCount?: number): void; /** * ---header-docs-section--- * # yummies/mobx * * ## Description * * Typed access to MobX **internal administration** (`$mobx`) for advanced tooling, migration scripts, * or introspection. Prefer public MobX APIs in application code; reach for this when you must align * with library internals or patch behavior at the administration layer. * * ## Usage * * ```ts * import { getMobxAdministration } from "yummies/mobx"; * ``` */ type ObservableObjectAdministration = Parameters['make_']>[0]; /** * Returns the internal MobX administration object associated with an observable target. * * @param context Observable object instance. * @returns MobX administration internals stored under `$mobx`. * * @example * ```ts * const admin = getMobxAdministration(store); * admin.name_; * ``` * * @example * ```ts * const values = getMobxAdministration(formState).values_; * ``` */ declare const getMobxAdministration: (context: AnyObject) => ObservableObjectAdministration; /** * ---header-docs-section--- * # yummies/mobx * * ## Description * * **Lazy subscriptions** tied to MobX observation: start work when the first reaction observes * tracked keys, stop when nothing listens anymore (optionally after a delay). Ideal for polling, * WebSocket feeds, or expensive caches that should idle when the UI is not mounted. * * ## Usage * * ```ts * import { lazyObserve } from "yummies/mobx"; * ``` */ /** * Starts side effects only while one or more MobX observables are being observed. * * When the first property becomes observed, `onStart` is called. When all tracked * properties become unobserved, `onEnd` is called with the value returned by * `onStart`. Cleanup can be delayed via `endDelay`. * * It uses MobX `onBecomeObserved` and `onBecomeUnobserved` hooks to perform * lazy subscription management. * * @template TMetaData Data returned from `onStart` and forwarded to `onEnd`. * @param config Configuration for tracked properties and lifecycle callbacks. * @returns Cleanup function that clears the tracked state and runs `onEnd`. * * @example * ```ts * const stop = lazyObserve({ * context: store, * property: 'items', * onStart: () => api.subscribe(), * onEnd: (subscription) => subscription.unsubscribe(), * }); * ``` * * @example * ```ts * lazyObserve({ * property: [boxA, boxB], * onStart: () => console.log('observed'), * endDelay: 300, * }); * ``` */ declare const lazyObserve: ({ context, property, onStart, onEnd, endDelay, }: { context?: any; property: any | any[]; onStart?: () => TMetaData; onEnd?: (metaData: TMetaData, cleanupFn: VoidFunction) => void; endDelay?: number | false; }) => () => void; export { DeepObservableStruct, annotation, applyObservable, createEnhancedAtom, createRef, flushPendingReactions, getMobxAdministration, isRef, lazyObserve, toRef }; export type { ComputedEqualsValue, ComputedOtherOptions, CreateRefConfig, IEnhancedAtom, ObservableAnnotationsArray, ObservableTypes, Ref, RefChangeListener };