atagen

malice

public
Star0
Watch0
984f84f9bf6c54c04625b38858111a7760036f51
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/src/lib/sharing.tsTypescript63 lines2.0 KB
1
interface Entry<T> {
2
  signature: string;
3
  item: T;
4
}
5
6
export interface ShareCache<T> {
7
  byKey: Map<string, Entry<T>>;
8
  list: T[];
9
}
10
11
export function newShareCache<T>(): ShareCache<T> {
12
  return { byKey: new Map(), list: [] };
13
}
14
15
function signature(text: string): string {
16
  let a = 0x811c9dc5;
17
  let b = 0x9747b28c;
18
  for (let i = 0; i < text.length; i += 1) {
19
    const c = text.charCodeAt(i);
20
    a = Math.imul(a ^ c, 0x01000193);
21
    b = Math.imul(b + c, 0x85ebca6b) ^ (b >>> 13);
22
  }
23
  return `${text.length}.${(a >>> 0).toString(36)}.${(b >>> 0).toString(36)}`;
24
}
25
26
/**
27
 * Parses `raw` while reusing the objects from the previous call whose serialised form is unchanged.
28
 * Entries are matched by `identify` and compared by a hash of their JSON, so only the latest list is retained.
29
 * Returns the previous array itself when every element was reused and the length matches.
30
 */
31
export function parseShared<T>(
32
  cache: ShareCache<T>,
33
  raw: unknown,
34
  parse: (entry: unknown) => T | null,
35
  identify: (entry: unknown) => string | null = () => null
36
): T[] {
37
  if (!Array.isArray(raw)) {
38
    cache.byKey = new Map();
39
    cache.list = cache.list.length ? [] : cache.list;
40
    return cache.list;
41
  }
42
  const byKey = new Map<string, Entry<T>>();
43
  const out: T[] = [];
44
  for (const entry of raw) {
45
    const sig = signature(JSON.stringify(entry));
46
    const base = identify(entry) ?? `~${sig}`;
47
    let key = base;
48
    for (let n = 1; byKey.has(key); n += 1) key = `${base}#${n}`;
49
    const previous = cache.byKey.get(key);
50
    const item = previous && previous.signature === sig ? previous.item : parse(entry);
51
    if (item === null) continue;
52
    byKey.set(key, { signature: sig, item });
53
    out.push(item);
54
  }
55
  cache.byKey = byKey;
56
  if (out.length === cache.list.length && out.every((item, index) => item === cache.list[index])) return cache.list;
57
  cache.list = out;
58
  return out;
59
}
60
61
export function sameList<T>(a: readonly T[], b: readonly T[]): boolean {
62
  return a === b || (a.length === b.length && a.every((item, index) => item === b[index]));
63
}