@difizen/mana-observable
Version:
161 lines (142 loc) • 4.51 kB
text/typescript
import 'reflect-metadata';
import type { Disposable } from '@difizen/mana-common';
import { isPlainObject, getPropertyDescriptor } from '@difizen/mana-common';
import { ObservableConfig } from './config';
import type { Traceable } from './core';
import { ObservableSymbol } from './core';
export namespace Observability {
export function isTraceable(data: any): data is Traceable {
return !!data && data[ObservableSymbol.Self];
}
export function isObject(obj: any): obj is Record<string | number | symbol, any> {
return !!obj && typeof obj === 'object';
}
export function canBeObservable(
obj: any,
): obj is Record<string | number | symbol, any> {
if (!isObject(obj)) {
return false;
}
if (ObservableConfig.shouldExclude(obj)) {
return false;
}
return true;
}
export function marked(obj: any, property?: string | symbol): boolean {
if (!isObject(obj)) {
return false;
}
const origin = getOrigin(obj);
if (property) {
return Reflect.hasOwnMetadata(ObservableSymbol.Observable, origin, property);
}
return Reflect.hasOwnMetadata(ObservableSymbol.Observable, origin);
}
export function mark(obj: Record<any, any>, property?: string | symbol) {
Reflect.defineMetadata(ObservableSymbol.Observable, true, obj);
if (property) {
Reflect.defineMetadata(ObservableSymbol.Observable, true, obj, property);
}
}
export function defineOrigin(obj: Record<any, any>, property: string | symbol) {
Reflect.defineMetadata(ObservableSymbol.KeepOrigin, true, obj, property);
}
export function shouldKeepOrigin(obj: any, property: string | symbol): boolean {
if (!isObject(obj)) {
return false;
}
const origin = getOrigin(obj);
if (isPlainObject(origin)) {
return false;
}
return Reflect.hasMetadata(
ObservableSymbol.KeepOrigin,
origin.constructor,
property,
);
}
export function getOrigin<T = any>(obj: T): T {
if (!isTraceable(obj)) {
return obj;
}
return obj[ObservableSymbol.Self];
}
export function equals(a: any, b: any) {
return getOrigin(a) === getOrigin(b);
}
export function getDisposable(
metaKey: any,
obj: Record<string, any>,
property?: string,
) {
if (property) {
return Reflect.getOwnMetadata(metaKey, obj, property);
}
return Reflect.getOwnMetadata(metaKey, obj);
}
export function setDisposable(
metaKey: any,
disposable: Disposable,
obj: Record<string, any>,
property?: string,
) {
if (property) {
Reflect.defineMetadata(metaKey, disposable, obj, property);
}
Reflect.defineMetadata(metaKey, disposable, obj);
}
}
export namespace ObservableProperties {
export function getOwn(obj: Record<string, any>): string[] | undefined {
return Reflect.getOwnMetadata(ObservableSymbol.ObservableProperties, obj);
}
export function get(obj: Record<string, any>): string[] | undefined {
return Reflect.getMetadata(ObservableSymbol.ObservableProperties, obj);
}
export function find(obj: Record<string, any>): string[] | undefined {
if (obj && obj.constructor) {
return get(obj.constructor);
}
return undefined;
}
export function add(obj: Record<any, any>, property: string): void {
const existingProperties = getOwn(obj);
if (existingProperties) {
existingProperties.push(property);
} else {
const protoProperties = get(obj) || [];
Reflect.defineMetadata(
ObservableSymbol.ObservableProperties,
[...protoProperties, property],
obj,
);
}
}
}
export namespace InstanceValue {
export function set(target: any, property: string, value: any) {
Reflect.defineMetadata(property, value, target);
}
export function get(target: any, property: string) {
return Reflect.getMetadata(property, target);
}
}
export type DesignType =
| undefined
| typeof Function
| typeof String
| typeof Boolean
| typeof Number
| typeof Array
| typeof Map
| typeof Object;
export const getOrigin = Observability.getOrigin;
export const equals = Observability.equals;
export function isReadonly(target: object, property: string | symbol): boolean {
const descriptor = getPropertyDescriptor(target, property);
if (descriptor?.configurable === false && descriptor?.writable === false) {
// non-configurable and non-writable property should return the actual value
return true;
}
return false;
}