@subsquid/batch-processor 0.2.0-portal-api.493495 → 0.2.0-portal-api.19316d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,199 @@
1
+ import type {TemplateRegistry as ITemplateRegistry, TemplateValue} from '@subsquid/util-internal-data-source'
2
+ import type {FiniteRange, Range} from '@subsquid/util-internal-range'
3
+ import {TemplateMutation} from './database'
4
+
5
+ export interface TemplateManager {
6
+ add(key: string, value: string, blockNumber: number): void
7
+ remove(key: string, value: string, blockNumber: number): void
8
+ has(key: string, value: string, blockNumber: number): boolean
9
+ }
10
+
11
+ class HandlerTemplates implements TemplateManager {
12
+ readonly data: TemplateMutation[] = []
13
+
14
+ constructor(
15
+ private readonly range: FiniteRange,
16
+ private readonly registry: TemplateRegistry,
17
+ ) {}
18
+
19
+ add(key: string, value: string, blockNumber: number): void {
20
+ this.mutate('add', key, value, blockNumber)
21
+ }
22
+
23
+ remove(key: string, value: string, blockNumber: number): void {
24
+ this.mutate('delete', key, value, blockNumber)
25
+ }
26
+
27
+ has(key: string, value: string, blockNumber: number): boolean {
28
+ return this.registry.has(key, value, blockNumber)
29
+ }
30
+
31
+ private mutate(type: TemplateMutation['type'], key: string, value: string, blockNumber: number): void {
32
+ if (blockNumber < this.range.from) {
33
+ throw new RangeError(`blockNumber ${blockNumber} is before batch start ${this.range.from}`)
34
+ }
35
+ let mutation: TemplateMutation = {type, key, value, blockNumber}
36
+ this.registry.apply(mutation)
37
+ this.data.push(mutation)
38
+ }
39
+ }
40
+
41
+ export class TemplateRegistry implements ITemplateRegistry {
42
+ private byKey = new Map<string, Map<string, Range[]>>()
43
+ private baseMutations: TemplateMutation[] = []
44
+ private undoLog: Array<{blockNumber: number, templates: TemplateMutation[]}> = []
45
+ private pendingTemplates: TemplateMutation[] = []
46
+
47
+ /**
48
+ * Rebuild state from finalized mutations and optionally replay hot block
49
+ * mutations to reconstruct the undo log.
50
+ *
51
+ * Finalized mutations form the base state. Hot block mutations are stored
52
+ * in the undo log so rollbackTo() works for hot block heights after restart.
53
+ */
54
+ init(
55
+ mutations: TemplateMutation[],
56
+ hotBlocks?: {blockNumber: number, templates: TemplateMutation[]}[]
57
+ ): void {
58
+ this.baseMutations = mutations
59
+ this.undoLog = hotBlocks ?? []
60
+ this.pendingTemplates = []
61
+ this.rebuild()
62
+ }
63
+
64
+ get(key: string): TemplateValue[] {
65
+ let m = this.byKey.get(key)
66
+ if (m == null) return []
67
+ let out: TemplateValue[] = []
68
+ for (let [value, ranges] of m) {
69
+ for (let range of ranges) {
70
+ out.push({value, range})
71
+ }
72
+ }
73
+ return out
74
+ }
75
+
76
+ has(key: string, value: string, block: number): boolean {
77
+ let byVal = this.byKey.get(key)
78
+ if (byVal == null) return false
79
+ let ranges = byVal.get(value)
80
+ if (ranges == null) return false
81
+ for (let range of ranges) {
82
+ if (block >= range.from && (range.to == null || block <= range.to)) {
83
+ return true
84
+ }
85
+ }
86
+ return false
87
+ }
88
+
89
+ apply(mutation: TemplateMutation): boolean {
90
+ let changed = this.applyToState(mutation)
91
+ if (changed) {
92
+ this.pendingTemplates.push(mutation)
93
+ }
94
+ return changed
95
+ }
96
+
97
+ async transact(
98
+ range: FiniteRange,
99
+ fn: (templates: TemplateManager) => Promise<void>,
100
+ ): Promise<{data: TemplateMutation[]; changed: boolean}> {
101
+ let templates = new HandlerTemplates(range, this)
102
+ try {
103
+ await fn(templates)
104
+ } catch (e) {
105
+ this.pendingTemplates = []
106
+ this.rebuild()
107
+ throw e
108
+ }
109
+ let changed = this.pendingTemplates.length > 0
110
+ if (changed) {
111
+ this.undoLog.push({blockNumber: range.to, templates: this.pendingTemplates})
112
+ }
113
+ this.pendingTemplates = []
114
+ return {data: templates.data, changed}
115
+ }
116
+
117
+ rollbackTo(blockNumber: number): void {
118
+ let splitIdx = upperBound(this.undoLog, blockNumber)
119
+ if (splitIdx >= this.undoLog.length) return
120
+ this.undoLog.length = splitIdx
121
+ this.pendingTemplates = []
122
+ this.rebuild()
123
+ }
124
+
125
+ prune(blockNumber: number): void {
126
+ let splitIdx = upperBound(this.undoLog, blockNumber)
127
+ for (let i = 0; i < splitIdx; i++) {
128
+ this.baseMutations.push(...this.undoLog[i].templates)
129
+ }
130
+ this.undoLog = this.undoLog.slice(splitIdx)
131
+ }
132
+
133
+ private applyToState({type, key, value, blockNumber}: TemplateMutation): boolean {
134
+ if (type === 'add') {
135
+ let byVal = this.byKey.get(key)
136
+ if (byVal == null) {
137
+ byVal = new Map()
138
+ this.byKey.set(key, byVal)
139
+ }
140
+
141
+ let ranges = byVal.get(value)
142
+ if (ranges == null) {
143
+ byVal.set(value, [{from: blockNumber}])
144
+ return true
145
+ }
146
+
147
+ let last = ranges[ranges.length - 1]
148
+ if (last.to != null) {
149
+ if (blockNumber < last.to) return false
150
+ ranges.push({from: blockNumber})
151
+ return true
152
+ }
153
+
154
+ if (blockNumber >= last.from) return false
155
+ last.from = blockNumber
156
+ return true
157
+ } else {
158
+ let byVal = this.byKey.get(key)
159
+ if (!byVal) return false
160
+ let ranges = byVal.get(value)
161
+ if (!ranges || ranges.length === 0) return false
162
+ let last = ranges[ranges.length - 1]
163
+ if (last.to != null) return false
164
+ if (blockNumber < last.from) return false
165
+ last.to = blockNumber
166
+ return true
167
+ }
168
+ }
169
+
170
+ private rebuild(): void {
171
+ this.byKey.clear()
172
+ let allMutations: TemplateMutation[] = [...this.baseMutations]
173
+ for (let entry of this.undoLog) {
174
+ allMutations.push(...entry.templates)
175
+ }
176
+ allMutations.sort((a, b) => {
177
+ if (a.blockNumber !== b.blockNumber) return a.blockNumber - b.blockNumber
178
+ if (a.type !== b.type) return a.type === 'delete' ? -1 : 1
179
+ return 0
180
+ })
181
+ for (let m of allMutations) {
182
+ this.applyToState(m)
183
+ }
184
+ }
185
+ }
186
+
187
+ function upperBound(log: Array<{blockNumber: number}>, target: number): number {
188
+ let lo = 0
189
+ let hi = log.length
190
+ while (lo < hi) {
191
+ let mid = (lo + hi) >> 1
192
+ if (log[mid].blockNumber > target) {
193
+ hi = mid
194
+ } else {
195
+ lo = mid + 1
196
+ }
197
+ }
198
+ return lo
199
+ }