@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.
- package/CHANGELOG.md +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.js +2 -2
- package/dist/logger.js.map +1 -1
- package/package.json +17 -13
- package/bin/migration-create.ts +0 -38
- package/buf.gen.yaml +0 -12
- package/jest.config.cjs +0 -21
- package/proto/bsync.proto +0 -134
- package/src/client.ts +0 -25
- package/src/config.ts +0 -90
- package/src/context.ts +0 -48
- package/src/db/index.ts +0 -200
- package/src/db/migrations/20240108T220751294Z-init.ts +0 -26
- package/src/db/migrations/20240717T224303472Z-notif-ops.ts +0 -24
- package/src/db/migrations/20250527T022203400Z-add-operation.ts +0 -20
- package/src/db/migrations/20250603T163446567Z-alter-operation.ts +0 -19
- package/src/db/migrations/index.ts +0 -8
- package/src/db/migrations/provider.ts +0 -8
- package/src/db/schema/index.ts +0 -16
- package/src/db/schema/mute_item.ts +0 -13
- package/src/db/schema/mute_op.ts +0 -18
- package/src/db/schema/notif_item.ts +0 -13
- package/src/db/schema/notif_op.ts +0 -16
- package/src/db/schema/operation.ts +0 -20
- package/src/db/types.ts +0 -19
- package/src/index.ts +0 -130
- package/src/logger.ts +0 -26
- package/src/routes/add-mute-operation.ts +0 -154
- package/src/routes/add-notif-operation.ts +0 -80
- package/src/routes/auth.ts +0 -15
- package/src/routes/delete-operations.ts +0 -45
- package/src/routes/index.ts +0 -28
- package/src/routes/put-operation.ts +0 -115
- package/src/routes/scan-mute-operations.ts +0 -65
- package/src/routes/scan-notif-operations.ts +0 -64
- package/src/routes/scan-operations.ts +0 -67
- package/src/routes/util.ts +0 -67
- package/tests/delete-operations.test.ts +0 -108
- package/tests/mutes.test.ts +0 -352
- package/tests/notifications.test.ts +0 -209
- package/tests/operations.test.ts +0 -327
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -7
package/src/db/index.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { EventEmitter } from 'node:events'
|
|
3
|
-
import {
|
|
4
|
-
Kysely,
|
|
5
|
-
KyselyPlugin,
|
|
6
|
-
PluginTransformQueryArgs,
|
|
7
|
-
PluginTransformResultArgs,
|
|
8
|
-
PostgresDialect,
|
|
9
|
-
QueryResult,
|
|
10
|
-
RootOperationNode,
|
|
11
|
-
UnknownRow,
|
|
12
|
-
} from 'kysely'
|
|
13
|
-
import { Migrator } from 'kysely/migration'
|
|
14
|
-
// eslint-disable-next-line import/default
|
|
15
|
-
import pg from 'pg'
|
|
16
|
-
// eslint-disable-next-line import/no-named-as-default-member
|
|
17
|
-
const { Pool: PgPool, types: pgTypes } = pg
|
|
18
|
-
type PgPool = InstanceType<typeof PgPool>
|
|
19
|
-
import type TypedEmitter from 'typed-emitter'
|
|
20
|
-
import { dbLogger } from '../logger.js'
|
|
21
|
-
import * as migrations from './migrations/index.js'
|
|
22
|
-
import { DbMigrationProvider } from './migrations/provider.js'
|
|
23
|
-
import { DatabaseSchema, DatabaseSchemaType } from './schema/index.js'
|
|
24
|
-
import { PgOptions } from './types.js'
|
|
25
|
-
|
|
26
|
-
export class Database {
|
|
27
|
-
pool: PgPool
|
|
28
|
-
db: DatabaseSchema
|
|
29
|
-
migrator: Migrator
|
|
30
|
-
txEvt = new EventEmitter() as TxnEmitter
|
|
31
|
-
destroyed = false
|
|
32
|
-
|
|
33
|
-
constructor(
|
|
34
|
-
public opts: PgOptions,
|
|
35
|
-
instances?: { db: DatabaseSchema; pool: PgPool },
|
|
36
|
-
) {
|
|
37
|
-
// if instances are provided, use those
|
|
38
|
-
if (instances) {
|
|
39
|
-
this.db = instances.db
|
|
40
|
-
this.pool = instances.pool
|
|
41
|
-
} else {
|
|
42
|
-
// else create a pool & connect
|
|
43
|
-
const { schema, url } = opts
|
|
44
|
-
const pool =
|
|
45
|
-
opts.pool ??
|
|
46
|
-
new PgPool({
|
|
47
|
-
connectionString: url,
|
|
48
|
-
max: opts.poolSize,
|
|
49
|
-
maxUses: opts.poolMaxUses,
|
|
50
|
-
idleTimeoutMillis: opts.poolIdleTimeoutMs,
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// Select count(*) and other pg bigints as js integer
|
|
54
|
-
pgTypes.setTypeParser(pgTypes.builtins.INT8, (n) => parseInt(n, 10))
|
|
55
|
-
|
|
56
|
-
// Setup schema usage, primarily for test parallelism (each test suite runs in its own pg schema)
|
|
57
|
-
if (schema && !/^[a-z_]+$/i.test(schema)) {
|
|
58
|
-
throw new Error(
|
|
59
|
-
`Postgres schema must only contain [A-Za-z_]: ${schema}`,
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
pool.on('error', onPoolError)
|
|
64
|
-
pool.on('connect', (client) => {
|
|
65
|
-
client.on('error', onClientError)
|
|
66
|
-
if (schema) {
|
|
67
|
-
// Shared objects such as extensions will go in the public schema
|
|
68
|
-
client.query(`SET search_path TO "${schema}",public;`)
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
this.pool = pool
|
|
73
|
-
this.db = new Kysely<DatabaseSchemaType>({
|
|
74
|
-
dialect: new PostgresDialect({ pool }),
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
this.migrator = new Migrator({
|
|
79
|
-
db: this.db,
|
|
80
|
-
migrationTableSchema: opts.schema,
|
|
81
|
-
provider: new DbMigrationProvider(migrations),
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
get schema(): string | undefined {
|
|
86
|
-
return this.opts.schema
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
get isTransaction() {
|
|
90
|
-
return this.db.isTransaction
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
assertTransaction() {
|
|
94
|
-
assert(this.isTransaction, 'Transaction required')
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
assertNotTransaction() {
|
|
98
|
-
assert(!this.isTransaction, 'Cannot be in a transaction')
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async transaction<T>(fn: (db: Database) => Promise<T>): Promise<T> {
|
|
102
|
-
const leakyTxPlugin = new LeakyTxPlugin()
|
|
103
|
-
const { dbTxn, txRes } = await this.db
|
|
104
|
-
.withPlugin(leakyTxPlugin)
|
|
105
|
-
.transaction()
|
|
106
|
-
.execute(async (txn) => {
|
|
107
|
-
const dbTxn = new Database(this.opts, {
|
|
108
|
-
db: txn,
|
|
109
|
-
pool: this.pool,
|
|
110
|
-
})
|
|
111
|
-
const txRes = await fn(dbTxn)
|
|
112
|
-
.catch(async (err) => {
|
|
113
|
-
leakyTxPlugin.endTx()
|
|
114
|
-
// ensure that all in-flight queries are flushed & the connection is open
|
|
115
|
-
await dbTxn.db.getExecutor().provideConnection(noopAsync)
|
|
116
|
-
throw err
|
|
117
|
-
})
|
|
118
|
-
.finally(() => leakyTxPlugin.endTx())
|
|
119
|
-
return { dbTxn, txRes }
|
|
120
|
-
})
|
|
121
|
-
dbTxn?.txEvt.emit('commit')
|
|
122
|
-
return txRes
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
onCommit(fn: () => void) {
|
|
126
|
-
this.assertTransaction()
|
|
127
|
-
this.txEvt.once('commit', fn)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async close(): Promise<void> {
|
|
131
|
-
if (this.destroyed) return
|
|
132
|
-
await this.db.destroy()
|
|
133
|
-
this.destroyed = true
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async migrateToOrThrow(migration: string) {
|
|
137
|
-
if (this.schema) {
|
|
138
|
-
await this.db.schema.createSchema(this.schema).ifNotExists().execute()
|
|
139
|
-
}
|
|
140
|
-
const { error, results } = await this.migrator.migrateTo(migration)
|
|
141
|
-
if (error) {
|
|
142
|
-
throw error
|
|
143
|
-
}
|
|
144
|
-
if (!results) {
|
|
145
|
-
throw new Error('An unknown failure occurred while migrating')
|
|
146
|
-
}
|
|
147
|
-
return results
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async migrateToLatestOrThrow() {
|
|
151
|
-
if (this.schema) {
|
|
152
|
-
await this.db.schema.createSchema(this.schema).ifNotExists().execute()
|
|
153
|
-
}
|
|
154
|
-
const { error, results } = await this.migrator.migrateToLatest()
|
|
155
|
-
if (error) {
|
|
156
|
-
throw error
|
|
157
|
-
}
|
|
158
|
-
if (!results) {
|
|
159
|
-
throw new Error('An unknown failure occurred while migrating')
|
|
160
|
-
}
|
|
161
|
-
return results
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export default Database
|
|
166
|
-
|
|
167
|
-
const onPoolError = (err: Error) => dbLogger.error({ err }, 'db pool error')
|
|
168
|
-
const onClientError = (err: Error) => dbLogger.error({ err }, 'db client error')
|
|
169
|
-
|
|
170
|
-
// utils
|
|
171
|
-
// -------
|
|
172
|
-
|
|
173
|
-
class LeakyTxPlugin implements KyselyPlugin {
|
|
174
|
-
private txOver = false
|
|
175
|
-
|
|
176
|
-
endTx() {
|
|
177
|
-
this.txOver = true
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
transformQuery(args: PluginTransformQueryArgs): RootOperationNode {
|
|
181
|
-
if (this.txOver) {
|
|
182
|
-
throw new Error('tx already failed')
|
|
183
|
-
}
|
|
184
|
-
return args.node
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async transformResult(
|
|
188
|
-
args: PluginTransformResultArgs,
|
|
189
|
-
): Promise<QueryResult<UnknownRow>> {
|
|
190
|
-
return args.result
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
type TxnEmitter = TypedEmitter.default<TxnEvents>
|
|
195
|
-
|
|
196
|
-
type TxnEvents = {
|
|
197
|
-
commit: () => void
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const noopAsync = async () => {}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Kysely, sql } from 'kysely'
|
|
2
|
-
|
|
3
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
-
await db.schema
|
|
5
|
-
.createTable('mute_op')
|
|
6
|
-
.addColumn('id', 'bigserial', (col) => col.primaryKey())
|
|
7
|
-
.addColumn('type', 'int2', (col) => col.notNull()) // integer enum: 0->add, 1->remove, 2->clear
|
|
8
|
-
.addColumn('actorDid', 'varchar', (col) => col.notNull())
|
|
9
|
-
.addColumn('subject', 'varchar', (col) => col.notNull())
|
|
10
|
-
.addColumn('createdAt', 'timestamptz', (col) =>
|
|
11
|
-
col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`),
|
|
12
|
-
)
|
|
13
|
-
.execute()
|
|
14
|
-
await db.schema
|
|
15
|
-
.createTable('mute_item')
|
|
16
|
-
.addColumn('actorDid', 'varchar', (col) => col.notNull())
|
|
17
|
-
.addColumn('subject', 'varchar', (col) => col.notNull())
|
|
18
|
-
.addColumn('fromId', 'bigint', (col) => col.notNull())
|
|
19
|
-
.addPrimaryKeyConstraint('mute_item_pkey', ['actorDid', 'subject'])
|
|
20
|
-
.execute()
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
24
|
-
await db.schema.dropTable('mute_item').execute()
|
|
25
|
-
await db.schema.dropTable('mute_op').execute()
|
|
26
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Kysely, sql } from 'kysely'
|
|
2
|
-
|
|
3
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
-
await db.schema
|
|
5
|
-
.createTable('notif_op')
|
|
6
|
-
.addColumn('id', 'bigserial', (col) => col.primaryKey())
|
|
7
|
-
.addColumn('actorDid', 'varchar', (col) => col.notNull())
|
|
8
|
-
.addColumn('priority', 'boolean')
|
|
9
|
-
.addColumn('createdAt', 'timestamptz', (col) =>
|
|
10
|
-
col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`),
|
|
11
|
-
)
|
|
12
|
-
.execute()
|
|
13
|
-
await db.schema
|
|
14
|
-
.createTable('notif_item')
|
|
15
|
-
.addColumn('actorDid', 'varchar', (col) => col.primaryKey())
|
|
16
|
-
.addColumn('priority', 'boolean', (col) => col.notNull())
|
|
17
|
-
.addColumn('fromId', 'bigint', (col) => col.notNull())
|
|
18
|
-
.execute()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
22
|
-
await db.schema.dropTable('notif_item').execute()
|
|
23
|
-
await db.schema.dropTable('notif_op').execute()
|
|
24
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Kysely, sql } from 'kysely'
|
|
2
|
-
|
|
3
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
-
await db.schema
|
|
5
|
-
.createTable('operation')
|
|
6
|
-
.addColumn('id', 'bigserial', (col) => col.primaryKey())
|
|
7
|
-
.addColumn('collection', 'varchar', (col) => col.notNull())
|
|
8
|
-
.addColumn('actorDid', 'varchar', (col) => col.notNull())
|
|
9
|
-
.addColumn('rkey', 'varchar', (col) => col.notNull())
|
|
10
|
-
.addColumn('method', 'int2', (col) => col.notNull())
|
|
11
|
-
.addColumn('payload', sql`bytea`)
|
|
12
|
-
.addColumn('createdAt', 'timestamptz', (col) =>
|
|
13
|
-
col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`),
|
|
14
|
-
)
|
|
15
|
-
.execute()
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
19
|
-
await db.schema.dropTable('operation').execute()
|
|
20
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Kysely } from 'kysely'
|
|
2
|
-
|
|
3
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
-
await db.schema
|
|
5
|
-
.alterTable('operation')
|
|
6
|
-
.renameColumn('collection', 'namespace')
|
|
7
|
-
.execute()
|
|
8
|
-
|
|
9
|
-
await db.schema.alterTable('operation').renameColumn('rkey', 'key').execute()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
13
|
-
await db.schema
|
|
14
|
-
.alterTable('operation')
|
|
15
|
-
.renameColumn('namespace', 'collection')
|
|
16
|
-
.execute()
|
|
17
|
-
|
|
18
|
-
await db.schema.alterTable('operation').renameColumn('key', 'rkey').execute()
|
|
19
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// NOTE this file can be edited by hand, but it is also appended to by the migration:create command.
|
|
2
|
-
// It's important that every migration is exported from here with the proper name. We'd simplify
|
|
3
|
-
// this with kysely's FileMigrationProvider, but it doesn't play nicely with the build process.
|
|
4
|
-
|
|
5
|
-
export * as _20240108T220751294Z from './20240108T220751294Z-init.js'
|
|
6
|
-
export * as _20240717T224303472Z from './20240717T224303472Z-notif-ops.js'
|
|
7
|
-
export * as _20250527T022203400Z from './20250527T022203400Z-add-operation.js'
|
|
8
|
-
export * as _20250603T163446567Z from './20250603T163446567Z-alter-operation.js'
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Migration, MigrationProvider } from 'kysely/migration'
|
|
2
|
-
|
|
3
|
-
export class DbMigrationProvider implements MigrationProvider {
|
|
4
|
-
constructor(private migrations: Record<string, Migration>) {}
|
|
5
|
-
async getMigrations(): Promise<Record<string, Migration>> {
|
|
6
|
-
return this.migrations
|
|
7
|
-
}
|
|
8
|
-
}
|
package/src/db/schema/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Kysely } from 'kysely'
|
|
2
|
-
import * as muteItem from './mute_item.js'
|
|
3
|
-
import * as muteOp from './mute_op.js'
|
|
4
|
-
import * as notifItem from './notif_item.js'
|
|
5
|
-
import * as notifOp from './notif_op.js'
|
|
6
|
-
import * as op from './operation.js'
|
|
7
|
-
|
|
8
|
-
export type DatabaseSchemaType = muteItem.PartialDB &
|
|
9
|
-
muteOp.PartialDB &
|
|
10
|
-
notifItem.PartialDB &
|
|
11
|
-
notifOp.PartialDB &
|
|
12
|
-
op.PartialDB
|
|
13
|
-
|
|
14
|
-
export type DatabaseSchema = Kysely<DatabaseSchemaType>
|
|
15
|
-
|
|
16
|
-
export default DatabaseSchema
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Selectable } from 'kysely'
|
|
2
|
-
|
|
3
|
-
export interface MuteItem {
|
|
4
|
-
actorDid: string
|
|
5
|
-
subject: string // did or aturi for list
|
|
6
|
-
fromId: number
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type MuteItemEntry = Selectable<MuteItem>
|
|
10
|
-
|
|
11
|
-
export const tableName = 'mute_item'
|
|
12
|
-
|
|
13
|
-
export type PartialDB = { [tableName]: MuteItem }
|
package/src/db/schema/mute_op.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { GeneratedAlways, Selectable } from 'kysely'
|
|
2
|
-
import { MuteOperation_Type } from '../../proto/bsync_pb.js'
|
|
3
|
-
|
|
4
|
-
export interface MuteOp {
|
|
5
|
-
id: GeneratedAlways<number>
|
|
6
|
-
type: MuteOperation_Type // integer enum: 0->add, 1->remove, 2->clear
|
|
7
|
-
actorDid: string
|
|
8
|
-
subject: string // did or aturi for list
|
|
9
|
-
createdAt: GeneratedAlways<Date>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type MuteOpEntry = Selectable<MuteOp>
|
|
13
|
-
|
|
14
|
-
export const tableName = 'mute_op'
|
|
15
|
-
|
|
16
|
-
export type PartialDB = { [tableName]: MuteOp }
|
|
17
|
-
|
|
18
|
-
export const createMuteOpChannel = 'mute_op_create' // used with listen/notify
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Selectable } from 'kysely'
|
|
2
|
-
|
|
3
|
-
export interface NotifItem {
|
|
4
|
-
actorDid: string
|
|
5
|
-
priority: boolean
|
|
6
|
-
fromId: number
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type NotifItemEntry = Selectable<NotifItem>
|
|
10
|
-
|
|
11
|
-
export const tableName = 'notif_item'
|
|
12
|
-
|
|
13
|
-
export type PartialDB = { [tableName]: NotifItem }
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { GeneratedAlways, Selectable } from 'kysely'
|
|
2
|
-
|
|
3
|
-
export interface NotifOp {
|
|
4
|
-
id: GeneratedAlways<number>
|
|
5
|
-
actorDid: string
|
|
6
|
-
priority: boolean | null
|
|
7
|
-
createdAt: GeneratedAlways<Date>
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type NotifOpEntry = Selectable<NotifOp>
|
|
11
|
-
|
|
12
|
-
export const tableName = 'notif_op'
|
|
13
|
-
|
|
14
|
-
export type PartialDB = { [tableName]: NotifOp }
|
|
15
|
-
|
|
16
|
-
export const createNotifOpChannel = 'notif_op_create' // used with listen/notify
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { GeneratedAlways } from 'kysely'
|
|
2
|
-
import { Method } from '../../proto/bsync_pb.js'
|
|
3
|
-
|
|
4
|
-
export type OperationMethod = Method.CREATE | Method.UPDATE | Method.DELETE
|
|
5
|
-
|
|
6
|
-
export interface Operation {
|
|
7
|
-
id: GeneratedAlways<number>
|
|
8
|
-
actorDid: string
|
|
9
|
-
namespace: string
|
|
10
|
-
key: string
|
|
11
|
-
method: OperationMethod
|
|
12
|
-
payload: Uint8Array<ArrayBuffer>
|
|
13
|
-
createdAt: GeneratedAlways<Date>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const tableName = 'operation'
|
|
17
|
-
|
|
18
|
-
export type PartialDB = { [tableName]: Operation }
|
|
19
|
-
|
|
20
|
-
export const createOperationChannel = 'operation_create' // used with listen/notify
|
package/src/db/types.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { DynamicModule, RawBuilder, SelectQueryBuilder } from 'kysely'
|
|
2
|
-
// eslint-disable-next-line import/default
|
|
3
|
-
import pg from 'pg'
|
|
4
|
-
type PgPool = pg.Pool
|
|
5
|
-
|
|
6
|
-
export type DbRef =
|
|
7
|
-
| RawBuilder<unknown>
|
|
8
|
-
| ReturnType<DynamicModule<unknown>['ref']>
|
|
9
|
-
|
|
10
|
-
export type AnyQb = SelectQueryBuilder<any, any, any>
|
|
11
|
-
|
|
12
|
-
export type PgOptions = {
|
|
13
|
-
url: string
|
|
14
|
-
pool?: PgPool
|
|
15
|
-
schema?: string
|
|
16
|
-
poolSize?: number
|
|
17
|
-
poolMaxUses?: number
|
|
18
|
-
poolIdleTimeoutMs?: number
|
|
19
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import events from 'node:events'
|
|
2
|
-
import http from 'node:http'
|
|
3
|
-
import { connectNodeAdapter } from '@connectrpc/connect-node'
|
|
4
|
-
// eslint-disable-next-line import/default
|
|
5
|
-
import httpTerminator from 'http-terminator'
|
|
6
|
-
import { ServerConfig } from './config.js'
|
|
7
|
-
import { AppContext, AppContextOptions } from './context.js'
|
|
8
|
-
import { createMuteOpChannel } from './db/schema/mute_op.js'
|
|
9
|
-
import { createNotifOpChannel } from './db/schema/notif_op.js'
|
|
10
|
-
import { createOperationChannel } from './db/schema/operation.js'
|
|
11
|
-
import { dbLogger, loggerMiddleware } from './logger.js'
|
|
12
|
-
import routes from './routes/index.js'
|
|
13
|
-
|
|
14
|
-
export * from './config.js'
|
|
15
|
-
export * from './client.js'
|
|
16
|
-
export { Database } from './db/index.js'
|
|
17
|
-
export { AppContext } from './context.js'
|
|
18
|
-
export { httpLogger } from './logger.js'
|
|
19
|
-
|
|
20
|
-
type BsyncServiceState = 'initialized' | 'started' | 'destroyed'
|
|
21
|
-
|
|
22
|
-
export class BsyncService {
|
|
23
|
-
public ctx: AppContext
|
|
24
|
-
public server: http.Server
|
|
25
|
-
private terminator: httpTerminator.HttpTerminator
|
|
26
|
-
private ac: AbortController
|
|
27
|
-
private state: BsyncServiceState = 'initialized'
|
|
28
|
-
|
|
29
|
-
constructor(opts: {
|
|
30
|
-
ctx: AppContext
|
|
31
|
-
server: http.Server
|
|
32
|
-
ac: AbortController
|
|
33
|
-
}) {
|
|
34
|
-
this.ctx = opts.ctx
|
|
35
|
-
this.server = opts.server
|
|
36
|
-
this.ac = opts.ac
|
|
37
|
-
this.terminator = httpTerminator.createHttpTerminator(opts)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
static async create(
|
|
41
|
-
cfg: ServerConfig,
|
|
42
|
-
overrides?: Partial<AppContextOptions>,
|
|
43
|
-
): Promise<BsyncService> {
|
|
44
|
-
const ac = new AbortController()
|
|
45
|
-
const ctx = await AppContext.fromConfig(cfg, ac.signal, overrides)
|
|
46
|
-
const handler = connectNodeAdapter({
|
|
47
|
-
routes: routes(ctx),
|
|
48
|
-
shutdownSignal: ac.signal,
|
|
49
|
-
})
|
|
50
|
-
const server = http.createServer((req, res) => {
|
|
51
|
-
loggerMiddleware(req, res)
|
|
52
|
-
if (isHealth(req.url)) {
|
|
53
|
-
res.statusCode = 200
|
|
54
|
-
res.setHeader('content-type', 'application/json')
|
|
55
|
-
return res.end(JSON.stringify({ version: cfg.service.version }))
|
|
56
|
-
}
|
|
57
|
-
handler(req, res)
|
|
58
|
-
})
|
|
59
|
-
return new BsyncService({ ctx, server, ac })
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async start(): Promise<http.Server> {
|
|
63
|
-
if (this.state !== 'initialized') {
|
|
64
|
-
throw new Error(`${this.constructor.name} already started`)
|
|
65
|
-
}
|
|
66
|
-
this.state = 'started'
|
|
67
|
-
|
|
68
|
-
const dbStatsInterval = setInterval(() => {
|
|
69
|
-
dbLogger.info(
|
|
70
|
-
{
|
|
71
|
-
idleCount: this.ctx.db.pool.idleCount,
|
|
72
|
-
totalCount: this.ctx.db.pool.totalCount,
|
|
73
|
-
waitingCount: this.ctx.db.pool.waitingCount,
|
|
74
|
-
},
|
|
75
|
-
'db pool stats',
|
|
76
|
-
)
|
|
77
|
-
}, 10000)
|
|
78
|
-
|
|
79
|
-
this.ac.signal.addEventListener('abort', () => {
|
|
80
|
-
clearInterval(dbStatsInterval)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
await this.setupAppEvents()
|
|
84
|
-
this.server.listen(this.ctx.cfg.service.port)
|
|
85
|
-
this.server.keepAliveTimeout = 90000
|
|
86
|
-
await events.once(this.server, 'listening')
|
|
87
|
-
return this.server
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async destroy(): Promise<void> {
|
|
91
|
-
if (this.state === 'destroyed') return
|
|
92
|
-
this.state = 'destroyed'
|
|
93
|
-
this.ac.abort()
|
|
94
|
-
try {
|
|
95
|
-
await this.terminator.terminate()
|
|
96
|
-
} finally {
|
|
97
|
-
await this.ctx.db.close()
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async setupAppEvents() {
|
|
102
|
-
const conn = await this.ctx.db.pool.connect()
|
|
103
|
-
this.ac.signal.addEventListener('abort', () => conn.release(), {
|
|
104
|
-
once: true,
|
|
105
|
-
})
|
|
106
|
-
// if these error, unhandled rejection should cause process to exit
|
|
107
|
-
conn.query(`listen ${createMuteOpChannel}`)
|
|
108
|
-
conn.query(`listen ${createNotifOpChannel}`)
|
|
109
|
-
conn.query(`listen ${createOperationChannel}`)
|
|
110
|
-
conn.on('notification', (notif) => {
|
|
111
|
-
if (notif.channel === createMuteOpChannel) {
|
|
112
|
-
this.ctx.events.emit(createMuteOpChannel)
|
|
113
|
-
}
|
|
114
|
-
if (notif.channel === createNotifOpChannel) {
|
|
115
|
-
this.ctx.events.emit(createNotifOpChannel)
|
|
116
|
-
}
|
|
117
|
-
if (notif.channel === createOperationChannel) {
|
|
118
|
-
this.ctx.events.emit(createOperationChannel)
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export default BsyncService
|
|
125
|
-
|
|
126
|
-
const isHealth = (urlStr: string | undefined) => {
|
|
127
|
-
if (!urlStr) return false
|
|
128
|
-
const url = new URL(urlStr, 'http://host')
|
|
129
|
-
return url.pathname === '/_health'
|
|
130
|
-
}
|
package/src/logger.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { type IncomingMessage } from 'node:http'
|
|
2
|
-
import { pinoHttp, stdSerializers } from 'pino-http'
|
|
3
|
-
import { obfuscateHeaders, subsystemLogger } from '@atproto/common'
|
|
4
|
-
|
|
5
|
-
export const dbLogger: ReturnType<typeof subsystemLogger> =
|
|
6
|
-
subsystemLogger('bsync:db')
|
|
7
|
-
export const httpLogger: ReturnType<typeof subsystemLogger> =
|
|
8
|
-
subsystemLogger('bsync')
|
|
9
|
-
|
|
10
|
-
export const loggerMiddleware = pinoHttp({
|
|
11
|
-
logger: httpLogger,
|
|
12
|
-
redact: {
|
|
13
|
-
paths: ['req.headers.authorization'],
|
|
14
|
-
},
|
|
15
|
-
serializers: {
|
|
16
|
-
err: (err: unknown) => ({
|
|
17
|
-
code: err?.['code'],
|
|
18
|
-
message: err?.['message'],
|
|
19
|
-
}),
|
|
20
|
-
req: (req: IncomingMessage) => {
|
|
21
|
-
const serialized = stdSerializers.req(req)
|
|
22
|
-
const headers = obfuscateHeaders(serialized.headers)
|
|
23
|
-
return { ...serialized, headers }
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
})
|