Repository navigation
Expand file tree
/
Copy pathdedupe.ts
More file actions
66 lines (62 loc) · 1.99 KB
/
Copy pathdedupe.ts
File metadata and controls
66 lines (62 loc) · 1.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* Node-id deduplication for upstream observations (warren-33aa).
*
* GitHub delivery is at-least-once: a notification wake-up can replay a
* review, comment, or check the controller has already ingested, and an
* edit produces a *new* source fact rather than a duplicate. Dedupe on the
* stable node id keeps ingested events exactly-once; first occurrence
* wins and later duplicates are counted, never dropped silently.
*/
import type { GithubNoded } from "./types.ts";
export interface DedupedItems<T> {
/** First occurrence of each node id, in original order. */
items: T[];
/** How many entries were dropped as duplicates of an earlier node id. */
duplicateCount: number;
/** Node ids that arrived more than once, in first-seen order. */
duplicateNodeIds: string[];
}
/**
* Deduplicate items by their stable `nodeId`. Empty node ids are kept as
* distinct items (they carry no identity to dedupe on).
*/
export function dedupeByNodeId<T extends GithubNoded>(items: readonly T[]): DedupedItems<T> {
const seen = new Set<string>();
const duplicateNodeIds: string[] = [];
const kept: T[] = [];
let duplicateCount = 0;
for (const item of items) {
if (item.nodeId.length === 0) {
kept.push(item);
continue;
}
if (seen.has(item.nodeId)) {
duplicateCount += 1;
if (!duplicateNodeIds.includes(item.nodeId)) {
duplicateNodeIds.push(item.nodeId);
}
continue;
}
seen.add(item.nodeId);
kept.push(item);
}
return { items: kept, duplicateCount, duplicateNodeIds };
}
/**
* Merge an incoming observation batch into an already-ingested id set.
* Returns only the never-seen items and grows `known` in place, so a poll
* loop can fold batches into durable state incrementally.
*/
export function filterNewByNodeId<T extends GithubNoded>(
known: Set<string>,
incoming: readonly T[],
): T[] {
const fresh: T[] = [];
for (const item of incoming) {
if (item.nodeId.length > 0 && !known.has(item.nodeId)) {
known.add(item.nodeId);
fresh.push(item);
}
}
return fresh;
}