@atproto/ozone 0.0.11 → 0.0.13
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.
- package/CHANGELOG.md +17 -0
- package/dist/api/label/queryLabels.d.ts +3 -0
- package/dist/api/label/subscribeLabels.d.ts +3 -0
- package/dist/config/config.d.ts +3 -0
- package/dist/config/env.d.ts +3 -0
- package/dist/context.d.ts +3 -0
- package/dist/db/index.js +3 -1
- package/dist/db/index.js.map +2 -2
- package/dist/db/schema/label.d.ts +4 -0
- package/dist/index.js +875 -454
- package/dist/index.js.map +3 -3
- package/dist/lexicon/index.d.ts +2 -0
- package/dist/lexicon/lexicons.d.ts +27 -0
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +1 -1
- package/dist/lexicon/types/com/atproto/admin/updateAccountPassword.d.ts +26 -0
- package/dist/logger.d.ts +1 -0
- package/dist/mod-service/util.d.ts +3 -0
- package/dist/sequencer/index.d.ts +2 -0
- package/dist/sequencer/outbox.d.ts +16 -0
- package/dist/sequencer/sequencer.d.ts +33 -0
- package/package.json +10 -10
- package/src/api/admin/emitModerationEvent.ts +16 -10
- package/src/api/index.ts +4 -0
- package/src/api/label/queryLabels.ts +58 -0
- package/src/api/label/subscribeLabels.ts +25 -0
- package/src/api/temp/fetchLabels.ts +2 -4
- package/src/config/config.ts +6 -0
- package/src/config/env.ts +6 -0
- package/src/context.ts +12 -0
- package/src/db/migrations/20231219T205730722Z-init.ts +7 -1
- package/src/db/schema/label.ts +7 -0
- package/src/index.ts +2 -0
- package/src/lexicon/index.ts +12 -0
- package/src/lexicon/lexicons.ts +32 -1
- package/src/lexicon/types/app/bsky/actor/defs.ts +2 -0
- package/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts +39 -0
- package/src/logger.ts +2 -0
- package/src/mod-service/index.ts +73 -72
- package/src/mod-service/status.ts +3 -0
- package/src/mod-service/util.ts +17 -0
- package/src/mod-service/views.ts +2 -5
- package/src/sequencer/index.ts +2 -0
- package/src/sequencer/outbox.ts +122 -0
- package/src/sequencer/sequencer.ts +143 -0
- package/tests/__snapshots__/moderation-events.test.ts.snap +53 -75
- package/tests/__snapshots__/moderation.test.ts.snap +4 -4
- package/tests/moderation-appeals.test.ts +19 -7
- package/tests/moderation-events.test.ts +7 -7
- package/tests/moderation-statuses.test.ts +2 -2
- package/tests/moderation.test.ts +14 -13
- package/tests/query-labels.test.ts +163 -0
- package/tests/repo-search.test.ts +0 -1
- package/tests/sequencer.test.ts +222 -0
- package/tests/server.test.ts +2 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { TestNetwork } from '@atproto/dev-env'
|
|
2
|
+
import { readFromGenerator, wait } from '@atproto/common'
|
|
3
|
+
import { LabelsEvt, Sequencer } from '../src/sequencer'
|
|
4
|
+
import Outbox from '../src/sequencer/outbox'
|
|
5
|
+
import { randomStr } from '@atproto/crypto'
|
|
6
|
+
import { Label } from '../src/lexicon/types/com/atproto/label/defs'
|
|
7
|
+
|
|
8
|
+
describe('sequencer', () => {
|
|
9
|
+
let network: TestNetwork
|
|
10
|
+
let sequencer: Sequencer
|
|
11
|
+
|
|
12
|
+
let totalEvts = 0
|
|
13
|
+
let lastSeen: number
|
|
14
|
+
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
network = await TestNetwork.create({
|
|
17
|
+
dbPostgresSchema: 'ozone_sequencer',
|
|
18
|
+
})
|
|
19
|
+
sequencer = network.ozone.ctx.sequencer
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await network.close()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const loadFromDb = (lastSeen: number) => {
|
|
27
|
+
return sequencer.db.db
|
|
28
|
+
.selectFrom('label')
|
|
29
|
+
.selectAll()
|
|
30
|
+
.where('id', '>', lastSeen)
|
|
31
|
+
.orderBy('id', 'asc')
|
|
32
|
+
.execute()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const evtToDbRow = (e: LabelsEvt) => {
|
|
36
|
+
const label = e.labels[0]
|
|
37
|
+
return {
|
|
38
|
+
id: e.seq,
|
|
39
|
+
...label,
|
|
40
|
+
cid: label.cid ? label.cid : '',
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const caughtUp = (outbox: Outbox): (() => Promise<boolean>) => {
|
|
45
|
+
return async () => {
|
|
46
|
+
const lastEvt = await outbox.sequencer.curr()
|
|
47
|
+
if (lastEvt === null) return true
|
|
48
|
+
return outbox.lastSeen >= (lastEvt ?? 0)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const createLabels = async (count: number): Promise<Label[]> => {
|
|
53
|
+
const labels: Label[] = []
|
|
54
|
+
for (let i = 0; i < count; i++) {
|
|
55
|
+
const did = `did:example:${randomStr(10, 'base32')}`
|
|
56
|
+
const label = {
|
|
57
|
+
src: 'did:example:labeler',
|
|
58
|
+
uri: did,
|
|
59
|
+
val: 'spam',
|
|
60
|
+
neg: false,
|
|
61
|
+
cts: new Date().toISOString(),
|
|
62
|
+
}
|
|
63
|
+
await network.ozone.ctx.db.transaction((dbTxn) =>
|
|
64
|
+
network.ozone.ctx.modService(dbTxn).createLabels([label]),
|
|
65
|
+
)
|
|
66
|
+
labels.push(label)
|
|
67
|
+
}
|
|
68
|
+
return labels
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
it('sends to outbox', async () => {
|
|
72
|
+
const count = 20
|
|
73
|
+
totalEvts += count
|
|
74
|
+
await createLabels(count)
|
|
75
|
+
const outbox = new Outbox(sequencer)
|
|
76
|
+
const evts = await readFromGenerator(outbox.events(-1), caughtUp(outbox))
|
|
77
|
+
expect(evts.length).toBe(totalEvts)
|
|
78
|
+
|
|
79
|
+
const fromDb = await loadFromDb(-1)
|
|
80
|
+
expect(evts.map(evtToDbRow)).toEqual(fromDb)
|
|
81
|
+
|
|
82
|
+
lastSeen = evts.at(-1)?.seq ?? lastSeen
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('sequences negative labels', async () => {
|
|
86
|
+
const count = 5
|
|
87
|
+
totalEvts += count
|
|
88
|
+
const created = await createLabels(count)
|
|
89
|
+
const toNegate = created
|
|
90
|
+
.slice(0, 2)
|
|
91
|
+
.map((l) => ({ ...l, neg: true, cts: new Date().toISOString() }))
|
|
92
|
+
await network.ozone.ctx
|
|
93
|
+
.modService(network.ozone.ctx.db)
|
|
94
|
+
.createLabels(toNegate)
|
|
95
|
+
|
|
96
|
+
const outbox = new Outbox(sequencer)
|
|
97
|
+
const evts = await readFromGenerator(
|
|
98
|
+
outbox.events(lastSeen),
|
|
99
|
+
caughtUp(outbox),
|
|
100
|
+
)
|
|
101
|
+
expect(evts.length).toBe(count)
|
|
102
|
+
|
|
103
|
+
const fromDb = await loadFromDb(lastSeen)
|
|
104
|
+
expect(evts.map(evtToDbRow)).toEqual(fromDb)
|
|
105
|
+
expect(evts[3].labels[0].uri).toEqual(toNegate[0].uri)
|
|
106
|
+
expect(evts[3].labels[0].neg).toBe(true)
|
|
107
|
+
expect(evts[4].labels[0].uri).toEqual(toNegate[1].uri)
|
|
108
|
+
expect(evts[4].labels[0].neg).toBe(true)
|
|
109
|
+
|
|
110
|
+
lastSeen = evts.at(-1)?.seq ?? lastSeen
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('handles cut over', async () => {
|
|
114
|
+
const count = 20
|
|
115
|
+
totalEvts += count
|
|
116
|
+
const outbox = new Outbox(sequencer)
|
|
117
|
+
const createPromise = createLabels(count)
|
|
118
|
+
const [evts] = await Promise.all([
|
|
119
|
+
readFromGenerator(outbox.events(-1), caughtUp(outbox), createPromise),
|
|
120
|
+
createPromise,
|
|
121
|
+
])
|
|
122
|
+
expect(evts.length).toBe(totalEvts)
|
|
123
|
+
|
|
124
|
+
const fromDb = await loadFromDb(-1)
|
|
125
|
+
expect(evts.map(evtToDbRow)).toEqual(fromDb)
|
|
126
|
+
|
|
127
|
+
lastSeen = evts.at(-1)?.seq ?? lastSeen
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('only gets events after cursor', async () => {
|
|
131
|
+
const count = 20
|
|
132
|
+
totalEvts += count
|
|
133
|
+
const outbox = new Outbox(sequencer)
|
|
134
|
+
const createPromise = createLabels(count)
|
|
135
|
+
const [evts] = await Promise.all([
|
|
136
|
+
readFromGenerator(
|
|
137
|
+
outbox.events(lastSeen),
|
|
138
|
+
caughtUp(outbox),
|
|
139
|
+
createPromise,
|
|
140
|
+
),
|
|
141
|
+
createPromise,
|
|
142
|
+
])
|
|
143
|
+
|
|
144
|
+
// +1 because we send the lastSeen date as well
|
|
145
|
+
expect(evts.length).toBe(count)
|
|
146
|
+
|
|
147
|
+
const fromDb = await loadFromDb(lastSeen)
|
|
148
|
+
expect(evts.map(evtToDbRow)).toEqual(fromDb)
|
|
149
|
+
|
|
150
|
+
lastSeen = evts.at(-1)?.seq ?? lastSeen
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('buffers events that are not being read', async () => {
|
|
154
|
+
const count = 20
|
|
155
|
+
totalEvts += count
|
|
156
|
+
const outbox = new Outbox(sequencer)
|
|
157
|
+
const createPromise = createLabels(count)
|
|
158
|
+
const gen = outbox.events(lastSeen)
|
|
159
|
+
// read enough to start streaming then wait so that the rest go into the buffer,
|
|
160
|
+
// then stream out from buffer
|
|
161
|
+
const [firstPart] = await Promise.all([
|
|
162
|
+
readFromGenerator(gen, caughtUp(outbox), createPromise, 5),
|
|
163
|
+
createPromise,
|
|
164
|
+
])
|
|
165
|
+
const secondPart = await readFromGenerator(
|
|
166
|
+
gen,
|
|
167
|
+
caughtUp(outbox),
|
|
168
|
+
createPromise,
|
|
169
|
+
)
|
|
170
|
+
const evts = [...firstPart, ...secondPart]
|
|
171
|
+
expect(evts.length).toBe(count)
|
|
172
|
+
|
|
173
|
+
const fromDb = await loadFromDb(lastSeen)
|
|
174
|
+
expect(evts.map(evtToDbRow)).toEqual(fromDb)
|
|
175
|
+
|
|
176
|
+
lastSeen = evts.at(-1)?.seq ?? lastSeen
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('errors when buffer is overloaded', async () => {
|
|
180
|
+
const count = 20
|
|
181
|
+
totalEvts += count
|
|
182
|
+
const outbox = new Outbox(sequencer, { maxBufferSize: 5 })
|
|
183
|
+
const gen = outbox.events(lastSeen)
|
|
184
|
+
const createPromise = createLabels(count)
|
|
185
|
+
// read enough to start streaming then wait to stream rest until buffer is overloaded
|
|
186
|
+
const overloadBuffer = async () => {
|
|
187
|
+
await Promise.all([
|
|
188
|
+
readFromGenerator(gen, caughtUp(outbox), createPromise, 5),
|
|
189
|
+
createPromise,
|
|
190
|
+
])
|
|
191
|
+
await wait(500)
|
|
192
|
+
await readFromGenerator(gen, caughtUp(outbox), createPromise)
|
|
193
|
+
}
|
|
194
|
+
await expect(overloadBuffer).rejects.toThrow('Stream consumer too slow')
|
|
195
|
+
|
|
196
|
+
await createPromise
|
|
197
|
+
|
|
198
|
+
const fromDb = await loadFromDb(lastSeen)
|
|
199
|
+
lastSeen = fromDb.at(-1)?.id ?? lastSeen
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('handles many open connections', async () => {
|
|
203
|
+
const count = 20
|
|
204
|
+
const outboxes: Outbox[] = []
|
|
205
|
+
for (let i = 0; i < 50; i++) {
|
|
206
|
+
outboxes.push(new Outbox(sequencer))
|
|
207
|
+
}
|
|
208
|
+
const createPromise = createLabels(count)
|
|
209
|
+
const readOutboxes = Promise.all(
|
|
210
|
+
outboxes.map((o) =>
|
|
211
|
+
readFromGenerator(o.events(lastSeen), caughtUp(o), createPromise),
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
const [results] = await Promise.all([readOutboxes, createPromise])
|
|
215
|
+
const fromDb = await loadFromDb(lastSeen)
|
|
216
|
+
for (const result of results) {
|
|
217
|
+
expect(result.length).toBe(count)
|
|
218
|
+
expect(result.map(evtToDbRow)).toEqual(fromDb)
|
|
219
|
+
}
|
|
220
|
+
lastSeen = results[0].at(-1)?.seq ?? lastSeen
|
|
221
|
+
})
|
|
222
|
+
})
|
package/tests/server.test.ts
CHANGED
|
@@ -55,6 +55,8 @@ describe('server', () => {
|
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
it('healthcheck fails when database is unavailable.', async () => {
|
|
58
|
+
// destory sequencer to release connection that would prevent the db from closing
|
|
59
|
+
await ozone.ctx.sequencer.destroy()
|
|
58
60
|
await ozone.ctx.db.close()
|
|
59
61
|
let error: AxiosError
|
|
60
62
|
try {
|