@atproto/bsync 0.0.0

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 (52) hide show
  1. package/LICENSE.txt +7 -0
  2. package/README.md +15 -0
  3. package/babel.config.js +3 -0
  4. package/bin/migration-create.ts +38 -0
  5. package/buf.gen.yaml +13 -0
  6. package/build.js +18 -0
  7. package/dist/client.d.ts +6 -0
  8. package/dist/config.d.ts +36 -0
  9. package/dist/context.d.ts +19 -0
  10. package/dist/db/index.d.ts +31 -0
  11. package/dist/db/migrations/20240108T220751294Z-init.d.ts +3 -0
  12. package/dist/db/migrations/index.d.ts +1 -0
  13. package/dist/db/migrations/provider.d.ts +6 -0
  14. package/dist/db/schema/index.d.ts +6 -0
  15. package/dist/db/schema/mute_item.d.ts +11 -0
  16. package/dist/db/schema/mute_op.d.ts +15 -0
  17. package/dist/db/types.d.ts +12 -0
  18. package/dist/gen/bsync_connect.d.ts +25 -0
  19. package/dist/gen/bsync_pb.d.ts +90 -0
  20. package/dist/index.d.ts +26 -0
  21. package/dist/index.js +76455 -0
  22. package/dist/index.js.map +7 -0
  23. package/dist/logger.d.ts +4 -0
  24. package/dist/routes/add-mute-operation.d.ts +5 -0
  25. package/dist/routes/auth.d.ts +3 -0
  26. package/dist/routes/index.d.ts +4 -0
  27. package/dist/routes/scan-mute-operations.d.ts +5 -0
  28. package/jest.config.js +6 -0
  29. package/package.json +49 -0
  30. package/proto/bsync.proto +55 -0
  31. package/src/client.ts +25 -0
  32. package/src/config.ts +90 -0
  33. package/src/context.ts +42 -0
  34. package/src/db/index.ts +194 -0
  35. package/src/db/migrations/20240108T220751294Z-init.ts +26 -0
  36. package/src/db/migrations/index.ts +5 -0
  37. package/src/db/migrations/provider.ts +8 -0
  38. package/src/db/schema/index.ts +9 -0
  39. package/src/db/schema/mute_item.ts +13 -0
  40. package/src/db/schema/mute_op.ts +18 -0
  41. package/src/db/types.ts +15 -0
  42. package/src/gen/bsync_connect.ts +54 -0
  43. package/src/gen/bsync_pb.ts +459 -0
  44. package/src/index.ts +91 -0
  45. package/src/logger.ts +22 -0
  46. package/src/routes/add-mute-operation.ts +173 -0
  47. package/src/routes/auth.ts +15 -0
  48. package/src/routes/index.ts +18 -0
  49. package/src/routes/scan-mute-operations.ts +69 -0
  50. package/tests/mutes.test.ts +350 -0
  51. package/tsconfig.build.json +4 -0
  52. package/tsconfig.json +14 -0
@@ -0,0 +1,459 @@
1
+ // @generated by protoc-gen-es v1.6.0 with parameter "target=ts,import_extension=.ts"
2
+ // @generated from file bsync.proto (package bsync, syntax proto3)
3
+ /* eslint-disable */
4
+ // @ts-nocheck
5
+
6
+ import type {
7
+ BinaryReadOptions,
8
+ FieldList,
9
+ JsonReadOptions,
10
+ JsonValue,
11
+ PartialMessage,
12
+ PlainMessage,
13
+ } from '@bufbuild/protobuf'
14
+ import { Message, proto3 } from '@bufbuild/protobuf'
15
+
16
+ /**
17
+ * @generated from message bsync.MuteOperation
18
+ */
19
+ export class MuteOperation extends Message<MuteOperation> {
20
+ /**
21
+ * @generated from field: string id = 1;
22
+ */
23
+ id = ''
24
+
25
+ /**
26
+ * @generated from field: bsync.MuteOperation.Type type = 2;
27
+ */
28
+ type = MuteOperation_Type.UNSPECIFIED
29
+
30
+ /**
31
+ * @generated from field: string actor_did = 3;
32
+ */
33
+ actorDid = ''
34
+
35
+ /**
36
+ * @generated from field: string subject = 4;
37
+ */
38
+ subject = ''
39
+
40
+ constructor(data?: PartialMessage<MuteOperation>) {
41
+ super()
42
+ proto3.util.initPartial(data, this)
43
+ }
44
+
45
+ static readonly runtime: typeof proto3 = proto3
46
+ static readonly typeName = 'bsync.MuteOperation'
47
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
48
+ { no: 1, name: 'id', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
49
+ {
50
+ no: 2,
51
+ name: 'type',
52
+ kind: 'enum',
53
+ T: proto3.getEnumType(MuteOperation_Type),
54
+ },
55
+ { no: 3, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
56
+ { no: 4, name: 'subject', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
57
+ ])
58
+
59
+ static fromBinary(
60
+ bytes: Uint8Array,
61
+ options?: Partial<BinaryReadOptions>,
62
+ ): MuteOperation {
63
+ return new MuteOperation().fromBinary(bytes, options)
64
+ }
65
+
66
+ static fromJson(
67
+ jsonValue: JsonValue,
68
+ options?: Partial<JsonReadOptions>,
69
+ ): MuteOperation {
70
+ return new MuteOperation().fromJson(jsonValue, options)
71
+ }
72
+
73
+ static fromJsonString(
74
+ jsonString: string,
75
+ options?: Partial<JsonReadOptions>,
76
+ ): MuteOperation {
77
+ return new MuteOperation().fromJsonString(jsonString, options)
78
+ }
79
+
80
+ static equals(
81
+ a: MuteOperation | PlainMessage<MuteOperation> | undefined,
82
+ b: MuteOperation | PlainMessage<MuteOperation> | undefined,
83
+ ): boolean {
84
+ return proto3.util.equals(MuteOperation, a, b)
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @generated from enum bsync.MuteOperation.Type
90
+ */
91
+ export enum MuteOperation_Type {
92
+ /**
93
+ * @generated from enum value: TYPE_UNSPECIFIED = 0;
94
+ */
95
+ UNSPECIFIED = 0,
96
+
97
+ /**
98
+ * @generated from enum value: TYPE_ADD = 1;
99
+ */
100
+ ADD = 1,
101
+
102
+ /**
103
+ * @generated from enum value: TYPE_REMOVE = 2;
104
+ */
105
+ REMOVE = 2,
106
+
107
+ /**
108
+ * @generated from enum value: TYPE_CLEAR = 3;
109
+ */
110
+ CLEAR = 3,
111
+ }
112
+ // Retrieve enum metadata with: proto3.getEnumType(MuteOperation_Type)
113
+ proto3.util.setEnumType(MuteOperation_Type, 'bsync.MuteOperation.Type', [
114
+ { no: 0, name: 'TYPE_UNSPECIFIED' },
115
+ { no: 1, name: 'TYPE_ADD' },
116
+ { no: 2, name: 'TYPE_REMOVE' },
117
+ { no: 3, name: 'TYPE_CLEAR' },
118
+ ])
119
+
120
+ /**
121
+ * @generated from message bsync.AddMuteOperationRequest
122
+ */
123
+ export class AddMuteOperationRequest extends Message<AddMuteOperationRequest> {
124
+ /**
125
+ * @generated from field: bsync.MuteOperation.Type type = 1;
126
+ */
127
+ type = MuteOperation_Type.UNSPECIFIED
128
+
129
+ /**
130
+ * @generated from field: string actor_did = 2;
131
+ */
132
+ actorDid = ''
133
+
134
+ /**
135
+ * @generated from field: string subject = 3;
136
+ */
137
+ subject = ''
138
+
139
+ constructor(data?: PartialMessage<AddMuteOperationRequest>) {
140
+ super()
141
+ proto3.util.initPartial(data, this)
142
+ }
143
+
144
+ static readonly runtime: typeof proto3 = proto3
145
+ static readonly typeName = 'bsync.AddMuteOperationRequest'
146
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
147
+ {
148
+ no: 1,
149
+ name: 'type',
150
+ kind: 'enum',
151
+ T: proto3.getEnumType(MuteOperation_Type),
152
+ },
153
+ { no: 2, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
154
+ { no: 3, name: 'subject', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
155
+ ])
156
+
157
+ static fromBinary(
158
+ bytes: Uint8Array,
159
+ options?: Partial<BinaryReadOptions>,
160
+ ): AddMuteOperationRequest {
161
+ return new AddMuteOperationRequest().fromBinary(bytes, options)
162
+ }
163
+
164
+ static fromJson(
165
+ jsonValue: JsonValue,
166
+ options?: Partial<JsonReadOptions>,
167
+ ): AddMuteOperationRequest {
168
+ return new AddMuteOperationRequest().fromJson(jsonValue, options)
169
+ }
170
+
171
+ static fromJsonString(
172
+ jsonString: string,
173
+ options?: Partial<JsonReadOptions>,
174
+ ): AddMuteOperationRequest {
175
+ return new AddMuteOperationRequest().fromJsonString(jsonString, options)
176
+ }
177
+
178
+ static equals(
179
+ a:
180
+ | AddMuteOperationRequest
181
+ | PlainMessage<AddMuteOperationRequest>
182
+ | undefined,
183
+ b:
184
+ | AddMuteOperationRequest
185
+ | PlainMessage<AddMuteOperationRequest>
186
+ | undefined,
187
+ ): boolean {
188
+ return proto3.util.equals(AddMuteOperationRequest, a, b)
189
+ }
190
+ }
191
+
192
+ /**
193
+ * @generated from message bsync.AddMuteOperationResponse
194
+ */
195
+ export class AddMuteOperationResponse extends Message<AddMuteOperationResponse> {
196
+ /**
197
+ * @generated from field: bsync.MuteOperation operation = 1;
198
+ */
199
+ operation?: MuteOperation
200
+
201
+ constructor(data?: PartialMessage<AddMuteOperationResponse>) {
202
+ super()
203
+ proto3.util.initPartial(data, this)
204
+ }
205
+
206
+ static readonly runtime: typeof proto3 = proto3
207
+ static readonly typeName = 'bsync.AddMuteOperationResponse'
208
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
209
+ { no: 1, name: 'operation', kind: 'message', T: MuteOperation },
210
+ ])
211
+
212
+ static fromBinary(
213
+ bytes: Uint8Array,
214
+ options?: Partial<BinaryReadOptions>,
215
+ ): AddMuteOperationResponse {
216
+ return new AddMuteOperationResponse().fromBinary(bytes, options)
217
+ }
218
+
219
+ static fromJson(
220
+ jsonValue: JsonValue,
221
+ options?: Partial<JsonReadOptions>,
222
+ ): AddMuteOperationResponse {
223
+ return new AddMuteOperationResponse().fromJson(jsonValue, options)
224
+ }
225
+
226
+ static fromJsonString(
227
+ jsonString: string,
228
+ options?: Partial<JsonReadOptions>,
229
+ ): AddMuteOperationResponse {
230
+ return new AddMuteOperationResponse().fromJsonString(jsonString, options)
231
+ }
232
+
233
+ static equals(
234
+ a:
235
+ | AddMuteOperationResponse
236
+ | PlainMessage<AddMuteOperationResponse>
237
+ | undefined,
238
+ b:
239
+ | AddMuteOperationResponse
240
+ | PlainMessage<AddMuteOperationResponse>
241
+ | undefined,
242
+ ): boolean {
243
+ return proto3.util.equals(AddMuteOperationResponse, a, b)
244
+ }
245
+ }
246
+
247
+ /**
248
+ * @generated from message bsync.ScanMuteOperationsRequest
249
+ */
250
+ export class ScanMuteOperationsRequest extends Message<ScanMuteOperationsRequest> {
251
+ /**
252
+ * @generated from field: string cursor = 1;
253
+ */
254
+ cursor = ''
255
+
256
+ /**
257
+ * @generated from field: int32 limit = 2;
258
+ */
259
+ limit = 0
260
+
261
+ constructor(data?: PartialMessage<ScanMuteOperationsRequest>) {
262
+ super()
263
+ proto3.util.initPartial(data, this)
264
+ }
265
+
266
+ static readonly runtime: typeof proto3 = proto3
267
+ static readonly typeName = 'bsync.ScanMuteOperationsRequest'
268
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
269
+ { no: 1, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
270
+ { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ },
271
+ ])
272
+
273
+ static fromBinary(
274
+ bytes: Uint8Array,
275
+ options?: Partial<BinaryReadOptions>,
276
+ ): ScanMuteOperationsRequest {
277
+ return new ScanMuteOperationsRequest().fromBinary(bytes, options)
278
+ }
279
+
280
+ static fromJson(
281
+ jsonValue: JsonValue,
282
+ options?: Partial<JsonReadOptions>,
283
+ ): ScanMuteOperationsRequest {
284
+ return new ScanMuteOperationsRequest().fromJson(jsonValue, options)
285
+ }
286
+
287
+ static fromJsonString(
288
+ jsonString: string,
289
+ options?: Partial<JsonReadOptions>,
290
+ ): ScanMuteOperationsRequest {
291
+ return new ScanMuteOperationsRequest().fromJsonString(jsonString, options)
292
+ }
293
+
294
+ static equals(
295
+ a:
296
+ | ScanMuteOperationsRequest
297
+ | PlainMessage<ScanMuteOperationsRequest>
298
+ | undefined,
299
+ b:
300
+ | ScanMuteOperationsRequest
301
+ | PlainMessage<ScanMuteOperationsRequest>
302
+ | undefined,
303
+ ): boolean {
304
+ return proto3.util.equals(ScanMuteOperationsRequest, a, b)
305
+ }
306
+ }
307
+
308
+ /**
309
+ * @generated from message bsync.ScanMuteOperationsResponse
310
+ */
311
+ export class ScanMuteOperationsResponse extends Message<ScanMuteOperationsResponse> {
312
+ /**
313
+ * @generated from field: repeated bsync.MuteOperation operations = 1;
314
+ */
315
+ operations: MuteOperation[] = []
316
+
317
+ /**
318
+ * @generated from field: string cursor = 2;
319
+ */
320
+ cursor = ''
321
+
322
+ constructor(data?: PartialMessage<ScanMuteOperationsResponse>) {
323
+ super()
324
+ proto3.util.initPartial(data, this)
325
+ }
326
+
327
+ static readonly runtime: typeof proto3 = proto3
328
+ static readonly typeName = 'bsync.ScanMuteOperationsResponse'
329
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
330
+ {
331
+ no: 1,
332
+ name: 'operations',
333
+ kind: 'message',
334
+ T: MuteOperation,
335
+ repeated: true,
336
+ },
337
+ { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
338
+ ])
339
+
340
+ static fromBinary(
341
+ bytes: Uint8Array,
342
+ options?: Partial<BinaryReadOptions>,
343
+ ): ScanMuteOperationsResponse {
344
+ return new ScanMuteOperationsResponse().fromBinary(bytes, options)
345
+ }
346
+
347
+ static fromJson(
348
+ jsonValue: JsonValue,
349
+ options?: Partial<JsonReadOptions>,
350
+ ): ScanMuteOperationsResponse {
351
+ return new ScanMuteOperationsResponse().fromJson(jsonValue, options)
352
+ }
353
+
354
+ static fromJsonString(
355
+ jsonString: string,
356
+ options?: Partial<JsonReadOptions>,
357
+ ): ScanMuteOperationsResponse {
358
+ return new ScanMuteOperationsResponse().fromJsonString(jsonString, options)
359
+ }
360
+
361
+ static equals(
362
+ a:
363
+ | ScanMuteOperationsResponse
364
+ | PlainMessage<ScanMuteOperationsResponse>
365
+ | undefined,
366
+ b:
367
+ | ScanMuteOperationsResponse
368
+ | PlainMessage<ScanMuteOperationsResponse>
369
+ | undefined,
370
+ ): boolean {
371
+ return proto3.util.equals(ScanMuteOperationsResponse, a, b)
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Ping
377
+ *
378
+ * @generated from message bsync.PingRequest
379
+ */
380
+ export class PingRequest extends Message<PingRequest> {
381
+ constructor(data?: PartialMessage<PingRequest>) {
382
+ super()
383
+ proto3.util.initPartial(data, this)
384
+ }
385
+
386
+ static readonly runtime: typeof proto3 = proto3
387
+ static readonly typeName = 'bsync.PingRequest'
388
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [])
389
+
390
+ static fromBinary(
391
+ bytes: Uint8Array,
392
+ options?: Partial<BinaryReadOptions>,
393
+ ): PingRequest {
394
+ return new PingRequest().fromBinary(bytes, options)
395
+ }
396
+
397
+ static fromJson(
398
+ jsonValue: JsonValue,
399
+ options?: Partial<JsonReadOptions>,
400
+ ): PingRequest {
401
+ return new PingRequest().fromJson(jsonValue, options)
402
+ }
403
+
404
+ static fromJsonString(
405
+ jsonString: string,
406
+ options?: Partial<JsonReadOptions>,
407
+ ): PingRequest {
408
+ return new PingRequest().fromJsonString(jsonString, options)
409
+ }
410
+
411
+ static equals(
412
+ a: PingRequest | PlainMessage<PingRequest> | undefined,
413
+ b: PingRequest | PlainMessage<PingRequest> | undefined,
414
+ ): boolean {
415
+ return proto3.util.equals(PingRequest, a, b)
416
+ }
417
+ }
418
+
419
+ /**
420
+ * @generated from message bsync.PingResponse
421
+ */
422
+ export class PingResponse extends Message<PingResponse> {
423
+ constructor(data?: PartialMessage<PingResponse>) {
424
+ super()
425
+ proto3.util.initPartial(data, this)
426
+ }
427
+
428
+ static readonly runtime: typeof proto3 = proto3
429
+ static readonly typeName = 'bsync.PingResponse'
430
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [])
431
+
432
+ static fromBinary(
433
+ bytes: Uint8Array,
434
+ options?: Partial<BinaryReadOptions>,
435
+ ): PingResponse {
436
+ return new PingResponse().fromBinary(bytes, options)
437
+ }
438
+
439
+ static fromJson(
440
+ jsonValue: JsonValue,
441
+ options?: Partial<JsonReadOptions>,
442
+ ): PingResponse {
443
+ return new PingResponse().fromJson(jsonValue, options)
444
+ }
445
+
446
+ static fromJsonString(
447
+ jsonString: string,
448
+ options?: Partial<JsonReadOptions>,
449
+ ): PingResponse {
450
+ return new PingResponse().fromJsonString(jsonString, options)
451
+ }
452
+
453
+ static equals(
454
+ a: PingResponse | PlainMessage<PingResponse> | undefined,
455
+ b: PingResponse | PlainMessage<PingResponse> | undefined,
456
+ ): boolean {
457
+ return proto3.util.equals(PingResponse, a, b)
458
+ }
459
+ }
package/src/index.ts ADDED
@@ -0,0 +1,91 @@
1
+ import http from 'node:http'
2
+ import events from 'node:events'
3
+ import { createHttpTerminator, HttpTerminator } from 'http-terminator'
4
+ import { connectNodeAdapter } from '@connectrpc/connect-node'
5
+ import { dbLogger, loggerMiddleware } from './logger'
6
+ import AppContext, { AppContextOptions } from './context'
7
+ import { ServerConfig } from './config'
8
+ import routes from './routes'
9
+ import { createMuteOpChannel } from './db/schema/mute_op'
10
+
11
+ export * from './config'
12
+ export * from './client'
13
+ export { Database } from './db'
14
+ export { AppContext } from './context'
15
+ export { httpLogger } from './logger'
16
+
17
+ export class BsyncService {
18
+ public ctx: AppContext
19
+ public server: http.Server
20
+ private ac: AbortController
21
+ private terminator: HttpTerminator
22
+ private dbStatsInterval: NodeJS.Timer
23
+
24
+ constructor(opts: {
25
+ ctx: AppContext
26
+ server: http.Server
27
+ ac: AbortController
28
+ }) {
29
+ this.ctx = opts.ctx
30
+ this.server = opts.server
31
+ this.ac = opts.ac
32
+ this.terminator = createHttpTerminator({ server: this.server })
33
+ }
34
+
35
+ static async create(
36
+ cfg: ServerConfig,
37
+ overrides?: Partial<AppContextOptions>,
38
+ ): Promise<BsyncService> {
39
+ const ctx = await AppContext.fromConfig(cfg, overrides)
40
+ const ac = new AbortController()
41
+ const handler = connectNodeAdapter({
42
+ routes: routes(ctx),
43
+ shutdownSignal: ac.signal,
44
+ })
45
+ const server = http.createServer((req, res) => {
46
+ loggerMiddleware(req, res)
47
+ handler(req, res)
48
+ })
49
+ return new BsyncService({ ctx, server, ac })
50
+ }
51
+
52
+ async start(): Promise<http.Server> {
53
+ this.dbStatsInterval = setInterval(() => {
54
+ dbLogger.info(
55
+ {
56
+ idleCount: this.ctx.db.pool.idleCount,
57
+ totalCount: this.ctx.db.pool.totalCount,
58
+ waitingCount: this.ctx.db.pool.waitingCount,
59
+ },
60
+ 'db pool stats',
61
+ )
62
+ }, 10000)
63
+ await this.setupAppEvents()
64
+ this.server.listen(this.ctx.cfg.service.port)
65
+ this.server.keepAliveTimeout = 90000
66
+ await events.once(this.server, 'listening')
67
+ return this.server
68
+ }
69
+
70
+ async destroy(): Promise<void> {
71
+ this.ac.abort()
72
+ await this.terminator.terminate()
73
+ await this.ctx.db.close()
74
+ clearInterval(this.dbStatsInterval)
75
+ }
76
+
77
+ async setupAppEvents() {
78
+ const conn = await this.ctx.db.pool.connect()
79
+ this.ac.signal.addEventListener('abort', () => conn.release(), {
80
+ once: true,
81
+ })
82
+ conn.query(`listen ${createMuteOpChannel}`) // if this errors, unhandled rejection should cause process to exit
83
+ conn.on('notification', (notif) => {
84
+ if (notif.channel === createMuteOpChannel) {
85
+ this.ctx.events.emit('mute_op_create')
86
+ }
87
+ })
88
+ }
89
+ }
90
+
91
+ export default BsyncService
package/src/logger.ts ADDED
@@ -0,0 +1,22 @@
1
+ import pinoHttp from 'pino-http'
2
+ import { subsystemLogger } from '@atproto/common'
3
+
4
+ export const dbLogger: ReturnType<typeof subsystemLogger> =
5
+ subsystemLogger('bsync:db')
6
+ export const httpLogger: ReturnType<typeof subsystemLogger> =
7
+ subsystemLogger('bsync')
8
+
9
+ export const loggerMiddleware = pinoHttp({
10
+ logger: httpLogger,
11
+ redact: {
12
+ paths: ['req.headers.authorization'],
13
+ },
14
+ serializers: {
15
+ err: (err) => {
16
+ return {
17
+ code: err?.code,
18
+ message: err?.message,
19
+ }
20
+ },
21
+ },
22
+ })