@atproto/bsky 0.0.156 → 0.0.158

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 (130) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/api/app/bsky/notification/getPreferences.d.ts +4 -0
  3. package/dist/api/app/bsky/notification/getPreferences.d.ts.map +1 -0
  4. package/dist/api/app/bsky/notification/getPreferences.js +39 -0
  5. package/dist/api/app/bsky/notification/getPreferences.js.map +1 -0
  6. package/dist/api/app/bsky/notification/putPreferencesV2.d.ts +4 -0
  7. package/dist/api/app/bsky/notification/putPreferencesV2.d.ts.map +1 -0
  8. package/dist/api/app/bsky/notification/putPreferencesV2.js +47 -0
  9. package/dist/api/app/bsky/notification/putPreferencesV2.js.map +1 -0
  10. package/dist/api/app/bsky/notification/util.d.ts +9 -0
  11. package/dist/api/app/bsky/notification/util.d.ts.map +1 -0
  12. package/dist/api/app/bsky/notification/util.js +84 -0
  13. package/dist/api/app/bsky/notification/util.js.map +1 -0
  14. package/dist/api/index.d.ts.map +1 -1
  15. package/dist/api/index.js +4 -0
  16. package/dist/api/index.js.map +1 -1
  17. package/dist/context.d.ts +3 -0
  18. package/dist/context.d.ts.map +1 -1
  19. package/dist/context.js +3 -0
  20. package/dist/context.js.map +1 -1
  21. package/dist/data-plane/bsync/index.d.ts.map +1 -1
  22. package/dist/data-plane/bsync/index.js +84 -0
  23. package/dist/data-plane/bsync/index.js.map +1 -1
  24. package/dist/data-plane/server/db/database-schema.d.ts +2 -1
  25. package/dist/data-plane/server/db/database-schema.d.ts.map +1 -1
  26. package/dist/data-plane/server/db/db.d.ts +1 -0
  27. package/dist/data-plane/server/db/db.d.ts.map +1 -1
  28. package/dist/data-plane/server/db/db.js.map +1 -1
  29. package/dist/data-plane/server/db/index.d.ts +1 -0
  30. package/dist/data-plane/server/db/index.d.ts.map +1 -1
  31. package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.d.ts +4 -0
  32. package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.d.ts.map +1 -0
  33. package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.js +24 -0
  34. package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.js.map +1 -0
  35. package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
  36. package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
  37. package/dist/data-plane/server/db/migrations/index.js +2 -1
  38. package/dist/data-plane/server/db/migrations/index.js.map +1 -1
  39. package/dist/data-plane/server/db/tables/private-data.d.ts +13 -0
  40. package/dist/data-plane/server/db/tables/private-data.d.ts.map +1 -0
  41. package/dist/data-plane/server/db/tables/private-data.js +5 -0
  42. package/dist/data-plane/server/db/tables/private-data.js.map +1 -0
  43. package/dist/data-plane/server/db/tables/record.d.ts +2 -1
  44. package/dist/data-plane/server/db/tables/record.d.ts.map +1 -1
  45. package/dist/data-plane/server/db/tables/record.js.map +1 -1
  46. package/dist/data-plane/server/index.d.ts +2 -1
  47. package/dist/data-plane/server/index.d.ts.map +1 -1
  48. package/dist/data-plane/server/index.js.map +1 -1
  49. package/dist/data-plane/server/routes/index.d.ts.map +1 -1
  50. package/dist/data-plane/server/routes/index.js +2 -0
  51. package/dist/data-plane/server/routes/index.js.map +1 -1
  52. package/dist/data-plane/server/routes/private-data.d.ts +9 -0
  53. package/dist/data-plane/server/routes/private-data.d.ts.map +1 -0
  54. package/dist/data-plane/server/routes/private-data.js +64 -0
  55. package/dist/data-plane/server/routes/private-data.js.map +1 -0
  56. package/dist/data-plane/server/util.d.ts +6 -6
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +3 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/lexicon/index.d.ts +4 -0
  61. package/dist/lexicon/index.d.ts.map +1 -1
  62. package/dist/lexicon/index.js +8 -0
  63. package/dist/lexicon/index.js.map +1 -1
  64. package/dist/lexicon/lexicons.d.ts +418 -0
  65. package/dist/lexicon/lexicons.d.ts.map +1 -1
  66. package/dist/lexicon/lexicons.js +225 -0
  67. package/dist/lexicon/lexicons.js.map +1 -1
  68. package/dist/lexicon/types/app/bsky/notification/defs.d.ts +40 -0
  69. package/dist/lexicon/types/app/bsky/notification/defs.d.ts.map +1 -1
  70. package/dist/lexicon/types/app/bsky/notification/defs.js +36 -0
  71. package/dist/lexicon/types/app/bsky/notification/defs.js.map +1 -1
  72. package/dist/lexicon/types/app/bsky/notification/getPreferences.d.ts +35 -0
  73. package/dist/lexicon/types/app/bsky/notification/getPreferences.d.ts.map +1 -0
  74. package/dist/lexicon/types/app/bsky/notification/getPreferences.js +7 -0
  75. package/dist/lexicon/types/app/bsky/notification/getPreferences.js.map +1 -0
  76. package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.d.ts +52 -0
  77. package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.d.ts.map +1 -0
  78. package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.js +7 -0
  79. package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.js.map +1 -0
  80. package/dist/proto/bsky_connect.d.ts +10 -1
  81. package/dist/proto/bsky_connect.d.ts.map +1 -1
  82. package/dist/proto/bsky_connect.js +9 -0
  83. package/dist/proto/bsky_connect.js.map +1 -1
  84. package/dist/proto/bsky_pb.d.ts +238 -0
  85. package/dist/proto/bsky_pb.d.ts.map +1 -1
  86. package/dist/proto/bsky_pb.js +703 -5
  87. package/dist/proto/bsky_pb.js.map +1 -1
  88. package/dist/proto/bsync_pb.d.ts +10 -10
  89. package/dist/proto/bsync_pb.d.ts.map +1 -1
  90. package/dist/proto/bsync_pb.js +15 -15
  91. package/dist/proto/bsync_pb.js.map +1 -1
  92. package/dist/stash.d.ts +26 -0
  93. package/dist/stash.d.ts.map +1 -0
  94. package/dist/stash.js +56 -0
  95. package/dist/stash.js.map +1 -0
  96. package/package.json +8 -8
  97. package/proto/bsky.proto +62 -0
  98. package/src/api/app/bsky/notification/getPreferences.ts +50 -0
  99. package/src/api/app/bsky/notification/putPreferencesV2.ts +62 -0
  100. package/src/api/app/bsky/notification/util.ts +123 -0
  101. package/src/api/index.ts +4 -0
  102. package/src/context.ts +6 -0
  103. package/src/data-plane/bsync/index.ts +109 -1
  104. package/src/data-plane/server/db/database-schema.ts +3 -1
  105. package/src/data-plane/server/db/db.ts +2 -0
  106. package/src/data-plane/server/db/index.ts +1 -0
  107. package/src/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.ts +22 -0
  108. package/src/data-plane/server/db/migrations/index.ts +1 -0
  109. package/src/data-plane/server/db/tables/private-data.ts +13 -0
  110. package/src/data-plane/server/db/tables/record.ts +3 -1
  111. package/src/data-plane/server/index.ts +3 -1
  112. package/src/data-plane/server/routes/index.ts +2 -0
  113. package/src/data-plane/server/routes/private-data.ts +94 -0
  114. package/src/index.ts +4 -0
  115. package/src/lexicon/index.ts +24 -0
  116. package/src/lexicon/lexicons.ts +227 -0
  117. package/src/lexicon/types/app/bsky/notification/defs.ts +76 -0
  118. package/src/lexicon/types/app/bsky/notification/getPreferences.ts +52 -0
  119. package/src/lexicon/types/app/bsky/notification/putPreferencesV2.ts +69 -0
  120. package/src/proto/bsky_connect.ts +11 -0
  121. package/src/proto/bsky_pb.ts +675 -0
  122. package/src/proto/bsync_pb.ts +15 -15
  123. package/src/stash.ts +75 -0
  124. package/tests/stash.test.ts +156 -0
  125. package/tests/views/notifications.test.ts +221 -0
  126. package/tests/views/thread-v2.test.ts +31 -33
  127. package/tsconfig.build.tsbuildinfo +1 -1
  128. package/tsconfig.tests.tsbuildinfo +1 -1
  129. package/tests/seed/thread-v2.ts +0 -874
  130. package/tests/seed/util.ts +0 -52
@@ -0,0 +1,123 @@
1
+ import { Un$Typed } from '@atproto/api'
2
+ import {
3
+ ChatPreference,
4
+ FilterablePreference,
5
+ Preference,
6
+ Preferences,
7
+ } from '../../../../lexicon/types/app/bsky/notification/defs'
8
+ import {
9
+ ChatNotificationFilter,
10
+ ChatNotificationPreference,
11
+ FilterableNotificationPreference,
12
+ NotificationFilter,
13
+ NotificationPreference,
14
+ NotificationPreferences,
15
+ } from '../../../../proto/bsky_pb'
16
+
17
+ type DeepPartial<T> = T extends object
18
+ ? {
19
+ [P in keyof T]?: DeepPartial<T[P]>
20
+ }
21
+ : T
22
+
23
+ const ensureChatPreference = (
24
+ p?: DeepPartial<ChatPreference>,
25
+ ): ChatPreference => {
26
+ const filters = ['all', 'accepted']
27
+ return {
28
+ filter:
29
+ typeof p?.filter === 'string' && filters.includes(p.filter)
30
+ ? p.filter
31
+ : 'all',
32
+ push: p?.push ?? true,
33
+ }
34
+ }
35
+
36
+ const ensureFilterablePreference = (
37
+ p?: DeepPartial<FilterablePreference>,
38
+ ): FilterablePreference => {
39
+ const filters = ['all', 'follows']
40
+ return {
41
+ filter:
42
+ typeof p?.filter === 'string' && filters.includes(p.filter)
43
+ ? p.filter
44
+ : 'all',
45
+ list: p?.list ?? true,
46
+ push: p?.push ?? true,
47
+ }
48
+ }
49
+
50
+ const ensurePreference = (p?: DeepPartial<Preference>): Preference => {
51
+ return {
52
+ list: p?.list ?? true,
53
+ push: p?.push ?? true,
54
+ }
55
+ }
56
+
57
+ const ensurePreferences = (
58
+ p: DeepPartial<Preferences>,
59
+ ): Un$Typed<Preferences> => {
60
+ return {
61
+ chat: ensureChatPreference(p.chat),
62
+ follow: ensureFilterablePreference(p.follow),
63
+ like: ensureFilterablePreference(p.like),
64
+ likeViaRepost: ensureFilterablePreference(p.likeViaRepost),
65
+ mention: ensureFilterablePreference(p.mention),
66
+ quote: ensureFilterablePreference(p.quote),
67
+ reply: ensureFilterablePreference(p.reply),
68
+ repost: ensureFilterablePreference(p.repost),
69
+ repostViaRepost: ensureFilterablePreference(p.repostViaRepost),
70
+ starterpackJoined: ensurePreference(p.starterpackJoined),
71
+ subscribedPost: ensurePreference(p.subscribedPost),
72
+ unverified: ensurePreference(p.unverified),
73
+ verified: ensurePreference(p.verified),
74
+ }
75
+ }
76
+
77
+ const protobufChatPreferenceToLex = (
78
+ p?: DeepPartial<ChatNotificationPreference>,
79
+ ): DeepPartial<ChatPreference> => {
80
+ return {
81
+ filter: p?.filter === ChatNotificationFilter.ACCEPTED ? 'accepted' : 'all',
82
+ push: p?.push?.enabled,
83
+ }
84
+ }
85
+
86
+ const protobufFilterablePreferenceToLex = (
87
+ p?: DeepPartial<FilterableNotificationPreference>,
88
+ ): DeepPartial<FilterablePreference> => {
89
+ return {
90
+ filter: p?.filter === NotificationFilter.FOLLOWS ? 'follows' : 'all',
91
+ list: p?.list?.enabled,
92
+ push: p?.push?.enabled,
93
+ }
94
+ }
95
+
96
+ const protobufPreferenceToLex = (
97
+ p?: DeepPartial<NotificationPreference>,
98
+ ): DeepPartial<Preference> => {
99
+ return {
100
+ list: p?.list?.enabled,
101
+ push: p?.push?.enabled,
102
+ }
103
+ }
104
+
105
+ export const protobufToLex = (
106
+ res: DeepPartial<NotificationPreferences>,
107
+ ): Un$Typed<Preferences> => {
108
+ return ensurePreferences({
109
+ chat: protobufChatPreferenceToLex(res.chat),
110
+ follow: protobufFilterablePreferenceToLex(res.follow),
111
+ like: protobufFilterablePreferenceToLex(res.like),
112
+ likeViaRepost: protobufFilterablePreferenceToLex(res.likeViaRepost),
113
+ mention: protobufFilterablePreferenceToLex(res.mention),
114
+ quote: protobufFilterablePreferenceToLex(res.quote),
115
+ reply: protobufFilterablePreferenceToLex(res.reply),
116
+ repost: protobufFilterablePreferenceToLex(res.repost),
117
+ repostViaRepost: protobufFilterablePreferenceToLex(res.repostViaRepost),
118
+ starterpackJoined: protobufPreferenceToLex(res.starterpackJoined),
119
+ subscribedPost: protobufPreferenceToLex(res.subscribedPost),
120
+ unverified: protobufPreferenceToLex(res.unverified),
121
+ verified: protobufPreferenceToLex(res.verified),
122
+ })
123
+ }
package/src/api/index.ts CHANGED
@@ -42,9 +42,11 @@ import unmuteActor from './app/bsky/graph/unmuteActor'
42
42
  import unmuteActorList from './app/bsky/graph/unmuteActorList'
43
43
  import unmuteThread from './app/bsky/graph/unmuteThread'
44
44
  import getLabelerServices from './app/bsky/labeler/getServices'
45
+ import getPreferences from './app/bsky/notification/getPreferences'
45
46
  import getUnreadCount from './app/bsky/notification/getUnreadCount'
46
47
  import listNotifications from './app/bsky/notification/listNotifications'
47
48
  import putPreferences from './app/bsky/notification/putPreferences'
49
+ import putPreferencesV2 from './app/bsky/notification/putPreferencesV2'
48
50
  import registerPush from './app/bsky/notification/registerPush'
49
51
  import updateSeen from './app/bsky/notification/updateSeen'
50
52
  import getConfig from './app/bsky/unspecced/getConfig'
@@ -122,10 +124,12 @@ export default function (server: Server, ctx: AppContext) {
122
124
  searchActors(server, ctx)
123
125
  searchActorsTypeahead(server, ctx)
124
126
  getSuggestions(server, ctx)
127
+ getPreferences(server, ctx)
125
128
  getUnreadCount(server, ctx)
126
129
  listNotifications(server, ctx)
127
130
  updateSeen(server, ctx)
128
131
  putPreferences(server, ctx)
132
+ putPreferencesV2(server, ctx)
129
133
  registerPush(server, ctx)
130
134
  getConfig(server, ctx)
131
135
  getPopularFeedGenerators(server, ctx)
package/src/context.ts CHANGED
@@ -13,6 +13,7 @@ import { DataPlaneClient, HostList } from './data-plane/client'
13
13
  import { FeatureGates } from './feature-gates'
14
14
  import { Hydrator } from './hydration/hydrator'
15
15
  import { httpLogger as log } from './logger'
16
+ import { StashClient } from './stash'
16
17
  import {
17
18
  ParsedLabelers,
18
19
  defaultLabelerHeader,
@@ -35,6 +36,7 @@ export class AppContext {
35
36
  signingKey: Keypair
36
37
  idResolver: IdResolver
37
38
  bsyncClient: BsyncClient
39
+ stashClient: StashClient
38
40
  courierClient: CourierClient | undefined
39
41
  authVerifier: AuthVerifier
40
42
  featureGates: FeatureGates
@@ -94,6 +96,10 @@ export class AppContext {
94
96
  return this.opts.bsyncClient
95
97
  }
96
98
 
99
+ get stashClient(): StashClient {
100
+ return this.opts.stashClient
101
+ }
102
+
97
103
  get courierClient(): CourierClient | undefined {
98
104
  return this.opts.courierClient
99
105
  }
@@ -4,10 +4,15 @@ import http from 'node:http'
4
4
  import { ConnectRouter } from '@connectrpc/connect'
5
5
  import { expressConnectMiddleware } from '@connectrpc/connect-express'
6
6
  import express from 'express'
7
+ import { TID } from '@atproto/common'
7
8
  import { AtUri } from '@atproto/syntax'
8
9
  import { ids } from '../../lexicon/lexicons'
9
10
  import { Service } from '../../proto/bsync_connect'
10
- import { MuteOperation_Type } from '../../proto/bsync_pb'
11
+ import {
12
+ Method,
13
+ MuteOperation_Type,
14
+ PutOperationRequest,
15
+ } from '../../proto/bsync_pb'
11
16
  import { Database } from '../server/db'
12
17
 
13
18
  export class MockBsync {
@@ -138,7 +143,110 @@ const createRoutes = (db: Database) => (router: ConnectRouter) =>
138
143
  throw new Error('not implemented')
139
144
  },
140
145
 
146
+ async putOperation(req) {
147
+ const { actorDid, namespace, key, method, payload } = req
148
+ if (
149
+ method !== Method.CREATE &&
150
+ method !== Method.UPDATE &&
151
+ method !== Method.DELETE
152
+ ) {
153
+ throw new Error(`Unsupported method: ${method}`)
154
+ }
155
+
156
+ const now = new Date().toISOString()
157
+ if (namespace === 'app.bsky.notification.defs#preferences') {
158
+ await handleNotificationPreferencesOperation(db, req, now)
159
+ } else {
160
+ await handleGenericOperation(db, req, now)
161
+ }
162
+
163
+ return {
164
+ operation: {
165
+ id: TID.nextStr(),
166
+ actorDid,
167
+ namespace,
168
+ key,
169
+ method,
170
+ payload,
171
+ },
172
+ }
173
+ },
174
+
175
+ async scanOperations() {
176
+ throw new Error('not implemented')
177
+ },
178
+
141
179
  async ping() {
142
180
  return {}
143
181
  },
144
182
  })
183
+
184
+ const handleNotificationPreferencesOperation = async (
185
+ db: Database,
186
+ req: PutOperationRequest,
187
+ now: string,
188
+ ) => {
189
+ const { actorDid, namespace, key, method, payload } = req
190
+ if (method === Method.CREATE || method === Method.UPDATE) {
191
+ return db.db
192
+ .insertInto('private_data')
193
+ .values({
194
+ actorDid,
195
+ namespace,
196
+ key,
197
+ payload: Buffer.from(payload).toString('utf8'),
198
+ indexedAt: now,
199
+ updatedAt: now,
200
+ })
201
+ .onConflict((oc) =>
202
+ oc.columns(['actorDid', 'namespace', 'key']).doUpdateSet({
203
+ payload: Buffer.from(payload).toString('utf8'),
204
+ updatedAt: now,
205
+ }),
206
+ )
207
+ .execute()
208
+ }
209
+
210
+ return handleGenericOperation(db, req, now)
211
+ }
212
+
213
+ const handleGenericOperation = async (
214
+ db: Database,
215
+ req: PutOperationRequest,
216
+ now: string,
217
+ ) => {
218
+ const { actorDid, namespace, key, method, payload } = req
219
+ if (method === Method.CREATE) {
220
+ return db.db
221
+ .insertInto('private_data')
222
+ .values({
223
+ actorDid,
224
+ namespace,
225
+ key,
226
+ payload: Buffer.from(payload).toString('utf8'),
227
+ indexedAt: now,
228
+ updatedAt: now,
229
+ })
230
+ .execute()
231
+ }
232
+
233
+ if (method === Method.UPDATE) {
234
+ return db.db
235
+ .updateTable('private_data')
236
+ .where('actorDid', '=', actorDid)
237
+ .where('namespace', '=', namespace)
238
+ .where('key', '=', key)
239
+ .set({
240
+ payload: Buffer.from(payload).toString('utf8'),
241
+ updatedAt: now,
242
+ })
243
+ .execute()
244
+ }
245
+
246
+ return db.db
247
+ .deleteFrom('private_data')
248
+ .where('actorDid', '=', actorDid)
249
+ .where('namespace', '=', namespace)
250
+ .where('key', '=', key)
251
+ .execute()
252
+ }
@@ -24,6 +24,7 @@ import * as post from './tables/post'
24
24
  import * as postAgg from './tables/post-agg'
25
25
  import * as postEmbed from './tables/post-embed'
26
26
  import * as postgate from './tables/post-gate'
27
+ import * as privateData from './tables/private-data'
27
28
  import * as profile from './tables/profile'
28
29
  import * as profileAgg from './tables/profile-agg'
29
30
  import * as quote from './tables/quote'
@@ -77,6 +78,7 @@ export type DatabaseSchemaType = duplicateRecord.PartialDB &
77
78
  starterPack.PartialDB &
78
79
  taggedSuggestion.PartialDB &
79
80
  quote.PartialDB &
80
- verification.PartialDB
81
+ verification.PartialDB &
82
+ privateData.PartialDB
81
83
 
82
84
  export type DatabaseSchema = Kysely<DatabaseSchemaType>
@@ -19,6 +19,8 @@ import * as migrations from './migrations'
19
19
  import { CtxMigrationProvider } from './migrations/provider'
20
20
  import { PgOptions } from './types'
21
21
 
22
+ export type { DatabaseSchema }
23
+
22
24
  export class Database {
23
25
  pool: PgPool
24
26
  db: DatabaseSchema
@@ -1 +1,2 @@
1
1
  export * from './db'
2
+ export type { DatabaseSchema } from './db'
@@ -0,0 +1,22 @@
1
+ import { Kysely } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('private_data')
6
+ .addColumn('actorDid', 'varchar', (col) => col.notNull())
7
+ .addColumn('namespace', 'varchar', (col) => col.notNull())
8
+ .addColumn('key', 'varchar', (col) => col.notNull())
9
+ .addColumn('payload', 'text', (col) => col.notNull())
10
+ .addColumn('indexedAt', 'varchar', (col) => col.notNull())
11
+ .addColumn('updatedAt', 'varchar', (col) => col.notNull())
12
+ .addPrimaryKeyConstraint('private_data_pkey', [
13
+ 'actorDid',
14
+ 'namespace',
15
+ 'key',
16
+ ])
17
+ .execute()
18
+ }
19
+
20
+ export async function down(db: Kysely<unknown>): Promise<void> {
21
+ await db.schema.dropTable('private_data').execute()
22
+ }
@@ -50,3 +50,4 @@ export * as _20250207T174822012Z from './20250207T174822012Z-add-label-exp'
50
50
  export * as _20250404T163421487Z from './20250404T163421487Z-verifications'
51
51
  export * as _20250526T023712742Z from './20250526T023712742Z-like-repost-via'
52
52
  export * as _20250528T221913281Z from './20250528T221913281Z-add-record-tags'
53
+ export * as _20250602T190357447Z from './20250602T190357447Z-add-private-data'
@@ -0,0 +1,13 @@
1
+ export interface PrivateData {
2
+ actorDid: string
3
+ namespace: string
4
+ key: string
5
+ // JSON-encoded
6
+ payload: string
7
+ indexedAt: string
8
+ updatedAt: string
9
+ }
10
+
11
+ export const tableName = 'private_data'
12
+
13
+ export type PartialDB = { [tableName]: PrivateData }
@@ -1,3 +1,5 @@
1
+ import { ColumnType } from 'kysely'
2
+
1
3
  export interface Record {
2
4
  uri: string
3
5
  cid: string
@@ -5,7 +7,7 @@ export interface Record {
5
7
  json: string
6
8
  indexedAt: string
7
9
  takedownRef: string | null
8
- tags: string[] | null
10
+ tags: ColumnType<string[] | null, string | undefined, string> | null
9
11
  }
10
12
 
11
13
  export const tableName = 'record'
@@ -3,9 +3,11 @@ import http from 'node:http'
3
3
  import { expressConnectMiddleware } from '@connectrpc/connect-express'
4
4
  import express from 'express'
5
5
  import { IdResolver, MemoryCache } from '@atproto/identity'
6
- import { Database } from './db'
6
+ import { Database, DatabaseSchema } from './db'
7
7
  import createRoutes from './routes'
8
8
 
9
+ export type { DatabaseSchema }
10
+
9
11
  export { RepoSubscription } from './subscription'
10
12
 
11
13
  export class DataPlaneServer {
@@ -14,6 +14,7 @@ import lists from './lists'
14
14
  import moderation from './moderation'
15
15
  import mutes from './mutes'
16
16
  import notifs from './notifs'
17
+ import privateData from './private-data'
17
18
  import profile from './profile'
18
19
  import quotes from './quotes'
19
20
  import records from './records'
@@ -40,6 +41,7 @@ export default (db: Database, idResolver: IdResolver) =>
40
41
  ...moderation(db),
41
42
  ...mutes(db),
42
43
  ...notifs(db),
44
+ ...privateData(db),
43
45
  ...profile(db),
44
46
  ...quotes(db),
45
47
  ...records(db),
@@ -0,0 +1,94 @@
1
+ import { ServiceImpl } from '@connectrpc/connect'
2
+ import { keyBy } from '@atproto/common'
3
+ import {
4
+ ChatPreference,
5
+ FilterablePreference,
6
+ Preference,
7
+ Preferences,
8
+ } from '../../../lexicon/types/app/bsky/notification/defs'
9
+ import { Service } from '../../../proto/bsky_connect'
10
+ import {
11
+ ChatNotificationFilter,
12
+ ChatNotificationPreference,
13
+ FilterableNotificationPreference,
14
+ NotificationFilter,
15
+ NotificationPreference,
16
+ NotificationPreferences,
17
+ } from '../../../proto/bsky_pb'
18
+ import { Database } from '../db'
19
+
20
+ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
21
+ async getNotificationPreferences(req) {
22
+ const { dids } = req
23
+ const res = await db.db
24
+ .selectFrom('private_data')
25
+ .selectAll()
26
+ .where('actorDid', 'in', dids)
27
+ .where('namespace', '=', 'app.bsky.notification.defs#preferences')
28
+ .where('key', '=', 'self')
29
+ .execute()
30
+
31
+ const byDid = keyBy(res, 'actorDid')
32
+ const preferences = dids.map((did) => {
33
+ const row = byDid.get(did)
34
+ if (!row) {
35
+ return {}
36
+ }
37
+ const p: Preferences = JSON.parse(row.payload)
38
+ return lexToProtobuf(p, row.payload)
39
+ })
40
+
41
+ return { preferences }
42
+ },
43
+ })
44
+
45
+ export const lexToProtobuf = (
46
+ p: Preferences,
47
+ json: string,
48
+ ): NotificationPreferences => {
49
+ return new NotificationPreferences({
50
+ entry: Buffer.from(json),
51
+ chat: lexChatPreferenceToProtobuf(p.chat),
52
+ follow: lexFilterablePreferenceToProtobuf(p.follow),
53
+ like: lexFilterablePreferenceToProtobuf(p.like),
54
+ likeViaRepost: lexFilterablePreferenceToProtobuf(p.likeViaRepost),
55
+ mention: lexFilterablePreferenceToProtobuf(p.mention),
56
+ quote: lexFilterablePreferenceToProtobuf(p.quote),
57
+ reply: lexFilterablePreferenceToProtobuf(p.reply),
58
+ repost: lexFilterablePreferenceToProtobuf(p.repost),
59
+ repostViaRepost: lexFilterablePreferenceToProtobuf(p.repostViaRepost),
60
+ starterpackJoined: lexPreferenceToProtobuf(p.starterpackJoined),
61
+ subscribedPost: lexPreferenceToProtobuf(p.subscribedPost),
62
+ unverified: lexPreferenceToProtobuf(p.unverified),
63
+ verified: lexPreferenceToProtobuf(p.verified),
64
+ })
65
+ }
66
+
67
+ const lexChatPreferenceToProtobuf = (
68
+ p: ChatPreference,
69
+ ): ChatNotificationPreference =>
70
+ new ChatNotificationPreference({
71
+ filter:
72
+ p.filter === 'accepted'
73
+ ? ChatNotificationFilter.ACCEPTED
74
+ : ChatNotificationFilter.ALL,
75
+ push: { enabled: p.push ?? true },
76
+ })
77
+
78
+ const lexFilterablePreferenceToProtobuf = (
79
+ p: FilterablePreference,
80
+ ): FilterableNotificationPreference =>
81
+ new FilterableNotificationPreference({
82
+ filter:
83
+ p.filter === 'follows'
84
+ ? NotificationFilter.FOLLOWS
85
+ : NotificationFilter.ALL,
86
+ list: { enabled: p.list ?? true },
87
+ push: { enabled: p.push ?? true },
88
+ })
89
+
90
+ const lexPreferenceToProtobuf = (p: Preference): NotificationPreference =>
91
+ new NotificationPreference({
92
+ list: { enabled: p.list ?? true },
93
+ push: { enabled: p.push ?? true },
94
+ })
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ import * as imageServer from './image/server'
29
29
  import { ImageUriBuilder } from './image/uri'
30
30
  import { createServer } from './lexicon'
31
31
  import { loggerMiddleware } from './logger'
32
+ import { createStashClient } from './stash'
32
33
  import { Views } from './views'
33
34
  import { VideoUriBuilder } from './views/util'
34
35
 
@@ -136,6 +137,8 @@ export class BskyAppView {
136
137
  interceptors: config.bsyncApiKey ? [bsyncAuth(config.bsyncApiKey)] : [],
137
138
  })
138
139
 
140
+ const stashClient = createStashClient(bsyncClient)
141
+
139
142
  const courierClient = config.courierUrl
140
143
  ? createCourierClient({
141
144
  baseUrl: config.courierUrl,
@@ -178,6 +181,7 @@ export class BskyAppView {
178
181
  signingKey,
179
182
  idResolver,
180
183
  bsyncClient,
184
+ stashClient,
181
185
  courierClient,
182
186
  authVerifier,
183
187
  featureGates,
@@ -138,9 +138,11 @@ import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor.js'
138
138
  import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList.js'
139
139
  import * as AppBskyGraphUnmuteThread from './types/app/bsky/graph/unmuteThread.js'
140
140
  import * as AppBskyLabelerGetServices from './types/app/bsky/labeler/getServices.js'
141
+ import * as AppBskyNotificationGetPreferences from './types/app/bsky/notification/getPreferences.js'
141
142
  import * as AppBskyNotificationGetUnreadCount from './types/app/bsky/notification/getUnreadCount.js'
142
143
  import * as AppBskyNotificationListNotifications from './types/app/bsky/notification/listNotifications.js'
143
144
  import * as AppBskyNotificationPutPreferences from './types/app/bsky/notification/putPreferences.js'
145
+ import * as AppBskyNotificationPutPreferencesV2 from './types/app/bsky/notification/putPreferencesV2.js'
144
146
  import * as AppBskyNotificationRegisterPush from './types/app/bsky/notification/registerPush.js'
145
147
  import * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/updateSeen.js'
146
148
  import * as AppBskyUnspeccedGetConfig from './types/app/bsky/unspecced/getConfig.js'
@@ -1853,6 +1855,17 @@ export class AppBskyNotificationNS {
1853
1855
  this._server = server
1854
1856
  }
1855
1857
 
1858
+ getPreferences<AV extends AuthVerifier>(
1859
+ cfg: ConfigOf<
1860
+ AV,
1861
+ AppBskyNotificationGetPreferences.Handler<ExtractAuth<AV>>,
1862
+ AppBskyNotificationGetPreferences.HandlerReqCtx<ExtractAuth<AV>>
1863
+ >,
1864
+ ) {
1865
+ const nsid = 'app.bsky.notification.getPreferences' // @ts-ignore
1866
+ return this._server.xrpc.method(nsid, cfg)
1867
+ }
1868
+
1856
1869
  getUnreadCount<AV extends AuthVerifier>(
1857
1870
  cfg: ConfigOf<
1858
1871
  AV,
@@ -1886,6 +1899,17 @@ export class AppBskyNotificationNS {
1886
1899
  return this._server.xrpc.method(nsid, cfg)
1887
1900
  }
1888
1901
 
1902
+ putPreferencesV2<AV extends AuthVerifier>(
1903
+ cfg: ConfigOf<
1904
+ AV,
1905
+ AppBskyNotificationPutPreferencesV2.Handler<ExtractAuth<AV>>,
1906
+ AppBskyNotificationPutPreferencesV2.HandlerReqCtx<ExtractAuth<AV>>
1907
+ >,
1908
+ ) {
1909
+ const nsid = 'app.bsky.notification.putPreferencesV2' // @ts-ignore
1910
+ return this._server.xrpc.method(nsid, cfg)
1911
+ }
1912
+
1889
1913
  registerPush<AV extends AuthVerifier>(
1890
1914
  cfg: ConfigOf<
1891
1915
  AV,