@atproto/bsky 0.0.124 → 0.0.125
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 +14 -0
- package/dist/api/app/bsky/notification/listNotifications.d.ts +7 -0
- package/dist/api/app/bsky/notification/listNotifications.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/listNotifications.js +21 -5
- package/dist/api/app/bsky/notification/listNotifications.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +24 -15
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +6 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +6 -0
- package/dist/context.js.map +1 -1
- package/dist/data-plane/client/hosts.d.ts +37 -0
- package/dist/data-plane/client/hosts.d.ts.map +1 -0
- package/dist/data-plane/client/hosts.js +106 -0
- package/dist/data-plane/client/hosts.js.map +1 -0
- package/dist/data-plane/client/index.d.ts +13 -0
- package/dist/data-plane/client/index.d.ts.map +1 -0
- package/dist/data-plane/client/index.js +133 -0
- package/dist/data-plane/client/index.js.map +1 -0
- package/dist/data-plane/{client.d.ts → client/util.d.ts} +3 -10
- package/dist/data-plane/client/util.d.ts.map +1 -0
- package/dist/data-plane/client/util.js +85 -0
- package/dist/data-plane/client/util.js.map +1 -0
- package/dist/data-plane/server/db/pagination.d.ts +69 -9
- package/dist/data-plane/server/db/pagination.d.ts.map +1 -1
- package/dist/data-plane/server/db/pagination.js +114 -14
- package/dist/data-plane/server/db/pagination.js.map +1 -1
- package/dist/data-plane/server/routes/notifs.d.ts.map +1 -1
- package/dist/data-plane/server/routes/notifs.js +3 -5
- package/dist/data-plane/server/routes/notifs.js.map +1 -1
- package/dist/etcd.d.ts +25 -0
- package/dist/etcd.d.ts.map +1 -0
- package/dist/etcd.js +109 -0
- package/dist/etcd.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/lexicon/index.d.ts +6 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +12 -0
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +304 -156
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +168 -80
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/embed/video.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/embed/video.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/embed/video.js.map +1 -1
- package/dist/lexicon/types/com/atproto/identity/defs.d.ts +17 -0
- package/dist/lexicon/types/com/atproto/identity/defs.d.ts.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/defs.js +16 -0
- package/dist/lexicon/types/com/atproto/identity/defs.js.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/refreshIdentity.d.ts +39 -0
- package/dist/lexicon/types/com/atproto/identity/refreshIdentity.d.ts.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/refreshIdentity.js +7 -0
- package/dist/lexicon/types/com/atproto/identity/refreshIdentity.js.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/resolveDid.d.ts +40 -0
- package/dist/lexicon/types/com/atproto/identity/resolveDid.d.ts.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/resolveDid.js +7 -0
- package/dist/lexicon/types/com/atproto/identity/resolveDid.js.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/resolveHandle.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/identity/resolveHandle.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/identity/resolveIdentity.d.ts +36 -0
- package/dist/lexicon/types/com/atproto/identity/resolveIdentity.d.ts.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/resolveIdentity.js +7 -0
- package/dist/lexicon/types/com/atproto/identity/resolveIdentity.js.map +1 -0
- package/dist/lexicon/types/com/atproto/repo/listRecords.d.ts +0 -4
- package/dist/lexicon/types/com/atproto/repo/listRecords.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/repo/listRecords.js.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/getRecord.d.ts +0 -2
- package/dist/lexicon/types/com/atproto/sync/getRecord.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts +1 -30
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js +0 -27
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js.map +1 -1
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +2 -1
- package/dist/logger.js.map +1 -1
- package/package.json +16 -15
- package/src/api/app/bsky/notification/listNotifications.ts +28 -6
- package/src/config.ts +45 -15
- package/src/context.ts +12 -1
- package/src/data-plane/client/hosts.ts +103 -0
- package/src/data-plane/client/index.ts +123 -0
- package/src/data-plane/client/util.ts +66 -0
- package/src/data-plane/server/db/pagination.ts +158 -35
- package/src/data-plane/server/routes/notifs.ts +4 -9
- package/src/etcd.ts +90 -0
- package/src/index.ts +26 -2
- package/src/lexicon/index.ts +36 -0
- package/src/lexicon/lexicons.ts +183 -83
- package/src/lexicon/types/app/bsky/embed/video.ts +1 -0
- package/src/lexicon/types/com/atproto/identity/defs.ts +30 -0
- package/src/lexicon/types/com/atproto/identity/refreshIdentity.ts +52 -0
- package/src/lexicon/types/com/atproto/identity/resolveDid.ts +52 -0
- package/src/lexicon/types/com/atproto/identity/resolveHandle.ts +1 -0
- package/src/lexicon/types/com/atproto/identity/resolveIdentity.ts +48 -0
- package/src/lexicon/types/com/atproto/repo/listRecords.ts +0 -4
- package/src/lexicon/types/com/atproto/sync/getRecord.ts +0 -2
- package/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +0 -59
- package/src/logger.ts +2 -0
- package/tests/etcd.test.ts +301 -0
- package/tests/views/__snapshots__/notifications.test.ts.snap +3 -3
- package/tests/views/notifications.test.ts +190 -10
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
- package/dist/data-plane/client.d.ts.map +0 -1
- package/dist/data-plane/client.js +0 -156
- package/dist/data-plane/client.js.map +0 -1
- package/src/data-plane/client.ts +0 -154
|
@@ -2,8 +2,8 @@ import { sql } from 'kysely'
|
|
|
2
2
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
3
|
import { AnyQb, DbRef } from './util'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
type KeysetCursor = { primary: string; secondary: string }
|
|
6
|
+
type KeysetLabeledResult = {
|
|
7
7
|
primary: string | number
|
|
8
8
|
secondary: string | number
|
|
9
9
|
}
|
|
@@ -22,14 +22,14 @@ export type LabeledResult = {
|
|
|
22
22
|
* Result -*-> LabeledResult <-*-> Cursor <--> packed/string cursor
|
|
23
23
|
* ↳ SQL Condition
|
|
24
24
|
*/
|
|
25
|
-
export abstract class GenericKeyset<R, LR extends
|
|
25
|
+
export abstract class GenericKeyset<R, LR extends KeysetLabeledResult> {
|
|
26
26
|
constructor(
|
|
27
27
|
public primary: DbRef,
|
|
28
28
|
public secondary: DbRef,
|
|
29
29
|
) {}
|
|
30
30
|
abstract labelResult(result: R): LR
|
|
31
|
-
abstract labeledResultToCursor(labeled: LR):
|
|
32
|
-
abstract cursorToLabeledResult(cursor:
|
|
31
|
+
abstract labeledResultToCursor(labeled: LR): KeysetCursor
|
|
32
|
+
abstract cursorToLabeledResult(cursor: KeysetCursor): LR
|
|
33
33
|
packFromResult(results: R | R[]): string | undefined {
|
|
34
34
|
const result = Array.isArray(results) ? results.at(-1) : results
|
|
35
35
|
if (!result) return
|
|
@@ -45,11 +45,11 @@ export abstract class GenericKeyset<R, LR extends LabeledResult> {
|
|
|
45
45
|
if (!cursor) return
|
|
46
46
|
return this.cursorToLabeledResult(cursor)
|
|
47
47
|
}
|
|
48
|
-
packCursor(cursor?:
|
|
48
|
+
packCursor(cursor?: KeysetCursor): string | undefined {
|
|
49
49
|
if (!cursor) return
|
|
50
50
|
return `${cursor.primary}__${cursor.secondary}`
|
|
51
51
|
}
|
|
52
|
-
unpackCursor(cursorStr?: string):
|
|
52
|
+
unpackCursor(cursorStr?: string): KeysetCursor | undefined {
|
|
53
53
|
if (!cursorStr) return
|
|
54
54
|
const result = cursorStr.split('__')
|
|
55
55
|
const [primary, secondary, ...others] = result
|
|
@@ -79,10 +79,43 @@ export abstract class GenericKeyset<R, LR extends LabeledResult> {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
paginate<QB extends AnyQb>(
|
|
83
|
+
qb: QB,
|
|
84
|
+
opts: {
|
|
85
|
+
limit?: number
|
|
86
|
+
cursor?: string
|
|
87
|
+
direction?: 'asc' | 'desc'
|
|
88
|
+
tryIndex?: boolean
|
|
89
|
+
// By default, pg does nullsFirst
|
|
90
|
+
nullsLast?: boolean
|
|
91
|
+
},
|
|
92
|
+
): QB {
|
|
93
|
+
const { limit, cursor, direction = 'desc', tryIndex, nullsLast } = opts
|
|
94
|
+
const keysetSql = this.getSql(this.unpack(cursor), direction, tryIndex)
|
|
95
|
+
return qb
|
|
96
|
+
.if(!!limit, (q) => q.limit(limit as number))
|
|
97
|
+
.if(!nullsLast, (q) =>
|
|
98
|
+
q.orderBy(this.primary, direction).orderBy(this.secondary, direction),
|
|
99
|
+
)
|
|
100
|
+
.if(!!nullsLast, (q) =>
|
|
101
|
+
q
|
|
102
|
+
.orderBy(
|
|
103
|
+
direction === 'asc'
|
|
104
|
+
? sql`${this.primary} asc nulls last`
|
|
105
|
+
: sql`${this.primary} desc nulls last`,
|
|
106
|
+
)
|
|
107
|
+
.orderBy(
|
|
108
|
+
direction === 'asc'
|
|
109
|
+
? sql`${this.secondary} asc nulls last`
|
|
110
|
+
: sql`${this.secondary} desc nulls last`,
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
.if(!!keysetSql, (qb) => (keysetSql ? qb.where(keysetSql) : qb)) as QB
|
|
114
|
+
}
|
|
82
115
|
}
|
|
83
116
|
|
|
84
117
|
type SortAtCidResult = { sortAt: string; cid: string }
|
|
85
|
-
type TimeCidLabeledResult =
|
|
118
|
+
type TimeCidLabeledResult = KeysetCursor
|
|
86
119
|
|
|
87
120
|
export class TimeCidKeyset<
|
|
88
121
|
TimeCidResult = SortAtCidResult,
|
|
@@ -97,7 +130,7 @@ export class TimeCidKeyset<
|
|
|
97
130
|
secondary: labeled.secondary,
|
|
98
131
|
}
|
|
99
132
|
}
|
|
100
|
-
cursorToLabeledResult(cursor:
|
|
133
|
+
cursorToLabeledResult(cursor: KeysetCursor) {
|
|
101
134
|
const primaryDate = new Date(parseInt(cursor.primary, 10))
|
|
102
135
|
if (isNaN(primaryDate.getTime())) {
|
|
103
136
|
throw new InvalidRequestError('Malformed cursor')
|
|
@@ -127,6 +160,9 @@ export class IndexedAtDidKeyset extends TimeCidKeyset<{
|
|
|
127
160
|
}
|
|
128
161
|
}
|
|
129
162
|
|
|
163
|
+
/**
|
|
164
|
+
* This is being deprecated. Use {@link GenericKeyset#paginate} instead.
|
|
165
|
+
*/
|
|
130
166
|
export const paginate = <
|
|
131
167
|
QB extends AnyQb,
|
|
132
168
|
K extends GenericKeyset<unknown, any>,
|
|
@@ -142,32 +178,119 @@ export const paginate = <
|
|
|
142
178
|
nullsLast?: boolean
|
|
143
179
|
},
|
|
144
180
|
): QB => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
181
|
+
return opts.keyset.paginate(qb, opts)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
type SingleKeyCursor = {
|
|
185
|
+
primary: string
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
type SingleKeyLabeledResult = {
|
|
189
|
+
primary: string | number
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* GenericSingleKey is similar to {@link GenericKeyset} but for a single key cursor.
|
|
194
|
+
*/
|
|
195
|
+
export abstract class GenericSingleKey<R, LR extends SingleKeyLabeledResult> {
|
|
196
|
+
constructor(public primary: DbRef) {}
|
|
197
|
+
abstract labelResult(result: R): LR
|
|
198
|
+
abstract labeledResultToCursor(labeled: LR): SingleKeyCursor
|
|
199
|
+
abstract cursorToLabeledResult(cursor: SingleKeyCursor): LR
|
|
200
|
+
packFromResult(results: R | R[]): string | undefined {
|
|
201
|
+
const result = Array.isArray(results) ? results.at(-1) : results
|
|
202
|
+
if (!result) return
|
|
203
|
+
return this.pack(this.labelResult(result))
|
|
204
|
+
}
|
|
205
|
+
pack(labeled?: LR): string | undefined {
|
|
206
|
+
if (!labeled) return
|
|
207
|
+
const cursor = this.labeledResultToCursor(labeled)
|
|
208
|
+
return this.packCursor(cursor)
|
|
209
|
+
}
|
|
210
|
+
unpack(cursorStr?: string): LR | undefined {
|
|
211
|
+
const cursor = this.unpackCursor(cursorStr)
|
|
212
|
+
if (!cursor) return
|
|
213
|
+
return this.cursorToLabeledResult(cursor)
|
|
214
|
+
}
|
|
215
|
+
packCursor(cursor?: SingleKeyCursor): string | undefined {
|
|
216
|
+
if (!cursor) return
|
|
217
|
+
return cursor.primary
|
|
218
|
+
}
|
|
219
|
+
unpackCursor(cursorStr?: string): SingleKeyCursor | undefined {
|
|
220
|
+
if (!cursorStr) return
|
|
221
|
+
const result = cursorStr.split('__')
|
|
222
|
+
const [primary, ...others] = result
|
|
223
|
+
if (!primary || others.length > 0) {
|
|
224
|
+
throw new InvalidRequestError('Malformed cursor')
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
primary,
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
getSql(labeled?: LR, direction?: 'asc' | 'desc') {
|
|
231
|
+
if (labeled === undefined) return
|
|
232
|
+
if (direction === 'asc') {
|
|
233
|
+
return sql`${this.primary} > ${labeled.primary}`
|
|
234
|
+
}
|
|
235
|
+
return sql`${this.primary} < ${labeled.primary}`
|
|
236
|
+
}
|
|
237
|
+
paginate<QB extends AnyQb>(
|
|
238
|
+
qb: QB,
|
|
239
|
+
opts: {
|
|
240
|
+
limit?: number
|
|
241
|
+
cursor?: string
|
|
242
|
+
direction?: 'asc' | 'desc'
|
|
243
|
+
// By default, pg does nullsFirst
|
|
244
|
+
nullsLast?: boolean
|
|
245
|
+
},
|
|
246
|
+
): QB {
|
|
247
|
+
const { limit, cursor, direction = 'desc', nullsLast } = opts
|
|
248
|
+
const keySql = this.getSql(this.unpack(cursor), direction)
|
|
249
|
+
return qb
|
|
250
|
+
.if(!!limit, (q) => q.limit(limit as number))
|
|
251
|
+
.if(!nullsLast, (q) => q.orderBy(this.primary, direction))
|
|
252
|
+
.if(!!nullsLast, (q) =>
|
|
253
|
+
q.orderBy(
|
|
167
254
|
direction === 'asc'
|
|
168
|
-
? sql`${
|
|
169
|
-
: sql`${
|
|
255
|
+
? sql`${this.primary} asc nulls last`
|
|
256
|
+
: sql`${this.primary} desc nulls last`,
|
|
170
257
|
),
|
|
171
|
-
|
|
172
|
-
|
|
258
|
+
)
|
|
259
|
+
.if(!!keySql, (qb) => (keySql ? qb.where(keySql) : qb)) as QB
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
type SortAtResult = { sortAt: string }
|
|
264
|
+
type TimeLabeledResult = SingleKeyCursor
|
|
265
|
+
|
|
266
|
+
export class IsoTimeKey<TimeResult = SortAtResult> extends GenericSingleKey<
|
|
267
|
+
TimeResult,
|
|
268
|
+
TimeLabeledResult
|
|
269
|
+
> {
|
|
270
|
+
labelResult(result: TimeResult): TimeLabeledResult
|
|
271
|
+
labelResult<TimeResult extends SortAtResult>(result: TimeResult) {
|
|
272
|
+
return { primary: result.sortAt }
|
|
273
|
+
}
|
|
274
|
+
labeledResultToCursor(labeled: TimeLabeledResult) {
|
|
275
|
+
return {
|
|
276
|
+
primary: new Date(labeled.primary).toISOString(),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
cursorToLabeledResult(cursor: SingleKeyCursor) {
|
|
280
|
+
const primaryDate = new Date(cursor.primary)
|
|
281
|
+
if (isNaN(primaryDate.getTime())) {
|
|
282
|
+
throw new InvalidRequestError('Malformed cursor')
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
primary: primaryDate.toISOString(),
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export class IsoSortAtKey extends IsoTimeKey<{
|
|
291
|
+
sortAt: string
|
|
292
|
+
}> {
|
|
293
|
+
labelResult(result: { sortAt: string }) {
|
|
294
|
+
return { primary: result.sortAt }
|
|
295
|
+
}
|
|
173
296
|
}
|
|
@@ -3,7 +3,7 @@ import { ServiceImpl } from '@connectrpc/connect'
|
|
|
3
3
|
import { sql } from 'kysely'
|
|
4
4
|
import { Service } from '../../../proto/bsky_connect'
|
|
5
5
|
import { Database } from '../db'
|
|
6
|
-
import {
|
|
6
|
+
import { IsoSortAtKey } from '../db/pagination'
|
|
7
7
|
import { countAll, notSoftDeletedClause } from '../db/util'
|
|
8
8
|
|
|
9
9
|
export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
@@ -41,15 +41,10 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
41
41
|
])
|
|
42
42
|
.select(priorityFollowQb.as('priority'))
|
|
43
43
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
ref('notif.recordCid'),
|
|
47
|
-
)
|
|
48
|
-
builder = paginate(builder, {
|
|
44
|
+
const key = new IsoSortAtKey(ref('notif.sortAt'))
|
|
45
|
+
builder = key.paginate(builder, {
|
|
49
46
|
cursor,
|
|
50
47
|
limit,
|
|
51
|
-
keyset,
|
|
52
|
-
tryIndex: true,
|
|
53
48
|
})
|
|
54
49
|
|
|
55
50
|
const notifsRes = await builder.execute()
|
|
@@ -63,7 +58,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
63
58
|
}))
|
|
64
59
|
return {
|
|
65
60
|
notifications,
|
|
66
|
-
cursor:
|
|
61
|
+
cursor: key.packFromResult(notifsRes),
|
|
67
62
|
}
|
|
68
63
|
},
|
|
69
64
|
|
package/src/etcd.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { once } from 'node:events'
|
|
2
|
+
import { Etcd3, Watcher } from 'etcd3'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A reactive map based on the keys and values stored within etcd under a given prefix.
|
|
6
|
+
*/
|
|
7
|
+
export class EtcdMap {
|
|
8
|
+
inner = new Map<string, VersionedValue>()
|
|
9
|
+
watcher: Watcher
|
|
10
|
+
connecting: Promise<void> | undefined
|
|
11
|
+
handlers: ((self: EtcdMap) => void)[] = []
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
private etcd: Etcd3,
|
|
15
|
+
private prefix = '',
|
|
16
|
+
) {
|
|
17
|
+
this.watcher = etcd.watch().prefix(prefix).watcher()
|
|
18
|
+
this.connecting = connectWatcher(this.watcher)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async connect() {
|
|
22
|
+
this.watcher.on('put', (kv) => {
|
|
23
|
+
const key = kv.key.toString()
|
|
24
|
+
const value = kv.value.toString()
|
|
25
|
+
const rev = revToInt(kv.mod_revision)
|
|
26
|
+
this.apply(key, { value, rev })
|
|
27
|
+
})
|
|
28
|
+
this.watcher.on('delete', (kv) => {
|
|
29
|
+
const key = kv.key.toString()
|
|
30
|
+
const value = null
|
|
31
|
+
const rev = revToInt(kv.mod_revision)
|
|
32
|
+
this.apply(key, { value, rev })
|
|
33
|
+
})
|
|
34
|
+
await this.connecting?.finally(() => {
|
|
35
|
+
this.connecting = undefined
|
|
36
|
+
})
|
|
37
|
+
const { kvs } = await this.etcd.getAll().prefix(this.prefix).exec()
|
|
38
|
+
for (const kv of kvs) {
|
|
39
|
+
const key = kv.key.toString()
|
|
40
|
+
const value = kv.value.toString()
|
|
41
|
+
const rev = revToInt(kv.mod_revision)
|
|
42
|
+
this.apply(key, { value, rev })
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get(key: string) {
|
|
47
|
+
return this.inner.get(key)?.value ?? null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
*values() {
|
|
51
|
+
for (const { value } of this.inner.values()) {
|
|
52
|
+
if (value !== null) {
|
|
53
|
+
yield value
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onUpdate(handler: (self: EtcdMap) => void) {
|
|
59
|
+
this.handlers.push(handler)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private update() {
|
|
63
|
+
for (const handler of this.handlers) {
|
|
64
|
+
handler(this)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private apply(key, vv: VersionedValue) {
|
|
69
|
+
const curr = this.inner.get(key)
|
|
70
|
+
if (curr && curr.rev > vv.rev) return
|
|
71
|
+
this.inner.set(key, vv)
|
|
72
|
+
this.update()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function revToInt(rev: string) {
|
|
77
|
+
return parseInt(rev, 10)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function connectWatcher(watcher: Watcher) {
|
|
81
|
+
await Promise.race([
|
|
82
|
+
once(watcher, 'connected'),
|
|
83
|
+
once(watcher, 'error').then((err) => Promise.reject(err)),
|
|
84
|
+
])
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type VersionedValue = {
|
|
88
|
+
rev: number
|
|
89
|
+
value: string | null
|
|
90
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import http from 'node:http'
|
|
|
3
3
|
import { AddressInfo } from 'node:net'
|
|
4
4
|
import compression from 'compression'
|
|
5
5
|
import cors from 'cors'
|
|
6
|
+
import { Etcd3 } from 'etcd3'
|
|
6
7
|
import express from 'express'
|
|
7
8
|
import { HttpTerminator, createHttpTerminator } from 'http-terminator'
|
|
8
9
|
import { AtpAgent } from '@atproto/api'
|
|
@@ -16,7 +17,11 @@ import { authWithApiKey as bsyncAuth, createBsyncClient } from './bsync'
|
|
|
16
17
|
import { ServerConfig } from './config'
|
|
17
18
|
import { AppContext } from './context'
|
|
18
19
|
import { authWithApiKey as courierAuth, createCourierClient } from './courier'
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
BasicHostList,
|
|
22
|
+
EtcdHostList,
|
|
23
|
+
createDataPlaneClient,
|
|
24
|
+
} from './data-plane/client'
|
|
20
25
|
import * as error from './error'
|
|
21
26
|
import { FeatureGates } from './feature-gates'
|
|
22
27
|
import { Hydrator } from './hydration/hydrator'
|
|
@@ -98,7 +103,20 @@ export class BskyAppView {
|
|
|
98
103
|
)
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
const
|
|
106
|
+
const etcd = config.etcdHosts.length
|
|
107
|
+
? new Etcd3({ hosts: config.etcdHosts })
|
|
108
|
+
: undefined
|
|
109
|
+
|
|
110
|
+
const dataplaneHostList =
|
|
111
|
+
etcd && config.dataplaneUrlsEtcdKeyPrefix
|
|
112
|
+
? new EtcdHostList(
|
|
113
|
+
etcd,
|
|
114
|
+
config.dataplaneUrlsEtcdKeyPrefix,
|
|
115
|
+
config.dataplaneUrls,
|
|
116
|
+
)
|
|
117
|
+
: new BasicHostList(config.dataplaneUrls)
|
|
118
|
+
|
|
119
|
+
const dataplane = createDataPlaneClient(dataplaneHostList, {
|
|
102
120
|
httpVersion: config.dataplaneHttpVersion,
|
|
103
121
|
rejectUnauthorized: !config.dataplaneIgnoreBadTls,
|
|
104
122
|
})
|
|
@@ -147,7 +165,9 @@ export class BskyAppView {
|
|
|
147
165
|
|
|
148
166
|
const ctx = new AppContext({
|
|
149
167
|
cfg: config,
|
|
168
|
+
etcd,
|
|
150
169
|
dataplane,
|
|
170
|
+
dataplaneHostList,
|
|
151
171
|
searchAgent,
|
|
152
172
|
suggestionsAgent,
|
|
153
173
|
topicsAgent,
|
|
@@ -184,6 +204,9 @@ export class BskyAppView {
|
|
|
184
204
|
}
|
|
185
205
|
|
|
186
206
|
async start(): Promise<http.Server> {
|
|
207
|
+
if (this.ctx.dataplaneHostList instanceof EtcdHostList) {
|
|
208
|
+
await this.ctx.dataplaneHostList.connect()
|
|
209
|
+
}
|
|
187
210
|
await this.ctx.featureGates.start()
|
|
188
211
|
const server = this.app.listen(this.ctx.cfg.port)
|
|
189
212
|
this.server = server
|
|
@@ -198,6 +221,7 @@ export class BskyAppView {
|
|
|
198
221
|
async destroy(): Promise<void> {
|
|
199
222
|
await this.terminator?.terminate()
|
|
200
223
|
this.ctx.featureGates.destroy()
|
|
224
|
+
await this.ctx.etcd?.close()
|
|
201
225
|
}
|
|
202
226
|
}
|
|
203
227
|
|
package/src/lexicon/index.ts
CHANGED
|
@@ -24,8 +24,11 @@ import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/u
|
|
|
24
24
|
import * as ComAtprotoAdminUpdateAccountPassword from './types/com/atproto/admin/updateAccountPassword.js'
|
|
25
25
|
import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus.js'
|
|
26
26
|
import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials.js'
|
|
27
|
+
import * as ComAtprotoIdentityRefreshIdentity from './types/com/atproto/identity/refreshIdentity.js'
|
|
27
28
|
import * as ComAtprotoIdentityRequestPlcOperationSignature from './types/com/atproto/identity/requestPlcOperationSignature.js'
|
|
29
|
+
import * as ComAtprotoIdentityResolveDid from './types/com/atproto/identity/resolveDid.js'
|
|
28
30
|
import * as ComAtprotoIdentityResolveHandle from './types/com/atproto/identity/resolveHandle.js'
|
|
31
|
+
import * as ComAtprotoIdentityResolveIdentity from './types/com/atproto/identity/resolveIdentity.js'
|
|
29
32
|
import * as ComAtprotoIdentitySignPlcOperation from './types/com/atproto/identity/signPlcOperation.js'
|
|
30
33
|
import * as ComAtprotoIdentitySubmitPlcOperation from './types/com/atproto/identity/submitPlcOperation.js'
|
|
31
34
|
import * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle.js'
|
|
@@ -436,6 +439,17 @@ export class ComAtprotoIdentityNS {
|
|
|
436
439
|
return this._server.xrpc.method(nsid, cfg)
|
|
437
440
|
}
|
|
438
441
|
|
|
442
|
+
refreshIdentity<AV extends AuthVerifier>(
|
|
443
|
+
cfg: ConfigOf<
|
|
444
|
+
AV,
|
|
445
|
+
ComAtprotoIdentityRefreshIdentity.Handler<ExtractAuth<AV>>,
|
|
446
|
+
ComAtprotoIdentityRefreshIdentity.HandlerReqCtx<ExtractAuth<AV>>
|
|
447
|
+
>,
|
|
448
|
+
) {
|
|
449
|
+
const nsid = 'com.atproto.identity.refreshIdentity' // @ts-ignore
|
|
450
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
451
|
+
}
|
|
452
|
+
|
|
439
453
|
requestPlcOperationSignature<AV extends AuthVerifier>(
|
|
440
454
|
cfg: ConfigOf<
|
|
441
455
|
AV,
|
|
@@ -449,6 +463,17 @@ export class ComAtprotoIdentityNS {
|
|
|
449
463
|
return this._server.xrpc.method(nsid, cfg)
|
|
450
464
|
}
|
|
451
465
|
|
|
466
|
+
resolveDid<AV extends AuthVerifier>(
|
|
467
|
+
cfg: ConfigOf<
|
|
468
|
+
AV,
|
|
469
|
+
ComAtprotoIdentityResolveDid.Handler<ExtractAuth<AV>>,
|
|
470
|
+
ComAtprotoIdentityResolveDid.HandlerReqCtx<ExtractAuth<AV>>
|
|
471
|
+
>,
|
|
472
|
+
) {
|
|
473
|
+
const nsid = 'com.atproto.identity.resolveDid' // @ts-ignore
|
|
474
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
475
|
+
}
|
|
476
|
+
|
|
452
477
|
resolveHandle<AV extends AuthVerifier>(
|
|
453
478
|
cfg: ConfigOf<
|
|
454
479
|
AV,
|
|
@@ -460,6 +485,17 @@ export class ComAtprotoIdentityNS {
|
|
|
460
485
|
return this._server.xrpc.method(nsid, cfg)
|
|
461
486
|
}
|
|
462
487
|
|
|
488
|
+
resolveIdentity<AV extends AuthVerifier>(
|
|
489
|
+
cfg: ConfigOf<
|
|
490
|
+
AV,
|
|
491
|
+
ComAtprotoIdentityResolveIdentity.Handler<ExtractAuth<AV>>,
|
|
492
|
+
ComAtprotoIdentityResolveIdentity.HandlerReqCtx<ExtractAuth<AV>>
|
|
493
|
+
>,
|
|
494
|
+
) {
|
|
495
|
+
const nsid = 'com.atproto.identity.resolveIdentity' // @ts-ignore
|
|
496
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
497
|
+
}
|
|
498
|
+
|
|
463
499
|
signPlcOperation<AV extends AuthVerifier>(
|
|
464
500
|
cfg: ConfigOf<
|
|
465
501
|
AV,
|