@atproto/bsync 0.0.32 → 0.0.33

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 (45) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/logger.js +2 -2
  3. package/dist/logger.js.map +1 -1
  4. package/package.json +17 -13
  5. package/bin/migration-create.ts +0 -38
  6. package/buf.gen.yaml +0 -12
  7. package/jest.config.cjs +0 -21
  8. package/proto/bsync.proto +0 -134
  9. package/src/client.ts +0 -25
  10. package/src/config.ts +0 -90
  11. package/src/context.ts +0 -48
  12. package/src/db/index.ts +0 -200
  13. package/src/db/migrations/20240108T220751294Z-init.ts +0 -26
  14. package/src/db/migrations/20240717T224303472Z-notif-ops.ts +0 -24
  15. package/src/db/migrations/20250527T022203400Z-add-operation.ts +0 -20
  16. package/src/db/migrations/20250603T163446567Z-alter-operation.ts +0 -19
  17. package/src/db/migrations/index.ts +0 -8
  18. package/src/db/migrations/provider.ts +0 -8
  19. package/src/db/schema/index.ts +0 -16
  20. package/src/db/schema/mute_item.ts +0 -13
  21. package/src/db/schema/mute_op.ts +0 -18
  22. package/src/db/schema/notif_item.ts +0 -13
  23. package/src/db/schema/notif_op.ts +0 -16
  24. package/src/db/schema/operation.ts +0 -20
  25. package/src/db/types.ts +0 -19
  26. package/src/index.ts +0 -132
  27. package/src/logger.ts +0 -26
  28. package/src/routes/add-mute-operation.ts +0 -154
  29. package/src/routes/add-notif-operation.ts +0 -80
  30. package/src/routes/auth.ts +0 -15
  31. package/src/routes/delete-operations.ts +0 -45
  32. package/src/routes/index.ts +0 -28
  33. package/src/routes/put-operation.ts +0 -115
  34. package/src/routes/scan-mute-operations.ts +0 -65
  35. package/src/routes/scan-notif-operations.ts +0 -64
  36. package/src/routes/scan-operations.ts +0 -67
  37. package/src/routes/util.ts +0 -67
  38. package/tests/delete-operations.test.ts +0 -108
  39. package/tests/mutes.test.ts +0 -352
  40. package/tests/notifications.test.ts +0 -209
  41. package/tests/operations.test.ts +0 -327
  42. package/tsconfig.build.json +0 -8
  43. package/tsconfig.build.tsbuildinfo +0 -1
  44. package/tsconfig.json +0 -7
  45. package/tsconfig.tests.json +0 -7
@@ -1,352 +0,0 @@
1
- import { Code, ConnectError } from '@connectrpc/connect'
2
- import getPort from 'get-port'
3
- import { wait } from '@atproto/common'
4
- import {
5
- BsyncClient,
6
- BsyncService,
7
- Database,
8
- authWithApiKey,
9
- createClient,
10
- envToCfg,
11
- } from '../src/index.js'
12
- import { MuteOperation, MuteOperation_Type } from '../src/proto/bsync_pb.js'
13
-
14
- describe('mutes', () => {
15
- let bsync: BsyncService
16
- let client: BsyncClient
17
-
18
- beforeAll(async () => {
19
- bsync = await BsyncService.create(
20
- envToCfg({
21
- port: await getPort(),
22
- dbUrl: process.env.DB_POSTGRES_URL,
23
- dbSchema: 'bsync_mutes',
24
- apiKeys: ['key-1'],
25
- longPollTimeoutMs: 500,
26
- }),
27
- )
28
- await bsync.ctx.db.migrateToLatestOrThrow()
29
- await bsync.start()
30
- client = createClient({
31
- httpVersion: '1.1',
32
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
33
- interceptors: [authWithApiKey('key-1')],
34
- })
35
- })
36
-
37
- afterAll(async () => {
38
- await bsync.destroy()
39
- })
40
-
41
- beforeEach(async () => {
42
- await clearMutes(bsync.ctx.db)
43
- })
44
-
45
- describe('addMuteOperation', () => {
46
- it('adds mute operations to add mutes.', async () => {
47
- await client.addMuteOperation({
48
- type: MuteOperation_Type.ADD,
49
- actorDid: 'did:example:a',
50
- subject: 'did:example:b',
51
- })
52
- await client.addMuteOperation({
53
- type: MuteOperation_Type.ADD,
54
- actorDid: 'did:example:a',
55
- subject: 'did:example:c',
56
- })
57
- // dupe has no effect
58
- await client.addMuteOperation({
59
- type: MuteOperation_Type.ADD,
60
- actorDid: 'did:example:a',
61
- subject: 'did:example:c',
62
- })
63
- await client.addMuteOperation({
64
- type: MuteOperation_Type.ADD,
65
- actorDid: 'did:example:b',
66
- subject: 'did:example:c',
67
- })
68
- await client.addMuteOperation({
69
- type: MuteOperation_Type.ADD,
70
- actorDid: 'did:example:c',
71
- subject: 'at://did:example:d/app.bsky.graph.list/rkey1',
72
- })
73
- expect(await dumpMuteState(bsync.ctx.db)).toEqual({
74
- 'did:example:a': ['did:example:b', 'did:example:c'],
75
- 'did:example:b': ['did:example:c'],
76
- 'did:example:c': ['at://did:example:d/app.bsky.graph.list/rkey1'],
77
- })
78
- })
79
-
80
- it('adds mute operations to remove mutes.', async () => {
81
- await client.addMuteOperation({
82
- type: MuteOperation_Type.ADD,
83
- actorDid: 'did:example:a',
84
- subject: 'did:example:b',
85
- })
86
- await client.addMuteOperation({
87
- type: MuteOperation_Type.ADD,
88
- actorDid: 'did:example:a',
89
- subject: 'did:example:c',
90
- })
91
- await client.addMuteOperation({
92
- type: MuteOperation_Type.ADD,
93
- actorDid: 'did:example:b',
94
- subject: 'did:example:c',
95
- })
96
- await client.addMuteOperation({
97
- type: MuteOperation_Type.REMOVE,
98
- actorDid: 'did:example:a',
99
- subject: 'did:example:c',
100
- })
101
- // removes nothing
102
- await client.addMuteOperation({
103
- type: MuteOperation_Type.REMOVE,
104
- actorDid: 'did:example:b',
105
- subject: 'did:example:d',
106
- })
107
- expect(await dumpMuteState(bsync.ctx.db)).toEqual({
108
- 'did:example:a': ['did:example:b'],
109
- 'did:example:b': ['did:example:c'],
110
- })
111
- })
112
-
113
- it('adds mute operations to clear mutes.', async () => {
114
- await client.addMuteOperation({
115
- type: MuteOperation_Type.ADD,
116
- actorDid: 'did:example:a',
117
- subject: 'did:example:b',
118
- })
119
- await client.addMuteOperation({
120
- type: MuteOperation_Type.ADD,
121
- actorDid: 'did:example:a',
122
- subject: 'did:example:c',
123
- })
124
- await client.addMuteOperation({
125
- type: MuteOperation_Type.ADD,
126
- actorDid: 'did:example:b',
127
- subject: 'did:example:c',
128
- })
129
- await client.addMuteOperation({
130
- type: MuteOperation_Type.CLEAR,
131
- actorDid: 'did:example:a',
132
- })
133
- expect(await dumpMuteState(bsync.ctx.db)).toEqual({
134
- 'did:example:b': ['did:example:c'],
135
- })
136
- })
137
-
138
- it('fails on bad inputs', async () => {
139
- await expect(
140
- client.addMuteOperation({
141
- type: MuteOperation_Type.ADD,
142
- actorDid: 'did:example:a',
143
- subject: 'invalid',
144
- }),
145
- ).rejects.toEqual(
146
- new ConnectError(
147
- 'subject must be a did or aturi on add or remove op',
148
- Code.InvalidArgument,
149
- ),
150
- )
151
- await expect(
152
- client.addMuteOperation({
153
- type: MuteOperation_Type.ADD,
154
- actorDid: 'did:example:a',
155
- }),
156
- ).rejects.toEqual(
157
- new ConnectError(
158
- 'subject must be a did or aturi on add or remove op',
159
- Code.InvalidArgument,
160
- ),
161
- )
162
- await expect(
163
- client.addMuteOperation({
164
- type: MuteOperation_Type.ADD,
165
- actorDid: 'did:example:a',
166
- subject: 'at://did:example:b/bad.collection/rkey1',
167
- }),
168
- ).rejects.toEqual(
169
- new ConnectError(
170
- 'subject must be a did or aturi on add or remove op',
171
- Code.InvalidArgument,
172
- ),
173
- )
174
- await expect(
175
- client.addMuteOperation({
176
- type: MuteOperation_Type.ADD,
177
- actorDid: 'invalid',
178
- subject: 'did:example:b',
179
- }),
180
- ).rejects.toEqual(
181
- new ConnectError('actor_did must be a valid did', Code.InvalidArgument),
182
- )
183
- await expect(
184
- client.addMuteOperation({
185
- type: MuteOperation_Type.REMOVE,
186
- actorDid: 'did:example:a',
187
- subject: 'invalid',
188
- }),
189
- ).rejects.toEqual(
190
- new ConnectError(
191
- 'subject must be a did or aturi on add or remove op',
192
- Code.InvalidArgument,
193
- ),
194
- )
195
- await expect(
196
- client.addMuteOperation({
197
- type: MuteOperation_Type.CLEAR,
198
- actorDid: 'did:example:a',
199
- subject: 'did:example:b',
200
- }),
201
- ).rejects.toEqual(
202
- new ConnectError(
203
- 'subject must not be set on a clear op',
204
- Code.InvalidArgument,
205
- ),
206
- )
207
- await expect(
208
- client.addMuteOperation({
209
- type: MuteOperation_Type.CLEAR,
210
- actorDid: 'invalid',
211
- }),
212
- ).rejects.toEqual(
213
- new ConnectError('actor_did must be a valid did', Code.InvalidArgument),
214
- )
215
- await expect(
216
- client.addMuteOperation({
217
- type: 100 as any,
218
- actorDid: 'did:example:a',
219
- subject: 'did:example:b',
220
- }),
221
- ).rejects.toEqual(
222
- new ConnectError('bad mute operation type', Code.InvalidArgument),
223
- )
224
- })
225
-
226
- it('requires auth', async () => {
227
- // unauthed
228
- const unauthedClient = createClient({
229
- httpVersion: '1.1',
230
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
231
- })
232
- const tryAddMuteOperation1 = unauthedClient.addMuteOperation({
233
- type: MuteOperation_Type.ADD,
234
- actorDid: 'did:example:a',
235
- subject: 'did:example:b',
236
- })
237
- await expect(tryAddMuteOperation1).rejects.toEqual(
238
- new ConnectError('missing auth', Code.Unauthenticated),
239
- )
240
- // bad auth
241
- const badauthedClient = createClient({
242
- httpVersion: '1.1',
243
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
244
- interceptors: [authWithApiKey('key-bad')],
245
- })
246
- const tryAddMuteOperation2 = badauthedClient.addMuteOperation({
247
- type: MuteOperation_Type.ADD,
248
- actorDid: 'did:example:a',
249
- subject: 'did:example:b',
250
- })
251
- await expect(tryAddMuteOperation2).rejects.toEqual(
252
- new ConnectError('invalid api key', Code.Unauthenticated),
253
- )
254
- })
255
- })
256
-
257
- describe('scanMuteOperations', () => {
258
- it('requires auth', async () => {
259
- // unauthed
260
- const unauthedClient = createClient({
261
- httpVersion: '1.1',
262
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
263
- })
264
- const tryScanMuteOperations1 = unauthedClient.scanMuteOperations({})
265
- await expect(tryScanMuteOperations1).rejects.toEqual(
266
- new ConnectError('missing auth', Code.Unauthenticated),
267
- )
268
- // bad auth
269
- const badauthedClient = createClient({
270
- httpVersion: '1.1',
271
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
272
- interceptors: [authWithApiKey('key-bad')],
273
- })
274
- const tryScanMuteOperations2 = badauthedClient.scanMuteOperations({})
275
- await expect(tryScanMuteOperations2).rejects.toEqual(
276
- new ConnectError('invalid api key', Code.Unauthenticated),
277
- )
278
- })
279
-
280
- it('pages over created mute ops.', async () => {
281
- // add 100 mute ops
282
- for (let i = 0; i < 10; ++i) {
283
- for (let j = 0; j < 8; ++j) {
284
- await client.addMuteOperation({
285
- type: MuteOperation_Type.ADD,
286
- actorDid: `did:example:${i}`,
287
- subject: `did:example:${j}`,
288
- })
289
- }
290
- for (let j = 0; j < 2; ++j) {
291
- await client.addMuteOperation({
292
- type: MuteOperation_Type.ADD,
293
- actorDid: `did:example:${i}`,
294
- subject: `at://did:example:0/app.bsky.graph.list/rkey${j}`,
295
- })
296
- }
297
- }
298
-
299
- let cursor: string | undefined
300
- const operations: MuteOperation[] = []
301
- do {
302
- const res = await client.scanMuteOperations({
303
- cursor,
304
- limit: 30,
305
- })
306
- operations.push(...res.operations)
307
- cursor = res.operations.length ? res.cursor : undefined
308
- } while (cursor)
309
-
310
- expect(operations.length).toEqual(100)
311
- const operationIds = operations.map((op) => parseInt(op.id, 10))
312
- const ascending = (a: number, b: number) => a - b
313
- expect(operationIds).toEqual([...operationIds].sort(ascending))
314
- })
315
-
316
- it('supports long-poll, finding an operation.', async () => {
317
- const scanPromise = client.scanMuteOperations({})
318
- await wait(100) // would be complete by now if it wasn't long-polling for an item
319
- const { operation } = await client.addMuteOperation({
320
- type: MuteOperation_Type.ADD,
321
- actorDid: 'did:example:a',
322
- subject: 'did:example:b',
323
- })
324
- const res = await scanPromise
325
- expect(res.operations.length).toEqual(1)
326
- expect(res.operations[0]).toEqual(operation)
327
- expect(res.cursor).toEqual(operation?.id)
328
- })
329
-
330
- it('supports long-poll, not finding an operation.', async () => {
331
- const res = await client.scanMuteOperations({})
332
- expect(res.cursor).toEqual('')
333
- expect(res.operations).toEqual([])
334
- })
335
- })
336
- })
337
-
338
- const dumpMuteState = async (db: Database) => {
339
- const items = await db.db.selectFrom('mute_item').selectAll().execute()
340
- const result: Record<string, string[]> = {}
341
- items.forEach((item) => {
342
- result[item.actorDid] ??= []
343
- result[item.actorDid].push(item.subject)
344
- })
345
- Object.values(result).forEach((subjects) => subjects.sort())
346
- return result
347
- }
348
-
349
- const clearMutes = async (db: Database) => {
350
- await db.db.deleteFrom('mute_item').execute()
351
- await db.db.deleteFrom('mute_op').execute()
352
- }
@@ -1,209 +0,0 @@
1
- import { Code, ConnectError } from '@connectrpc/connect'
2
- import getPort from 'get-port'
3
- import { wait } from '@atproto/common'
4
- import {
5
- BsyncClient,
6
- BsyncService,
7
- Database,
8
- authWithApiKey,
9
- createClient,
10
- envToCfg,
11
- } from '../src/index.js'
12
- import { NotifOperation } from '../src/proto/bsync_pb.js'
13
-
14
- describe('notifications', () => {
15
- let bsync: BsyncService
16
- let client: BsyncClient
17
-
18
- beforeAll(async () => {
19
- bsync = await BsyncService.create(
20
- envToCfg({
21
- port: await getPort(),
22
- dbUrl: process.env.DB_POSTGRES_URL,
23
- dbSchema: 'bsync_notifications',
24
- apiKeys: ['key-1'],
25
- longPollTimeoutMs: 500,
26
- }),
27
- )
28
- await bsync.ctx.db.migrateToLatestOrThrow()
29
- await bsync.start()
30
- client = createClient({
31
- httpVersion: '1.1',
32
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
33
- interceptors: [authWithApiKey('key-1')],
34
- })
35
- })
36
-
37
- afterAll(async () => {
38
- await bsync.destroy()
39
- })
40
-
41
- beforeEach(async () => {
42
- await clearNotifs(bsync.ctx.db)
43
- })
44
-
45
- describe('addNotifOperation', () => {
46
- it('adds notif operations to set priority.', async () => {
47
- // true + true
48
- await client.addNotifOperation({
49
- actorDid: 'did:example:a',
50
- priority: true,
51
- })
52
- await client.addNotifOperation({
53
- actorDid: 'did:example:a',
54
- priority: true,
55
- })
56
- // true + none
57
- await client.addNotifOperation({
58
- actorDid: 'did:example:b',
59
- priority: true,
60
- })
61
- await client.addNotifOperation({
62
- actorDid: 'did:example:b',
63
- })
64
- // true + false
65
- await client.addNotifOperation({
66
- actorDid: 'did:example:c',
67
- priority: true,
68
- })
69
- await client.addNotifOperation({
70
- actorDid: 'did:example:c',
71
- priority: false,
72
- })
73
- // false + true
74
- await client.addNotifOperation({
75
- actorDid: 'did:example:d',
76
- priority: false,
77
- })
78
- await client.addNotifOperation({
79
- actorDid: 'did:example:d',
80
- priority: true,
81
- })
82
- expect(await dumpNotifState(bsync.ctx.db)).toEqual({
83
- 'did:example:a': true,
84
- 'did:example:b': true,
85
- 'did:example:c': false,
86
- 'did:example:d': true,
87
- })
88
- })
89
-
90
- it('fails on bad inputs', async () => {
91
- await expect(
92
- client.addNotifOperation({
93
- actorDid: 'invalid',
94
- priority: true,
95
- }),
96
- ).rejects.toEqual(
97
- new ConnectError('actor_did must be a valid did', Code.InvalidArgument),
98
- )
99
- })
100
-
101
- it('requires auth', async () => {
102
- // unauthed
103
- const unauthedClient = createClient({
104
- httpVersion: '1.1',
105
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
106
- })
107
- const tryAddNotifOperation1 = unauthedClient.addNotifOperation({
108
- actorDid: 'did:example:a',
109
- })
110
- await expect(tryAddNotifOperation1).rejects.toEqual(
111
- new ConnectError('missing auth', Code.Unauthenticated),
112
- )
113
- // bad auth
114
- const badauthedClient = createClient({
115
- httpVersion: '1.1',
116
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
117
- interceptors: [authWithApiKey('key-bad')],
118
- })
119
- const tryAddNotifOperation2 = badauthedClient.addNotifOperation({
120
- actorDid: 'did:example:a',
121
- })
122
- await expect(tryAddNotifOperation2).rejects.toEqual(
123
- new ConnectError('invalid api key', Code.Unauthenticated),
124
- )
125
- })
126
- })
127
-
128
- describe('scanNotifOperations', () => {
129
- it('requires auth', async () => {
130
- // unauthed
131
- const unauthedClient = createClient({
132
- httpVersion: '1.1',
133
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
134
- })
135
- const tryScanNotifOperations1 = unauthedClient.scanNotifOperations({})
136
- await expect(tryScanNotifOperations1).rejects.toEqual(
137
- new ConnectError('missing auth', Code.Unauthenticated),
138
- )
139
- // bad auth
140
- const badauthedClient = createClient({
141
- httpVersion: '1.1',
142
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
143
- interceptors: [authWithApiKey('key-bad')],
144
- })
145
- const tryScanNotifOperations2 = badauthedClient.scanNotifOperations({})
146
- await expect(tryScanNotifOperations2).rejects.toEqual(
147
- new ConnectError('invalid api key', Code.Unauthenticated),
148
- )
149
- })
150
-
151
- it('pages over created notif ops.', async () => {
152
- // add 100 notif ops
153
- for (let i = 0; i < 100; ++i) {
154
- await client.addNotifOperation({
155
- actorDid: `did:example:${i}`,
156
- priority: i % 2 === 0,
157
- })
158
- }
159
-
160
- let cursor: string | undefined
161
- const operations: NotifOperation[] = []
162
- do {
163
- const res = await client.scanNotifOperations({
164
- cursor,
165
- limit: 30,
166
- })
167
- operations.push(...res.operations)
168
- cursor = res.operations.length ? res.cursor : undefined
169
- } while (cursor)
170
-
171
- expect(operations.length).toEqual(100)
172
- const operationIds = operations.map((op) => parseInt(op.id, 10))
173
- const ascending = (a: number, b: number) => a - b
174
- expect(operationIds).toEqual([...operationIds].sort(ascending))
175
- })
176
-
177
- it('supports long-poll, finding an operation.', async () => {
178
- const scanPromise = client.scanNotifOperations({})
179
- await wait(100) // would be complete by now if it wasn't long-polling for an item
180
- const { operation } = await client.addNotifOperation({
181
- actorDid: 'did:example:a',
182
- })
183
- const res = await scanPromise
184
- expect(res.operations.length).toEqual(1)
185
- expect(res.operations[0]).toEqual(operation)
186
- expect(res.cursor).toEqual(operation?.id)
187
- })
188
-
189
- it('supports long-poll, not finding an operation.', async () => {
190
- const res = await client.scanNotifOperations({})
191
- expect(res.cursor).toEqual('')
192
- expect(res.operations).toEqual([])
193
- })
194
- })
195
- })
196
-
197
- const dumpNotifState = async (db: Database) => {
198
- const items = await db.db.selectFrom('notif_item').selectAll().execute()
199
- const result: Record<string, boolean> = {}
200
- items.forEach((item) => {
201
- result[item.actorDid] = item.priority
202
- })
203
- return result
204
- }
205
-
206
- const clearNotifs = async (db: Database) => {
207
- await db.db.deleteFrom('notif_item').execute()
208
- await db.db.deleteFrom('notif_op').execute()
209
- }