53 lines
2.8 KiB
TypeScript
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));
|
|
}
|