UNPKG

mobx-keystone

Version:

A MobX powered state management solution based on data trees with first class support for TypeScript, snapshots, patches and much more

373 lines (333 loc) 7.64 kB
import * as mobx from "mobx" import { IObservableArray, isObservableArray, isObservableMap, isObservableObject, isObservableSet, ObservableMap, ObservableSet, } from "mobx" import { JSONPrimitiveValue, PrimitiveValue } from "./types" /** * A mobx-keystone error. */ export class MobxKeystoneError extends Error { constructor(msg: string) { super(msg) // Set the prototype explicitly. Object.setPrototypeOf(this, MobxKeystoneError.prototype) } } /** * @internal */ export function failure(msg: string) { return new MobxKeystoneError(msg) } const writableHiddenPropDescriptor: PropertyDescriptor = { enumerable: false, writable: true, configurable: false, value: undefined, } /** * @internal */ export function addHiddenProp(object: any, propName: PropertyKey, value: any, writable = true) { if (writable) { Object.defineProperty(object, propName, writableHiddenPropDescriptor) object[propName] = value } else { Object.defineProperty(object, propName, { enumerable: false, writable, configurable: true, value, }) } } /** * @internal */ export function makePropReadonly<T>(object: T, propName: keyof T, enumerable: boolean) { const propDesc = Object.getOwnPropertyDescriptor(object, propName) if (propDesc) { propDesc.enumerable = enumerable if (propDesc.get) { delete propDesc.set } else { propDesc.writable = false } Object.defineProperty(object, propName, propDesc) } } /** * @internal */ export function isPlainObject(value: unknown): value is Record<PropertyKey, unknown> { if (!isObject(value)) { return false } const proto = Object.getPrototypeOf(value) return proto === Object.prototype || proto === null } /** * @internal */ export function isObject(value: unknown): value is Record<PropertyKey, unknown> { return value !== null && typeof value === "object" } /** * @internal */ export function isPrimitive(value: unknown): value is PrimitiveValue { switch (typeof value) { case "number": case "string": case "boolean": case "undefined": case "bigint": return true default: return value === null } } /** * @internal */ export function isJSONPrimitive(value: unknown): value is JSONPrimitiveValue { switch (typeof value) { case "number": return Number.isFinite(value) case "string": case "boolean": return true default: return value === null } } /** * @internal */ export function deleteFromArray<T>(array: T[], value: T): boolean { const index = array.indexOf(value) if (index >= 0) { array.splice(index, 1) return true } return false } /** * @internal */ export function isMap(val: unknown): val is Map<any, any> | ObservableMap { return val instanceof Map || isObservableMap(val) } /** * @internal */ export function isSet(val: unknown): val is Set<any> | ObservableSet { return val instanceof Set || isObservableSet(val) } /** * @internal */ export function isArray(val: unknown): val is any[] | IObservableArray { return Array.isArray(val) || isObservableArray(val) } /** * @internal */ export const inDevMode = process.env.NODE_ENV !== "production" /** * @internal */ export function assertIsObject(value: unknown, argName: string): asserts value is object { if (!isObject(value)) { throw failure(`${argName} must be an object`) } } /** * @internal */ export function assertIsPlainObject(value: unknown, argName: string): asserts value is object { if (!isPlainObject(value)) { throw failure(`${argName} must be a plain object`) } } /** * @internal */ export function assertIsObservableObject(value: unknown, argName: string): asserts value is object { if (!isObservableObject(value)) { throw failure(`${argName} must be an observable object`) } } /** * @internal */ export function assertIsObservableArray( value: unknown, argName: string ): asserts value is IObservableArray { if (!isObservableArray(value)) { throw failure(`${argName} must be an observable array`) } } /** * @internal */ export function assertIsMap( value: unknown, argName: string ): asserts value is Map<any, any> | ObservableMap { if (!isMap(value)) { throw failure(`${argName} must be a map`) } } /** * @internal */ export function assertIsSet( value: unknown, argName: string ): asserts value is Set<any> | ObservableSet { if (!isSet(value)) { throw failure(`${argName} must be a set`) } } /** * @internal */ export function assertIsFunction(value: unknown, argName: string): asserts value is Function { if (typeof value !== "function") { throw failure(`${argName} must be a function`) } } /** * @internal */ export function assertIsPrimitive( value: unknown, argName: string ): asserts value is PrimitiveValue { if (!isPrimitive(value)) { throw failure(`${argName} must be a primitive`) } } /** * @internal */ export function assertIsString(value: unknown, argName: string): asserts value is string { if (typeof value !== "string") { throw failure(`${argName} must be a string`) } } /** * @internal */ export const runAfterNewSymbol = Symbol("runAfterNew") /** * @internal */ export const runBeforeOnInitSymbol = Symbol("runBeforeOnInit") type LateInitializationFunctionsArray = ((instance: any) => void)[] /** * @internal */ export function addLateInitializationFunction( target: any, symbol: symbol, fn: (instance: any) => void ) { let array: LateInitializationFunctionsArray | undefined = target[symbol] // biome-ignore lint/suspicious/noPrototypeBuiltins: support old browsers if (!(array && Object.prototype.hasOwnProperty.call(target, symbol))) { // leave base array unmodified, create new array in the derived class array = array ? array.slice() : [] addHiddenProp(target, symbol, array) } array.push(fn) } /** * @internal */ export function runLateInitializationFunctions(target: any, symbol: symbol): void { const fns: LateInitializationFunctionsArray | undefined = target[symbol] if (fns) { for (const fn of fns) { fn(target) } } } const warningsAlreadyDisplayed = new Set<string>() /** * @internal */ export function logWarning(type: "warn" | "error", msg: string, uniqueKey?: string): void { if (uniqueKey) { if (warningsAlreadyDisplayed.has(uniqueKey)) { return } warningsAlreadyDisplayed.add(uniqueKey) } msg = "[mobx-keystone] " + msg switch (type) { case "warn": console.warn(msg) break case "error": console.error(msg) break default: throw failure(`unknown log type - ${type}`) } } /** * @internal */ export function lazy<A extends unknown[], R>(getter: (...args: A) => R): typeof getter { let memoizedValue: R let memoized = false return (...args: A): R => { if (!memoized) { memoizedValue = getter(...args) memoized = true } return memoizedValue } } /** * @internal */ export const identityFn = <T>(x: T): T => x /** * @internal */ export const mobx6 = { makeObservable: (mobx as any)[ // just to ensure import * is kept properly String.fromCharCode("l".charCodeAt(0) + 1) + "akeObservable" ], } /** * @internal */ export function propNameToSetterName(propName: string): string { return `set${propName[0].toUpperCase()}${propName.slice(1)}` } /** * @internal */ export function getMobxVersion(): number { if (mobx6.makeObservable!) { return 6 } else { return 5 } } /** * @internal */ export const namespace = "mobx-keystone"