@atproto/bsync 0.0.31 → 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 (48) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/logger.js +2 -2
  6. package/dist/logger.js.map +1 -1
  7. package/package.json +17 -13
  8. package/bin/migration-create.ts +0 -38
  9. package/buf.gen.yaml +0 -12
  10. package/jest.config.cjs +0 -21
  11. package/proto/bsync.proto +0 -134
  12. package/src/client.ts +0 -25
  13. package/src/config.ts +0 -90
  14. package/src/context.ts +0 -48
  15. package/src/db/index.ts +0 -200
  16. package/src/db/migrations/20240108T220751294Z-init.ts +0 -26
  17. package/src/db/migrations/20240717T224303472Z-notif-ops.ts +0 -24
  18. package/src/db/migrations/20250527T022203400Z-add-operation.ts +0 -20
  19. package/src/db/migrations/20250603T163446567Z-alter-operation.ts +0 -19
  20. package/src/db/migrations/index.ts +0 -8
  21. package/src/db/migrations/provider.ts +0 -8
  22. package/src/db/schema/index.ts +0 -16
  23. package/src/db/schema/mute_item.ts +0 -13
  24. package/src/db/schema/mute_op.ts +0 -18
  25. package/src/db/schema/notif_item.ts +0 -13
  26. package/src/db/schema/notif_op.ts +0 -16
  27. package/src/db/schema/operation.ts +0 -20
  28. package/src/db/types.ts +0 -19
  29. package/src/index.ts +0 -130
  30. package/src/logger.ts +0 -26
  31. package/src/routes/add-mute-operation.ts +0 -154
  32. package/src/routes/add-notif-operation.ts +0 -80
  33. package/src/routes/auth.ts +0 -15
  34. package/src/routes/delete-operations.ts +0 -45
  35. package/src/routes/index.ts +0 -28
  36. package/src/routes/put-operation.ts +0 -115
  37. package/src/routes/scan-mute-operations.ts +0 -65
  38. package/src/routes/scan-notif-operations.ts +0 -64
  39. package/src/routes/scan-operations.ts +0 -67
  40. package/src/routes/util.ts +0 -67
  41. package/tests/delete-operations.test.ts +0 -108
  42. package/tests/mutes.test.ts +0 -352
  43. package/tests/notifications.test.ts +0 -209
  44. package/tests/operations.test.ts +0 -327
  45. package/tsconfig.build.json +0 -8
  46. package/tsconfig.build.tsbuildinfo +0 -1
  47. package/tsconfig.json +0 -7
  48. package/tsconfig.tests.json +0 -7
@@ -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
- }
@@ -1,327 +0,0 @@
1
- import assert from 'node:assert'
2
- import { Code, ConnectError } from '@connectrpc/connect'
3
- import getPort from 'get-port'
4
- import { wait } from '@atproto/common'
5
- import {
6
- BsyncClient,
7
- BsyncService,
8
- Database,
9
- authWithApiKey,
10
- createClient,
11
- envToCfg,
12
- } from '../src/index.js'
13
- import { Method, Operation } from '../src/proto/bsync_pb.js'
14
-
15
- describe('operations', () => {
16
- let bsync: BsyncService
17
- let client: BsyncClient
18
-
19
- const validPayload0 = Buffer.from(JSON.stringify({ value: 0 }))
20
- const validPayload1 = Buffer.from(JSON.stringify({ value: 1 }))
21
- const invalidPayload = Buffer.from('{invalid json}')
22
-
23
- beforeAll(async () => {
24
- bsync = await BsyncService.create(
25
- envToCfg({
26
- port: await getPort(),
27
- dbUrl: process.env.DB_POSTGRES_URL,
28
- dbSchema: 'bsync_operations',
29
- apiKeys: ['key-1'],
30
- longPollTimeoutMs: 500,
31
- }),
32
- )
33
- await bsync.ctx.db.migrateToLatestOrThrow()
34
- await bsync.start()
35
- client = createClient({
36
- httpVersion: '1.1',
37
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
38
- interceptors: [authWithApiKey('key-1')],
39
- })
40
- })
41
-
42
- afterAll(async () => {
43
- await bsync.destroy()
44
- })
45
-
46
- beforeEach(async () => {
47
- await clearOps(bsync.ctx.db)
48
- })
49
-
50
- describe('putOperation', () => {
51
- it('requires auth.', async () => {
52
- // unauthed
53
- const unauthedClient = createClient({
54
- httpVersion: '1.1',
55
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
56
- })
57
- const tryPutOperation1 = unauthedClient.putOperation({
58
- actorDid: 'did:example:a',
59
- namespace: 'app.bsky.some.col',
60
- key: 'key1',
61
- method: Method.CREATE,
62
- payload: validPayload0,
63
- })
64
- await expect(tryPutOperation1).rejects.toEqual(
65
- new ConnectError('missing auth', Code.Unauthenticated),
66
- )
67
- // bad auth
68
- const badauthedClient = createClient({
69
- httpVersion: '1.1',
70
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
71
- interceptors: [authWithApiKey('key-bad')],
72
- })
73
- const tryPutOperation2 = badauthedClient.putOperation({
74
- actorDid: 'did:example:a',
75
- namespace: 'app.bsky.some.col',
76
- key: 'key1',
77
- method: Method.CREATE,
78
- payload: validPayload0,
79
- })
80
- await expect(tryPutOperation2).rejects.toEqual(
81
- new ConnectError('invalid api key', Code.Unauthenticated),
82
- )
83
- })
84
-
85
- it('fails on bad inputs.', async () => {
86
- await expect(
87
- client.putOperation({
88
- actorDid: 'did:example:a',
89
- namespace: 'bad-namespace',
90
- key: 'key1',
91
- method: Method.CREATE,
92
- payload: validPayload0,
93
- }),
94
- ).rejects.toEqual(
95
- new ConnectError(
96
- 'operation namespace is invalid NSID',
97
- Code.InvalidArgument,
98
- ),
99
- )
100
- await expect(
101
- client.putOperation({
102
- actorDid: 'bad-did',
103
- namespace: 'app.bsky.some.col',
104
- key: 'key1',
105
- method: Method.CREATE,
106
- payload: validPayload0,
107
- }),
108
- ).rejects.toEqual(
109
- new ConnectError(
110
- 'operation actor_did is invalid DID',
111
- Code.InvalidArgument,
112
- ),
113
- )
114
- await expect(
115
- client.putOperation({
116
- actorDid: 'did:example:a',
117
- namespace: 'app.bsky.some.col',
118
- key: '',
119
- method: Method.CREATE,
120
- payload: validPayload0,
121
- }),
122
- ).rejects.toEqual(
123
- new ConnectError('operation key is required', Code.InvalidArgument),
124
- )
125
- await expect(
126
- client.putOperation({
127
- actorDid: 'did:example:a',
128
- namespace: 'app.bsky.some.col',
129
- key: 'key1',
130
- method: Method.UNSPECIFIED,
131
- payload: validPayload0,
132
- }),
133
- ).rejects.toEqual(
134
- new ConnectError('operation method is invalid', Code.InvalidArgument),
135
- )
136
- await expect(
137
- client.putOperation({
138
- actorDid: 'did:example:a',
139
- namespace: 'app.bsky.some.col',
140
- key: 'key1',
141
- method: Method.CREATE,
142
- payload: invalidPayload,
143
- }),
144
- ).rejects.toEqual(
145
- new ConnectError(
146
- 'payload must be a valid JSON when method is CREATE or UPDATE',
147
- Code.InvalidArgument,
148
- ),
149
- )
150
- await expect(
151
- client.putOperation({
152
- actorDid: 'did:example:a',
153
- namespace: 'app.bsky.some.col',
154
- key: 'key1',
155
- method: Method.UPDATE,
156
- payload: invalidPayload,
157
- }),
158
- ).rejects.toEqual(
159
- new ConnectError(
160
- 'payload must be a valid JSON when method is CREATE or UPDATE',
161
- Code.InvalidArgument,
162
- ),
163
- )
164
- await expect(
165
- client.putOperation({
166
- actorDid: 'did:example:a',
167
- namespace: 'app.bsky.some.col',
168
- key: 'key1',
169
- method: Method.DELETE,
170
- payload: validPayload0,
171
- }),
172
- ).rejects.toEqual(
173
- new ConnectError(
174
- 'cannot specify a payload when method is DELETE',
175
- Code.InvalidArgument,
176
- ),
177
- )
178
- })
179
-
180
- it('puts operations.', async () => {
181
- const res1 = await client.putOperation({
182
- actorDid: 'did:example:a',
183
- namespace: 'app.bsky.some.col',
184
- key: 'key1',
185
- method: Method.CREATE,
186
- payload: validPayload0,
187
- })
188
- const res2 = await client.putOperation({
189
- actorDid: 'did:example:a',
190
- namespace: 'app.bsky.other.col#id',
191
- key: 'key1',
192
- method: Method.UPDATE,
193
- payload: validPayload1,
194
- })
195
-
196
- expect(res1.operation?.id).toBe('1')
197
- expect(res2.operation?.id).toBe('2')
198
- expect(await dumpOps(bsync.ctx.db)).toStrictEqual([
199
- {
200
- id: 1,
201
- actorDid: 'did:example:a',
202
- namespace: 'app.bsky.some.col',
203
- key: 'key1',
204
- method: Method.CREATE,
205
- payload: validPayload0,
206
- createdAt: expect.any(Date),
207
- },
208
- {
209
- id: 2,
210
- actorDid: 'did:example:a',
211
- namespace: 'app.bsky.other.col#id',
212
- key: 'key1',
213
- method: Method.UPDATE,
214
- payload: validPayload1,
215
- createdAt: expect.any(Date),
216
- },
217
- ])
218
- })
219
-
220
- it('returns the operations on creation.', async () => {
221
- const res = await client.putOperation({
222
- actorDid: 'did:example:a',
223
- namespace: 'app.bsky.some.col',
224
- key: 'key1',
225
- method: Method.CREATE,
226
- payload: validPayload0,
227
- })
228
-
229
- const op = res.operation
230
- assert(op)
231
- // Compare each field individually to avoid custom serialization by proto response objects.
232
- expect(op.id).toBe('3')
233
- expect(op.actorDid).toBe('did:example:a')
234
- expect(op.namespace).toBe('app.bsky.some.col')
235
- expect(op.key).toBe('key1')
236
- expect(op.method).toBe(Method.CREATE)
237
- expect(op.payload).toEqual(new Uint8Array(validPayload0))
238
- })
239
- })
240
-
241
- describe('scanOperations', () => {
242
- it('requires auth.', async () => {
243
- // unauthed
244
- const unauthedClient = createClient({
245
- httpVersion: '1.1',
246
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
247
- })
248
- const tryScanOperations1 = unauthedClient.scanOperations({})
249
- await expect(tryScanOperations1).rejects.toEqual(
250
- new ConnectError('missing auth', Code.Unauthenticated),
251
- )
252
- // bad auth
253
- const badauthedClient = createClient({
254
- httpVersion: '1.1',
255
- baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
256
- interceptors: [authWithApiKey('key-bad')],
257
- })
258
- const tryScanOperations2 = badauthedClient.scanOperations({})
259
- await expect(tryScanOperations2).rejects.toEqual(
260
- new ConnectError('invalid api key', Code.Unauthenticated),
261
- )
262
- })
263
-
264
- it('pages over created ops.', async () => {
265
- // add 100 ops
266
- for (let i = 0; i < 100; ++i) {
267
- await client.putOperation({
268
- actorDid: `did:example:${i}`,
269
- namespace: 'app.bsky.some.col',
270
- key: 'key1',
271
- method: Method.CREATE,
272
- payload: validPayload0,
273
- })
274
- }
275
-
276
- let cursor: string | undefined
277
- const operations: Operation[] = []
278
- do {
279
- const res = await client.scanOperations({
280
- cursor,
281
- limit: 30,
282
- })
283
- operations.push(...res.operations)
284
- cursor = res.operations.length ? res.cursor : undefined
285
- } while (cursor)
286
-
287
- expect(operations.length).toEqual(100)
288
- const operationIds = operations.map((op) => parseInt(op.id, 10))
289
- const ascending = (a: number, b: number) => a - b
290
- expect(operationIds).toEqual([...operationIds].sort(ascending))
291
- })
292
-
293
- it('supports long-poll, finding an operation.', async () => {
294
- const scanPromise = client.scanOperations({})
295
- await wait(100) // would be complete by now if it wasn't long-polling for an item
296
- const { operation } = await client.putOperation({
297
- actorDid: 'did:example:a',
298
- namespace: 'app.bsky.some.col',
299
- key: 'key1',
300
- method: Method.CREATE,
301
- payload: validPayload0,
302
- })
303
- const res = await scanPromise
304
- expect(res.operations.length).toEqual(1)
305
- expect(res.operations[0]).toEqual(operation)
306
- expect(res.cursor).toEqual(operation?.id)
307
- })
308
-
309
- it('supports long-poll, not finding an operation.', async () => {
310
- const res = await client.scanOperations({})
311
- expect(res.cursor).toEqual('')
312
- expect(res.operations).toEqual([])
313
- })
314
- })
315
- })
316
-
317
- const dumpOps = async (db: Database) => {
318
- return db.db
319
- .selectFrom('operation')
320
- .selectAll()
321
- .orderBy('id', 'asc')
322
- .execute()
323
- }
324
-
325
- const clearOps = async (db: Database) => {
326
- await db.db.deleteFrom('operation').execute()
327
- }
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../tsconfig/node.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "outDir": "./dist",
6
- },
7
- "include": ["./src"],
8
- }
@@ -1 +0,0 @@
1
- {"version":"7.0.0-dev.20260614.1","root":["./src/client.ts","./src/config.ts","./src/context.ts","./src/index.ts","./src/logger.ts","./src/db/index.ts","./src/db/types.ts","./src/db/migrations/20240108T220751294Z-init.ts","./src/db/migrations/20240717T224303472Z-notif-ops.ts","./src/db/migrations/20250527T022203400Z-add-operation.ts","./src/db/migrations/20250603T163446567Z-alter-operation.ts","./src/db/migrations/index.ts","./src/db/migrations/provider.ts","./src/db/schema/index.ts","./src/db/schema/mute_item.ts","./src/db/schema/mute_op.ts","./src/db/schema/notif_item.ts","./src/db/schema/notif_op.ts","./src/db/schema/operation.ts","./src/proto/bsync_connect.ts","./src/proto/bsync_pb.ts","./src/routes/add-mute-operation.ts","./src/routes/add-notif-operation.ts","./src/routes/auth.ts","./src/routes/delete-operations.ts","./src/routes/index.ts","./src/routes/put-operation.ts","./src/routes/scan-mute-operations.ts","./src/routes/scan-notif-operations.ts","./src/routes/scan-operations.ts","./src/routes/util.ts"]}
package/tsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "include": [],
3
- "references": [
4
- { "path": "./tsconfig.build.json" },
5
- { "path": "./tsconfig.tests.json" },
6
- ],
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "extends": "../../tsconfig/tests.json",
3
- "compilerOptions": {
4
- "rootDir": ".",
5
- },
6
- "include": ["./tests"],
7
- }