Files
Endoleap/src/lib/canvas-core/values.ts
T

53 lines
2.8 KiB
TypeScript

import type { Id, Json, ReadonlyValueSet } from './types.ts';
export function fault(code: string, targets: readonly Id[] = []): Error & { code: string; targets: readonly Id[] } {
return Object.assign(new Error(code), { code, targets });
}
export function reject(error: unknown) {
const value = error as { code?: string; targets?: readonly string[] };
return Object.freeze({ status: 'rejected' as const, code: value?.code || 'invalid_command', targets: value?.targets || [] });
}
export function freeze<T>(value: T): T {
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
for (const child of Object.values(value)) freeze(child);
Object.freeze(value);
}
return value;
}
export function clone<T>(value: T): T {
if (value === undefined) return value;
return JSON.parse(JSON.stringify(value)) as T;
}
export function equal(a: unknown, b: unknown): boolean {
if (Object.is(a, b)) return true;
if (!a || !b || typeof a !== 'object' || typeof b !== 'object' || Array.isArray(a) !== Array.isArray(b)) return false;
const left = Object.keys(a), right = Object.keys(b);
return left.length === right.length && left.every(key => Object.prototype.hasOwnProperty.call(b, key) && equal((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]));
}
/** A capability-safe read view: unlike TS ReadonlyMap, no .set is exposed at runtime. */
export function readMap<K, V>(input: ReadonlyMap<K, V>): ReadonlyMap<K, V> {
const map = new Map(input);
const view: ReadonlyMap<K, V> = {
get size() { return map.size; }, get: key => map.get(key), has: key => map.has(key),
entries: () => map.entries(), keys: () => map.keys(), values: () => map.values(),
[Symbol.iterator]: () => map[Symbol.iterator](),
forEach: (callback, thisArg) => map.forEach((value, key) => callback.call(thisArg, value, key, view)),
};
return Object.freeze(view);
}
export function readSet<T>(input: Iterable<T>): ReadonlyValueSet<T> {
const set = new Set(input);
const view: ReadonlyValueSet<T> = {
get size() { return set.size; }, has: key => set.has(key), entries: () => set.entries(), keys: () => set.keys(), values: () => set.values(),
[Symbol.iterator]: () => set[Symbol.iterator](), forEach: callback => set.forEach(value => callback(value, value, view)),
};
return Object.freeze(view);
}
export function validJson(value: unknown, depth = 0): value is Json {
if (depth > 64) return false;
if (value === null || typeof value === 'string' || typeof value === 'boolean') return true;
if (typeof value === 'number') return Number.isFinite(value);
if (Array.isArray(value)) return value.every(item => validJson(item, depth + 1));
return !!value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype && Object.values(value).every(child => validJson(child, depth + 1));
}