@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @atproto/bsync
2
2
 
3
+ ## 0.0.33
4
+
5
+ ### Patch Changes
6
+
7
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update TypeScript build to rely on references to composite internal projects
8
+
9
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bundle only necessary files in the NPM tarball, including the `CHANGELOG.md` and `README.md` files (if present).
10
+
11
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Build with `noImplicitAny` enabled
12
+
13
+ - Updated dependencies [[`28a0b58`](https://github.com/bluesky-social/atproto/commit/28a0b588147863eaef948cd2bb8fc0f19d08cda9), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07)]:
14
+ - @atproto/syntax@0.6.4
15
+ - @atproto/common@0.6.5
16
+
3
17
  ## 0.0.32
4
18
 
5
19
  ### Patch Changes
package/dist/logger.js CHANGED
@@ -9,8 +9,8 @@ export const loggerMiddleware = pinoHttp({
9
9
  },
10
10
  serializers: {
11
11
  err: (err) => ({
12
- code: err?.['code'],
13
- message: err?.['message'],
12
+ code: err?.code,
13
+ message: err?.message,
14
14
  }),
15
15
  req: (req) => {
16
16
  const serialized = stdSerializers.req(req);
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AACpD,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEnE,MAAM,CAAC,MAAM,QAAQ,GACnB,eAAe,CAAC,UAAU,CAAC,CAAA;AAC7B,MAAM,CAAC,MAAM,UAAU,GACrB,eAAe,CAAC,OAAO,CAAC,CAAA;AAE1B,MAAM,CAAC,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACvC,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE;QACN,KAAK,EAAE,CAAC,2BAA2B,CAAC;KACrC;IACD,WAAW,EAAE;QACX,GAAG,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC,CAAC;YACtB,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC;YACnB,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC;SAC1B,CAAC;QACF,GAAG,EAAE,CAAC,GAAoB,EAAE,EAAE;YAC5B,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAC1C,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;YACpD,OAAO,EAAE,GAAG,UAAU,EAAE,OAAO,EAAE,CAAA;QACnC,CAAC;KACF;CACF,CAAC,CAAA","sourcesContent":["import { type IncomingMessage } from 'node:http'\nimport { pinoHttp, stdSerializers } from 'pino-http'\nimport { obfuscateHeaders, subsystemLogger } from '@atproto/common'\n\nexport const dbLogger: ReturnType<typeof subsystemLogger> =\n subsystemLogger('bsync:db')\nexport const httpLogger: ReturnType<typeof subsystemLogger> =\n subsystemLogger('bsync')\n\nexport const loggerMiddleware = pinoHttp({\n logger: httpLogger,\n redact: {\n paths: ['req.headers.authorization'],\n },\n serializers: {\n err: (err: unknown) => ({\n code: err?.['code'],\n message: err?.['message'],\n }),\n req: (req: IncomingMessage) => {\n const serialized = stdSerializers.req(req)\n const headers = obfuscateHeaders(serialized.headers)\n return { ...serialized, headers }\n },\n },\n})\n"]}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AACpD,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEnE,MAAM,CAAC,MAAM,QAAQ,GACnB,eAAe,CAAC,UAAU,CAAC,CAAA;AAC7B,MAAM,CAAC,MAAM,UAAU,GACrB,eAAe,CAAC,OAAO,CAAC,CAAA;AAE1B,MAAM,CAAC,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACvC,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE;QACN,KAAK,EAAE,CAAC,2BAA2B,CAAC;KACrC;IACD,WAAW,EAAE;QACX,GAAG,EAAE,CAAC,GAAQ,EAAE,EAAE,CAAC,CAAC;YAClB,IAAI,EAAE,GAAG,EAAE,IAAI;YACf,OAAO,EAAE,GAAG,EAAE,OAAO;SACtB,CAAC;QACF,GAAG,EAAE,CAAC,GAAoB,EAAE,EAAE;YAC5B,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAC1C,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;YACpD,OAAO,EAAE,GAAG,UAAU,EAAE,OAAO,EAAE,CAAA;QACnC,CAAC;KACF;CACF,CAAC,CAAA","sourcesContent":["import { type IncomingMessage } from 'node:http'\nimport { pinoHttp, stdSerializers } from 'pino-http'\nimport { obfuscateHeaders, subsystemLogger } from '@atproto/common'\n\nexport const dbLogger: ReturnType<typeof subsystemLogger> =\n subsystemLogger('bsync:db')\nexport const httpLogger: ReturnType<typeof subsystemLogger> =\n subsystemLogger('bsync')\n\nexport const loggerMiddleware = pinoHttp({\n logger: httpLogger,\n redact: {\n paths: ['req.headers.authorization'],\n },\n serializers: {\n err: (err: any) => ({\n code: err?.code,\n message: err?.message,\n }),\n req: (req: IncomingMessage) => {\n const serialized = stdSerializers.req(req)\n const headers = obfuscateHeaders(serialized.headers)\n return { ...serialized, headers }\n },\n },\n})\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/bsync",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "license": "MIT",
5
5
  "description": "Sychronizing service for app.bsky App View (Bluesky API)",
6
6
  "keywords": [
@@ -13,6 +13,18 @@
13
13
  "url": "https://github.com/bluesky-social/atproto",
14
14
  "directory": "packages/bsync"
15
15
  },
16
+ "files": [
17
+ "./dist",
18
+ "./README.md",
19
+ "./CHANGELOG.md"
20
+ ],
21
+ "type": "module",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ }
27
+ },
16
28
  "engines": {
17
29
  "node": ">=22"
18
30
  },
@@ -25,8 +37,8 @@
25
37
  "pg": "^8.10.0",
26
38
  "pino-http": "^11.0.0",
27
39
  "typed-emitter": "^2.1.0",
28
- "@atproto/common": "^0.6.4",
29
- "@atproto/syntax": "^0.6.3"
40
+ "@atproto/common": "^0.6.5",
41
+ "@atproto/syntax": "^0.6.4"
30
42
  },
31
43
  "devDependencies": {
32
44
  "@bufbuild/buf": "^1.28.1",
@@ -34,15 +46,7 @@
34
46
  "@connectrpc/protoc-gen-connect-es": "^1.1.4",
35
47
  "@types/pg": "^8.15.5",
36
48
  "get-port": "^5.1.1",
37
- "jest": "^30.0.0",
38
- "ts-node": "^10.8.2"
39
- },
40
- "type": "module",
41
- "exports": {
42
- ".": {
43
- "types": "./dist/index.d.ts",
44
- "default": "./dist/index.js"
45
- }
49
+ "jest": "^30.0.0"
46
50
  },
47
51
  "scripts": {
48
52
  "codegen:buf": "buf generate proto",
@@ -52,7 +56,7 @@
52
56
  "test": "NODE_OPTIONS=--experimental-vm-modules ../dev-infra/with-test-db.sh jest",
53
57
  "test:log": "tail -50 test.log | pino-pretty",
54
58
  "test:updateSnapshot": "jest --updateSnapshot",
55
- "migration:create": "ts-node ./bin/migration-create.ts",
59
+ "migration:create": "node ./bin/migration-create.ts",
56
60
  "buf:gen": ">&2 echo 'DEPRECATED: run `pnpm run codegen:buf` instead ' && pnpm run codegen:buf"
57
61
  }
58
62
  }
@@ -1,38 +0,0 @@
1
- #!/usr/bin/env ts-node
2
-
3
- import * as fs from 'node:fs/promises'
4
- import * as path from 'node:path'
5
-
6
- export async function main() {
7
- const now = new Date()
8
- const prefix = now.toISOString().replace(/[^a-z0-9]/gi, '') // Order of migrations matches alphabetical order of their names
9
- const name = process.argv[2]
10
- if (!name || !name.match(/^[a-z0-9-]+$/)) {
11
- process.exitCode = 1
12
- return console.error(
13
- 'Must pass a migration name consisting of lowercase digits, numbers, and dashes.',
14
- )
15
- }
16
- const filename = `${prefix}-${name}`
17
- const dir = path.join(__dirname, '..', 'src', 'db', 'migrations')
18
-
19
- await fs.writeFile(path.join(dir, `${filename}.ts`), template, { flag: 'wx' })
20
- await fs.writeFile(
21
- path.join(dir, 'index.ts'),
22
- `export * as _${prefix} from './${filename}.js'\n`,
23
- { flag: 'a' },
24
- )
25
- }
26
-
27
- const template = `import { Kysely } from 'kysely'
28
-
29
- export async function up(db: Kysely<unknown>): Promise<void> {
30
- // Migration code
31
- }
32
-
33
- export async function down(db: Kysely<unknown>): Promise<void> {
34
- // Migration code
35
- }
36
- `
37
-
38
- main()
package/buf.gen.yaml DELETED
@@ -1,12 +0,0 @@
1
- version: v1
2
- plugins:
3
- - plugin: es
4
- opt:
5
- - target=ts
6
- - import_extension=
7
- out: src/proto
8
- - plugin: connect-es
9
- opt:
10
- - target=ts
11
- - import_extension=.js
12
- out: src/proto
package/jest.config.cjs DELETED
@@ -1,21 +0,0 @@
1
- /** @type {import('jest').Config} */
2
- module.exports = {
3
- displayName: 'Bsync',
4
- transform: {
5
- '^.+\\.(t|j)s$': [
6
- '@swc/jest',
7
- {
8
- jsc: {
9
- parser: { syntax: 'typescript', importAttributes: true },
10
- experimental: { keepImportAttributes: true },
11
- transform: {},
12
- },
13
- module: { type: 'es6' },
14
- },
15
- ],
16
- },
17
- extensionsToTreatAsEsm: ['.ts'],
18
- transformIgnorePatterns: [],
19
- setupFiles: ['<rootDir>/../../test.setup.ts'],
20
- moduleNameMapper: { '^(\\.\\.?\\/.+)\\.js$': ['$1.ts', '$1.js'] },
21
- }
package/proto/bsync.proto DELETED
@@ -1,134 +0,0 @@
1
- syntax = "proto3";
2
-
3
- package bsync;
4
- option go_package = "./;bsync";
5
-
6
- //
7
- // Sync
8
- //
9
-
10
-
11
- message MuteOperation {
12
- enum Type {
13
- TYPE_UNSPECIFIED = 0;
14
- TYPE_ADD = 1;
15
- TYPE_REMOVE = 2;
16
- TYPE_CLEAR = 3;
17
- }
18
- string id = 1;
19
- Type type = 2;
20
- string actor_did = 3;
21
- string subject = 4;
22
- }
23
-
24
- message AddMuteOperationRequest {
25
- MuteOperation.Type type = 1;
26
- string actor_did = 2;
27
- string subject = 3;
28
- }
29
-
30
- message AddMuteOperationResponse {
31
- MuteOperation operation = 1;
32
- }
33
-
34
- message ScanMuteOperationsRequest {
35
- string cursor = 1;
36
- int32 limit = 2;
37
- }
38
-
39
- message ScanMuteOperationsResponse {
40
- repeated MuteOperation operations = 1;
41
- string cursor = 2;
42
- }
43
-
44
- message NotifOperation {
45
- string id = 1;
46
- string actor_did = 2;
47
- optional bool priority = 3;
48
- }
49
-
50
- message AddNotifOperationRequest {
51
- string actor_did = 1;
52
- optional bool priority = 2;
53
- }
54
-
55
- message AddNotifOperationResponse {
56
- NotifOperation operation = 1;
57
- }
58
-
59
- message ScanNotifOperationsRequest {
60
- string cursor = 1;
61
- int32 limit = 2;
62
- }
63
-
64
- message ScanNotifOperationsResponse {
65
- repeated NotifOperation operations = 1;
66
- string cursor = 2;
67
- }
68
-
69
-
70
- enum Method {
71
- METHOD_UNSPECIFIED = 0;
72
- METHOD_CREATE = 1;
73
- METHOD_UPDATE = 2;
74
- METHOD_DELETE = 3;
75
- }
76
-
77
- message Operation {
78
- string id = 1;
79
- string actor_did = 2;
80
- string namespace = 3;
81
- string key = 4;
82
- Method method = 5;
83
- bytes payload = 6;
84
- }
85
-
86
- message PutOperationRequest {
87
- string actor_did = 1;
88
- string namespace = 2;
89
- string key = 3;
90
- Method method = 4;
91
- bytes payload = 5;
92
- }
93
-
94
- message PutOperationResponse {
95
- Operation operation = 1;
96
- }
97
-
98
- message ScanOperationsRequest {
99
- string cursor = 1;
100
- int32 limit = 2;
101
- }
102
-
103
- message ScanOperationsResponse {
104
- repeated Operation operations = 1;
105
- string cursor = 2;
106
- }
107
-
108
- message DeleteOperationsByActorAndNamespaceRequest {
109
- string actor_did = 1;
110
- string namespace = 2;
111
- }
112
-
113
- message DeleteOperationsByActorAndNamespaceResponse {
114
- int32 deleted_count = 1;
115
- }
116
-
117
-
118
- // Ping
119
- message PingRequest {}
120
- message PingResponse {}
121
-
122
-
123
- service Service {
124
- // Sync
125
- rpc AddMuteOperation(AddMuteOperationRequest) returns (AddMuteOperationResponse);
126
- rpc ScanMuteOperations(ScanMuteOperationsRequest) returns (ScanMuteOperationsResponse);
127
- rpc AddNotifOperation(AddNotifOperationRequest) returns (AddNotifOperationResponse);
128
- rpc ScanNotifOperations(ScanNotifOperationsRequest) returns (ScanNotifOperationsResponse);
129
- rpc PutOperation(PutOperationRequest) returns (PutOperationResponse);
130
- rpc ScanOperations(ScanOperationsRequest) returns (ScanOperationsResponse);
131
- rpc DeleteOperationsByActorAndNamespace(DeleteOperationsByActorAndNamespaceRequest) returns (DeleteOperationsByActorAndNamespaceResponse);
132
- // Ping
133
- rpc Ping(PingRequest) returns (PingResponse);
134
- }
package/src/client.ts DELETED
@@ -1,25 +0,0 @@
1
- import {
2
- Interceptor,
3
- PromiseClient,
4
- createPromiseClient,
5
- } from '@connectrpc/connect'
6
- import {
7
- ConnectTransportOptions,
8
- createConnectTransport,
9
- } from '@connectrpc/connect-node'
10
- import { Service } from './proto/bsync_connect.js'
11
-
12
- export type BsyncClient = PromiseClient<typeof Service>
13
-
14
- export const createClient = (opts: ConnectTransportOptions): BsyncClient => {
15
- const transport = createConnectTransport(opts)
16
- return createPromiseClient(Service, transport)
17
- }
18
-
19
- export const authWithApiKey =
20
- (apiKey: string): Interceptor =>
21
- (next) =>
22
- (req) => {
23
- req.header.set('authorization', `Bearer ${apiKey}`)
24
- return next(req)
25
- }
package/src/config.ts DELETED
@@ -1,90 +0,0 @@
1
- import assert from 'node:assert'
2
- import { envBool, envInt, envList, envStr } from '@atproto/common'
3
-
4
- export const envToCfg = (env: ServerEnvironment): ServerConfig => {
5
- const serviceCfg: ServerConfig['service'] = {
6
- port: env.port ?? 2585,
7
- version: env.version ?? 'unknown',
8
- longPollTimeoutMs: env.longPollTimeoutMs ?? 10000,
9
- }
10
-
11
- assert(env.dbUrl, 'missing postgres url')
12
- const dbCfg: ServerConfig['db'] = {
13
- url: env.dbUrl,
14
- schema: env.dbSchema,
15
- poolSize: env.dbPoolSize,
16
- poolMaxUses: env.dbPoolMaxUses,
17
- poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs,
18
- migrate: env.dbMigrate,
19
- }
20
-
21
- assert(env.apiKeys.length > 0, 'missing api keys')
22
- const authCfg: ServerConfig['auth'] = {
23
- apiKeys: new Set(env.apiKeys),
24
- }
25
-
26
- return {
27
- service: serviceCfg,
28
- db: dbCfg,
29
- auth: authCfg,
30
- }
31
- }
32
-
33
- export type ServerConfig = {
34
- service: ServiceConfig
35
- db: DatabaseConfig
36
- auth: AuthConfig
37
- }
38
-
39
- type ServiceConfig = {
40
- port: number
41
- version?: string
42
- longPollTimeoutMs: number
43
- }
44
-
45
- type DatabaseConfig = {
46
- url: string
47
- schema?: string
48
- poolSize?: number
49
- poolMaxUses?: number
50
- poolIdleTimeoutMs?: number
51
- migrate?: boolean
52
- }
53
-
54
- type AuthConfig = {
55
- apiKeys: Set<string>
56
- }
57
-
58
- export const readEnv = (): ServerEnvironment => {
59
- return {
60
- // service
61
- port: envInt('BSYNC_PORT'),
62
- version: envStr('BSYNC_VERSION'),
63
- longPollTimeoutMs: envInt('BSYNC_LONG_POLL_TIMEOUT_MS'),
64
- // database
65
- dbUrl: envStr('BSYNC_DB_POSTGRES_URL'),
66
- dbSchema: envStr('BSYNC_DB_POSTGRES_SCHEMA'),
67
- dbPoolSize: envInt('BSYNC_DB_POOL_SIZE'),
68
- dbPoolMaxUses: envInt('BSYNC_DB_POOL_MAX_USES'),
69
- dbPoolIdleTimeoutMs: envInt('BSYNC_DB_POOL_IDLE_TIMEOUT_MS'),
70
- dbMigrate: envBool('BSYNC_DB_MIGRATE'),
71
- // secrets
72
- apiKeys: envList('BSYNC_API_KEYS'),
73
- }
74
- }
75
-
76
- export type ServerEnvironment = {
77
- // service
78
- port?: number
79
- version?: string
80
- longPollTimeoutMs?: number
81
- // database
82
- dbUrl?: string
83
- dbSchema?: string
84
- dbPoolSize?: number
85
- dbPoolMaxUses?: number
86
- dbPoolIdleTimeoutMs?: number
87
- dbMigrate?: boolean
88
- // secrets
89
- apiKeys: string[]
90
- }
package/src/context.ts DELETED
@@ -1,48 +0,0 @@
1
- import { EventEmitter } from 'node:events'
2
- import type TypedEmitter from 'typed-emitter'
3
- import { ServerConfig } from './config.js'
4
- import { Database } from './db/index.js'
5
- import { createMuteOpChannel } from './db/schema/mute_op.js'
6
- import { createNotifOpChannel } from './db/schema/notif_op.js'
7
- import { createOperationChannel } from './db/schema/operation.js'
8
-
9
- export type AppContextOptions = {
10
- db: Database
11
- cfg: ServerConfig
12
- shutdown: AbortSignal
13
- }
14
-
15
- export class AppContext {
16
- db: Database
17
- cfg: ServerConfig
18
- shutdown: AbortSignal
19
- events: TypedEmitter.default<AppEvents>
20
-
21
- constructor(opts: AppContextOptions) {
22
- this.db = opts.db
23
- this.cfg = opts.cfg
24
- this.shutdown = opts.shutdown
25
- this.events = new EventEmitter() as TypedEmitter.default<AppEvents>
26
- }
27
-
28
- static async fromConfig(
29
- cfg: ServerConfig,
30
- shutdown: AbortSignal,
31
- overrides?: Partial<AppContextOptions>,
32
- ): Promise<AppContext> {
33
- const db = new Database({
34
- url: cfg.db.url,
35
- schema: cfg.db.schema,
36
- poolSize: cfg.db.poolSize,
37
- poolMaxUses: cfg.db.poolMaxUses,
38
- poolIdleTimeoutMs: cfg.db.poolIdleTimeoutMs,
39
- })
40
- return new AppContext({ db, cfg, shutdown, ...overrides })
41
- }
42
- }
43
-
44
- export type AppEvents = {
45
- [createMuteOpChannel]: () => void
46
- [createNotifOpChannel]: () => void
47
- [createOperationChannel]: () => void
48
- }
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 () => {}