atagen

malice

public
Star0
Watch0
ad8b0072720d59d5295e9e97bffd453bd55e660c
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/src/lib/sharing.ts

a802a95814e6atagen2026-10-26 03:48 UTC1
interface Entry<T> {
a802a95814e6atagen2026-10-26 03:48 UTC2
  signature: string;
a802a95814e6atagen2026-10-26 03:48 UTC3
  item: T;
a802a95814e6atagen2026-10-26 03:48 UTC4
}
a802a95814e6atagen2026-10-26 03:48 UTC5
a802a95814e6atagen2026-10-26 03:48 UTC6
export interface ShareCache<T> {
a802a95814e6atagen2026-10-26 03:48 UTC7
  byKey: Map<string, Entry<T>>;
a802a95814e6atagen2026-10-26 03:48 UTC8
  list: T[];
a802a95814e6atagen2026-10-26 03:48 UTC9
}
a802a95814e6atagen2026-10-26 03:48 UTC10
a802a95814e6atagen2026-10-26 03:48 UTC11
export function newShareCache<T>(): ShareCache<T> {
a802a95814e6atagen2026-10-26 03:48 UTC12
  return { byKey: new Map(), list: [] };
a802a95814e6atagen2026-10-26 03:48 UTC13
}
a802a95814e6atagen2026-10-26 03:48 UTC14
a802a95814e6atagen2026-10-26 03:48 UTC15
function signature(text: string): string {
a802a95814e6atagen2026-10-26 03:48 UTC16
  let a = 0x811c9dc5;
a802a95814e6atagen2026-10-26 03:48 UTC17
  let b = 0x9747b28c;
a802a95814e6atagen2026-10-26 03:48 UTC18
  for (let i = 0; i < text.length; i += 1) {
a802a95814e6atagen2026-10-26 03:48 UTC19
    const c = text.charCodeAt(i);
a802a95814e6atagen2026-10-26 03:48 UTC20
    a = Math.imul(a ^ c, 0x01000193);
a802a95814e6atagen2026-10-26 03:48 UTC21
    b = Math.imul(b + c, 0x85ebca6b) ^ (b >>> 13);
a802a95814e6atagen2026-10-26 03:48 UTC22
  }
a802a95814e6atagen2026-10-26 03:48 UTC23
  return `${text.length}.${(a >>> 0).toString(36)}.${(b >>> 0).toString(36)}`;
a802a95814e6atagen2026-10-26 03:48 UTC24
}
a802a95814e6atagen2026-10-26 03:48 UTC25
a802a95814e6atagen2026-10-26 03:48 UTC26
/**
a802a95814e6atagen2026-10-26 03:48 UTC27
 * Parses `raw` while reusing the objects from the previous call whose serialised form is unchanged.
a802a95814e6atagen2026-10-26 03:48 UTC28
 * Entries are matched by `identify` and compared by a hash of their JSON, so only the latest list is retained.
a802a95814e6atagen2026-10-26 03:48 UTC29
 * Returns the previous array itself when every element was reused and the length matches.
a802a95814e6atagen2026-10-26 03:48 UTC30
 */
a802a95814e6atagen2026-10-26 03:48 UTC31
export function parseShared<T>(
a802a95814e6atagen2026-10-26 03:48 UTC32
  cache: ShareCache<T>,
a802a95814e6atagen2026-10-26 03:48 UTC33
  raw: unknown,
a802a95814e6atagen2026-10-26 03:48 UTC34
  parse: (entry: unknown) => T | null,
a802a95814e6atagen2026-10-26 03:48 UTC35
  identify: (entry: unknown) => string | null = () => null
a802a95814e6atagen2026-10-26 03:48 UTC36
): T[] {
a802a95814e6atagen2026-10-26 03:48 UTC37
  if (!Array.isArray(raw)) {
a802a95814e6atagen2026-10-26 03:48 UTC38
    cache.byKey = new Map();
a802a95814e6atagen2026-10-26 03:48 UTC39
    cache.list = cache.list.length ? [] : cache.list;
a802a95814e6atagen2026-10-26 03:48 UTC40
    return cache.list;
a802a95814e6atagen2026-10-26 03:48 UTC41
  }
a802a95814e6atagen2026-10-26 03:48 UTC42
  const byKey = new Map<string, Entry<T>>();
a802a95814e6atagen2026-10-26 03:48 UTC43
  const out: T[] = [];
a802a95814e6atagen2026-10-26 03:48 UTC44
  for (const entry of raw) {
a802a95814e6atagen2026-10-26 03:48 UTC45
    const sig = signature(JSON.stringify(entry));
a802a95814e6atagen2026-10-26 03:48 UTC46
    const base = identify(entry) ?? `~${sig}`;
a802a95814e6atagen2026-10-26 03:48 UTC47
    let key = base;
a802a95814e6atagen2026-10-26 03:48 UTC48
    for (let n = 1; byKey.has(key); n += 1) key = `${base}#${n}`;
a802a95814e6atagen2026-10-26 03:48 UTC49
    const previous = cache.byKey.get(key);
a802a95814e6atagen2026-10-26 03:48 UTC50
    const item = previous && previous.signature === sig ? previous.item : parse(entry);
a802a95814e6atagen2026-10-26 03:48 UTC51
    if (item === null) continue;
a802a95814e6atagen2026-10-26 03:48 UTC52
    byKey.set(key, { signature: sig, item });
a802a95814e6atagen2026-10-26 03:48 UTC53
    out.push(item);
a802a95814e6atagen2026-10-26 03:48 UTC54
  }
a802a95814e6atagen2026-10-26 03:48 UTC55
  cache.byKey = byKey;
a802a95814e6atagen2026-10-26 03:48 UTC56
  if (out.length === cache.list.length && out.every((item, index) => item === cache.list[index])) return cache.list;
a802a95814e6atagen2026-10-26 03:48 UTC57
  cache.list = out;
a802a95814e6atagen2026-10-26 03:48 UTC58
  return out;
a802a95814e6atagen2026-10-26 03:48 UTC59
}
a802a95814e6atagen2026-10-26 03:48 UTC60
a802a95814e6atagen2026-10-26 03:48 UTC61
export function sameList<T>(a: readonly T[], b: readonly T[]): boolean {
a802a95814e6atagen2026-10-26 03:48 UTC62
  return a === b || (a.length === b.length && a.every((item, index) => item === b[index]));
a802a95814e6atagen2026-10-26 03:48 UTC63
}