@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/api/label/queryLabels.d.ts +3 -0
  3. package/dist/api/label/subscribeLabels.d.ts +3 -0
  4. package/dist/config/config.d.ts +3 -0
  5. package/dist/config/env.d.ts +3 -0
  6. package/dist/context.d.ts +3 -0
  7. package/dist/db/index.js +3 -1
  8. package/dist/db/index.js.map +2 -2
  9. package/dist/db/schema/label.d.ts +4 -0
  10. package/dist/index.js +875 -454
  11. package/dist/index.js.map +3 -3
  12. package/dist/lexicon/index.d.ts +2 -0
  13. package/dist/lexicon/lexicons.d.ts +27 -0
  14. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +1 -1
  15. package/dist/lexicon/types/com/atproto/admin/updateAccountPassword.d.ts +26 -0
  16. package/dist/logger.d.ts +1 -0
  17. package/dist/mod-service/util.d.ts +3 -0
  18. package/dist/sequencer/index.d.ts +2 -0
  19. package/dist/sequencer/outbox.d.ts +16 -0
  20. package/dist/sequencer/sequencer.d.ts +33 -0
  21. package/package.json +10 -10
  22. package/src/api/admin/emitModerationEvent.ts +16 -10
  23. package/src/api/index.ts +4 -0
  24. package/src/api/label/queryLabels.ts +58 -0
  25. package/src/api/label/subscribeLabels.ts +25 -0
  26. package/src/api/temp/fetchLabels.ts +2 -4
  27. package/src/config/config.ts +6 -0
  28. package/src/config/env.ts +6 -0
  29. package/src/context.ts +12 -0
  30. package/src/db/migrations/20231219T205730722Z-init.ts +7 -1
  31. package/src/db/schema/label.ts +7 -0
  32. package/src/index.ts +2 -0
  33. package/src/lexicon/index.ts +12 -0
  34. package/src/lexicon/lexicons.ts +32 -1
  35. package/src/lexicon/types/app/bsky/actor/defs.ts +2 -0
  36. package/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts +39 -0
  37. package/src/logger.ts +2 -0
  38. package/src/mod-service/index.ts +73 -72
  39. package/src/mod-service/status.ts +3 -0
  40. package/src/mod-service/util.ts +17 -0
  41. package/src/mod-service/views.ts +2 -5
  42. package/src/sequencer/index.ts +2 -0
  43. package/src/sequencer/outbox.ts +122 -0
  44. package/src/sequencer/sequencer.ts +143 -0
  45. package/tests/__snapshots__/moderation-events.test.ts.snap +53 -75
  46. package/tests/__snapshots__/moderation.test.ts.snap +4 -4
  47. package/tests/moderation-appeals.test.ts +19 -7
  48. package/tests/moderation-events.test.ts +7 -7
  49. package/tests/moderation-statuses.test.ts +2 -2
  50. package/tests/moderation.test.ts +14 -13
  51. package/tests/query-labels.test.ts +163 -0
  52. package/tests/repo-search.test.ts +0 -1
  53. package/tests/sequencer.test.ts +222 -0
  54. 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
+ })
@@ -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 {