@atproto/bsky 0.0.209 → 0.0.211

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 (216) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE.txt +1 -1
  3. package/dist/api/app/bsky/draft/createDraft.d.ts +4 -0
  4. package/dist/api/app/bsky/draft/createDraft.d.ts.map +1 -0
  5. package/dist/api/app/bsky/draft/createDraft.js +38 -0
  6. package/dist/api/app/bsky/draft/createDraft.js.map +1 -0
  7. package/dist/api/app/bsky/draft/deleteDraft.d.ts +4 -0
  8. package/dist/api/app/bsky/draft/deleteDraft.d.ts.map +1 -0
  9. package/dist/api/app/bsky/draft/deleteDraft.js +19 -0
  10. package/dist/api/app/bsky/draft/deleteDraft.js.map +1 -0
  11. package/dist/api/app/bsky/draft/getDrafts.d.ts +4 -0
  12. package/dist/api/app/bsky/draft/getDrafts.d.ts.map +1 -0
  13. package/dist/api/app/bsky/draft/getDrafts.js +36 -0
  14. package/dist/api/app/bsky/draft/getDrafts.js.map +1 -0
  15. package/dist/api/app/bsky/draft/updateDraft.d.ts +4 -0
  16. package/dist/api/app/bsky/draft/updateDraft.d.ts.map +1 -0
  17. package/dist/api/app/bsky/draft/updateDraft.js +24 -0
  18. package/dist/api/app/bsky/draft/updateDraft.js.map +1 -0
  19. package/dist/api/app/bsky/feed/searchPosts.js +3 -3
  20. package/dist/api/app/bsky/feed/searchPosts.js.map +1 -1
  21. package/dist/api/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.d.ts.map +1 -1
  22. package/dist/api/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.js +9 -8
  23. package/dist/api/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.js.map +1 -1
  24. package/dist/api/app/bsky/unspecced/getPostThreadV2.js +1 -1
  25. package/dist/api/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
  26. package/dist/api/app/bsky/unspecced/getSuggestedFeeds.d.ts.map +1 -1
  27. package/dist/api/app/bsky/unspecced/getSuggestedFeeds.js +3 -4
  28. package/dist/api/app/bsky/unspecced/getSuggestedFeeds.js.map +1 -1
  29. package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.d.ts.map +1 -1
  30. package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.js +9 -8
  31. package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.js.map +1 -1
  32. package/dist/api/app/bsky/unspecced/getSuggestedUsers.d.ts.map +1 -1
  33. package/dist/api/app/bsky/unspecced/getSuggestedUsers.js +37 -20
  34. package/dist/api/app/bsky/unspecced/getSuggestedUsers.js.map +1 -1
  35. package/dist/api/app/bsky/unspecced/getTrendingTopics.d.ts.map +1 -1
  36. package/dist/api/app/bsky/unspecced/getTrendingTopics.js +6 -2
  37. package/dist/api/app/bsky/unspecced/getTrendingTopics.js.map +1 -1
  38. package/dist/api/app/bsky/unspecced/getTrends.d.ts.map +1 -1
  39. package/dist/api/app/bsky/unspecced/getTrends.js +9 -8
  40. package/dist/api/app/bsky/unspecced/getTrends.js.map +1 -1
  41. package/dist/api/index.d.ts.map +1 -1
  42. package/dist/api/index.js +10 -0
  43. package/dist/api/index.js.map +1 -1
  44. package/dist/config.d.ts +6 -4
  45. package/dist/config.d.ts.map +1 -1
  46. package/dist/config.js +18 -21
  47. package/dist/config.js.map +1 -1
  48. package/dist/data-plane/bsync/index.js +38 -0
  49. package/dist/data-plane/bsync/index.js.map +1 -1
  50. package/dist/data-plane/server/db/database-schema.d.ts +2 -1
  51. package/dist/data-plane/server/db/database-schema.d.ts.map +1 -1
  52. package/dist/data-plane/server/db/database-schema.js.map +1 -1
  53. package/dist/data-plane/server/db/migrations/20260112T133951271Z-add-drafts.d.ts +4 -0
  54. package/dist/data-plane/server/db/migrations/20260112T133951271Z-add-drafts.d.ts.map +1 -0
  55. package/dist/data-plane/server/db/migrations/20260112T133951271Z-add-drafts.js +25 -0
  56. package/dist/data-plane/server/db/migrations/20260112T133951271Z-add-drafts.js.map +1 -0
  57. package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
  58. package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
  59. package/dist/data-plane/server/db/migrations/index.js +2 -1
  60. package/dist/data-plane/server/db/migrations/index.js.map +1 -1
  61. package/dist/data-plane/server/db/pagination.d.ts +9 -0
  62. package/dist/data-plane/server/db/pagination.d.ts.map +1 -1
  63. package/dist/data-plane/server/db/pagination.js +7 -1
  64. package/dist/data-plane/server/db/pagination.js.map +1 -1
  65. package/dist/data-plane/server/db/tables/draft.d.ts +12 -0
  66. package/dist/data-plane/server/db/tables/draft.d.ts.map +1 -0
  67. package/dist/data-plane/server/db/tables/draft.js +5 -0
  68. package/dist/data-plane/server/db/tables/draft.js.map +1 -0
  69. package/dist/data-plane/server/indexing/index.d.ts +2 -0
  70. package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
  71. package/dist/data-plane/server/indexing/index.js +2 -0
  72. package/dist/data-plane/server/indexing/index.js.map +1 -1
  73. package/dist/data-plane/server/indexing/plugins/germ-declaration.d.ts +7 -0
  74. package/dist/data-plane/server/indexing/plugins/germ-declaration.d.ts.map +1 -0
  75. package/dist/data-plane/server/indexing/plugins/germ-declaration.js +75 -0
  76. package/dist/data-plane/server/indexing/plugins/germ-declaration.js.map +1 -0
  77. package/dist/data-plane/server/routes/drafts.d.ts +6 -0
  78. package/dist/data-plane/server/routes/drafts.d.ts.map +1 -0
  79. package/dist/data-plane/server/routes/drafts.js +30 -0
  80. package/dist/data-plane/server/routes/drafts.js.map +1 -0
  81. package/dist/data-plane/server/routes/index.d.ts.map +1 -1
  82. package/dist/data-plane/server/routes/index.js +2 -0
  83. package/dist/data-plane/server/routes/index.js.map +1 -1
  84. package/dist/data-plane/server/routes/interactions.d.ts.map +1 -1
  85. package/dist/data-plane/server/routes/interactions.js +6 -0
  86. package/dist/data-plane/server/routes/interactions.js.map +1 -1
  87. package/dist/data-plane/server/routes/profile.d.ts.map +1 -1
  88. package/dist/data-plane/server/routes/profile.js +5 -1
  89. package/dist/data-plane/server/routes/profile.js.map +1 -1
  90. package/dist/data-plane/server/routes/records.d.ts.map +1 -1
  91. package/dist/data-plane/server/routes/records.js +1 -0
  92. package/dist/data-plane/server/routes/records.js.map +1 -1
  93. package/dist/data-plane/server/util.d.ts +6 -6
  94. package/dist/feature-gates.d.ts +17 -14
  95. package/dist/feature-gates.d.ts.map +1 -1
  96. package/dist/feature-gates.js +67 -35
  97. package/dist/feature-gates.js.map +1 -1
  98. package/dist/hydration/actor.d.ts +5 -0
  99. package/dist/hydration/actor.d.ts.map +1 -1
  100. package/dist/hydration/actor.js +13 -0
  101. package/dist/hydration/actor.js.map +1 -1
  102. package/dist/hydration/hydrator.d.ts +1 -1
  103. package/dist/hydration/hydrator.d.ts.map +1 -1
  104. package/dist/hydration/hydrator.js +6 -1
  105. package/dist/hydration/hydrator.js.map +1 -1
  106. package/dist/index.js +3 -3
  107. package/dist/index.js.map +1 -1
  108. package/dist/lexicon/index.d.ts +18 -0
  109. package/dist/lexicon/index.d.ts.map +1 -1
  110. package/dist/lexicon/index.js +55 -1
  111. package/dist/lexicon/index.js.map +1 -1
  112. package/dist/lexicon/lexicons.d.ts +1156 -278
  113. package/dist/lexicon/lexicons.d.ts.map +1 -1
  114. package/dist/lexicon/lexicons.js +455 -2
  115. package/dist/lexicon/lexicons.js.map +1 -1
  116. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +19 -1
  117. package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
  118. package/dist/lexicon/types/app/bsky/actor/defs.js +18 -0
  119. package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
  120. package/dist/lexicon/types/app/bsky/draft/createDraft.d.ts +27 -0
  121. package/dist/lexicon/types/app/bsky/draft/createDraft.d.ts.map +1 -0
  122. package/dist/lexicon/types/app/bsky/draft/createDraft.js +7 -0
  123. package/dist/lexicon/types/app/bsky/draft/createDraft.js.map +1 -0
  124. package/dist/lexicon/types/app/bsky/draft/defs.d.ts +106 -0
  125. package/dist/lexicon/types/app/bsky/draft/defs.d.ts.map +1 -0
  126. package/dist/lexicon/types/app/bsky/draft/defs.js +97 -0
  127. package/dist/lexicon/types/app/bsky/draft/defs.js.map +1 -0
  128. package/dist/lexicon/types/app/bsky/draft/deleteDraft.d.ts +14 -0
  129. package/dist/lexicon/types/app/bsky/draft/deleteDraft.d.ts.map +1 -0
  130. package/dist/lexicon/types/app/bsky/draft/deleteDraft.js +7 -0
  131. package/dist/lexicon/types/app/bsky/draft/deleteDraft.js.map +1 -0
  132. package/dist/lexicon/types/app/bsky/draft/getDrafts.d.ts +24 -0
  133. package/dist/lexicon/types/app/bsky/draft/getDrafts.d.ts.map +1 -0
  134. package/dist/lexicon/types/app/bsky/draft/getDrafts.js +7 -0
  135. package/dist/lexicon/types/app/bsky/draft/getDrafts.js.map +1 -0
  136. package/dist/lexicon/types/app/bsky/draft/updateDraft.d.ts +15 -0
  137. package/dist/lexicon/types/app/bsky/draft/updateDraft.d.ts.map +1 -0
  138. package/dist/lexicon/types/app/bsky/draft/updateDraft.js +7 -0
  139. package/dist/lexicon/types/app/bsky/draft/updateDraft.js.map +1 -0
  140. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedUsers.d.ts +1 -1
  141. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedUsers.js.map +1 -1
  142. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedUsersSkeleton.d.ts +1 -1
  143. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedUsersSkeleton.js.map +1 -1
  144. package/dist/lexicon/types/com/germnetwork/declaration.d.ts +24 -0
  145. package/dist/lexicon/types/com/germnetwork/declaration.d.ts.map +1 -0
  146. package/dist/lexicon/types/com/germnetwork/declaration.js +27 -0
  147. package/dist/lexicon/types/com/germnetwork/declaration.js.map +1 -0
  148. package/dist/proto/bsky_connect.d.ts +22 -1
  149. package/dist/proto/bsky_connect.d.ts.map +1 -1
  150. package/dist/proto/bsky_connect.js +21 -0
  151. package/dist/proto/bsky_connect.js.map +1 -1
  152. package/dist/proto/bsky_pb.d.ts +119 -0
  153. package/dist/proto/bsky_pb.d.ts.map +1 -1
  154. package/dist/proto/bsky_pb.js +343 -5
  155. package/dist/proto/bsky_pb.js.map +1 -1
  156. package/dist/stash.d.ts +1 -0
  157. package/dist/stash.d.ts.map +1 -1
  158. package/dist/stash.js +1 -0
  159. package/dist/stash.js.map +1 -1
  160. package/dist/views/index.d.ts.map +1 -1
  161. package/dist/views/index.js +15 -3
  162. package/dist/views/index.js.map +1 -1
  163. package/package.json +10 -10
  164. package/proto/bsky.proto +39 -1
  165. package/src/api/app/bsky/draft/createDraft.ts +46 -0
  166. package/src/api/app/bsky/draft/deleteDraft.ts +19 -0
  167. package/src/api/app/bsky/draft/getDrafts.ts +46 -0
  168. package/src/api/app/bsky/draft/updateDraft.ts +26 -0
  169. package/src/api/app/bsky/feed/searchPosts.ts +4 -4
  170. package/src/api/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.ts +10 -10
  171. package/src/api/app/bsky/unspecced/getPostThreadV2.ts +2 -2
  172. package/src/api/app/bsky/unspecced/getSuggestedFeeds.ts +4 -5
  173. package/src/api/app/bsky/unspecced/getSuggestedStarterPacks.ts +10 -10
  174. package/src/api/app/bsky/unspecced/getSuggestedUsers.ts +60 -25
  175. package/src/api/app/bsky/unspecced/getTrendingTopics.ts +9 -5
  176. package/src/api/app/bsky/unspecced/getTrends.ts +10 -10
  177. package/src/api/index.ts +10 -0
  178. package/src/config.ts +23 -26
  179. package/src/data-plane/bsync/index.ts +47 -0
  180. package/src/data-plane/server/db/database-schema.ts +3 -1
  181. package/src/data-plane/server/db/migrations/20260112T133951271Z-add-drafts.ts +24 -0
  182. package/src/data-plane/server/db/migrations/index.ts +1 -0
  183. package/src/data-plane/server/db/pagination.ts +8 -0
  184. package/src/data-plane/server/db/tables/draft.ts +11 -0
  185. package/src/data-plane/server/indexing/index.ts +3 -0
  186. package/src/data-plane/server/indexing/plugins/germ-declaration.ts +63 -0
  187. package/src/data-plane/server/routes/drafts.ts +37 -0
  188. package/src/data-plane/server/routes/index.ts +2 -0
  189. package/src/data-plane/server/routes/interactions.ts +6 -0
  190. package/src/data-plane/server/routes/profile.ts +8 -0
  191. package/src/data-plane/server/routes/records.ts +1 -0
  192. package/src/feature-gates.ts +83 -35
  193. package/src/hydration/actor.ts +26 -2
  194. package/src/hydration/hydrator.ts +8 -1
  195. package/src/index.ts +3 -3
  196. package/src/lexicon/index.ts +72 -0
  197. package/src/lexicon/lexicons.ts +465 -2
  198. package/src/lexicon/types/app/bsky/actor/defs.ts +37 -0
  199. package/src/lexicon/types/app/bsky/draft/createDraft.ts +46 -0
  200. package/src/lexicon/types/app/bsky/draft/defs.ts +208 -0
  201. package/src/lexicon/types/app/bsky/draft/deleteDraft.ts +33 -0
  202. package/src/lexicon/types/app/bsky/draft/getDrafts.ts +42 -0
  203. package/src/lexicon/types/app/bsky/draft/updateDraft.ts +34 -0
  204. package/src/lexicon/types/app/bsky/unspecced/getSuggestedUsers.ts +1 -1
  205. package/src/lexicon/types/app/bsky/unspecced/getSuggestedUsersSkeleton.ts +1 -1
  206. package/src/lexicon/types/com/germnetwork/declaration.ts +53 -0
  207. package/src/proto/bsky_connect.ts +22 -1
  208. package/src/proto/bsky_pb.ts +235 -0
  209. package/src/stash.ts +3 -0
  210. package/src/views/index.ts +15 -3
  211. package/tests/views/__snapshots__/profile.test.ts.snap +7 -0
  212. package/tests/views/age-assurance-v2.test.ts +0 -2
  213. package/tests/views/age-assurance.test.ts +0 -2
  214. package/tests/views/drafts.test.ts +293 -0
  215. package/tests/views/profile.test.ts +58 -1
  216. package/tsconfig.build.tsbuildinfo +1 -1
@@ -9,7 +9,7 @@ import {
9
9
  } from '../../../../hydration/hydrator'
10
10
  import { Server } from '../../../../lexicon'
11
11
  import { SkeletonTrend } from '../../../../lexicon/types/app/bsky/unspecced/defs'
12
- import { QueryParams } from '../../../../lexicon/types/app/bsky/unspecced/getTrendingTopics'
12
+ import { QueryParams } from '../../../../lexicon/types/app/bsky/unspecced/getTrends'
13
13
  import {
14
14
  HydrationFnInput,
15
15
  PresentationFnInput,
@@ -33,11 +33,10 @@ export default function (server: Server, ctx: AppContext) {
33
33
  ? req.headers['x-bsky-topics'].join(',')
34
34
  : req.headers['x-bsky-topics'],
35
35
  })
36
- const { ...result } = await getTrends(
36
+ const result = await getTrends(
37
37
  {
38
38
  ...params,
39
- viewer: viewer ?? undefined,
40
- hydrateCtx: hydrateCtx.copy({ viewer }),
39
+ hydrateCtx,
41
40
  headers,
42
41
  },
43
42
  ctx,
@@ -56,7 +55,7 @@ const skeleton = async (input: SkeletonFnInput<Context, Params>) => {
56
55
  const res = await ctx.topicsAgent.app.bsky.unspecced.getTrendsSkeleton(
57
56
  {
58
57
  limit: params.limit,
59
- viewer: params.viewer,
58
+ viewer: params.hydrateCtx.viewer ?? undefined,
60
59
  },
61
60
  {
62
61
  headers: params.headers,
@@ -78,8 +77,9 @@ const hydration = async (
78
77
  }
79
78
  dids = dedupeStrs(dids)
80
79
  const pairs: Map<string, string[]> = new Map()
81
- if (params.viewer) {
82
- pairs.set(params.viewer, dids)
80
+ const viewer = params.hydrateCtx.viewer
81
+ if (viewer) {
82
+ pairs.set(viewer, dids)
83
83
  }
84
84
  const [profileState, bidirectionalBlocks] = await Promise.all([
85
85
  ctx.hydrator.hydrateProfilesBasic(dids, params.hydrateCtx),
@@ -91,12 +91,12 @@ const hydration = async (
91
91
 
92
92
  const noBlocks = (input: RulesFnInput<Context, Params, SkeletonState>) => {
93
93
  const { skeleton, params, hydration } = input
94
-
95
- if (!params.viewer) {
94
+ const viewer = params.hydrateCtx.viewer
95
+ if (!viewer) {
96
96
  return skeleton
97
97
  }
98
98
 
99
- const blocks = hydration.bidirectionalBlocks?.get(params.viewer)
99
+ const blocks = hydration.bidirectionalBlocks?.get(viewer)
100
100
  const filteredSkeleton: SkeletonState = {
101
101
  trends: skeleton.trends.map((t) => ({
102
102
  ...t,
package/src/api/index.ts CHANGED
@@ -19,6 +19,10 @@ import removeData from './app/bsky/contact/removeData'
19
19
  import sendNotification from './app/bsky/contact/sendNotification'
20
20
  import startPhoneVerification from './app/bsky/contact/startPhoneVerification'
21
21
  import verifyPhone from './app/bsky/contact/verifyPhone'
22
+ import createDraft from './app/bsky/draft/createDraft'
23
+ import deleteDraft from './app/bsky/draft/deleteDraft'
24
+ import getDrafts from './app/bsky/draft/getDrafts'
25
+ import updateDraft from './app/bsky/draft/updateDraft'
22
26
  import getActorFeeds from './app/bsky/feed/getActorFeeds'
23
27
  import getActorLikes from './app/bsky/feed/getActorLikes'
24
28
  import getAuthorFeed from './app/bsky/feed/getAuthorFeed'
@@ -66,6 +70,7 @@ import putActivitySubscription from './app/bsky/notification/putActivitySubscrip
66
70
  import putPreferences from './app/bsky/notification/putPreferences'
67
71
  import putPreferencesV2 from './app/bsky/notification/putPreferencesV2'
68
72
  import registerPush from './app/bsky/notification/registerPush'
73
+ import unregisterPush from './app/bsky/notification/unregisterPush'
69
74
  import updateSeen from './app/bsky/notification/updateSeen'
70
75
  import getAgeAssuranceState from './app/bsky/unspecced/getAgeAssuranceState'
71
76
  import getConfig from './app/bsky/unspecced/getConfig'
@@ -104,6 +109,10 @@ export default function (server: Server, ctx: AppContext) {
104
109
  createBookmark(server, ctx)
105
110
  deleteBookmark(server, ctx)
106
111
  getBookmarks(server, ctx)
112
+ createDraft(server, ctx)
113
+ deleteDraft(server, ctx)
114
+ getDrafts(server, ctx)
115
+ updateDraft(server, ctx)
107
116
  dismissMatch(server, ctx)
108
117
  getMatches(server, ctx)
109
118
  getSyncStatus(server, ctx)
@@ -172,6 +181,7 @@ export default function (server: Server, ctx: AppContext) {
172
181
  putPreferences(server, ctx)
173
182
  putPreferencesV2(server, ctx)
174
183
  registerPush(server, ctx)
184
+ unregisterPush(server, ctx)
175
185
  getConfig(server, ctx)
176
186
  getPopularFeedGenerators(server, ctx)
177
187
  getTaggedSuggestions(server, ctx)
package/src/config.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import assert from 'node:assert'
2
+ import { noUndefinedVals } from '@atproto/common'
2
3
  import { subLogger as log } from './logger'
3
4
 
4
5
  type LiveNowConfig = {
@@ -80,8 +81,8 @@ export interface ServerConfigValues {
80
81
  indexedAtEpoch?: Date
81
82
  // misc/dev
82
83
  blobCacheLocation?: string
83
- statsigKey?: string
84
- statsigEnv?: string
84
+ growthBookApiHost?: string
85
+ growthBookClientKey?: string
85
86
  // threads
86
87
  bigThreadUris: Set<string>
87
88
  bigThreadDepth?: number
@@ -106,6 +107,7 @@ export interface ServerConfigValues {
106
107
  proxyPreferCompressed?: boolean
107
108
  kws?: KwsConfig
108
109
  debugFieldAllowedDids: Set<string>
110
+ draftsLimit: number
109
111
  }
110
112
 
111
113
  export class ServerConfig {
@@ -210,14 +212,12 @@ export class ServerConfig {
210
212
  )
211
213
  const modServiceDid = process.env.MOD_SERVICE_DID
212
214
  assert(modServiceDid)
213
- const statsigKey =
215
+
216
+ const growthBookApiHost = process.env.BSKY_GROWTHBOOK_API_HOST || undefined
217
+ const growthBookClientKey =
214
218
  process.env.NODE_ENV === 'test'
215
219
  ? 'secret-key'
216
- : process.env.BSKY_STATSIG_KEY || undefined
217
- const statsigEnv =
218
- process.env.NODE_ENV === 'test'
219
- ? 'test'
220
- : process.env.BSKY_STATSIG_ENV || 'development'
220
+ : process.env.BSKY_GROWTHBOOK_CLIENT_KEY || undefined
221
221
  const clientCheckEmailConfirmed =
222
222
  process.env.BSKY_CLIENT_CHECK_EMAIL_CONFIRMED === 'true'
223
223
  const topicsEnabled = process.env.BSKY_TOPICS_ENABLED === 'true'
@@ -322,6 +322,10 @@ export class ServerConfig {
322
322
  envList(process.env.BSKY_DEBUG_FIELD_ALLOWED_DIDS),
323
323
  )
324
324
 
325
+ const draftsLimit = process.env.BSKY_DRAFTS_LIMIT
326
+ ? parseInt(process.env.BSKY_DRAFTS_LIMIT || '', 10)
327
+ : 500
328
+
325
329
  return new ServerConfig({
326
330
  version,
327
331
  debugMode,
@@ -365,8 +369,8 @@ export class ServerConfig {
365
369
  blobRateLimitBypassHostname,
366
370
  adminPasswords,
367
371
  modServiceDid,
368
- statsigKey,
369
- statsigEnv,
372
+ growthBookApiHost,
373
+ growthBookClientKey,
370
374
  clientCheckEmailConfirmed,
371
375
  topicsEnabled,
372
376
  indexedAtEpoch,
@@ -388,7 +392,8 @@ export class ServerConfig {
388
392
  proxyPreferCompressed,
389
393
  kws,
390
394
  debugFieldAllowedDids,
391
- ...stripUndefineds(overrides ?? {}),
395
+ draftsLimit,
396
+ ...noUndefinedVals(overrides ?? {}),
392
397
  })
393
398
  }
394
399
 
@@ -565,12 +570,12 @@ export class ServerConfig {
565
570
  return this.cfg.blobCacheLocation
566
571
  }
567
572
 
568
- get statsigKey() {
569
- return this.cfg.statsigKey
573
+ get growthBookApiHost() {
574
+ return this.cfg.growthBookApiHost
570
575
  }
571
576
 
572
- get statsigEnv() {
573
- return this.cfg.statsigEnv
577
+ get growthBookClientKey() {
578
+ return this.cfg.growthBookClientKey
574
579
  }
575
580
 
576
581
  get clientCheckEmailConfirmed() {
@@ -656,18 +661,10 @@ export class ServerConfig {
656
661
  get debugFieldAllowedDids() {
657
662
  return this.cfg.debugFieldAllowedDids
658
663
  }
659
- }
660
664
 
661
- function stripUndefineds(
662
- obj: Record<string, unknown>,
663
- ): Record<string, unknown> {
664
- const result = {}
665
- Object.entries(obj).forEach(([key, val]) => {
666
- if (val !== undefined) {
667
- result[key] = val
668
- }
669
- })
670
- return result
665
+ get draftsLimit() {
666
+ return this.cfg.draftsLimit
667
+ }
671
668
  }
672
669
 
673
670
  function envList(str: string | undefined): string[] {
@@ -180,6 +180,8 @@ const createRoutes = (db: Database) => (router: ConnectRouter) =>
180
180
  await handleAgeAssuranceV2EventOperation(db, req, now)
181
181
  } else if (namespace === Namespaces.AppBskyBookmarkDefsBookmark) {
182
182
  await handleBookmarkOperation(db, req, now)
183
+ } else if (namespace === Namespaces.AppBskyDraftDefsDraftWithId) {
184
+ await handleDraftOperation(db, req, now)
183
185
  }
184
186
  } catch (err) {
185
187
  httpLogger.warn({ err, namespace }, 'mock bsync put operation failed')
@@ -415,3 +417,48 @@ const handleBookmarkOperation = async (
415
417
  })
416
418
  }
417
419
  }
420
+
421
+ const handleDraftOperation = async (
422
+ db: Database,
423
+ req: PutOperationRequest,
424
+ now: string,
425
+ ) => {
426
+ const { actorDid, key, method, payload } = req
427
+
428
+ if (method === Method.CREATE) {
429
+ const payloadString = Buffer.from(payload).toString('utf8')
430
+
431
+ await db.db
432
+ .insertInto('draft')
433
+ .values({
434
+ creator: actorDid,
435
+ key,
436
+ createdAt: now,
437
+ updatedAt: now,
438
+ payload: payloadString,
439
+ })
440
+ .execute()
441
+ }
442
+
443
+ if (method === Method.UPDATE) {
444
+ const payloadString = Buffer.from(payload).toString('utf8')
445
+
446
+ await db.db
447
+ .updateTable('draft')
448
+ .where('creator', '=', actorDid)
449
+ .where('key', '=', key)
450
+ .set({
451
+ updatedAt: now,
452
+ payload: payloadString,
453
+ })
454
+ .execute()
455
+ }
456
+
457
+ if (method === Method.DELETE) {
458
+ await db.db
459
+ .deleteFrom('draft')
460
+ .where('creator', '=', actorDid)
461
+ .where('key', '=', key)
462
+ .execute()
463
+ }
464
+ }
@@ -8,6 +8,7 @@ import * as algo from './tables/algo'
8
8
  import * as blobTakedown from './tables/blob-takedown'
9
9
  import * as bookmark from './tables/bookmark'
10
10
  import * as didCache from './tables/did-cache'
11
+ import * as draft from './tables/draft'
11
12
  import * as duplicateRecord from './tables/duplicate-record'
12
13
  import * as feedGenerator from './tables/feed-generator'
13
14
  import * as feedItem from './tables/feed-item'
@@ -83,6 +84,7 @@ export type DatabaseSchemaType = duplicateRecord.PartialDB &
83
84
  verification.PartialDB &
84
85
  privateData.PartialDB &
85
86
  activitySubscription.PartialDB &
86
- bookmark.PartialDB
87
+ bookmark.PartialDB &
88
+ draft.PartialDB
87
89
 
88
90
  export type DatabaseSchema = Kysely<DatabaseSchemaType>
@@ -0,0 +1,24 @@
1
+ import { Kysely } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('draft')
6
+ .addColumn('creator', 'varchar', (col) => col.notNull())
7
+ .addColumn('key', 'varchar', (col) => col.notNull())
8
+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
9
+ .addColumn('updatedAt', 'varchar', (col) => col.notNull())
10
+ .addColumn('payload', 'text', (col) => col.notNull())
11
+ .addPrimaryKeyConstraint('draft_pkey', ['creator', 'key'])
12
+ .execute()
13
+
14
+ // Supports getting paginated drafts by updatedAt.
15
+ await db.schema
16
+ .createIndex('draft_creator_updated_at_key_idx')
17
+ .on('draft')
18
+ .columns(['creator', 'updatedAt', 'key'])
19
+ .execute()
20
+ }
21
+
22
+ export async function down(db: Kysely<unknown>): Promise<void> {
23
+ await db.schema.dropTable('draft').execute()
24
+ }
@@ -56,3 +56,4 @@ export * as _20250627T025331240Z from './20250627T025331240Z-add-actor-age-assur
56
56
  export * as _20250812T183735692Z from './20250812T183735692Z-add-bookmarks'
57
57
  export * as _20250813T174955711Z from './20250813T174955711Z-add-post-agg-bookmarks'
58
58
  export * as _20251120T004738098Z from './20251120T004738098Z-update-actor-age-assurance-v2'
59
+ export * as _20260112T133951271Z from './20260112T133951271Z-add-drafts'
@@ -296,6 +296,14 @@ export class IsoSortAtKey extends IsoTimeKey<{
296
296
  }
297
297
  }
298
298
 
299
+ export class IsoUpdatedAtKey extends IsoTimeKey<{
300
+ updatedAt: string
301
+ }> {
302
+ labelResult(result: { updatedAt: string }) {
303
+ return { primary: result.updatedAt }
304
+ }
305
+ }
306
+
299
307
  type KeyResult = { key: string }
300
308
  type RkeyLabeledResult = SingleKeyCursor
301
309
 
@@ -0,0 +1,11 @@
1
+ export interface Draft {
2
+ creator: string
3
+ key: string
4
+ createdAt: string
5
+ updatedAt: string
6
+ payload: string
7
+ }
8
+
9
+ export const tableName = 'draft'
10
+
11
+ export type PartialDB = { [tableName]: Draft }
@@ -21,6 +21,7 @@ import * as Block from './plugins/block'
21
21
  import * as ChatDeclaration from './plugins/chat-declaration'
22
22
  import * as FeedGenerator from './plugins/feed-generator'
23
23
  import * as Follow from './plugins/follow'
24
+ import * as GermDeclaration from './plugins/germ-declaration'
24
25
  import * as Labeler from './plugins/labeler'
25
26
  import * as Like from './plugins/like'
26
27
  import * as List from './plugins/list'
@@ -55,6 +56,7 @@ export class IndexingService {
55
56
  labeler: Labeler.PluginType
56
57
  notifDeclaration: NotifDeclaration.PluginType
57
58
  chatDeclaration: ChatDeclaration.PluginType
59
+ germDeclaration: GermDeclaration.PluginType
58
60
  verification: Verification.PluginType
59
61
  status: Status.PluginType
60
62
  }
@@ -81,6 +83,7 @@ export class IndexingService {
81
83
  labeler: Labeler.makePlugin(this.db, this.background),
82
84
  notifDeclaration: NotifDeclaration.makePlugin(this.db, this.background),
83
85
  chatDeclaration: ChatDeclaration.makePlugin(this.db, this.background),
86
+ germDeclaration: GermDeclaration.makePlugin(this.db, this.background),
84
87
  verification: Verification.makePlugin(this.db, this.background),
85
88
  status: Status.makePlugin(this.db, this.background),
86
89
  }
@@ -0,0 +1,63 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import { AtUri } from '@atproto/syntax'
3
+ import * as lex from '../../../../lexicon/lexicons'
4
+ import { BackgroundQueue } from '../../background'
5
+ import { Database } from '../../db'
6
+ import { DatabaseSchema } from '../../db/database-schema'
7
+ import { RecordProcessor } from '../processor'
8
+
9
+ // @NOTE this indexer is a placeholder to ensure it gets indexed in the generic records table
10
+
11
+ const lexId = lex.ids.ComGermnetworkDeclaration
12
+
13
+ const insertFn = async (
14
+ _db: DatabaseSchema,
15
+ uri: AtUri,
16
+ _cid: CID,
17
+ _obj: unknown,
18
+ _timestamp: string,
19
+ ): Promise<unknown | null> => {
20
+ if (uri.rkey !== 'self') return null
21
+ return true
22
+ }
23
+
24
+ const findDuplicate = async (): Promise<AtUri | null> => {
25
+ return null
26
+ }
27
+
28
+ const notifsForInsert = () => {
29
+ return []
30
+ }
31
+
32
+ const deleteFn = async (
33
+ _db: DatabaseSchema,
34
+ uri: AtUri,
35
+ ): Promise<unknown | null> => {
36
+ if (uri.rkey !== 'self') return null
37
+ return true
38
+ }
39
+
40
+ const notifsForDelete = () => {
41
+ return { notifs: [], toDelete: [] }
42
+ }
43
+
44
+ export type PluginType = RecordProcessor<unknown, unknown>
45
+
46
+ export const makePlugin = (
47
+ db: Database,
48
+ background: BackgroundQueue,
49
+ ): PluginType => {
50
+ const processor = new RecordProcessor(db, background, {
51
+ lexId,
52
+ insertFn,
53
+ findDuplicate,
54
+ deleteFn,
55
+ notifsForInsert,
56
+ notifsForDelete,
57
+ })
58
+ // @TODO use lexicon validation
59
+ processor.assertValidRecord = () => null
60
+ return processor
61
+ }
62
+
63
+ export default makePlugin
@@ -0,0 +1,37 @@
1
+ import { PlainMessage, Timestamp } from '@bufbuild/protobuf'
2
+ import { ServiceImpl } from '@connectrpc/connect'
3
+ import { Service } from '../../../proto/bsky_connect'
4
+ import { DraftInfo } from '../../../proto/bsky_pb'
5
+ import { Database } from '../db'
6
+ import { IsoUpdatedAtKey } from '../db/pagination'
7
+
8
+ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
9
+ async getActorDrafts(req) {
10
+ const { actorDid, cursor, limit } = req
11
+ const { ref } = db.db.dynamic
12
+
13
+ let builder = db.db
14
+ .selectFrom('draft')
15
+ .where('draft.creator', '=', actorDid)
16
+ .selectAll()
17
+
18
+ const key = new IsoUpdatedAtKey(ref('draft.updatedAt'))
19
+ builder = key.paginate(builder, {
20
+ cursor,
21
+ limit,
22
+ })
23
+
24
+ const res = await builder.execute()
25
+ return {
26
+ drafts: res.map(
27
+ (d): PlainMessage<DraftInfo> => ({
28
+ key: d.key,
29
+ payload: Buffer.from(d.payload),
30
+ createdAt: Timestamp.fromDate(new Date(d.createdAt)),
31
+ updatedAt: Timestamp.fromDate(new Date(d.updatedAt)),
32
+ }),
33
+ ),
34
+ cursor: key.packFromResult(res),
35
+ }
36
+ },
37
+ })
@@ -5,6 +5,7 @@ import { Database } from '../db'
5
5
  import activitySubscription from './activity-subscription'
6
6
  import blocks from './blocks'
7
7
  import bookmarks from './bookmarks'
8
+ import drafts from './drafts'
8
9
  import feedGens from './feed-gens'
9
10
  import feeds from './feeds'
10
11
  import follows from './follows'
@@ -34,6 +35,7 @@ export default (db: Database, idResolver: IdResolver) =>
34
35
  ...activitySubscription(db),
35
36
  ...blocks(db),
36
37
  ...bookmarks(db),
38
+ ...drafts(db),
37
39
  ...feedGens(db),
38
40
  ...feeds(db),
39
41
  ...follows(db),
@@ -49,6 +49,11 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
49
49
  .whereRef('creator', '=', ref('profile_agg.did'))
50
50
  .select(countAll.as('val'))
51
51
  .as('starterPacksCount'),
52
+ db.db
53
+ .selectFrom('draft')
54
+ .whereRef('creator', '=', ref('profile_agg.did'))
55
+ .select(countAll.as('val'))
56
+ .as('draftsCount'),
52
57
  ])
53
58
  .execute()
54
59
  const byDid = keyBy(res, 'did')
@@ -61,6 +66,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
61
66
  starterPacks: req.dids.map(
62
67
  (uri) => byDid.get(uri)?.starterPacksCount ?? 0,
63
68
  ),
69
+ drafts: req.dids.map((uri) => byDid.get(uri)?.draftsCount ?? 0),
64
70
  }
65
71
  },
66
72
  async getStarterPackCounts(req) {
@@ -38,6 +38,9 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
38
38
  const notifDeclarationUris = dids.map(
39
39
  (did) => `at://${did}/app.bsky.notification.declaration/self`,
40
40
  )
41
+ const germDeclarationUris = dids.map(
42
+ (did) => `at://${did}/com.germnetwork.declaration/self`,
43
+ )
41
44
  const { ref } = db.db.dynamic
42
45
  const [
43
46
  handlesRes,
@@ -46,6 +49,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
46
49
  statuses,
47
50
  chatDeclarations,
48
51
  notifDeclarations,
52
+ germDeclarations,
49
53
  ] = await Promise.all([
50
54
  db.db
51
55
  .selectFrom('actor')
@@ -73,6 +77,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
73
77
  getRecords(db)({ uris: statusUris }),
74
78
  getRecords(db)({ uris: chatDeclarationUris }),
75
79
  getRecords(db)({ uris: notifDeclarationUris }),
80
+ getRecords(db)({ uris: germDeclarationUris }),
76
81
  ])
77
82
 
78
83
  const verificationsBySubjectDid = verificationsReceived.reduce(
@@ -95,6 +100,8 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
95
100
  chatDeclarations.records[i].record,
96
101
  )
97
102
 
103
+ const germDeclaration = germDeclarations.records[i]
104
+
98
105
  const verifications = verificationsBySubjectDid.get(did) ?? []
99
106
  const verifiedBy: VerifiedBy = verifications.reduce((acc, cur) => {
100
107
  acc[cur.creator] = {
@@ -173,6 +180,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
173
180
  trustedVerifier: row?.trustedVerifier ?? false,
174
181
  verifiedBy,
175
182
  statusRecord: status,
183
+ germRecord: germDeclaration,
176
184
  tags: [],
177
185
  profileTags: [],
178
186
  allowActivitySubscriptionsFrom: activitySubscription(),
@@ -27,6 +27,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
27
27
  db,
28
28
  ids.AppBskyNotificationDeclaration,
29
29
  ),
30
+ getGermDeclarationRecords: getRecords(db, ids.ComGermnetworkDeclaration),
30
31
  getStarterPackRecords: getRecords(db, ids.AppBskyGraphStarterpack),
31
32
  getVerificationRecords: getRecords(db, ids.AppBskyGraphVerification),
32
33
  getStatusRecords: getRecords(db, ids.AppBskyActorStatus),